Sign in to follow this  
rogerdv

Unity Copying Skinned Mesh Renderer and bones from one GO to another in unity

Recommended Posts

rogerdv    418

Im trying to implement a modular character system in Unity3d 5, which seems to be quite poorly documented. So far, what I have underestood from several sources is that I have to split the character mesh in the required parts, export each one separately including the skeleton, and export the skeleton without geometry. Then each part must be instantiated, but just to copy its Skinned Mesh Renderer component to a new GameObject, which is child of a parent game object that holds the asembled character mesh plus the skeleton. Also, each Skinned Mesh Renderer must have its bones overwriten with the ones from the skeleton.

I have tried to implement this, but I have only  managed to make an script that runs and has no syntax errors, but it does nothing:

using UnityEngine;
using System.Collections;

public class assemble : MonoBehaviour {
	Transform me;
	Animator anim;
	private Vector3 position;
	NavMeshAgent agent;

	GameObject head;
	GameObject torso;
	GameObject hands;
	GameObject legs;
	GameObject feet;	

	// Use this for initialization
	void Start () {
		me = transform;
		anim = GetComponent<Animator>();
		agent = GetComponent<NavMeshAgent> ();
		
		GameObject prefab = Resources.Load("vmale-head") as GameObject;
		GameObject tt2 = Instantiate (prefab);
		head = new GameObject("head");
		head.transform.SetParent( transform);
		head.transform.localPosition = Vector3.zero;
		head.transform.localRotation = Quaternion.Euler(Vector3.zero);
		head.transform.localScale = transform.localScale;
		var comp = head.AddComponent<SkinnedMeshRenderer>();
		SkinnedMeshRenderer[] t = tt2.GetComponentsInChildren<SkinnedMeshRenderer> ();
		if (t.Length > 0) {
			comp.sharedMesh = t [0].sharedMesh; //copy geometry
			comp.materials = t [0].materials; //and copy materials
		} else
			Debug.LogWarning("No components found!!");		
		Destroy (tt2);

		prefab = Resources.Load("vmale-torso") as GameObject;
		tt2 = Instantiate (prefab);
		torso = new GameObject("torso");
		torso.transform.SetParent( transform);
		torso.transform.localPosition = Vector3.zero;
		torso.transform.localRotation = Quaternion.Euler(Vector3.zero);
		comp = torso.AddComponent<SkinnedMeshRenderer>();
		t = tt2.GetComponentsInChildren<SkinnedMeshRenderer> ();
		if (t.Length > 0) {
			comp.sharedMesh = t [0].sharedMesh;
			comp.materials = t [0].materials;
		} else
			Debug.LogWarning("No components found!!");
		Destroy (tt2);
			
	}
}

The parent gameobject  gets the 2 children Im creating, but they are empty, no actual geometry is copied (or at least, it is not visible) and the new gameobjects are placed at 0,0,0, in the corner of the map. Also, I cant find a way to attach the skeleton to the parent or copy its bones, because the exported file doesnt produces an Skinned Mesh Render, as it has no geometry.

Can somebody give me some directions about this?

Edited by rogerdv

Share this post


Link to post
Share on other sites
Trigves    237

Here is the old script that I mentioned in the other thread (and was using it). Not the optimal one, just proof of the concept:

using UnityEngine;
using System.Collections.Generic;

using HierarchyDict = System.Collections.Generic.Dictionary<string, UnityEngine.Transform>;
using BoneTransformDict = System.Collections.Generic.Dictionary<string, utils.Tuple<UnityEngine.Transform, string>>;

namespace utils
{
	public class MeshCombiner
	{
#region Operations
		//! Combine mesh.
		/*!
			\return combined mesh instance.
		*/
		public static Mesh Combine(GameObject Object, GameObject NewObject)
		{
			// Dummy parent holder
			GameObject dummy_parent = new GameObject("DummyParent");

			// Be sure new object is at same position as reference object, otherwise
			// combined mesh would be distored
			NewObject.transform.position = Object.transform.position;

			// All skin renderers in all children
			var skin_renderers = Object.GetComponentsInChildren<SkinnedMeshRenderer>();
			// All available bones
			var all_bones = new BoneTransformDict();
			// Traverse through all skinned mesh renderers
			foreach (var renderer in skin_renderers)
			{
				var renderer_bones = renderer.bones;
				foreach (var bone in renderer_bones)
				{
					// Bone doesn't exist, add it
					if (!all_bones.ContainsKey(bone.name))
						all_bones[bone.name] = new utils.Tuple<Transform, string>(bone, bone.parent.name);
				}
			}

			var combineInstanceArrays = new Dictionary<Material, List<CombineInstance>>();
			var bone_weights = new Dictionary<Mesh, BoneWeight[]>();
			// Map between bone name and index
			var added_bones = new Dictionary<string, int>();
			// List of child objects holding the skinned mesh renderers to be
			// destroyed when finished
			var child_objects_to_destroy = new List<GameObject>();

			int bone_index = 0;
			foreach (var renderer in skin_renderers)
			{
				child_objects_to_destroy.Add(renderer.transform.parent.gameObject);

				var renderer_bones = renderer.bones;
				// Add all bones as first and save the indices of them
				foreach (var bone in renderer_bones)
				{
					// Bone not yet added
					if (!added_bones.ContainsKey(bone.name))
						added_bones[bone.name] = bone_index++;
				}
				// Adjust bone weights indices based on real indices of bones
				var bone_weights_list = new BoneWeight[renderer.sharedMesh.boneWeights.Length];
				var renderer_bone_weights = renderer.sharedMesh.boneWeights;
				for (int i = 0; i < renderer_bone_weights.Length; ++i)
				{

					BoneWeight current_bone_weight = renderer_bone_weights[i];

					current_bone_weight.boneIndex0 = added_bones[renderer_bones[current_bone_weight.boneIndex0].name];
					current_bone_weight.boneIndex2 = added_bones[renderer_bones[current_bone_weight.boneIndex2].name];
					current_bone_weight.boneIndex3 = added_bones[renderer_bones[current_bone_weight.boneIndex3].name];
					current_bone_weight.boneIndex1 = added_bones[renderer_bones[current_bone_weight.boneIndex1].name];

					bone_weights_list[i] = current_bone_weight;
				}
				bone_weights[renderer.sharedMesh] = bone_weights_list;

				// Handle bad input
				if (renderer.sharedMaterials.Length != renderer.sharedMesh.subMeshCount)
				{
					Debug.LogError("Mismatch between material count and submesh count. Is this the correct MeshRenderer?");
					continue;
				}

				// Prepare stuff for mesh combination with same materials
				for (int i = 0; i < renderer.sharedMesh.subMeshCount; i++)
				{
					// Material not in dict, add it
					if (!combineInstanceArrays.ContainsKey(renderer.sharedMaterials[i]))
						combineInstanceArrays[renderer.sharedMaterials[i]] = new List<CombineInstance>();
					var actual_mat_list = combineInstanceArrays[renderer.sharedMaterials[i]];
					// Add new instance
					var combine_instance = new CombineInstance();
					combine_instance.transform = renderer.transform.localToWorldMatrix;
					combine_instance.subMeshIndex = i;
					combine_instance.mesh = renderer.sharedMesh;

					actual_mat_list.Add(combine_instance);
				}
				// No need to use it anymore
				renderer.enabled = false;
			}
			var bones_hierarchy = new HierarchyDict();
			// Recreate bone structure
			foreach (var bone in all_bones)
			{
				// Bone not processed, process it
				if (!bones_hierarchy.ContainsKey(bone.Key))
					AddParent(bone.Key, bones_hierarchy, all_bones, dummy_parent);
			}

			// Create bone array from preprocessed dict
			var bones = new Transform[added_bones.Count];
			foreach (var bone in added_bones)
				bones[bone.Value] = bones_hierarchy[bone.Key];

			// Get the root bone
			Transform root_bone = bones[0];

			while (root_bone.parent != null)
			{
				// Get parent
				if (bones_hierarchy.ContainsKey(root_bone.parent.name))
					root_bone = root_bone.parent;
				else
					break;
			}


			// Create skinned mesh renderer GO
			GameObject combined_mesh_go = new GameObject("Combined");
			combined_mesh_go.transform.parent = NewObject.transform;
			combined_mesh_go.transform.localPosition = Vector3.zero;

			// Fill bind poses
			var bind_poses = new Matrix4x4[bones.Length];
			for (int i = 0; i < bones.Length; ++i)
				bind_poses[i] = bones[i].worldToLocalMatrix * combined_mesh_go.transform.localToWorldMatrix;

			// Need to move it to new GO
			root_bone.parent = NewObject.transform;

			// Combine meshes into one
			var combined_new_mesh = new Mesh();
			var combined_vertices = new List<Vector3>();
			var combined_uvs = new List<Vector2>();
			var combined_indices = new List<int[]>();
			var combined_bone_weights = new List<BoneWeight>();
			var combined_materials = new Material[combineInstanceArrays.Count];

			var vertex_offset_map = new Dictionary<Mesh, int>();

			int vertex_index_offset = 0;
			int current_material_index = 0;

			foreach (var combine_instance in combineInstanceArrays)
			{
				combined_materials[current_material_index++] = combine_instance.Key;
				var submesh_indices = new List<int>();
				// Process meshes for each material
				foreach (var combine in combine_instance.Value)
				{
					// Update vertex offset for current mesh
					if (!vertex_offset_map.ContainsKey(combine.mesh))
					{
						// Add vertices for mesh
						combined_vertices.AddRange(combine.mesh.vertices);
						// Set uvs
						combined_uvs.AddRange(combine.mesh.uv);
						// Add weights
						combined_bone_weights.AddRange(bone_weights[combine.mesh]);

						vertex_offset_map[combine.mesh] = vertex_index_offset;
						vertex_index_offset += combine.mesh.vertexCount;
					}
					int vertex_current_offset = vertex_offset_map[combine.mesh];

					var indices = combine.mesh.GetTriangles(combine.subMeshIndex);
					// Need to "shift" indices
					for (int k = 0; k < indices.Length; ++k)
						indices[k] += vertex_current_offset;

					submesh_indices.AddRange(indices);
				}
				// Push indices for given submesh
				combined_indices.Add(submesh_indices.ToArray());
			}

			combined_new_mesh.vertices = combined_vertices.ToArray();
			combined_new_mesh.uv = combined_uvs.ToArray();
			combined_new_mesh.boneWeights = combined_bone_weights.ToArray();

			combined_new_mesh.subMeshCount = combined_materials.Length;
			for (int i = 0; i < combined_indices.Count; ++i)
				combined_new_mesh.SetTriangles(combined_indices[i], i);

			// Create mesh renderer
			SkinnedMeshRenderer combined_skin_mesh_renderer = combined_mesh_go.AddComponent<SkinnedMeshRenderer>();
			combined_skin_mesh_renderer.sharedMesh = combined_new_mesh;
			combined_skin_mesh_renderer.bones = bones;
			combined_skin_mesh_renderer.rootBone = root_bone;
			combined_skin_mesh_renderer.sharedMesh.bindposes = bind_poses;

			combined_skin_mesh_renderer.sharedMesh.RecalculateNormals();
			combined_skin_mesh_renderer.sharedMesh.RecalculateBounds();
			combined_skin_mesh_renderer.sharedMaterials = combined_materials;

			// Destroy children
			foreach (var child in child_objects_to_destroy)
				GameObject.DestroyImmediate(child);
			// Destroy dummy parent
			GameObject.DestroyImmediate(dummy_parent);

			return combined_skin_mesh_renderer.sharedMesh;
		}

		static void AddParent(string BoneName, HierarchyDict BoneHierarchy, BoneTransformDict AllBones, GameObject DummyParent)
		{
			Transform actual_bone = null;
			// Must be bone
			if (AllBones.ContainsKey(BoneName))
			{
				var bone_tuple = AllBones[BoneName];
				// Add parent recursively if not added
				if (!BoneHierarchy.ContainsKey(bone_tuple._2))
				{
					AddParent(bone_tuple._2, BoneHierarchy, AllBones, DummyParent);
					// Unparent all children of parents
					Unparent(BoneHierarchy[bone_tuple._2], DummyParent);
				}


				bone_tuple._1.parent = BoneHierarchy[bone_tuple._2];
				actual_bone = bone_tuple._1;
			}

			BoneHierarchy[BoneName] = actual_bone;
		}

		static void Unparent(Transform Parent, GameObject DummyParent)
		{
			if (Parent != null)
			{
				var unparent_list = new List<Transform>();

				foreach (Transform child in Parent.transform)
					unparent_list.Add(child);

				foreach (var child in unparent_list)
					child.parent = DummyParent.transform;
			}
		}
#endregion
	}
}

Share this post


Link to post
Share on other sites
rogerdv    418

I found the problem in my script: I was copying only the SkinnedMeshRenderer to see "if it worked", but it didnt.  I added bone copy and now I see the resulting character and it idle animation, but other animations give several errors: Quaternion To Matrix conversion failed because input Quaternion is invalid {-0.757474, 0.102703, -0.196973, 0.627736} l=1.017166

Here is the relevant (I think) code:

#region TransformCatalog
class TransformCatalog : Dictionary<string, Transform>
{
	#region Constructors
	public TransformCatalog(Transform transform)
	{
		Catalog(transform);
	}
	#endregion
	
	#region Catalog
	private void Catalog(Transform transform)
	{
		Add(transform.name, transform);
		foreach (Transform child in transform)
			Catalog(child);
	}
	#endregion
}
#endregion


#region DictionaryExtensions
class DictionaryExtensions
{
	public static TValue Find<TKey, TValue>(Dictionary<TKey, TValue> source, TKey key)
	{
		TValue value;
		source.TryGetValue(key, out value);
		return value;
	}
}
#endregion

var boneCatalog = new TransformCatalog(me);
//repeat for each body part
GameObject prefab = Resources.Load("vmale-head") as GameObject;
GameObject tt2 = Instantiate (prefab);
head = new GameObject("head");
head.transform.SetParent( transform);
head.transform.localPosition = Vector3.zero;
head.transform.localRotation = Quaternion.Euler(Vector3.zero);
head.transform.localScale = transform.localScale;		
var skinnedMeshRenderers = tt2.GetComponentsInChildren<SkinnedMeshRenderer> ();
foreach (var sourceRenderer in skinnedMeshRenderers)
{
        var comp = head.AddComponent<SkinnedMeshRenderer>();
	comp.sharedMesh = sourceRenderer.sharedMesh;
	comp.materials = sourceRenderer.materials;
	comp.bones = TranslateTransforms(sourceRenderer.bones, boneCatalog);
}
Destroy (tt2);

I guess that Im missing more info that needs to be copied. The code is mostly based on the sample posted here.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this