bodyOffset = Matrix.Invert(RelativeBindPose["hips"]) * body.WorldTransform
Let's derive the source of this expression. ActorSpaceBoneToEntity below is the transform from the bone transform to the entity transform in actor space at initialization.
InitialBoneTransform * ActorSpaceBoneToEntity = InitialEntityTransform
(InitialBoneTransform^-1 * InitialBoneTransform) * ActorSpaceBoneToEntity = InitialBoneTransform^-1 * InitialEntityTransform
ActorSpaceBoneToEntity = InitialBoneTransform^-1 * InitialEntityTransform
So,
assuming that RelativeBindPose["hips"] returns the initial actor space absolute transform of the hips bone (this is a critically important assumption! if it's a relative transform from parent to child, none of this or the original will produce a particularly meaningful result) and the InitialEntityTransform is in actor space (the spaces must be consistent for this process to make any sense), the given code produces the ActorSpaceBoneToEntity. However, the ActorSpaceBoneToEntity isn't necessarily useful by itself.
To demonstrate this, note that the ActorSpaceBoneToEntity was 'built' to be used like entityTransform = boneTransform * ActorSpaceBoneToEntity. This works when given the initial transforms, but watch what happens if you try to use a different bone transform. ActorSpaceBoneToEntity is in actor space, not the local space of the bone. Instead of getting the entity position, it just applies that same relative transform unhelpfully.
If we look at the usage of the ActorSpaceBoneToEntity (bodyOffset):
Code: Select all
boneTransform = bodyOffset * associatedEntity.WorldTransform
we can see that this produces an even less meaningful result. Using the naming convention adopted earlier exposes the issue:
BoneTransform = ActorSpaceBoneToEntity * EntityTransform
Just reading it off in English doesn't sound right. But, this usage does hint at the true desired value of bodyOffset. For that expression to make sense, it should read:
BoneTransform = EntityToBoneInEntityLocalSpace * EntityTransform
By making bodyOffset the offset from the bone to the entity in the entity's local space, you can reconstruct the world transform of the bone if you are given the entity's world transform. Think of it as attaching the offset to the entity; the offset 'stick' is attached to the entity spins and moves with the entity. The end of the stick is where the bone is.
We want to compute that stick. The above can be adapted quite directly and then solved for an expression for EntityToBoneInEntityLocalSpace:
InitialBoneTransform = EntityToBoneInEntityLocalSpace * InitialEntityTransform
InitialBoneTransform * InitialEntityTransform^-1 = EntityToBoneInEntityLocalSpace * (InitialEntityTransform * InitialEntityTransform^-1)
EntityToBoneInEntityLocalSpace = InitialBoneTransform * InitialEntityTransform^-1
Another way to think of this is that the InitialBoneTransform is being 'pulled' into the local space of the entity by multiplying by the initial entity transform inverse. This is the same way cameras work: by multiplying by the camera's view matrix, you are actually multiplying by the
inverse of the camera's world matrix. That 'pulls' objects from world space into camera space (also known as view space).
So, using BoneTransform = EntityToBoneInEntityLocalSpace * EntityTransform with the above construction of an EntityToBoneInEntityLocalSpace transform gets you the BoneTransform in world space (assuming the EntityTransform is in world space).
Be sure to keep track of what space things are in; passing something from world space into a function expecting actor space would be nasty. The below snippet is suspect:
Code: Select all
if (boneIsAttachedtoPhysicsEntity)
boneTransform = bodyOffset * associatedEntity.WorldTransform
else
boneTransform = relativebindPose[bone]
As just described, boneTransform = bodyOffset * associatedEntity.WorldTransform will produce a world space bone transform (assuming associatedEntity is being simulated in world space, and not actor space somehow). However, relativebindPose seems like it will almost certainly not contain a world space bone. Instead, (consistent with the earlier previous assumption) I'd assume it contained an actor space bone transform- or, based on the name, a relative transform from a parent bone to a child bone (which is even less appropriate when compared against the boneTransform = bodyOffset * associatedEntity.WorldTransform computed above). That inconsistency will cause problems.
If we look ahead:
Finally, calculate the skinning matrices in local space, which are then passed to the shader. The root of course has no parent, so its worldTransform ends up being the same as the boneTransform
worldTransform = boneTransform * parentWorldTransform
skinTransform = Matrix.Invert(relativeBindPose) * worldTransform
it appears the previous assumptions are violated. 'boneTransform' is here is used as if it is a local transform from the parent to the child. That is not what was computed by the assumptions used above. Using the stated assumptions, you do not need to multiply by the parentWorldTransform at all. The boneTransform
is the world transform (when computed as BoneTransform = EntityToBoneInEntityLocalSpace * EntityTransform).
There are a variety of ways to make this consistent again. For example:
-You could change up the assumptions so that it tries to work with relative transforms earlier on.
-You could compute BoneTransform = EntityToBoneInEntityLocalSpace * EntityTransform and then pull BoneTransform into the local space of its parent transform so that BoneTransform would then be a relative transform (this is somewhat circular). The boneTransform grabbed directly from relativebindPose when the bone isn't following an entity could be left alone then.
-You could change up the final step to expect world space data, or at least actor space data. Whichever way, make sure the spaces are consistent.
So, there's no one spot where the problem is. There just appear to be various inconsistencies depending on how the system is looked at.
Those sorts of inconsistencies cannot really be debugged by looking only at the final output of a long sequence of transforms- this is where the value of breaking down the problem comes in. Instead of fiddling with different parts of the long pipeline, work with only a single part at a time- like the construction of the initial 'stick' transform to ensure that it is producing exactly what you want. Doing so is hugely easier. Coding additional debug visualization to help view these intermediate stages is almost always worth it if problems are encountered.
In fact, this is so important, it's worth a completely redundant paragraph: in the presence of nontrivial failures, do not attempt to debug the final result of a complex system all at once. It will only cause suffering. Too many things can fail in too many ways. Don't be afraid to make a completely separate program to isolate a single tiny little piece, or to test your understanding of some of the
individual fundamental processes without any interference from other parts of the system. It is one of the best possible uses of time in debugging (hence 'unit testing,' 'test driven development,' and all the related concepts).
(Disclaimer: This was written immediately before snooze time, and, as it is now snooze time, proofreading will be nonexistent. There may be catastrophic errors in the above.)