1864 lines
63 KiB
C#
1864 lines
63 KiB
C#
//////////////////////////////////////////////////////
|
|
// Copyright (c) BrainFailProductions
|
|
//////////////////////////////////////////////////////
|
|
|
|
|
|
#if UNITY_2018_2_OR_NEWER
|
|
#define UNITY_8UV_SUPPORT
|
|
#endif
|
|
|
|
#if UNITY_2017_3_OR_NEWER
|
|
#define UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
#endif
|
|
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
using UnityEngine.Rendering;
|
|
#endif
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace BrainFailProductions.PolyFewRuntime
|
|
{
|
|
|
|
public static class MeshCombiner
|
|
{
|
|
|
|
#region DATA_STRUCTURES
|
|
|
|
private static MeshRenderer[] unityCombinedMeshRenderers = null;
|
|
private static Material[] unityCombinedMeshesMats = null;
|
|
private static bool didUseUnityCombine = false;
|
|
public static bool generateUV2 = false;
|
|
|
|
public struct StaticRenderer
|
|
{
|
|
public string name;
|
|
public bool isNewMesh;
|
|
public Transform transform;
|
|
public Mesh mesh;
|
|
public Material[] materials;
|
|
}
|
|
|
|
public struct SkinnedRenderer
|
|
{
|
|
public bool hasBlendShapes;
|
|
public string name;
|
|
public bool isNewMesh;
|
|
public Transform transform;
|
|
public Mesh mesh;
|
|
public Material[] materials;
|
|
public Transform rootBone;
|
|
public Transform[] bones;
|
|
}
|
|
|
|
[Serializable]
|
|
public struct BlendShape
|
|
{
|
|
/// <summary>
|
|
/// The name of the blend shape.
|
|
/// </summary>
|
|
public string ShapeName;
|
|
/// <summary>
|
|
/// The blend shape frames.
|
|
/// </summary>
|
|
public BlendShapeFrame[] Frames;
|
|
|
|
/// <summary>
|
|
/// Creates a new blend shape.
|
|
/// </summary>
|
|
/// <param name="shapeName">The name of the blend shape.</param>
|
|
/// <param name="frames">The blend shape frames.</param>
|
|
public BlendShape(string shapeName, BlendShapeFrame[] frames)
|
|
{
|
|
this.ShapeName = shapeName;
|
|
this.Frames = frames;
|
|
}
|
|
}
|
|
|
|
|
|
[Serializable]
|
|
public struct BlendShapeFrame
|
|
{
|
|
/// <summary>
|
|
/// The name of the blend shape this frame is associated with.
|
|
/// </summary>
|
|
public string shapeName;
|
|
/// <summary>
|
|
/// The weight of the blend shape frame.
|
|
/// </summary>
|
|
public float frameWeight;
|
|
/// <summary>
|
|
/// The delta vertices of the blend shape frame.
|
|
/// </summary>
|
|
public Vector3[] deltaVertices;
|
|
/// <summary>
|
|
/// The delta normals of the blend shape frame.
|
|
/// </summary>
|
|
public Vector3[] deltaNormals;
|
|
/// <summary>
|
|
/// The delta tangents of the blend shape frame.
|
|
/// </summary>
|
|
public Vector3[] deltaTangents;
|
|
/// <summary>
|
|
/// The vertex offset to be used in the combined mesh vertex array.
|
|
/// </summary>
|
|
public int vertexOffset;
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a new blend shape frame.
|
|
/// </summary>
|
|
/// <param name="frameWeight">The weight of the blend shape frame.</param>
|
|
/// <param name="deltaVertices">The delta vertices of the blend shape frame.</param>
|
|
/// <param name="deltaNormals">The delta normals of the blend shape frame.</param>
|
|
/// <param name="deltaTangents">The delta tangents of the blend shape frame.</param>
|
|
public BlendShapeFrame(float frameWeight, Vector3[] deltaVertices, Vector3[] deltaNormals, Vector3[] deltaTangents)
|
|
{
|
|
this.frameWeight = frameWeight;
|
|
this.deltaVertices = deltaVertices;
|
|
this.deltaNormals = deltaNormals;
|
|
this.deltaTangents = deltaTangents;
|
|
this.shapeName = "";
|
|
this.vertexOffset = -1;
|
|
}
|
|
|
|
|
|
public BlendShapeFrame(string shapeName, float frameWeight, Vector3[] deltaVertices, Vector3[] deltaNormals, Vector3[] deltaTangents, int vertexOffset)
|
|
{
|
|
this.shapeName = shapeName;
|
|
this.frameWeight = frameWeight;
|
|
this.deltaVertices = deltaVertices;
|
|
this.deltaNormals = deltaNormals;
|
|
this.deltaTangents = deltaTangents;
|
|
this.vertexOffset = vertexOffset;
|
|
}
|
|
}
|
|
|
|
|
|
public static class MeshUtils
|
|
{
|
|
#region Consts
|
|
/// <summary>
|
|
/// The count of supported UV channels.
|
|
/// </summary>
|
|
#if UNITY_8UV_SUPPORT
|
|
public const int UVChannelCount = 8;
|
|
#else
|
|
public const int UVChannelCount = 4;
|
|
#endif
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// Creates a new mesh.
|
|
/// </summary>
|
|
/// <param name="vertices">The mesh vertices.</param>
|
|
/// <param name="indices">The mesh sub-mesh indices.</param>
|
|
/// <param name="normals">The mesh normals.</param>
|
|
/// <param name="tangents">The mesh tangents.</param>
|
|
/// <param name="colors">The mesh colors.</param>
|
|
/// <param name="boneWeights">The mesh bone-weights.</param>
|
|
/// <param name="uvs">The mesh 4D UV sets.</param>
|
|
/// <param name="bindposes">The mesh bindposes.</param>
|
|
/// <returns>The created mesh.</returns>
|
|
public static Mesh CreateMesh(Vector3[] vertices, int[][] indices, Vector3[] normals, Vector4[] tangents, Color[] colors, BoneWeight[] boneWeights, List<Vector2>[] uvs, Matrix4x4[] bindposes, BlendShape[] blendShapes)
|
|
{
|
|
return CreateMesh(vertices, indices, normals, tangents, colors, boneWeights, uvs, null, null, bindposes, blendShapes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new mesh.
|
|
/// </summary>
|
|
/// <param name="vertices">The mesh vertices.</param>
|
|
/// <param name="indices">The mesh sub-mesh indices.</param>
|
|
/// <param name="normals">The mesh normals.</param>
|
|
/// <param name="tangents">The mesh tangents.</param>
|
|
/// <param name="colors">The mesh colors.</param>
|
|
/// <param name="boneWeights">The mesh bone-weights.</param>
|
|
/// <param name="uvs">The mesh 4D UV sets.</param>
|
|
/// <param name="bindposes">The mesh bindposes.</param>
|
|
/// <returns>The created mesh.</returns>
|
|
public static Mesh CreateMesh(Vector3[] vertices, int[][] indices, Vector3[] normals, Vector4[] tangents, Color[] colors, BoneWeight[] boneWeights, List<Vector4>[] uvs, Matrix4x4[] bindposes, BlendShape[] blendShapes)
|
|
{
|
|
return CreateMesh(vertices, indices, normals, tangents, colors, boneWeights, null, null, uvs, bindposes, blendShapes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new mesh.
|
|
/// </summary>
|
|
/// <param name="vertices">The mesh vertices.</param>
|
|
/// <param name="indices">The mesh sub-mesh indices.</param>
|
|
/// <param name="normals">The mesh normals.</param>
|
|
/// <param name="tangents">The mesh tangents.</param>
|
|
/// <param name="colors">The mesh colors.</param>
|
|
/// <param name="boneWeights">The mesh bone-weights.</param>
|
|
/// <param name="uvs2D">The mesh 2D UV sets.</param>
|
|
/// <param name="uvs3D">The mesh 3D UV sets.</param>
|
|
/// <param name="uvs4D">The mesh 4D UV sets.</param>
|
|
/// <param name="bindposes">The mesh bindposes.</param>
|
|
/// <returns>The created mesh.</returns>
|
|
public static Mesh CreateMesh(Vector3[] vertices, int[][] indices, Vector3[] normals, Vector4[] tangents, Color[] colors, BoneWeight[] boneWeights, List<Vector2>[] uvs2D, List<Vector3>[] uvs3D, List<Vector4>[] uvs4D, Matrix4x4[] bindposes, BlendShape[] blendShapes)
|
|
{
|
|
var newMesh = new Mesh();
|
|
int subMeshCount = indices.Length;
|
|
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
IndexFormat indexFormat;
|
|
var indexMinMax = MeshUtils.GetSubMeshIndexMinMax(indices, out indexFormat);
|
|
newMesh.indexFormat = indexFormat;
|
|
#endif
|
|
|
|
if (bindposes != null && bindposes.Length > 0)
|
|
{
|
|
newMesh.bindposes = bindposes;
|
|
}
|
|
|
|
newMesh.subMeshCount = subMeshCount;
|
|
newMesh.vertices = vertices;
|
|
|
|
// If after assigning normals blendshapes are assigned, then blendshapes do not work correctly
|
|
// In URP and HDRP configurations, so we add blendshapes first and then assign normals
|
|
if (blendShapes != null)
|
|
{
|
|
MeshUtils.ApplyMeshBlendShapes(newMesh, blendShapes);
|
|
}
|
|
|
|
if (normals != null && normals.Length > 0)
|
|
{
|
|
newMesh.normals = normals;
|
|
}
|
|
if (tangents != null && tangents.Length > 0)
|
|
{
|
|
newMesh.tangents = tangents;
|
|
}
|
|
if (colors != null && colors.Length > 0)
|
|
{
|
|
newMesh.colors = colors;
|
|
}
|
|
if (boneWeights != null && boneWeights.Length > 0)
|
|
{
|
|
newMesh.boneWeights = boneWeights;
|
|
}
|
|
|
|
if (uvs2D != null)
|
|
{
|
|
for (int uvChannel = 0; uvChannel < uvs2D.Length; uvChannel++)
|
|
{
|
|
if (uvs2D[uvChannel] != null && uvs2D[uvChannel].Count > 0)
|
|
{
|
|
newMesh.SetUVs(uvChannel, uvs2D[uvChannel]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uvs3D != null)
|
|
{
|
|
for (int uvChannel = 0; uvChannel < uvs3D.Length; uvChannel++)
|
|
{
|
|
if (uvs3D[uvChannel] != null && uvs3D[uvChannel].Count > 0)
|
|
{
|
|
newMesh.SetUVs(uvChannel, uvs3D[uvChannel]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uvs4D != null)
|
|
{
|
|
for (int uvChannel = 0; uvChannel < uvs4D.Length; uvChannel++)
|
|
{
|
|
if (uvs4D[uvChannel] != null && uvs4D[uvChannel].Count > 0)
|
|
{
|
|
newMesh.SetUVs(uvChannel, uvs4D[uvChannel]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//if (blendShapes != null)
|
|
//{
|
|
// MeshUtils.ApplyMeshBlendShapes(newMesh, blendShapes); //baw did
|
|
//}
|
|
|
|
|
|
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
|
|
{
|
|
var subMeshTriangles = indices[subMeshIndex];
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
var minMax = indexMinMax[subMeshIndex];
|
|
if (indexFormat == UnityEngine.Rendering.IndexFormat.UInt16 && minMax.y > ushort.MaxValue)
|
|
{
|
|
int baseVertex = minMax.x;
|
|
for (int index = 0; index < subMeshTriangles.Length; index++)
|
|
{
|
|
subMeshTriangles[index] -= baseVertex;
|
|
}
|
|
newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, baseVertex);
|
|
}
|
|
else
|
|
{
|
|
newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, 0);
|
|
}
|
|
#else
|
|
newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false);
|
|
#endif
|
|
}
|
|
|
|
newMesh.RecalculateBounds();
|
|
return newMesh;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the blend shapes of a mesh.
|
|
/// </summary>
|
|
/// <param name="mesh">The mesh.</param>
|
|
/// <returns>The mesh blend shapes.</returns>
|
|
public static BlendShape[] GetMeshBlendShapes(Mesh mesh)
|
|
{
|
|
if (mesh == null)
|
|
throw new ArgumentNullException(nameof(mesh));
|
|
|
|
int vertexCount = mesh.vertexCount;
|
|
int blendShapeCount = mesh.blendShapeCount;
|
|
if (blendShapeCount == 0)
|
|
return null;
|
|
|
|
var blendShapes = new BlendShape[blendShapeCount];
|
|
|
|
for (int blendShapeIndex = 0; blendShapeIndex < blendShapeCount; blendShapeIndex++)
|
|
{
|
|
string shapeName = mesh.GetBlendShapeName(blendShapeIndex);
|
|
int frameCount = mesh.GetBlendShapeFrameCount(blendShapeIndex);
|
|
var frames = new BlendShapeFrame[frameCount];
|
|
|
|
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
|
{
|
|
float frameWeight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex);
|
|
|
|
var deltaVertices = new Vector3[vertexCount];
|
|
var deltaNormals = new Vector3[vertexCount];
|
|
var deltaTangents = new Vector3[vertexCount];
|
|
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);
|
|
|
|
frames[frameIndex] = new BlendShapeFrame(frameWeight, deltaVertices, deltaNormals, deltaTangents);
|
|
}
|
|
|
|
blendShapes[blendShapeIndex] = new BlendShape(shapeName, frames);
|
|
}
|
|
|
|
return blendShapes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies and overrides the specified blend shapes on the specified mesh.
|
|
/// </summary>
|
|
/// <param name="mesh">The mesh.</param>
|
|
/// <param name="blendShapes">The mesh blend shapes.</param>
|
|
public static void ApplyMeshBlendShapes(Mesh mesh, BlendShape[] blendShapes)
|
|
{
|
|
if (mesh == null)
|
|
throw new ArgumentNullException(nameof(mesh));
|
|
|
|
mesh.ClearBlendShapes();
|
|
if (blendShapes == null || blendShapes.Length == 0)
|
|
return;
|
|
|
|
for (int blendShapeIndex = 0; blendShapeIndex < blendShapes.Length; blendShapeIndex++)
|
|
{
|
|
string shapeName = blendShapes[blendShapeIndex].ShapeName;
|
|
var frames = blendShapes[blendShapeIndex].Frames;
|
|
|
|
if (frames != null)
|
|
{
|
|
for (int frameIndex = 0; frameIndex < frames.Length; frameIndex++)
|
|
{
|
|
mesh.AddBlendShapeFrame(shapeName, frames[frameIndex].frameWeight, frames[frameIndex].deltaVertices, frames[frameIndex].deltaNormals, frames[frameIndex].deltaTangents);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the UV sets for a specific mesh.
|
|
/// </summary>
|
|
/// <param name="mesh">The mesh.</param>
|
|
/// <returns>The UV sets.</returns>
|
|
public static List<Vector4>[] GetMeshUVs(Mesh mesh)
|
|
{
|
|
if (mesh == null)
|
|
throw new ArgumentNullException(nameof(mesh));
|
|
|
|
var uvs = new List<Vector4>[UVChannelCount];
|
|
for (int channel = 0; channel < UVChannelCount; channel++)
|
|
{
|
|
uvs[channel] = GetMeshUVs(mesh, channel);
|
|
}
|
|
return uvs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the UV list for a specific mesh and UV channel.
|
|
/// </summary>
|
|
/// <param name="mesh">The mesh.</param>
|
|
/// <param name="channel">The UV channel.</param>
|
|
/// <returns>The UV list.</returns>
|
|
public static List<Vector4> GetMeshUVs(Mesh mesh, int channel)
|
|
{
|
|
if (mesh == null)
|
|
throw new ArgumentNullException(nameof(mesh));
|
|
else if (channel < 0 || channel >= UVChannelCount)
|
|
throw new ArgumentOutOfRangeException(nameof(channel));
|
|
|
|
var uvList = new List<Vector4>(mesh.vertexCount);
|
|
mesh.GetUVs(channel, uvList);
|
|
return uvList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the number of used UV components in a UV set.
|
|
/// </summary>
|
|
/// <param name="uvs">The UV set.</param>
|
|
/// <returns>The number of used UV components.</returns>
|
|
public static int GetUsedUVComponents(List<Vector4> uvs)
|
|
{
|
|
|
|
if (uvs == null || uvs.Count == 0)
|
|
return 0;
|
|
|
|
int usedComponents = 0;
|
|
|
|
foreach (var uv in uvs)
|
|
{
|
|
if (usedComponents < 1 && uv.x != 0f)
|
|
{
|
|
usedComponents = 1;
|
|
}
|
|
if (usedComponents < 2 && uv.y != 0f)
|
|
{
|
|
usedComponents = 2;
|
|
}
|
|
if (usedComponents < 3 && uv.z != 0f)
|
|
{
|
|
usedComponents = 3;
|
|
}
|
|
if (usedComponents < 4 && uv.w != 0f)
|
|
{
|
|
usedComponents = 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return usedComponents;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a list of 4D UVs into 2D.
|
|
/// </summary>
|
|
/// <param name="uvs">The list of UVs.</param>
|
|
/// <returns>The array of 2D UVs.</returns>
|
|
public static Vector2[] ConvertUVsTo2D(List<Vector4> uvs)
|
|
{
|
|
if (uvs == null)
|
|
return null;
|
|
|
|
var uv2D = new Vector2[uvs.Count];
|
|
for (int i = 0; i < uv2D.Length; i++)
|
|
{
|
|
var uv = uvs[i];
|
|
uv2D[i] = new Vector2(uv.x, uv.y);
|
|
}
|
|
return uv2D;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a list of 4D UVs into 3D.
|
|
/// </summary>
|
|
/// <param name="uvs">The list of UVs.</param>
|
|
/// <returns>The array of 3D UVs.</returns>
|
|
public static Vector3[] ConvertUVsTo3D(List<Vector4> uvs)
|
|
{
|
|
if (uvs == null)
|
|
return null;
|
|
|
|
var uv3D = new Vector3[uvs.Count];
|
|
for (int i = 0; i < uv3D.Length; i++)
|
|
{
|
|
var uv = uvs[i];
|
|
uv3D[i] = new Vector3(uv.x, uv.y, uv.z);
|
|
}
|
|
return uv3D;
|
|
}
|
|
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
/// <summary>
|
|
/// Returns the minimum and maximum indices for each submesh along with the needed index format.
|
|
/// </summary>
|
|
/// <param name="indices">The indices for the submeshes.</param>
|
|
/// <param name="indexFormat">The output index format.</param>
|
|
/// <returns>The minimum and maximum indices for each submesh.</returns>
|
|
public static Vector2Int[] GetSubMeshIndexMinMax(int[][] indices, out IndexFormat indexFormat)
|
|
{
|
|
if (indices == null)
|
|
throw new ArgumentNullException(nameof(indices));
|
|
|
|
var result = new Vector2Int[indices.Length];
|
|
indexFormat = IndexFormat.UInt16;
|
|
for (int subMeshIndex = 0; subMeshIndex < indices.Length; subMeshIndex++)
|
|
{
|
|
int minIndex, maxIndex;
|
|
GetIndexMinMax(indices[subMeshIndex], out minIndex, out maxIndex);
|
|
result[subMeshIndex] = new Vector2Int(minIndex, maxIndex);
|
|
|
|
int indexRange = (maxIndex - minIndex);
|
|
if (indexRange > ushort.MaxValue)
|
|
{
|
|
indexFormat = IndexFormat.UInt32;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
private static void GetIndexMinMax(int[] indices, out int minIndex, out int maxIndex)
|
|
{
|
|
if (indices == null || indices.Length == 0)
|
|
{
|
|
minIndex = maxIndex = 0;
|
|
return;
|
|
}
|
|
|
|
minIndex = int.MaxValue;
|
|
maxIndex = int.MinValue;
|
|
|
|
for (int i = 0; i < indices.Length; i++)
|
|
{
|
|
if (indices[i] < minIndex)
|
|
{
|
|
minIndex = indices[i];
|
|
}
|
|
if (indices[i] > maxIndex)
|
|
{
|
|
maxIndex = indices[i];
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
|
|
#endregion DATA_STRUCTURES
|
|
|
|
|
|
|
|
#region PUBLIC_METHODS
|
|
|
|
|
|
public static StaticRenderer[] GetStaticRenderers(MeshRenderer[] renderers)
|
|
{
|
|
var newRenderers = new List<StaticRenderer>(renderers.Length);
|
|
for (int rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++)
|
|
{
|
|
var renderer = renderers[rendererIndex];
|
|
var meshFilter = renderer.GetComponent<MeshFilter>();
|
|
if (meshFilter == null)
|
|
{
|
|
Debug.LogWarning("A renderer was missing a mesh filter and was ignored.", renderer);
|
|
continue;
|
|
}
|
|
|
|
var mesh = meshFilter.sharedMesh;
|
|
if (mesh == null)
|
|
{
|
|
Debug.LogWarning("A renderer was missing a mesh and was ignored.", renderer);
|
|
continue;
|
|
}
|
|
|
|
newRenderers.Add(new StaticRenderer()
|
|
{
|
|
name = renderer.name,
|
|
isNewMesh = false,
|
|
transform = renderer.transform,
|
|
mesh = mesh,
|
|
materials = renderer.sharedMaterials
|
|
});
|
|
}
|
|
return newRenderers.ToArray();
|
|
}
|
|
|
|
|
|
public static SkinnedRenderer[] GetSkinnedRenderers(SkinnedMeshRenderer[] renderers)
|
|
{
|
|
var newRenderers = new List<SkinnedRenderer>(renderers.Length);
|
|
for (int rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++)
|
|
{
|
|
var renderer = renderers[rendererIndex];
|
|
|
|
var mesh = renderer.sharedMesh;
|
|
if (mesh == null)
|
|
{
|
|
Debug.LogWarning("A renderer was missing a mesh and was ignored.", renderer);
|
|
continue;
|
|
}
|
|
|
|
newRenderers.Add(new SkinnedRenderer()
|
|
{
|
|
name = renderer.name,
|
|
isNewMesh = false,
|
|
transform = renderer.transform,
|
|
mesh = mesh,
|
|
materials = renderer.sharedMaterials,
|
|
rootBone = renderer.rootBone,
|
|
bones = renderer.bones
|
|
});
|
|
}
|
|
return newRenderers.ToArray();
|
|
}
|
|
|
|
|
|
public static StaticRenderer[] CombineStaticMeshes(Transform transform, int levelIndex, MeshRenderer[] renderers, bool autoName = true, string combinedBaseName = "")
|
|
{
|
|
if (renderers.Length == 0)
|
|
return null;
|
|
|
|
var newRenderers = new List<StaticRenderer>(renderers.Length);
|
|
|
|
if (renderers.Length > 1)
|
|
{
|
|
var staticMeshes = (from renderer in renderers
|
|
where renderer.GetComponent<MeshFilter>() != null && renderer.GetComponent<MeshFilter>().sharedMesh != null
|
|
select renderer.GetComponent<MeshFilter>()).ToArray();
|
|
|
|
CombineMeshesUnity(transform, staticMeshes);
|
|
didUseUnityCombine = true;
|
|
}
|
|
|
|
Material[] combinedMaterials;
|
|
Mesh combinedMesh;
|
|
|
|
if (unityCombinedMeshRenderers == null)
|
|
{
|
|
combinedMesh = CombineMeshes(transform, renderers, out combinedMaterials);
|
|
}
|
|
else
|
|
{
|
|
if (unityCombinedMeshRenderers.Length == 1)
|
|
{
|
|
combinedMaterials = unityCombinedMeshesMats.ToArray();
|
|
combinedMesh = unityCombinedMeshRenderers[0].GetComponent<MeshFilter>().sharedMesh;
|
|
}
|
|
|
|
else if (unityCombinedMeshRenderers.Length == 0)
|
|
{
|
|
combinedMesh = CombineMeshes(transform, renderers, out combinedMaterials);
|
|
}
|
|
|
|
else
|
|
{
|
|
combinedMesh = CombineMeshes(transform, unityCombinedMeshRenderers, out combinedMaterials);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (unityCombinedMeshRenderers != null)
|
|
{
|
|
foreach (var item in unityCombinedMeshRenderers) { UnityEngine.GameObject.DestroyImmediate(item.gameObject); }
|
|
}
|
|
|
|
unityCombinedMeshRenderers = null;
|
|
unityCombinedMeshesMats = null;
|
|
|
|
string baseName = string.IsNullOrWhiteSpace(combinedBaseName) ? transform.name : combinedBaseName;
|
|
|
|
|
|
string rendererName = string.Format("{0}_combined_static", baseName);
|
|
|
|
if (autoName)
|
|
{
|
|
if (transform != null)
|
|
{
|
|
combinedMesh.name = string.Format("{0}_static{1:00}", transform.name, levelIndex);
|
|
}
|
|
}
|
|
|
|
newRenderers.Add(new StaticRenderer()
|
|
{
|
|
name = rendererName,
|
|
isNewMesh = true,
|
|
transform = null,
|
|
mesh = combinedMesh,
|
|
materials = combinedMaterials
|
|
});
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
//UnityEditor.MeshUtility.Optimize(combinedMesh);
|
|
// Optimizing screws up the combined mesh sometimes by streching it into wierd shapes
|
|
|
|
if (generateUV2)
|
|
{
|
|
UnityEditor.Unwrapping.GenerateSecondaryUVSet(combinedMesh);
|
|
}
|
|
#endif
|
|
|
|
didUseUnityCombine = false;
|
|
|
|
return newRenderers.ToArray();
|
|
}
|
|
|
|
|
|
public static SkinnedRenderer[] CombineSkinnedMeshes(Transform transform, int levelIndex, SkinnedMeshRenderer[] renderers, ref SkinnedMeshRenderer[] renderersActuallyCombined, bool autoName = true, string combinedBaseName = "")
|
|
{
|
|
if (renderers.Length == 0)
|
|
return null;
|
|
|
|
// TODO: Support to merge sub-meshes and atlas textures
|
|
|
|
var newRenderers = new List<SkinnedRenderer>(renderers.Length);
|
|
//var blendShapeRenderers = (from renderer in renderers
|
|
// where renderer.sharedMesh != null && renderer.sharedMesh.blendShapeCount > 0
|
|
// select renderer); //baw did
|
|
|
|
var renderersWithoutMesh = (from renderer in renderers
|
|
where renderer.sharedMesh == null
|
|
select renderer);
|
|
var combineRenderers = (from renderer in renderers
|
|
where renderer.sharedMesh != null // && renderer.sharedMesh.blendShapeCount == 0 baw did
|
|
select renderer).ToArray();
|
|
|
|
|
|
renderersActuallyCombined = combineRenderers;
|
|
|
|
// Warn about renderers without a mesh
|
|
foreach (var renderer in renderersWithoutMesh)
|
|
{
|
|
Debug.LogWarning("A renderer was missing a mesh and was ignored.", renderer);
|
|
}
|
|
|
|
|
|
//Don't combine meshes with blend shapes
|
|
//foreach (var renderer in blendShapeRenderers)
|
|
//{
|
|
// newRenderers.Add(new SkinnedRenderer()
|
|
// {
|
|
// name = renderer.name,
|
|
// isNewMesh = false,
|
|
// transform = renderer.transform,
|
|
// mesh = renderer.sharedMesh,
|
|
// materials = renderer.sharedMaterials,
|
|
// rootBone = renderer.rootBone,
|
|
// bones = renderer.bones,
|
|
// hasBlendShapes = true
|
|
// });
|
|
//}
|
|
|
|
|
|
if (combineRenderers.Length > 0)
|
|
{
|
|
Material[] combinedMaterials;
|
|
Transform[] combinedBones;
|
|
var combinedMesh = CombineMeshes(transform, combineRenderers, out combinedMaterials, out combinedBones);
|
|
string baseName = string.IsNullOrWhiteSpace(combinedBaseName) ? transform.name : combinedBaseName;
|
|
string rendererName = string.Format("{0}_combined_skinned", baseName);
|
|
|
|
if (autoName)
|
|
{
|
|
combinedMesh.name = string.Format("{0}_skinned{1:00}", transform.name, levelIndex);
|
|
}
|
|
|
|
var rootBone = FindBestRootBone(transform, combineRenderers);
|
|
|
|
newRenderers.Add(new SkinnedRenderer()
|
|
{
|
|
name = rendererName,
|
|
isNewMesh = false,
|
|
transform = null,
|
|
mesh = combinedMesh,
|
|
materials = combinedMaterials,
|
|
rootBone = rootBone,
|
|
bones = combinedBones
|
|
});
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
//UnityEditor.MeshUtility.Optimize(combinedMesh);
|
|
// Optimizing screws up the combined mesh sometimes by streching it into wierd shapes
|
|
|
|
if (generateUV2)
|
|
{
|
|
UnityEditor.Unwrapping.GenerateSecondaryUVSet(combinedMesh);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
return newRenderers.ToArray();
|
|
}
|
|
|
|
|
|
public static Mesh CombineMeshes(Transform rootTransform, MeshRenderer[] renderers, out Material[] resultMaterials, Dictionary<Transform, Transform> topLevelParents = null, Dictionary<string, BlendShapeFrame> blendShapes = null)
|
|
{
|
|
bool hasUnknownRootTransform = false;
|
|
|
|
if (rootTransform == null)
|
|
hasUnknownRootTransform = true;
|
|
//throw new System.ArgumentNullException(nameof(rootTransform));
|
|
|
|
if (renderers == null)
|
|
throw new System.ArgumentNullException(nameof(renderers));
|
|
|
|
|
|
|
|
var meshes = new Mesh[renderers.Length];
|
|
|
|
var transforms = new Matrix4x4[renderers.Length];
|
|
|
|
Tuple<Matrix4x4, bool>[] normalsTransforms = new Tuple<Matrix4x4, bool>[renderers.Length];
|
|
var materials = new Material[renderers.Length][];
|
|
|
|
|
|
|
|
for (int i = 0; i < renderers.Length; i++)
|
|
{
|
|
|
|
var renderer = renderers[i];
|
|
|
|
if (renderer == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The renderer at index {0} is null.", i), nameof(renderers));
|
|
|
|
|
|
|
|
var rendererTransform = renderer.transform;
|
|
|
|
var meshFilter = renderer.GetComponent<MeshFilter>();
|
|
|
|
if (meshFilter == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The renderer at index {0} has no mesh filter.", i), nameof(renderers));
|
|
|
|
else if (meshFilter.sharedMesh == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The mesh filter for renderer at index {0} has no mesh.", i), nameof(renderers));
|
|
|
|
else if (!meshFilter.sharedMesh.isReadable)
|
|
|
|
throw new System.ArgumentException(string.Format("The mesh in the mesh filter for renderer at index {0} is not readable.", i), nameof(renderers));
|
|
|
|
|
|
|
|
meshes[i] = meshFilter.sharedMesh;
|
|
|
|
if (hasUnknownRootTransform)
|
|
{
|
|
rootTransform = topLevelParents[rendererTransform];
|
|
}
|
|
|
|
if (didUseUnityCombine) { transforms[i] = rendererTransform.localToWorldMatrix; }
|
|
else { transforms[i] = rootTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix; }
|
|
|
|
Vector3 lossyScale = rendererTransform.transform.lossyScale;
|
|
bool isUniformScale = Mathf.Approximately(lossyScale.x, lossyScale.y) && Mathf.Approximately(lossyScale.y, lossyScale.z);
|
|
|
|
if (!isUniformScale)
|
|
{
|
|
Debug.LogWarning($"The GameObject \"{rendererTransform.name}\" has non uniform scaling applied. This might cause the combined mesh normals to be incorrectly calculated resulting in slight variation in lighting.");
|
|
}
|
|
|
|
normalsTransforms[i] = Tuple.Create(rootTransform.localToWorldMatrix * rendererTransform.localToWorldMatrix, !isUniformScale); //baw did
|
|
materials[i] = renderer.sharedMaterials;
|
|
}
|
|
|
|
|
|
return CombineMeshes(meshes, transforms, normalsTransforms, materials, out resultMaterials, blendShapes);
|
|
|
|
}
|
|
|
|
|
|
public static Mesh CombineMeshes(Transform rootTransform, SkinnedMeshRenderer[] renderers, out Material[] resultMaterials, out Transform[] resultBones)
|
|
{
|
|
|
|
//if (rootTransform == null)
|
|
|
|
//throw new System.ArgumentNullException(nameof(rootTransform));
|
|
|
|
if (renderers == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(renderers));
|
|
|
|
|
|
|
|
var meshes = new Mesh[renderers.Length];
|
|
|
|
var transforms = new Matrix4x4[renderers.Length];
|
|
|
|
Tuple<Matrix4x4, bool>[] normalsTransforms = new Tuple<Matrix4x4, bool>[renderers.Length];
|
|
|
|
var materials = new Material[renderers.Length][];
|
|
|
|
var bones = new Transform[renderers.Length][];
|
|
|
|
Dictionary<string, BlendShapeFrame> blendShapes = new Dictionary<string, BlendShapeFrame>();
|
|
|
|
int vertexOffset = 0;
|
|
|
|
for (int i = 0; i < renderers.Length; i++)
|
|
{
|
|
|
|
var renderer = renderers[i];
|
|
|
|
if (renderer == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The renderer at index {0} is null.", i), nameof(renderers));
|
|
|
|
else if (renderer.sharedMesh == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The renderer at index {0} has no mesh.", i), nameof(renderers));
|
|
|
|
else if (!renderer.sharedMesh.isReadable)
|
|
|
|
throw new System.ArgumentException(string.Format("The mesh in the renderer at index {0} is not readable.", i), nameof(renderers));
|
|
|
|
|
|
|
|
var rendererTransform = renderer.transform;
|
|
|
|
meshes[i] = renderer.sharedMesh;
|
|
|
|
// IF NO BONES
|
|
//transforms[i] = rootTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix;
|
|
//transforms[i] = rendererTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix;
|
|
// IF ANY BONES
|
|
|
|
|
|
Vector3 lossyScale = rendererTransform.transform.lossyScale;
|
|
bool isUniformScale = Mathf.Approximately(lossyScale.x, lossyScale.y) && Mathf.Approximately(lossyScale.y, lossyScale.z);
|
|
|
|
|
|
// baw did
|
|
if (renderer.bones == null || renderer.bones.Length == 0)
|
|
{
|
|
transforms[i] = rootTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix;
|
|
normalsTransforms[i] = Tuple.Create(rootTransform.localToWorldMatrix * rendererTransform.localToWorldMatrix, !isUniformScale); //baw did
|
|
}
|
|
else
|
|
{
|
|
transforms[i] = rendererTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix;
|
|
normalsTransforms[i] = Tuple.Create(rendererTransform.worldToLocalMatrix * rendererTransform.localToWorldMatrix, !isUniformScale);
|
|
}
|
|
|
|
if (!isUniformScale)
|
|
{
|
|
Debug.LogWarning($"The GameObject \"{rendererTransform.name}\" has non uniform scaling applied. This might cause the combined mesh normals to be incorrectly calculated resulting in slight variation in lighting.");
|
|
}
|
|
|
|
|
|
|
|
materials[i] = renderer.sharedMaterials;
|
|
|
|
bones[i] = renderer.bones;
|
|
|
|
for (int a = 0; a < bones[i].Length; a++)
|
|
{
|
|
Transform t = bones[i][a];
|
|
MeshFilter mf = t == null ? null : t.GetComponent<MeshFilter>();
|
|
Mesh m = mf == null ? null : mf.sharedMesh;
|
|
|
|
if (m != null)
|
|
{
|
|
Debug.LogWarning($"You have a static mesh attached to the bone:\"{t.name}\". The mesh combination logic will not deal with this properly, since that would require it to modify the original game object hierarchy. You might get erroneous results on mesh combination.");
|
|
}
|
|
}
|
|
|
|
|
|
Mesh mesh = renderer.sharedMesh;
|
|
int rendererId = renderer.GetHashCode();
|
|
|
|
if (mesh.blendShapeCount > 0)
|
|
{
|
|
for (int s = 0; s < mesh.blendShapeCount; s++)
|
|
{
|
|
for (int f = 0; f < mesh.GetBlendShapeFrameCount(s); f++)
|
|
{
|
|
Vector3[] deltaVertices = new Vector3[mesh.vertexCount];
|
|
Vector3[] deltaNormals = new Vector3[mesh.vertexCount];
|
|
Vector3[] deltaTangents = new Vector3[mesh.vertexCount];
|
|
|
|
if (!blendShapes.ContainsKey(mesh.GetBlendShapeName(s) + rendererId))
|
|
{
|
|
mesh.GetBlendShapeFrameVertices(s, f, deltaVertices, deltaNormals, deltaTangents);
|
|
blendShapes.Add(mesh.GetBlendShapeName(s) + rendererId, new BlendShapeFrame(mesh.GetBlendShapeName(s) + rendererId, mesh.GetBlendShapeFrameWeight(s, f), deltaVertices, deltaNormals, deltaTangents, vertexOffset));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
vertexOffset += mesh.vertexCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CombineMeshes(meshes, transforms, normalsTransforms, materials, bones, out resultMaterials, out resultBones, blendShapes);
|
|
|
|
}
|
|
|
|
|
|
public static Mesh CombineMeshes(Mesh[] meshes, Matrix4x4[] transforms, Tuple<Matrix4x4, bool>[] normalsTransforms, Material[][] materials, out Material[] resultMaterials, Dictionary<string, BlendShapeFrame> blendShapes = null)
|
|
{
|
|
|
|
if (meshes == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(meshes));
|
|
|
|
else if (transforms == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(transforms));
|
|
|
|
else if (materials == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(materials));
|
|
|
|
|
|
|
|
Transform[] resultBones;
|
|
|
|
return CombineMeshes(meshes, transforms, normalsTransforms, materials, null, out resultMaterials, out resultBones, blendShapes);
|
|
|
|
}
|
|
|
|
|
|
public static Mesh CombineMeshes(Mesh[] meshes, Matrix4x4[] transforms, Tuple<Matrix4x4, bool>[] normalsTransforms, Material[][] materials, Transform[][] bones, out Material[] resultMaterials, out Transform[] resultBones, Dictionary<string, BlendShapeFrame> blendShapes = null)
|
|
{
|
|
|
|
if (meshes == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(meshes));
|
|
|
|
else if (transforms == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(transforms));
|
|
|
|
else if (materials == null)
|
|
|
|
throw new System.ArgumentNullException(nameof(materials));
|
|
|
|
else if (transforms.Length != meshes.Length)
|
|
|
|
throw new System.ArgumentException("The array of transforms doesn't have the same length as the array of meshes.", nameof(transforms));
|
|
|
|
else if (materials.Length != meshes.Length)
|
|
|
|
throw new System.ArgumentException("The array of materials doesn't have the same length as the array of meshes.", nameof(materials));
|
|
|
|
else if (bones != null && bones.Length != meshes.Length)
|
|
|
|
throw new System.ArgumentException("The array of bones doesn't have the same length as the array of meshes.", nameof(bones));
|
|
|
|
|
|
|
|
int totalVertexCount = 0;
|
|
|
|
int totalSubMeshCount = 0;
|
|
|
|
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
|
|
{
|
|
|
|
var mesh = meshes[meshIndex];
|
|
|
|
if (mesh == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The mesh at index {0} is null.", meshIndex), nameof(meshes));
|
|
|
|
else if (!mesh.isReadable)
|
|
|
|
throw new System.ArgumentException(string.Format("The mesh at index {0} is not readable.", meshIndex), nameof(meshes));
|
|
|
|
|
|
|
|
totalVertexCount += mesh.vertexCount;
|
|
|
|
totalSubMeshCount += mesh.subMeshCount;
|
|
|
|
|
|
|
|
// Validate the mesh materials
|
|
|
|
var meshMaterials = materials[meshIndex];
|
|
|
|
if (meshMaterials == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The materials for mesh at index {0} is null.", meshIndex), nameof(materials));
|
|
|
|
else if (meshMaterials.Length != mesh.subMeshCount)
|
|
|
|
throw new System.ArgumentException(string.Format("The materials for mesh at index {0} doesn't match the submesh count ({1} != {2}).", meshIndex, meshMaterials.Length, mesh.subMeshCount), nameof(materials));
|
|
|
|
|
|
|
|
for (int materialIndex = 0; materialIndex < meshMaterials.Length; materialIndex++)
|
|
{
|
|
|
|
if (meshMaterials[materialIndex] == null)
|
|
throw new System.ArgumentException(string.Format("The material at index {0} for mesh {1} is null.", materialIndex, mesh.name), nameof(materials));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the mesh bones
|
|
|
|
if (bones != null)
|
|
{
|
|
|
|
var meshBones = bones[meshIndex];
|
|
|
|
if (meshBones == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The bones for mesh at index {0} is null.", meshIndex), nameof(meshBones));
|
|
|
|
|
|
|
|
for (int boneIndex = 0; boneIndex < meshBones.Length; boneIndex++)
|
|
|
|
{
|
|
|
|
if (meshBones[boneIndex] == null)
|
|
|
|
throw new System.ArgumentException(string.Format("The bone at index {0} for mesh at index {1} is null.", boneIndex, meshIndex), nameof(meshBones));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var combinedVertices = new List<Vector3>(totalVertexCount);
|
|
|
|
var combinedIndices = new List<int[]>(totalSubMeshCount);
|
|
|
|
List<Vector3> combinedNormals = null;
|
|
|
|
List<Vector4> combinedTangents = null;
|
|
|
|
List<Color> combinedColors = null;
|
|
|
|
List<BoneWeight> combinedBoneWeights = null;
|
|
|
|
var combinedUVs = new List<Vector4>[MeshUtils.UVChannelCount];
|
|
|
|
|
|
|
|
List<Matrix4x4> usedBindposes = null;
|
|
|
|
List<Transform> usedBones = null;
|
|
|
|
var usedMaterials = new List<Material>(totalSubMeshCount);
|
|
|
|
var materialMap = new Dictionary<Material, int>(totalSubMeshCount);
|
|
|
|
|
|
|
|
int currentVertexCount = 0;
|
|
|
|
for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
|
|
{
|
|
|
|
var mesh = meshes[meshIndex];
|
|
|
|
var meshTransform = transforms[meshIndex];
|
|
|
|
var normalsTransform = normalsTransforms[meshIndex];
|
|
|
|
var meshMaterials = materials[meshIndex];
|
|
|
|
var meshBones = (bones != null ? bones[meshIndex] : null);
|
|
|
|
|
|
|
|
int subMeshCount = mesh.subMeshCount;
|
|
|
|
int meshVertexCount = mesh.vertexCount;
|
|
|
|
var meshVertices = mesh.vertices;
|
|
|
|
var meshNormals = mesh.normals;
|
|
|
|
var meshTangents = mesh.tangents;
|
|
|
|
var meshUVs = MeshUtils.GetMeshUVs(mesh);
|
|
|
|
var meshColors = mesh.colors;
|
|
|
|
var meshBoneWeights = mesh.boneWeights;
|
|
|
|
var meshBindposes = mesh.bindposes;
|
|
|
|
|
|
|
|
// Transform vertices with bones to keep only one bindpose
|
|
|
|
if (meshBones != null && meshBoneWeights != null && meshBoneWeights.Length > 0 && meshBindposes != null && meshBindposes.Length > 0 && meshBones.Length == meshBindposes.Length)
|
|
{
|
|
|
|
if (usedBindposes == null)
|
|
{
|
|
|
|
usedBindposes = new List<Matrix4x4>(meshBindposes);
|
|
|
|
usedBones = new List<Transform>(meshBones);
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] boneIndices = new int[meshBones.Length];
|
|
|
|
for (int i = 0; i < meshBones.Length; i++)
|
|
{
|
|
|
|
int usedBoneIndex = usedBones.IndexOf(meshBones[i]);
|
|
|
|
if (usedBoneIndex == -1 || meshBindposes[i] != usedBindposes[usedBoneIndex])
|
|
|
|
{
|
|
|
|
usedBoneIndex = usedBones.Count;
|
|
|
|
usedBones.Add(meshBones[i]);
|
|
|
|
usedBindposes.Add(meshBindposes[i]);
|
|
|
|
}
|
|
|
|
boneIndices[i] = usedBoneIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then we remap the bones
|
|
|
|
RemapBones(meshBoneWeights, boneIndices);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transforms the vertices, normals and tangents using the mesh transform
|
|
|
|
TransformVertices(meshVertices, ref meshTransform);
|
|
|
|
TransformNormals(meshNormals, ref normalsTransform);
|
|
|
|
TransformTangents(meshTangents, ref normalsTransform);
|
|
|
|
// Copy vertex positions & attributes
|
|
|
|
CopyVertexPositions(combinedVertices, meshVertices);
|
|
|
|
CopyVertexAttributes(ref combinedNormals, meshNormals, currentVertexCount, meshVertexCount, totalVertexCount, new Vector3(1f, 0f, 0f));
|
|
|
|
CopyVertexAttributes(ref combinedTangents, meshTangents, currentVertexCount, meshVertexCount, totalVertexCount, new Vector4(0f, 0f, 1f, 1f));
|
|
|
|
CopyVertexAttributes(ref combinedColors, meshColors, currentVertexCount, meshVertexCount, totalVertexCount, new Color(1f, 1f, 1f, 1f));
|
|
|
|
CopyVertexAttributes(ref combinedBoneWeights, meshBoneWeights, currentVertexCount, meshVertexCount, totalVertexCount, new BoneWeight());
|
|
|
|
|
|
|
|
for (int channel = 0; channel < meshUVs.Length; channel++)
|
|
|
|
{
|
|
|
|
CopyVertexAttributes(ref combinedUVs[channel], meshUVs[channel], currentVertexCount, meshVertexCount, totalVertexCount, new Vector4(0f, 0f, 0f, 0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
|
|
|
|
{
|
|
|
|
var subMeshMaterial = meshMaterials[subMeshIndex];
|
|
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
|
|
var subMeshIndices = mesh.GetTriangles(subMeshIndex, true);
|
|
|
|
#else
|
|
|
|
var subMeshIndices = mesh.GetTriangles(subMeshIndex);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (currentVertexCount > 0)
|
|
|
|
{
|
|
|
|
for (int index = 0; index < subMeshIndices.Length; index++)
|
|
|
|
{
|
|
|
|
subMeshIndices[index] += currentVertexCount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int existingSubMeshIndex;
|
|
|
|
if (materialMap.TryGetValue(subMeshMaterial, out existingSubMeshIndex))
|
|
{
|
|
|
|
combinedIndices[existingSubMeshIndex] = MergeArrays(combinedIndices[existingSubMeshIndex], subMeshIndices);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
int materialIndex = combinedIndices.Count;
|
|
|
|
materialMap.Add(subMeshMaterial, materialIndex);
|
|
|
|
usedMaterials.Add(subMeshMaterial);
|
|
|
|
combinedIndices.Add(subMeshIndices);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
currentVertexCount += meshVertexCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
var resultVertices = combinedVertices.ToArray();
|
|
|
|
var resultIndices = combinedIndices.ToArray();
|
|
|
|
var resultNormals = (combinedNormals != null ? combinedNormals.ToArray() : null);
|
|
|
|
var resultTangents = (combinedTangents != null ? combinedTangents.ToArray() : null);
|
|
|
|
var resultColors = (combinedColors != null ? combinedColors.ToArray() : null);
|
|
|
|
var resultBoneWeights = (combinedBoneWeights != null ? combinedBoneWeights.ToArray() : null);
|
|
|
|
var resultUVs = combinedUVs.ToArray();
|
|
|
|
var resultBindposes = (usedBindposes != null ? usedBindposes.ToArray() : null);
|
|
|
|
resultMaterials = usedMaterials.ToArray();
|
|
|
|
resultBones = (usedBones != null ? usedBones.ToArray() : null);
|
|
|
|
|
|
Mesh combinedMesh = MeshUtils.CreateMesh(resultVertices, resultIndices, resultNormals, resultTangents, resultColors, resultBoneWeights, resultUVs, resultBindposes, null);
|
|
|
|
|
|
if (blendShapes != null && blendShapes.Count > 0)
|
|
{
|
|
foreach (BlendShapeFrame blendShape in blendShapes.Values)
|
|
{
|
|
Vector3[] deltaVertices = new Vector3[combinedMesh.vertexCount];
|
|
Vector3[] deltaNormals = new Vector3[combinedMesh.vertexCount];
|
|
Vector3[] deltaTangents = new Vector3[combinedMesh.vertexCount];
|
|
|
|
for (int p = 0; p < blendShape.deltaVertices.Length; p++)
|
|
{
|
|
deltaVertices.SetValue(blendShape.deltaVertices[p], p + blendShape.vertexOffset);
|
|
deltaNormals.SetValue(blendShape.deltaNormals[p], p + blendShape.vertexOffset);
|
|
deltaTangents.SetValue(blendShape.deltaTangents[p], p + blendShape.vertexOffset);
|
|
}
|
|
|
|
combinedMesh.AddBlendShapeFrame(blendShape.shapeName, blendShape.frameWeight, deltaVertices, deltaNormals, deltaTangents);
|
|
}
|
|
}
|
|
|
|
// If after assigning normals blendshapes are assigned, then blendshapes do not work correctly
|
|
// In URP and HDRP configurations, so we add blendshapes first and then assign normals
|
|
combinedMesh.normals = resultNormals;
|
|
combinedMesh.tangents = resultTangents;
|
|
combinedMesh.RecalculateBounds();
|
|
|
|
return combinedMesh;
|
|
|
|
}
|
|
|
|
|
|
#endregion PUBLIC_METHODS
|
|
|
|
|
|
|
|
#region PRIVATE_METHODS
|
|
|
|
private static void ParentAndResetTransform(Transform transform, Transform parentTransform)
|
|
{
|
|
transform.SetParent(parentTransform);
|
|
transform.localPosition = Vector3.zero;
|
|
transform.localRotation = Quaternion.identity;
|
|
transform.localScale = Vector3.one;
|
|
}
|
|
|
|
|
|
private static void ParentAndOffsetTransform(Transform transform, Transform parentTransform, Transform originalTransform)
|
|
{
|
|
transform.position = originalTransform.position;
|
|
transform.rotation = originalTransform.rotation;
|
|
transform.localScale = originalTransform.lossyScale;
|
|
transform.SetParent(parentTransform, true);
|
|
}
|
|
|
|
|
|
private static Transform FindBestRootBone(Transform transform, SkinnedMeshRenderer[] skinnedMeshRenderers)
|
|
{
|
|
if (skinnedMeshRenderers == null || skinnedMeshRenderers.Length == 0)
|
|
return null;
|
|
|
|
Transform bestBone = null;
|
|
float bestDistance = float.MaxValue;
|
|
for (int i = 0; i < skinnedMeshRenderers.Length; i++)
|
|
{
|
|
if (skinnedMeshRenderers[i] == null || skinnedMeshRenderers[i].rootBone == null)
|
|
continue;
|
|
|
|
var rootBone = skinnedMeshRenderers[i].rootBone;
|
|
var distance = (rootBone.position - transform.position).sqrMagnitude;
|
|
if (distance < bestDistance)
|
|
{
|
|
bestBone = rootBone;
|
|
bestDistance = distance;
|
|
}
|
|
}
|
|
|
|
return bestBone;
|
|
}
|
|
|
|
|
|
private static Transform FindBestRootBone(Dictionary<Transform, Transform> topLevelParents, SkinnedMeshRenderer[] skinnedMeshRenderers)
|
|
{
|
|
if (skinnedMeshRenderers == null || skinnedMeshRenderers.Length == 0)
|
|
return null;
|
|
|
|
Transform bestBone = null;
|
|
float bestDistance = float.MaxValue;
|
|
for (int i = 0; i < skinnedMeshRenderers.Length; i++)
|
|
{
|
|
if (skinnedMeshRenderers[i] == null || skinnedMeshRenderers[i].rootBone == null)
|
|
continue;
|
|
|
|
Transform topParent = topLevelParents[skinnedMeshRenderers[i].transform];
|
|
var rootBone = skinnedMeshRenderers[i].rootBone;
|
|
var distance = (rootBone.position - topParent.position).sqrMagnitude;
|
|
if (distance < bestDistance)
|
|
{
|
|
bestBone = rootBone;
|
|
bestDistance = distance;
|
|
}
|
|
}
|
|
|
|
return bestBone;
|
|
}
|
|
|
|
|
|
private static Transform GetTopLevelParent(Transform forObject)
|
|
{
|
|
Transform topLevelParent = forObject;
|
|
|
|
while (topLevelParent.parent != null) { topLevelParent = topLevelParent.parent; }
|
|
|
|
return topLevelParent;
|
|
}
|
|
|
|
|
|
private static void CopyVertexPositions(List<Vector3> list, Vector3[] arr)
|
|
{
|
|
|
|
if (arr == null || arr.Length == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (int i = 0; i < arr.Length; i++)
|
|
|
|
{
|
|
|
|
list.Add(arr[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static void CopyVertexAttributes<T>(ref List<T> dest, IEnumerable<T> src, int previousVertexCount, int meshVertexCount, int totalVertexCount, T defaultValue)
|
|
{
|
|
|
|
if (src == null || src.Count() == 0)
|
|
|
|
{
|
|
|
|
if (dest != null)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < meshVertexCount; i++)
|
|
|
|
{
|
|
|
|
dest.Add(defaultValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dest == null)
|
|
|
|
{
|
|
|
|
dest = new List<T>(totalVertexCount);
|
|
|
|
for (int i = 0; i < previousVertexCount; i++)
|
|
|
|
{
|
|
|
|
dest.Add(defaultValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dest.AddRange(src);
|
|
|
|
}
|
|
|
|
|
|
private static T[] MergeArrays<T>(T[] arr1, T[] arr2)
|
|
{
|
|
|
|
var newArr = new T[arr1.Length + arr2.Length];
|
|
|
|
System.Array.Copy(arr1, 0, newArr, 0, arr1.Length);
|
|
|
|
System.Array.Copy(arr2, 0, newArr, arr1.Length, arr2.Length);
|
|
|
|
return newArr;
|
|
|
|
}
|
|
|
|
|
|
private static void TransformVertices(Vector3[] vertices, ref Matrix4x4 transform)
|
|
{
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i] = transform.MultiplyPoint3x4(vertices[i]);
|
|
}
|
|
}
|
|
|
|
|
|
private static void TransformNormals(Vector3[] normals, ref Tuple<Matrix4x4, bool> transform)
|
|
{
|
|
|
|
if (normals == null)
|
|
return;
|
|
|
|
for (int i = 0; i < normals.Length; i++)
|
|
{
|
|
// Non-UniformScaling
|
|
if (transform.Item2 == true)
|
|
{
|
|
Quaternion rotation = Quaternion.LookRotation(transform.Item1.GetColumn(2), transform.Item1.GetColumn(1));
|
|
normals[i] = rotation * normals[i]; //baw did
|
|
}
|
|
else
|
|
{
|
|
normals[i] = transform.Item1.MultiplyVector(normals[i]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static void TransformTangents(Vector4[] tangents, ref Tuple<Matrix4x4, bool> transform)
|
|
{
|
|
|
|
if (tangents == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Vector3 tengentDir;
|
|
|
|
for (int i = 0; i < tangents.Length; i++)
|
|
|
|
{
|
|
|
|
tengentDir = transform.Item1.MultiplyVector(new Vector3(tangents[i].x, tangents[i].y, tangents[i].z));
|
|
|
|
tangents[i] = new Vector4(tengentDir.x, tengentDir.y, tengentDir.z, tangents[i].w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static void RemapBones(BoneWeight[] boneWeights, int[] boneIndices)
|
|
{
|
|
|
|
for (int i = 0; i < boneWeights.Length; i++)
|
|
|
|
{
|
|
|
|
if (boneWeights[i].weight0 > 0)
|
|
|
|
{
|
|
|
|
boneWeights[i].boneIndex0 = boneIndices[boneWeights[i].boneIndex0];
|
|
|
|
}
|
|
|
|
if (boneWeights[i].weight1 > 0)
|
|
|
|
{
|
|
|
|
boneWeights[i].boneIndex1 = boneIndices[boneWeights[i].boneIndex1];
|
|
|
|
}
|
|
|
|
if (boneWeights[i].weight2 > 0)
|
|
|
|
{
|
|
|
|
boneWeights[i].boneIndex2 = boneIndices[boneWeights[i].boneIndex2];
|
|
|
|
}
|
|
|
|
if (boneWeights[i].weight3 > 0)
|
|
|
|
{
|
|
|
|
boneWeights[i].boneIndex3 = boneIndices[boneWeights[i].boneIndex3];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static Matrix4x4 ScaleMatrix(ref Matrix4x4 matrix, float scale)
|
|
{
|
|
|
|
return new Matrix4x4()
|
|
|
|
{
|
|
|
|
m00 = matrix.m00 * scale,
|
|
|
|
m01 = matrix.m01 * scale,
|
|
|
|
m02 = matrix.m02 * scale,
|
|
|
|
m03 = matrix.m03 * scale,
|
|
|
|
|
|
|
|
m10 = matrix.m10 * scale,
|
|
|
|
m11 = matrix.m11 * scale,
|
|
|
|
m12 = matrix.m12 * scale,
|
|
|
|
m13 = matrix.m13 * scale,
|
|
|
|
|
|
|
|
m20 = matrix.m20 * scale,
|
|
|
|
m21 = matrix.m21 * scale,
|
|
|
|
m22 = matrix.m22 * scale,
|
|
|
|
m23 = matrix.m23 * scale,
|
|
|
|
|
|
|
|
m30 = matrix.m30 * scale,
|
|
|
|
m31 = matrix.m31 * scale,
|
|
|
|
m32 = matrix.m32 * scale,
|
|
|
|
m33 = matrix.m33 * scale
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
private static void CombineMeshesUnity(Transform parentTransform, MeshFilter[] meshFilters)
|
|
{
|
|
|
|
var combineMeshInstanceDictionary = new Dictionary<Material, List<CombineInstance>>();
|
|
int totalVertsCount = 0;
|
|
|
|
foreach (var meshFilter in meshFilters)
|
|
{
|
|
// Check if we are in older versions of Unity with max vertex limit <= 65534
|
|
|
|
if (meshFilter == null) { continue; }
|
|
|
|
Mesh m = meshFilter.sharedMesh;
|
|
|
|
if (m == null) { continue; }
|
|
|
|
totalVertsCount += m.vertexCount;
|
|
}
|
|
|
|
|
|
foreach (var meshFilter in meshFilters)
|
|
{
|
|
var mesh = meshFilter.sharedMesh;
|
|
//var vertices = new List<Vector3>(); //uncomment when manual mesh duplication is uncommented
|
|
//mesh.GetVertices(vertices); //uncomment when manual mesh duplication is uncommented
|
|
var materials = meshFilter.GetComponent<Renderer>().sharedMaterials;
|
|
var subMeshCount = meshFilter.sharedMesh.subMeshCount;
|
|
|
|
|
|
if (materials == null)
|
|
{
|
|
throw new System.ArgumentException(string.Format("The materials for GameObject are null.", meshFilter.transform.name), nameof(materials));
|
|
}
|
|
|
|
else if (materials.Length != mesh.subMeshCount)
|
|
{
|
|
throw new System.ArgumentException(string.Format("The materials for mesh {0} on GameObject {1} doesn't match the submesh count ({2} != {3}).", mesh.name, meshFilter.transform.name, materials.Length, mesh.subMeshCount), nameof(materials));
|
|
}
|
|
|
|
|
|
for (int materialIndex = 0; materialIndex < materials.Length; materialIndex++)
|
|
{
|
|
if (materials[materialIndex] == null)
|
|
{
|
|
throw new System.ArgumentException(string.Format("The material at index {0} for mesh {1} on GameObject {2} is null.", materialIndex, mesh.name, meshFilter.transform.name), nameof(materials));
|
|
}
|
|
}
|
|
|
|
|
|
for (var i = 0; i < subMeshCount; i++)
|
|
{
|
|
var material = materials[i];
|
|
var triangles = new List<int>();
|
|
mesh.GetTriangles(triangles, i);
|
|
|
|
//manual mesh duplication
|
|
|
|
//var newMesh = new Mesh
|
|
//{
|
|
// vertices = vertices.ToArray(),
|
|
// triangles = triangles.ToArray(),
|
|
// uv = mesh.uv,
|
|
// normals = mesh.normals,
|
|
// colors = mesh.colors
|
|
//};
|
|
|
|
var newMesh = UnityEngine.Object.Instantiate(mesh);
|
|
newMesh.triangles = triangles.ToArray();
|
|
|
|
|
|
if (!combineMeshInstanceDictionary.ContainsKey(material))
|
|
{
|
|
combineMeshInstanceDictionary.Add(material, new List<CombineInstance>());
|
|
}
|
|
|
|
|
|
//var combineInstance = new CombineInstance
|
|
//{ transform = meshFilter.transform.localToWorldMatrix, mesh = newMesh };
|
|
var combineInstance = new CombineInstance
|
|
{ transform = (parentTransform.worldToLocalMatrix * meshFilter.transform.localToWorldMatrix) , mesh = newMesh };
|
|
|
|
combineMeshInstanceDictionary[material].Add(combineInstance);
|
|
}
|
|
}
|
|
|
|
|
|
unityCombinedMeshRenderers = new MeshRenderer[combineMeshInstanceDictionary.Count];
|
|
unityCombinedMeshesMats = new Material[combineMeshInstanceDictionary.Count];
|
|
|
|
int index = 0;
|
|
|
|
foreach (var kvp in combineMeshInstanceDictionary)
|
|
{
|
|
var newObject = new GameObject(kvp.Key.name);
|
|
|
|
var meshRenderer = newObject.AddComponent<MeshRenderer>();
|
|
var meshFilter = newObject.AddComponent<MeshFilter>();
|
|
|
|
meshRenderer.material = kvp.Key;
|
|
var combinedMesh = new Mesh();
|
|
|
|
#if UNITY_MESH_INDEXFORMAT_SUPPORT
|
|
if (totalVertsCount > 65534) { combinedMesh.indexFormat = IndexFormat.UInt32; }
|
|
#endif
|
|
|
|
combinedMesh.CombineMeshes(kvp.Value.ToArray());
|
|
|
|
meshFilter.sharedMesh = combinedMesh;
|
|
|
|
//#if UNITY_EDITOR
|
|
// UnityEditor.MeshUtility.Optimize(meshFilter.sharedMesh);
|
|
|
|
// if (generateUV2)
|
|
// {
|
|
// UnityEditor.Unwrapping.GenerateSecondaryUVSet(meshFilter.sharedMesh);
|
|
// }
|
|
//#endif
|
|
|
|
newObject.transform.parent = parentTransform.parent;
|
|
|
|
unityCombinedMeshesMats[index] = kvp.Key;
|
|
unityCombinedMeshRenderers[index] = meshRenderer;
|
|
|
|
index++;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endregion PRIVATE_METHODS
|
|
|
|
}
|
|
}
|