public override bool PlanTimeToTarget(Projectile3D projectile3D,
                                          Vector3 initialPosition,
                                          Vector3 targetPosition,
                                          ref float timeToTarget)
    {
        // Allow this planner to only modify an already existing plan, not create a new one.
        if (timeToTarget <= 0) return false;

        // Convert the problem to principal space
        PrincipalProjectile principalProjectile;
        PrincipalSpace3D principalSpace3D = PrincipalSpace3D.Create(projectile3D,
            initialPosition, targetPosition, out principalProjectile);

        Vector2 principalTargetPosition = principalSpace3D.ToPrincipalPosition(targetPosition);

        PrincipalTrajectory principalTrajectory = new PrincipalTrajectory(principalProjectile);
        principalTrajectory.v0 = principalTrajectory.GetInitialVelocityGivenRelativeTargetAndTime(
                principalTargetPosition, timeToTarget);

        // notify helper classes/structs of the trajectory to test
        _trajectoryOverGameObjectHelper.SetupTrajectory(principalSpace3D, principalTrajectory, principalTargetPosition, marginDistance);

        // (Re-)check all the gameobjects until no further intersections are detected, or until
        // an intersecting gameobject is found that can't have a trajectory passing over it.
        int lastIntersectingGameObjectIndex = -1;
        //bool foundAnotherIntersection;
        //do
        {
            //foundAnotherIntersection = false;

            for (int index = 0; index < obstacles.Length; ++index)
            {
                if (index != lastIntersectingGameObjectIndex)
                {
                    float foundTimeToTarget = _trajectoryOverGameObjectHelper.GetTimeToTargetOverGameObject(obstacles[index]);
                    if (foundTimeToTarget >= 0) // found intersection
                    {
                        //foundAnotherIntersection = true;
                        lastIntersectingGameObjectIndex = index;
                        timeToTarget = foundTimeToTarget;
                        if (timeToTarget == 0)
                        {
                            return true; // trajectory over the object is impossible, so give up
                        }

                        // use the new initial velocity in the tested trajecory
                        principalTrajectory.v0 = principalTrajectory.GetInitialVelocityGivenRelativeTargetAndTime(
                                principalTargetPosition, timeToTarget);
                        // notify helper classes/structs of the new trajectory to test
                        _trajectoryOverGameObjectHelper.SetupTrajectory(principalSpace3D,
                            principalTrajectory, principalTargetPosition, marginDistance);
                    }
                }
            }
        }
        //while (foundAnotherIntersection);

        return lastIntersectingGameObjectIndex >= 0; // true iff the plan changed
    }
            // Find a trajectory for which the trajectory's y is at least as high as any
            // of the vertices remaining in _edgesOnPrincipalPlane.
            private float GetTimeToTargetOnIntersection(
                PrincipalSpace3D principalSpace3D, 
                PrincipalTrajectory principalTrajectory,
                Vector2 principalTargetPosition)
            {
                for (int index = 0; index < _edgesOnPrincipalPlane.vertexCount; index += 2)
                {
                    if (_edgeClassifier.CrossesXBound(
                        _edgesOnPrincipalPlane.vertices[index],
                        _edgesOnPrincipalPlane.vertices[index + 1]))
                    {
                        return 0;
                    }
                }

                float timeToTarget = 0.0f;
                for (int index = 0; index < _edgesOnPrincipalPlane.vertexCount; index += 2)
                {
                    Vector2 vertex = _edgesOnPrincipalPlane.vertices[index];
                    if (principalTrajectory.PositionYAtX(vertex.x) < vertex.y)
                    {
                        /*
                        Debug.DrawLine(principalSpace3D.p0,
                                       principalSpace3D.p0 + principalSpace3D.xAxis * vertex.x + principalSpace3D.yAxis * vertex.y,
                                       Color.grey);
                        */
                        timeToTarget = PrincipalTimePlanners.GetTimeToTargetRGivenIntermediatePositionQ(
                                principalTrajectory,
                                principalTargetPosition,
                                vertex);

                        principalTrajectory.v0 =
                            principalTrajectory.GetInitialVelocityGivenRelativeTargetAndTime(
                            principalTargetPosition, timeToTarget);
                    }
                }
                return timeToTarget;
            }