Random.onUnitSphere but within a defined range

alt text

I'm trying to instiate a prefabs randomly onUnitSphere, but Instead of instantiating all over the sphere I want them to only instantiate within a small radias on the sphere.

Any Ideas? Thanks - C

Well, `onUnitSphere` is not the right function for this. I would calculate it on my own.

This method is only acceptable for small areas (spot angles). It has a very bad distribution for greater angles (70-90). I just calculate a random point within a unit-circle (radius 1.0). The point get spherically projected onto your sphere. The further away this plane circle is from your sphere center, the smaller the area gets. The tangens just calculates that distance ;).

Here's a sample script. Place it on a gameobject to see the distribution.

function GetPointOnUnitSphereCap(targetDirection : Quaternion, angle : float) : Vector3
{
    var angleInRad = Mathf.Clamp(90.0-angle,Mathf.Epsilon,90.0-Mathf.Epsilon) * Mathf.Deg2Rad;
    var distance = Mathf.Tan(angleInRad);
    var PointOnCircle = Random.insideUnitCircle;
    var V = Vector3(PointOnCircle.x,PointOnCircle.y,distance);
    V.Normalize();
    return targetDirection*V;
}

var angle : float = 45;

function Update ()
{
    for(var i = 0;i<50;i++)
    {
        var V = GetPointOnUnitSphereCap(transform.rotation,angle);
        Debug.DrawRay(transform.position,V,Color.red);
    }
}

That's just a simple method that came to my mind. It would be more accurate if you only use angles. I guess that would be even simpler :D


edit

This one has a way better distribution and works up to 180 (full sphere)

// UnityScript
function GetPointOnUnitSphereCap(targetDirection : Quaternion, angle : float) : Vector3
{
    var angleInRad = Random.Range(0.0,angle) * Mathf.Deg2Rad;
    var PointOnCircle = (Random.insideUnitCircle.normalized)*Mathf.Sin(angleInRad);
    var V = Vector3(PointOnCircle.x,PointOnCircle.y,Mathf.Cos(angleInRad));
    return targetDirection*V;
}

//C# version
public static Vector3 GetPointOnUnitSphereCap(Quaternion targetDirection, float angle)
{
    var angleInRad = Random.Range(0.0f,angle) * Mathf.Deg2Rad;
    var PointOnCircle = (Random.insideUnitCircle.normalized)*Mathf.Sin(angleInRad);
    var V = Vector3(PointOnCircle.x,PointOnCircle.y,Mathf.Cos(angleInRad));
    return targetDirection*V;
}
public static Vector3 GetPointOnUnitSphereCap(Vector3 targetDirection, float angle)
{
    return GetPointOnUnitSphereCap(Quaternion.LookRotation(targetDirection), angle);
}

edit

My GetPointOnUnitSphereCap function takes a Quaternion to define the direction of the cone (spot) in the same way as the spotlight of unity. The cone always points the the local z-axis. In my example i used the rotation of the gameobject it’s attached to.

To get a point relative to another gameobject just add the point to the position of that gameobject. Since the point my function returns is on the unit-sphere (radius 1.0) you may want to multiply it first with your desired radius.

var other : Transform;
var radius = 5.0;
var angle = 45.0;

var V = GetPointOnUnitSphereCap(other.rotation,angle) * radius;
V = other.position + V;

Note: If you want a different direction as the local z axis of your large cube, you can add an empty GameObject as child to your cube and rotate this one into the direction you want.

One simple solution could be to get an unit vector pointing to the center of the radius. And then do two rotations around two orthogonal vectors (use OrthoNormalize). The rotation should be within the radius. (You could probably use onUnitSphere for this).

Another way to go is to take a look at steradians and solid angles:

http://en.wikipedia.org/wiki/Steradian