'0-1 scaling on 3d sphere with raycast in unity

I am shooting a raycast on a 3d sphere and i want a method which can get a float number between 0 (center of spehere) to 1 (circumference or outline of sphere) depending upon the raycasthit point (position). How can i achieve this.

I have tried calculating diffrence between the position of sphere and raycast impact point, it works somewhat but is very complex to implement in code.

Is there any other way of doing this or am i missing some math functions or vector functions which can help in this case.



Solution 1:[1]

You could take a Plane which is perpendicular to the ray direction and goes through the sphere's origin and project the hit position onto it using Plane.Raycast like so

...
if(Physics.Raycast(ray, out var hit))
{
    if(hit.collider is SphereCollider hitSphere)
    {
        // This is assuming that your sphere is actually a sphere with uniform local scales
        var sphereRadius = hitSphere.radius * hitSphere.tranform.lossyScale.x;

        var hitObjectCenter = hit.collider.bounds.center;
        var plane = new Plane(-ray.diretcion, hitObjectCenter);
        if(plane.Raycast(ray, out var hitDistance))
        {
            var planeHitPoint = ray.GetPoint(hitDistance);

            var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;

            // now result should be your value between 0 (sphere center) and 1 (exactly on the sphere "edge" - probably never fully reached of course)
        }
    }
}

since our plane uses the ray direction as normal, you could probably also use Plane.ClosestPointOnPlane as approximation like e.g.

var plane = new Plane(-ray.diretcion, hitObjectCenter);
var planeHitPoint = plane.ClosestPointOnPlane(hitDistance);
var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;

the result should be almost identical, not sure which is faster but I guess the raycast is more precise (we are speaking about a difference of somewhat like 0.000001 ;) ).


As a little demo because explaining how this works is a bit complicated ^^

enter image description here

public class Example : MonoBehaviour
{
    public Camera mainCamera;

    private float result;
    private Ray ray;
    private Vector3 hitPoint;
    private Vector3? hitObjectCenter;
    private Vector3 planeHitPoint;

    private void Awake()
    {
        if (!mainCamera) mainCamera = Camera.main;
    }

    private void Update()
    {
        ray = mainCamera.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out var hit) && hit.collider is SphereCollider hitSphere)
        {
            hitPoint = hit.point;

            // This is assuming that your sphere is actually a sphere with uniform local scales
            var sphereRadius = hitSphere.radius * hitSphere.transform.lossyScale.x;

            hitObjectCenter = hit.collider.bounds.center;
            var plane = new Plane(-ray.direction, hitObjectCenter.Value);
            if(plane.Raycast(ray, out var hitDistance))
            {
                var planeHitPoint = ray.GetPoint(hitDistance);

                var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;
            }
        }
        else
        {
            hitObjectCenter = null;
        }
    }

    private Mesh planeMesh;

    private void OnDrawGizmos()
    {
        // if not hitting a sphere do nothing
        if (!hitObjectCenter.HasValue)
        {
            return;
        }

        // lazy initialize plan visualization
        if (!planeMesh)
        {
            planeMesh = new Mesh
            {
                vertices = new Vector3[4]
                {
                    new Vector3(-1, -1),
                    new Vector3(-1, 1),
                    new Vector3(1, 1),
                    new Vector3(1, -1)
                },
                triangles = new[]
                {
                    0, 1, 2,
                    0, 2, 3,

                    // backfaces
                    0, 2, 1,
                    0, 3, 2
                },
                normals = new Vector3[4]
                {
                    Vector3.forward,
                    Vector3.forward,
                    Vector3.forward,
                    Vector3.forward,
                }
            };
        }

        // Visualize ray
        Gizmos.color = Color.green;
        Gizmos.DrawLine(ray.origin, hitPoint);

        // Visualize hit point on sphere
        Gizmos.DrawWireSphere(hitPoint, 0.05f);

        // Visualize Plane
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireMesh(planeMesh, hitObjectCenter.Value, Quaternion.LookRotation(ray.direction));

        // visualize hit point on plane
        Gizmos.DrawWireSphere(planeHitPoint, 0.05f);

        // Visualize distance
        Gizmos.color = Color.red;
        Gizmos.DrawLine(planeHitPoint, hitObjectCenter.Value);

        // print result on screen
        Handles.Label((planeHitPoint + hitObjectCenter.Value) / 2f, $"RESULT: {result:0.000}", new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter });
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1