The mathematics of anime homing missiles

I created a prototype missile attack! This required tricky mathematics, which will be discussed in this article.

We’ll talk about cubic bezier curves, perlin noise And rotation minimizing frames.


Let Ichiro Itano proud.

There will be quite a bit of code in the article, because we will focus on geometry. Many people are intimidated by math, but remember that you don’t have to understand everything to use it.

In general, there are two styles of writing movement code.

Iterative code

updates the object’s position incrementally frame by frame, following a process that college professors call

integration

. A popular example of this style is

euler method

at which we calculate the object’s velocity vector and during the time step “push” the position in this direction:

void Update( float DeltaTime ) {
	Vector3 Velocity = CalculateVelocity();
	Vector3 Position = GetPosition();
	SetPosition( Position + DeltaTime * Velocity );
}

Delta is just a mathematical notation for “change”, e.g. “change over time of this Update()”

This is a natural way to create character controls where player input changes every frame, or in complex physics simulations where there is no known real-time analytics solution.

If you know the whole movement in advance, you can use analytic codein which the entire path is calculated from the initial conditions (mathematicians call this parametric curve), then sampled current time. A good example of this is the famous cubic bezier curve:

Vector3 CalcBezierPos( Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t ) {
	float t_ = 1 - t;
	return
		(t_ * t_ * t_)    * P1 + 
		(3 * t_ * t_ * t) * P2 +
		(3 * t_ * t * t)  * P3 + 
		(t * t * t)       * P4 ;
}

If you have ever worked with a vector editor, you will recognize it. Bezier curves are

cubic polynomials

that is, these are the simplest contours with four degrees of freedom: end points P0 and P3 and “control points” P1 and P2 that affect orientation and curvature.

Input value t called input parameter, is a coefficient in the range 0-1. That is, for example, t=0.333 is about a third of the way. To move a point, we simply take the time elapsed since the movement started and divide by the total duration of the movement.

float StartTime;
float Duration;

void Update() {
	float CurrentTime = Time.time;
	float Elapsed = CurrentTime - StartTime;
	if( Elapsed >= Duration )
		SetPosition( P3 ); // мы в конце
	else
		SetPosition( CalcBezierPos( P0, P1, P2, P3, Elapsed / Duration ) );
}

Apart from the position, we can also use the Bezier curve parameters to calculate

derivative at t

, that is, the rate of change. This vector is useful because it

tangent to curve

, that is, points in the direction of movement. To convert it to speed, you need to divide it by the total duration.

Vector3 CalcBezierDeriv( Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t ) {
	float t_ = 1 - t;
	return  (
		( 3 * t_ * t_ ) * ( P1 - P0 ) + 
		( 6 * t_ * t ) * ( P2 - P1 ) + 
		( 3 * t * t ) * ( P3 - P2 ) ;
}

float Velocity = CalcBezierDeriv( P0, P1, P2, P3, Elapsed / Duration ) / Duration;


Speed ​​\u003d Meters Per Second \u003d ( Meters Per T ) / ( Seconds Per T ) \u003d Deriv / Duration

Since I know where the homing missile’s path starts (the launcher) and where it ends (the drawn target), I decided to use a Bézier curve as the basis for the missile’s path.


P1 is placed in front of the fighter and P2 is projected from the target’s surface.

Using an analytical solution simplifies the job because we don’t have to calculate a complex “simulation” of hitting the right place, and we can fine-tune the time between shot and hit, which is more intuitive than second-order physical quantities.

Quite an acceptable effect, but rather boring. You can improve it.

Having created the foundation, you can start decorating. Rocket attacks in the anime move along chaotic paths, increasing the dynamics. We can simulate this by adding noise.

Special effects artists usually use perlin noise – pseudo-random fluctuations, they are chaotic, but smooth. The noise code is too long to post here, but examples are easy to find online.


Look for simplex noise (name of a popular optimized variant).

There is an obvious problem here: I want the offset at the endpoints to be zero so the missile is aligned with the launcher and target. I solved it by multiplying by envelopewhich is zero at the ends and one in the middle.

How to turn the noise function into a displacement distorted along a 3D curve? We will compute two independent noise values ​​and use them as the X and Y components of a rotation-transformed displacement vector tied to the derivative of the Bezier curve.

Vector3 LocalOffset;
float NoiseFreq = 2f; // коэффициент частоты вихляния
float NoiseAmp = 8;   // коэффициент величины вихляния
float Envelope = 1 - (1 - 2 * t) * (1 - 2 * t);
LocalOffset.x = NoiseAmp * Envelope * Noise( NoiseSeedX, NoiseFreq * Elapsed );
LocalOffset.y = NoiseAmp * Envelope * Noise( NoiseSeedY, NoiseFreq * Elapsed );
LocalOffset.z = 0;
Quaternion Frame = Quaternion.LookRotation( CalcBezierDeriv( P0, P1, P2, P3, t ) );
SetPosition( CalcBezierPos( P0, P1, P2, P3, t ) + Frame * LocalOffset );


The rocket wobbles along the red and green arrows

This almost worked, but sometimes there were glitches. The thus calculated vertically fixed rotation blocks tied to the derivative, but often rotate, especially when the path is vertical. Instead, we need so-called Rotation Minimizing Framewhich do not have instantaneous rotation and have only minimal wiggle between directions.


(A) Vertically pinned frames (B) Rotation Minimizing Frames

The methodology for calculating minimizing frames is generally mathematically difficult, but, fortunately, in 2006 an article was published in which surprisingly simple “nudge” frame without rotation called Double Reflection Method. We don’t have to deal with the output, it’s enough to know that it’s cheap and works.

Quaternion Frame; // Инициализируем со значением Quaternion.LookDirection( P1 - P0 );

void UpdateFrame( float t ) {
	// "нормаль" и "касательная" начала
	var n0 = Frame * Vector3.up;
	var t0 = Frame * Vector3.forward;

	// "касательная" цели
	var t1 = CalcBezierDeriv( P0, P1, P2, P3, t ).normalized;

	// первое отражение
	var v1 = CalcBezierPos( P0, P1, P2, P3, t ) - GetPosition(); 
	var c1 = v1.sqrMagnitude;
	var n0_l = n0 - (2 / c1) * Vector3.Dot(v1, n0) * v1;
	var t0_l = t0 - (2 / c1) * Vector3.Dot(v1, t0) * v1;

	// второе отражение
	var v2 = t1 - t0_l;
	var c2 = v2.sqrMagnitude;
	var n1 = n0_l - (2 / c2) * Vector3.Dot(v2, n0_l) * v2;

	// создаём поворот, используя в качестве оси "вверх" нормаль цели
	Frame = Quaternion.LookRotation( t1, n1 );
}


Happened!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *