by Spike » Sun Mar 29, 2015 11:29 pm
everything is relative.
imagine you have a model with a vertex in blender. lets say the vertex is at '16 8 24' in blender's coordinate system. lets also say blender's coordinate system is friendly and matches quake.
moving that coord into quake requires two transformations, but lets start with what happens if neither happen. the engine loads your model, throws it at the screen, and... well, okay, it can't move, its always at '16 8 24'. well that's a pain.
well, okay, if we add the entity's origin to that coord, then it can move around, right? so we do some maths and now we translate that coord to self.origin + '16 8 24'. hurrah, now it can move around!... well, okay, the entity can't rotate and that's a bit of a bummer... we'll need to do something about that...
okay, what happens if we generate 3 directions from our entity, namely forwards, right, and up. we'll assume that the v_forward direction points along the x axis - '1 0 0', the v_right direction points along the y axis - '0 1 0', and finally the v_up direction points along the z axis - '0 0 1', and this then gives us this formula to determine where the vertex is: (self.origin + v_forward*16 + v_right*8 + v_up*24).
doing this, our vertex is now positioned exactly as in the previous origin+'16 8 24' point, but remember that these vectors are strictly perpendicular to each other.
remember how right-angle triangles work? you have a horizontal and vertical axis, and they're perpendicular to each other - exactly like our 3 axial direction vectors.
this is where it gets mathsy.
x*x+y*y=h*h (note: in 3d, you can just add +z*z to the left side there)
HEY! THAT'S PYTHAGORUS!
yeah, well, when you have a direction, h, the hypotenuse, must be 1.
with a 45-degree angle on the hypotenuse, we end up with x and y equal. sqrts says that with a hypotenuse of 1, the result says that both lengths must be something close to 0.707.
so what happens if we determine the position of our point(s) with these vectors: '0.707 0.707 0' '0.707 -0.707 0' '0 0 1' ? our point(s) will have been rotated by 45 degrees around the vertical!, one way or the other.
you're probably asking why one of them has a -0.7 there. well, that's because they need to remain perpendicular. if our forwards vector is now pointing down+right (when viewed from above, instead of just right), then our right vector must be pointing down+left (instead of just down).
note that cos(45) == 0.707. yup, cos(theta) = x/h, while sin(theta) = y/h. note that more interestingly sin(theta) = cos(theta+90). oh look. perpendicular.
right, so, we can do v_forward = [cos(angle), sin(angle), 0]; v_right = [sin(angle), -cos(angle), 0]; v_up = [0, 0, 1]; (yes, I switched to using fteqcc's vector construction stuff, because why not).
and this now allows us to rotate our axis freely around the vertical. This is basically what the makevectors builtin does, where the angle used above is the yaw angle (_y).
makevectors generates 3 axis, rotates them around the vertical by the _y angle, rotates them around the right vector by the _x angle, and then rotates around the forward vector by the _z angle, effectively.
right, now, remember how I said that everything is relative earlier, yeah?
well, lets say that instead of a vertex, its actually a bone. the origins all work out the same at least, but directions are more data, but each individual direction is actually a little easier than the origin - the .origin doesn't matter for determining the bone's directions/axis and thus you can transform the bone's forward/right/up vectors exactly like you transformed the origin, relative to its parent position (ie: parent.origin=='0 0 0'). its basically the same thing - rotating a direction around its parent, whether its a unit vector or a displacement from that origin its basically the same thing.
so by determining the entity's directions/axis, we can determine the directions+origin of the root bone of that entity, and from the root bone, we can determine the next bone, etc, until we end up doing the same with the mesh verticies.
so ... wasn't this about angles?
well, yes, but also no. the whole point of this is to show you that skeletal stuff CRAVES directions (read: matricies). the only reason angles come into the whole thing is because angles is what the engine needs for determining the directions of your entities (besides, its smaller for networking, and qc moders tend to prefer just messing with monster.angles_y instead of 3 entire vectors, plus, angles '0 0 0' is not going to result in singularities while its an infinitely small point if that's meant to be a unit vector - good luck with THAT). If you want to avoid using angles, you can use the renderflags|=RF_USEAXIS thing to directly use the v_forward etc vectors instead of the ent's angles (this naturally means you have to manually regenerate those vectors each frame somehow, but hey, it can be done).
randomly switching around the v_forward etc vectors in the call to vectoangles is just a cheap way to rotate the axis to different coordinate systems.
note that vectoangles only uses a forwards and optionally an up direction. it assumes that the resulting angles will point along the 'forwards' vector (this gives the pitch+yaw values on its own), and uses the 'up' vector purely to rotate the effective right+up vectors around the forwards vector.
for added confusion, vectoangles follows the .angles convention, while makevectors follows the v_angle convention. the difference is whether pitch is positive or negative. this is some silly unfixable id bug which has been mentioned multiple times... fteextensions+fteqcc is meant to include remarks on whether a pitch angle is positive or negative.
oh yes, the thing I didn't mention earlier... the engine uses v_left, not v_right. if z is vertically upwards and x is right-wards/east, and y is northwards - then your y axis is to the left of your forwards axis.
the last thing: matrix transpose.
combing a parent with a relative child is nice and easy and yields a result in the same coordinate system as the parent, but how do you determine those positions relative to the parent?
child = self.origin - self.parent.origin;
makevectors(self.parent.angles);
childrel_x = child * v_forward;
childrel_y = child * v_left;
childrel_z = child * v_up;
repeat for each axis too (but ignore the parent's origin for those...)
this ONLY works when your matrix is built from unit vectors. you'll get inverse scaling as soon as it isn't, and you don't want that. in other cases, you'll need to invert the parent's matrix and use that instead, which is non-trivial.
note that dotproducts like those are useful in other situations - determining how far something is to your right is really quite useful.
note that if you DO scale the v_forward vectors etc, the verticies attached to those bones will be scaled accordingly. such scaling can come from blender too, so have fun with that.
.