Who wants a little maths lesson?

The shader for the tile-based voxel environment is pretty much finished now, so it's time to move on to the next part of the engine - other objects that can be placed within the environment.

`Please login to see this picture.`

Let's start with 2D "billboard" sprites - and just while we figure out the maths, we'll work in two-dimensions only.

So, first of all we need a vector pointing in the direction the camera is facing:

camDirX = cos(cameraAngle)

camDirY = 0 - sin(cameraAngle)

(right now, that's a "unit vector", with a length of 1)

`Please login to see this picture.`

Next we need a vector (purple) perpendicular to that, some distance in front of the camera, and with a length equal to the width of the viewing window. If if we were to draw lines from each end of this vector to the camera, the area inside would be our field of vision.

The distance of this vector from the camera (ie. the length of the orange vector) defines how wide the field of vision is - the shorter the distance, the wider the field of vision (we just pick a value that looks "right").

Next, let's calculate the vector perpendicular to the camera direction vector. To do this, we simply swap the X and Y components, inverting one of them. Note that there are two possibilities - either 90 degrees to the left or 90 degrees to the right - so we'll pick the vector to the right.

perpCamDirX = -1 * camDirY

perpCamDirY = camDirX

`Please login to see this picture.`

Now, we need the vector from the camera to the sprite (white).

camToSprX = spritePosX - cameraPosX

camToSprY = spritePosY - cameraPosY

Next comes the clever part. Instead of using the sprite's position relative to the X and Y axis, we can find its position relative to the camera direction (orange) and the vector perpendicular to that (blue). We can find the length of the light purple vector by calculating the "dot product" of the sprite's position vector and the camera direction vector:

camToSprA = (camToSprX * camDirX ) + (camToSprY * camDirY)

Note that if this value is negative, it means the sprite is behind the camera, and will therefore not be visible.

And we can find the length of the light green vector by calculating the dot product of the sprite's position vector and the vector perpendicular to the camera direction:

camToSprB = (camToSprX * perpCamDirX ) + (camToSprY * perpCamDirY)

To get the gradient of the line from the camera to the sprite (white), we can divide the second of those values (green) by the first (purple).

gradSpr = camToSprB / camToSprA

Next, we need to find the gradient of a line from the camera to the right edge of the field of vision (red). We could do the same as before, but there's an easier way. We know that the length of the orange vector is simply our cameraDistance variable. We also know that the total length of the purple vector is equal to the width of the viewing window, so if we halve that we get the length of vector to the right of the camera direction. Divide one by the other, and we have the gradient of the red line:

gradView = (viewWidth / 2) / cameraDistance

`Please login to see this picture.`

If we divide gradSpr by gradView, we get the position of the horizontal position of the sprite relative to the centre of the viewing window. A value of 0 means the sprite is in the centre of the view. A value of +1 means the sprite is at the right edge of the view. A value of -1 means the sprite is at the left edge of the view. A value outside the range -1 to +1 means the sprite is outside the field of view.

If we add 1 and then divide the result by 2, we convert that to a value in the range 0 to 1, and if we multiply this value by the width of the viewing window, we get the horizontal position of the sprite on screen, in pixels (light mauve).

It's basically the same for the vertical position, although things do start to get complicated if we add another degree of freedom for the camera (roll)... And then there's the small matter of scaling the sprite with distance...