Need good reference for matrix commands (order)

Monkey Forums/Monkey Programming/Need good reference for matrix commands (order)

AaronK(Posted 2012) [#1]
Hi guys. I'm trying to do a simple 2D scene graph implementation and am in some confusion over the matrix commands in Monkey (Scale, Translate, Rotate mainly). What order are these things applied in because it seems that they are applied in reverse order of execution? For instance I expect:

Rotate angle
Translate x,y

To rotate my object around it's origin, then move it somewhere into world space. However it seems I have to do the opposite. The opposite to me seems like it would move the object into world space, then rotate the entire object around the origin.

I'm starting to think that I can use this to easily add the final projection into the screen, but how I was initially thinking of it it was going to require an extra step of that final projection (Multiplying by the final projection matrix). How do you guys handle the screen projection? NOTE: I'm only concerned about 2d here.

Anyway, are there any good references on this anywhere?


JIM(Posted 2012) [#2]
Imagine all you commands are relative to the screen coordinate system.
Now imagine your image on the screen.

If you rotate, your image will rotate (relative to the screen).
If you translate, the image will move (after being rotated)
If you then scale, it will scale everything.

If you want to rotate your image on a circle, translate it first.
Then, when rotating (relative to the screen) the translation will be included in the transformation.

If you have a good understanding of transformation matrices you'll understand this better:

You work in world space.
If you start with the identity matrix, rotating will include the translation (which is 0). Translating will not alter the rotation, so your object, already rotated, gets translated.


AdamRedwoods(Posted 2012) [#3]
Apple has pretty good info:
https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_affine/dq_affine.html#//apple_ref/doc/uid/TP30001066-CH204-SW1


Mojo rotation does not effect translation, but translation effects rotation and scale.
This is so images can use HandleX(), HandleY() to rotate around a different origin point. In my opinion, it doesn't HAVE to be this way, but it is correct methodology when using a Transform() command for affine transformations.

Perhaps a Position(x,y) command would be more useful (faster?) than a Translate(x,y) command. You can make this yourself and get around Transform() by setting the X,Y directly into the matrix without multiplying:
Local mat:Float[] = GetMatrix()
SetMatrix (mat[0],mat[1],mat[2],mat[3],mat[4],x,y)


I believe the terminology behind these two methods, methods being Position or Translation, (in 3D at least) is multiplying the matrix by a vector or by a point.


Also for reference, I wrote up Transform() docs here (and maybe I should write up in the monkey wiki):
http://monkeycoder.co.nz/Community/posts.php?topic=1098&


AaronK(Posted 2012) [#4]
Hi guys, thanks for the input. That Apple link does clear things up a bit. I noticed on that page their example of Rotate, Scale, Translate actually had the scale affecting the translate. When they translated 1/4 the screen across the previous scale means the translation was actually much smaller in physical space.

I also see there's an Affine Transformation which is something different to the CTM. I assume now that Scale, Rotate, Translate work on the CTM and Transform creates an Affine Transformation and applies it to the CTM?

Adam, in your explanation of Transform (see below) you have the * scale in brackets. Is there a reason? Do I need to ignore that when pluggin in values? I can't see why a scale factor would affect rotation?

iy = rotation -sin(a) (* scale)
jx = rotation sin(a) (* scale)


And finally, I think I'm misreading this from your reply
"Mojo rotation does not effect translation, but translation effects rotation"

Because as I mentioned in my original post I'm finding that when I Translate, then Rotate I actually get rotation around the object origin (Not around the translated origin) which seems to me like Translation is not affecting rotation, which is at odd with your quote.

Thanks


AdamRedwoods(Posted 2012) [#5]
I also see there's an Affine Transformation which is something different to the CTM. I assume now that Scale, Rotate, Translate work on the CTM and Transform creates an Affine Transformation and applies it to the CTM?

Correct. Apple's terminology is that affine transform is another matrix that you create to apply to the CTM.

Adam, in your explanation of Transform (see below) you have the * scale in brackets. Is there a reason?
EDIT: I did that assuming that if scale = 1.0 and you have no rotation, then it's 0. I need to re-write that doc since I should use "position" vs. "translation".

I can't see why a scale factor would affect rotation?
because in the transformation matrix, scale and rotation are kept together. I don't know enough of the math to explain this, it's based on Cartesian systems. Play with the numbers and you'll see how scale and rotation are inter-related.

which is at odd with your quote.

I see what you're saying, yeah, that's mind-bending with the order of matrix operations and how the CTM defines what your output is going to be, but yet we see it visually as layered top to bottom.

Key point is that matrix transforms are specific in order of application, and I'm speaking in terms of how the mojo affine commands transform the Current Transform Matrix......
anything you add afterwards, could effect the previous state.
- an early Rotate, is going to be effected by a later Translate.
- an early Scale, is going to be effected by a later Translate.

but....
Mojo rotation does not effect translation, but translation effects rotation and scale.

- an early Translate, is NOT going to be effected by a later Rotate.
- an early Translate, is NOT going to be effected by a later Scale.

Also note, Translation effects Scale. Scale does not effect rotation or translation.

Another note:
This confusion is why I think a Position() command would work better than a Translate() command.


AdamRedwoods(Posted 2012) [#6]
And to add one last thing to make everything confusing:
I noticed on that page their example of Rotate, Scale, Translate actually had the scale affecting the translate. When they translated 1/4 the screen across the previous scale means the translation was actually much smaller in physical space.


I use the term TRANSLATE effects ROTATE and SCALE, but perhaps "effects" isn't the right term, but rather "gets multiplied by". So in essence, rotation and scale do effect translate-- BUT only when translate is taken into consideration into the solving equation-- thus why I restate my original quote that Translate effects Rotation and Scale, but not the other way around, because of the solving equations used.

If I said Rotate and Scale effect Translate, then you would think that adding the scale command after the translate would then scale the translation, but it doesn't.

Is anyone confused yet?


AaronK(Posted 2012) [#7]
Phew! Thanks for that. I was going to ask you about your use of X "effects" Y but you went and reexplained it which helped.

I think this is a crucial question :)
"BUT only when translate is taken into consideration into the solving equation"

The system seems to have a mind of its own when deciding when to solve the equation and apply things and in what order.

I'll do some more experimenting at home and see where I get.

Many thanks.


AdamRedwoods(Posted 2012) [#8]

BUT only when translate is taken into consideration into the solving equation

....which is why I use the statement (and keep adding to it):
Mojo rotation and scale do not effect translation, but translation effects rotation and scale, in respect to order of operations.

Said in another way:
The translation equation USES rotation and scale to figure its output CTM.
Rotation and scale equations do NOT MODIFY translation to figure its output CTM.


marksibly(Posted 2012) [#9]
Hi,

Yep, the matrix ops work 'backwards'.

I like to think of the matrix as a stack of ops, eg:

translate x,y
rotate r
scale h,v

...means 'scale by h,v' THEN 'rotate by r' THEN 'translate by x,y'.

Note that the order of ops is VERY important - swapping 2 ops will have a major effect! You can visualize what effect the order of ops will have by applying them, in order, to a sample point, say (+1,+1).

The trans/rot/scale order above is actually kind of an important one - it's generally the order you want to use to achieve 'useful' transforms. Try visualizing a sprite that is first scaled, then rotated, then translated. This is generally what you want to do, but imagine swapping the rotate/scale steps and you end up with a 'warped' sprite!

It's also not correct correct to say 'translate doesn't affect scale' or anything - it all just depends on what ops happen to be on the stack.

Why is it backwards like this? Basically, because it's far more useful this way - it allows you to place 'more global' ops earlier in your code than 'more local' ops.

And this is generally where you want them - rendering code generally executes spatially 'top down', esp. in the case of scene graphs etc.

So it allows you to write functions like DrawTree or something that may do a bunch of matrix ops to rotate branches into place or whatever and doesn't have to care about what other ops may already be on the stack. These other ops will ultimately transform the tree as a single entity into place on the screen, but DrawTree doesn't have to know about that while it executes.

Similarly, DrawTree might call DrawLeaf with the matrix set so the leaf is transformed into the right place on the tree. And DrawLeaf can in turn use transforms without having to know about this or it's place on the tree/world/screen.


AaronK(Posted 2012) [#10]
Hi Mark, thanks, that all sounds good.

Because I want to support windowed rendering (Where things are rendered into a non-fullscreen sized area on the screen) I can have that final "screen projection" translation and scaling as my first operation correct?

e.g. If I wanted to draw my scene in the middle of the screen but only 1/2 as high and 1/2 as wide I should be able to go

Translate DeviceWidth() / 2, DeviceHeight() / 2
Scale 0.5, 0.5

Then go merrily about my way traversing the tree and they can all do their own Translate, Rotate, Scale's and draws in their parent's coordinate space and everything should work out?

Is there any benefit to handling all the rotate/scale/translate myself and actually call DrawImage with 0's for everything, or should I use DrawImage with handles, positions, scale and rotation parameters?


Cheers

BTW: I found this google books link that helped. It explains the CTM more like building a matrix to transform a coordinate space as opposed to transforming an object. When you look at it this way, if you apply a scale first of say 0.5, then you're making all future translations halved as well.


http://books.google.co.nz/books?id=ivCbtqq6jVcC&pg=PA87&lpg=PA87&dq=quartz+ctm+order&source=bl&ots=JHKpA6InQK&sig=6_mITKPzmXNWQZo-qhOTyjTTSO4&hl=en&sa=X&ei=q_5PT-mOKoOziQeJlaXvDg&ved=0CDkQ6AEwAw#v=onepage&q=quartz%20ctm%20order&f=false


marksibly(Posted 2012) [#11]
Hi,

> Translate DeviceWidth() / 2, DeviceHeight() / 2
> Scale 0.5, 0.5

That's the general idea, though you probably want to use /4 to center.

Also, you might want to include a 'virtual resolution' when scaling, eg:

Translate DeviceWidth/4,DeviceHeight/4
Scale .5,.5
Scale Float(DeviceWidth)/WIDTH,Float(DeviceHeight)/HEIGHT

...or just...

Translate DeviceWidth/4,DeviceHeight/4
Scale Float(DeviceWidth)/WIDTH/2,Float(DeviceHeight)/HEIGHT/2

This allows you to draw to a 'virtual' WIDTH x HEIGHT canvas that ends up in the center of the display regardless of actual device resolution.

You can do other cool global effects too, like spinning the entire window (read backwards!):

rot+=1
Translate DeviceWidth/4,DeviceHeight/4 'translate to center
Scale Float(DeviceWidth)/2,Float(DeviceHeight)/2 'scale to half device size
Translate .5,.5 'back to 0..1
Rotate rot 'spin!
Translate -.5,-.5 'translate to -.5..+5
Scale 1.0/WIDTH,1.0/HEIGHT 'scale to 0..1

With some (careful) translations thrown in, you can do letterboxing too.

> Is there any benefit to handling all the rotate/scale/translate myself and actually call DrawImage with 0's for everything, or should I use DrawImage with handles, positions, scale and rotation parameters?

Probably the only benefit is flexibility - you can do more than just scale/rotate/translate with custom transforms. Apart from 'shearing' though...can't think of anything too useful.

Also, image handles add another wrinkle. The 'correct' order of transforms for a rotated/scaled image with a non-0 handle is:

translate draw_x,draw_y
rotate draw_rot
scale draw_scalex,draw_scaley
translate -handle_x,-handle_y
...can drawimage 0,0 now...

ie: the handle adds another translation that needs to occur *before* the drawing transforms.

The DrawImage commands that include rot, scale take this into account to produce expected results (this is largely why they're there), but if you're doing manual transforms it's something you need to keep in mind.


slenkar(Posted 2012) [#12]
I use the Rotate and Translate commands to parent one image to another

http://monkeycoder.co.nz/Community/posts.php?topic=501


AaronK(Posted 2012) [#13]
Thanks again Mark, just one more thing...

"you probably want to use /4 to center"????

Did you get this because the 0,0 of the drawing is at the top,left? If so, that is OK as in my example I was assuming I would do a translation elsewhere (first) to translate everything around the centre of a "camera" and then translate that into the middle of the screen. However your way might do it in less operations if I build the camera straight into it.

Cheers


marksibly(Posted 2012) [#14]
Hi,

> Did you get this because the 0,0 of the drawing is at the top,left?

Yep, if 0,0 is your center then your way is right. I think. Pretty sure you have the general idea anyway!