0
\$\begingroup\$

Imagine a rocket is accelerating in a straight line toward PointA, picking up momentum every frame, then suddenly decides it wants to go toward PointB instead.

enter image description here

It can't just turn straight toward PointB and start accelerating, because the new acceleration plus the pre-existing momentum will carry it wildly off-course, and not straight toward PointB.

enter image description here

It could turn exactly 180° around to aim directly away from PointA and thrust until it comes to a stop, then turn toward PointB and start accelerating from scratch. But that's very inefficient, especially if the two points are in generally similar directions.

It would be more efficient to turn and start accelerating in some specific direction that will bleed off the unnecessary momentum but keep any useful momentum, course-correcting so that it is now moving toward PointB.

enter image description here

I'm trying to figure out how to calculate that "specific direction" that the rocket needs to start accelerating toward, in order to efficiently reach PointB. I have the rocket's position, the Vector3 of its inertia, and the Vector3 locations of PointA and PointB, but I'm not experienced enough in Vector Math to totally understand what's needed.

Especially since we're talking about acceleration (not just speed), so the angle will no doubt need to be recalculated each frame as the changing momentum leads to a changing angle toward PointB, etc.

Can anyone help me out?

\$\endgroup\$
  • \$\begingroup\$ This is really a physics question, not a game development question. If you replace "Vector3" with "vector" you could paste the entire question as-is on the physics StackExchange. \$\endgroup\$ – Kevin Jul 10 at 19:44
  • \$\begingroup\$ @Kevin It could be a physics question, but I posted it here because I'm asking in the context of game design, and for all I know there's a "Vector3.CalculateIntercept" method or something already built into Unity. If there's a method or a tool already made for this, that's all I need. I'm not interested in the actual physics, just in finding a solution for my game. \$\endgroup\$ – Nerrolken Jul 10 at 19:46
  • 2
    \$\begingroup\$ If you can change the acceleration direction instantaneously, then this becomes a problem of picking a quadratic Bézier curve, since a point moving with constant acceleration traces exactly this kind of parabolic arc. If you have to turn gradually over time, then this becomes a non-trivial planning problem that you may want to approximate for simplicity. \$\endgroup\$ – DMGregory Jul 10 at 19:46
  • \$\begingroup\$ @DMGregory Interesting, thanks! I'll check out quadratic Bezier curves and see what I can do. \$\endgroup\$ – Nerrolken Jul 10 at 19:48
2
\$\begingroup\$

Let's assume that although it takes time to change our momentum, we're allowed to change our acceleration vector instantaneously. (If we have to model gradually changing acceleration over time, computing the optimal intercept trajectory gets much harder, so I'd argue it's not worth it - the trajectory we'll get this way still has \$C_1\$ continuity, so it looks smooth from the player's eye view, and that's often enough to look plausible).

Using the observation in this previous answer:

Bezier diagram

  • if start at point \$\vec A\$ with a velocity of \$\vec v_1\$,
  • and we have a maximum acceleration given by \$ a_{max} \$
  • then if we project a point \$\vec D\$ out \$ T \$ seconds along our current velocity, (\$ D = \vec A + T \vec v_1\$)
  • and draw a circle around that point with radius \$ \frac {T^2} 2 a_{max}\$
  • ...then we have a viable parabolic trajectory to reach any point on or inside that circle at time \$T\$.

So, imagine this in motion: we launch a tiny green dot from our position, representing the disc of points we could occupy \$T\$ seconds from now. (Or ball - the reasoning applies in 3D too if needed). As the dot moves away it also grows. And ultimately, it grows faster than it moves away, so eventually it will touch even points behind our start position.

Our question then is: when does the border of this disc/ball touch our destination point \$C\$?

ie. At what time \$T\$ does...

$$\begin{align} \| \vec C - \left( \vec A + T \vec v_1 \right) \| &= \frac {T^2} 2 a_{max}\\ \left( \left( \vec C - \vec A \right) - T \vec v_1 \right)^2 &= \left( \frac {T^2} 2 a_{max}\right)^2, \text{let } \vec c = \vec C - \vec A\\ \vec c \cdot \vec c - T \vec c \cdot \vec v_1 + t^2 \vec v_1 \cdot \vec v1 &= \frac {T^4} 4 a_{max}^2\\ 0 &= T^4 \frac {a_{max}^2} 4 - T^2 v_1^2 + T \vec c \cdot \vec v_1 - c^2 \end{align}$$

This is a quartic function - not the simplest to solve, but you can find existing libraries with solvers you can use. (One is suggested in the comments here)

Once you run the solver, you can take the least positive real solution \$ T_*\$ as the earliest opportunity to hit this point.

Then your point \$\vec D = \vec A + T_* \vec v_1\$, and your acceleration vector is \$\vec a = \left( \vec C - \vec D \right) \frac 2 {T_*^2}\$, which should have magnitude \$a_{max}\$ to within our accumulated rounding error, by construction.

Continue accelerating with this vector \$\vec a\$ for duration \$T_*\$ and you'll trace a parabolic curve to your destination point. Your velocity on impact will be along the line joining your destination and the point halfway between \$\vec A\$ and \$ \vec D\$.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

An elegant solution popped into my head:

The core of it is this code that should run each frame:

Vector3 desiredVector = (destination - transform.position).normalized;
Vector3 currentVector = body.velocity.normalized;
accelerationVector = desiredVector - currentVector;

This code picks an acceleration vector that will counteract momentum that is moving us away from the target, and produce a reasonably optimal trajectory.

However, this code is only concerned with counteracting momentum that is moving us away from the target ("undesired momentum"). There's a certain point where we've already killed almost all of the undesired momentum and now we should thrust straight towards the target. To achieve this, we can set a threshold that controls when we stop trying to counteract undesired momentum and simply start accelerating straight towards the target.

A more complete solution looks like this:

[SerializeField] protected float acceleration = 1;
[Range(0, .5f)]
[Tooltip("Controls how soon we stop curving and thrust straight towards the target")]
[SerializeField] protected float velocityThreshold = .2f;
[Tooltip("We consider the target reached when the distance is less than this value")]
[SerializeField] protected float distanceThreshold = 1f;
[SerializeField] protected Vector3 accelerationVector = Vector3.forward;

private Rigidbody body;

void Start() {
    body = GetComponent<Rigidbody>();
}

private void Update() {
    body.velocity += accelerationVector.normalized * acceleration * Time.deltaTime;
    //rotate us to face the direction we're currently moving in
    transform.LookAt(transform.position + body.velocity.normalized * 2f, Vector3.up);
}

public void ChangeDestination(Vector3 newDestination) {
    StartCoroutine(MoveTowards(newDestination));
}

private IEnumerator MoveTowards(Vector3 newDestination) {
    //Counteract momentum that is moving is in the wrong direction
    do {
        UpdateAccelerationVector(newDestination);
        yield return null;
    } while (accelerationVector.magnitude > velocityThreshold);
    //Thrust straight towards target
    while (Vector3.Distance(transform.position, newDestination) > distanceThreshold) {
        accelerationVector = newDestination - transform.position;
        yield return null;
    }
}

/// <summary>
/// Calculate an acceleration vector that will counteract any undesired momentum and move us along an optimal curve
/// </summary>
/// <param name="destination"></param>
private void UpdateAccelerationVector(Vector3 destination) {
    Vector3 desiredVector = (destination - transform.position).normalized;
    Vector3 currentVector = body.velocity.normalized;
    accelerationVector = desiredVector - currentVector;
}

//draw the acceleration vector using gizmos
private void OnDrawGizmos() {
    Gizmos.DrawLine(transform.position, transform.position + (accelerationVector.normalized * 10));
}

You will almost certainly need to adjust the velocity and position thresholds to suit your specific situation, and may eventually need to find a more robust solution than a simple numeric threshold, but this should get you most of the way there.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.