Announcement

Collapse
No announcement yet.

Calculating Projectile Velocity for Parabolic Arc

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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.

    Comment


    • #3
      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.

      Comment


      • #4
        It would appear a quadratic equation might well be what I need.

        I'm currently reading through this:
        https://blog.forrestthewoods.com/sol...s-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,
        Last edited by Greywacke; 04-21-2017, 06:17 AM.

        Comment


        • #5
          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.

          Comment


          • #6
            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/b7c3d4561dbc12af...daff3e353e.gif

            Far Range:
            https://i.gyazo.com/195338553e2af816...3165d7cb17.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.
            Last edited by Greywacke; 04-21-2017, 06:53 AM.

            Comment


            • #7
              Didn't see your edit previously.

              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).

              Comment


              • #8
                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:Click image for larger version

Name:	17fbfbe35e7acee659da0a96c9dd98a3.png
Views:	1
Size:	15.8 KB
ID:	1126706

                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?
                Last edited by Greywacke; 04-21-2017, 07:34 AM.

                Comment


                • #9
                  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.

                  Comment


                  • #10
                    I hadn't just though about the pitch I was using.
                    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)
                    Last edited by Greywacke; 04-21-2017, 08:11 AM.

                    Comment


                    • #11
                      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.

                      Comment


                      • #12
                        Thanks for your patience btw

                        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);
                        Last edited by Greywacke; 04-21-2017, 08:55 AM. Reason: Added log quote

                        Comment


                        • #13
                          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.

                          Comment


                          • #14
                            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.
                            Last edited by Greywacke; 04-21-2017, 09:15 AM.

                            Comment


                            • #15
                              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!

                              Comment

                              Working...
                              X