Planet Tech Art
Last update: December 21, 2014 04:04 AM
December 19, 2014

Adventures in the 4th dimension

In our last discussion of 3d math, we started to plumb the mysteries of the matrix. Along the way we discovered two important facts: First, that it’s possible to write an article about matrices with only the merest smidge of a Keanu Reeves mention and second (almost as important), that matrices are just a convention for applying dot products in series. We walked through the derivation of matrices for a series of dot products and shows how hat simple operation allows you to do rotations in two and three dimensions.

Naturally, any TA reading this will be knows there's more. We all know that the matrices we’re most familiar with — the transform matrices that drive animation and modeling — do more than rotate. So this this time out we’re going talk about how translation — spatial offsets — can be packed into matrices.  And we're going to do it in a truly brain bending way.  Sort of.
If none of this sounds familiar, you may want to return to the previous post in the series before continuing.

After all of the time we’ve spent with dot products in this series, one thing we should remember is that dots are additive — if you dot two vectors, you sum up all of the products. “Additive” is a nice quality to have if we’re thinking about adding translations to our matrices  It suggests that maybe we can use the additive-ness of dot products to teach our matrices how to do translations as well as rotations.

Multiplying a vector against a matrix, you’ll recall, is nothing more than stringing together a set of dot products between the vector and the columns of the matrix. So, putting together the fact that dots are additive and the fact that matrix multiplication uses dots, it seems logical that we can just stick our translation right onto the bottom of the matrix.  By dropping it down at the end of the matrix columns, we'll add it add it to our results. One important side effect that we’ll have to worry about is that this will break the pretty symmetry we noted last time whereby every matrix row is an axis in the matrix's local coordinate system.  However we’ll deal with that after we know it works.

To keep things simple, let’s start with a rotate matrix that doesn’t do any, you know, rotating — a matrix that works but leaves incoming data unchanged. That'll make it easier to see when our translations kick in. The correct math moniker for this do-nothing matrix is an identity matrix (as in the otherwise-inexplicable MakeIdentity command in Maya) and it’s just a set of rows that match the default XYZ axes:

100
010
001

I won’t bother with the math here, but if your work it out for yourself you’ll quickly see that dotting the columns of this matrix in turn against any vector returns the original vector unchanged.

Next, we’d like to add some extra information to this matrix to include a translation. Since we know our dots are going down the columns, if we tack on an extra row we should be getting a new value added to the output: hopefully, the translation we want. Adding an extra row for translation gives us a 4X3 matrix like this (with an example translation of [1,2,3] :
100
010
001
123

For future reference, matrices are usually described as ‘rows by columns’; in the last article we derived our matrix first as a 2X2 then as a 3X3 matrx. Most transformation matrices in 3d software are 4X4, for reasons that will become apparent shortly, but Max users will find this 4X3 format familiar — Maxscript makes extensive use of 4x3 matrices for object transforms.
So now we’ve got a test matrix that should offset our initial value by  [1,2,3]. However, we immediately run into a problem: as we try to multiply our vector against this matrix. The columns now have 4 items but our vector only has 3. How can we sum up? Dot products require that both vectors being dotted have the same number of products, as you can see here:

[1,1,1] dot [1,0,0,1] = (1 * 1) + (1 * 0) + (1 * 0) + (??? * 1) 

To make this work, we are going to need to extend our vector to grab the translation values from the new matrix row. It needs to become a 4-dimensional vector. The fourth dimension! Trippy! Cue theremin music....
We've actually dimension jumped before, while working through rotation matrices. We could borrow the same tactic we used in the last post when we moved from a 2-D matrix to a 3-D matrix by just taking on a zero to our vector. This seems like a natural idea, since we know that the 2-D vector [X,Y] is equivalent to the 3-D vector [X,Y,0]. So let’s see what happens if we do the dot products:

[1,1,1,0] dot [1,0,0,1] = (1 * 1) + (1 * 0) + (1 * 0) + (0 * 1) = 1
[1,1,1,0] dot [0,1,0,2] = (1 * 0) + (1 * 1) + (1 * 0) + (0 * 2) = 1
[1,1,1,0] dot [0,0,1,3] = (1 * 0) + (1 * 0) + (1 * 1) + (0 * 3) = 1

Not what we were hoping for: our result is still  [1,1,1]. What happened?

The extra zero has allowed us to do the dot product — but it's  also zeroing out the translation we are trying to add. Evidently zero is not what we want here (this is not just an misstep, though: we'll come back to those zeroes later).
For now, the fix is pretty obvious, even though it’s much less obvious how to what the fix is supposed to mean. If we turn that final zero into a one, we’ll get our translation added to the original value:

1,1,1,1 dot 1,0,0,1 = (1 * 1) + (1 * 0) + (1 * 0) + (1 * 1) = 2
1,1,1,1 dot 0,1,0,2 = (1 * 0) + (1 * 1) + (1 * 0) + (1 * 2) = 3
1,1,1,1 dot 0,0,1,3 = (1 * 0) + (1 * 0) + (1 * 1) + (1 * 3) = 4

There, at last, is the translation we are looking for; our vector [1,1,1,1]has become [2,3,4], reflecting the offset in the last row of the matrix.

Well, it’s nice to get the right result, but this still leaves us with a bit of a conundrum.  I know what [2,3,4] means. But what the heck is that last coordinate doing there? Did we just make it up?

X,Y,Z,WTH?

You may remember from our original discussion of dot products that vector is actually a very general term, encompassing any bundle of numbers. In tech art we’re used to thinking of vectors as XYZ bundles in 3-D space, but a vector can just as easily be something else — such as your weekly Starbucks expenditure, which is how we started down this road in the first place. 3-D points can be represented by vectors — but so could any bundle of 3 numbers which formed part of a linear equation; say, the value of the dollar, the euro and the yen on a given day. Dot products and matrices work the same way regardless of the subject matter. So, one thing we know already is that all 3-D points are vectors, so to speak, but not all vectors are 3-D.
Not only did he pioneer analytical geometry, he seems to have invented the Mall Santa look too.

The vectors we use in graphics, of course are usually Euclidean vectors: a set of 3 numbers which represent a spatial offset in the X,Y and Z spatial dimensions. The word vector comes from the Latin word for one who carries: the vector is the spatial difference between two positions. We get misled by the fact that programming languages usually use the algebraic name vector (as “bundle of numbers”) for the data type we use to hold the geometric Euclidean vector. The fact that algebraic vectors and Euclidean vectors share the same noun while meaning different things is, to put it mildly, annoying.  With the goofy stuff we're getting in to, I personally would be happy to skip these minor surprises.
To understand what that weird extra number, however, we have to add in a third concept: the Euclidean point. Which is also frequently represented in code by something called "vector" but which is represents a different idea. Sigh. We will have to distinguish between two things which look similar when written down or stored as vectors in computer memory but which actually mean two different things. Up till now we've talked about vectors and points as if they were interchangeable, but to make the translation matrix work we need to differentiate them.

The default Euclidean vector is a purely relative quantity. It represents a change in position. That's why the vector that gets you from [0,0,0] to [1,1,1] and the vector that gets you from [8,8,8] to [9,9,9] are the same: the vector proper has no location of it's own. You can think of it as a surface normal, which tells you which way a surface is facing without telling you anything about where the surface actually is, or the direction of a directional light which illuminates along a direction and which doesn't actually reside anywhere in 3-D space.

On the other hand a Euclidean point is an actual location in space. The point [1,1,1] is just that : the location [1,1,1]. it has no 'facing' or 'direction' the way a surface normal does - and it's not the same as any other 3-D point. It's an address, while a regular vector is an offset.

That's where our fourth coordinate comes in. The fourth coordinate in our example tells us if we’re dealing with a Euclidean point or a Euclidean vector, that is, if we are dealing with something that can be translated or not.  If the last coordinate is a 1, the data is a point which can be transformed (moved, rotated and scaled). If the last coordinate is a 0, the data is a vector, which can be rotated and scaled but not moved. The last number is known as the homogeneous coordinate, although most people refer to it as the “W” component by analogy with X Y and Z.  Although I kind of wish they had just wrapped it around back to A, or started at W, or something. XYZW? Like I said, I'd like to concentrate on the mind-warping concepts more and the annoying terminology less.  Oh well.

Homegeneophobia


If you’re practically minded, all you really need to know today is that a W of 1 is a point and a W of 0 is a direction. If you are especially literal minded, in fact, this next bit may be a bit... bizarre. You can probably skip it without missing much practical information, but try to stick it out. It will give you an appreciation of the abstract beauty the underlies matrix math.  I'm going to try to explain of the ‘meaning’ of the W coordinate but take this with a grain of salt, since this one goes a bit beyond my limited mathematical imagination.
We've already suggested that the W component represents a 4th dimension.  While that's kind of hard to visualize, we can see the results by 'projecting' onto the XYZ space that we are used to. Got that? Just like we project a 3-D set of points onto the 2-D screen of our computers, we can project a 4-D quantity into 3 dimensions.
Another way to think about it is that an XYZW vector is one point along a 4-dimensional line that intersects 3-space.  In this image, engraver/ math whiz / literal Renaissance Man Albrecht Durer is using a perspective scrim to do his life drawing: projecting a 3-D reality on the 2-D silk screen by keeping his eye in one location and then seeing how the 3-D lady lines up with his 2-D grid.
A decent analogy for projecting 4-D down to 3, here a 3-D world projected  down to 2:

In this word, each 2-D point on the scrim corresponds to a 3-D line running from Durer's eye through the plane of the scrim and beyond.  In a matrix, each 3-D point is on a similar line that runs into the fourth dimension.  While it's hard to visualize, it's mathematically consistent - which is why the mathematicians like it.

How cool – or confusing – is that?  

The point where our mystical 4-D vector intersects our plain old 3-D space corresponds to the point where Durer's eyeline passes through the scrim.  In our case, the point is  [X,Y,Z] divided by W. One side effect of this is that there are many different 4-D points that correspond to the same 3-D point: [1,1,1,1], [2,2,2,2] and [-1,-1,-1,-1] all represent the same point.  In the illustration above, you can see how each of the orange lines hits one point in 2-D, but that the point lies on a 3-D line. Going from 4-D space down to 3-D works the same way - except that the extra dimension is brain-bendingly hard to visualize.

A W value of 1 represents the projection of our 4-D vector onto boring old 3-D reality, sort of like the plane of the perspective scrim in the image above.  W values less than one approach the 'eye point', while values larger than 1 extend past the scrim into the scene.  To understand how the W changes the projected value in 3-D, imagine picking a point on Durer's 2-D screen and pushing back through the screen. As the distance (the W) increases, the projected point will get closer to the center of the screen.  In fact, this is plain old 1-point perspective in action:  A W approaches infinity, any coordinate translates into the perspective vanishing point, which in this case is the center of the scrim.

all lines converge at W=infinity, at least according to Piero Della Francesca

If you’re still unable to wrap your brain around this - and I am not sure I really can, so don’t feel bad about it, you might find this YouTube from Jamie King helpful. You can relate it to the Durer image by imagining Jamie's example image is taken looking down on Durer's little perspective machine from above:

Extra points for the gratuitous Bill and Ted reference, btw.

This same analogy also explains, sort of, why W=0 vectors don’t move. As W increases, the points will converge on the center of his scrim, that is, the perspective vanishing point. On the other hand as W gets smaller they move away: the effect is like a camera zooming in:  everything on the image plane moves away from the vanishing point. As W reaches zero the 'zoom' is now infinite: In math, all of your 4-D points would have become impossible to convert back to 3-D because you'd be dividing their XYZ positions by zero.  It's sort of the inverse of a black hole: instead of all points collapsing down into a singularity, they are instead all smeared out infinitely -- which makes them effectively the same anyway. There's no difference between [1,1,1,0] and [999,999,999,0] in position, since they are both 'located' at  [undefined,undefined,undefined] in 3 dimensions.

Since movement has no meaning in this bizarro singularity world, translations don't do anything. But — brain bend alert —  rotations still work. Of course, we already know from our earlier experiments with W's set to zero: the dots against the first 3 rows of the 4X3 matrix haven't changed, but a W=0 input vector won't translate.  Put another way, since dot products are a way of projecting one vector on to another, projecting a any 4-D vector onto a different 4-D vector with a W of 0 will keep you right at the 'eye point' out of which all those 4-D rays are shooting, so you won't have any W-ness to project yourself out into the 3-D world.

 It's simultaneously baffling and awe-inspring. Like Goat Simulator.

If you've stuck it out this far, the whole visualization actually has one imporant side benefit. It explains the other reason we need homogeneous coordinates: they allow us to handle perspective projections and regular geometry using the same set of rules. W coordinates that aren’t 0’s or 1’s generally crop up only when you’re working with the perspective matrix of a camera or trying to transform points from world space to screen space. However that’s a matter for another time.

For now, however, I need to relax my frontal lobe.


Why did they wear those hankies on their heads, anyway?
Turning something nice and obvious like a 3-D point into an in infinite line in a dimension where parallel lines can intersect is just the sort of thing that gives mathematicians a bad name. Thankfully we don’t really need to understand all the metaphysics: we can just rely happily on the fact that this extra abstraction lets us handle translations using the same math we use for rotations. And we should be grateful that the kind of folks who do understand the way 4-dimensional vectors are projected into our 3-D world left us the 4X4 matrix which (despite this little exercise in gimcrackery) is a remarkably elegant and handy tool for practical purposes and can still be done with junior high school math skills.
Gottfried Chen’s blog also makes an heroic attempt to explain this to mere mortals. The great-grandaddy of all these discussions is Edwin Abbot’s classic novella (you read that right - it’s fiction) Flatland

Homogenius!

Alright, let's get our feet back on the ground (which involves setting our Z coordinate to 0 and our W coordinate to 1).

If you just skipped over the mental gymnastics above —or if you just need to be brought back down to earth — let’s remind ourselves where we are:
We've got a nice, easy to manage system for packing spatial translations and rotations into a single operation, in the form of the 4X3 matrix. By adding a W coordinate — the mysterious homogeneous coordinate – to the end of our original vector, we have gained the ability to do translations. We've also shown how we can toggle back and forth between rotation-only vector operations and rotate-and-translate point operations by changing the W coordinate from 0 to 1.
There is one little flaw to this nifty system, however: it’s lossy. Our 4-part vectors let us distinguish between points and pure vectors, but our 4x3 matrix is only giving us back 3 components not 4. This is fine if all we want is the points, but it’s throwing away information we might need to keep if, for example, if we wanted to multiply a point by several matrices in series.
If we want to get a 4—way vector back from the matrix we are going to need an extra column. Luckily, we know what we want from that extra column — we just need to preserve that W value and nothing else. So how do we get there?
We already know from last time that the first 3 rows of our matrix are supposed to be the axes of the coordinate system which our matrix defines. By definition, an axis can’t move: it’s a direction, not a position. That suggests that it’s going turn into a vector with a W of 0 when we expand it into the next column. After all, you can’t move the X axis or the Y axis: no matter how you rotate it around it is only an axis if it passes through origin. The last row, on the other hand, is a translation: it is actually intended to enforce a change of location: In other words, it’s a point with a W value of 1, rather than a vector with a W of 0.
In other words our 4x3 matrix turns into a 4 x 4 matrix that looks like this:
1000
0100
0010
0001

The first 3 rows are the vectors defining our coordinate system and the last row is a point defining the spatial offset. Any TA should be able to visualize this as transform node — a group, a joint, a null or whatever you prefer — aligned so that it’s axes line up with the first 3 rows and it’s origin sits at the XYZ position of the fourth row.

The nice bit is that, despite all the 4-dimensional mumbo-jumbo this 4X4 matrix (just like the 3X3 and 4X3 versions we’ve touched on before) is still just a plain old set of dot products when you clear away all the verbiage, special typography and extra dimensions. Dot your 4-D point or vector against the columns of this 4-D matrix and you’ll get back a rotated vector, just like we did when learning how matrices work. If your incoming W is set to 0, you’ll get just a rotation; if it’s set to 1, you’ll get a rotation and a translation at the same time. With plain old bagels-and-coffee math.
Petty slick, huh?
So, after a consciousness-expanding (and headache-inducing) journey into other dimensions, we’ve finally sort of arrived at the full 4X4 matrix that powers every graphics application under the sun. And, amazingly enough, we’ve just scratched the surface (What is the surface of a 4-D object anyway? My brain hurts.)
Next time out we’ll talk about how a 4x4 matrix can encode scale as well, which luckily is a little less Timothy Leary than what we’ve already gone through.  Until then here's an animated gif of a 4-dimensional cube (which in this case is a 2-D projection of the 3-D physical extrusion of the 4-D object.... piece of cake!)

Me, I need a good stiff drink.

posts in this series

by Steve Theodore (noreply@blogger.com) at December 19, 2014 05:16 PM


Dot Matrix

We started our math review with a look at the dot product, and started out by showing how dots work in a minimalist way. This time out we’ll do the same thing the most basic component of 3d math - the matrix.



There was a time when this was 'computer graphics'

Once you start looking closely, you'/ll find that  dot product and a matrix actually have a lot in common. As an older gentleman once told me when I proudly showed hin a 72 dpi dithered picture printed on my 1986 vintage Apple 2, "Wait a minute... it's all just.... dots?"

In fact, matrix multiplication is done by using dot products, as we’ll see shortly.  However, matrices are more complicated, both in concept and execution. For that reason we'll devote this post through how matrices work in the simplest possible way, so that it’s easy to see both the how and why of what they do. This post will be primarily about the most minimal example of how a matrix functions. I’ll do it in 2-d to keep the math a bit less wordy, though  the same thing works in 3 or even more dimensions. I’ll also be sticking to a simple rotate-only matrix to start with so the workings are simple - I’ll add in translations and scales next time out to keep the focus on the basics.

First things first


So, starting with the bare minimum, let’s suppose we've got a simple unit-length vector [1,0] and we’d like to figure out how to rotate it. Rotating that unit vector 45 degrees should end up as [.707, .707], as you can see below:
We're trying to figure out an operation that will give these values as we rotate from [1,0] to [0,1]

(If the numbers seem surprising, you might want to hop back to the discussion of the unit circle  in our earlier discussion of dot products.)

 The question is, what kind of operations do we need to do to perform that rotation? What tools do we have to make it work - and, even more importantly, to make it work for any vector and not just this one example?

First, just to clear the decks, let's check off things we know wont’ work.

We can see that difference between the first vector and the second is [-.293, .707] – but it’s pretty obvious that simple addition is not the same thing as performing a rotation. If you’re not convinced, just note that adding the same vector again will get you [.121, 1.414] rather than the expected [0,1].
Plain old multiplication either - there is no number we can multiply against the original [1,0] that will get a non-zero result in the Y component.

So what can we do?  Fortunately, our old friend the dot product comes to the rescue. If you recall how we introduced dots, you should remember that one of the uses of the dot product is to project one vector on to another.


So suppose what would happen if we tried to project our first vector onto another vector that looked like a rotated coordinate system. In other words, we could hold our original vector constant and ‘rotate’ the X-axis counterclockwise by 45 degrees. It’s a theory-of-relativity kind of thing: rotating our vector N degrees clockwise and rotating the world N degrees counter-clockwise are the same thing. By projecting our X-axis against the rotated vector, though, we get the X component we want from a 45 degree angle.
rotating a vector (left) is the same as counter-rotating the coordinate system (right)

We can use the unit circle (or the chart of angle values above) to figure out what the right vector for the counter rotated X-axis is. In the rotated-X-axis world we will be dotting [1,0] against the vector [.707, -.707]:

dot ([1,0], [.707, -.707])

equals

(1 * .707) + (0 * -.707) = .707

That operation gives us a good X-component - it represents how much of the original X is left when projected onto an X axis that has been rotated. If we do it again - remember, we’re trying to get a repeatable operation - we get

dot ([.707, .707], [.707, -.707])

equals

(.707 * .707) + (.707 * -.707) = 0

Which is what we want for the X component after two rotations. This dot product thing seems to be paying off (and I should know – I’ve been milking it for posts for a while).
Of course, this only gives us half of the rotated vector! However, analogy suggests that we can get the Y component of the vector by projecting onto a rotated Y axis, just as we did for the X. The Y axis, rotated clockwise 45 degrees, is [.707, .707]. Dotting against our original vector gives us

dot ([1,0], [.707, .707])

in other words

(1 * .707) + (0 * .707) = .707

which is the Y component we want after one application. The same operation on the rotated vector gives us

dot ([.707, .707], [.707, .707])

namely

(.707 * .707) + (.707 * .707) = 1

Again, this gives us the Y value we expect for a 90 degree rotation.

An example matrix showing a 90 degree rotation

Dots to Matrix

So, that shows we can rotate a vector by using two dot products: dot the X component of the vector against a counter-rotated X axis and the Y component of the vector against a counter-rotated Y axis, and you get the rotated result. (Remember, the axes are rotated against the rotation you’re actually applying, because you want the projection of the rotated vector and you’re moving the universe instead of the data, Einstein-style).

Now that we know how it works, it would be nice to have a simple way of saying “just do that two-dot thing” - in other words, we'd like to define an operation that will apply the two dot products at the same time, giving us the rotation we're after. And that’s all that the matrix - the mysterious whatchamacallit at the heart of 3-D math – really boils down to this:  it’s simply a convention for saying “make a new vector out of these dot products”.


I kind of hate the internet... but  I must admit, the mere existence of a pepakura Minecraft
character for Dot Matrix from Spaceballs warms my heart.
 So here's the notation that is commonly used for saying "make a new vector out of dot products." We can re-write the whole mess above very simply as

[1,0] * [  .707,  .707]
[ -.707, .707]

Where the first column of the matrix is the X-axis of our counter-rotated coordinate system and the second column is the Y-axis of the same. It's just a convention for saying:

x = [1,0] dot [ .707, -.707]
y = [1,0] dot [ .707, .707]

which is exactly the same thing we took a couple of paragraphs above to explain in words.
So in the end it’s amazingly – almost embarrassingly – simple: you dot your vector against each of the columns in the matrix in turn and voila! you’ve got a new vector which applies the matrix transform. The big, scary matrix monster turns out not to be so scary - once you pull off this mask it turns out to be nothing but Old Man Dot Product in disguise!

It would have worked, too, if it wasn't for you meddling kids!
 In this example we’re only covering rotations : scales and translations we’ll touch on in a later outing -- however they work the same way.  Translation and scale are encoded into matrices a bit differently - but the mechanics are identical: Just dot the vector against each column in the matrix and you have your transformation.
The big takeaway from this exercise is that the basic math is the same and it requires no skills you didn’t learn by seventh grade (or at least the first post in this series).  Matrices just aren't that hard once you know what they are actually doing.
As I've said several times, all of this power is really based on simple math (addition and multiplication) disciplined by conventions such as normalized vectors in dot products or the row-column arrangement I’ve shown here. A convention, however, is to some degree arbitrary. In matrices, for example, you could get the same results by representing what I’ve written as rows to be columns and vice versa, and then dotting your vectors against the rows rather than the columns. The arrangement I’ve use here is known as ‘row major’, and the alternate arrangement is ‘column major’. You can usually recognize row-major systems because row-major operations tend to be written as "vector times matrix" where column major operations are usually written "matrix times vector."  The actual math is the same, apart from the convention used to write it down.
The choice between row-major and column-major matrices is  typically made for you by the the environment you’re working in, so you will rarely have to worry about it. Still, we will revisit this in future discussion of matrices.  I'll be using row-major throughout to keep things consistent, and also because that is how Maya - my usual go-to app - is organized.

Matrix Fun

Working through this stuff one piece at a time should give even the most hardened and results oriented TA an dim appreciation for what the mathematicians mean by ‘elegance’. Here’s what’s so beautiful about this setup: Written out the way we've done it, the rows of the matrix correspond to the coordinate system you’d get by applying the matrix. Thus, after a 45 degree rotation your X-axis is now pointing at [.707, .707] and your Y is now pointing at [-.707, .707].  So far we've stuck to 2-D examples, but the same is true in higher dimensions as well: the 4x4 matrices that we use everywhere in graphics, the local coordinate system is encoded the same way.
This is almost perfect in it’s elegance. Consider this little piece of gibberish from Maya:


cmds.xform('persp', q=True, m=True)
[0.7071067811865475,
-2.7755575615628907e-17,
-0.7071067811865476,
0.0,
-0.3312945782245394,
0.8834522085987726,
-0.3312945782245393,
0.0,
0.6246950475544245,
0.46852128566581774,
0.6246950475544244,
0.0,
240.0,
180.0,
240.0,
1.0] #

That doesn’t appear to mean much beyond ‘WTH?’. However, when rearranged into a matrix (and truncated to fewer digits for legibility), it’s:


[ 0.707, 0.000,-0.707, 0.000]
[-0.331, 0.883,-0.331, 0.000]
[ 0.625, 0.468, 0.625, 0.000]
[ 240.0, 180.0, 240.0, 1.000]

Which means the the persp camera in my Maya scene has an X axis pointing at [0.707, 0.000,-0.707], a Y axis pointing at [-0.331, 0.883,-0.331] and a Z axis pointing at [0.625, 0.468, 0.625] . We’ll talk about the meaning of those zeros in the 4th column and the last row next time out. While it’s still a bit tough to visualize, it’s actually meaningful - not just some magic computer-y stuff you have to take on faith.
As a side benefit, the matrix-rows-are-local-axes scheme allows you to extract the cardinal axes of a matrix without doing anything fancier than grabbing a row. In the camera example, we can tell the camera is ‘aiming’ along [-0.625, -0.468, -0.625] (Maya cameras aim down their own negative Z axis, so I’ve just taken that third row and multiplied by -1). You could use use this to figure out if the camera "sees" something by dotting that vector against a vector from the camera's position to the target, as we discussed last time. Extracting local axes this way is the key to many common applications, such as look-at constraints and camera framing.
Of course,anybody who knows any 3d graphics at all, of course, knows matrices are used for a lot more than just rotations, and that we’ve just scratched the surface. I’ve walked through the derivation this way for two reasons: first, to show how the matrix is really nothing more than a convention for applying dot products in series. Second, because I want to underline the importance of the fact that matrix rows are axes of a local coordinate system*. Next time out we’ll explain how matrices can also represent scale and translation, and how to put matrices together for even more matrix-y goodness.

* in a row major matrix, anyway.  And subject to some interesting qualifications we'll talk about in a later post....


PS: The Rotation Matrix Formula

There's one last topic to cover on rotation matrices: how to apply a generic rotation for any value and not just our 45 degree example. Keeping in mind what we've learned -- that the rows (of our row major matrix, anyway) are the axes of the rotated coordinate system --  The 2-D example we've used all along generalizes very easily.  The unit circle tells us that the X and Y axes of a rotated coordinate system will look like this (where X is the first row and Y is the second)


 cos(theta)  sin(theta)
-sin(theta)   cos(theta)

The cosine / sin in the first row takes the X and Y values from the unit circle, where the X axis is [1,0] and the Y axis is [0,1] You can check those values for a 0 rotation, and you'll see how that lines up with the default X and Y axes:

1 0
0 1

Using the same formula for a  30 degree rotation would give us

 .866  .5
-.5  .866

since the cosine of 30 degrees is .866 and the sine is .5.  This also shows how that  negative sine works: the Y axis starts rotating backwards into negative-X as the coordinate system rotates counter-clockwise).

Although we haven't covered 3-D rotations this time out, it's not hard to see how this 2-D XY rotation should be the same thing as a rotation around the Z axis in 3 dimensions. A row-major Z rotation matrix looks like this:
 cos(theta)  sin(theta) 0
-sin(theta)  cos(theta) 0
0 0 1

This makes perfect sense when you remember that the rows of the matrix correspond to the axes of the rotated coordinate system in the matrix: in this example the X and Y axes are being rotated on the XY plane, but the Z axis still points straight at [0,0,1] and neither X nor Y is rotating into the Z at all (hence the zeros tacked on to the first two rows).
Knowing that, it makes sense that an X rotation matrix -- with the X axis held constant and Y and Z rotating on the YZ plane -- looks like this:
100
0  cos(theta)  sin(theta)
0 -sin(theta)  cos(theta)

The Y rotation matrix is a bit trickier. We know that the Y axis will be [0,1,0], but the sin-cos rotations have to be split among the X and Z axes like this so that the rotation is limited to the XZ plane:
 cos(theta) 0 -sin(theta)
010
 sin(theta) 0  cos(theta)

These 3X3 matrices will do 3-D rotations, but you'll rarely see them alone. In most practical uses these matrices will be embedded into a 4X4 transformation matrix (for reasons we'll be talking about in a future post) but they will work the same way (for example, you can see them quite clearly in the list of matrixes that accompanies the Maya xform command.  Next time out we'll talk about why these 3X3 matrixes turn into 4X4's and how that difference is key to including translations as well as rotations. Until then - keep dotting. And May the Schwartz Be With You! (Dot Matrix sighting at 0:30)

posts in this series

by Steve Theodore (noreply@blogger.com) at December 19, 2014 05:16 PM


Dot's all, folks

Last time out I went on (probably a bit too long) on the virtues of the dot product - the operation which takes two lists of numbers and multiplies them to create a single product. The highlight of the whole thing was the cosine dot product - the handy fact that the dot product of two normalized vectors is the cosine of the angle between them.

Now that the theory is out of the way, it’s time to highlight some of the zillions of applications for this handy little operation.


If none of this sounds familiar you might want to revisit the first post in the series before continuing.

The dot product is incredibly useful for a TA for two reasons. First, dots allow you to convert between geometric measures and angles without the need for matrices or complex formulae. Second, dots provide an efficient way to project one vector on to another, allowing you to measure distances and quantities relative to an arbitrary axis or vector - a great tool for anything from color conversions in a pixel shader to measuring motion in a complex rig.
Before getting down to cases, a quick reminder of one important side fact we pointed out last time. A cosine dot product can only tell you how different the angle between two vectors is - not what rotations would transform one vector into the other. If you try out this example you’ll see that the dot of [1,0,0] against both [.5, .866, 0] and [.5, -.866, 0] is .5, which (if you remember your sines and cosines) means the relative angle is 30 degrees. However one of those two vectors is clockwise from [1,0,0] and the other is counter-clockwise from it. The dot, by itself, can’t tell you which one is which. Don’t forget that bit!
As I mentioned in the last article, the math for dots is trivially simple. Maxscript includes vector math functions by default, as does MEL, but vanilla maya.cmds does not. If you want to experiment with examples mentioned here in Maya python, you can import pymel.core.datataypes and use the Vector. I’ve also put a simple vector module up on Github that works in Maya.cmds. I’ll be using that for these examples but translating between MXS, Pymel, and cmds should be a no-brainer.

rigging

One of the most common tasks in rigging is wrangling information into the correct frame of reference.This is particularly tough when dealing with angular data, since angles are often presented in the form of Euler angles whose numeric values can vary unpredictably and which are therefore hard to use in expressions or code. Here are a few examples of how dot’s can help riggers get angular information while avoiding the Euler blues

The Bends

Dot’s are an excellent way to measure the extension of a limb, without relying on an Euler value which might be affected by local axis orientations, joint orients, or rotated local axes. Here’s an example that gets a reliable value for the extension of an arm (note: this is vanilla maya, you could do it more succintly with Pymel but it’s a better illustration to do it from scratch)


shoulder_pos = cmds.xform('r_shoulder', t=True, w=True)
elbow_pos = cmds.xform('r_elbow', t=True, w=True)
wrist_pos = cmds.xform('r_wrist', t=True, w=True)

bicep_vector = (Vector3(*elbow_pos) - Vector3(*shoulder_pos)).normalized()
forearm_vector = (Vector3(*wrist_pos) - Vector3(*elbow_pos)).normalized()
elbow_bend = Vector3.dot(bicep_vector, forearm_vector)

then arm_extension will be 1 at full extension and 0 when the arm is bent back completely on itself (ouch!). You can map use this extension value to drive muscle deformations, blendshapes, or other behaviors without worrying about th underlying Euler values or converting from angles to linear ranges.

Leaning In

It’s often useful to have a general idea what a character’s whole body is doing, rather than focusing entirely on individual joint positions and orientations. For example, you might want to have rig behaviors turn on when a character is ‘upright’ and off when it it is ‘prone’, or vice-versa. Figuring out the gross orientation is often hard because there are so many bones cooperating to produce the visual effect – and because different animators may use different controls in different ways: animator A may prefer to put all of the big rotations onto a center-of-gravity control while animator B does everything on the pelvis.
Dots are great for extracting pose info from the world space position of key bones instead of trying to intuit them from rotation values. For example:


head_pos = cmds.xform('head', t=True, w=True)
pelvis_pos = cmds.xform('pelvis', t=True, w=True)

# how upright is the character’s body?
body_vector = (Vector3(*head_pos) - Vector3(*pelvis_pos)).normalized()
upright = Vector3.dot(body_vector, Vector3(0,1,0)) # for a y-up world

Here upright will be close to 1 for an upstanding character, close to 0 for a prone character, and close to -1 for an upside down character (eg, during a handstand). This version tracks the pelvis-to-head vector so it will respond to things like a hunched-over spine; but one of the nice side effects of vector math it that you can easily ‘weight’ different elements as you put together your vectors. For example:

chest_pos = cmds.xform('spine_3', q=True, t=True, w=True)
head_and_chest = (Vector3(*chest_pos) * 2 + Vector3(*head_pos)) / 3.0
body_vector = (Vector3(*head_and_chest) - Vector3(*pelvis_pos)).normalized()
upright = Vector3.dot(body_vector, Vector3(0,1,0))
would include bias the uprightness vector towards ‘spine_3’, diminishing the influence of the head on the final results.

Looky here

You don’t always have to use positions to drive dot-product calculations. You can always get the local orientation of a transform by looking at it’s matrix (the exact reason for this will be shown in a later posting, for now take it on faith). This allows you to see how closely a given object is oriented towards a given vector.
For example, something like this will help you figure out if a character’s body is oriented in roughly the same direction as the character’s root bone:


# assuming that the bones are constructed with positive z as 'forward'
world_forward = lambda b: cmds.getAttr(b + ".worldMatrix")[8:11]
root_forward = Vector3(*world_forward('root'))
pelvis_forward = Vector3(*world_forward('pelvis'))
shoulders_forward = Vector3(*world_forward('spine_3'))
head_forward = Vector3(*world_forward('head'))
# get a weighted average of the pelvis, shoulder and head directions
composite = ((pelvis_forward * 3) + (shoulders_forward * 2) + head_forward) / 5.0
# flatten the composite and root vectors into 2 dimensions:
composite = composite * Vector3(1,0,1)
root_forward = root_forward * Vector3(1,0,1)
orientation = Vector3.dot(composite.normalized(), (root_forward.normalized())

A value of 1 would have the character facing precisely along the same direction as it’s root bone in 2D. This kind of thing is especially useful when you’re trying to manage a lot of animations which need to begin and end in similar poses - you can quickly check the overall posture of a lot of characters without too much detailed analysis to spot problems before going in and looking at the troublesome ones for hand fixing.

shaders

Even more than rigging, shader authoring frequently involves a return to the math fundamentals. The most familiar example of the dot product in shader writing is the Lambert rendering equation which we discussed in the last post. However, you can get a variety of other handy effects from the dot inb shaders. The key is to find the right set of vectors to work with.
For example, if you dot a surface normal against the vector along which the camera is looking, the result will tell you how directly surface is facing the camera. This allows you to create a fresnel or edge-highlighting effect.
Here’s a snippet of a very minimal Unity shader that illustrates the principle:

void surf (Input IN, inout SurfaceOutput o) {
// a hacky way to get the camera vector…
float3 cam_vect = UNITY_MATRIX_IT_MV[2].xyz;
float result = 1 - dot( cam_vect, o.Normal);
o.Albedo = float3(.5,pow(result, FresnelPower) ,.5);
o.Alpha = 1;
}

The only thing worth noting here is the way the result value is being inverted: we want the result number to be close to 1 at the horizon and close to zero where the camera normal and the surface normal are aligned, which is the reverse of what the dot would normally give us. By raising the result value to a higher or lower power (using pow) we can sharpen or soften the effect; since it the result value should always be 1 or lower a higher power will result in a smaller result value and thus a tighter highlight as you can see in the images.

The dotting the camera vector against the surface normal produces an edge highlight fresnel-style effect.
The size of the effect can be tweaked by raising the dot product value to a higher or lower power.

You can re-map that dot product in other ways as well. The popular Team Fortress 2 shader, for example, takes the dot between the light and the surface normal - which, of course, will range in value from -1 to 1 - and re-maps it onto the range 0 to 1 so it can be used to lookup a color value from a texture. That’s how the game achieves it’s distinctive ‘wrap-around’ lighting:

The Team Fortress shader uses a shifted dot-product to look up lighting values from a hand-authored color ramp, creatng a distinctive illustrational look.

Both of those uses use the ‘cosine falloff’ intepretation of the dot product, that is, they represent angular differences. However dots have another mathematical meaning: they represent the projection of one vector onto another. One really cool aspect the projective use of the dot is that the logic works in color spaces as well as physical space. For example, a shader writer of can get the luminance of a pixel elegantly like this:

float luma = dot( float3(0.2126, 0.7152, 0.0722), pixel_color);

Which is essentially projecting the color onto a ‘luminance vector’ dominated by green (numbers derived from this) You could use the same trick to identify ‘warm’ colors by dotting against a warm rgb value like (.707, .707, 0) - high dot values will be warm and low dot values will be cool. It takes some meditation to really grok what’s going on (try parsing what’s happening in this example!) but dots can be a very handy trick for navigating color space as well as 3-d space.
Shader writers have one more sneaky use for dots - they can be a cheap substitute for selection functions. Shader authors often have to pack data into vectors for efficiency, but accessing one component of a vector would need an expensive if-then branch in theshader. Dots, however, can let you pick one component out of your vector without using branches. Since the dot of any vector composed of all zeros is of course zero. If one component is a one and the rest are zeros, the result will be the corresponding component of the other vector. Thus:

float3 y = float3(0,1,0);
float3 x = float3(1,0,0);
float3 val = float3(.5, .866, 0);
// dot(x,val) == val.x = .5;
// dot(y,val) == val.y = .866;
This is more compiler friendly than inserting a branch into the shader code to conditionally pick one component or another. I’ve found it especially useful in Unity, where ShaderLab limits your ability to pass custom data types to shaders and it’s often necessary to pack data into vectors or matrices just to get it from the game to the shader.

tools

It’s easy to see who the kinds of tricks we’ve already laid out for shaders and rigging generalize for tool writing. The dot of a surface normal and a vector is a great proxy for whether or not the a surface is facing something, dots are great for analyzing geometry. For example, A tree-and-rock scattering script can dot the normal of a terrain against gravity to figure out which slopes are too steep for trees, or which areas are bottomland where there ought to be lots of bushes. A terrain editing tool could against a sun vector to identify exposed areas where the grass is yellowed and shady spots where it’s lush and green.
As with rigging , the dot also provides a way to check relative orientations. For example, you might need to know if an object has been where another object can ‘see’ it. If you dot a reference vector - such the object’s local X or Z axs - against the vector to a target, you can figure out if the target is ‘ahead’ or ‘behind’ the reference object. For example this function would tell you if the target was within some angle of the forward axis of the observer:

   def target_visible(reference, target, cone-angle = .5_):
"""Is target within <coneangle> when viewed on references' local Z axis?"""
reference-vector = cmds.getAttr(reference + ".worldMatrix")[8:11]
target_pos = cmds.xform(target, q=True, t=True, ws=True)
reference-pos = cmds.xform(reference, q=True, t=True, ws=True)
target-vector = (Vector3(*target-pos) - Vector3(* reference-pos))
return Vector3.dot(target-vector.normalized(), reference-vector.normalized()) <= cone-angle

You could restrict that to one or two axes using the same trick in the rigging example, or use the full cone angle as done here.
As an aside, this brings up the issue of converting between dots and angles. Since the geometric dot product (as always, assuming you’ve got normalized vectors) is a cosine, you can convert it to an angle by using the arc-cosine function (acos in Python and most other languages) like so:
cosine = Vector3.dot(a, b)
angle_in_radians = math.acos(cosine)
angle_in_degrees = math.degrees(angle_in_radians)

The projective function of dots is also useful in tools. For example, you can use a dot to clamp a line to the position of the mouse, even if the line is constrained so that the mouse doesn’t physically rest on the line:


line_vector = Vector2(.707, .707)  # a 45 degree line
line_origin = Vector2(200, 200) # start drawing at pixel (200,200)
while (mouse.down()):
line_end = Vector2.dot( Vector2(*mouse.position) - line_origin, line_vector)
draw_line (line_origin, line_origin + line_end)

Further reading

If this one whetted your appetite and you need to know more, here’s a few links I found handy while reading up:

by Steve Theodore (noreply@blogger.com) at December 19, 2014 05:15 PM


Bagels and Coffee, or, the vector dot product and you

I’ve been boning up on my math lately.
Like most TA’s I’ve cobbled together a bag of tricks from different situations I’ve dealt with over the years, but I’ve never really gone back to shore up my shaky high school trigonometry and pre-calculus. It’s certainly possible (at least, I hope it is!) to be a good TA with only seat-of-the-pants math skills — after all, we have parenting and scaling and all the other cool tricks in our apps to do the heavy lifting for us. Still, I’ve been finding that paying more attention to the math fundamentals is helping me solve problems more efficiently and elegantly than my patented hack-and-slash techniques did.
So, I’m starting an occasional series on some basic math concepts that I hope will be useful to other TA’s. I know it’s been helpful to me - there’s nothing that concentrates the mind like putting something out there on the internet for public commentary - it’s really forces you to think things through… At least, as long as you’re not on Twitter.


To kick off the series, I want to start off with a simple operation that I use all the time, the humble dot product. Also known as the 'scalar' product, the dot is an operation for turning lists of numbers into a single number. It’s also astonishingly useful for graphics. I’ve used it for years, but only recently did I try to see how and why it works instead of just relying on the second-hand assurance that it works.
The dot is all about combining operations on lists. We always run into it in the context of geometric vectors, but in the pure math world vector is just another way of saying “list of similar numbers.” If you go to the coffee shop every day and buy a $5 latte, its obviously going to cost $25 a week (Tote that up over 48 work weeks a year - it's a lot of money! I bring instant. But I digress). If you buy a $2 bagel on monday and a $3 cookie on Wednesday and Friday, how much will it cost?:
5 * 5 = $25 for coffee
2 * 1 = $2 for bagel
3 * 2 = $6 for cookies
This makes $33 total a week (you really should bring in your snacks from home. You'll save a ton!)
Besides helping you save money on lunch, this is a classic (though non-3-d related) example of the dot product in action. Dots are nothing more than a structured way of multiplying two lists of numbers. In this case we have list of prices:
[5, 2, 3]
and a list of quantities:
[5, 1, 2]
The dot operation merely multiplies the numbers in the same position in the list and adds them together. As you can see, this is trivial math:
(5 * 5) +  (2 * 1) + (3 * 2)

Despite it's humble origins, however, this trick -- multiplying ordered pairs of numbers and adding them up - is absolutely basic in 3-D graphics. The lists of prices and quantities become vectors (in fact, general purpose algebra calls any list a 'vector') and with a simple convention the dot product takes on a very interesting and useful set of properties for TA’s to exploit.
The most famous example of the dot product in graphics is the original Lambert shading equation:
N dot L
Where N is a surface normal and L is the angle of the incident light.





The 'Lambert shader' is based on this math textbook from 1760. How cool is that?

Lambertian shading is probably the single most common operation in computer graphics, but it’s the same math as figuring out your coffee budget. Here’s how the magical translation from bagels and coffee to shaded pixels works:
Imagine a sphere being lit by a directional light from straight above, in classic CG fashion. The vector to the light would be
[0, 0, 1]
On top of the sphere, the normal vector would point the same way - it too would point up towards
[0, 0, 1]
The dot of these two is:
(0 * 0) + (0 * 0) + (1 * 1) 
in other words, 1. This makes sense: our light is directly overhead, so the sample point on top of the sphere receives the full incoming light. Compare this to a point halfway down the sphere. A a normal point 45 degrees from the vertical might be
[.707, 0, .707]
And the dot would be
(0 *.707) + (0 * 0) + (1 * .707)
or .707. That means this sample point is getting about 70% of the incoming light. At the horizon of the sphere the dot will be [0,0,1] dot [1, 0, 0]. This dots out to
(1 * 0) + (0 * 0) + (0 * 1) 
or 0. This makes sense - at the horizon of the sphere the light is parallel to the surface and imparts no light.
Or, in pretty picture form:

Wherefore art thou cos(theta)?

So, it appears of this fancy-pants rendering is coming from the same bagels-and-coffee trick. How come? Lambert’s law isn’t some simple interpolation - it’s based on cosines, which give it the characteristic soft falloff around the horizon. How does this work?
The sharp-eyed reader might notice that all of the vectors in this example are normalized, that is to say the length of all of the vectors in this example are 1. That’s is the special convention that turns a plain-vanilla dot product into a geometric proposition. As long as the vectors are normalized -- but only if they are normalized -- the dot product of the light vector and the normal vector is the cosine of the angle between the two vectors. That’s what makes the nice soft falloff on a Lambert-lit object, but it has lots of other properties as well.
To understand how this bagels-and-coffee math turns into trigonometry, remember that ‘normalizing’ a vector just means setting its length to one. Visualize what happens if you sweep a 1-unit long line segment around in a circle, starting from the horizontal. As the segment rotates, you can draw a right triangle from it’s end point up or down to the horizontal axis, as in the example below:


If you recall your high-school trigonometry you’ll remember that the cosine of an angle in a right triangle is the ratio between the side of a right triangle next to the angle and the hypotenuse of the same triangle (the “CAH” in “SOHCAHTOA,” if you learned it the way I did). In this case, our hypotenuse is always 1 (it’s a unit line). so  the cosine is just the width of our right triangle. All of this works as described only if the vectors are normalized,however - when your dots give you wonky results, non-normalized vectors are always the first thing to look for.
This video from Khan Academy gives you a more in-depth derivation if this description isn’t clear.
Once you grasp the unit-circle-cosine setup, it’s easy to see how dotting unit vectors creates cosine values rather than lunch budgets. See what happens when you dot a random vector against [1,0,0]:
example = [.866, .5, 0]
reference = [1, 0, 0]
example dot reference = (.866 * 1) + (.5 * 0) + (0 * 0) = .866
As you can see the X component of the example vector has been preserved, but the other two are zeroed out. (This illustrates the meaning of the dot project - it’s the projection of one vector on to another. We’ll touch on that more in the next post).
In this case, projecting that 60 degree line segment onto the vector [1,0,0]creates a line segment from [0,0,0]to [.866,0,0] and the same kind of right triangle we described above. The ratio of the hypotenuse vector to this new ‘adjacent’ vector is .866 / 1, that is, plain old .866 — which we we know from the unit circle is the cosine of 60 degrees and the answer we were looking for.
This is how the dot of two normalized (!) vectors is alway the cosine of the angle between them.

Dot's all, folks

So that's the basic theory of the dot product. Of course what the ruthlessly practical TA will want to know about is uses, not theory. Some of the applications will be obvious but there is a plethora of less obvious ways the dot product can make your life eaiser. I’ll hit those in my next post.  In the meantime, bring instant coffee instead of paying for that venti tripple mocchachino every day. That stuff’ll totally blow your budget.

posts in this series


by Steve Theodore (noreply@blogger.com) at December 19, 2014 05:15 PM


New Photoshop Exporter- ExportThis!

After a long and (mostly) successful run of controlling Photoshop with Python, I decided to take a stab at writing a fully integrated Photoshop Extension, this time using the native Javascript based API.

The end result is a cleaner, smaller and all round better Photoshop tool that does a number of kinda cool things.

Export This!
Some of the User-facing features
Because its written to take advantage of the native API there is a significant difference in app size- Texture Monkey being almost 40mb when including all the additional GUI and Python library files it required, vs a 29kb .ZXP installer for ExportThis. The ZXP is easily distributed, and uses Adobe Extension Manager CC to install/update/remove itself. 

Within the tool, the document template system was added to allow artists to easily make documents that would be compatible with the exporter (as a lot of it is group name/hierachy based) but I expanded on it to allow artists to make and save their own particular document setups too. 

I chose JSON files for the templates, user configuration and project definitions due to it's ease of use- and the fact that it can easily be read by anybody. This turned out to be a little bit of a challenge, as Javascript is generally blocked from accessing any local files, as well as the Asynchronous nature of Javascript itself meaning that sometimes your functions end up being called before you have any data to read.

In addition to the trouble with reading files, another odd little challenge popped up due to Javascript supporting JSON parsers out of the box and Javascript Extended completely lacking any. I got around this using the eval feature in order to read the JSON strings back into useful objects.

The tool uses the Adobe extension framework, which is made up of several components, each of which operate on different levels and have their own little quirks. 

The GUI is created in a very similar way to a web page, using HTML and CSS to define it's layout, content and internal behaviors. I was able to embed things like interactive help drop-downs using JQuery, as well as easily modifiable selection lists and other standard web-fare. 

Behind the GUI is a Javascript file that bridges the GUI to the actual Photoshop JSX extension. This is where it starts to get a little clunky, for three particular reasons:
  • This part of the tool has the same security restrictions imposed on it as any other web related Javascript code, for example, restrictions to the local file system. 
  • All calls to the actual Photoshop extension are passed as strings to evaluate (including arguments) meaning passing data back and forth between the extension and the GUI can be pretty cumbersome.
  • Javascript operates in an async manner. Due to this, the Javascript layer was kept very lightweight to minimize introducing any lame async related bugs. Although initially this lead to a little bit of head-scratching on my part, it resulted in a much easier to maintain tool, with all the heavy lifting confined to the Photoshop JSX engine. 
As I decided pretty early on that I wanted to use JSON files to store both user settings (such as the last project used, and their local project root) I had to figure out a way to be able to both read and write data from the tool, which I ended up doing on the Photoshop JSX side, which is the third component!

The Photoshop JSX (JavascriptExtended) file is where all the work actually happens- This is the part of the tool that can access the local drive, as well as drive Photoshop. I wrote it to contain a mix of environment definition and actual exporting functions. Any data sourced from the local drive that might be required by the GUI is read by the JSX and then passed back as a string to be parsed into a JSON object in memory. In a similar fashion, any GUI modified data is passed back as a string to be written back to the file system by the JSX. Lots of juggling!

The JSX also contains a component that can interact with the local system through the command line in order to check out elements from Perforce, using .bat files. 

(the hidden) GUIRilla, or a sprite exporter. 

Although the main focus of the exporter is Texture exporting for 3d models, an additional feature is the ability to author and export multiple 2D sprites from a single document. The exporter allows a "UI_SPRITE" flag document name prefix to be defined in the Project Settings, which will change the exporter to treat the document as a sprite atlas and export each group as a individual trimmed element, with transparency. It's pretty cool, but I'll go into this in a little more depth in another post, as its a whole other workflow. 

Overall, it was a fun exercise and the Artists using it have responded very well to it as a replacement for TextureMonkey. 

If you made it this far, hooray! Drop me a line if you are working with the Adobe Extension framework at all. Next up will be some tips for rapid iteration while working on extensions, and some of the terrible mistakes I made along the way...

by Peter Hanshaw (noreply@blogger.com) at December 19, 2014 03:37 AM


Victor @ CG Spectrum

I have the pleasure of helping out CG Spectrum with some of their rigging, including Victor, who is now being offered as part of a free animation course promotion:

http://40.media.tumblr.com/b2ed6599266a899dc489114e421c47ce/tumblr_ngsixzdcQQ1tbl731o2_r1_500.png

by Morgan Loomis at December 19, 2014 03:07 AM


December 16, 2014

Grabbing for good enough

Uncle Bob, who I consider my favorite programming writer, had a post a few weeks ago titled “Thorns around the Gold“. In it he describes how writing tests for your core functionality first can be harmful. Instead, Uncle Bob prefers to probe for “thorns” around the “gold” first.

I shy away from any tests that are close to the core functionality until I have completely surrounded the problem with passing tests that describe everything but the core functionality. Then, and only then, do I go get The Gold.

I haven’t been doing TDD for nearly as long as Uncle Bob but I was shocked to read this. I’ve always learned and taught that you should create positive tests first, and only need as many negative tests as you feel are warranted. While you may not grab the gold immediately, you at least step towards the gold. How many thorns you expose is a judgement call. In Python, most people don’t even bother validating for None inputs, and instead just let things raise (or not). Of course, this depends on your users. For libraries limited to one internal application, I wouldn’t “probe many hedges.” For open source libraries, I validate pretty aggressively.

Of particular interest was this:

I often find that if all the ancillary behaviors are in place before I approach the core functionality, then the core functionality is much easier to implement.

I always thought you should only program what you need and no more. It seems very strange to assume the ancillary behaviors will be needed. It seems like a violation of YAGNI.

I have been trying to reconcile Uncle Bob’s advice here, and the TDD best practices I’ve learned and developed. But I cannot. Either I’ve been receiving and giving years of bad advice, or Uncle Bob has made a rare mistake.

by Rob Galanakis at December 16, 2014 12:38 AM


December 15, 2014

UE 4!

I finally got myself a copy of unreal 4.
There are a lot of things I want to test but my main goal was to see if I could make an animated version of my drawings.

This is a first test after a few hours of work. Very early but promising, I think I'll be able to achieve what I have in mind.


Yeah, it's a screenshot. So much for an ‘animated’ version. I'll also have to find a method for capturing editor stuff …

by mkalt0235 (noreply@blogger.com) at December 15, 2014 07:17 AM


December 13, 2014

Importing cubemaps from single images

So this tweet on EXR format in texture pipeline and replies on cubemaps made me write this…

Typically skies or environment maps are authored as regular 2D textures, and then turned into cubemaps at “import time”. There are various cubemap layouts commonly used: lat-long, spheremap, cross-layout etc.

In Unity 4 we had the pipeline where the user had to pick which projection the source image is using. But for Unity 5, ReJ realized that it’s just boring useless work! You can tell these projections apart quite easily by looking at image aspect ratio.

So now we default to “automatic” cubemap projection, which goes like this:

  • If aspect is 4:3 or 3:4, it’s a horizontal or vertical cross layout.
  • If aspect is square, it’s a sphere map.
  • If aspect is 6:1 or 1:6, it’s six cubemap faces in a row or column.
  • If aspect is 1:1.85, it’s a lat-long map.

Now, some images don’t quite match these exact ratios, so the code is doing some heuristics. Actual code looks like this right now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
float longAxis = image.GetWidth();
float shortAxis = image.GetHeight();
bool definitelyNotLatLong = false;
if (longAxis < shortAxis)
{
    Swap (longAxis, shortAxis);
    // images that have height > width are never latlong maps
    definitelyNotLatLong = true;
}

const float aspect = shortAxis / longAxis;
const float probSphere = 1-Abs(aspect - 1.0f);
const float probLatLong = (definitelyNotLatLong) ?
    0 :
    1-Abs(aspect - 1.0f / 1.85f);
const float probCross = 1-Abs(aspect - 3.0f / 4.0f);
const float probLine = 1-Abs(aspect - 1.0f / 6.0f);
if (probSphere > probCross &&
    probSphere > probLine &&
    probSphere > probLatLong)
{
    // sphere map
    return kGenerateSpheremap;
}
if (probLatLong > probCross &&
    probLatLong > probLine)
{
    // lat-long map
    return kGenerateLatLong;
}
if (probCross > probLine)
{
    // cross layout
    return kGenerateCross;
}
// six images in a row
return kGenerateLine;

So that’s it. There’s no point in forcing your artists to paint lat-long maps, and use some external software to convert to cross layout, or something.

Now of course, you can’t just look at image aspect and determine all possible projections out of it. For example, both spheremap and “angular map” are square. But in my experience heuristics like the above are good enough to cover most common use cases (which seem to be: lat-long, cross layout or a sphere map).

by at December 13, 2014 10:00 AM


All we are saying is give API 2.0 a chance

Doing all this math-related posting has reminded me of something I've been meaning to write up:

Maya's python API 2.0, first introduced in the 2013 version, got off to a rocky start. People complained about missing functions and missing modules.  It uses (mostly) the same function and class names as the original OpenMaya Python, which is a recipe for confusion. The documentation is pretty confusing too, since it points at the original C++ docs and leaves it up to you to do much of the translation in your head.    However....



One thing that API 2 definitely does right is to eliminate the dreaded MScriptUtil, with its ugly and confusing interface and all of the opportunities for failures that it includes.  I've been busy porting over a bunch of geometry utilities to the new API and I'm routinely finding that stuff like this:


def APIVector( iterable, normal=False ):
'''
return an iterable as an OpenMaya MVector

if iterable is an openMaya MVector, returns untouched
'''
if isinstance( iterable, OpenMaya.MVector ):
o_vector = iterable
else:
assert len( iterable ) == 3, "argument to APIVector must have 3 entries"
v_util = OpenMaya.MScriptUtil()
it = list( copy( iterable ) )
v_util.createFromDouble( iterable[0], iterable[1], iterable[2] )
o_vector = OpenMaya.MVector( v_util.asDoublePtr() )

if normal:
o_vector = o_vector.normal ()
return o_vector

Turns into to this:


def APIVector(*iterable, **kwargs):
    result = None
try:
result = api2.MVector(iterable)
except ValueError:
result = api2.MVector(iterable[0])
finally:
if kwargs.pop('normal', False):
result.normalize()
return result

In other words, one reasonable line for 4 icky ones.

Plus, the new versions are generally more pythonic - the API 2 version of MVector, for example supports both dot-access, bracket access, and iteration over the vector components (though, annoyingly, not slicing).

It's certainly not all perfect. You do have to be very careful about mixing API 1 and API 2 code in the same functions - even though they are both wrapping the same C++ underpinnings they are are mutually incompatible.  Some things are still cumbersome -- converting strings to MSelectionList items to MObjects to MFNs is still a waste of good brain cells -- but it's a step in the right direction. I'll post more as I know more.

By the way, I spent several minutes surfing around for a funny image to wrap up on, I even did a meme-generator.com thing with The Most Interesting Man In The World saying something dismissive about MScriptUtil.  And then I thought... "What's the point."

See? Progress is possible.   Or maybe I'm just getting old. In Internet Years I'm already like 7,303.



by Steve Theodore (noreply@blogger.com) at December 13, 2014 03:42 AM


December 11, 2014

Qt Designer is harmful, exhibit A

Last week, iker j. de los mozos posted a Qt tutorial on his blog. The post was retweeted a number of times, so I figure people liked it.

The post exemplifies what is wrong with the Qt Designer, and also how a little more investment in learning can pay dividends for your career.

I know it’s unfair to give people code reviews on things they just put out for free, but I consider it even worse to allow people to continue to use the Qt Designer with a clear conscience. I thank Ike for his post, and for syndicating his feed on Planet Tech Art, and hope that no one takes my writeup below personally. It’s not an attack on a person, it’s trying to point out there there is a much better way to do things.

There are 117 lines of Python code in Ike’s tool for locking and unlocking transformation attributes. This sounds like a small amount, but is for an experienced Pythonista it indicates huge waste. For comparison, the entire ZMQ-based request/reply client and server I built for Practical Maya Programming with Python is the same size or smaller (depending on the version). If we take a closer look at his code (see link above), we can see a ton of copy and pasted functionality. This is technically a separate concern from the use of the Designer, but in my experience the two go hand-in-hand. The duplication inherent in GUI tools carries over to the way you program.

Let’s look at some refactored code where we build the GUI in code (warning, I haven’t tried this code since I don’t have Maya on this machine):

from functools import partial
from PySide import QtGui
import pymel.core as pmc
import qthelpers

OPTIONS = [
    dict(label='Position', btn='POS', attr='translate'),
    dict(label='Rotation', btn='ROT', attr='rotate'),
    dict(label='Scale', btn='SCALE', attr='scale')
]

def setLock(obj, attr, value):
    obj.setAttr(attr, lock=value, keyable=not value)

def isAttrLocked(obj, attr):
    return obj.getAttr(attr, q=True, lock=True)

def toggleRowCallback(attr):
    obj = pmc.ls()[0]
    value = isAttrLocked(obj, attr + 'X')
    for axis in 'XYZ':
        setLock(obj, attr + axis, value)

def toggleCellCallback(attr, state):
    obj = pmc.ls()[0]
    setLock(obj, attr, state)

def makeRow(options):
    return qthelpers.row(
        [QtGui.QLabel(options['label'])] +
        map(lambda axis: qhelpers.checkbox(onCheck=partial(toggleCellCallback, options['attr'] + axis)), 'XYZ') +
        qhelpers.button(label='lock ' + options['btn'], onClick=partial(toggleRowCallback, options['attr']))
    )

def main():
    window = qthelpers.table(map(makeRow, OPTIONS), title='lockAndHide UI', base=QtGui.QMainWindow)
    window.show()

Why is this code better? Well, for starters, it’s less than a third of the size (37 lines) and there’s less duplication. These are very good things. When we want to change behavior- such as auto-updating the checkboxes when our selection changes- we can put it in one place, not nine or more.

So the code is better, but what other benefits are there to not using the Designer?
– We pull common primitives, like a “row” (QWidget with HBoxLayout) and “table” into a qthelpers module, so we can use this across all GUIs. This saves huge amounts of boilerplate over the long run, especially since we can customize what parameters we pass to it (like onClick being a callback).
– The GUI is clear from the code because the UI is built declaratively. I do not even need to load the UI into the Designer or run the code to understand it. I can just read the bottom few lines of the file and know what this looks like.
– You learn new things. We use functools.partial for currying, instead of explicit callbacks. This is more complicated to someone that only knows simple tools, but becomes an indispensable tool as you get more advanced. We are not programming in Turtle Graphics. We are using the wonderful language of Python. We should take advantage of that.

Again, I thank Ike for his blog post, and hope I haven’t upset anyone. Ike’s code is pretty consistent with the type of code I’ve seen from Technical Artists. It’s time to do better. Start by ditching the Designer and focusing on clean code.

by Rob Galanakis at December 11, 2014 10:38 AM


Google Project Tango, here we go!!!

We just got word that we were accepted into Google super awesome Project Tango. I can’t describe in words what we are feeling right now but we hope to be able to do some really cool and wicked stuff with it and you can be sure that we will keep you posted!!!

Click here to view the embedded video.

Click here to view the embedded video.

by Artur Leao at December 11, 2014 08:00 AM


December 10, 2014

December Rigging Dojo’s Artist in Residence (A.I.R) : Matt Kapfhammer

December 2014 (A.I.R) this Friday 12th – 9pm EST With Matt Kapfhammer from Weta Digital We would love to have you join the conversation by subscribe here. Our Monthly live web …

The post December Rigging Dojo’s Artist in Residence (A.I.R) : Matt Kapfhammer appeared first on Rigging Dojo.

by Rigging Dojo at December 10, 2014 04:05 PM


December 08, 2014

XMas promo!!! Buy our products for 1€

XMas time is here and we thought we could do something nice for you, so we have a special discount code that you can use to purchase some of our products at 1€! 80% discount :)

The products are:

Just use this code when checking out: XMAS2014

Enjoy it until the 25 of December and Merry XMas :)

by Artur Leao at December 08, 2014 09:23 PM


The QA Department Mindset

From this post by Rands, titled “The QA Mindset”:

At the current gig, there’s no QA department. […]

My concern is that the absence of QA is the absence of a champion for aspects of software development that everyone agrees are important, but often no one is willing to own. Unit tests, automation, test plans, bug tracking, and quality metrics. The results of which give QA a unique perspective. Traditionally, they are known as the folks who break things, who find bugs, but QA’s role is far more important. It’s not that QA can discover what is wrong, they intimately understand what is right and they unfailingly strive to push the product in that direction.

I believe these are humans you want in the building.

At my current job, we don’t have a QA department either. And like Rands, I wasn’t comfortable at first. I’ve worked on teams without QA, but an entire company without a QA Department? I’ve certainly had questions about the use of a QA department, but does that mean they are a bad idea?

Yes, and this line in Rands’ defense is why:

Unit tests, automation, test plans, bug tracking, and quality metrics. The results of which give QA a unique perspective.

I am a staunch believer of “building quality in.” Every bug that slips out is a failure of your development process. The way to higher quality is not to find, or fix, more bugs. It’s to avoid them in the first place.

If you rely on QA to champion unit testing, automation, bug tracking, and quality metrics, your development process is lacking its most important tools and measures to improving quality. Quality can’t be imposed by QA, it must grow out of enabled and engaged development teams.

I have a saying: “Don’t hire to fix a problem.” If you have a quality problem, hiring a QA department isn’t going to fix it. You instead hide the systematic problems that cause quality issues in the first place.

This is not to say “the QA mindset” isn’t valuable. It is. One of my best hires was Bjorgvin Reynisson, who was a Test Engineer at Nokia and I hired as a QA Engineer at CCP. He was embedded with the graphics engine team and he helped them develop extensive automated correctness and performance testing systems. He worked with them to recognized holes in their process and test coverage. He helped with tracking issues and increasing quality. This is the “QA Mindset” I treasure, and this type of person is invaluable to development teams. Bjorgvin unlocked a latent “culture of quality” in the team he was a part of.

I contrast this “QA Mindset” with the “QA Department Mindset“. The QA Department Mindset has two damning characteristics. First, it is innately adversarial, as Rands notes.

Yes, there is often professional conflict between the teams. Yes, I often had to gather conflicting parties together and explain[…]

Second, it is by definition a separate department, which creates obstacles to better integrating engineering and QA.

Bjorgvin should be spending time with his teammates and the rest of the developers figuring out how to improve the entire development process. He should not be spending time with other QA personnel focused on QA functions. When I was Technical Director for EVE Online, I made sure there were minimal discussions gated by job title. Talk of a trade went on in Communities of Practice, which were open to all. Sometimes this didn’t happen, and those times were mistakes.

Like Rands says:

Yes, we actually have the same goal: rigorously confirming whether or not the product is great.

If that’s the case, QA should not be broken out into a separate department. QA should be working side by side, reporting into the same people, measured by the same success metrics, contributing to the holistic success of an entire product.

I love the QA Mindset. It’s tragic that having a QA Mindset gets confused with having a QA Department.

by Rob Galanakis at December 08, 2014 12:45 PM