So the cross product along with the dot product is the bread and butter of vector math – but I’d never known really whats happening internally. Essentially the cross product of two vectors (two directions), creates a new vector thats orthogonal to them. This means its a vector thats 90 degrees perpendicular to product of the other two.
So if we have two vectors [1,0,0] and [0,1,0], the cross product will be [0,0,1] – Likewise if we swap these vectors the cross product will be [0, 0, -1]. Even though we doing multiplication internally, we’re also doing subraction and vector order matters here. We can use sarrus’ rule, to get the cross product which is like finding the determinant – which i’ll discuss in matrix math:
If we have two vectors, [1, 0, 0] and [0, 1, 0] we can put them into a 3 by 3 matrix, with an imaginary row at the top:
I J K
1 0 0
0 1 0
We’ll get the determinant of each part of the imaginary row. Starting with the I, we’ll disregard any row and column it crosses and keep the rest. So for I it’ll become:
0 0
1 0
Next we’ll multiply the first value of the first row, by the last value of the last row – in this case 0, 0 and subtract it from first value of the last multiplied by the last value of the first row:
I = 1 * ((0 * 0) – (1 * 0)) = 0
This is the first part of a new vector – Lets see how this looks for the whole thing: cross ([1, 0, 0], [0,1,0]) =
I = 1 * ((0 * 0) – (1 * 0)) = 0
J = 1 * ((1 * 0) – (0 * 0)) = 0
K = 1 * ((1 * 1) – ( 0 * 0) = 1
We can see that the last part makes a difference, we’re doing (1 *1) – (0 * 0), so 1 – 0. If we’d have swapped the initial vectors around we’d have (0 * 0) – (1 * 1) = -1. Next up we’ll break into matrices..
A lot of maths I use tends to be abstracted away either in libraries I use, or inside the application. I’m going to go back to basics starting with vector maths, and moving onto matrices – these, in my opinion are the back bone to doing what we do. I’ll cover from the ground up and then go into some more complex areas: determinants, inverse multiplication, decomposition etc. I’ll be learning a bunch of this stuff along the way. Lets get started:
Vectors
So a vector is basically a direction from the origin. [1, 2, 3] basically means we have a point thats moved 1 in the X direction, 2 in the Y and 3 the Z direction.
Vectors can be added together simply by adding the parts of each together. [1, 2, 3] + [4, 5, 6] = [(1+4), (2+5), (3+6)]. Subtraction follows a similar process.
Vectors can be multiplied against a scalar (float) value by multiplying each part by it: [1, 2, 3] * 5 = [(1*5), (2*5), (3*5)].
We can get the length of a vector by firstly, powering each part by 2, then summing (adding up) these parts, and finally getting the square root of the total. This looks like this len([1, 2, 3]) = sqrt((1^2) + (2^2) + (3^2)).
Using this length we can get the normal of the vector. Normalizing a vector keeps its direction, but its length becomes 1.0. This is important in finding angles, unit vectors and matrix scale. To do this we first get the vectors length, and then divide each part of the vector by it:
normal([1, 2, 3]) =
length = sqrt((1^2) + (2^2) + (3^2))
normal = [1/length, 2/length, 3/length]
The dot product of two 3d vectors (x, y, z), basically returns the magnitude of one vector projected onto another. If we have two vectors [1, 0, 0] and [1, 1, 0]; when we project the latter onto the former, the value along the formers length is the dot product. To get the dot product of two vectors we simply multiply the parts together:
[1, 2, 3] . [4, 5, 6] = [(1*4), (2 * 5), (3 * 6)]
We can use the dot product to get the angle between two vectors too. If we first normalize each vector, we can get the angle by getting the inverse cos (or acos) of this dot. This will return a radian, so we can convert it into degrees by multiplying it by (180 / pi):
cos(norm([1,2, 3] . norm([4, 5, 6]) )^-1 * (180/pi)
Next cross products..
I really like Disney’s approach to rigging - with their latests paper instead of building modular units such as ‘arm’ or ‘hand’, they’ve instead devised a method to make the coding itself humanely readable and easy to understand.
Variables & Operators
I’ve been thinking about this myself and essentially it breaks down into two things 1) you need to be able to assign variables and 2) pass operators.
If we take a simple class based function:
create.box(length=1.0, height=1.0, width=1.0)
We have have some base fields – the class, its method, and some arguments passed as assigned types. So what does a field need if it was humanly readable?
- Firstly it would need a value for the fields name.
- It would need to know if it can pass multiple values.
- It would need to know if it passes values or fields.
- It would need some sort of rule on its syntax and definition.
So for the above method we’d have something like this:
CLS create DEF box ARG 3 FLOAT length 1.0 FLOAT height 1.0 FLOAT length 1.0
So pretty straight forward – each of these CLS, DEF etc is a field type, with its own rules that govern what the next input should be. An example with a list field could be as follows:
LIST none 2 FLOAT none 1.0 VECTOR none [1,0,1]
So this is a list field type, which has no assignment (thats why its none), has 2 for the next input because it allows for multiple inputs. The reason why the next input is FLOAT is because crucially, fields need be able to pass other fields as input. So the code would compile to something like this:
#(1.0, [1,0,1])
The ‘#()’, is a syntax definition for the field, along with the ‘,’ – values/fields passed to a field could and probably should have a syntax definition too.
With something like this we could code a framework pretty easily, and because these are just one lines they are pretty easily modded. Here’s an example of say an ik chain:
CLS ik_chain create METHOD none chain ARGS none 3 VECTOR none [0,0,0] VECTOR none [10, 0, 10] VECTOR none [0,0,20]
This would evaluate to something like this:
ik_chain=create.chain([0,0,0], [10,0,10], [0,0,20])
What i don’t like about this is assignment is using none when its not really needed – which i dont like. Crucially i think the string you’d pass should only have what you need. E.g.
CLS ik_chain METHOD chain ARGS 3 VECTOR [0,0,0] VECTOR [10,0,10] VECTOR [0,0,20]
And even the field types themselves could go possibly?
ik_chain create chain 3 [0,0,0] [10,0,10] [0,0,20]
But now we have a problem – by stripping field types etc.. we start to lean towards a structured string. 3 for example can’t be placed anywhere – in fact what does it mean without some association?
So defining fields allows for customization, but we have to be careful to only give enough information for the data to be compiled. My hiccup is that anything can be assigned e.g.
var=1.0
So do we type ‘FLOAT var 1.0′ for it to be assigned and ‘FLOAT none 1.0′ – for it not to be? It’s seems the none is just extraneous info we don’t really need. Could value that gets passed to the rule allow for assignment? e.g.
FLOAT ‘var=1.0′ – we’d have to split this string based on the ‘=’ and then pass the second half to the rule.
CLS ‘ik_chain=create’ METHOD ‘chain’ ARGS 3 [0,0,0] [10,0,10] [0,0,20]
So as I previously mentioned, multi-directional constraints can be achieved by invalidating the transforms. What I did’nt mention is that its pretty damn hard mixing up a group constraint mechanic with a non-group mechanic.
I’m resolving myself to have either one or the other, i.e a single object that can switch to multiple parents, or a group of objects that can share/switch to a single parent. With the group method I need to tweak some things and wrap the switching into a simple function. The structure basically looks/acts like this.
We have a switch attribute, that basically stores its targets, and index of target to switch to. The last target is deemed the world or invalidation target (unless i expose it for change). The reason for this if we have two objects A and B, with both in each others targets we need to invalidate one when it tries to switch to itself. We also need to switch that first E.g.
- A, B both have each others targets, both set to world.
- B switches to A, A is still set to world – the link is fine.
- A switches to B – the causes circular dependency.
So to fix this we walk the controllers and determine which will be invalidate, in this case B trying to connect to B will invalidate itself automatically to the world, allowing A to happily connect to it.
Why is this nice, well the core doesn’t change at all. All we need to build is a check for invalidation based on the index, invalidate and switch in the correct order.
Essentially a switch-box for switches.
I’ll be on sabbatical for the next 6 weeks – it’s going to be great as it’ll be the first time i’ve truly taken a long break from working in the past 11 years! I’ll have time to rest and reset.
I should have some time now to post more – I’ve been deep in the computational maths world for the past 5 weeks or so, turning my brain into a bit of a math soup! which will definitely spill out here bar any NDA specific stuff.
For the life of me I can’t figure this out – If I have a null and a cube at the origin, with the cube rotated and the null placed at [0,20,0] – Setting the nulls transform by multiplying it by that of the cubes should put it into the coordinate space of the cubes. (Essentially orbiting it about the cube)
This doesn’t appear to be the case though, as it appears to be doing the transformation in-place I.e. doing the transform with the first 3 rows and then adding the position part 4th row.
from pyfbsdk import *
box = FBFindModelByName('Cube')
null = FBFindModelByName('Null')
k = FBMatrix()
box.GetMatrix(k)
j = FBMatrix()
null.GetMatrix(j)
m = FBMatrix(j * k)
null.SetMatrix(m)
EDIT:
Just found out the FBMatrixMult - seems to do the job.
I’m working with weak references in a script controller and trying to get around an issue. If the reference is validated incorrectly from keyframe changes (moving, deleting etc) I get a hard script controller error – “illegal cyclic reference”.
It appears nothing can catch the error before the script controller does – callbacks, node events, all happen after the controller is evaluated.
I’d love a way to essentially ping the controller – but im not sure its possible as cyclic-error have to be caught early.
I may have to resort to creating a rollout to move edit keys etc, but that affects usability.
From my earlier posts if we break the idea of a pose being directly attached to weight 1:1, we can place it anywhere in a n-dimensional space made up of multiple weights. E.g.
pose A = weight A @ 1.0
pose B = weight B @ 1.0
pose C = weight A @ 1.0 * weight B @ 1.0
The @ denotes the position along the weight governing its interpolation e.g. if the position was at 0.5 the interpolation would go from 0.0 – 1.0 (0.5) – 0.0. Likewise if the position was at 0.0 the interpolation would go from 1.0 – 0.0 and vice verse if it was at 1.0.
Zero value positions
If we include dimensions with zero values we can have absolute interpolation. We can show this with a 2d space:
Pose A = weight A @ 1.0 * weight B @ 0.0
Pose B = weight A @ 0.0 * weight B @ 1.0
If weight A is at 1.0 and weight B at 0.0 the pose A will be at 1.0, once we start to introduce weight B, pose A gets reduced until 0.0 when weight B is at 1.0.
Mixing additive and absolute poses
This can be done simply by deciding on the dimensionality of the poses. If we have three weights: A, B and C we could have poses for weights A and B additive and absolute for C:
Pose A = weight A @ 1.0 * weight C @ 0.0
Pose B = weight B @ 1.0 * weight C @ 0.0
Pose C = weight C @ 1.0
Plus a corrective could still be applied:
Pose D = weight A @ 1.0 * weight B @ 1.0
Never really needed to do this, but if you try to scale a rotation as a quaternion all you do is scale it’s axis’. The only way I know of is summation:
(
local rot = (eulerAngles 0 45 0) as quat
local sum = quat 0 0 0 1
local scl = 2.5
for i = 1 to floor(scl) do
(
sum += rot
)
sum += slerp (quat 0 0 0 1) rot (mod floor(scl) scl)
)
So Maulik over at his blog is looking into developing multi-directional constraints. I’ve recently started looking into this and wanted to share my thoughts.
Essentially bi-directional/multi-directional constraints are a sub-set of regular transform space switching – crucially the difference comes not in the abstraction and application of spaces but in the ‘messaging’ of transformations. With a standard space switch your basically pushing the source objects transform into the targets and then multiplying it by an offset transform to keep it in its original place. Something like so:
1. We store the transform of B relative to A.
2. We set A’s transform to B’s relative to A’s parent space.
3. We apply the stored offset back.
A = B * A.parent^-1 * Offset
Deriving the type of switch (rotational/entire) is a trivial case of just returning that part of the matrix. With bi-directional/multi-directional constraints everything described above is that same, what changes is the messaging of the transforms being applied. If we take the example of two characters A and B playing a game of tug-of-war, we can see that if they both pull nothing happens. Each is sending its transform notifications to the other and its causing a stalemate. This is our cyclic dependency.
To rectify this you need to control how/when transform messages are sent. On initialization of the system only one party should be sending messages to the other - when the switch happens, you invalidate i.e switch the transform messages being sent to the controlled party with a neutral one e.g world. Now you can send transform messages in the opposite direction from the original controlled party to the controlling one:
1. B sends transform messages to A.
2. Invalidate A (so it’s messages being sent to it are neutral e.g from the world).
3. Send A’s transform messages to B.
Weak References
If you’re software supports weak referencing things get easier – basically a weak reference (in 3ds max) monitors a nodes transforms without referencing the node directly. Only when changes occur does the monitor tell its actual dependencies – so how this works in practice is that you make the weak reference to the node, (an array of targets for example) but only pass transform messages once the nodes been invalidated. There’s some trickery too – adding a blank pointer to the target so it still updates for example.
1. Two nodes: A and B.
2. A’s targets = #(B,world) – weak references without referencing the targets themselves.
3. B’s targets = #(A,world).
4. A’s transform is set to world, B’s set to A.
5. Invalidate B’s by setting it to world.
6. Set A’s transform to B’s
Once you’ve set the transform, its messages flow through correctly. No cyclic nature happens because its has been invalidated first. I will try to discuss more on the ui side of this – it gets a little tricky..
