ExtendFrame is really simple - here it is:
;Extend a Bone's FRAME struct for RagDoll Physics
;Noe that ID3DXAllocateHierarchy now cleans up this resource
Method CRagDoll.ExtendFrame,uses esi,pBoneFrame
mov ebx,pBoneFrame
mov .FRAME.pRagDollBone,$MemAlloc(sizeof RagDollBone,MEM_INIT_ZERO)
MethodEnd


BuildBonePhysics is simple too, if we look at it as a series of steps.
First, here's a helper method that we'll require.
;Search for a Bone whose Name matches that of the given Frame
;Returns: index of matching Bone, or -1 for failure
Method CRagDoll.BoneIndexFromBoneFrame,uses esi,pBoneFrame
LOCAL pSkin
LOCAL numBones
LOCAL BoneNum
SetObject esi
.if .m_pFirstMesh==0
DbgWarning "NULL m_pFirstMesh","CRagDoll.BoneIndexFromBoneFrame"
invoke ExitProcess,0
.endif
mov ebx,.m_pFirstMesh
m2m pSkin, .MESHCONTAINER.Base.pSkinInfo
; Search for a bone with same name as frame
mov numBones,$ICall (pSkin::ID3DXSkinInfo.GetNumBones)
mov BoneNum , -1
xor ecx,ecx
.while ecx<numBones
push ecx
ICall pSkin::ID3DXSkinInfo.GetBoneName,ecx
mov ebx,pBoneFrame
invoke lstrcmp,eax, .D3DXFRAME.pName
pop ecx
.if eax==0
mov BoneNum ,ecx
.break
.endif
inc ecx
.endw
mov eax,BoneNum
MethodEnd


Now for the motherlode: note that this Method is not quite complete.
;Calculate a BoundingBox for a BoneFrame
;which encloses all vertices affected by
;the Bone, as well as the ConnectionPoints
;between the Bone and any other Bones.
;The resulting Box info is stored in
;the Frame's RagDollBone extension struct.
Method CRagDoll.GetBoneBoundingBoxSize,uses esi,pBoneFrame
LOCAL pSkin:ptr ID3DXSkinInfo
LOCAL pMesh:ptr MESHCONTAINER
LOCAL pFrameChild
LOCAL numBones ;#Bones in Model
LOCAL BoneNum ;Index of our Bone
LOCAL NumVertices ;#Vertices attached to our Bone
LOCAL Stride ;#Bytes per Vertex in original Mesh
LOCAL Vertices ;ptr to array of dword-sized Vertex Indices
LOCAL Weights ;ptr to array of real4 sized Skin Weights
LOCAL pVertices ;ptr to original mesh VB
LOCAL pvecPtr ;
LOCAL vecPos:Vec3
LOCAL matInvFrame:D3DXMATRIX
LOCAL pmatInvBone ;ptr to Bone's Inverse Offset Matrix
LOCAL vecMin:Vec3
LOCAL vecMax:Vec3


; Set default min and max coordinates
and vecMin.x,0
and vecMin.y,0
and vecMin.z,0
and vecMax.x,0
and vecMax.y,0
and vecMax.z,0

;The first order of business in GetBoundingBoxSize is
;to find the bone that matches the frame's name.
;This bone, or rather the skinned mesh bone interface object
;(ID3DXSkinInfo), queries which vertices are connected to the bone.

; Only process bone vertices if there is a bone to work with..
; Interesting Fact : BoneFrames are Named, other frames are not.
mov ebx,pBoneFrame
.if .D3DXFRAME.pName!=0
; Get a pointer to ID3DXSkinInfo interface for easier handling.
SetObject esi
.if .m_pFirstMesh==0
DbgWarning "NULL m_pFirstMesh","CRagDoll.GetBoneBoundingBoxSize"
invoke ExitProcess,0
.endif
mov ebx,.m_pFirstMesh
mov pMesh,ebx
m2m pSkin, .MESHCONTAINER.Base.pSkinInfo

;Search for a Bone whose name matches the name of the Frame
mov BoneNum,$OCall (BoneIndexFromBoneFrame,pBoneFrame)

; Only continue if we found our target bone by name
.if BoneNum != -1
;After you've found an ID3DXSkinInfo for the bone in question,
;you query it for the number of vertices attached and allocate
;arrays of DWORD and REAL4 values to hold the vertex indices
;and weights.

; Get the number of vertices attached to the bone
mov NumVertices,$ICall(pSkin::ID3DXSkinInfo.GetNumBoneInfluences,BoneNum)
.if eax!=0
; Get stride of vertex data
mov ebx,pMesh
ICall .MESHCONTAINER.Base.MeshData.pMesh::ID3DXMesh.GetFVF
mov Stride ,$invoke (D3DXGetFVFVertexSize,eax)

; Get bone's offset inversed transformation matrix
mov pmatInvBone,$ICall (pSkin::ID3DXSkinInfo.GetBoneOffsetMatrix,BoneNum)

;Allocate temp memory for arrays
;of vertex indices and skin weights
shl eax,2 ;*4 for dword sized
push eax
mov Vertices ,$MemAlloc(eax)
pop eax
mov Weights  ,$MemAlloc(eax)
; Get the bone influences (vertices and skinweights)
ICall pSkin::ID3DXSkinInfo.GetBoneInfluence,BoneNum, Vertices, Weights

;Now that the vertex indices are stored in the Vertices buffer
;(which you accomplished by calling GetBoneInfluence), you can
;begin iterating through each vertex, transforming the vertices
;by the bone's inverse transformation and using the transformed
;vertices to calculate the size of the bounding box.

; Lock vertex buffer and go through all of
; the vertices that are connected to our bone
mov ebx,pMesh
ICall .MESHCONTAINER.Base.MeshData.pMesh::ID3DXMesh.LockVertexBuffer,D3DLOCK_READONLY, addr pVertices

xor ecx,ecx
.while ecx<NumVertices
push ecx
; Get pointer to vertex coordinates
;pvecPtr = pVertices+Vertices*Stride
shl ecx,2
add ecx,Vertices
mov eax,dword ptr ;eax=Vertices
mul Stride ;eax=Vertices*Stride
add eax,pVertices ;eax=pVertices+Vertices*Stride
mov pvecPtr,eax
; Transform vertex by bone offset transformation
;(ie from Modelspace to Bonespace)
;This makes the vertices relative to the Bone instead of
;to the Origin of the Model.
invoke D3DXVec3TransformCoord,addr vecPos, pvecPtr, pmatInvBone

;Get min/max values
fMin vecMin.x,vecPos.x
fstp vecMin.x
fMin vecMin.y,vecPos.y
fstp vecMin.y
fMin vecMin.z,vecPos.z
fstp vecMin.z
;
fMax vecMax.x,vecPos.x
fstp vecMax.x
fMax vecMax.y,vecPos.y
fstp vecMax.y
fMax vecMax.z,vecPos.z
fstp vecMax.z

pop ecx
inc ecx
.endw

;Unlock VB
mov ebx,pMesh
ICall .MESHCONTAINER.Base.MeshData.pMesh::ID3DXMesh.UnlockVertexBuffer

; Free temp resources
MemFree Vertices
MemFree Weights
.endif

;At the end of this bit of code, you'll have the extents of
;the bounding box stored in the two vectors (vecMin and vecMax)
;you instanced at the beginning of the function.
;The array of vertex indices is freed (as well as the vertex weights),
;and processing continues by accounting for the point where
;the bone connects to its parent and child bones.

;To factor in the connection points, you basically grab
;the world-space coordinates of the connected bones and
;transform them by the bone's inverse transformation.
;These coordinates are then compared to the coordinates
;stored in the vecMin and vecMax vectors.

; Factor in child bone connection points to size
mov ebx,pBoneFrame
.if .D3DXFRAME.pFrameFirstChild!=0
; Get the bone's inverse transformation to
; position child connections.
mov ebx,pBoneFrame
invoke D3DXMatrixInverse,addr matInvFrame,NULL,addr .FRAME.matCombined

; Go through all child frames connected to this frame
mov ebx,pBoneFrame
m2m pFrameChild,.D3DXFRAME.pFrameFirstChild
.while pFrameChild!=0
; Get the frame's vertex coordinates and transform it
mov ebx,pFrameChild
fld .FRAME.matCombined.m30
fld .FRAME.matCombined.m31
fld .FRAME.matCombined.m32
fstp vecPos.z
fstp vecPos.y
fstp vecPos.x
invoke D3DXVec3TransformCoord,addr vecPos, addr vecPos, addr matInvFrame

;Get min/max values
fMin vecMin.x,vecPos.x
fstp vecMin.x
fMin vecMin.y,vecPos.y
fstp vecMin.y
fMin vecMin.z,vecPos.z
fstp vecMin.z
;
fMax vecMax.x,vecPos.x
fstp vecMax.x
fMax vecMax.y,vecPos.y
fstp vecMax.y
fMax vecMax.z,vecPos.z
fstp vecMax.z

; Go to next child bone
mov ebx,pFrameChild
m2m pFrameChild , .D3DXFRAME.pFrameSibling
.endw
.endif

;You can now finish the function by storing the size of the box.
;If the box is too small, set the size to a minimum amount
;(MINIMUM_BONE_SIZE is set to 1.0f).

; Calc the bounding box size
mov eax,pBoneFrame
mov eax,.FRAME.pRagDollBone
fld  vecMax.x
fsub vecMin.x
fabs
fstp .RagDollBone.m_vecBBSize.x
;
fld  vecMax.y
fsub vecMin.y
fabs
fstp .RagDollBone.m_vecBBSize.y
;
fld  vecMax.z
fsub vecMin.z
fabs
fstp .RagDollBone.m_vecBBSize.z

; Make sure each bone has a minimal size
mov eax,pBoneFrame
mov eax,.FRAME.pRagDollBone
fMin .RagDollBone.m_vecBBSize.x,MINIMUM_BONE_SIZE
fstpReg ebx
.if ebx == .RagDollBone.m_vecBBSize.x
mov eax,pBoneFrame
fld MINIMUM_BONE_SIZE
fst .RagDollBone.m_vecBBSize.x
fmul fHalf
fstp vecMax.x
.endif

mov eax,pBoneFrame
mov eax,.FRAME.pRagDollBone
fMin .RagDollBone.m_vecBBSize.y,MINIMUM_BONE_SIZE
fstpReg ebx
.if ebx == .RagDollBone.m_vecBBSize.y
mov eax,pBoneFrame
fld MINIMUM_BONE_SIZE
fst .RagDollBone.m_vecBBSize.y
fmul fHalf
fstp vecMax.y
.endif

mov eax,pBoneFrame
mov eax,.FRAME.pRagDollBone
fMin .RagDollBone.m_vecBBSize.z,MINIMUM_BONE_SIZE
fstpReg ebx
.if ebx == .RagDollBone.m_vecBBSize.z
fld MINIMUM_BONE_SIZE
fst .RagDollBone.m_vecBBSize.z
fmul fHalf
fstp vecMax.z
.endif

; Set the bone's offset to center based on half the size
; of the bounding box and the max position
mov eax,pBoneFrame
mov eax,.FRAME.pRagDollBone
lea ebx,.RagDollBone.m_vecJointOffset
lea eax,.RagDollBone.m_vecBBSize
fld .Vec3.x
fmul fHalf
fsub vecMax.x
fstp .Vec3.x
;
fld .Vec3.y
fmul fHalf
fsub vecMax.y
fstp .Vec3.y
;
fld .Vec3.z
fmul fHalf
fsub vecMax.z
fstp .Vec3.z
.endif
.endif
MethodEnd


It's important to note that "the boneboxes are defined in bonespace and must be transformed back into modelspace before self-collision tests, and then into worldspace before testing against anything else".
The reason they are defined in bonespace is so that they are "bone-aligned", so that we can strongly associate the orientation of a bone and its box.. in fact, they share the same  transformation matrix.

This means when the physics orients our boxes, and thus updates the orientation matrix, the bone orientation changes to suit - it can't help it - it's using the same matrix :)

Now you can see why I chose to extend the FRAME struct the way I did - we're strongly associating a "box physics object" with each "bone frame object".

In the next day or two I'll post an update of the entire project source + binary :)
Questions?
Posted on 2006-04-10 11:41:42 by Homer
We should be in a position now to be able to call our entrypoint method (CRagDoll::LoadXFile) and have the BoneBoxes automatically generated to suit our Bones.
We're actually only describing the Boxes in terms of a pair of min/max corner values, which won't quite cut the mustard - we really ought to describe the Box as a set of eight Points (we already defined an array to hold them).

Note once more that the Boxes are NOT axially-aligned ... they are Bone-aligned.
In terms of collision detection, I'm only going to perform point-based collision tests.
We will test the eight box points against various spheres and planes.

Since the Boxes overlap each other slightly in ModelSpace, we will not perform collision testing between the boxes of any Bones that are directly connected.
IE, there will be NO testing for collision between LeftUpperArm and LeftLowerArm, but there WILL be testing for collision between LeftUpperArm and LeftHand.
This means that we're allowing the skinmesh to self-intersect at the "creases" formed by the joints between bones.

I intend to implement rendering of the BoneBoxes during Animated mode as my next step, to make it obvious what the heck all this BoneBox business is about.
Next is to implement the switch to RagDoll mode, and enough collision-detection to test for (if not resolve) hits between "boneboxes that are not sharing a joint".

The physics code is practically complete now, but I choose to leave it unimplemented until the bonebox stuff is proven .. For now, I'm just gonna pretend those methods don't exist ;)

I know, I know, I'm waffling on about stuff you can't actually SEE, and not sharing updates of the binary as often as I could.. I'll repost when I think the framework has reached its next milestone, and until then, you'll have to be satisfied with the snippets of code I'm posting along the way.

I'm inclined to separate the physics code from the CRagDoll class, so I'll try to write it in a way that lends itself toward encapsulation, and then when its working, if I still feel justified in doing so, I'll create one or more physics-specific object classes.

Has anyone been playing with the earlier project framework since I posted it?
I'm just curious who I'm actually talking to, aside from writing a reference for myself..
Posted on 2006-04-11 01:51:54 by Homer
Hi Homer
I haven't played nor compiled your code, but I'm interested in how you create the "BoneBoxes" since I need something similar for my parts/assemblies of my CAD project.

Biterider
Posted on 2006-04-11 03:40:42 by Biterider
Let's review how to construct the BoneBoxes :)

For any Bone, we are able to obtain several different things. For the problem of determining the bounds of a Bone's influence, we want:
1 - a transformation matrix which moves us from the origin of the Bone to the origin of the Model - it's known as an "inverse bone offset matrix", and it's simple to obtain.
2 - the subset of vertices which are affected by the Bone (given as a set of indices).

Once we have these two things, we can look up each affected vertex and then transform it from modelspace into bonespace using the inverse bone offset matrix. We can do this for all the vertices in the affected subset, and track the min and max values as we go, discarding the vertices as we go, since we're only obtaining the bounds.
The end result of this is a pair of min/max values describing the untransformed boundingbox.

You might care to imagine this as "transforming the affected bodypart to the origin so that our boundingbox is relative to the bone origin, rather than the model origin".
It also means that the boundingbox now shares the Bone's transformation matrix ;)

We've now constructed one or more Bone-oriented boundingboxes, all that remains is to transform them back from bonespace to modelspace (using the inverse of the previously used matrix) but we don't do that until after we're done manipulating it (be it via animation or physics), because as long as the box is in bonespace, its easier to orient, and since the Bone and its Box share a common matCombined matrix, their orientations are in fact locked together.. this is why its so useful to bone-align the boxes :)

Posted on 2006-04-11 04:24:38 by Homer
Hi Homer,

Has anyone been playing with the earlier project framework since I posted it?
I'm just curious who I'm actually talking to, aside from writing a reference for myself..


In the next day or two I'll post an update of the entire project source + binary


I can assure you this is the most important thread I'm interested in at the moment.
As I told you I compiled the project. And I will do so again.
So you are talking at least to me.

Keep on going. It's most interesting.
It's not only a reference for yourself, but also for the ones who are looking over your shoulder at the moment, and the ones who will will be reading this thread in the future.

Am I going too fast? Have I dumbed it down too much? Is this stuff too weird? etc..


Regarding this I can say: Keeping things simple is not so bad. It's better to overexplain something than telling to little. In the end it will attract more people, even after this thread has been closed for a long time.

Friendly regards,
mdevries
Posted on 2006-04-11 13:08:38 by mdevries
keep going Homer
busy atm with artwork and lightmap renderer
Posted on 2006-04-11 13:37:56 by daydreamer
OK, let's keep going :)

One of the most interesting things I gained from studying Microsoft's MultiAnim demo was learning how to set up an animation event callback for the purpose of timing a sound effect.
That demo used an xfile called something like "Tiny4Anim.x", it had 4 animationsets for the character (loiter, walk, jog, and 'look at wristwatch'). The walk and jog animations had special callbacks set up to play a "footfall" sound effect at hardcoded positions in the respective animationsets. I'm DEFINITELY going to implement something like this - not just for footfalls but particularly for weapon attacks. I'll implement this as a side-note just after I implement the animation blending controls, allowing you to define your own timed animation soundeffects via a common callback, at runtime, via the gui :)

Weapons are a problem - they tend to require their own animations, and they fall into at least two categories : one and two handed.

My current thinking is to import all weapons that a character can wield into the same Scene as the character, and create extra joints in the hands which are later used as weapon attachment points/orientation controls, then one by one, attach each weapon to the model, create the attack animations, and detach weapon from model - it's the hand joints that will orient our weapon during attacks, the weapon itself isn't necessarily animated (this is cool to mention though - our weapon mesh can be another animated skinmesh if we really want, with its OWN AnimationSets...)

There's another of these so-called "attachment points" placed near the rectum of the character, which is used to attach the model to a saddle or chair, and at least two more on the character's feet, used to sense the groundplane, eg climb stairs (basically we need to fudge the animation with respect to the terrain).

Can you guys think of any more useful "attachment points", or situations where the ones I described are unsufficient?
Who's good with Maya or other high end modeller and knows how to export user data in XFile format, and/or can supply a demo model with N animations for use in this free educational project?

Posted on 2006-04-12 03:51:32 by Homer
I spent yesterday making a series of minor changes to the framework, and am happy to report that I can now load Tiny4Anim.x, and that the ListView now correctly displays the state of the AnimationController.. also, I modified my SetTrackTransitionKey method to set up a timed animation blending event which fades out one existing AnimationTrack and simultaneously fades another one in.. what remains is to process doubleclicks on the ListView control for selecting a new Animation, and using the transition code to smoothly fade to the new animation .. and to implement the blender editing controls, below the ListView.

BoneBoxes are being calculated, but I have no way to prove they are correct yet.

Anyway, here's an update of the Binary so you can see what's been going on :)
I think that next  I'll add some code to display the boneboxes while the animation plays..

PS : Since I moved the code for Loading a skinmesh to be driven by the GUI's menu controls, I've noticed an intermittent GPF that occurs SOMETIMES, but usually not.
It occurs inside the D3DXLoadMeshHierarchyFromX api, and appears NOT to occur within our ID3DXAllocateHieraerchy callbacks, or be due to register preservation during the callbacks... can anyone else verify/reproduce this problem?

Have a nice day :)
Attachments:
Posted on 2006-04-13 23:41:58 by Homer

Another small bug in D3DX9Anim.inc
Interface is ID3DXAnimationController
Correction is:
  STDMETHOD SetTrackPosition,             dword, real8


Happy nailing of the godhead, everyone.
Posted on 2006-04-14 02:38:51 by Homer

OK, let's keep going :)

One of the most interesting things I gained from studying Microsoft's MultiAnim demo was learning how to set up an animation event callback for the purpose of timing a sound effect.
That demo used an xfile called something like "Tiny4Anim.x", it had 4 animationsets for the character (loiter, walk, jog, and 'look at wristwatch'). The walk and jog animations had special callbacks set up to play a "footfall" sound effect at hardcoded positions in the respective animationsets. I'm DEFINITELY going to implement something like this - not just for footfalls but particularly for weapon attacks. I'll implement this as a side-note just after I implement the animation blending controls, allowing you to define your own timed animation soundeffects via a common callback, at runtime, via the gui :)

Weapons are a problem - they tend to require their own animations, and they fall into at least two categories : one and two handed.

My current thinking is to import all weapons that a character can wield into the same Scene as the character, and create extra joints in the hands which are later used as weapon attachment points/orientation controls, then one by one, attach each weapon to the model, create the attack animations, and detach weapon from model - it's the hand joints that will orient our weapon during attacks, the weapon itself isn't necessarily animated (this is cool to mention though - our weapon mesh can be another animated skinmesh if we really want, with its OWN AnimationSets...)

There's another of these so-called "attachment points" placed near the rectum of the character, which is used to attach the model to a saddle or chair, and at least two more on the character's feet, used to sense the groundplane, eg climb stairs (basically we need to fudge the animation with respect to the terrain).

Can you guys think of any more useful "attachment points", or situations where the ones I described are unsufficient?
Who's good with Maya or other high end modeller and knows how to export user data in XFile format, and/or can supply a demo model with N animations for use in this free educational project?



I am not yet learned modelling so much I can make a character, I am still texturing and cheats modelling with transmap existing meshes
I have some animations, but no clue howto export the whole animation to Xfile format
I really like the callback function, think a swordswinging animation also can check for if swords angles meet certain conditions, a lighteffect appear
but here a callback function can connect to collisiondetection vs another character/breakable object and act according to what happens
Posted on 2006-04-14 03:15:34 by daydreamer
Hi Homer,

Can you guys think of any more useful "attachment points", or situations where the ones I described are unsufficient?


What if the character lies down on a surface that is not horizontal? Perhaps it is a slope?
Or it is sitting on a chair, and the the back of the chair is not 100% vertical.
Would an extra attachment point between the shoulders be usefull?
In that case you would have 4 attachment points to work with:
2 at the feet, 1 near the rectum, and 1 between the shoulders


Anyway, here's an update of the Binary so you can see what's been going on
I think that next?  I'll add some code to display the boneboxes while the animation plays..

PS : Since I moved the code for Loading a skinmesh to be driven by the GUI's menu controls, I've noticed an intermittent GPF that occurs SOMETIMES, but usually not.
It occurs inside the D3DXLoadMeshHierarchyFromX api, and appears NOT to occur within our ID3DXAllocateHieraerchy callbacks, or be due to register preservation during the callbacks... can anyone else verify/reproduce this problem?


At first I thougt I didn't have tiny4anix.x
But it had a slightly different name on my machine: tiny_4anim.x
When I tried to load it, it resulted in a GPF.
I'm using DX 9 (SDK December 2005) on an XP SP2 machine.

I also tried to load tiny.x with the attached executable.
When I open the x.file the grey area just flickers once.
The model is not loaded into that area. But there was no GPF.

Friendly regards,
mdevries.
Posted on 2006-04-14 06:21:40 by mdevries

There is a problem I'm aware of regarding the render window : occasionally , for several possible reasons, we "lose our render device"..
The demo framework makes no attempt to detect or remedy this situation.
Until I add code to handle this situation, I suggest that you make the debug window smaller, this seems to help.

With regards to Tiny_4Anim.x, the demo will certainly crash if the texture filepath does not exist, so you'll need to make a folder called Tiny in the same folder as the demo, and then copy the DDS skin file into it.

Even having done this, there is still a small chance of GPF during loading, which I've mentioned already.

Anyway, please try the stuff I suggested and let me know how you went.
Posted on 2006-04-14 07:22:22 by Homer
Hi Homer,

I created the Tiny-directory under the directory the DXSkinMesh.exe file was in.
In that subdirectory I placed tiny_skin.dds. This is the right file I guess? Or are more files required?

I also placed Tiny.x and Tiny_skin.bmp in the subdirectory.
When I load Tiny.x the listview mentions only Track 0: Wieight = 1.0, and Length = 1.0
The rest of the values are 0. The model itself doesn't show up.

When I load Tiny_4anim.x, the Listview is filled with information for the Tracks 0 to 3.
In track 0: Weight = 1.0 and Length = 1.0. All other values are 0.
The model doesn't show up in this case either.

So, in both situations the grey area still remains just grey.
Both models still won't show up.
And indead: sometimes a GPF follows, but not always.
But adding the Tiny-directory, and putting the Tiny_skin.dds file in it, is certainly a step forward.

Until I add code to handle this situation, I suggest that you make the debug window smaller, this seems to help.


Regarding this suggestion: There was no difference, as far as I could see.

Friendly regards,
mdevries.
Posted on 2006-04-14 10:13:55 by mdevries
Yeah, that's the only file you need.. its just because in Tiny4Anim.x, the texture has been given a pathname rather than just a filename, and I haven't written any code to strip paths from texture files and/or go hunting in other folders for them.
The path given in this xfile is "tiny\tiny_skin.dds"
The dds file is an image file containing the texture, you don't need the bmp for this xfile.

Check this out, I haven't looked into it yet, but theres TWO MESHES in Tiny4Anim.x, the first mesh only has 8 faces, the second mesh is the main mesh.
The old CModel.Draw method might have failed on this xfile, because it only draws the first meshcontainer encountered (plus submeshes).
I changed CModel.Draw to draw a given MeshContainer, and changed CModel.DrawFrame to call CModel.Draw for each encountered MeshContainer, and I call CModel.DrawFrame, pRootFrame in my Render thread.
What ya think the 8-faced mesh is? (It doesnt get rendered, maybe it has no material?)
I'm guessing: its a BoundingBox that deforms with the animated mesh,improving its accuracy.

Here's an update of the binary.
It greatly reduces the frequency of the unknown gpf, as well as addressing some problems in the ListView display.. best of all, this version has all the controls implemented.

I still really don't have a clue what's causing this intermittant gpf, but strangely, it seems to be related to rendering, which is disabled during the loading, so thats quirky.

You can use left mousebutton to select an AnimationTrack (click the track identifier) and then use the lower controls to alter the track settings and hit Update to use the new values.
You can doubleclick any AnimationTrack to switch from the current animation to the one you clicked - and the switching of animations will not be "sharp", a "transition blend" is performed..

Yes, the  tracks other than 0 are initialized with Zero values - that's just the way things are insde Tiny4Anim.x, and it gets worse - it turns out that there's four AnimationSets, but only two AnimationTracks to play them on.. the update fixes that by using the Cloning trick I described in another post to expand the capacity of the AnimationController, so we can have enough AnimationTracks to play all AnimationSets at once if we wanna.

Set a secondary track's Weight and Speed and Disable/ReEnable it to mix it with the current Animation - you can mix in all four Animations at once.

The Update button only refers to the Weight,Speed and Position fields.
The Enable/Disable toggle is implemented independantly.
The Priority toggle is not yet implemented, and isn't really important.

You might notice that once you Update you start getting garbage values appearing in the ListView, and that when you force a Switch Transition that you don't see the values in the listview "crossfading" as they actually do.

Oh well, it's getting there - I said I'd implement the Blender controls along the way, and it sure looks better to me now you can play all the animations and screw around with them :)

I have no problem rendering with this version, provided that I don't cover the rendering window during the loading (or any other time, as this causes loss of render device after some seconds)

Attachments:
Posted on 2006-04-15 00:41:51 by Homer

I think it's a good thing to build up the framework sourcecode in stages, posting it along the way, rather than post a robust framework that deals with all situations, because mountains of casecode that rarely get executed are not conducive to learning ;)

I'll repost the entire source shortly, and at this stage, I'll want to discuss the existing framework again in order to 'set the stage' for showing how to handle "losing the render device" (where and how to handle this tragedy).

Maybe - just maybe - this relates to the unknown gpf ;)
Posted on 2006-04-15 01:00:34 by Homer
Hi Homer,

Check this out, I haven't looked into it yet, but theres TWO MESHES in Tiny4Anim.x, the first mesh only has 8 faces, the second mesh is the main mesh.


DubbelClicking on Tiny_skin.dds shows 2 identical versions of the skin (as far as I can see). Maybe it has something to do with it.


I tested the new version of DXSkinMesh.exe. The character shows up now.
And I can alter the settings in the listview.
More than that: I can select a different animation from it, being reflected in the render-window.

I haven't seen intermittent GPF's under the new circumstances sofar.


Friendly regards,
mdevries.
Posted on 2006-04-15 09:15:23 by mdevries
I'll probably begin writing two new object classes this evening, for the purpose of implementing Instancing of our animated models..not so useful for Player models, but exceedingly handy for Enemies :)

Basically each Instance of our model will have its own position, orientation etc, and most importantly, each will have its own AnimationController.. this is important because when we start instancing our animated model, we can't mess with the original AC that we got from Loading..  we have to Clone a copy of it into each new Instance, and use that instead.

So, I feel it's convenient to write a CCharacter class which represents an Instance of a referenced CRagDoll, and a Manager class to serve as the primary interface for user calls would be nice too.

I don't like chocolate.
Posted on 2006-04-15 09:16:36 by Homer
Here's some code which demonstrates how to correctly Instance our RagDoll as described in the previous posting.
It shows how to take advantage of an existing class as a 'reference object' (CRagDoll), and uses a lot of existing code ;)
The CRagDollInstance object takes advantage of our proven working code and provides the ability to create, animate and render N unique instances of any loaded ragdoll.

Instances would likely be stored in a standard OA32 Collection.

The idea is that we load a RagDoll using our existing code, and then attach it to one or more of these new-fangled "CRagDollInstance" objects (so each instance knows which ragdoll it represents).

Note that this code is Beta and unimplemented, but should basically be right.
We should be able to load a RagDoll, then create some Instances of it and start animating and rendering the Instances individually, like in Microsoft's MultiAnim demo.
(But unlike that demo, there's still no code to drive the animations/behaviours of the instances, this is what I call a "character class". Microsoft's character class was called CTiny. I'll get to character classes soon, that's where we describe a "character", which is a set of behaviours that drive the animations of our model.. if we wanna write a game, we'll have to define several character classes to describe our various kinds of enemies and such. Tiny is an example of a character class, but not a great one :)

;This object represents an INSTANCE of a loaded RagDoll.
CRagDollInstanceID equ 83484
Object CRagDollInstance,CRagDollInstanceID,Primer

RedefineMethod Init,Pointer ;pRagDoll
RedefineMethod Done
StaticMethod Update,REAL8 ;rElapsedTime
StaticMethod Draw

;Position of the instance in WorldSpace
DefineVariable m_vWorldPos,Vec3,{<>}
;Matrix to Position the instance in WorldSpace
DefineVariable matWorldTrans,D3DXMATRIX,{<>}
;Matrix to Orient the model instance in WorldSpace
DefineVariable matWorldRot,D3DXMATRIX,{<>}
;Pointer to our Cloned AnimationController
DefineVariable m_pAC,Pointer,NULL
;Pointer to Loaded Model (which this Instance is referencing)
DefineVariable pRagDoll,Pointer,NULL
ObjectEnd

;Cleanup:
;Release the Cloned AC (see Init)
Method CRagDollInstance.Done,uses esi
SetObject esi
SafeRelease .m_pAC
MethodEnd

;Initialize:
;Clone a copy of the RagDoll's AnimationController.
;Then initialize the Cloned AC.
Method CRagDollInstance.Init,uses esi, pRagDoll
LOCAL dwTracks,pAC,numEvents,numOutputs
.if pRagDoll==NULL
DbgWarning "Error : NULL CRagDoll","CRagDollInstance.Init"
invoke ExitProcess,0
.endif
mov ebx,pRagDoll
.if .CRagDoll.m_pAnimController==NULL
DbgWarning "Error : NULL AnimationController","CRagDollInstance.Init"
mov eax,E_FAIL
ExitMethod
.endif
;Grab a copy of the Original AC from the RagDoll
m2m pAC,.CRagDoll.m_pAnimController

    ;Clone the AnimationController
    mov numEvents,$ICall (pAC::ID3DXAnimationController.GetMaxNumEvents)
    mov numOutputs,$ICall (pAC::ID3DXAnimationController.GetMaxNumAnimationOutputs)
    SetObject esi
    ICall pAC::ID3DXAnimationController.CloneAnimationController,numOutputs,.CRagDoll.m_dwAnimationSetCount,.CRagDoll.m_dwAnimationSetCount,numEvents,addr .m_pAC
.if eax!=S_OK
DbgWarning "Error : Failed Cloning AnimationController","CRagDollInstance.Init"
mov eax,E_FAIL
ExitMethod
.endif

    ; Start with all tracks disabled
    mov dwTracks, $ICall (.m_pAC::ID3DXAnimationController.GetMaxNumTracks)
    xor ecx,ecx
    .while ecx < dwTracks
push ecx
ICall .m_pAC::ID3DXAnimationController.SetTrackEnable,ecx, FALSE
pop ecx
inc ecx
.endw
    mov eax, S_OK
MethodEnd

;Basically adds an instance-specific transformation
;to position and orient the instance when we render it :)
;Aside from that, it uses code in CRagDoll to do its work.
Method CRagDollInstance.Draw,uses esi
SetObject esi
;Update the instance's Translation and Orientation matrices
invoke D3DXMatrixTranslation,addr .matWorldTrans,addr .m_vWorldPos
invoke D3DXMatrixRotationYawPitchRoll,addr .matWorldRot,addr .m_fYaw,addr .m_fPitch,addr .m_fRoll
;Combine them
invoke D3DXMatrixMultiply,addr .matWorldCombined,addr .matWorldRot,addr .matWorldTrans
;Apply them
ICall pD3DDevice::IDirect3DDevice9.SetTransform, D3DTS_WORLD,addr .matWorldTrans
;Draw the skinmesh
mov eax,.pRagDoll
OCall .pRagDoll::CRagDoll.DrawFrame,.CRagDoll.m_pRootFrame
MethodEnd

;This replacement version of CModel.Update uses the
;instance-specific AnimationController we Cloned
;to update the SkinMesh (so it' ready for rendering)
;rather than the original AC (owned by the RagDoll)
Method CRagDollInstance.Update,uses esi, dElapsedTime:REAL8
local pMesh,Bones,SrcPtr,DestPtr
;Update the time for animation
SetObject esi
.if .m_pAC!=0 ;&& .m_dwCurrentAnimation != -1
ICall .m_pAC::ID3DXAnimationController.AdvanceTime,dElapsedTime,NULL
.endif

mov ebx,.pRagDoll
.if .CRagDoll.m_pFrameRoot!=0
;Update the matCombined matrices of the Frames
;by performing a recursive matrix multiplication
OCall .pRagDoll::CRagDoll.UpdateFrameMatrices,.CRagDoll.m_pFrameRoot, NULL

;UPDATE THE MATRIX PALETTE AND THE SKINMESH
mov ebx,.pRagDoll
m2m pMesh ,.CRagDoll.m_pFirstMesh
.if pMesh!=0
mov ebx,pMesh
.if .MESHCONTAINER.Base.pSkinInfo!=0

;Get number of Bones (from SkinInfo)
ICall .MESHCONTAINER.Base.pSkinInfo::ID3DXSkinInfo.GetNumBones
mov Bones,eax

;Calculate each Matrix in the m_pBoneMatrices array
;(Combine each Bone's matCombined and BoneOffset matrices)
xor ecx,ecx
.while ecx<Bones
push ecx

mov eax,sizeof D3DXMATRIX
mul ecx
mov ebx,.pRagDoll
add eax,.CRagDoll.m_pBoneMatrices
push eax ;eax=ptr to nth target BoneMatrix
;
mov eax,ecx
shl eax,2
mov edx,eax
mov ebx,pMesh
add eax,.MESHCONTAINER.ppFrameMatrices
add edx,.MESHCONTAINER.ppBoneOffsetMatrices
mov ebx,dword ptr ;source ebx=ptr to FRAME.matCombined
mov edx,dword ptr ;source edx=ptr to BoneOffset matrix
;
pop eax

invoke D3DXMatrixMultiply,eax,edx,ebx
pop ecx
inc ecx
.endw

;We're ready to transform the mesh vertices :)

; Lock the source and target vertex buffers
mov ebx,pMesh
ICall .MESHCONTAINER.Base.MeshData.pMesh::ID3DXMesh.LockVertexBuffer,D3DLOCK_READONLY, addr SrcPtr

mov ebx,pMesh
.if .MESHCONTAINER.pSkinMesh!=0
mov ebx,pMesh
ICall .MESHCONTAINER.pSkinMesh::ID3DXMesh.LockVertexBuffer,0, addr DestPtr
.else
DbgWarning "NULL pSkinMesh"
invoke ExitProcess,0
.endif

; Update the skinned mesh using provided transformations
mov ebx,pMesh
ICall .MESHCONTAINER.Base.pSkinInfo::ID3DXSkinInfo.UpdateSkinnedMesh,.m_pBoneMatrices, NULL, SrcPtr, DestPtr

; We're done with this meshcontainer..
; Unlock the vertex buffers
mov ebx,pMesh
ICall .MESHCONTAINER.pSkinMesh::ID3DXMesh.UnlockVertexBuffer
mov ebx,pMesh
ICall .MESHCONTAINER.Base.MeshData.pMesh::ID3DXMesh.UnlockVertexBuffer
.endif
.endif
.endif
MethodEnd



Posted on 2006-04-15 11:26:50 by Homer
Hi Homer,

Tiny is an example of a character class, but not a great one


In what way do you consider Tiny not to be a great class?

Friendly regards,
mdevries.
Posted on 2006-04-15 17:22:37 by mdevries
Tiny is a simple demo character class.
There's only four animations, so the number of possible activities that Tiny can engage in is quite small. For example, Tiny can't Jump.

For now, I'd like to steer the conversation back to the BoneBoxes.

We've managed to define our BoneBoxes as sets of Min/Max values, and determined the offset from the center of the box to the point where the box connects to its parent.
Note that the Boxes are defined in BoneSpace (are Bone-Aligned).
This was achieved by applying a per-bone matrix that was obtained during loading, whose purpose is to move from modelspace to bonespace (bindpose).
In order to get our boxes back into modelspace, we need to apply a different matrix.
If we applied the inverse of the matrix we used to make the boxes, we'd have our boxes oriented around the model in bindpose.. but not animated.
We need to also consider the animation matrix ie, obtain our "final matrix" from the Palette which is used to transform the skinmesh vertices, and use THAT to transform our BoundingBox vertices from BoneSpace back to ModelSpace.
Since this final matrix is dynamic, we need to re-transform our BoneBoxes each time the matrix is modified, ie, within the Update code. This way, our BoneBoxes are automatically oriented to follow the animated Bones, which is what we want at first so we can render the boxes during animation and prove that stuff is working the way we planned..


Posted on 2006-04-15 18:30:40 by Homer