CombineSkinnedMeshes

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Description)
(Description)
Line 3: Line 3:
  
 
Class Model that allows you combine multiple skinned meshes at runtime, useful for Avatar System.
 
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 in according to your own model.
+
IMPORTANT: The number of vertices and bones is very particular, you must set it up according to your own model.
 +
 
 +
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
 +
else
 +
{
 +
GameObject go = new GameObject("CombinedSkinnedMesh");
 +
go.transform.parent = transform;
 +
go.transform.localScale = Vector3.one;
 +
go.transform.localRotation = Quaternion.identity;
 +
go.transform.localPosition = Vector3.zero;
 +
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>

Revision as of 14:56, 28 April 2009

Description

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.

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 else { GameObject go = new GameObject("CombinedSkinnedMesh"); go.transform.parent = transform; go.transform.localScale = Vector3.one; go.transform.localRotation = Quaternion.identity; go.transform.localPosition = Vector3.zero; 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
Namespaces

Variants
Actions
Navigation
Extras
Toolbox