Daniel Keast

Pico 8: Projection Part 2

Pico 8, Programming

This is part 2 of me walking through the process of software rendering 3D objects on Pico 8, click here for part 1. In this post I’m projecting a wireframe cube made up of triangles. Click here to give it a try.

The _init function now has 8 vertices for each of the points of the cube, and a new table called tris. This table holds 12 triangles, since each face of the cube is made of 2 triangles. There isn’t much reason for this yet, but rendering triangles is a much simpler process than quads and so will make life easier going forward. Each triangle is 3 points, and each point is an index into the verts table.

function _init()
 --8 points of a cube
 --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},
  {x=-1,y=-1,z= 1},
  {x=-1,y= 1,z= 1},
  {x= 1,y= 1,z= 1},
  {x= 1,y=-1,z= 1}
 }

 --indexes into verts
 --two triangles per cube face 
 tris={
  --front
  {1,2,3},{1,3,4},
  --back
  {8,7,6},{8,6,5},
  --left
  {5,6,2},{5,2,1},
  --right
  {4,3,7},{4,7,8},
  --top
  {5,1,4},{5,4,8},
  --bottom
  {2,6,7},{2,7,3}
 }

 pos={x=0,y=0,z=3}
end

The update method is unchanged.

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

I’ve merged the three functions to project a point from the previous post. This function moves, projects and then converts to screen space in one.

--merged the functions from
--the previous post into one
function project(v)
 --move
 local x=v.x+pos.x
 local y=v.y+pos.y
 local z=v.z+pos.z
 --project to screen
 return {
  x=(x/z+1)/2*128,
  y=(y/z+1)/2*128
 }
end

Now the _draw function first loops through the vertices, projecting each and then storing it in a new local table called proj. I do this because each point is referenced multiple times in the triangles. Projecting them all upfront means that it only happens once at the start of the frame.

I then loop through the triangles table, find the projected vertex for each point, and draw each of its lines.

function _draw()
 cls()
 
 --project all of the vertices
 --first, since each is
 --referenced by multiple
 --triangles
 local proj={}
 for v in all(verts) do
  add(proj,project(v))
 end

 --for each triangle, grab the
 --projected vertex and draw
 --the triangle
 for t in all(tris) do
  local a=proj[t[1]]
  local b=proj[t[2]]
  local c=proj[t[3]]

  line(a.x,a.y,b.x,b.y,11)
  line(b.x,b.y,c.x,c.y,11)
  line(c.x,c.y,a.x,a.y,11)
 end

 print("x:"..pos.x,8)
 print("y:"..pos.y,8)
 print("z:"..pos.z,8)
end