0
votes

Goal: Render distance lines between two points on the surface of a mesh-based primitive (i.e. sphere, cube, etc).

Lines drawn on surface of sphere Current Solution: Iteratively traverse distance line between two end points and "reverse" raycast through this point somehow. Since the distance line directly connects both vertices through the mesh, the according points on the mesh surface are required.

Ray ray = new Ray();
RaycastHit raycastHit;
ray.origin = posOnDistanceLine;
ray.direction = raycastNormal.normalized;
// Reverse ray since we can't raycast from inside the mesh
ray.origin = ray.GetPoint(1);
ray.direction = -ray.direction;

Lines are then drawn using Unity's LineRenderer which is being populated with positions of vertices whenever a change in normals (to previous raycast) is identified.

Issues:

  • Horrible performance (as 100 rays are cast whenever the end points move).

  • Solution doesn't always work and produces unexpected, jagged lines / points.

Question: Is there a better approach to implement this?

1

1 Answers

1
votes

If you want to optimize the solution, you might need to make a script for each primitive and utilize the primitive-specific math.

For example, instead of casting rays, you could simply get the radius of the mesh and put the line vertex at the radius * directionFromCenter.

Here is an example script:

[RequireComponent(typeof(LineRenderer))]
public class SurfaceLine : MonoBehaviour, IPointerClickHandler
{
    [SerializeField] private float pointsPerUnit;
    [SerializeField] private MeshFilter mesh;

    private Vector3 start;
    private Vector3 end;
    private LineRenderer lineRenderer;

    void Awake()
    {
        this.lineRenderer = this.GetComponent<LineRenderer>();
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        if(eventData.button == PointerEventData.InputButton.Left)
        {
            this.start = this.transform.InverseTransformPoint(eventData.pointerCurrentRaycast.worldPosition);
            this.Render();
            return;
        }

        if(eventData.button == PointerEventData.InputButton.Right)
        {
            this.end = this.transform.InverseTransformPoint(eventData.pointerCurrentRaycast.worldPosition);
            this.Render();
        }
    }

    private void Render()
    {
        var distance = Vector3.Distance(this.end, this.start);

        var direction = (this.end - this.start).normalized;

        var numPoints = Mathf.FloorToInt(distance * this.pointsPerUnit);

        numPoints = Mathf.Max(numPoints, 2);

        this.lineRenderer.positionCount = numPoints;

        var positions = new Vector3[numPoints];

        var stepInDir = direction * (distance / (float)numPoints);

        for(int i = 0; i < numPoints; i++)
        {
            positions[i] = this.start + i * stepInDir;

            var dirFromCenter = positions[i] - this.mesh.mesh.bounds.center;

            positions[i] = this.mesh.mesh.bounds.center + dirFromCenter.normalized * (this.mesh.mesh.bounds.size.x / 2.0f);
        }

        positions[positions.Length - 1] = this.end;

        this.lineRenderer.SetPositions(positions);
    }
}

This seems to perform okay in an update loop too. The down side is of course that the solution is not generic. You will need a strategy per primitive.

Alternatively you can at least leverage the pointsPerUnit concept in the script to control the resolution of your line and stick with ray casting. I think the strange artefacts you are seeing is a result of too high point density. Making the points per unit of distance consistent may have better performance too.

Here is the result for the script above: enter image description here