Skip to content

179. 3D Dungeon – messing with rotations

November 1, 2014

Who would have thought you’d have to worry about rotations in a dungeon?

But when I added more decorations, not just to walls, but to the floor, rotations became important. (I have never been any good at solving those puzzles where you have to find the best match among several rotated patterns, so I always dread rotations).

But I think I got it right. And it makes an interesting post, and gives me a chance to explain the modelMatrix you may have seen in Codea.

Imagine you are standing in a dungeon room. You want to put images on the walls – I call them posters, but they are simple flat 2D images that sit on a surface, like writing, or a Stop sign, or a picture.

You also want to be able to put an image on the floor (eg a  pool of dangerous chemicals that blocks a passage) or the roof.

What does Codea need?

Codea needs

  • the tile position of the “poster” image, ie where in the map it is
  • the direction the image is facing (forward, backwards, left, right)
  • whether the image is vertical or horizontal (ie upright or flat – and if flat, whether floor or roof)

What I mean by facing forward is the usual direction in 3D, looking toward negative z. So left will be facing toward negative x, and so on.

Vertical and forward facing

The natural position of images is vertical, facing forwards (if the camera is facing forwards, as we are assuming). So that requires no rotations. We can just draw the image.

Rotating left and right

A left facing image requires a rotation of 90 degrees to the left (quick quiz – on which of the x, y or z axes?).

If you haven’t done much 3D, you may be surprised to know the rotation is on the y axis, and although you are turning left, it is a positive +90 degree rotation.

(Skip the following explanation if you know it).

Why the y axis? Imagine your image, attached to a pole, like a street sign, facing forwards. To turn it left, we simply rotate the pole. This pole is vertical, running up and down, along the y axis. (If we rotated on x instead, the image would lean forward or backward. If we rotated on z, the image would tip over to the left or the right).

Why do we rotate +90 degrees to turn left? I understand it’s because that’s how mathematicians prefer it, but there is a logical way to remember it. Imagine you are drawing on paper, and you want to draw an image on its left side (rotated to the left). The easiest way to do this is to turn the paper 90 degrees to the right, so now you can draw the image upright (which is obviously a lot easier!). Then when you’re done, you turn the paper 90 degrees back to the left, and there is your image on its left hand side.

So when you do the same thing in Codea, you rotate +90 degrees (to the right), draw the image, then rotate back -90 degrees (using popMatrix). That example helps me understand the +90 degree rotation.

If the image is horizontal, then the 90 degree rotation is on the x axis, not the y axis.

And you might want to rotate the image in both x and y, eg if the image is horizontal and also rotated left by 90 degrees.

Avoiding collisions with the surface

This is bad enough, but the big problem with my poster images is that they need to be just in front of the wall (or floor or roof) surface. If they were exactly on the surface, they would flicker as OpenGL got confused about whether to draw the poster or the surface. A separation of about 1 pixel works well.

If the poster is in front of you, this means adding 1 to the z value, to bring it slightly forward. If the poster is on the left, then the x value needs to be increased by 1. If it is right facing, x needs to be decreased by 1. If the poster is horizontal, it is y that needs changing by 1.

So it looked like I would be stuck with a whole lot of “if” tests, and all sorts of different adjustments to x, or y, or z, depending on the orientation of the image – as well as the rotations I talked about above.

What a mess.

Using modelMatrix

However, I think there is a better way.

Codea has something called modelMatrix, which is a 4×4 matrix.

And it is really amazing.

This little matrix of just 16 numbers can not only store (all at the same time)

  • translation to a different x, y, z position
  • rotations in x, y or z
  • resizing (eg halving your image size)

but when you multiply it by a position vector (x, y, z), it will translate, rotate and resize it correctly!

So instead of all my if tests, it may be easier to just do my translation, rotation, and resizing, then appy the resulting modelMatrix to the vertex positions I would normally use for a forward facing,vertical image, to do everything I need above.

And this is how I did it

  • translate to the position of the image
  • rotate left/right/backward if required
  • rotate from vertical to horizontal if required
  • apply any resizing adjustment (eg multiply image size by 0.05)
  • create vertices for a vertical, forward facing image
  • multiply the vertices by modelMatrix

I wanted to make it easy for the dungeon designer to specify where the posters were placed. So I ask for

  • the image to use
  • a tile position
  • the size (eg 0.05, which is multiplied by the original image size)
  • the orientation (left/right/backward, and vertical/horizontal)

To make the orientation easy to specify, I ask for a two character string, the first is one of v,f,r (for vertical [ie wall], floor or roof), and the second is one of n,s,e,w (for north, south, east, west). North is forwards (towards negative z).

So here is an entry for an image on a left hand wall (vertical, west).

{img=readImage("Dropbox:Dn_StopSignSmall"),pos=vec3(61,10,8),
      size=0.04,orientation="vw"}

And here is the main code that creates a mesh in exactly the right place, 1 pixel in front of the wall.

pushMatrix()
translate(px,py,pz) --translate to the image position

--now the orientation------------
local xx=string.sub(p.orientation,1,1) --get characters off string
local yy=string.sub(p.orientation,2,2)
--the table below helps Codea convert from the characters provided 
--by the user, to actual rotation angles
posterOrientions={["n"]=0,["w"]=90,["e"]=-90,["s"]=180, --on y axis
                  ["v"]=0,["f"]=-90,["r"]=90}  --on x axis 
--look up x and y axis rotation
local rx,ry=posterOrientions[xx],posterOrientions[yy]
if ry then rotate(ry,0,1,0) end --rotate in y
if rx then rotate(rx,1,0,0) end --rotate in x

translate(0,0,1) --move forward 1 pixel in front of wall
scale(p.size) --resize image

--define standard vertices for forward facing, vertical image
v={vec3(-w,-h,0),vec3(w,-h,0),vec3(w,h,0),vec3(-w,h,0)}
--apply modelMatrix to these vertices
for i=1,4 do v[i]=modelMatrix()*v[i] end
m.vertices={v[1],v[2],v[3],v[3],v[4],v[1]}

--the texture mappings are the same for all orientations
t={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)} 
m.texCoords=t 
popMatrix()

I prefer this to a mess of if tests, because it is clearer and simpler.

Exploring modelMatrix

So what does modelMatrix look like after you do translations and rotations?

The 4×4 matrix is made up as follows:

R  R  R  0
R  R  R  0
R  R  R  0
Tx Ty Tz 1

The 3×3 section at top left marked R, holds the rotation and size information.

The first three numbers in the bottom row hold the translation values for x, y and z.

The right hand column is just there to make the arithmetic work (when you multiply the matrix by a vector).

So here is modelMatrix before any adjustments [to see this, just type print(modelMatrix()) ]

1  0  0  0
0  1  0  0
0  0  1  0
0  0  0  1

If we translate to (100,200,300), modelMatrix will change to this

1   0   0   0
0   1   0   0
0   0   1   0
100 200 300 1

No matter what rotations or resizing we do, that bottom row won’t change, it will simply have the translation values.

If we rotate by 90 degrees in y, to turn left, we get

0   0  -1   0
0   1   0   0
1   0   0   0
0   0   0   1

If we leave that rotation as it is, and halve the size of our image with scale(0.5), we get

0   0  -0.5 0
0   0.5 0   0
0.5 0   0   0
0   0   0   1

If you want to play with modelMatrix, you can use this code (NB no draw function needed)

function setup()
    --commment out the lines you don't want, play with the numbers
    translate(100,200,300)
    rotate(15,1,0,0) --rotate in x
    rotate(-45,0,1,0) --rotate in y
    rotate(60,0,0,1) --rotate in z
    scale(0.5)  -rescale image
    print(modelMatrix())
end

And if you want to see if it really works, let’s try placing a poster image at (100,200,300), rotated left, and half size. The image is 20 pixels wide .

function setup()
    translate(100,200,300)
    rotate(90,0,1,0) --rotate in y (as explained previously)
    scale(0.5)  --rescale image
    --we start with the image facing forward, centred on (0,0,0)
    --we'll just convert two vectors to demonstrate
    v1=vec3(-10,0,0) --position of bottom left of image
    v2=vec3(+10,0,0) --position of bottom right of image
    print(modelMatrix()*v1)  --> (100,200,305)
    print(modelMatrix()*v2)  --> (100,200,295)
end

So the image that was centred on (0,0,0) is now centred on (100,200,300), and you can see it has been rotated left and scaled, because the z value runs from +z towards -z, and the change is only 10, half the original value, as we wanted.

Advertisement

From → Programming

One Comment

Trackbacks & Pingbacks

  1. 181. Drawing 3D characters | coolcodea

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: