- Home /
Deriving a velocity from a camera movement
or "Can I Interpret Angular Movement as Linear Velocity" or "How I learned to stop worrying and love Mathematics".
Hi again, Unity Answers fellas.
I'm currently trying to simulate a modern battle tank's Fire Control System in Unity. Here's a summary of what I have so far:
The sight is pointed at a target (aimpoint)
A laser (raycast) is shot out to get the range
The aimpoint is adjusted upwards depending on distance, muzzle velocity, and tank tilt
Lead is added depending on the tank's current velocity as well as the target's
The turret slews around and elevates the gun to center on the final aimpoint
The shot is fired and hits the target. On the way!
As it stands, it's already pretty good. Hitting a moving target (while moving yourself) at less than a 1000m is a cakewalk while doing the same thing at 3000+ meters might make you sweat a little bit. But I want to make it better. Turn your attention to number 4.
"Lead is added depending on the tank's current velocity as well as the target's"
This literally means I get the velocity of the rigidbody attached to the target and do my calculations from there. It works fine and dandy if the Gunner is an NPC who can store a reference to the bad guy in his scripts somewhere and just take a peek at his velocity now and then. But what if the Gunner is the player?
Here's a shot of my totally-WIP Gunner's Primary Sight Extension. The red numbers below the crosshairs are the range to the target. The light gray cross is the mouse cursor.
The GPSE, as a turret, isn't controlled using the traditional Mouse Look. Instead, the distance between the cursor and the center of the screen is calculated and the view rotates accordingly. If any of you have played M1 Tank Platoon 2 (I have) or Steel Beasts (I haven't), you'd know that the lead is actually calculated from the rotation speed of the viewport and the distance to the target. Hence, once you are able to keep a moving target in the center of the screen, the turret will automatically add lead, ensuring a hit (unless the target suddenly changes direction while the shell is in flight).
So here's what I want to know:
"Can I conjure up a velocity value based on the movement of the center of the screen and taking into account the distance and magnification?"
or
"If I aim my camera at an imaginary point a specific distance away from me and pan the camera slowly, can I get that moving imaginary point's velocity?"
Answer by sevensixtytwo · Sep 15, 2014 at 03:42 PM
Whelp, here I go again. I'll just leave this here in case another person tries to implement a lead-assist for ballistic projectiles. Note that you'll need to use it with this script on the wiki.
Calculate Lead For Projectiles
Some of us might know this dandy equation:
V=wr
where V = Linear Velocity, w = Angular Velocity and r = radius
Yay for astronomy! So then it becomes a matter of finding the r and the w. The r was pretty easy. The hitpoint.distance of the raycast would serve. w on the other hand, was a bit trickier...
I had to overhaul my mouse tracking code for the turrets, making the rotation speed dependent on the current field of view (magnification).
//our target's "velocity", moveX and moveY are the speeds the camera is rotating at
//minor confusion, the moveX is actually the camera's Y-rotation and vice-versa.
//for Z, see later.
var trackingVelocity = Vector3(moveX,-moveY,Z);
Before we can use it, it has to be converted to world space since it's currently relative to the camera.
trackingVelocity = viewport.transform.TransformDirection(trackingVelocity);
And then finally multiply trackingVelocity (w) by the distance (r) to get the final linear velocity (V). Now it can be used as the targetVelocity component of my CalculateLead function.
var targetVelocity = trackingVelocity * hit.distance;
finalAimpoint = FirstOrderIntercept(shooterPosition,shooterVelocity,shotSpeed,targetPosition,targetVelocity);
Works like a beast, though I am still working on the Z component of the velocity. I'm trying storing the hit.distance of every frame in LateUpdate and comparing it to the new one in Update. More updates soon. Unless anyone else pitches in, I'll mark this as answered once I finish it.
UDATE: Mission accomplished. I ended up saving the hit.point at the end of every frame and then using:
Z = (previousHit - newHit).magnitude * Time.deltaTime;
targetVelocity.z = Z;
The end result is much more accurate. Targets moving 40MPH at 1000+ meters can be hit consistently but it still requires a steady hand since any jerk or twitch can send the shot flying wildly off target. Perfect.