// Returns remaining movement
    private Vector3 pointerMoveInCylinder(SimulatedPointer pointer, Vector3 movement)
    {
        Vector3 a       = path[pointer.progress / 2];
        Vector3 b       = path[(pointer.progress + 1) / 2];
        Vector3 forward = (b - a).normalized;

        float distance       = movement.magnitude;
        float distanceToExit = distance * Vector3.Dot(b - pointer.position, forward) / Vector3.Dot(movement, forward);

        if (float.IsInfinity(distanceToExit) || distanceToExit < 0)
        {
            distanceToExit = float.PositiveInfinity;
        }
        float distanceToStart = distance * Vector3.Dot(a - pointer.position, forward) / Vector3.Dot(movement, forward);

        if (float.IsInfinity(distanceToStart) || distanceToStart < 0)
        {
            distanceToStart = float.PositiveInfinity;
        }

        if (distance < distanceToExit && distance < distanceToStart)
        {
            pointer.position += movement;
            Vector3 outVec = Vector3.ProjectOnPlane(pointer.position - a, forward);
            if (outVec.sqrMagnitude > pathRadius * pathRadius)
            {
                pointer.position   = a + Vector3.Project(pointer.position - a, forward) + outVec.normalized * pathRadius;
                pointer.overshoot += outVec.magnitude - pathRadius;
                if (pointer.overshoot > (b - a).magnitude * pathOvershootFactor)
                {
                    pointer.progress = -1;
                }
            }
            return(Vector3.zero);
        }
        else if (distance >= distanceToExit)
        {
            pointer.position += forward * distanceToExit;
            Vector3 outVec = Vector3.ProjectOnPlane(pointer.position - a, forward);
            if (outVec.sqrMagnitude > pathRadius * pathRadius)
            {
                pointer.position = a + Vector3.Project(pointer.position - a, forward) + outVec.normalized * pathRadius;
            }
            pointer.progress++;
            pointer.overshoot = 0;
            return(forward * (distance - distanceToExit));
        }
        else
        {
            pointer.progress = -1;
            return(Vector3.zero);
        }
    }
    // Returns remaining movement
    private Vector3 pointerMoveInSphere(SimulatedPointer pointer, Vector3 movement)
    {
        Vector3 center           = path[pointer.progress / 2];
        Vector3 right            = Vector3.Cross(pointer.position - center, movement);
        Vector3 up               = Vector3.Cross(movement, right).normalized;
        Vector3 forward          = movement.normalized;
        Vector3 outDirection     = pointer.progress < (path.Length - 1) * 2 ? (path[pointer.progress / 2 + 1] - center).normalized : Vector3.zero;
        float   outThreshold     = Mathf.Sqrt(jointRadius * jointRadius - pathRadius * pathRadius);
        float   height           = Vector3.Dot(pointer.position - center, up);
        float   halfLength       = Mathf.Sqrt(jointRadius * jointRadius - height * height);
        float   distance         = movement.magnitude;
        float   distanceToBorder = halfLength - Vector3.Dot(pointer.position - center, forward);

        if (distance < distanceToBorder)
        {
            pointer.position += distance * forward;
            return(Vector3.zero);
        }
        else
        {
            Vector3 contactPosition = pointer.position + distanceToBorder * forward;
            if (Vector3.Dot(contactPosition - center, outDirection) > outThreshold)
            {
                // Progress to the next segment
                pointer.position = contactPosition;
                pointer.progress++;
                pointer.overshoot = 0;
                return((distance - distanceToBorder) * forward);
            }
            else
            {
                Vector3 finalPoint = pointer.position + distance * forward;
                finalPoint         = center + (finalPoint - center).normalized * jointRadius;
                pointer.position   = finalPoint;
                pointer.overshoot += distance - distanceToBorder;
                if (Vector3.Dot(finalPoint - center, outDirection) > outThreshold)
                {
                    pointer.progress++;
                    pointer.overshoot = 0;
                }
                else if (pointer.progress >= 2 && pointer.overshoot > (center - path[pointer.progress / 2 - 1]).magnitude * jointOvershootFactor)
                {
                    pointer.progress = -1;
                }
                return(Vector3.zero);
            }
        }
    }
    void Update()
    {
        if (LeapControl.handState == LeapControl.HandState.Pointing)
        {
            input = Vector3.ProjectOnPlane(LeapControl.fingerPoint, Vector3.forward);
            for (int s = minScaleLevel; s <= maxScaleLevel; s++)
            {
                SimulatedPointer newPointer = new SimulatedPointer(path[0], input);
                newPointer.multiplier = Mathf.Pow(scaleFactor, s);
                pointers.Add(newPointer);
            }
        }
        else
        {
            clear();
        }

        foreach (SimulatedPointer pointer in pointers)
        {
            if (Time.time - pointer.startTime > maxDuration)
            {
                pointer.progress = -1;
                continue;
            }
            Vector3 movement = input - prevInput;
            while (movement.sqrMagnitude > 0)
            {
                if (pointer.progress < 0)
                {
                    break;
                }
                if (pointer.progress == (path.Length - 1) * 2)
                {
                    finish();
                    break;
                }
                if (pointer.progress % 2 == 0)
                {
                    movement = pointerMoveInSphere(pointer, movement * pointer.multiplier);
                }
                else
                {
                    movement = pointerMoveInCylinder(pointer, movement * pointer.multiplier);
                }
            }
        }

        if (visualizing)
        {
            foreach (SimulatedPointer pointer in pointers)
            {
                if (pointer.progress < 0)
                {
                    if (visualMarkers.ContainsKey(pointer))
                    {
                        Destroy(visualMarkers[pointer]);
                    }
                }
                else
                {
                    if (!visualMarkers.ContainsKey(pointer))
                    {
                        GameObject newMarker = Instantiate(marker);
                        newMarker.transform.SetParent(visualizationContainer.transform, false);
                        visualMarkers.Add(pointer, newMarker);
                    }
                    visualMarkers[pointer].transform.localPosition = pointer.position;
                }
            }
        }

        pointers.RemoveWhere(pointer => pointer.progress < 0);
        prevInput = input;
    }