Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

One skeleton, multiple meshes (customization)

Discussion in 'Animation' started by ComboRoutine, Oct 1, 2015.

  1. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    I need a way to combine multiple bodyparts into a character controlled by a skeleton (or an alternative way of doing the customization process). Simple as that, really.
    In my current project I have all the bodyparts moving with their own skeletons controlled by the same Animator (movement using NavMeshAgent and root motion), but they bump into eachother and get desynced, not to mention collisions with other objects and optimisation worries.
    I'm not too experienced as I'm entirely self taught and prone to finding the easy way out or brute forcing things, so I'd prefer something simple I can start out with over endless lines of code, but at this point I just want anything that works. There has to be some way to get something as common as customization done. I'm on my knees, just... just give me something, please. Any functional tutorials out there? Any related terms I could google? Anything?
     
    Last edited: Oct 21, 2015
  2. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    This is what I'm using

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. using HierarchyDict = System.Collections.Generic.Dictionary<string, UnityEngine.Transform>;
    6. using BoneTransformDict = System.Collections.Generic.Dictionary<string, utils.Tuple<UnityEngine.Transform, string>>;
    7.  
    8. namespace utils
    9. {
    10.     public class MeshCombiner
    11.     {
    12. #region Operations
    13.         //! Combine mesh.
    14.         /*!
    15.             \return combined mesh instance.
    16.         */
    17.         public static GameObject Combine(List<SkinnedMeshRenderer> SkinnedRenderers)
    18.         {
    19.             // Generated GO
    20.             GameObject final_mesh_go = new GameObject("Mesh");
    21.             // Dummy parent holder
    22.             GameObject dummy_parent = new GameObject("DummyParent");
    23.  
    24.             // All available bones
    25.             var all_bones = new BoneTransformDict();
    26.             // Traverse through all skinned mesh renderers
    27.             foreach(var renderer in SkinnedRenderers)
    28.             {
    29.                 var renderer_bones = renderer.bones;
    30.                 foreach (var bone in renderer_bones)
    31.                 {
    32.                     // Bone doesn't exist, add it
    33.                     if (!all_bones.ContainsKey(bone.name))
    34.                         all_bones[bone.name] = new utils.Tuple<Transform, string>(bone, bone.parent.name);
    35.                 }
    36.             }
    37.  
    38.             var combineInstanceArrays = new Dictionary<Material, List<CombineInstance>>();
    39.             var bone_weights = new Dictionary<Mesh, BoneWeight[]>();
    40.             // Map between bone name and index
    41.             var added_bones = new Dictionary<string, int>();
    42.             // List of child objects holding the skinned mesh renderers to be
    43.             // destroyed when finished
    44.             var child_objects_to_destroy = new List<GameObject>();
    45.  
    46.             int bone_index = 0;
    47.             foreach(var renderer in SkinnedRenderers)
    48.             {
    49.                 child_objects_to_destroy.Add(renderer.transform.parent.gameObject);
    50.  
    51.                 var renderer_bones = renderer.bones;
    52.                 // Add all bones as first and save the indices of them
    53.                 foreach (var bone in renderer_bones)
    54.                 {
    55.                     // Bone not yet added
    56.                     if (!added_bones.ContainsKey(bone.name))
    57.                         added_bones[bone.name] = bone_index++;
    58.                 }
    59.                 // Adjust bone weights indices based on real indices of bones
    60.                 var bone_weights_list = new BoneWeight[renderer.sharedMesh.boneWeights.Length];
    61.                 var renderer_bone_weights = renderer.sharedMesh.boneWeights;
    62.                 for (int i = 0; i < renderer_bone_weights.Length; ++i)
    63.                 {
    64.  
    65.                     BoneWeight current_bone_weight = renderer_bone_weights[i];
    66.  
    67.                     current_bone_weight.boneIndex0 = added_bones[renderer_bones[current_bone_weight.boneIndex0].name];
    68.                     current_bone_weight.boneIndex2 = added_bones[renderer_bones[current_bone_weight.boneIndex2].name];
    69.                     current_bone_weight.boneIndex3 = added_bones[renderer_bones[current_bone_weight.boneIndex3].name];
    70.                     current_bone_weight.boneIndex1 = added_bones[renderer_bones[current_bone_weight.boneIndex1].name];
    71.  
    72.                     bone_weights_list[i] = current_bone_weight;
    73.                 }
    74.                 bone_weights[renderer.sharedMesh] = bone_weights_list;
    75.  
    76.                 // Handle bad input
    77.                 if (renderer.sharedMaterials.Length != renderer.sharedMesh.subMeshCount)
    78.                 {
    79.                     Debug.LogError("Mismatch between material count and submesh count. Is this the correct MeshRenderer?");
    80.                     continue;
    81.                 }
    82.  
    83.                 // Prepare stuff for mesh combination with same materials
    84.                 for (int i = 0; i < renderer.sharedMesh.subMeshCount; i++)
    85.                 {
    86.                     // Material not in dict, add it
    87.                     if (!combineInstanceArrays.ContainsKey(renderer.sharedMaterials[i]))
    88.                         combineInstanceArrays[renderer.sharedMaterials[i]] = new List<CombineInstance>();
    89.                     var actual_mat_list = combineInstanceArrays[renderer.sharedMaterials[i]];
    90.                     // Add new instance
    91.                     var combine_instance = new CombineInstance();
    92.                     combine_instance.transform = renderer.transform.localToWorldMatrix;
    93.                     combine_instance.subMeshIndex = i;
    94.                     combine_instance.mesh = renderer.sharedMesh;
    95.  
    96.                     actual_mat_list.Add(combine_instance);
    97.                 }
    98.                 // No need to use it anymore
    99.                 renderer.enabled = false;
    100.             }
    101.             var bones_hierarchy = new HierarchyDict();
    102.             // Recreate bone structure
    103.             foreach (var bone in all_bones)
    104.             {
    105.                 // Bone not processed, process it
    106.                 if (!bones_hierarchy.ContainsKey(bone.Key))
    107.                     AddParent(bone.Key, bones_hierarchy, all_bones, dummy_parent);
    108.             }
    109.  
    110.             // Create bone array from preprocessed dict
    111.             var bones = new Transform[added_bones.Count];
    112.             foreach (var bone in added_bones)
    113.                 bones[bone.Value] = bones_hierarchy[bone.Key];
    114.  
    115.             // Get the root bone
    116.             Transform root_bone = bones[0];
    117.  
    118.             while (root_bone.parent != null)
    119.             {
    120.                 // Get parent
    121.                 if (bones_hierarchy.ContainsKey(root_bone.parent.name))
    122.                     root_bone = root_bone.parent;
    123.                 else
    124.                     break;
    125.             }
    126.  
    127.  
    128.             // Create skinned mesh renderer GO
    129.             GameObject combined_mesh_go = new GameObject("Combined");
    130.             combined_mesh_go.transform.parent = final_mesh_go.transform;
    131.             combined_mesh_go.transform.localPosition = Vector3.zero;
    132.  
    133.             // Fill bind poses
    134.             var bind_poses = new Matrix4x4[bones.Length];
    135.             for (int i = 0; i < bones.Length; ++i)
    136.                 bind_poses[i] = bones[i].worldToLocalMatrix * combined_mesh_go.transform.localToWorldMatrix;
    137.  
    138.             // Need to move it to new GO
    139.             root_bone.parent = final_mesh_go.transform;
    140.  
    141.             // Combine meshes into one
    142.             var combined_new_mesh = new Mesh();
    143.             var combined_vertices = new List<Vector3>();
    144.             var combined_uvs = new List<Vector2>();
    145.             var combined_indices = new List<int[]>();
    146.             var combined_bone_weights = new List<BoneWeight>();
    147.             var combined_materials = new Material[combineInstanceArrays.Count];
    148.  
    149.             var vertex_offset_map = new Dictionary<Mesh, int>();
    150.  
    151.             int vertex_index_offset = 0;
    152.             int current_material_index = 0;
    153.  
    154.             foreach (var combine_instance in combineInstanceArrays)
    155.             {
    156.                 combined_materials[current_material_index++] = combine_instance.Key;
    157.                 var submesh_indices = new List<int>();
    158.                 // Process meshes for each material
    159.                 foreach (var combine in combine_instance.Value)
    160.                 {
    161.                     // Update vertex offset for current mesh
    162.                     if (!vertex_offset_map.ContainsKey(combine.mesh))
    163.                     {
    164.                         // Add vertices for mesh
    165.                         combined_vertices.AddRange(combine.mesh.vertices);
    166.                         // Set uvs
    167.                         combined_uvs.AddRange(combine.mesh.uv);
    168.                         // Add weights
    169.                         combined_bone_weights.AddRange(bone_weights[combine.mesh]);
    170.  
    171.                         vertex_offset_map[combine.mesh] = vertex_index_offset;
    172.                         vertex_index_offset += combine.mesh.vertexCount;
    173.                     }
    174.                     int vertex_current_offset = vertex_offset_map[combine.mesh];
    175.  
    176.                     var indices = combine.mesh.GetTriangles(combine.subMeshIndex);
    177.                     // Need to "shift" indices
    178.                     for (int k = 0; k < indices.Length; ++k)
    179.                         indices[k] += vertex_current_offset;
    180.  
    181.                     submesh_indices.AddRange(indices);
    182.                 }
    183.                 // Push indices for given submesh
    184.                 combined_indices.Add(submesh_indices.ToArray());
    185.             }
    186.  
    187.             combined_new_mesh.vertices = combined_vertices.ToArray();
    188.             combined_new_mesh.uv = combined_uvs.ToArray();
    189.             combined_new_mesh.boneWeights = combined_bone_weights.ToArray();
    190.  
    191.             combined_new_mesh.subMeshCount = combined_materials.Length;
    192.             for (int i = 0; i < combined_indices.Count; ++i)
    193.                 combined_new_mesh.SetTriangles(combined_indices[i], i);
    194.  
    195.             // Create mesh renderer
    196.             SkinnedMeshRenderer combined_skin_mesh_renderer = combined_mesh_go.AddComponent<SkinnedMeshRenderer>();
    197.             combined_skin_mesh_renderer.sharedMesh = combined_new_mesh;
    198.             combined_skin_mesh_renderer.bones = bones;
    199.             combined_skin_mesh_renderer.rootBone = root_bone;
    200.             combined_skin_mesh_renderer.sharedMesh.bindposes = bind_poses;
    201.  
    202.             combined_skin_mesh_renderer.sharedMesh.RecalculateNormals();
    203.             combined_skin_mesh_renderer.sharedMesh.RecalculateBounds();
    204.             combined_skin_mesh_renderer.sharedMaterials = combined_materials;
    205.  
    206.             // Destroy children
    207.             foreach (var child in child_objects_to_destroy)
    208.                 GameObject.DestroyImmediate(child);
    209.             // Destroy dummy parent
    210.             GameObject.DestroyImmediate(dummy_parent);
    211.  
    212.             return final_mesh_go;
    213.         }
    214.  
    215.         static void AddParent(string BoneName, HierarchyDict BoneHierarchy, BoneTransformDict AllBones, GameObject DummyParent)
    216.         {
    217.             Transform actual_bone = null;
    218.             // Must be bone
    219.             if (AllBones.ContainsKey(BoneName))
    220.             {
    221.                 var bone_tuple = AllBones[BoneName];
    222.                 // Add parent recursively if not added
    223.                 if (!BoneHierarchy.ContainsKey(bone_tuple._2))
    224.                 {
    225.                     AddParent(bone_tuple._2, BoneHierarchy, AllBones, DummyParent);
    226.                     // Unparent all children of parents
    227.                     Unparent(BoneHierarchy[bone_tuple._2], DummyParent);
    228.                 }
    229.  
    230.  
    231.                 bone_tuple._1.parent = BoneHierarchy[bone_tuple._2];
    232.                 actual_bone = bone_tuple._1;
    233.             }
    234.  
    235.             BoneHierarchy[BoneName] = actual_bone;
    236.         }
    237.  
    238.         static void Unparent(Transform Parent, GameObject DummyParent)
    239.         {
    240.             if (Parent != null)
    241.             {
    242.                 var unparent_list = new List<Transform>();
    243.  
    244.                 foreach (Transform child in Parent.transform)
    245.                     unparent_list.Add(child);
    246.  
    247.                 foreach (var child in unparent_list)
    248.                     child.parent = DummyParent.transform;
    249.             }
    250.         }
    251. #endregion
    252.     }
    253. }
    254.  
     
    Senshi, eses and ComboRoutine like this.
  3. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Really appreciate the help, like you don't even know how much, but... I'm just having a really hard time using this, let alone getting a hang of how exactly it works.
    I know you've already gone way beyond the call of duty, but could you maybe just throw me a one or two sentence tutorial of how I'm supposed to call this, and from where?
     
  4. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    Sorry for the late reply,
    the function you want to use is Combine(). It takes the list of skinned mesh renderers you want to combine in one. Maybe it's not what you want. As first you must have some skinned meshes prepared. For instance if you want to combine the human torso and head, then you need to load appropriate prefabs (for head, torso), get the skinned mesh renderers from them and pass them to this function. The returned object is the GO with single skinned mesh renderer and also the bones as Transform GO.

    Anyway, I forgot to include the Tuple class
    Code (CSharp):
    1. public struct Tuple<T1, T2>
    2.     {
    3.         public readonly T1 _1;
    4.         public readonly T2 _2;
    5.  
    6.         public Tuple(T1 T1_, T2 T2_)
    7.         {
    8.             _1 = T1_;
    9.             _2 = T2_;
    10.         }
    11.     }
    12.  
    Also I don't know how good are you at programming but you should be at least past the beginning stage to use it correctly.
     
  5. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    No problem dude, you're already doing way more than could be asked of you.
    I have a working base knowledge, but am completely clueless when it comes to all this rendering madness, and have some pretty big language barriers to overcome. At the moment I'm thinking something along the lines of
    Code (csharp):
    1. List<SkinnedMeshRenderer> meshRenderers = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
    2. utils.MeshCombiner.Combine(meshRenderers);
    but, umm... that's not right, is it? I'm getting a type conversion error, so I'm guessing GetComponentsInChildren returns an array, and arrays and lists don't mix? Had to look into the whole List thing, so... would this work?
    Code (csharp):
    1. List<SkinnedMeshRenderer> meshRenderers = new List<SkinnedMeshRenderer> (gameObject.GetComponentsInChildren<SkinnedMeshRenderer>());
    2. utils.MeshCombiner.Combine(meshRenderers);
    Am I even close? Because, I mean... They did combine into one object, but it's more of an amorphous blob combination of all the parts, with scales that I can't even describe. The parts are connected to the bones, but the positioning is nothing like the original skeleton, not even close. Did I mess something up with the whole List thing, or...?
     
    Last edited: Oct 5, 2015
  6. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    Yes I think that should work. I'm not primarly the C# developer but I think the List should have a constructor accepting the raw Array.

    Also I forgot to mentioned the preconditions for the function.

    All the parts *MUST* have same base skeleton (but could have some additional bones if needed). That is, the structure, the bone names, all must be same.

    So maybe there could be the error.

    Anyway, if you got some errors, please post the whole error message you get. If you got some screenshot of the output, please post it.
    Does the combined mesh looks like the things from the "The Thing" movie?
     
  7. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    The skeletons for each part are identical, yeah. I built all the meshes around the same base skeleton file.
    No errors popping up on the console. All the meshes get stretched or shrunk in different dimensions and apparently move to the 0,0,0 location upon creation (not sure if intended or not).
    See: video of mesh madness
    Note: model was made as a placeholder out of boredom. Please don't judge :p

    Any idea what might be causing the weird stretching, scaling and whatnot?
     
  8. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    If you could post me the mesh I could look at it. Is the base model somehow scaled? That is, some parts have some scale set, etc?
     
  9. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Went through the meshes for safe measure and found no scalings. I mean, some of the meshes aren't exactly size 1,1,1, but changing these values doesn't seem to change anything anyway, so I'm assuming they just take on the size of the bones and can't be changed, yeah? (I did try changing them to 1,1,1 anyway. No difference). The objects themselves are 1,1,1 though.
    Meshes are Blender files, uploaded them to MediaFire, left the .meta data in there in case that helps.
     
  10. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    So I looked at your model and don't know where the problem is. For me it looks like the scale issue. But it could by that my code isn't working right, also.
    Looking at your blend file in blender, there is some scale set. So I applied the scale and it looks like it is ok, only rotated. I've tried it only with legs.

    So try to apply all the scales and rotations on the models.
     
  11. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Well s**t. Never even realized that's how Blender handles sizes in object mode. That really seems like the issue, changing the object sizes in blender to 1 seems to produce very similar results to how the mesh combiner acts in Unity. After fixing things around in Blender I got the legs to work, I think. They look about right. Still getting some weird rotations and I need to figure out how to actually add components to the combined mesh object instead of the old object, but that's a problem for Future-Scrawny.
    Seriously though, can I like buy you a pizza or something? You've been a massive help.

    Edit: Fixed all the parts in Blender and they look fine in Unity, so that's definitely a thing. Rotations are still a bit strange, but meh
     
    Last edited: Oct 11, 2015
  12. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    That's ok ;)
    Don't know why the rotation is wrong... maybe unity is somehow internally swapping some axis when importing from blender. You could try to export from blender as fbx to find out how it will looks like.
     
  13. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    I did try that, but I ended up with a 1/100 scale version when imported to Unity, and the mesh combiner made the whole part apparently invisible, so... I decided not to even go there. At the moment I'm perfectly happy just turning the whole thing 270 degrees around X after combining the meshes
    One more question if you don't mind, how do you deal with actually doing things with the combined mesh? Do you just add components through code in runtime, or...?
     
  14. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    For the scale, on the import tab you could change the "scale" of the imported object. So try this one. I would really like to know if this rotation bug is specific to blender files.

    The second question, I do everything at runtime, the mesh combining etc. But I have prefab with animator, etc, and I just add the combined GO as the child to this prefab. But this is also consequence as whole game is "procedural", that is everything is loaded, prepared and so on in the runtime, based on config files, custom scene files, maps, etc.
     
  15. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Well, after a quick test run I'm even more confused. Exported as an .fbx with scaling at 100. When added to the scene, the size is correct, but after running the mesh combiner, it shrinks back to what I assume is that same 1/100 scale. Wondering how the exporter actually handles the scaling.
    Also, I noticed a little something that happens with the combiner. The bones in the combined mesh seem to be relative to where the combining objects are. As in, if you combine the objects at 0,0,0 the bones are where they're supposed to be, but if you do it at 10,0,0 the mesh will be at 0,0,0 but the bones will be at 10,0,0. See: images. Not sure if intended or not

    Edit: Looked into the rotation, and apparently it's a Blender thing. Z axis is facing up when in most cases that'd be Y. "Blender uses the right hand coordinate system with the Z axis pointing upwards. This is common with the coordinate systems used by most common 3D CAD packages". Exporting as .fbx should fix that problem I hope.

    Edit2: The bones in the exported .fbx file have a scale of 100, meshes have a scale of 1. No idea what the exporter is up to. Go figure /shrug. I guess I could just export everything as an .fbx, then scale the combined mesh up? Seems a little cheap, but I mean... Got any other ideas?

    Edit3: So yeah, scratch that. .fbx also seems to be rotated no matter what I set as the "up" direction while exporting (even defining different directions to different parts). Must be the skeleton then, yeah? Skeleton's standing upright, mesh is lying on the floor face down. There are no answers, all I find are more questions lol
     
    Last edited: Oct 12, 2015
  16. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    This mesh offset could probably happen. Haven't tested it yet as all my combined meshes are initially at (0,0,0).

    Regarding the rotation, be sure that all the rotation, scale is applied before exporting (Ctrl+A in blender I think). Also you could try with simple plane export from blender and experiment if the rotation change. If you change the axis orientation in export, it should change it in unity. Hope you're using the .fbx in unity and not the .blend files ;)

    Regarding the scaling issue, don't know where could the problem be. Anyway, scaling the objects in unity is in most cases not a good thing. So scale the object on import or in the 3D program.

    Also you definitelly should have the 1:1 copy of the combined mesh (no additional scaling, rotating should be done). Othewise other problem could arise. It could be I have some bugs in code and would like to know. It also could be that the model got some issue. We should find out if the model is 100% correct. What about trying some other model?

    I'm attaching the sample project, to be sure you're using the functionality right.
     

    Attached Files:

  17. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    I think I found the rotation problem. There is one Transform named "Armature" which is parent for the root bone Waist. But it is strange, that "Armature" isn't part of the bone hierarchy. Need to look at this more closely.
     
  18. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Ran into some even stranger rotation problems where I had two instances of the same part/file, neither of which had any in-editor rotations on them, but they were facing different directions. At this point I don't even, so I made a clean project with just the bare essentials to keep testing.

    Yes, all rotations and whatnot are applied and I'm using the right files. Changing the up direction when exporting .fbx models does change the initial direction of the models in Unity, but has no apparent effect on the rotation that happens when the meshes are combined.

    Scaling is still a total mystery, but assuming the rotation problem is not related to exporting as .blend files, I can just keep using those and scaling won't be a problem.

    So, I made a quick new model and bones, rigged it and exported. Shocking news: more weird things happened.
    Note: irrelevant things, feel free to skip
    I made the right side of the model, duplicated the arms and legs, and mirrored to the other side. Apparently Blender's idea of mirroring is just setting an object's scale to -1, meaning it F***s everything up, including normals. Slightly mad at whoever came up with this brilliant idea.
    End irrelevance

    Using the new rig (didn't bother chopping it up, just a single skeleton) with the mesh combiner, it did not fall flat on it's face, but on it's side. Exporting it as an .fbx will result in it lying on it's side from the moment he's brought into the scene, regardless of what I put in as it's exported up direction.
    Just about to hit my boiling point, gonna take a break for a few hours.

    Update: Interesting fact, I found this, and the mesh behaves the same way, falling on it's face. So it's definitely something on my end. Gonna dive back into google and see if I bump into anything.

    Update2: Well, minor success. After messing with a ton of exporting, I found that by turning the whole thing 270 around the X axis in Blender, applying rotations, then exporting as .fbx and changing Y to be the up direction and Z to be the forward direction, I can actually get the model to stand upright ever after the mesh combining. Armature's still sporting a 270 rotation around the X axis, but seems to be countered by the export settings. Similarly I can get the .blend files to work by rotating the model 90 degrees around X, then applying the rotation and saving. Sort of brute forcing it, but whatever works, right?
    Another slightly interesting fact is that having an animator component on the parent object of the to-be-combined parts makes the combined mesh fall flat on it's face again. So yeah, that sort of ties the whole thing into one big knot all over again. Adding animations messes things up, and I don't know if you're aware, but animations are sort of important.
    Back to the drawing board.
     
    Last edited: Oct 13, 2015
  19. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    I need to look at it. Don't have a time now, maybe weekend. I could maybe fix it. The main problem is this "Armature" Transform which is exported as parent of the root bone, but isn't referenced anywhere. So I'll need to support it somehow and use it's rotation as basis. Or just make it also parent for the combined bones. Need to look at it.
     
  20. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    So I tried to play with it but, without success. I just don't have enough time to debug it deeply. But it looks like that this "Armature" Transform is somehow causing the issue. I don't know any workarounds now. Maybe unity has some internal states which aren't exported through API.
    The animations should work. But it could be related to that "Armature" transform.
     
  21. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Ah, too bad. Haven't found anything groundbreaking myself either. Really appreciate the effort though. Gonna keep trying
     
  22. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    Anyway, IIRC there is some demo package on the asset store that is showcasing the mesh replacement. I don't know the name, but the scene has some little girl and you can change the clothes. Maybe you could peek there and find out how are they doing though. Maybe their method will suffice.
    I'm sorry I couldn't help more. Also there is a chance that my code is doing something wrong. So maybe someone more experienced could look at it and find what is causing the trouble.
     
  23. Alima-Studios

    Alima-Studios

    Joined:
    Nov 12, 2014
    Posts:
    78
  24. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    @Scrawny, got a new version of the mesh combiner. You could try if it fix your problems. I have fixed some bind pose problems
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. using HierarchyDict = System.Collections.Generic.Dictionary<string, UnityEngine.Transform>;
    5. using BoneTransformDict = System.Collections.Generic.Dictionary<string, jax.utils.Tuple<UnityEngine.Transform, string>>;
    6.  
    7. namespace utils
    8. {
    9.     struct BoneDef
    10.     {
    11.         public int m_Index;
    12.         public Matrix4x4 m_BindPose;
    13.     }
    14.  
    15.     public class MeshCombiner
    16.     {
    17. #region Operations
    18.         //! Combine mesh.
    19.         /*!
    20.             \return combined mesh instance.
    21.         */
    22.         public static GameObject Combine(List<SkinnedMeshRenderer> SkinnedRenderers)
    23.         {
    24.             // Generated GO
    25.             GameObject final_mesh_go = new GameObject("Mesh");
    26.             // Dummy parent holder
    27.             GameObject dummy_parent = new GameObject("DummyParent");
    28.  
    29.             // All available bones
    30.             var all_bones = new BoneTransformDict();
    31.             // Traverse through all skinned mesh renderers
    32.             foreach(var renderer in SkinnedRenderers)
    33.             {
    34.                 var renderer_bones = renderer.bones;
    35.                 foreach (var bone in renderer_bones)
    36.                 {
    37.                     // Bone doesn't exist, add it
    38.                     if (!all_bones.ContainsKey(bone.name))
    39.                         all_bones[bone.name] = new utils.Tuple<Transform, string>(bone, bone.parent.name);
    40.                 }
    41.             }
    42.  
    43.             var combineInstanceArrays = new Dictionary<Material, List<CombineInstance>>();
    44.             var bone_weights = new Dictionary<Mesh, BoneWeight[]>();
    45.             // Map between bone name and bone info
    46.             var added_bones = new Dictionary<string, BoneDef>();
    47.             // List of child objects holding the skinned mesh renderers to be
    48.             // destroyed when finished
    49.             var child_objects_to_destroy = new List<GameObject>();
    50.  
    51.             int bone_index = 0;
    52.             foreach(var renderer in SkinnedRenderers)
    53.             {
    54.                 child_objects_to_destroy.Add(renderer.transform.parent.gameObject);
    55.  
    56.                 var renderer_bones = renderer.bones;
    57.                 var bind_poses_mesh = renderer.sharedMesh.bindposes;
    58.  
    59.                 int bone_index_local = 0;
    60.                 // Add all bones as first and save the indices of them
    61.                 foreach (var bone in renderer_bones)
    62.                 {
    63.                     // Bone not yet added
    64.                     if(!added_bones.ContainsKey(bone.name))
    65.                     {
    66.                         var bonde_def = new BoneDef();
    67.  
    68.                         bonde_def.m_Index = bone_index++;
    69.                         // Save the original bind pose also
    70.                         bonde_def.m_BindPose = bind_poses_mesh[bone_index_local];
    71.  
    72.                         added_bones[bone.name] = bonde_def;
    73.                     }
    74.                     ++bone_index_local;
    75.                 }
    76.                 // Adjust bone weights indices based on real indices of bones
    77.                 var bone_weights_list = new BoneWeight[renderer.sharedMesh.boneWeights.Length];
    78.                 var renderer_bone_weights = renderer.sharedMesh.boneWeights;
    79.                 for (int i = 0; i < renderer_bone_weights.Length; ++i)
    80.                 {
    81.  
    82.                     BoneWeight current_bone_weight = renderer_bone_weights[i];
    83.  
    84.                     current_bone_weight.boneIndex0 = added_bones[renderer_bones[current_bone_weight.boneIndex0].name].m_Index;
    85.                     current_bone_weight.boneIndex2 = added_bones[renderer_bones[current_bone_weight.boneIndex2].name].m_Index;
    86.                     current_bone_weight.boneIndex3 = added_bones[renderer_bones[current_bone_weight.boneIndex3].name].m_Index;
    87.                     current_bone_weight.boneIndex1 = added_bones[renderer_bones[current_bone_weight.boneIndex1].name].m_Index;
    88.  
    89.                     bone_weights_list[i] = current_bone_weight;
    90.                 }
    91.                 bone_weights[renderer.sharedMesh] = bone_weights_list;
    92.  
    93.                 // Handle bad input
    94.                 if (renderer.sharedMaterials.Length != renderer.sharedMesh.subMeshCount)
    95.                 {
    96.                     Debug.LogError("Mismatch between material count and submesh count. Is this the correct MeshRenderer?");
    97.                     continue;
    98.                 }
    99.  
    100.                 // Prepare stuff for mesh combination with same materials
    101.                 for (int i = 0; i < renderer.sharedMesh.subMeshCount; i++)
    102.                 {
    103.                     // Material not in dict, add it
    104.                     if (!combineInstanceArrays.ContainsKey(renderer.sharedMaterials[i]))
    105.                         combineInstanceArrays[renderer.sharedMaterials[i]] = new List<CombineInstance>();
    106.                     var actual_mat_list = combineInstanceArrays[renderer.sharedMaterials[i]];
    107.                     // Add new instance
    108.                     var combine_instance = new CombineInstance();
    109.                     combine_instance.transform = renderer.transform.localToWorldMatrix;
    110.                     combine_instance.subMeshIndex = i;
    111.                     combine_instance.mesh = renderer.sharedMesh;
    112.  
    113.                     actual_mat_list.Add(combine_instance);
    114.                 }
    115.                 // No need to use it anymore
    116.                 renderer.enabled = false;
    117.             }
    118.             var bones_hierarchy = new HierarchyDict();
    119.             // Recreate bone structure
    120.             foreach (var bone in all_bones)
    121.             {
    122.                 // Bone not processed, process it
    123.                 if (!bones_hierarchy.ContainsKey(bone.Key))
    124.                     AddParent(bone.Key, bones_hierarchy, all_bones, dummy_parent);
    125.             }
    126.  
    127.             // Create bone array from preprocessed dict
    128.             var bones = new Transform[added_bones.Count];
    129.             // Also fill the bind poses
    130.             var bind_poses = new Matrix4x4[bones.Length];
    131.             foreach(var bone in added_bones)
    132.             {
    133.                 bones[bone.Value.m_Index] = bones_hierarchy[bone.Key];
    134.                 bind_poses[bone.Value.m_Index] = bone.Value.m_BindPose;
    135.             }
    136.  
    137.             // Get the root bone
    138.             Transform root_bone = bones[0];
    139.  
    140.             while (root_bone.parent != null)
    141.             {
    142.                 // Get parent
    143.                 if (bones_hierarchy.ContainsKey(root_bone.parent.name))
    144.                     root_bone = root_bone.parent;
    145.                 else
    146.                     break;
    147.             }
    148.  
    149.  
    150.             // Create skinned mesh renderer GO
    151.             GameObject combined_mesh_go = new GameObject("Combined");
    152.             combined_mesh_go.transform.parent = final_mesh_go.transform;
    153.             combined_mesh_go.transform.localPosition = Vector3.zero;
    154.  
    155.             // Need to move it to new GO
    156.             root_bone.parent = final_mesh_go.transform;
    157.  
    158.             // Combine meshes into one
    159.             var combined_new_mesh = new Mesh();
    160.             var combined_vertices = new List<Vector3>();
    161.             var combined_uvs = new List<Vector2>();
    162.             var combined_indices = new List<int[]>();
    163.             var combined_bone_weights = new List<BoneWeight>();
    164.             var combined_materials = new Material[combineInstanceArrays.Count];
    165.  
    166.             var vertex_offset_map = new Dictionary<Mesh, int>();
    167.  
    168.             int vertex_index_offset = 0;
    169.             int current_material_index = 0;
    170.  
    171.             foreach (var combine_instance in combineInstanceArrays)
    172.             {
    173.                 combined_materials[current_material_index++] = combine_instance.Key;
    174.                 var submesh_indices = new List<int>();
    175.                 // Process meshes for each material
    176.                 foreach (var combine in combine_instance.Value)
    177.                 {
    178.                     // Update vertex offset for current mesh
    179.                     if (!vertex_offset_map.ContainsKey(combine.mesh))
    180.                     {
    181.                         // Add vertices for mesh
    182.                         combined_vertices.AddRange(combine.mesh.vertices);
    183.                         // Set uvs
    184.                         combined_uvs.AddRange(combine.mesh.uv);
    185.                         // Add weights
    186.                         combined_bone_weights.AddRange(bone_weights[combine.mesh]);
    187.  
    188.                         vertex_offset_map[combine.mesh] = vertex_index_offset;
    189.                         vertex_index_offset += combine.mesh.vertexCount;
    190.                     }
    191.                     int vertex_current_offset = vertex_offset_map[combine.mesh];
    192.  
    193.                     var indices = combine.mesh.GetTriangles(combine.subMeshIndex);
    194.                     // Need to "shift" indices
    195.                     for (int k = 0; k < indices.Length; ++k)
    196.                         indices[k] += vertex_current_offset;
    197.  
    198.                     submesh_indices.AddRange(indices);
    199.                 }
    200.                 // Push indices for given submesh
    201.                 combined_indices.Add(submesh_indices.ToArray());
    202.             }
    203.  
    204.             combined_new_mesh.vertices = combined_vertices.ToArray();
    205.             combined_new_mesh.uv = combined_uvs.ToArray();
    206.             combined_new_mesh.boneWeights = combined_bone_weights.ToArray();
    207.  
    208.             combined_new_mesh.subMeshCount = combined_materials.Length;
    209.             for (int i = 0; i < combined_indices.Count; ++i)
    210.                 combined_new_mesh.SetTriangles(combined_indices[i], i);
    211.  
    212.             // Create mesh renderer
    213.             SkinnedMeshRenderer combined_skin_mesh_renderer = combined_mesh_go.AddComponent<SkinnedMeshRenderer>();
    214.             combined_skin_mesh_renderer.sharedMesh = combined_new_mesh;
    215.             combined_skin_mesh_renderer.bones = bones;
    216.             combined_skin_mesh_renderer.rootBone = root_bone;
    217.             combined_skin_mesh_renderer.sharedMesh.bindposes = bind_poses;
    218.  
    219.             combined_skin_mesh_renderer.sharedMesh.RecalculateNormals();
    220.             combined_skin_mesh_renderer.sharedMesh.RecalculateBounds();
    221.             combined_skin_mesh_renderer.sharedMaterials = combined_materials;
    222.  
    223.             // Destroy children
    224.             foreach (var child in child_objects_to_destroy)
    225.                 GameObject.DestroyImmediate(child);
    226.             // Destroy dummy parent
    227.             GameObject.DestroyImmediate(dummy_parent);
    228.  
    229.             return final_mesh_go;
    230.         }
    231.  
    232.         static void AddParent(string BoneName, HierarchyDict BoneHierarchy, BoneTransformDict AllBones, GameObject DummyParent)
    233.         {
    234.             Transform actual_bone = null;
    235.             // Must be bone
    236.             if (AllBones.ContainsKey(BoneName))
    237.             {
    238.                 var bone_tuple = AllBones[BoneName];
    239.                 // Add parent recursively if not added
    240.                 if (!BoneHierarchy.ContainsKey(bone_tuple._2))
    241.                 {
    242.                     AddParent(bone_tuple._2, BoneHierarchy, AllBones, DummyParent);
    243.                     // Unparent all children of parents
    244.                     Unparent(BoneHierarchy[bone_tuple._2], DummyParent);
    245.                 }
    246.  
    247.  
    248.                 bone_tuple._1.parent = BoneHierarchy[bone_tuple._2];
    249.                 actual_bone = bone_tuple._1;
    250.             }
    251.  
    252.             BoneHierarchy[BoneName] = actual_bone;
    253.         }
    254.  
    255.         static void Unparent(Transform Parent, GameObject DummyParent)
    256.         {
    257.             if (Parent != null)
    258.             {
    259.                 var unparent_list = new List<Transform>();
    260.  
    261.                 foreach (Transform child in Parent.transform)
    262.                     unparent_list.Add(child);
    263.  
    264.                 foreach (var child in unparent_list)
    265.                     child.parent = DummyParent.transform;
    266.             }
    267.         }
    268. #endregion
    269.     }
    270. }
     
  25. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    Thank you for this script!
     
  26. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    Guys, if you need to combine skinned meshes and preserve their blendshape data, take a Trigve's script and make these changes:

    Code (CSharp):
    1. // bone_weights[renderer.sharedMesh] = bone_weights_list;
    2.  
    3. var nm = new Mesh();
    4. renderer.BakeMesh(nm);
    5. bone_weights[nm] = bone_weights_list;
    6.  
    7. // combine_instance.mesh = renderer.sharedMesh;
    8.  
    9. combine_instance.mesh = nm;
    Worms.jpg Worms.jpg

    This worked for me
    Thanks again for the script!
     
    Last edited: Feb 6, 2016
  27. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    After I divided my model into segments to dynamically substitute them with different armor sets I've encountered with these annoying artifacts and now see the solution in vertixes welding.
    But... How to make it?

    Body.jpg
     
  28. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    Hmmm, that's strange.

    Is the left image already divided? I don't have the much time (as always ;)) but if you could prepare some simplified mesh which reproduce the behavior I could look at it.Now, I remember that I have a similar artifacts but with shadows from the combined mesh. Maybe it is the same problem.
     
  29. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    No, the left image is full body single mesh, the right one are group of segments I made by cutting source mesh
     
  30. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    The problem appears right away I cut my source body mesh into the segments:

    Body.jpg

    It's not related to the script, I just don't know whether its possible to join body parts smoothly and trying to solve the situation by velding the vertices.
     
  31. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    Bodies1.jpg Bodies2.jpg

    A managed to add vertices welding into the script. 1st model is full body source mesh, 2nd are body parts prepared for combining, the 3rd one is combined and welded SkinnedMeshRenderer. Bone weights animation looks fine but the UVs are not, as you see.

    Here is current version of the script:

    Code (CSharp):
    1. namespace Jax.Utils
    2. {
    3.     using UnityEngine;
    4.     using System.Collections.Generic;
    5.  
    6.     using HierarchyDict = System.Collections.Generic.Dictionary<string, UnityEngine.Transform>;
    7.     using BoneTransformDict = System.Collections.Generic.Dictionary<string, Jax.Utils.Tuple<UnityEngine.Transform, string>>;
    8.  
    9.     struct BoneDef
    10.     {
    11.         public int m_Index;
    12.         public Matrix4x4 m_BindPose;
    13.     }
    14.    
    15.     public struct Tuple<T1, T2>
    16.     {
    17.         public readonly T1 _1;
    18.         public readonly T2 _2;
    19.         public Tuple(T1 T1_, T2 T2_)
    20.         {
    21.             _1 = T1_;
    22.             _2 = T2_;
    23.         }
    24.     }
    25.  
    26.     public class MeshCombiner
    27.     {
    28.         #region Operations
    29.         //! Combine mesh.
    30.         /*!
    31.             \return combined mesh instance.
    32.         */
    33.         public static GameObject Combine(List<SkinnedMeshRenderer> SkinnedRenderers, float weldThresold)
    34.         {
    35.             // Generated GO
    36.             GameObject final_mesh_go = new GameObject("Mesh");
    37.             // Dummy parent holder
    38.             GameObject dummy_parent = new GameObject("DummyParent");
    39.  
    40.             // All available bones
    41.             var all_bones = new BoneTransformDict();
    42.             // Traverse through all skinned mesh renderers
    43.             foreach (var renderer in SkinnedRenderers)
    44.             {
    45.                 var renderer_bones = renderer.bones;
    46.                 foreach (var bone in renderer_bones)
    47.                 {
    48.                     // Bone doesn't exist, add it
    49.                     if (!all_bones.ContainsKey(bone.name))
    50.                         all_bones[bone.name] = new Jax.Utils.Tuple<Transform, string>(bone, bone.parent.name);
    51.                 }
    52.             }
    53.  
    54.             var combineInstanceArrays = new Dictionary<Material, List<CombineInstance>>();
    55.             var bone_weights = new Dictionary<Mesh, BoneWeight[]>();
    56.            
    57.             // Map between bone name and bone info
    58.             var added_bones = new Dictionary<string, BoneDef>();
    59.             // List of child objects holding the skinned mesh renderers to be
    60.             // destroyed when finished
    61.             var child_objects_to_destroy = new List<GameObject>();
    62.  
    63.             int bone_index = 0;
    64.             foreach (var renderer in SkinnedRenderers)
    65.             {
    66.                 child_objects_to_destroy.Add(renderer.transform.parent.gameObject);
    67.  
    68.                 var renderer_bones = renderer.bones;
    69.                 var bind_poses_mesh = renderer.sharedMesh.bindposes;
    70.  
    71.                 int bone_index_local = 0;
    72.                 // Add all bones as first and save the indices of them
    73.                 foreach (var bone in renderer_bones)
    74.                 {
    75.                     // Bone not yet added
    76.                     if (!added_bones.ContainsKey(bone.name))
    77.                     {
    78.                         var bonde_def = new BoneDef();
    79.  
    80.                         bonde_def.m_Index = bone_index++;
    81.                         // Save the original bind pose also
    82.                         bonde_def.m_BindPose = bind_poses_mesh[bone_index_local];
    83.  
    84.                         added_bones[bone.name] = bonde_def;
    85.                     }
    86.                     ++bone_index_local;
    87.                 }
    88.                 // Adjust bone weights indices based on real indices of bones
    89.                 var bone_weights_list = new BoneWeight[renderer.sharedMesh.boneWeights.Length];
    90.                 var renderer_bone_weights = renderer.sharedMesh.boneWeights;
    91.                 for (int i = 0; i < renderer_bone_weights.Length; ++i)
    92.                 {
    93.  
    94.                     BoneWeight current_bone_weight = renderer_bone_weights[i];
    95.  
    96.                     current_bone_weight.boneIndex0 = added_bones[renderer_bones[current_bone_weight.boneIndex0].name].m_Index;
    97.                     current_bone_weight.boneIndex2 = added_bones[renderer_bones[current_bone_weight.boneIndex2].name].m_Index;
    98.                     current_bone_weight.boneIndex3 = added_bones[renderer_bones[current_bone_weight.boneIndex3].name].m_Index;
    99.                     current_bone_weight.boneIndex1 = added_bones[renderer_bones[current_bone_weight.boneIndex1].name].m_Index;
    100.  
    101.                     bone_weights_list[i] = current_bone_weight;
    102.                 }
    103.                
    104.                 var nm = new Mesh();
    105.                 renderer.BakeMesh(nm);
    106.                    
    107.                 // nm.name = renderer.sharedMesh.name;
    108.                 // nm.bindposes = (Matrix4x4[]) renderer.sharedMesh.bindposes.Clone();
    109.                 // nm.boneWeights = (BoneWeight[]) renderer.sharedMesh.boneWeights.Clone();
    110.                
    111.                 // bone_weights[renderer.sharedMesh] = bone_weights_list;
    112.                 bone_weights[nm] = bone_weights_list;
    113.  
    114.                 // Handle bad input
    115.                 if (renderer.sharedMaterials.Length != renderer.sharedMesh.subMeshCount)
    116.                 {
    117.                     Debug.LogError("Mismatch between material count and submesh count. Is this the correct MeshRenderer?");
    118.                     continue;
    119.                 }
    120.  
    121.                 // Prepare stuff for mesh combination with same materials
    122.                 for (int i = 0; i < renderer.sharedMesh.subMeshCount; i++)
    123.                 {
    124.                     // Material not in dict, add it
    125.                     if (!combineInstanceArrays.ContainsKey(renderer.sharedMaterials[i]))
    126.                         combineInstanceArrays[renderer.sharedMaterials[i]] = new List<CombineInstance>();
    127.                     var actual_mat_list = combineInstanceArrays[renderer.sharedMaterials[i]];
    128.                     // Add new instance
    129.                     var combine_instance = new CombineInstance();
    130.                     combine_instance.transform = renderer.transform.localToWorldMatrix;
    131.                     combine_instance.subMeshIndex = i;
    132.                     // combine_instance.mesh = renderer.sharedMesh;
    133.                    
    134.                    
    135.                     combine_instance.mesh = nm;
    136.                    
    137.                     actual_mat_list.Add(combine_instance);
    138.                 }
    139.                 // No need to use it anymore
    140.                 renderer.enabled = false;
    141.             }
    142.             var bones_hierarchy = new HierarchyDict();
    143.             // Recreate bone structure
    144.             foreach (var bone in all_bones)
    145.             {
    146.                 // Bone not processed, process it
    147.                 if (!bones_hierarchy.ContainsKey(bone.Key))
    148.                     AddParent(bone.Key, bones_hierarchy, all_bones, dummy_parent);
    149.             }
    150.  
    151.             // Create bone array from preprocessed dict
    152.             var bones = new Transform[added_bones.Count];
    153.             // Also fill the bind poses
    154.             var bind_poses = new Matrix4x4[bones.Length];
    155.             foreach (var bone in added_bones)
    156.             {
    157.                 bones[bone.Value.m_Index] = bones_hierarchy[bone.Key];
    158.                 bind_poses[bone.Value.m_Index] = bone.Value.m_BindPose;
    159.             }
    160.  
    161.             // Get the root bone
    162.             Transform root_bone = bones[0];
    163.  
    164.             while (root_bone.parent != null)
    165.             {
    166.                 // Get parent
    167.                 if (bones_hierarchy.ContainsKey(root_bone.parent.name))
    168.                     root_bone = root_bone.parent;
    169.                 else
    170.                     break;
    171.             }
    172.  
    173.  
    174.             // Create skinned mesh renderer GO
    175.             GameObject combined_mesh_go = new GameObject("Combined");
    176.             combined_mesh_go.transform.parent = final_mesh_go.transform;
    177.             combined_mesh_go.transform.localPosition = Vector3.zero;
    178.  
    179.             // Need to move it to new GO
    180.             root_bone.parent = final_mesh_go.transform;
    181.  
    182.             // Combine meshes into one
    183.             var combined_new_mesh = new Mesh();
    184.             var combined_vertices = new List<Vector3>();
    185.             var combined_uvs = new List<Vector2>();
    186.             var combined_indices = new List<int[]>();
    187.             var combined_bone_weights = new List<BoneWeight>();
    188.             var combined_materials = new Material[combineInstanceArrays.Count];
    189.  
    190.             var vertex_offset_map = new Dictionary<Mesh, int>();
    191.  
    192.             int vertex_index_offset = 0;
    193.             int current_material_index = 0;
    194.  
    195.             foreach (var combine_instance in combineInstanceArrays)
    196.             {
    197.                 combined_materials[current_material_index++] = combine_instance.Key;
    198.                 List<int> submesh_indices = new List<int>();
    199.                 // Process meshes for each material
    200.                 foreach (var combine in combine_instance.Value)
    201.                 {                  
    202.                     // Update vertex offset for current mesh
    203.                     if (!vertex_offset_map.ContainsKey(combine.mesh))
    204.                     {
    205.                         // Add vertices for mesh
    206.                         combined_vertices.AddRange(combine.mesh.vertices);
    207.                         // Set uvs
    208.                         combined_uvs.AddRange(combine.mesh.uv);
    209.                         // Add weights
    210.                         combined_bone_weights.AddRange(bone_weights[combine.mesh]);
    211.                        
    212.  
    213.                         vertex_offset_map[combine.mesh] = vertex_index_offset;
    214.                         vertex_index_offset += combine.mesh.vertexCount;
    215.                     }
    216.                     int vertex_current_offset = vertex_offset_map[combine.mesh];
    217.  
    218.                     int[] indices = combine.mesh.GetTriangles(combine.subMeshIndex);
    219.                     // Need to "shift" indices
    220.                     for (int k = 0; k < indices.Length; ++k)
    221.                         indices[k] += vertex_current_offset;
    222.  
    223.                     submesh_indices.AddRange(indices);
    224.                 }
    225.                 // Push indices for given submesh
    226.                 combined_indices.Add(submesh_indices.ToArray());
    227.             }
    228.            
    229.             var welded_vertices = new List<Vector3>();
    230.             var welded_uvs = new List<Vector2>();
    231.             var welded_bone_weights = new List<BoneWeight>();
    232.            
    233.             int h = 0;
    234.             foreach (Vector3 vert in combined_vertices) {
    235.                 bool ok = true;
    236.                 foreach (Vector3 newVert in welded_vertices)
    237.                     if (Vector3.Distance(newVert, vert) <= weldThresold) {
    238.                         ok = false;
    239.                         break;
    240.                     }
    241.                        
    242.                 if (ok) {
    243.                     welded_vertices.Add(vert);
    244.                     welded_uvs.Add(combined_uvs[h]);
    245.                     welded_bone_weights.Add(combined_bone_weights[h]);
    246.                 }
    247.  
    248.                 ++h;
    249.             }
    250.            
    251.            
    252.            
    253.             Debug.Log(combined_vertices.Count + " / " + combined_uvs.Count + " / " + combined_bone_weights.Count + " -> " + welded_vertices.Count + " / " + welded_uvs.Count + " / " + welded_bone_weights.Count);
    254.            
    255.             combined_new_mesh.vertices = welded_vertices.ToArray();
    256.             combined_new_mesh.uv = welded_uvs.ToArray();
    257.             combined_new_mesh.boneWeights = welded_bone_weights.ToArray();
    258.            
    259.            
    260.  
    261.             // combined_new_mesh.vertices = combined_vertices.ToArray();
    262.             // combined_new_mesh.uv = combined_uvs.ToArray();
    263.             // combined_new_mesh.boneWeights = combined_bone_weights.ToArray();
    264.  
    265.             combined_new_mesh.subMeshCount = combined_materials.Length;
    266.             for (int i = 0; i < combined_indices.Count; ++i) {
    267.                 // combined_new_mesh.SetTriangles(combined_indices[i], i);
    268.                
    269.                 for (int l = 0; l < combined_indices[i].Length; l ++) {
    270.                     for (int j = 0; j < welded_vertices.Count; j ++) {
    271.                         if (Vector3.Distance( welded_vertices[j], combined_vertices[ combined_indices[i][l] ] ) <= weldThresold) {
    272.                             combined_indices[i][l] = j;
    273.                             break;
    274.                         }
    275.                     }
    276.                 }
    277.                
    278.                 combined_new_mesh.SetTriangles(combined_indices[i], i);
    279.             }
    280.            
    281.                
    282.             // Create mesh renderer
    283.             SkinnedMeshRenderer combined_skin_mesh_renderer = combined_mesh_go.AddComponent<SkinnedMeshRenderer>();
    284.             combined_skin_mesh_renderer.sharedMesh = combined_new_mesh;
    285.             combined_skin_mesh_renderer.bones = bones;
    286.             combined_skin_mesh_renderer.rootBone = root_bone;
    287.             combined_skin_mesh_renderer.sharedMesh.bindposes = bind_poses;
    288.  
    289.             combined_skin_mesh_renderer.sharedMesh.RecalculateNormals();
    290.             combined_skin_mesh_renderer.sharedMesh.RecalculateBounds();
    291.             combined_skin_mesh_renderer.sharedMaterials = combined_materials;
    292.  
    293.             // Destroy children
    294.             foreach (var child in child_objects_to_destroy)
    295.                 GameObject.DestroyImmediate(child);
    296.             // Destroy dummy parent
    297.             GameObject.DestroyImmediate(dummy_parent);
    298.  
    299.             return final_mesh_go;
    300.         }
    301.  
    302.         static void AddParent(string BoneName, HierarchyDict BoneHierarchy, BoneTransformDict AllBones, GameObject DummyParent)
    303.         {
    304.             Transform actual_bone = null;
    305.             // Must be bone
    306.             if (AllBones.ContainsKey(BoneName))
    307.             {
    308.                 var bone_tuple = AllBones[BoneName];
    309.                 // Add parent recursively if not added
    310.                 if (!BoneHierarchy.ContainsKey(bone_tuple._2))
    311.                 {
    312.                     AddParent(bone_tuple._2, BoneHierarchy, AllBones, DummyParent);
    313.                     // Unparent all children of parents
    314.                     Unparent(BoneHierarchy[bone_tuple._2], DummyParent);
    315.                 }
    316.  
    317.  
    318.                 bone_tuple._1.parent = BoneHierarchy[bone_tuple._2];
    319.                 actual_bone = bone_tuple._1;
    320.             }
    321.  
    322.             BoneHierarchy[BoneName] = actual_bone;
    323.         }
    324.  
    325.         static void Unparent(Transform Parent, GameObject DummyParent)
    326.         {
    327.             if (Parent != null)
    328.             {
    329.                 var unparent_list = new List<Transform>();
    330.  
    331.                 foreach (Transform child in Parent.transform)
    332.                     unparent_list.Add(child);
    333.  
    334.                 foreach (var child in unparent_list)
    335.                     child.parent = DummyParent.transform;
    336.             }
    337.         }
    338.        
    339.         #endregion
    340.     }
    341. }
     
  32. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    If the divided mesh is looking wrong (as in the "first" reply) than maybe you're dividing it wrong. I'm not a 3D artist so I don't know the techniques which could be applied that all the normals etc. are preserved after the operation. I don't think it is right approach to use welding in this script, because it is heuristic approach, which I don't like :) If you could post me this simplified model (whole and divided) I could try something with it
     
  33. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    @Trigve Really sorry about the late reply, apparently one of my email addresses had some issues. The explosion of hope when I saw new messages on this thread man, hnnggh.

    But alas, didn't change the way things are behaving on my end. Model's fine until I play an animation, at which point the whole thing including the skeleton turns 90 degrees and falls flat on it's face, both physically and metaphorically. Does not happen on single models without the mesh combination.
     
    Last edited: Feb 13, 2016
  34. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    And as for me, I decided not to divide body mesh and hide segments covered by armor. Instead of it character now have mesh copy with transparent cloth material and higher draw order. Works fine.
     
  35. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    And now I noticed that lose all smoothing after combine mesh. F***!

    Dude.jpg
     

    Attached Files:

    • Dude.jpg
      Dude.jpg
      File size:
      284.1 KB
      Views:
      1,089
    Last edited: Feb 9, 2016
  36. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    I'll try to look at it on friday or weekend.
     
  37. Tonghae

    Tonghae

    Joined:
    Nov 1, 2013
    Posts:
    16
    Well, instead of recalculating normals I just copy them with other mesh data:

    Code (CSharp):
    1. var combined_vertices = new List<Vector3>();
    2. // add this
    3. var combined_normals = new List<Vector3>();
    4.  
    5. ...
    6.  
    7. combined_vertices.AddRange(combine.mesh.vertices);
    8. // add this
    9. combined_normals.AddRange(combine.mesh.normals);
    10.  
    11. ...
    12.  
    13. // remove this
    14. // combined_skin_mesh_renderer.sharedMesh.RecalculateNormals();
    Not sure if it's correct approach or not, but it works for me exactly as I want to Cool.jpg
     
  38. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    Tonghae, yes, you're probably right with the normals. But don't forget the this line in the code also
    Code (CSharp):
    1. combined_new_mesh.normals = combined_normals.ToArray();
    Scrawny, could you post me the animation files? Or better the models you're using for combining with animations?
     
  39. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    @Trigve I'm just gonna pop the entire project HERE. It's a slightly older version but should serve the same purpose. It's one big mess full of different things I've tried, so prepare your butt.
    Original Assets>Scenes>Test meld mesh, play the scene, then turn on the animator on PC_MeldTest_Blend to see how things go down. Alternatively turn off it's CombineMesh script and see that it works as intended (though it only works on the legs)
     
  40. Trigve

    Trigve

    Joined:
    Mar 17, 2013
    Posts:
    139
    So I've looked at it and the problem is the "Armature" transform that is the parent of the bones. So the combined bones should be parented under this Transform. There isn't clean solution to this (at least I don't see one), but you could modify the combine function to take the Transform/string object as a parameter under which the bones will be parented and change the combine function to do it.

    Honestly, I don't like the "Armature" transform. So I would advice to look at the models in blender and try to remove the "Armature" transform so that root bone "doesn't have any parent".
     
  41. ComboRoutine

    ComboRoutine

    Joined:
    Feb 3, 2014
    Posts:
    24
    Found this thread in my bookmarks. Never fixed it, remains to be the absolute bane of my existence and grinded (ground?) the project to a halt. Tried working on it again, PC monitor is currently in orbit. Rip.
     
  42. endie

    endie

    Joined:
    Jan 16, 2013
    Posts:
    30
    Cough cough more than a year later I stumbled upon this thread. I put together the new version of Trigve's MeshCombiner script (which accounts for stored normals). Then added a few lines to the CombineMesh script:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class CombineMesh : MonoBehaviour {
    7.     //List<SkinnedMeshRenderer> meshRenderers = new List<SkinnedMeshRenderer>();
    8.  
    9.     void Start()
    10.     {
    11.         Transform _thisTransform = transform;
    12.  
    13.         List<SkinnedMeshRenderer> meshRenderers = new List<SkinnedMeshRenderer> (gameObject.GetComponentsInChildren<SkinnedMeshRenderer>());
    14.         utils.MeshCombiner.Combine(meshRenderers);
    15.  
    16.         GameObject _mesh = GameObject.Find ("Mesh");
    17.         _mesh.transform.position = _thisTransform.position; // set the new mesh's position to the player object's position
    18.         _mesh.transform.rotation = _thisTransform.rotation * Quaternion.Euler(-90,0,0); // set the new mesh's position to the player object's rotation
    19.         _mesh.transform.parent = transform; //THEN parent to the player object for a 0,0,0 transform
    20.         DrawBones drawBones = gameObject.AddComponent<DrawBones> ();
    21.     }
    22. }
    23.  
    24.  
    Basically, the way Blender currently handles FBX export is that when exporting the armature, it sets the armature's 'up' axis to z+ which is then problematic when the MeshCombiner takes the armature out of the container that is making its axis appear correctly. I played around with some export settings in Blender and didn't actually find a way to change this problem with the source file, so instead figured I'd do it in the script.

    I also went in and corrected the positioning as well since I had the need to spawn the player somewhere other than 0,0,0 world space.

    Tbh, I'm a newbie coder. There could be a cleaner way to do this, but it works for me.
     
    Last edited: Nov 16, 2017
    kushagra_unity368 and local306 like this.