Skip to content

241. WoT – shooting!

October 24, 2015

I was going to add buildings, but I think I might be able to make the trees work in a game, and also, there is less programming work with trees, because tanks can just drive straight through them, while they have to dodge round buildings.

So I’m going to look at shooting – or more specifically, how we can find out where our shot hits a tank, and what damage it does.

This is a long post, with no pictures, and quite intricate in places, but if you haven’t done this kind of thing before, you should find it interesting, even if you have to re-read parts more than once (I am no different – I take time to understand this stuff, and then make all sorts of mistakes programming it).

Shot trajectory

Unlike World of Warships, tanks fire at fairly close range (in my game, anyway), so I’m going to assume they shoot straight, without any gravity effect, because my research suggests that muzzle velocity is between 700 and 1000 m/s, which translates to 2000-3000 pixels per second, which is extremely fast – something like 35-50 pixels per frame!

I found that that real life damage reduces with distance, however, so having looked at some damage tables, I will initially allow for that very simply, reducing damage linearly by 1/10000 for each pixel travelled (my maximum range is unlikely to exceed 2000 pixels).

I’m going to assume for the moment that the target tank may be travelling as fast as 16 pixels per second, which is a little short of 20 kmh in the real world, which is realistic for WW2 tanks, and is what I’ve been using so far.

Did it hit, and where?

So when I shoot, I will have a starting point, speed, and a vector giving me the direction of the shot.

In between a shot being fired, and it reaching a tank, the tank may have moved, so I can’t do the “hit” calculation at the time the shot is fired. I need to move the shot forward each frame, detect if it is inside (or has passed right through) the target tank, and only then calculate where it hit, and at what angle.

So I have several steps

  1. has the shot reached the tank
  2. if so, exactly where did it hit
  3. and at what angle
  4. what damage did it do?

(Note – for now, I’m ignoring the complications of rotations and translations. I’ll deal with those later).

What did it hit?

I’ll start with part of “what did it hit” first. I have a function based on the Moller-Trumbone algorithm (sounds impressive? I found it by googling) which will tell me if a line intersects a triangle, and if it does, exactly where it hits. It’s an efficient method, and Codea can run it 500-2000 (depending on your iPad) times per frame – if it’s doing nothing else. But of course we are doing other things in the draw cycle.

This function needs to be run for each triangle in the tank mesh, except

  • the barrel – you’re unlikely to damage that because it’s round
  • track and wheels – these meshes are fake 3D and not box shaped, so they are unsuitable for penetration testing – instead, I’ll define a special bounding box, ie a 3D box enclosing the wheels and track on each side, which is 24 triangles.

Has the shot reached the tank?

I start with 348 vertices, which is 116 triangles, and adjusting for the exceptions above, I get just over 100 triangles. So I could possibly afford to test all the vertices each frame while the shot is flying through the air, except that

  • more than one tank may be firing, and
  • a shot fired at one tank may hit a different tank, so I may need to test the vertices in more than one tank to be sure

So I prefer to make it more efficient.

One way of doing this is using the “normals” of the triangles (which are already calculated) to tell me if the shot started out on the same side of the tank as the triangle I’m looking at. If it didn’t, then I can ignore that triangle. This can be done with one dot product (giving a positive result if the triangle is not facing our shot), and allows me to ignore at least half the triangles.

if shotDirection:dot(triangleNormal)>0 then --don't bother testing

Given the shot is travelling at 35 pixels per frame, and my tank is only 20 pixels long and 9 pixels wide, it is probable that it will be in front of the tank in one frame, and behind it in the next, so I may never detect it as being inside the tank when I measure the position at each frame. (This “tunnelling” is a problem in physics engines too, and is why you need to set the bullet property for fast moving objects when using physics in 2D).

So how will I know when the shot reaches the tank?  If I only knew which vertex was closest to me, I could test if the shot had passed it, but it could be changing if the target tank is turning, and I don’t want to be sorting vertices every frame.

A more efficient approach may be to use a 3D bounding box for the whole tank, and test its triangles, using normals to test whether the starting position of the shot is on our side of the triangle (the dot product is negative), and the current position of the shot is on the other side of the triangle (the dot product is positive), which tells me the shot has passed through (or past) that triangle.

I only need to do this for one of the two triangles on each face of the bounding box (because they both face the same way), which means only 9 dot products (2 each for the 3 faces pointing toward us, and 1 for the others) are required per frame, until the shot reaches the tank. Only then do I test all the triangles in the tank in detail.

--test the first triangle on each side of the bounding box
for i=1,36,6 do
    --calc direction from current position, to first vertex
    currDir = (boxVert[i]-currentPos):normalize
    if shotDirection:dot(boxNormal[i])<0 and 
       currDir:dot(boxNormal[i])>0 then
         ShotHasReachedTheTank=true
    end
end

However, I think there is an even faster way to know if the shot is close to the tank – not as accurate, but, given the speed of the shot, probably good enough. Instead of a bounding box, I can use a bounding sphere, in other words, calculate the radius of the tank, which will be the distance from its centre to its diagonal corner, and then I only have to calculate the distance from the shot to the centre of the tank in each frame, and if it is less than this radius, I’ll test all tank triangles. So that is just one dist function, which means I can afford to check all the tanks to see if the shot is hitting them.

   if currentShotPos:dist(tankCentrePos))<tankRadius then
      ShotHasReachedTheTank=true
   end

What did it hit? (part 2)

When the shot reaches the tank, we need to check all the triangles to see if it penetrated.

We have four sets of mesh triangles to test

  • two sets of tracks (for each of which I’ve made a bounding box, as discussed above)
  • the turret
  • the body

First, I’ll use the dot product to tell me if a triangle is facing the shot, and if it isn’t, I’ll ignore that triangle, because it can’t be hit by the shot.

If the triangle is facing the shot, I use the Moller-Trumbone algorithm (it does sound impressive, doesn’t it?) to help me. Here is my function, which includes the dot product test from the previous paragraph (if you want to use this code without that test, just leave out the first line of code).

--o is start of line, d is normalised line direction
--v0, v1, v2 are triangle vertices, n is normal of triangle
--returns hit point, or nil if it misses
function LineIntersectsTriangle(o,d,v1,v2,v3,n) --all vec3
    if d:dot(n)>0 then return end
    local e1,e2=v2-v1,v3-v1
    local p=d:cross(e2)
    local det=e1:dot(p)
    if math.abs(det)<0.001 then return end
    local inv_det=1/det
    local t=o-v1
    local u=t:dot(p)*inv_det
    if u<0 or u>1 then return end
    local q=t:cross(e1)
    local v=d:dot(q)*inv_det
    if v<0 or u+v>1 then return end
    local a=e2:dot(q)*inv_det
    if a>0 then return o+d*a end
end

At what angle did it hit?

This is dead easy, because we can get it from using the dot function on the direction of the shot and the normal of the triangle that we hit (which we are doing in the first line of the LineIntersectsTriangle above). The dot function gives us a value from 0 (hit at 90 degrees, ie to the side) to 1 (direct frontal hit). Gven this angle, and the armour thickness, we can calculate how much armour the shot needs to penetrate, using some trigonometry I haven’t done yet.

World <–> Object space

There is one issue I’ve ignored until now. I’ve been saying I’ll check all the tank triangles against the shot, but I only have one list of tank vertices, with the tank centred on (0,0,0) and facing left. When I draw any tank, I translate and rotate the tank mesh (and all its vertices).

So to compare the tank triangles for a particular tank, I first have to translate and rotate the tank vertices to the actual position of that tank, which is quite a lot of work for the iPad.

But there is an alternative. Instead of modifying the tank vertices, I can modify the position and direction of the shot, because I’m only interested in the position of the shot relative to the tank, and the rest of the scene (trees and other tanks) doesn’t matter. Effectively, I’m asking the question “if my tank is at (0,0,0) and facing left, which is how the mesh is set up, where is the shot coming from?”.

This means subtracting the position of the tank from the position of the shot, effectively translating to the tank position, so now its centre is at (0,0,0).

Then, if the tank is rotated at angle a, I rotate the direction of the shot the other way, by -a. Now I can check the tank triangles (without making any changes to them) with the modified shot position and direction. The advantage is that I only have to adjust one point instead of hundreds.

Technically, my set of tank vertices is said to be in “object space”, with all the positions defined relative to some point (0,0,0), generally chosen as the point around which that object will rotate. So each object will have its own centre point, and then I translate and rotate it to its place in the scene, which is called “world space”.

So when I’m comparing two objects, like a shot and a tank, I can do it all in world space, or I can convert one of them into the object space of the other, as I’m doing above.

I’m sorry if you don’t get this, but it’s quite a complex subject, and this isn’t the place for a full explanation, so I recommend some reading if you want to understand it.

Damage

I think I’ll look at that once I have the hit detection fully up and running, along with the aiming controls, and the shooting visuals.

Controls

But for now, I’m trying to sort out how to control movement,  and I think this is how I’ll do it

  • the tank can turn left and right, and go forward and backward (joystick)
  • the turret can turn left and right (second joystick, left and right)
  • the barrel can rotate up and down (second joystick, up and down)
  • the player can look around in any direction (swipe on screen)
  • the player can select a target, and the turret and barrel will rotate toward it (touch and hold)
  • the player can shoot (shoot button)

I’ll probably need an aiming circle on the screen to show where the barrel is pointing.

This is all quite complicated, so it may take a few days to sort out, and be a bit different. I know that World of Tanks uses the keyboard for a lot of the controls, but of course we don’t have one (and I hate using keys like WASD to steer anyway), so it all has to be done with touch (there is tilting too, but I don’t think that is going to work too well!).

But the graphics already look so great, it’s well worth all the hard work with geometry..

 

 

 

 

 

Advertisement
Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: