This Pico 8 cart projects a square in 3D space onto the screen. Click here to give it a try.
The first step is creating the 3D points, and a position in space to view them in the _init function:
function _init()
--4 points of a square
--these are in world space
--i.e. -1 to 1
verts={
{x=-1,y=-1,z=-1},
{x=-1,y= 1,z=-1},
{x= 1,y= 1,z=-1},
{x= 1,y=-1,z=-1}
}
pos={x=0,y=0,z=3}
end
Then in _update give the user the ability to move the position around:
function _update60()
if (btn(➡️)) pos.x+=.1
if (btn(⬅️)) pos.x-=.1
if (btn(⬇️)) pos.y+=.1
if (btn(⬆️)) pos.y-=.1
if (btn(❎)) pos.z+=.1
if (btn(🅾️)) pos.z-=.1
end
This function adds the position to its vertex argument, moving it into place.
--move the vertex based on
--user input
function position(v)
return {
x=v.x+pos.x,
y=v.y+pos.y,
z=v.z+pos.z
}
end
This function is the actual projection, it gives the illusion of depth by moving points closer to the centre of the screen as they move further away. It is surprising to me just how incredibly simple this is to produce such a cool effect. I’d always assumed that there was some more magic going on.
--divides x and y by z
--
--higher z = further away
--higher z = lower x,y value
--higher z = closer to center
--
--i.e. points converge as they
--approach the vanishing point
function project(v)
return {
x=v.x/v.z,
y=v.y/v.z
}
end
This function then converts the projected points into coordinates on the screen. The Pico 8 screen is a 128x128 square.
--convert a point in world space
--to screen coordinates
--
-- (-1 .. 1 ) + 1
-- = ( 0 .. 2 ) / 2
-- = 0 .. 1 * 128
-- = 0 .. 128
function screen(v)
return {
x=(v.x+1)/2*128,
y=(v.y+1)/2*128
}
end
The _draw function is then very simple. Clear the screen, project each point, and set the pixel on the screen. Nothing else to it.
function _draw()
cls()
for v in all(verts) do
v=position(v)
v=project(v)
v=screen(v)
pset(v.x,v.y,11)
end
print("x:"..pos.x,8)
print("y:"..pos.y,8)
print("z:"..pos.z,8)
end