Norbyte / lslib

Tools for manipulating Divinity Original Sin and Baldur's Gate 3 files
MIT License
728 stars 134 forks source link

Export bones / animations of Anno 1404 to dae #23

Open hansmbakker opened 5 years ago

hansmbakker commented 5 years ago

I tried exporting some Anno1404 files using the converter. I added granny2.dll from the game. I used the master branch of lslib, updated today.

The animations and meshes are stored in separate .gr2 files.

Probably because the bones are not exported, the animation does not work when it is imported in 3ds max. Is there something I'm doing wrong? I can provide sample files upon request.

hansmbakker commented 5 years ago

I debugged the code; it turns out that LSLib correctly reads the skeleton properties, but

Also, when forcing to call ExportSkeleton(...) by changing https://github.com/Norbyte/lslib/blob/aa7be33f6d5ef111c712d67a3d21ee5cd2e42b2c/LSLib/Granny/Model/ColladaExporter.cs#L460 to

if (model.Skeleton != null)

it turns out that desc.HasBoneWeights is false at https://github.com/Norbyte/lslib/blob/aa7be33f6d5ef111c712d67a3d21ee5cd2e42b2c/LSLib/Granny/Model/Mesh.cs#L636

This prevents the skeletons / bones to be exported. I'm not sure though why the BoneWeights are not found correctly; in GrannyViewer the model and animations work correctly.

Could you provide any guidance here?

hansmbakker commented 5 years ago

I found https://github.com/Norbyte/lslib/issues/13 and https://github.com/Norbyte/lslib/issues/15. You mention that older Granny files are not supported.

The GR2 version that Anno1404 uses shows as 7 in the header property (this sounds like it is the same as Divinity?) and lists Granny Standard Exporter, SDK version 2.8.12.0 as exporter info.

Norbyte commented 5 years ago

Can you provide a copy of the mesh and model GR2? There are many possibilities for what goes wrong. It is quite hard to tell without a sample, because most games customize their GR2 format in some way. Some use special vertex attribute formats, some skip encoding the skeleton entirely in animation files, etc.

hansmbakker commented 5 years ago

@Norbyte (files removed for copyright reasons)

Norbyte commented 5 years ago

I've checked the mesh and animations.

a) n_repair_crane_goods_lod0.gr2: This file contains 5 different meshes with 5 different skeletons; all of these skeletons have one single bone with the same name as the model & skeleton. Although the meshes have bone bindings, the vertices themselves are not skinned, i.e. they don't carry bone indices or weights in the vertex data. Because of this, the skeletons are marked as 'dummy' and are not exported.

I find this quite strange, as the usual way these animations are done is by creating a single-part or multipart mesh, binding all submeshes to different bones of the same skeleton and adding a dummy root bone or multiple separate roots.

Nevertheless, a quick workaround would be suppressing the dummy skeleton checks for your model, i.e. keeping the change you mentioned in ExportSkeleton (i.e. if (model.Skeleton != null)), and forcing the exporter to emit a skin and mesh binding, i.e. changing the line at https://github.com/Norbyte/lslib/blob/master/LSLib/Granny/Model/ColladaExporter.cs#L260 to bool hasSkin = skelRef != null;.

b) repair_crane_work01.gr2: This contains animations for both mesh files. The bone data was stripped from the gr2 file, so exporting the animation itself to Collada will not result in a useful result, as no importer tool will be able to associate the animation frames with the bones they should be animating. (Blender will just say removed 96 unused curves and discard the entire animation). You could copy&paste the library_animations and library_animation_clips nodes from the animation Collada, and paste it into the export of n_repair_crane_goods_lod0.gr2; this way it'll be able to bind on import.

Another issue is the lack of bone weights. Because there are none, the animation will show up in Blender, but it'll just animate the bones, the vertices themselves won't be moving because there is no association between bones and vertices. One thing that can be done here is forcing each bone to have an 1.0 influence from the parent bone. i.e.: add this before https://github.com/Norbyte/lslib/blob/master/LSLib/Granny/Model/ColladaExporter.cs#L260

meshBinding.Mesh.VertexFormat.HasBoneWeights = true;
var skinnedVerts = meshBinding.Mesh.PrimaryVertexData.Deduplicator.Vertices.Uniques;
for (var i = 0; i < skinnedVerts.Count; i++)
{
    var v = skinnedVerts[i];
    v.Indices.A = 0;
    v.Weights.A = 0xff;
    skinnedVerts[i] = v;
}
hansmbakker commented 5 years ago

That's great, thank you for looking into this! I'll try it!

hansmbakker commented 5 years ago

I tried it, but it didn't seem to work - in 3ds max the Skin modifier is shown when importing the resulting dae file, but the animation doesn't seem to have effect on the bones?

Strange indeed that they modelled/ exported it this way.

hansmbakker commented 5 years ago

It turns out that for characters (people as opposed to objects), there are Bone Indices included in the vertices, but not Bone Weights. Also the bone index field has uint32 in the granny file rather than uint8 which LSLib expects. I suppose this requires the VertexStructure to be modified for it not to load the wrong data? I'm wondering how Granny handles this itself, since in grannyviewer all looks fine..

Norbyte commented 5 years ago

I haven't seen uint32 bone indices before, usually there is a bone index array of N elements, and a bone weights array of N elements. I think this may be an older version of the GR2 format where bone weights were not yet supported, and each vertex was bound to exactly one bone with a weight of 1.0.

Alternatively, it could be an optimization as the game doesn't need sophisticated models that bind vertices to multiple bones, and instead they padded the bone index to a multiple of 4. (GPUs generally don't handle data that is not 4 byte aligned data very well, so vertex buffers are usually padded to a multiple of 4 bytes).

A special case could be added to vertex processing that autofills the first element in BoneWeights to 0xff if there is no bone weight array, only a bone index array.

Granny supports quite a few vertex formats, I think the list of builtin formats is over 40-50, so they probably have a special case in their library for this.

hansmbakker commented 5 years ago

Ok, thank you! I’ll look further into it.