Cette question se situe entre l'infographie, la probabilité et la programmation, mais comme je la codifie pour un projet Unity en C #, j'ai décidé de la publier ici. Désolé si ce n'est pas approprié.

J'ai besoin de résoudre ce problème: étant donné un objet sur une sphère 3D à une certaine position, et étant donné une plage de degrés, échantillonnez des points sur la sphère uniformément dans la plage donnée.

Par exemple: Image de gauche: le cube représente le centre de la sphère, la sphère verte est la position de départ. Je veux couvrir uniformément toute la surface du cercle dans un certain degré, par exemple de -90 à 90 degrés autour de la sphère verte. Mon approche (image de droite) ne fonctionne pas car elle sur-échantillonne les points proches de la position de départ.

enter image description here

Mon échantillonneur:


Vector3 getRandomEulerAngles(float min, float max)
{
    float degree = Random.Range(min, max);
    return degree * Vector3.Normalize(new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max)));
}

Et pour couvrir la moitié supérieure de la sphère, j'appellerais getRandomEulerAngles(-90, 90).

Une idée?

0
Vaaal88 31 oct. 2020 à 18:37

3 réponses

Meilleure réponse

Adapté de Nice Schertler, c'est le code que j'utilise


    Vector3 GetRandomAroundSphere(float angleA, float angleB, Vector3 aroundPosition)
    {
        Assert.IsTrue(angleA >= 0 && angleB >= 0 && angleA <= 180 && angleB <= 180, "Both angles should be[0, 180]");
        var v = Random.Range(0F, 1F);
        var a = Mathf.Cos(Mathf.Deg2Rad * angleA);
        var b = Mathf.Cos(Mathf.Deg2Rad * angleB);

        float azimuth = v * 2.0F * UnityEngine.Mathf.PI;
        float cosDistFromZenith = Random.Range(Mathf.Min(a, b), Mathf.Max(a, b));
        float sinDistFromZenith = UnityEngine.Mathf.Sqrt(1.0F - cosDistFromZenith * cosDistFromZenith);
        Vector3 pqr = new Vector3(UnityEngine.Mathf.Cos(azimuth) * sinDistFromZenith, UnityEngine.Mathf.Sin(azimuth) * sinDistFromZenith, cosDistFromZenith);
        Vector3 rAxis = aroundPosition; // Vector3.up when around zenith
        Vector3 pAxis = UnityEngine.Mathf.Abs(rAxis[0]) < 0.9 ? new Vector3(1F, 0F, 0F) : new Vector3(0F, 1F, 0F);
        Vector3 qAxis = Vector3.Normalize(Vector3.Cross(rAxis, pAxis));
        pAxis = Vector3.Cross(qAxis, rAxis);
        Vector3 position = pqr[0] * pAxis + pqr[1] * qAxis + pqr[2] * rAxis;
        return position;
    }
0
Vaaal88 2 nov. 2020 à 16:14

Nous pouvons utiliser un échantillonnage sphérique uniforme pour cela. Étant donné deux variables aléatoires u et v (uniformément distribuées), nous pouvons calculer un point aléatoire (p, q, r) sur la sphère (également uniformément distribué) avec:

float azimuth = v * 2.0 * PI;
float cosDistFromZenith = 1.0 - u;
float sinDistFromZenith = sqrt(1.0 - cosDistFromZenith * cosDistFromZenith);
(p, q, r) = (cos(azimuth) * sinDistFromZenith, sin(azimuth) * sinDistFromZenith, cosDistFromZenith);

Si nous mettons notre direction de référence (l'emplacement de votre objet) en position zénithale, nous devons échantillonner v de [0, 1] pour obtenir toutes les directions autour de l'objet et u dans [cos(minDistance), cos(maxDistance)], où minDistance et maxDistance sont les distances angulaires de l'objet que vous souhaitez autoriser. Une distance de 90° ou Pi/2 vous donnera un hémisphère. Une distance de 180° ou Pi vous donnera la sphère complète.

Maintenant que nous pouvons échantillonner la région autour de l'objet en position zénithale, nous devons également prendre en compte d'autres emplacements d'objet. Laissons l'objet positionné en (ox, oy, oz), qui est un vecteur unitaire décrivant la direction à partir du centre de la sphère.

Nous construisons ensuite un système de coordonnées local:

rAxis = (ox, oy, oz)
pAxis = if |ox| < 0.9 : (1, 0, 0)
        else          : (0, 1, 0)
qAxis = normalize(cross(rAxis, pAxis))
pAxis = cross(qAxis, rAxis)

Et enfin, nous pouvons obtenir notre point aléatoire (x, y, z) sur la surface de la sphère:

(x, y, z) = p * pAxis + q * qAxis + r * rAxis
1
Nico Schertler 1 nov. 2020 à 06:48

Essaye ça:

public class Sphere : MonoBehaviour
{
    public float Radius = 10f;
    public float Angle = 90f;

    private void Start()
    {
        for (int i = 0; i < 10000; i++)
        {
            var randomPosition = GetRandomPosition(Angle, Radius);
            Debug.DrawLine(transform.position, randomPosition, Color.green, 100f);
        }
    }

    private Vector3 GetRandomPosition(float angle, float radius)
    {
        var rotationX = Quaternion.AngleAxis(Random.Range(-angle, angle), transform.right);
        var rotationZ = Quaternion.AngleAxis(Random.Range(-angle, angle), transform.forward);
        var position = rotationZ * rotationX * transform.up * radius + transform.position;

        return position;
    }
}
1
Евгений Глебов 31 oct. 2020 à 18:51