As of the last post in this series we had a textured floating cube in space. You could rotate it, move it and zoom in and out. That rotation and movement was performed directly on the cube, with the camera effectively staying in place looking down the z axis. This falls apart as soon as there are multiple objects. We want to be able to move around looking at things from different perspectives. First we need to move and rotate objects in the world, and then we need to be able to move the camera and look at things without them interfering with each other.
As always, click here to give it a try, and here to download it. Here are the previous posts:
Whereas before we just had a single set of position and rotation variables for the state of the cube in _init, now we have a table of cubes. I have created the values for two cubes here, each with its own state.
We then have a camera table, which also has the same data structure. Note, however, that it does not have a z angle. I have not implemented leaning left and right, and so do not need it.
cubes={
{
p={x=0,y=0,z=2.5},
r={x=0,y=0,z=0}
},
{
p={x=0,y=1,z=1.5},
r={x=2,y=0,z=0}
}
}
cam={
p={x=0,y=0,z=-2.5},
r={x=0,y=0}
}
The _update function has had significant changes. Previously, since we were always looking down the z axis, we could move by simply adding or subtracting to each axis in the position table. If we carried on with this approach pressing down would walk backwards when the cart starts, but if you look to the left and press down you would still walk in the same direction. Now we need to use the y angle which is our rotation around the vertical axis. This is the direction we are facing. Applying different variations of the sin and cos of this angle allows us to walk forwards, backwards and strafe.
if btn(🅾️) then
if btn(➡️) then
cam.p.x+=cos(cam.r.y)/10
cam.p.z+=sin(cam.r.y)/10
end
if btn(⬅️) then
cam.p.x-=cos(cam.r.y)/10
cam.p.z-=sin(cam.r.y)/10
end
if (btn(⬇️)) cam.r.x+=.01
if (btn(⬆️)) cam.r.x-=.01
else
if (btn(➡️)) cam.r.y+=.01
if (btn(⬅️)) cam.r.y-=.01
if btn(⬇️) then
cam.p.x+=sin(cam.r.y)/10
cam.p.z-=cos(cam.r.y)/10
end
if btn(⬆️) then
cam.p.x-=sin(cam.r.y)/10
cam.p.z+=cos(cam.r.y)/10
end
end
I’ve added a new section to the end of the _update function which applies some changes to the rotation and position of the two cubes. There’s nothing particularly clever happening here, I’m just taking the sin and cos of the result of the time function to get a loop and give something interesting to look at.
--values selected through the
--methodical process of random
--choice
local t=time()/10
cubes[1].r.y=sin(t)
cubes[1].r.x=cos(t)
cubes[1].r.z=sin(t*0.3)
cubes[1].p.y=sin(t*5)-1
cubes[1].p.x=cos(t*5)+2
t/=4
cubes[2].r.x=sin(t)
cubes[2].r.y=cos(t)
cubes[2].r.z=cos(t*0.9)
I picked apart the move and project function again. This is because I need to run the move independently of projecting in the _draw function.
function move(a,x,y,z)
return {
x=a.x+x,
y=a.y+y,
z=a.z+z,
u=a.u,
v=a.v
}
end
function project(v)
return {
x=flr((v.x/v.z+1)/2*128),
y=flr((v.y/v.z+1)/2*128),
u=v.u,
v=v.v
}
end
Now in the draw function we iterate through the entries in the cube table. For each cube we:
- Rotate it on its own axis so that it doesn’t go off centre.
- Place it in the world using its position variables.
- Subtract the camera position from the cube’s world position. This makes the camera the new origin point.
- Rotate around the new origin, in the opposite direction of the camera to bring the cube “into frame”. This is akin to when you look right, the world appears to move left.
When rotating the world for the camera transform, first we need to rotate on the y axis and then on the x. Otherwise, when you look up and down, your back will always be down the z axis. This means that if you look left or right and then look up, you would actually lean to the side.
After this we project the points and rasterise the triangle using the exact same process as before.
for cube in all(cubes) do
local proj={}
for v in all(verts) do
v=rotate_x(v,cube.r.x)
v=rotate_y(v,cube.r.y)
v=rotate_z(v,cube.r.z)
v=move(v,
cube.p.x,
cube.p.y,
cube.p.z)
v=move(v,
-cam.p.x,
-cam.p.y,
-cam.p.z)
v=rotate_y(v,-cam.r.y)
v=rotate_x(v,-cam.r.x)
add(proj,project(v))
end
for t in all(tris) do
local a=proj[t[1]]
local b=proj[t[2]]
local c=proj[t[3]]
draw_tri(a,b,c)
end
end
There are multiple things you might notice that are not correct now. Firstly one of the cubes is always drawn on top of the other one, even if it should be in front. We need to make sure that the closest object appears on top. Secondly, when you walk into one of the cubes, everything goes haywire. This is down to the depth value becoming 0 and then negative, which throws off all of the calculations. I’ll get to fixing those over the next couple of posts.