# Thread: Calculating Projectile Velocity for Parabolic Arc

1. 0
Infiltrator
Join Date
Mar 2016
Posts
10

## Calculating Projectile Velocity for Parabolic Arc

I'm having trouble finding the correct formula to calculate the velocity needed to propel an actor with a Projectile Movement Component from its start position to a known target.

The start angle is predetermined, and so is known.
The Target Coordinates are known.
The direction and distance can be established.

What I need to find is the Velocity.

I really need the projectile to travel in an arc towards the target.

I've made several attempts, and my most successful approach is below, which was adapted from a coding example in another forum.

Code:
```			AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(newrot.Pitch, newrot.Yaw, newrot.Roll), SpawnParams);

float distance = (startPos - TargetPosition).Size();
float angleToPoint = atan2(TargetPosition.Z - startPos.Z, (TargetPosition - startPos).Size());
float distanceFactor = 1 / 10000;
float angleCorrection = (PI*0.18) * (distance * distanceFactor);
float velocityX = cos(angleToPoint + angleCorrection) * 3000;
float velocityZ = sin(angleToPoint + angleCorrection) * -1 * 3000 + 500;

FVector Vel = FVector(velocityX, 0, velocityZ);
Proj->ProjectileComponent->SetVelocityInLocalSpace(Vel);```
This gives a semi satisfactory result, but the problem is that this does not take the launch angle into account and almost always either falls short of the target or goes too long for distant and near targets respectively. It is not at all accurate.

I have a few cheat approaches that I may have to employ (such as a UCurve and distance to dictate the height based on the distance to the target), but I know there is a relatively simple formulaic solution.
Unfortunately, mathematics is not my strong point, so I turn to you for help.

2. 1
Champion

Join Date
Jan 2015
Posts
908
You can separate launch speed (V) into horizontal and vertical components:
Vx = V*cos(Theta)
Vz = V*sin(Theta),
where Theta is the launch angle from the horizontal.

Then using s = ut + (1/2)at^2, with a = -g vertically and a = 0 horizontally, putting it together yields the following ugly result:

V = (Sx / cos(Theta)) * Sqrt(g / (2 * (Sx * tan(Theta) - Sz)))

where Sx and Sz are the horizontal and vertical displacements to the target (so Sz is negative if the target is below the firing point). For g, use World->GetGravityZ().

Once you have V, then the velocity vector is just [ V*cos(Theta), 0, V*sin(Theta) ].

Hopefully I got all that right, let me know if you have problems.
I'm working on a marketplace pack to cover all sorts of aiming calculations for trajectories, prediction, AI & aim assist, etc. Won't be done for a while yet though.

3. 0
Enlightened

Join Date
Mar 2014
Posts
3,985
I'm assuming you don't mind that your projectile can launch with a variable speed?

If not that's fine - otherwise it's a Quadratic Equation.

4. 0
Infiltrator
Join Date
Mar 2016
Posts
10
It would appear a quadratic equation might well be what I need.

https://blog.forrestthewoods.com/solving-ballistic-trajectories-b0165523348c

I know this should be simple, but I'm afraid that I find it so slow to read algebraic formula not in code form.

If I have any success, then I will be sure to post it here!

Also; my previous reply doesn't seem to have appeared. Does it need moderating?

Edit: Rewritten response to Kamrann.

So, below is my current implementation of your maths, but I appear to be missing something as the projectiles always fall short.

I'm logging the distance and height difference, and they appear to be correct.

Code:
```			float Theta = 30.f; // launch angle

FVector dir = TargetPosition - startPos;
float Sz = dir.Z;// height difference
dir.Z = 0;
float Sx = dir.Size();// distance

float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz)));

FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));

UE_LOG(LogTemp, Warning, TEXT("Distance =  %f"), Sx);
UE_LOG(LogTemp, Warning, TEXT("Height  =  %f"), Sz);```
Am I misinterpreting what you're saying here?

Then using s = ut + (1/2)at^2, with a = -g vertically and a = 0 horizontally,

5. 0
Champion

Join Date
Jan 2015
Posts
908
Yeah I was assuming variable speed is expected, otherwise with fixed distance, launch angle and launch speed, there won't be any solutions.

It's possible you post is awaiting moderation since you're new, but then I don't see why subsequent posts would get through.

If code is simpler, this engine function is doing essentially the same thing as what I posted. Though it does more besides, so for your case you'd need to extract the relevant bits.

6. 0
Infiltrator
Join Date
Mar 2016
Posts
10
I understand. The velocity of the projectile is what I want to return from this calculation, in order to apply it to the projectile, so no, the speed is not fixed. The angle however is fixed; primarily for the sake of simplicity.
I'm not sure how to adjust my above code to give me the correct velocity.

Not sure if I can embed gifs, but the following links demonstrate the results I'm getting:

Close Range:
https://i.gyazo.com/b7c3d4561dbc12af6e6336daff3e353e.gif

Far Range:
https://i.gyazo.com/195338553e2af816603f9c3165d7cb17.gif

Apologies for the poor quality!
The Close Range shots appear to be on target, but the Far Range ones still fall short.

In order to get this result, I've had to angle the projectile on spawn too. Without doing that , the projectiles were moved barely a few feet. Is this per chance a weight issue?

I'm also getting a 404 error from that link.

Thanks.

7. 1
Champion

Join Date
Jan 2015
Posts
908

Are you working in 2D only (X and Z)? I'd assumed so from the way you set Y to 0 in your first post, but the way you set dir.Z to 0 then take Size suggests maybe you're in 3D?

Just to confirm, I believe your Gravity variable should be +9.8, not -9.8. And you should check that term isn't negative before passing to Sqrt (if it is, it means no solution, though that shouldn't happen for sensible inputs in this case).

Finally, what are the trig functions you're using? C-runtime? In the code you pasted you've passed in the angle in degrees, nearly all trig functions expect input in radians (= degrees * PI / 180).

8. 0
Infiltrator
Join Date
Mar 2016
Posts
10
Yes, I am in 3D. I do indeed set Z to 0 in order to get the distance on the X, Y plane only.

I'm also using World()->GetGravityZ();

I've also just converted the launch angle to radians.
Code:
`float Theta = (40 * PI / 180); // launch angle`
And below is how I'm actually spawning the projectile (note how I'm adding the pitch):
Code:
```
FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(newrot.Pitch + 40, newrot.Yaw, newrot.Roll), SpawnParams);```
Also, these are the sorts of values I'm getting from my debug logs:
LogTemp:Warning: Distance = 5476.720703
LogTemp:Warning: Distance = 2737.744141
LogTemp:Warning: Height = -202.580521
So, as you can see, the projectile spawn is higher than the target position.
That shouldn't be a problem, should it?

I'm getting a satisfactory arc now, but, for distant targets, the range seems too short to reach.

Edit:

Is it possible that the problem lies with the Movement Component (and my misunderstanding of its functionality) rather than the code?

Adjusting the speeds here yields different results, but I would have thought that the velocity would dictate the speed?

9. 0
Champion

Join Date
Jan 2015
Posts
908
The formula assumes launch angle to be relative to the horizontal. So you shouldn't be adding pitch to the lookat rotation. That would indeed cause the projectile to fall short if the target was below the launch position. Just use
Code:
`FRotator(40, lookat.Yaw, 0)`
Also, not sure what you're doing with VelocityOutput, since the way that was coded assumed 2D. I think you need to be passing FVector(V, 0, 0) to the SetVelocityInLocalSpace method on the UProjectileMovementComponent, immediately after spawning your actor.

10. 0
Infiltrator
Join Date
Mar 2016
Posts
10
Made those two changes, but the results stills seem incorrect. The current lead seems to suggest that Initial speed dictates how far the projectiles go, and not the velocity set after spawning.

After adding the below line of code, setting velocity to 0, the projectile now simply falls to the ground, which suggest the results of the calculation was never being applied at all!
Going to have to do some investigating...

Code:
```AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0,0,0));```
Edit:
So I'm getting this from printing part of the output velocity vector
LogTemp:Warning: VelocityOutput = -nan(ind)

11. 0
Champion

Join Date
Jan 2015
Posts
908
VelocityOutput shouldn't exist anymore, at least not in the form it was when you last posted the code.

Maybe just post everything as you have it now.

12. 0
Infiltrator
Join Date
Mar 2016
Posts
10

I've narrowed down where that log is coming from. Bear with me...

So, printing V produces "-nan(ind)"
Code:
` float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz)));`
I attempted to break down and log sections of the equation and these are the results:

Code:
`UE_LOG(LogTemp, Warning, TEXT("V =  %f"), (Gravity / (2 * (Sx * tan(Theta) - Sz))) );`
This produces
LogTemp:Warning: Gravity / (2 * (Sx * tan(Theta)- Sz)) = -0.171647
Code:
`UE_LOG(LogTemp, Warning, TEXT("V =  %f"), FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz))));`
So, when finding the Square root, I get the -nan(ind) message.

This suggests that something is wrong with the calculation of V, or one of the variables which are fed into it.

As for the current code block, it is as follows:
Code:
```			FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();

FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0,0,0));

const float Gravity = World->GetGravityZ();
float Theta = (40 * PI / 180); // launch angle

FVector dir = TargetPosition - startPos; //direction
float Sz = dir.Z;// height difference
dir.Z = 0; // remove hight from direction
float Sx = dir.Size();// distance

float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz)));

FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));

Proj->ProjectileComponent->SetVelocityInLocalSpace(VelocityOutput);```

13. 1
Champion

Join Date
Jan 2015
Posts
908
Try this:
Code:
```FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();
FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);

const float Gravity = World->GetGravityZ();
float Theta = (40 * PI / 180); // launch angle

FVector dir = TargetPosition - startPos; //direction
float Sz = dir.Z;// height difference
dir.Z = 0; // remove hight from direction
float Sx = dir.Size();// distance

float Discriminant = Gravity / (2 * (Sx * tan(Theta) - Sz));
if(Discriminant > 0)
{
float V = (Sx / cos(Theta)) * FMath::Sqrt(Discriminant);

FVector LocalVelocity = FVector(V, 0, 0);

AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
Proj->ProjectileComponent->SetVelocityInLocalSpace(LocalVelocity);
}```
If the discriminant term is negative, it means there are no possible solutions for the given inputs, so you just shouldn't fire. For example, if you tried to fire at something that is at 60 degrees up with a launch angle of only 40 degrees.
If you're finding the discriminant is negative when you think your inputs are valid, then log the value of Gravity to check it's not negative. If it is, negate it.

14. 0
Infiltrator
Join Date
Mar 2016
Posts
10
You were correct about the gravity, and i had spotted that problem after rereading an earlier post of yours.

So, I only ever fire at targets that are lower than the origin's Z axis.

I have to leave the computer for a while now, but I will take a look at your code sample when I return.

Currently, the velocity does appear to be applied (and since fixing the gravity, now prints), but seems to be far lower than it should be.

I really thought this would have been far simpler that I'm making it. =P

Edit: RESULT!

I need to run a few more checks when return later before I confirm, but it now appears to be working! For your sake, and Mine I hope this is true.

15. 0
Infiltrator
Join Date
Mar 2016
Posts
10
Here is my final, working code - for the benefit of anyone else whom it may help.

Code:
```	FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = Instigator;

FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();

const FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(0, newrot.Yaw, 0), SpawnParams);
Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0, 0, 0));

//  == Fixed Angle Projectile Velocity Calculation ==

const float Gravity = World->GetGravityZ() * -1; // Gravity. (Must be a positive value)
//const float Gravity = 980.f; // This is the same as the line above (unless your project settings have been changed)
const float Theta = (40 * PI / 180); // Launch angle in radians (40 being the launch angle in degrees)

FVector dir = TargetPosition - startPos; //direction
float Sz = dir.Z; // Height difference
dir.Z = 0; // Remove hight from direction
float Sx = dir.Size(); // Distance

const float V = (Sx / cos(Theta)) * FMath::Sqrt((Gravity * 1) / (2 * (Sx * tan(Theta) - Sz)));
FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));

Proj->ProjectileComponent->SetVelocityInLocalSpace(VelocityOutput);```
So, all along it was a series of red herrings, and the original code/ algorithm was largely correct.

One thing you may notice, however, that the value of gravity in UE4 is -980. I found I had to move the decimal point as originally I was using 9.81f as gravity, and you will note that the value also must be positive.

Again, thank you for all of the help!