From Unify Community Wiki
Revision as of 19:49, 28 April 2009 by Gabrielmsantos (Talk | contribs)

Jump to: navigation, search

Author: Gabriel Santos


Class Model that allows you combine multiple skinned meshes at runtime, useful for Avatar System. IMPORTANT: The number of vertices and bones is very particular, you must set it up according to your own model.

This Class uses the MeshCombineUtility provided by Unity!

Feel free to clean up the code and add to it. :D

C# Code

<csharp> /*IMPORTANT: READ !!!!!! @Autor: Gabriel Santos @Description Class that Combine Multilple SkinnedMeshes in just one Skinned Mesh Renderer @Usage: Just AddComponent("CombineSkinnedMeshes") to your root component; @IMPORTANT: This script uses the MeshCombineUtility Script provided by Unity

PS: It was tested with FBX files exported from 3D MAX The Vertex Number and Bone Number must be configured according to your own AVATAR... You can make a Counter to get the Number of Vertices from your imported character, I choice not do it since this is script is just executed one time... */

using UnityEngine; using System.Collections;

public class CombineSkinnedMeshes : MonoBehaviour {

/// Usually rendering with triangle strips is faster. /// However when combining objects with very low triangle counts, it can be faster to use triangles. /// Best is to try out which value is faster in practice. public bool generateTriangleStrips = true; public bool castShadows = true; public bool receiveShadows = true;

/* This is for very particular use, you must set it regarding to your Character */ public static int VERTEX_NUMBER= YourVerices; //The number of vertices total of the character public static int BONE_NUMBER = YourBones; //The number of bones total

// Use this for initialization void Start () {

//Getting all Skinned Renderer from Children Component[] allsmr = GetComponentsInChildren(typeof(SkinnedMeshRenderer)); Matrix4x4 myTransform = transform.worldToLocalMatrix; Hashtable materialToMesh= new Hashtable();

//The hash with the all bones references: it will be used for set up the BoneWeight Indexes Hashtable boneHash =new Hashtable();

/* If you want make a counter in order to get the total of Vertices and Bones do it here ... */

//The Sum of All Child Bones Transform[] totalBones = new Transform[BONE_NUMBER];//Total of Bones for my Example //The Sum of the BindPoses Matrix4x4[] totalBindPoses = new Matrix4x4[BONE_NUMBER];//Total of BindPoses //The Sum of BoneWeights BoneWeight[] totalBoneWeight = new BoneWeight[VERTEX_NUMBER];//total of Vertices for my Example

int offset=0; int b_offset=0; Transform[] usedBones= new Transform[totalBones.Length];

for(int i=0;i<allsmr.Length;i++) { //Getting one by one SkinnedMeshRenderer smrenderer = (SkinnedMeshRenderer)allsmr[i]; //Making changes to the Skinned Renderer MeshCombineUtility.MeshInstance instance = new MeshCombineUtility.MeshInstance ();

//Setting the Mesh for the instance instance.mesh = smrenderer.sharedMesh;

if (smrenderer != null && smrenderer.enabled && instance.mesh != null) {

instance.transform = myTransform * smrenderer.transform.localToWorldMatrix;

//Setting Materials Material[] materials = smrenderer.sharedMaterials;

for (int m=0;m<materials.Length;m++) { //Getting Minimum of SubMesh instance.subMeshIndex = System.Math.Min(m, instance.mesh.subMeshCount - 1);

//Setting Meshed Instances ArrayList objects = (ArrayList)materialToMesh[materials[m]]; if (objects != null) { objects.Add(instance); } else { objects = new ArrayList (); objects.Add(instance); materialToMesh.Add(materials[m], objects); } }

//Copying Bones for(int x=0;x<smrenderer.bones.Length;x++) {

bool flag = false; for(int j=0;j<totalBones.Length;j++) { if(usedBones[j]!=null) //If the bone was already inserted if((smrenderer.bones[x]==usedBones[j])) { flag = true; break; } }

//If Bone is New ... if(!flag) { //Debug.Log("Inserted bone:"+smrenderer.bones[x].name); for(int f=0;f<totalBones.Length;f++) { //Insert bone at the firs free position if(usedBones[f]==null) { usedBones[f] = smrenderer.bones[x]; break; } } //inserting bones in totalBones totalBones[offset]=smrenderer.bones[x]; //Reference HashTable boneHash.Add(smrenderer.bones[x].name,offset);

//Recalculating BindPoses //totalBindPoses[offset] = smrenderer.sharedMesh.bindposes[x] ; totalBindPoses[offset] = smrenderer.bones[x].worldToLocalMatrix * transform.localToWorldMatrix ; offset++;



//RecalculateBoneWeights for(int x=0;x<smrenderer.sharedMesh.boneWeights.Length ;x++) { //Just Copying and changing the Bones Indexes !! totalBoneWeight[b_offset] = recalculateIndexes(smrenderer.sharedMesh.boneWeights[x],boneHash,smrenderer.bones); b_offset++; } //Disabling current SkinnedMeshRenderer ((SkinnedMeshRenderer)allsmr[i]).enabled = false;


} foreach (DictionaryEntry de in materialToMesh) { ArrayList elements = (ArrayList)de.Value; MeshCombineUtility.MeshInstance[] instances = (MeshCombineUtility.MeshInstance[])elements.ToArray(typeof(MeshCombineUtility.MeshInstance));

// We have a maximum of one material, so just attach the mesh to our own game object if (materialToMesh.Count == 1) {

// Make sure we have a SkinnedMeshRenderer if (GetComponent(typeof(SkinnedMeshRenderer)) == null) { gameObject.AddComponent(typeof(SkinnedMeshRenderer)); }

//Setting Skinned Renderer SkinnedMeshRenderer objRenderer = (SkinnedMeshRenderer)GetComponent(typeof(SkinnedMeshRenderer)); objRenderer.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips); objRenderer.material = (Material)de.Key;

objRenderer.castShadows = castShadows; objRenderer.receiveShadows = receiveShadows;

//Setting Bindposes objRenderer.sharedMesh.bindposes = totalBindPoses;

//Setting BoneWeights objRenderer.sharedMesh.boneWeights = totalBoneWeight;

//Setting bones objRenderer.bones =totalBones;

objRenderer.sharedMesh.RecalculateNormals(); objRenderer.sharedMesh.RecalculateBounds(); //Enable Mesh objRenderer.enabled = true;

/* Debug.Log("############################################"); Debug.Log("bindPoses "+objRenderer.sharedMesh.bindposes.Length); Debug.Log("boneWeights "+objRenderer.sharedMesh.boneWeights.Length); Debug.Log("Bones "+objRenderer.bones.Length); Debug.Log("Vertices "+objRenderer.sharedMesh.vertices.Length); */ } // We have multiple materials to take care of, build one mesh / gameobject for each material // and parent it to this object

                       //FOR NOW JUST WORKS WITH ONE MATERIAL !!!

else { GameObject go = new GameObject("CombinedSkinnedMesh"); go.transform.parent = transform; go.transform.localScale =; go.transform.localRotation = Quaternion.identity; go.transform.localPosition =; go.AddComponent(typeof(SkinnedMeshRenderer)); ((SkinnedMeshRenderer)go.GetComponent(typeof(SkinnedMeshRenderer))).material = (Material)de.Key;

SkinnedMeshRenderer objRenderer = (SkinnedMeshRenderer)go.GetComponent(typeof(SkinnedMeshRenderer)); objRenderer.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);

//Setting Bindposes objRenderer.sharedMesh.bindposes = totalBindPoses;

//Setting BoneWeights objRenderer.sharedMesh.boneWeights = totalBoneWeight;

//Setting bones objRenderer.bones =totalBones;

objRenderer.sharedMesh.RecalculateNormals(); objRenderer.sharedMesh.RecalculateBounds();

//Enable Mesh objRenderer.enabled = true;

} */ } }

/* @Description: Revert the order of an array of components (NOT USED) */ static Component[] revertComponent(Component[] comp ) { Component[] result = new Component[comp.Length]; int x=0; for(int i=comp.Length-1;i>=0;i--) { result[x++]=comp[i]; }

return result; }

/* @Description: Setting the Indexes for the new bones */ static BoneWeight recalculateIndexes(BoneWeight bw,Hashtable boneHash,Transform[] meshBones ) { BoneWeight retBw = bw; retBw.boneIndex0 = (int)boneHash[meshBones[bw.boneIndex0].name]; retBw.boneIndex1 = (int)boneHash[meshBones[bw.boneIndex1].name]; retBw.boneIndex2 = (int)boneHash[meshBones[bw.boneIndex2].name]; retBw.boneIndex3 = (int)boneHash[meshBones[bw.boneIndex3].name]; return retBw; } } </csharp>

Personal tools