This is mostly for witchsong and myself, but feel free to eavesdrop :)
I'm going to explain how I setup the bones/mesh in my maxscript plugin. I'm not very good with matrices, so maybe you can shed some light on what I may be missing. To begin with, I have noticed alot of parallels between the MD5 (doom3) model format and M3. I believe they use similar methods to animate their models. You can find out more about the MD5 file format at Doom3world.
The IREF chunks in M3's seem to contain the transformation matrix for each of the bone nodes. However this is not the bindpose, the nodes are additionally positioned, orientated and scaled through the bone chunks all relative to the parent. In my script I don't even use the matrices to arrange the bindpose bone setup.
First, I create all the bones, then I set up the chain by linking all the bones with parents to their parent bone. After that I iterate through the bones based on depth so that I begin with bones furthest from the root bone of the chain. Then I apply the scale, rotation and position (in that order) to the bone in the coordinate system of the parent bone (in maxscript you have to indicate the coordinate system) for all the bones so that the root bone is done last. I'm not sure if this is the correct way to go about arranging the bones but it gives me what I believe is the bindpose of the model when no animation data is present. That's the bindpose of the bones covered.
Code is here:
mb=mread.bones--M3bonechunksfori=1tomb.countdo--iteratethroughbonechunks(b=mb[i]--assignbcurrentboneiniteration--createbonenode,placementseemsirrelevantaschangedlatercb=BoneSys.createBone[0,0,0][0,0,0][0,0,0.1]--3dsMaxsettings,nothingimportantjustvisualelementscb.name=b.namecb.width=bonesizecb.height=bonesizecb.showLinks=truecb.boneScaleType=#none--appendthecbnodeontocbonesarray(maxscriptdynamicallyenlargesarray)appendcbonescb)maxviewsredraw--redrawsallviewports--Afterbonenodesarecreated,linkthemtogetherecho"Setting up bone hierarchy"fori=1tomb.countdo--iteratethroughbonechunks/nodes(b=mb[i]--currentbonechunk--Maxscriptuses1basedarraysinsteadof0based---1fornobonebecomes0,0becomes1,andsoforthifb.parent!=0then--ifbonehasaparent...(cbones[i].parent=cbones[b.parent]--assignthenodesparent))echo"Set up bone depth"--sortbonesbydepthfunction,returnsanarrayofdepthassortedboneswith--xandymembers.xindicatesdepthofbone,yindicatesthebonelocalbd=M3_Get_Bone_Depthmread.bonesecho"Set up bone transform"fori=1tocbones.countdo--iteratethroughbonenodes(--Dodeepestbonesfirstlocalh=bd[i].y--h=bonetotransformb=mread.bones[h]--M3bonechunkcb=cbones[h]--3dsMaxbonenodeincoordsysparent--intheparentcoordinatesystem(--applythescale,rotationandtranslationinthebonechunk--tothebonenodecb.scale=b.scalecb.rotation=b.rotcb.position=b.pos))
Now the bindpose is not arranged so that it deforms the standard mesh, so you need to adjust the vertices so that the mesh fits the bone placement. The way I do this, I really don't understand the math completely to be honest, but it seems to work at least partially.
Iterating through each vertex:
Multiply the original vertex position by the IREF matrix of the first bone it's weighted to (I assume there's a way to do all the bones a vertex is weighted to but I don't know how yet)
Multiply the new vertex position by the scale, inverse rotation and position of the bone. The rotation is the inverse due to the way 3ds max stores transform matrix rotations.
Hopefully that makes a bit of sense. I know maxscript can be confusing to read coming from C. Now I know the bones/mesh seems a bit off. I'm not sure why, I think this is as far as I can get. Figuring out the rest will be up to someone who has a better understanding of matrices and such.
I've got a few models to compare to show that my bindposes are at least mostly accurate. The in-game shots are done by blanking all the animid's in the bone chunks so that they don't reference any sequence data. All you're left with is the bindpose of the model. I notice that some meshes aren't in the right place, I believe this has something to do with vertices that are weighted to multiple bones which I'm not taking into consideration, and also attachments using their own transformation matrix.
Immortal has bad geometry, something I need to look into
Initial Reference (fancy wording for bindpose?) would be my guess. Yes, so far as I can tell the matrix has no impact on the bone itself. The bone bindpose is setup through the scale/rotations/translation values found in the bone chunks. The IREF values seem like they're only used for putting vertices in the bindpose position. I'm thinking you could move the vertices to their IREF positions then have the bones carry them to the bindpose position, but I'm not getting good results trying that method through maxscript.
I've found a guide on how to calculate final vertex positions with vertices weighted to multiple bones in this MD5 guide which I've added to my new M3 import script. The M3 format is extremely close to how the MD5 format is read, at least when it comes to bones/vertices/bindposes and animations. Even moving the vertices with multiple bone weights, there's geometry ending up where it shouldn't be. The immortal is very tricky, its left cannon is completely reversed. The zealot's bones do not animate as they should (check out the Fidget_Full animation to see what I mean) and its blade vertices seem bound to the wrong bones and consequently positioned incorrectly. I'm definitely missing something major when it comes to animations. My bindposes are almost identical to how they look in-game for most models, but some such as the immortal and the zealot have vertices seemingly weighted to bogus bones and ending up in weird orientations/positions. I'll keep investigating, there's something we're missing.
Whoa, really nice job, NiN. I take it, player colors are applied in-game somehow? So where the player color should be, it's just black on the model? Is there an easy way to apply player colors to the models or do we have to do it all by hand?
Hrm? Which seams? The normals seem fine to me, can put up a shot of the max normals or you can look for yourself in max. You don't have to do the player colours by hand Sixen, I'm just rendering them with no colour. Witchsong is correct. Really have to figure out these animations and why they're going so wrong. It's confusing because some bones seem to go where they should be while others are completely messed up.
I've looked into the smoothing, it's because your importing the model through the OBJ importer in 3ds max. It uses an algorithm to automatically smooth faces based on their angles to one another when no smoothing groups are provided, I'm assuming the SC2 engine does something similar. I'm not sure how to do this through my script, but I've checked my normals against the ones present in your OBJ generated files and they're the same. I'll try and hunt down some maxscript code that does similar smoothing to the OBJ importer.
I'm also interested in figuring out the basic information an M3 requires for rendering. It'll be easy to export static mesh geometry at this point, the only problems I foresee are the materials with their layer information. However I believe alot of the values are mostly pointless (such as the many animation ref's) so it won't be long before we can start putting custom models into SC2. My concern is that we're at a road block with animations. Until we figure out what's going wrong, we're going to be stuck with just geometry exporting capabilities, and that's not much fun.
I've almost completed the code to serialize models into .m3 files, what I need now is some Collada file with animations and materials, so I can start working on code to read that data into the current object-structure I got.
I can also do some testing on how much data is needed, what can be excluded etc. as soon as I got my code finished.
I want a rather simple model, with some bones, an animation, and an UV wrapped material. And it should include two meshes. It should be exported to OpenCOLLADA (not Autodesk Collada), not sure if the OpenCOLLADA plugin is included in 3ds Max (don't think so).
Would been awesome if someone could fix that (I cannot get my head around 3ds Max, the only thing I can do is import models :P).
Nin: The last three shorts in the REGN struct... In the M2 files, the short after the bone stuff looked like it could be the root bone or something, so the first of these shorts could be the root bone possibly. The second short isn't actually a short, it's two bytes, indices I think, because the two bytes contain the same value. The third short, I have no idea what is, but maybe related to the two bytes before it?
Max's default collada exporter sux :P
I had the open collada one already
I wasn't sure what you meant with "two meshes" if you meant two separate meshes or two meshes connected so I did both. "merged" has meshes connected and "not merged" has them separate.
The main geometry's like a cube but one of the sides is extruded and acts as an "arm". The bones' animation makes the whole thing rotate and the "cube" has an hemisphere on top of it. If it's not merged the hemisphere just sits there while everything else happens (animation) if it's merged the hemisphere follows along.
Here's the animation for the not merged one so you can appreciate it better.