Follow the withe rabbit ... all the secret of the matrix here

Hello,

I have dig a little bit in matrix for screen coordinates manipulation with modelMatrix() and I am sure one may be interresting of understanding how this is working…

First of all the matrix return by modelMatrix() is a 4 x 4 matrix { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 }
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

representing the transformation made by call to translate() , scale() or rotate() …
Sx Xy Xz 0
Yx Sy Yz 0
Zx Zy Sz 0
Tx Ty Tz 1
or
Xx Xy Xz 0
Yx Yy Yz 0
Zx Zy Zz 0
Tx Ty Tz 1

where Sx, Sy, Sz are the scale applied to each axes (x,y,z)
and Tx Ty Tz are the scale applied to each axes (x,y,z)
For rotation, it is a bit more complex, the matrix indicate the portion of each X,Y,Z to apply to each axes (x,y,z).
You will note that Sx=Xx , Sy=Yy and Sz=Zz this mean that rotation and scale are sharing the same value, so if you have both a scale and a rotation, you can not separate them looking at the matrix.

So the simple translation matrix is a multiplication (*) by this matrix:
0 0 0 0
0 0 0 0
0 0 1 0
Tx Ty Tz 1

The simple scaling matrix is a multiplication by this one:
0 0 0 0
0 0 0 0
0 0 1 0
Tx Ty Tz 1

And the rotation part is done using multiplication by the rotation matrix, for a rotation on z of angle r :
cos r - sin r 0 0
sin r cos r 0 0
0 0 1 0
0 0 0 1

For an easy use, codea as some quick multiplication by each of those using :
m=m:translate(Tx,Ty,Tz)
m=m:scale(Sx,Sy,Sz)
m=m:rotate(r) (for a rotation on axe z, case for a 2D transformation for example)

To convert a World Wx,Wy,Wz coordinate to Screen coordinate X,Y,Z you just need to multiply the matrix returned by modelMatrix() by the 4 dimentions vector (Wx,Wy,Wz,0)

The formula is :
X=WxSx + WyYx + WzZx + Tx
Y=Wx
Xy + WySy + WzZy + Ty
Z=WxXz + WyYz + Wz*Sz + Tz

It works in 2D, but you just have more 0 in the matrix !

The beauty of Matrix is that you just have to multiply then to merge transformation.
The inverse of a Matrix will do a reverse transformation (only if determinant is non null)

If you want to play a little with it in 2D, here is a sample program :

– matrix

    -- Use this function to perform your initial setup
    function setup()
        iparameter("tx",0,100)   -- transpose X
        iparameter("ty",0,100)   -- transpose Y
        parameter("sx",0,2,1)    -- scale X
        parameter("sy",0,2,1)    -- scale Y
        iparameter("r",0,360)    -- rotation around Z
        iparameter("mode",0,1,0)  -- When 0 show transformation automatic, when 1 show manual calculation
    end

    -- This function gets called once every frame
    function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    tm=matrix()
    tm=tm:translate(tx,ty)
    tm=tm:scale(sx,sy)
    tm=tm:rotate(r)
    if mode==0 then
        translate(tx,ty)
        scale(sx,sy)
        rotate(r)
        stroke(255, 221, 0, 255)
        line(250,200,400,450)
    else
        stroke(91, 28, 241, 255)
        a=toscreen(tm,vec2(250,200))
        b=toscreen(tm,vec2(400,450))
        line(a.x,a.y,b.x,b.y)
    end
    m=modelMatrix()
    resetMatrix()
    resetStyle()
    for i = 1,16 do
      text(i.."="..m[i],100,40*i) 
      text(i.."="..tm[i],200,40*i) 
    end
end

  function toscreen(mat,ve)
    x=mat[1]*ve.x+mat[5]*ve.y+mat[13]
    y=mat[2]*ve.x+mat[6]*ve.y+mat[14]
    return vec2(x,y)
  end

Thanks a lot Pat! You’ve explained exactly the kind of thing i needed to understand, and the way i need it to be explained (noob-ready :wink: ). Btw, I’am not sure from running your example that it is really 2D, but never mind. If you get deeper understanding about this matrix stuff (i’m not talking about quaternions theory, but practical things), please keep sharing, i’m sure it will help a lot of people.

What’s wrong with quaternions? Extremely useful way to implement rotations!

Ha ha! Nothing wrong Andrew! Just i cant view them easily in my mind, although i’ve tried a couple times (wikipedia and Penrose book). I get lost too quickly on this subject (my fault) and don’t practice enough to master the concept. I won’t do it again, i promise… :smiley:

The only thing wrong with quaternions is that they’re harder to grasp conceptually than Euler angles (which have issues, but have the advantage of being easy to understand, conceptually). I still won’t claim I understand quaternions - I just know how to use them - mostly, with trial and error - and why you’d want to (gimbal-lock is a bitch).

Andrew - if you know a resource we could look at to understand quaternions, I’d be interested.

Same for me, I use quaternions, but dont understand them :slight_smile: But they are perfect to create a transition from one rotation matrix to another. With simple matrix transition the object would be stretched, but with quaternions it rotates.

@Bortels, regarding the gimbal lock: If you want to avoid it when rotating an object e.g. with the fingers touch, you can do that easily with matrices too. Just create a global rotation matrix. Then with each touch event you calculate the delta rotation, build a rotation matrix from it and multiply it on the global rotation matrix. Thats it. As you now decide for each delta which direction you go, you have no gimbal lock like you would have with a global x and y variable thats effected by your touch and then builds the rotation matrix. You can find a sample how this works here:

http://twolivesleft.com/Codea/Talk/discussion/comment/12847

Help me understand, are these any faster that the native calls? Are the native calls under the hood doing the same thing?

I’m all for easy to read and undestand code; we obviously can wrap these calls into a function, but at the end of the day, what is more likely to be of help is not just an understanding of the matrix functions (which this did a good job of explaining) but an example of when they could be best used.

It’s just my propblem I think, but it seems that the matrix stuff is cumbersome and doesn’t really “help” more that the native features.

Okay I thought of one idea: exploding meshes!

.@pat Hello. I’ve been playing with 3D and had some difficulties to find how to get the new coordinates of a point after applying these transforms. Of course i finally found out, but it would have been simpler for me if i had read it directly in your great post. Would you mind adding this in your post for real noobs like me? I’am sure it will help:

If you have a point (x,y,z) and want its new coordinates after some transformation, just put the coordinates in a matrix as below, apply the transforms, and read back the results. That’s it!

        m = matrix( 
        x,0,0,0,
        y,0,0,0,
        z,0,0,0,
        0,0,0,0)
        m = m:rotate(theta,0,1,0)
        m = m:rotate(phi,phiAxis[1],phiAxis[2],phiAxis[3])
        x = m[1]
        y = m[5]
        z = m[9]

Interesting…

So, i took your idea and applied it to two triangles on a screen. Rotate and Scale work fine, but for some reason, a m:translate doesn’t do anything! Why would that be?

function setup()
    -- all the unique vertices that make up a cube
    local vertices = {
      vec3(-0.5, -0.5,  0.5), -- Left  bottom front
      vec3( 0.5, -0.5,  0.5), -- Right bottom front
      vec3( 0.5,  0.5,  0.5), -- Right top    front
      vec3(-0.5,  0.5,  0.5), -- Left  top    front
      vec3(-0.5, -0.5, -0.5), -- Left  bottom back
      vec3( 0.5, -0.5, -0.5), -- Right bottom back
      vec3( 0.5,  0.5, -0.5), -- Right top    back
      vec3(-0.5,  0.5, -0.5), -- Left  top    back
    }
    local f1 = {
      vertices[2], vertices[6], vertices[7],    }
    
    fm1 = mesh()
    fm1.vertices = f1
    fm1:setColors(255,0,0,255)
end

function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    perspective(40, WIDTH/HEIGHT,0.1, 500)
   
    camera(10,0,0,  0,0,0,  0,1,0) 
    
    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
   --if (math.floor(ElapsedTime) > (lastTime or 0)) then 
        lastTime = math.floor(ElapsedTime)
        for k,v in pairs(fm1.vertices) do
            --print (v) --to see if any diffs
        
        m=matrix(
        v.x,0,0,0,
        v.y,0,0,0,
        v.z,0,0,0,
        0,0,0,0)
        m=m:rotate(1.5,1,1,1) --WORKS
        m=m:scale(.99,.99,.99)  --WORKS
        m=m:translate(0,.04,0) --NOTHING
        v.x = m[1] +0
        v.y=m[5]+0 --putting .04 here WORKS though...
        v.z=m[9]+0
        fm1:vertex(k, v.x, v.y, v.z)
        end
    --end


    fm1:draw()

end

The 4x4 matrix is actually doing the job of a 3x3 matrix and a translation vector. It’s actually working the wrong way round, as it’s acting on row vectors, but that’s not important.

So you have a matrix M of the form:

A 0
a 1

where A is a 3x3 matrix, a is a length 3 row vector, 0 is a column vector of 0s, and the 1 is the single number in the bottom right corner. This represents a linear transformation (A) followed by a translation (a). To find out the result of applying this to a 3-vector x, we pre-multiply by the row vector (x 1). The result is x A + a (as we’re doing row operations).

When composing operations, you multiply the matrices. In this case, you multiply on the right: to do (A,a) followed by (B,b) you do

A 0  x  B 0
a 1     b 1

This yields

AB     0
aB + b 1

whence by reading off the bottom row, you can actually figure out what the operation (B,b) would have done to the vector a.

But due to the order in which things happen, you actually have to specify the operations in reverse. So to figure out what happens to v under a rotation by angle about axis r, a scaling by scalefactor, and a translation by a, do:

m = matrix()
m:translate(a.x,a.y,a.z)
m:rotate(angle,r.x,r.y,r.z)
m:scale(scalefactor)
m:translate(v.x,v.y,v.z)

and then read off m[13], m[14], and m[15].

(Here’s where that annoying bug in Markdown bites: it inserts a space at the beginning of code. Makes doing aligned text difficult.)

(Double bother. I just properly read the first post in this thread. It’s not quite accurate.)

I’ve read the above and assumed that “v” is the vertex that I am modifying and that a is the translatevector, r is the rotate vector and scalefactor is a size to scale vertices.

Based on that, would it be fair to state that the first set of T/R/S code is “setting up” the effects on a vertex in a “zero coordinate” place, and that last translate with “v” is “moving” the vertex into position based on the results of the matrix math?

I’m afraid I don’t have a clear idea of what you’re thinking there so I don’t know how to explain it in those terms.

ok, let me change what I’m saying…what I am looking to accomplish is to have a triangle t move in a vector and rotate while moving. I am trying to simulate a volumetric explosion. Unfortunately for me, I am not a math major, so I don’t know a formula to do this. So, I am hacking it.

I am rendering a triangle by:

Creating a new matrix() and then
Translate to place T1 (emulating a vector by moving item) --m:translate
Rotating R degrees in the matrix on all axes --m:rotate(angle, 1,1,1)
Scaling it down by S percent in the matrix --m:Scale
Doing ANOTHER translate of triangle T by T’s position

Once done, I read the matrix back into the triangle so that the next operation, the triangle has the effects of the previous matrix math.

One strange side effect: seems that the triangles are rotating around a zero coordinate, and not the place where they last were. Why would that be? I want to have the fragment “spin” as it moves, not rotate around the zero coord. To have to move my rotate BEFORE my first translate?

a rotation matrix rotates around the center. if your object is translated, then you have to move it back to the center, rotate it and then move it back. and dont forget to do this backwards, that means you execute the matrix operations in the other order like you think it should be. thats because of the math behind, so you first think what should happen and then apply it backwards.

e.g. you have an object with its center of rotation at 10, 0, 0. to rotate it around the objects center, you would have to move it to the center, then rotate it and finally move it back. in this case:

translate -10, 0, 0
rotate 30, 0, 0
translate 10, 0, 0

as the math is applied backwards you have to do it like that:

translate 10, 0, 0
rotate 30, 0, 0
translate -10, 0, 0

Don’t the translate steps dissapear from the equation if the object is centered at 0,0,0?

I wouldn’t accumulate the matrices. I would accumulate the translation, rotation, and scaling separately and apply them anew each run.

function setup()
    -- all the unique vertices that make up a cube
    local vertices = {
      vec3(-0.5, -0.5,  0.5), -- Left  bottom front
      vec3( 0.5, -0.5,  0.5), -- Right bottom front
      vec3( 0.5,  0.5,  0.5), -- Right top    front
      vec3(-0.5,  0.5,  0.5), -- Left  top    front
      vec3(-0.5, -0.5, -0.5), -- Left  bottom back
      vec3( 0.5, -0.5, -0.5), -- Right bottom back
      vec3( 0.5,  0.5, -0.5), -- Right top    back
      vec3(-0.5,  0.5, -0.5), -- Left  top    back
    }
    local f1 = {
      vertices[2], vertices[6], vertices[7],    }

    fm1 = mesh()
    fm1.vertices = f1
    fm1:setColors(255,0,0,255)
    m = matrix()
    m = m:translate(1,2,3)
    m = m:scale(2)
    m = m:rotate(90)
    m = m:translate(2,3,4)
        print(m)
    cube = mesh()
    corners = {}
    for l=0,7 do
        i,j,k=l%2,math.floor(l/2)%2,math.floor(l/4)%2
        table.insert(corners,{vec3(i,j,k),color(255*i,255*j,255*k)})
    end
    vertices = {}
    colours = {}
    for _,v in ipairs({
        {1,2,3,4},
        {5,6,7,8},
        {1,2,5,6},
        {3,4,7,8},
        {1,3,5,7},
        {2,4,6,8}
            }) do
        for _,u in ipairs({1,2,3,2,3,4}) do
            table.insert(vertices,corners[v[u]][1])
            table.insert(colours,corners[v[u]][2])
        end
    end
    cube.vertices = vertices
    cube.colors = colours
    nf = 0
    sf = 1
    m = matrix()
    watch("m")
end

function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    perspective(40, WIDTH/HEIGHT)

    camera(10,10,10,  0,0,0,  0,1,0) 
    --[[
    cube:draw()
    translate(2,3,4)
    cube:draw()
    resetMatrix()
    rotate(90,0,1,0)
    cube:draw()
    resetMatrix()
    scale(1,-2,.5)
    cube:draw()
    --]]


    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
   --if (math.floor(ElapsedTime) > (lastTime or 0)) then 
        lastTime = math.floor(ElapsedTime)
        --for k,v in pairs(fm1.vertices) do
            --print (v) --to see if any diffs

        translate(0,nf*.02,0) --NOTHING
        rotate(nf*1.5,1,1,1) --WORKS

        scale(sf,sf,sf)  --WORKS
        m = modelMatrix()
        --[[
        m=m:translate(v.x,v.y,v.z)
        v.x =m[13] +0
        v.y=m[14]+0 --putting .04 here WORKS though...
        v.z=m[15]+0
        fm1:vertex(k, v.x, v.y, v.z)
        --end
    --end
--]]

    cube:draw()
nf = nf + 1
sf = sf * .99
end

Bit of a mess but hopefully you can disentangle it.

OK, I’m trying but I just don’t seem to get it…I’ve tried tens of permutations, but don’t seem to get getting the right one.

Assuming that I have a camera at 5,5,5 pointed at 0,0,0 and I’m using the default “Cube” code laying around (-0.5 to 0.5), all I want to try to do (now) is have each triangle rotate and fly away. At this point, I can get those things to happen independently, but not together.

I’ve got this function stubbed out, but it’s not operational so far… not sure what I need to do to get the “magic” to happen. I really want to understand matrices, but this is just proving that I don’t get it :slight_smile:

function Fragment:draw()
    if self.lifeLeft > 0 then
        self.lifeLeft = self.lifeLeft - 1
    end
    if self.lifeLeft ~= 0 then
        pushMatrix()
        m=matrix() 
        for k,v in pairs(self.thisMesh.vertices) do

---MAGIC HAPPENS HERE
--TRIED m=m:function - usually fails
--TRIED m:function - also usually fails...
            m=m:translate(-v.x,-v.y,-v.z)    --translate based on objects actual coords
            m:rotate(20,0,1,0)    --rotation values
            m:scale(.999)     --scale factor overall. static .999 to fade slowly away or vec3 later
            m:translate(v.x,v.y,v.z)    --translate based on objects actual coords

            --read off "bottom row"...see Andrew_Stacey's forum post on "White Rabbit"
            v.x=m[13] 
            v.y=m[14] 
            v.z=m[15] 

            --Assign new xyz to the vertex for render and storage.
            self.thisMesh:vertex(k, v.x, v.y, v.z)  
        end
        popMatrix()
    end

    self.thisMesh:draw()

end