简体   繁体   中英

Random Rotation on a 3D sphere given an angle

This question is in between computer graphic, probability, and programming, but since I am coding it for an Unity project in C# I decided to post it here. Sorry if not appropriate.

I need to solve this problem: given a object on a 3d sphere at a certain position, and given a range of degrees, sample points on the sphere uniformly within the given range.

For example: Left picture: the cube represents the center of the sphere, the green sphere is the starting position. I want to uniformly cover all surface of the circle within a certain degree, for example from -90 to 90 degrees around the green sphere. My approach (right picture) doesn't work as it over-samples points that are close to the starting position.

在此处输入图片说明

My sampler:


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)));
}

and for covering the top half of the sphere I would call getRandomEulerAngles(-90, 90) .

Any idea?

We can use a uniform sphere sampling for that. Given two random variables u and v (uniformly distributed), we can calculate a random point (p, q, r) on the sphere (also uniformly distributed) with:

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);

If we put our reference direction (your object location) into zenithal position, we need to sample v from [0, 1] to get all directions around the object and u in [cos(minDistance), cos(maxDistance)] , where minDistance and maxDistance are the angle distances from the object you want to allow. A distance of 90° or Pi/2 will give you a hemisphere. A distance of 180° or Pi will give you the full sphere.

Now that we can sample the region around the object in zenithal position, we need to account for other object locations as well. Let the object be positioned at (ox, oy, oz) , which is a unit vector describing the direction from the sphere center.

We then build a local coordinate system:

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)

And finally, we can get our random point (x, y, z) on the sphere surface:

(x, y, z) = p * pAxis + q * qAxis + r * rAxis

Try this:

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;
    }
}

Adapted from Nice Schertler, this is the code I am using


    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;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM