Six attempts over three years, but finally, IT WORKS !!
I'm currently having a play around with 'character classes' and more advanced skinmesh animation topics such as weight-blending multiple animations, but if you are sick of waiting, just ask and I'll post a binary demo and full source in its current state :)

Posted on 2006-04-03 01:55:10 by Homer
Congraduations mate, and you KNOW I've been waiting to see a demo :>
Posted on 2006-04-03 01:59:19 by Synfire
Hi man
We are all waiting for your demo!  :P

Biterider
Posted on 2006-04-03 04:29:57 by Biterider
Hi Homer,

What an achievement! You persisted, and won! Congratulations.

I'm can't wait to see the results of your great work.
So, please expose the binary and the source to the world.

Friendly regards,
mdevries
Posted on 2006-04-03 15:46:52 by mdevries
OK don't get too excited - its just a start :)

- the framework is not robust
- the gui controls are not implemented, so you can't change animations
- theres no camera controls
- the demo only loads tiny.x, you can't choose another file
- due to the board's file size limit, you'll have to supply tiny.x yourself
- you'll have to mess with the paths of includefiles if you wanna rebuild it.

Please let me know if theres any problems :)
Attachments:
Posted on 2006-04-04 10:13:40 by Homer

I'd like to start an informal discussion of the sourcecode with those who are interested, so I'll begin by describing some of the framework as it stands.

The first class I want to discuss is CModel.
This class is responsible for loading,animating and rendering a model.
The entrypoint method is called LoadXFile.
This method is mostly a wrapper for a call to a DX api function called D3DXLoadMeshHierarchyFromX, which in turn requires from us a pointer to a small callback interface called ID3DXAllocateHierarchy.

Thats right - D3DXLoadMeshHierarchyFromX does not do all the work - it requires us to provide four methods for creating and destroying Frame and Mesh container objects.

So together, CModel and ID3DXAllocateHierarchy look after all the Loading, as well as setting up some linear arrays to aid animation. Loading the model correctly is about 40 percent of the work involved in implementing animated skinmeshes :)

You have the sourcecode in your hot little hands already. Any questions?
Posted on 2006-04-05 09:34:57 by Homer
Hi Homer,

The first thing I tried was building the project.
Ofcourse I had to change some include file paths
But I also had to remove the colon in line 41 in CModel.inc before I could build the project.

	StaticMethod Update, REAL8          ;Was :REAL8


Are you able to build the project without removing the colon in line 41?

Friendly regards,
mdevries.
Posted on 2006-04-05 16:35:58 by mdevries
Yes I can, but my OA32 has been slightly modified by myself over time, and that most likely explains why.. interesting :)

Continuing my rant from yesterday..

ID3DXAllocateHierarchy (callback interface for the meshloader api)
There's four Methods in here.
CreateFrame
CreateMeshContainer
DestroyFrame
DestroyMeshContainer.

The last two are just for cleaning up resources.
CreateFrame method allocates a Frame struct, its not so interesting.
CreateMeshContainer allocates a MeshContainer struct and then proceeds to fill its fields via a bunch of input params.. it  allocates and copies material info,loads Textures,and it sets up arrays of matrices for animating the mesh.
We generally don't call any of these methods ourself.. we're just propping up the D3DXLoadMeshHierarchyFromX api (its our opportunity to extend the structs if we wanna).

I'm expecting some questions soon.
Meanwhile, work continues on code to help with animation blending.


Posted on 2006-04-06 02:54:10 by Homer
Today I wrote a new Method for the CModel class called "SetTrackTransitionKey".
While doing so, I discovered some errors in one of the DX9 includes !!!
The file is D3DX9Anim.inc
The interface is ID3DXAnimationController
The CORRECTION is
  STDMETHOD KeyTrackSpeed,                dword, dword, real8,real8,dword
  STDMETHOD KeyTrackWeight,              dword, dword, real8,real8,dword
  STDMETHOD KeyTrackPosition,            dword, real8,real8
  STDMETHOD KeyTrackEnable,              dword, dword, real8
  STDMETHOD KeyPriorityBlend,            dword, real8,real8


Have a nice day :)
Posted on 2006-04-06 05:50:52 by Homer
Corrected. Thanks.

Biterider
Posted on 2006-04-06 06:47:32 by Biterider
Forgive the chaotic nature of this post, it is a reflection of my state of mind.
I spent a day thinking instead of coding..

I'm going to modify or scrap the new Method I wrote which sets up a transition from track A to track B (one track fades out, and the other fades in simultaneously)
It's based closely on one of the methods from Tiny.cpp used to smoothly transition eg from "walking" to "jogging".

It's a clue to the solution I desire, but it's heading in the wrong direction.
You see, I had been distracted by sourcecode which shows how it is possible to "clone the animationcontroller we obtained when we loaded a skinmesh" in order to create as many AnimationTracks as we want, "so we can have spare tracks for doing transitions".

When we load a SkinMesh,  we receive an AnimationController with N AnimationTracks (where N is equal to the number of AnimationSets) and are not able to create any more without using the Cloning trick described above. I suspect that each of the AnimationTracks are preset to each of the AnimationSets, but if not, we can do it..
My point is that if we want to transition from one AnimationSet to another, we can do so with the existing Tracks, without needing "spare tracks" at all. And if we want to play a blend of every possible AnimationSet at once, we can do it (by enabling all tracks). It's not absolutely necessary to write a manager for AnimationTracks :)

With this in mind, we can define all kinds of complex "animation combos" by thinking of the whole set of AnimationTracks as a "state machine", and by declaring each "animation combo" as a description of the state of all the animation tracks :)

If we display the entire set of AnimationTracks in a gui control, its fairly easy for the User to create any number of "animation combos" and save them off to a datafile :)

Now, can anyone recommend/offer source for a nice propertysheet/spreadsheet/custom listview style of control for runtime editing of the animation track table?
Posted on 2006-04-07 04:36:25 by Homer
Today:
I added a System Menu to the GUI, and set up some Load and Unload menuitems.
You can now select any xfile to open.

I fixed an "intermittent bug" within ID3DXAllocateHierarchy  (it only happened sometimes.. due to a register being trashed).

I added a ListView control to the GUI, which displays all the AnimationTracks and their settings, and a handful of smaller controls for altering the settings of the selected track.
The ListView content is rebuilt whenever we Load a skinmesh.

Does anyone want to donate a model with several animations?
Posted on 2006-04-07 23:37:54 by Homer
Since I feel confident about the pending implementation of blended animations, I spent this evening researching the implementation of "RagDoll Physics" with respect to my existing framework (ie, modern skinmesh-based ragdolls as opposed to classic multipartite systems). I have to say that it was quite interesting reading... my eyes have been opened (:shock:), it was well worth the time spent !!!

For those who are interested in checking out some cool advanced stuff (very well written and a joy to read), here's a link:
http://talha.zabvision.edu.pk/Game%20Programming/AdamsAdvancedAnimationwithDirectX/J.Adams%20-%20Advanced%20Animation%20with%20DirectX.pdf

It just so happens that I spent some time working on an ODE physics project not too long ago, so it's not such an impossible dream to implement the same kind of system described in the aforementioned document...  :D

What I have in mind :
1 - Ability to switch from Animated mode to RagDoll mode when character dies or is falling.
2 - Ability to disable one or more limbs of Animated character (which flop about uselessly).
3 - Ability to disable one or more limbs of Animated character (limb is being controlled by the user).

Sound like fun? :)
Posted on 2006-04-08 07:39:05 by Homer

In order to implement RagDoll physics I'll need to "create a Doll from my model".
Basically I need to create a BoundingBox for each Bone.
The orientation of each box is that of the Bone it encompasses.
To be more precise, for each Bone, find the set of vertices affected by this Bone, and find the boundingbox of the set of affected vertices. Then expand the box to include its connection points with other Bones if necessary.
It makes a heck of a lot of sense to me to add an option to the Loader to perform this step within ID3DXAllocateHierarchy, here's why:
One of the things that ID3DXAllocateHierarchy does is create an array of pointers to "bone offset matrices". IE, for each Bone, we obtain a ptr to that bone's offset matrix, and store all the pointers in an array.
It just so happens that in order to build our 'boneboxes', for each Bone, we need to obtain the bone offset matrix in order to transform the affected vertices from modelspace into bonespace (a requirement).
We may as well calculate each "bonebox" during this loop in ID3DXAllocateHierarchy :)
The cost is that my ID3DXAllocateHierarchy implementation becomes dependant on CModel, the FRAME struct is extended to include a boundingbox, and a bool switch is added to CModel's Init method to choose whether to prepare RagDoll stuff..

What's your opinion?
Should CModel support RagDoll mode implicitly, so we don't need CRagDoll?
Should CRagDoll inherit from CModel instead, so that ID3DXAllocateHierarchy remains independant from CModel?
Perhaps you have a better idea?
Posted on 2006-04-09 02:27:03 by Homer
I've decided to make a new class called CRagDoll which derives from CModel.
Here's a sneak preview of how I am spending my time :)

I'm extending the CModel class to support RagDoll physics.
The attachment shows how I intend to attach physics information to the BoneFrames, and how I intend to work with a hierarchy of Bone-oriented boundingboxes for my RagDoll physics.
It contains code for attaching the extension struct and for calculating the "BoneBoxes".

As for the animation blending stuff, it will be implemented in CModel as originally intended.
Attachments:
Posted on 2006-04-09 08:13:59 by Homer
Here's how it works:

A model starts in "Animated" mode.
It's wandering happily along when suddenly (think of a reason) it dies, and in doing so, switches from "Animated" mode to "RagDoll" mode.
At this point, we need to initialize our Physics state to reflect the orientation of the Bones at the moment of death.
From now on, we'll be controlling the matCombined matrices in the BoneFrames : they will become outputs for the bonebox physics. The physics engine will drive the matCombined fields of the BoneFrames, and rendering will be as usual.

An alternative/extension idea is to have the "Mode" flag on each Bone, meaning that we could "disable a model's wounded arm" so it flops about :)

Posted on 2006-04-09 09:08:16 by Homer
STAGE 1 : EXTEND OUR BONEFRAME STRUCTURE

The very first thing that I did was further extend our FRAME struct to include a new field.
;Our extended D3DXFRAME struct
FRAME struct
;D3DXFRAME members
Base D3DXFRAME <>
;Extra stuff
matCombined D3DXMATRIX <>;Combined Frame Matrix
pRagDollBone dd ? ;Optional : RagDoll physics data
FRAME ends


The addition of one 32bit pointer isn't very expensive for implementations that don't require RagDoll support, but it's quite a powerful tool for us, because it actually points to another allocated struct, RagDollBone, which contains all kinds of per-bone data for the physics simulation.

Don't worry too much about all these fields, I don't want this thread to turn into a discussion on ODE physics (yet?)..
RagDollBone struct
; Frame that this bone is connected to
m_Frame dd ?
; Mass and 1/Mass (one-over-mass)
m_Mass REAL4 ?
; Coefficient of restitution value
; 0 = no bounce
; 1 = 'super' bounce
; >1 = gain power in bounce
m_Coefficient REAL4 ?
m_ParentBone dd ?;Pointer to parent BoneFrame
; Connection-to-parent offset and parent-to-bone offset
m_vecJointOffset Vec3 <>
m_vecParentOffset Vec3 <>
; Size of BoneBox
m_vecBBSize Vec3 <>

; Linear force and angular momentum
m_vecForce Vec3 <>
m_vecTorque Vec3 <>
; Original orientation of bone
m_quatOrientation D3DXQUATERNION <>
; Rate of resolution (0-1) to resolve slerp interpolation
; This is used to make bones return to their initial
; orientation relative to its parent.
m_ResolutionRate REAL4 ?
; Body's inverse moment of inertia tensor matrix
m_matInvInertiaTensor D3DXMATRIX <>
; Points (in body space) that form bounding box
; and connection-to-parent offset position
m_vecPoints Vec3 9 dup (<>)
; Bone state
m_State RagDollBoneState <>
RagDollBone ends


That last field is an embedded struct, RagDollBoneState. Again, don't fret about this struct, I am only including it here to complete the picture..
;This struct describes the Physics state
;of a Bone, its BoundingBox, etc
RagDollBoneState struct
m_vecPosition Vec3 <> ;  Position
m_vecAngularMomentum Vec3 <> ;  Angular momentum
m_vecLinearVelocity  Vec3 <> ;  Linear velocity
m_vecAngularVelocity Vec3 <> ;  Angular velocity
m_quatOrientation D3DXQUATERNION <> ;  Orientation
m_matOrientation  D3DXMATRIX    <> ;  Orientation
; Transformed points, including connection-to-parent
; position and parent-to-bone offset
m_vecPoints Vec3 10 dup (<>)
RagDollBoneState ends


OK, so we're extending the FRAME struct by adding a pointer to a blob of data that contains all that crap I just posted.. This is only relevant to "Frames which represent Bones", so there's likely to be one or two Frames that won't need extending.

Now, the next thing I did immediately is add a line to ID3DXAllocateHierarchy::DestroyFrame method, which deallocates our per-Frame pRagDollBone extension if it exists,  so that garbage collection of our extension structs is automatic.

Posted on 2006-04-10 01:56:20 by Homer
STAGE 2 : EXTEND OUR LOADER

This posting will interest those of you who are not very familiar with OOP.

I needed a way to apply my extension to the bone-representative Frames in CModel..but I didn't want to mess up CModel with non-essential stuff.
I decided to use a little oop magic so that I could have it both ways :)

Next thing on the agenda was to begin implementing a CRagDoll class, which derives from (inherits) the CModel class.
The first method I wrote for the CRagDoll class was a new LoadXFile method.
It doesn't replace the one in CModel, it enhances it.
Check it out :)

;Overloaded version of LoadXFile
;We load the model, then we set up the physics
Method CRagDoll.LoadXFile,uses esi,pstrFile
SetObject esi
;First, perform a standard CModel.LoadXFile call
ACall esi.LoadXFile,pstrFile
push eax ;Preserve retval
;Now, setup physics information in the BoneFrames
OCall BuildBonePhysics,.m_pFrameRoot,NULL
pop eax ;Restore retval
MethodEnd


In CRagDoll's class definition, I used "RedefineMethod LoadXFile,Pointer"

ACall is "AncestorCall" - it means "call a named method in the class from which the current class is derived".. Since CRagDoll derives from CModel, the ACall is referring to a method in CModel, not a method in CRagDoll.. clear as mud?

So, looking at the new LoadXFile method,  it's really doing two things.
Firstly it ACalls the old CModel version of LoadXFile, then it OCalls a new method in CRagDoll called BuildBonePhysics.. which doesn't exist yet.

Just looking at the OCall, more experienced coders will sense that it's probably a recursive function entrypoint, since it has two parameters, one of them being a node struct (pFrame) and the other being initially NULL (likely used to pass data between recursion depths)

This is indeed the case.
The first parameter is used as a nodepointer to walk the frame hierarchy, and the second parameter is used to track the Parent of the current node (root's parent is NULL).

In order to extend the BoneFrames, we need to walk the entire frame hierarchy, looking for BoneFrames.. that's a recursive job, so we use a recursive method (BuildBonePhysics).

BuildBonePhysics does more than just extend the BoneFrames for physics though, its also responsible for calculating a BoundingBox for each Bone, as outlined previously.

I'm not showing you BuildBonePhysics today.

The last thing I did was to replace all the references to CModel in the main ASM file with references to CRagDoll.
Even though CRagDoll is just an infant with bugger all Methods, we mustn't forget that it inherits everything that CModel has - all the functionality, all the data fields.
You might say "any calls to methods that we did not OverRide (like we did to LoadXFile) fall through to the Ancestor class" (although its not really implemented that way..)

So, excepting LoadXFile, all our CModel methods are available within CRagDoll.
CRagDoll enhances CModel :)

Can I have some feedback please?
Am I going too fast? Have I dumbed it down too much? Is this stuff too weird? etc..
Posted on 2006-04-10 02:36:12 by Homer
Just keep going, Homer - lack of feedback might be because you're mixing a load of non-trivial topic; OOP, D3D, Bone physics etc. Quite a mouthful to swallow for many.

Don't dumb things down, though. If you want more feedback, I guess you should start a somewhat simpler side-project :)
Posted on 2006-04-10 05:08:00 by f0dder
STAGE 3 - BuildBonePhysics

I admit it, I haven't completed this Method yet.
I'm not filling all the fields yet.. I skipped ahead to work on a few of the physics-related methods in CRagDoll..
But I'll outline what happens in BuildBonePhysics anyway.

It's a recursive function. The first thing it does is check its pFrame parameter as to whether the Frame has a name.. only BoneFrames are named in our hierarchy, so if the Frame is named, its a BoneFrame.
Having detected a BoneFrame, a call is made to a method called ExtendFrame, which allocates a new RagDollBone struct and stores its ptr in Frame.pRagDollBone,
then a call is made to a method called GetBoneBoundingBoxSize which calculates the BoundingBox of a Bone's vertices and attachmentpoints.
My current code fails to initialize all the other fields in the RagDollBone struct.
At the end of the method is code to walk the sibling and child frames, and thats all folks.

Heh, so now I guess I'll have to show you the ExtendFrame and GetBoneBoundingBoxSize methods :)
With all the code mentioned so far put together, we're in a position to initialize everything we need to play with dolls like girly men .. all that's missing (aside from the aforementioned new methods) is the actual physics code, right? Pretty much :)
I've already made a good start on that stuff, as I mentioned, but I'm far from satisfied.
Right now I'm using euler integration and a variable timestep (like my reference material) but I might later change/add to this a fixed-step Runge-Kutta integrator.. we'll see :)

As I was saying, once I post the new methods, we have enough code to surround our animated thingy in a series of boxes.. it'd be a good time to add code to render the boxes as lines during animation playback, and then clean up the codebase before proceeding.

The next step after that is to implement the physics code, add code for collision detection between bodyparts that are not neighbours (what I think is the bare minimum for a ragdoll demo) and then add some clever tools to the gui to allow the user to apply forces to the demo model (so it can slap itself silly), and then regroup (cleanup and review) again.

Somewhere inbetween I'll finish implementing the animation blender controls..


Posted on 2006-04-10 09:05:13 by Homer