🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Calculate 3D parabolic trajectory

Started by
9 comments, last by JoeJ 2 years, 7 months ago

I want to implement a sort of mortar/rocket projectile with a parabolic trajectory. All the formulas I find uses time, which I dont care too much about, and X/Y (no Z). I know the origin coordinates of the projectile, and the coordinates where it should hit. How I can use that to calculate the movement?

Advertisement

How does an object move, without time?

Yeah, time is part of that problem, so you can't (or want to) remove it from the equation to solve it.
I think i have a solution where the input is target and turret positions, and launch velocity. Output is angle to orient the turret, and the time it takes to hit it. (There are two angles, a higher and a lower one)
Is this what you want? Code is ugly but i could post it tomorrow.

With a constant acceleration, the simplest possible first-order Euler integrator will give you that.

init:
  position = cannonPosition;
  velocity = cannonDirection * muzzleSpeed;

update:
  position = position + velocity * deltaTime + acceleration * deltaTime * deltaTime / 2;
  velocity = velocity + acceleration * deltaTime;

The precision of this trajectory depends on the size of the time step, because it's a first order integrator.

Also, you might want to add an additional term to velocity, where air drag (proportional to negative constant times velocity) gets added to it.

Acceleration is generally “Up * -9.81” m/s/s.

All variables are 3-vector variables.

enum Bool { True, False, FileNotFound };

I think one issue is there is more than one parabola that will work. I think you need one other bit of starting info, like a maximum height for instance.

Gnollrunner said:
I think one issue is there is more than one parabola that will work. I think you need one other bit of starting info, like a maximum height for instance.

It's a quadratic equation, so it has two solutions but not more. In practice you would usually choose the lower angle because it hits the target faster. The higher angle is better only if the lower trajectory would be blocked by an obstacle.
However, using the higher angle also looks much cooler because it feels more impressive that we still hit the target precisely this way.

hplus0603 said:
The precision of this trajectory depends on the size of the time step, because it's a first order integrator. Also, you might want to add an additional term to velocity, where air drag (proportional to negative constant times velocity) gets added to it.

If there is no air resistance and the target is not moving, we can use the closed form solution and there is no need for iterative integration.
When i did this i used a physics engine to see if angle was computed correctly, which uses some variant of RK4 for integration. The resulting error was very small and i could still hit small targets over big distances. It feels very satisfying to watch the simulation, so i wonder why passive projectiles are not used more often in games. : )

JoeJ said:

Gnollrunner said:
I think one issue is there is more than one parabola that will work. I think you need one other bit of starting info, like a maximum height for instance.

It's a quadratic equation, so it has two solutions but not more.

Hmmmmm? I'm not so sure. That seems non intuitive. I believe there are many charge / trajectory combinations that let artillery hit the same spot.

Edit: although with a fixed charge that seems like it would be the case.

Gnollrunner said:
Hmmmmm? I'm not so sure. That seems non intuitive. I believe there are many charge / trajectory combinations that let artillery hit the same spot.

Ofc. limit to two solutions is given only if all variables are fixed. If launch velocity of your projectile is arbitrary, you could also calculate just that based on a given launch angle.

Thinking of it, it's just a parabola, so there is indeed no need to involve time. I was wrong about that i guess.

It seems to me that one could threat the horizontal component of the parabola as a single “dimension”, and thus reduce the 3D form of the parabola back to the 2D form--and thus the 2D equation.

That is, the equation of the parabola would become something like this (presuming that the vertical dimension is “z”):

z = a + b*(xyDist) + c*(xyDist)^2

Where “xyDist” is simply the length of the horizontal component of the parabola.

As to determining “xyDist”, at its source it stems simply from the horizontal vector between the source and the target--i.e.:

horizontalVector = (targetPos - sourcePos).xy

And since, if I recall correctly, a parabola “travels” in the horizontal at a fixed rate, the vector that gives “xyDist” at a given percentage along the parabola's arc is simply the above “horizontalVector” multiplied by that percentage. Thus said vector and its “xyDist” might be calculated like so:

def getXYComponentForPercAlongParabola(perc):
	xyAtThisPerc = horizontalVector * perc
	xyDist = xyAtThisPerc.length()
	return xyAtThisPerc, xyDist

So, given a target and a source, it seems to me that one can calculate the horizontal vector, find its full length, use that as a known “x-coordinate” in determining an acceptable 2D equation for the parabola, and then use that equation to determine the resultant arc. The 3D calculation would then look something like this:

# Given a percentage along the arc, "perc", and presuming a function
# that calculates a y-coordinate for an x-coordinate on a known 2D parabola,
# "getParabolicHeight":

def get3DParabolicPoint(perc):
	xyAtThisPerc, xyDist = getXYComponentForPercAlongParabola(perc)
	zPos = getParabolicHeight(xyDist)
	xPos = xyAtThisPerc.x
	yPos = xyAtThisPerc.y
	return xPos, yPos, zPos

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

Found my code:

inline float GetLaunchAngle (float vel, float grav, float diffH, float diffV, bool takeHigherAngle = 1)
	{
		vel *= vel;
		float r = sqrt (fabs(vel*vel - grav * (grav * diffH*diffH + 2.0*diffV*vel)));
		if (takeHigherAngle) r *= -1.0;
		float s = vel - r;
		float t = s / (grav*diffH);
		return atan(-t);
	}
	
inline sVec3 GetLaunchVelocity (sVec3 target, sVec3 launchPos, sVec3 gravityDir, float gravity, float launchVel)
	{
		sVec3 diff = target - launchPos;
		float diffV = gravityDir.Dot(diff);
		sVec3 plane = diff - diffV * gravityDir;
		float diffH = plane.Length();
		if (diffH < FP_EPSILON) return gravityDir * -launchVel;

		// todo: handle out of reach and numerical special cases
		float angle = GetLaunchAngle (launchVel, gravity, diffH, diffV);

		// construct velocity vector to apply to projectile...
		sVec3 vv = plane; vv /= diffH;
		vv *= cos(angle);
		vv += gravityDir * sin(angle);
		vv *= launchVel;
		return vv;
	}
sVec3 projTarget; BodyGetCenterOfMass (targetBody, projTarget); // target
sVec3 launchPos; BodyGetCenterOfMass (projectileBody, launchPos); // turret
float launchVel = 40;
sVec3 projVel = GetLaunchVelocity (projTarget, launchPos, sVec3 (0,-1,0), 9.8f, launchVel);
NewtonBodySetVelocity (projectileBody, (float*)&projVel); // body representing the projectile. After setting velocity once, it should fly to the target

I see i did not use time there. But would be useful e.g. to extend it to hitting a target moving at constant velocity. Resulting time could be used to offset the target accordingly, and after some iterations we would get a solution with acceptable error.

Edit: Some more related code…

	inline float PeakTime (sVec3 vel, sVec3 gravity)
	{
		return gravity.Dot(vel) / gravity.SqL(); // sql == gravity.Dot(gravity)
	}
	
	inline sVec3 Displacement (sVec3 vel, sVec3 gravity, float time)
	{
		return (vel * time) + 0.5 * gravity * time*time;
	}

	inline sVec3 Velocity (sVec3 vel, sVec3 gravity, float time)
	{
		return vel + gravity * time;
	}
	
	/*
	v^2=u^2 + 2as

	s = displacement
	u = initial velocity
	v = final velocity 
	a = acc
	*/

I guess this PeakTime() function gives time until the projectile gets at its highest point of the trajectory, and Velocity() gives new velocity from initial velocity after some given time.
Not sure - later i rewrote all this for the use of constant acceleration controllers. But you can derive all this from the given equation in the comment.

This topic is closed to new replies.

Advertisement