Hi All
I’m new to Unity3D and happened to use the “improved meshcombineutility” that superpig have posted on 2011, may I check if there’s any update to the code other than the changing of Get/SetTriangleStripes to Get/SetTriangles that have been highlighted from 2015?
I have changed that in my current project and am facing the error of
“Failed setting triangles. The number of supplied triangle indices must be a multiple of 3. UnityEngine.Mesh:SetTriangles (int[ ],int)”
This points to the following places:
“MeshCombineUtility:CreateCombinedMesh () (At line 187 of MeshCombineUtility)
MeshCombineUtility:Combine (System.Collections.Generic.IEnumerable`1<MeshCombineUtility/MeshInstance>,bool) (At line 208 of MeshCombineUtility)
CombineChildren.Combine () (Line 92 CombineChildren)
CombineChildren:Start () (Line 23 CombineChildren)”
As well as
“Non-convex MeshCollider with non-kinematic RigidBody is no longer supported since Unity 5. If you want to use a non-convex mesh either make the Rigidbody kinematic or remove the Rigidbody component.”
Thanks in advance!
MeshCombineUtility.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class MeshCombineUtility
{
private readonly List<Color> _colors = new List<Color>();
private readonly bool _generateStrips;
private readonly List<Vector3> _normals = new List<Vector3>();
private readonly Dictionary<int, List<int>> _strip = new Dictionary<int, List<int>>();
private readonly List<Vector4> _tangents = new List<Vector4>();
private readonly Dictionary<int, List<int>> _triangles = new Dictionary<int, List<int>>();
private readonly List<Vector2> _uv = new List<Vector2>();
private readonly List<Vector2> _uv1 = new List<Vector2>();
private readonly List<Vector3> _vertices = new List<Vector3>();
/// <summary>
/// Creates a new, empty MeshCombineUtility object for combining meshes.
/// </summary>
/// <param name="generateStrips">true if the meshes you're going to combine are all triangle-strip based; false if you just want to use triangle lists.</param>
public MeshCombineUtility(bool generateStrips)
{
_generateStrips = generateStrips;
}
/// <summary>
/// Allocate space for adding a load more vertex data.
/// </summary>
/// <param name="numVertices">The number of vertices you're about to add.</param>
public void PrepareForAddingVertices(int numVertices)
{
int shortfall = numVertices - (_vertices.Capacity - _vertices.Count);
_vertices.Capacity += shortfall;
_normals.Capacity += shortfall;
_tangents.Capacity += shortfall;
_uv.Capacity += shortfall;
_uv1.Capacity += shortfall;
_colors.Capacity += shortfall;
}
/// <summary>
/// Allocate space for adding a load more triangles to the triangle list.
/// </summary>
/// <param name="targetSubmeshIndex">The index of the submesh that you're going to add triangles to.</param>
/// <param name="numIndices">The number of triangle indicies (number of triangles * 3) that you want to reserve space for.</param>
public void PrepareForAddingTriangles(int targetSubmeshIndex, int numIndices)
{
if (!_triangles.ContainsKey(targetSubmeshIndex))
_triangles.Add(targetSubmeshIndex, new List<int>());
int shortfall = numIndices - (_triangles[targetSubmeshIndex].Capacity - _triangles[targetSubmeshIndex].Count);
_triangles[targetSubmeshIndex].Capacity += shortfall;
}
/// <summary>
/// Allocate space for adding a load more triangle strips to the combiner.
/// </summary>
/// <param name="targetSubmeshIndex">The index of the submesh you're going to add strips to.</param>
/// <param name="stripLengths">A sequence of strip lengths (in number-of-indices). These numbers will be used to automatically calculate how many bridging indices need to be added.</param>
public void PrepareForAddingStrips(int targetSubmeshIndex, IEnumerable<int> stripLengths)
{
if (!_strip.ContainsKey(targetSubmeshIndex))
_strip.Add(targetSubmeshIndex, new List<int>());
int requiredCapacity = _strip[targetSubmeshIndex].Count;
foreach (int srcStripLength in stripLengths)
{
int adjStripLength = srcStripLength;
if (requiredCapacity > 0)
if ((requiredCapacity & 1) == 1)
adjStripLength += 3;
else
adjStripLength += 2;
requiredCapacity += adjStripLength;
}
if (_strip[targetSubmeshIndex].Capacity < requiredCapacity)
_strip[targetSubmeshIndex].Capacity = requiredCapacity;
}
/// <summary>
/// Add a mesh instance to the combiner.
/// </summary>
/// <param name="instance">The mesh instance to add.</param>
public void AddMeshInstance(MeshInstance instance)
{
int baseVertexIndex = _vertices.Count;
PrepareForAddingVertices(instance.mesh.vertexCount);
_vertices.AddRange(instance.mesh.vertices.Select(v => instance.transform.MultiplyPoint(v)));
_normals.AddRange(
instance.mesh.normals.Select(n => instance.transform.inverse.transpose.MultiplyVector(n).normalized));
_tangents.AddRange(instance.mesh.tangents.Select(t =>
{
var p = new Vector3(t.x, t.y, t.z);
p =
instance.transform.inverse.transpose.
MultiplyVector(p).normalized;
return new Vector4(p.x, p.y, p.z, t.w);
}));
_uv.AddRange(instance.mesh.uv);
_uv1.AddRange(instance.mesh.uv2);
_colors.AddRange(instance.mesh.colors);
if (_generateStrips)
{
int[] inputstrip = instance.mesh.GetTriangles(instance.subMeshIndex);
PrepareForAddingStrips(instance.targetSubMeshIndex, new[] {inputstrip.Length});
List<int> outputstrip = _strip[instance.targetSubMeshIndex];
if (outputstrip.Count != 0)
{
if ((outputstrip.Count & 1) == 1)
{
outputstrip.Add(outputstrip[outputstrip.Count - 1]);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
}
else
{
outputstrip.Add(outputstrip[outputstrip.Count - 1]);
outputstrip.Add(inputstrip[0] + baseVertexIndex);
}
}
outputstrip.AddRange(inputstrip.Select(s => s + baseVertexIndex));
}
else
{
int[] inputtriangles = instance.mesh.GetTriangles(instance.subMeshIndex);
PrepareForAddingTriangles(instance.targetSubMeshIndex, inputtriangles.Length);
_triangles[instance.targetSubMeshIndex].AddRange(inputtriangles.Select(t => t + baseVertexIndex));
}
}
/// <summary>
/// Add multiple mesh instances to the combiner, allocating space for them all up-front.
/// </summary>
/// <param name="instances">The instances to add.</param>
public void AddMeshInstances(IEnumerable<MeshInstance> instances)
{
instances = instances.Where(instance => instance.mesh);
PrepareForAddingVertices(instances.Sum(instance => instance.mesh.vertexCount));
foreach (var targetSubmesh in instances.GroupBy(instance => instance.targetSubMeshIndex))
{
if (_generateStrips)
{
PrepareForAddingStrips(targetSubmesh.Key,
targetSubmesh.Select(instance => instance.mesh.GetTriangles(instance.subMeshIndex).Length));
}
else
{
PrepareForAddingTriangles(targetSubmesh.Key,
targetSubmesh.Sum(instance => instance.mesh.GetTriangles(instance.subMeshIndex).Length));
}
}
foreach (MeshInstance instance in instances)
AddMeshInstance(instance);
}
/// <summary>
/// Generate a single mesh from the instances that have been added to the combiner so far.
/// </summary>
/// <returns>A combined mesh.</returns>
public Mesh CreateCombinedMesh()
{
var mesh = new Mesh
{
name = "Combined Mesh",
vertices = _vertices.ToArray(),
normals = _normals.ToArray(),
colors = _colors.ToArray(),
uv = _uv.ToArray(),
uv2 = _uv1.ToArray(),
tangents = _tangents.ToArray(),
subMeshCount = (_generateStrips) ? _strip.Count : _triangles.Count
};
if (_generateStrips)
{
foreach (var targetSubmesh in _strip)
mesh.SetTriangles(targetSubmesh.Value.ToArray(), targetSubmesh.Key); /* */
}
else
{
foreach (var targetSubmesh in _triangles)
mesh.SetTriangles(targetSubmesh.Value.ToArray(), targetSubmesh.Key);
}
return mesh;
}
/// <summary>
/// Combine the given mesh instances into a single mesh and return it.
/// </summary>
/// <param name="instances">The mesh instances to combine.</param>
/// <param name="generateStrips">true to use triangle strips, false to use triangle lists.</param>
/// <returns>A combined mesh.</returns>
public static Mesh Combine(IEnumerable<MeshInstance> instances, bool generateStrips)
{
var processor = new MeshCombineUtility(generateStrips);
processor.AddMeshInstances(instances);
return processor.CreateCombinedMesh(); /* */
}
#region Nested type: MeshInstance
public class MeshInstance
{
/// <summary>
/// The source mesh.
/// </summary>
public Mesh mesh;
/// <summary>
/// The submesh from the source mesh that you want to add to the combiner.
/// </summary>
public int subMeshIndex;
/// <summary>
/// The submesh that you want this instance to be combined into. Group instances that should
/// share a material into the same target submesh index.
/// </summary>
public int targetSubMeshIndex;
/// <summary>
/// The instance transform.
/// </summary>
public Matrix4x4 transform;
}
#endregion
}
CombineChildren.cs
using UnityEngine;
using System.Collections;
/*
Attach this script as a parent to some game objects. The script will then combine the meshes at startup.
This is useful as a performance optimization since it is faster to render one big mesh than many small meshes. See the docs on graphics performance optimization for more info.
Different materials will cause multiple meshes to be created, thus it is useful to share as many textures/material as you can.
*/
//[ExecuteInEditMode()]
[AddComponentMenu("Mesh/Combine Children")]
public class CombineChildren : 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 int frameToWait = 0;
public bool generateTriangleStrips = true, combineOnStart = true, destroyAfterOptimized = false, castShadow = true, receiveShadow = true, keepLayer = true, addMeshCollider = false;
void Start()
{
if (combineOnStart && frameToWait == 0) Combine();
else StartCoroutine(CombineLate());
}
IEnumerator CombineLate()
{
for (int i = 0; i < frameToWait; i++ ) yield return 0;
Combine();
}
[ContextMenu("Combine Now on Childs")]
public void CallCombineOnAllChilds()
{
CombineChildren[] c = gameObject.GetComponentsInChildren<CombineChildren>();
int count = c.Length;
for (int i = 0; i < count; i++) if(c[i] != this)c[i].Combine();
combineOnStart = enabled = false;
}
/// This option has a far longer preprocessing time at startup but leads to better runtime performance.
[ContextMenu ("Combine Now")]
public void Combine () {
Component[] filters = GetComponentsInChildren(typeof(MeshFilter));
Matrix4x4 myTransform = transform.worldToLocalMatrix;
Hashtable materialToMesh= new Hashtable();
for (int i=0;i<filters.Length;i++) {
MeshFilter filter = (MeshFilter)filters[i];
Renderer curRenderer = filters[i].GetComponent<Renderer>();
MeshCombineUtility.MeshInstance instance = new MeshCombineUtility.MeshInstance ();
instance.mesh = filter.sharedMesh;
if (curRenderer != null && curRenderer.enabled && instance.mesh != null) {
instance.transform = myTransform * filter.transform.localToWorldMatrix;
Material[] materials = curRenderer.sharedMaterials;
for (int m=0;m<materials.Length;m++) {
instance.subMeshIndex = System.Math.Min(m, instance.mesh.subMeshCount - 1);
ArrayList objects = (ArrayList)materialToMesh[materials[m]];
if (objects != null) {
objects.Add(instance);
}
else
{
objects = new ArrayList ();
objects.Add(instance);
materialToMesh.Add(materials[m], objects);
}
}
if (Application.isPlaying && destroyAfterOptimized && combineOnStart) Destroy(curRenderer.gameObject);
else if (destroyAfterOptimized) DestroyImmediate(curRenderer.gameObject);
else curRenderer.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 mesh filter & renderer
if (GetComponent(typeof(MeshFilter)) == null)
gameObject.AddComponent(typeof(MeshFilter));
if (!GetComponent("MeshRenderer"))
gameObject.AddComponent<MeshRenderer>();
MeshFilter filter = (MeshFilter)GetComponent(typeof(MeshFilter));
if (Application.isPlaying) filter.mesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
else filter.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
GetComponent<Renderer>().material = (Material)de.Key;
GetComponent<Renderer>().enabled = true;
if (addMeshCollider) gameObject.AddComponent<MeshCollider>();
GetComponent<Renderer>().castShadows = castShadow;
GetComponent<Renderer>().receiveShadows = receiveShadow;
}
// 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("Combined mesh");
if (keepLayer) go.layer = gameObject.layer;
go.transform.parent = transform;
go.transform.localScale = Vector3.one;
go.transform.localRotation = Quaternion.identity;
go.transform.localPosition = Vector3.zero;
go.AddComponent(typeof(MeshFilter));
go.AddComponent<MeshRenderer>();
go.GetComponent<Renderer>().material = (Material)de.Key;
MeshFilter filter = (MeshFilter)go.GetComponent(typeof(MeshFilter));
if(Application.isPlaying)filter.mesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
else filter.sharedMesh = MeshCombineUtility.Combine(instances, generateTriangleStrips);
go.GetComponent<Renderer>().castShadows = castShadow;
go.GetComponent<Renderer>().receiveShadows = receiveShadow;
if (addMeshCollider) go.AddComponent<MeshCollider>();
}
}
}
}