Rasagar/Library/PackageCache/com.unity.probuilder/Runtime/MeshOperations/CombineMeshes.cs
2024-08-26 23:07:20 +03:00

336 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides methods for merging multiple <see cref="ProBuilderMesh"/> objects into a single mesh.
/// </summary>
public static class CombineMeshes
{
/// <summary>
/// Merges a collection of <see cref="ProBuilderMesh"/> objects to create as few meshes as possible. This may result in
/// more than one mesh due to a max vertex count limit of 65535.
/// </summary>
/// <param name="meshes">The collection of meshes to merge.</param>
/// <returns>
/// A list of merged meshes. In most cases this will be a single mesh. However it can be multiple in cases
/// where the resulting vertex count exceeds the maximum allowable value.
/// </returns>
[Obsolete("Combine(IEnumerable<ProBuilderMesh> meshes) is deprecated. Plase use Combine(IEnumerable<ProBuilderMesh> meshes, ProBuilderMesh meshTarget).")]
public static List<ProBuilderMesh> Combine(IEnumerable<ProBuilderMesh> meshes)
{
return CombineToNewMeshes(meshes);
}
/// <summary>
/// Merges a collection of <see cref="ProBuilderMesh"/> objects into as few meshes as possible. It re-uses the `meshTarget` object as the first
/// destination for the first <see cref="ProBuilderMesh.maxVertexCount"/> -1 vertices. If the sum of vertices is above <see cref="ProBuilderMesh.maxVertexCount"/> - 1,
/// it generates new meshes unless there is a single mesh left. In that case it appends it to the return list.
/// </summary>
/// <param name="meshes">A collection of meshes to merge. This collection should include the `meshTarget` object.</param>
/// <param name="meshTarget">A mesh to use as the starting point for merging and which will be kept as a reference (target). This mesh must be present in the `meshes` collection.</param>
/// <returns>
/// A list of merged meshes. In most cases this is a single mesh corresponding to `meshTarget`. However it can be multiple in cases
/// where the resulting vertex count exceeds the maximum allowable value.
/// </returns>
public static List<ProBuilderMesh> Combine(IEnumerable<ProBuilderMesh> meshes, ProBuilderMesh meshTarget)
{
if (meshes == null)
throw new ArgumentNullException("meshes");
if (meshTarget == null)
throw new ArgumentNullException("meshTarget");
if (!meshes.Any() || meshes.Count() < 2 )
return null;
if (!meshes.Contains(meshTarget))
return null;
var vertices = new List<Vertex>(meshTarget.GetVertices());
var faces = new List<Face>(meshTarget.facesInternal);
var sharedVertices = new List<SharedVertex>(meshTarget.sharedVertices);
var sharedTextures = new List<SharedVertex>(meshTarget.sharedTextures);
int offset = meshTarget.vertexCount;
var materialMap = new List<Material>(meshTarget.renderer.sharedMaterials);
var targetTransform = meshTarget.transform;
var firstMeshContributors = new List<ProBuilderMesh>();
var remainderMeshContributors = new List<ProBuilderMesh>();
var currentMeshVertexCount = offset;
foreach (var mesh in meshes)
{
if (mesh != meshTarget)
{
if (currentMeshVertexCount + mesh.vertexCount < ProBuilderMesh.maxVertexCount)
{
currentMeshVertexCount += mesh.vertexCount;
firstMeshContributors.Add(mesh);
}
else
{
remainderMeshContributors.Add(mesh);
}
}
}
var autoUvFaces = new List<Face>();
AccumulateMeshesInfo(
firstMeshContributors,
offset,
ref vertices,
ref faces,
ref autoUvFaces,
ref sharedVertices,
ref sharedTextures,
ref materialMap,
targetTransform
);
meshTarget.SetVertices(vertices);
meshTarget.faces = faces;
meshTarget.sharedVertices = sharedVertices;
meshTarget.sharedTextures = sharedTextures != null ? sharedTextures.ToArray() : null;
meshTarget.renderer.sharedMaterials = materialMap.ToArray();
meshTarget.ToMesh();
meshTarget.Refresh();
UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(meshTarget, autoUvFaces);
MeshValidation.EnsureMeshIsValid(meshTarget, out int removedVertices);
var returnedMesh = new List<ProBuilderMesh>() { meshTarget };
if (remainderMeshContributors.Count > 1)
{
var newMeshes = CombineToNewMeshes(remainderMeshContributors);
foreach (var mesh in newMeshes)
{
MeshValidation.EnsureMeshIsValid(mesh, out removedVertices);
returnedMesh.Add(mesh);
}
}
else if (remainderMeshContributors.Count == 1)
{
returnedMesh.Add(remainderMeshContributors[0]);
}
return returnedMesh;
}
static List<ProBuilderMesh> CombineToNewMeshes(IEnumerable<ProBuilderMesh> meshes)
{
if (meshes == null)
throw new ArgumentNullException("meshes");
if (!meshes.Any() || meshes.Count() < 2)
return null;
var vertices = new List<Vertex>();
var faces = new List<Face>();
var autoUvFaces = new List<Face>();
var sharedVertices = new List<SharedVertex>();
var sharedTextures = new List<SharedVertex>();
int offset = 0;
var materialMap = new List<Material>();
AccumulateMeshesInfo(
meshes,
offset,
ref vertices,
ref faces,
ref autoUvFaces,
ref sharedVertices,
ref sharedTextures,
ref materialMap
);
var res = SplitByMaxVertexCount(vertices, faces, sharedVertices, sharedTextures);
var pivot = meshes.LastOrDefault().transform.position;
foreach (var m in res)
{
m.renderer.sharedMaterials = materialMap.ToArray();
InternalMeshUtility.FilterUnusedSubmeshIndexes(m);
m.SetPivot(pivot);
UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(m, autoUvFaces);
}
return res;
}
static void AccumulateMeshesInfo(
IEnumerable<ProBuilderMesh> meshes,
int offset,
ref List<Vertex> vertices,
ref List<Face> faces,
ref List<Face> autoUvFaces,
ref List<SharedVertex> sharedVertices,
ref List<SharedVertex> sharedTextures,
ref List<Material> materialMap,
Transform targetTransform = null
)
{
foreach (var mesh in meshes)
{
var meshVertexCount = mesh.vertexCount;
var transform = mesh.transform;
var meshVertices = mesh.GetVertices();
var meshFaces = mesh.facesInternal;
var meshSharedVertices = mesh.sharedVertices;
var meshSharedTextures = mesh.sharedTextures;
var materials = mesh.renderer.sharedMaterials;
var materialCount = materials.Length;
for (int i = 0; i < meshVertexCount; i++)
{
var worldVertex = transform.TransformVertex(meshVertices[i]);
if (targetTransform != null)
vertices.Add(targetTransform.InverseTransformVertex(worldVertex));
else
vertices.Add(worldVertex);
}
foreach (var face in meshFaces)
{
var newFace = new Face(face);
newFace.ShiftIndexes(offset);
// prevents uvs from shifting when being converted from local coords to world space
if (!newFace.manualUV && !newFace.uv.useWorldSpace)
{
newFace.manualUV = true;
autoUvFaces.Add(newFace);
}
var material = materialCount > 0 ? materials[Math.Clamp(face.submeshIndex, 0, materialCount - 1)] : null;
var submeshIndex = materialMap.IndexOf(material);
if (submeshIndex > -1)
{
newFace.submeshIndex = submeshIndex;
}
else
{
if (material == null)
{
newFace.submeshIndex = 0;
}
else
{
newFace.submeshIndex = materialMap.Count;
materialMap.Add(material);
}
}
faces.Add(newFace);
}
foreach (var sv in meshSharedVertices)
{
var nsv = new SharedVertex(sv);
nsv.ShiftIndexes(offset);
sharedVertices.Add(nsv);
}
foreach (var st in meshSharedTextures)
{
var nst = new SharedVertex(st);
nst.ShiftIndexes(offset);
sharedTextures.Add(nst);
}
offset += meshVertexCount;
}
}
static ProBuilderMesh CreateMeshFromSplit(List<Vertex> vertices,
List<Face> faces,
Dictionary<int, int> sharedVertexLookup,
Dictionary<int, int> sharedTextureLookup,
Dictionary<int, int> remap,
Material[] materials)
{
// finalize mesh
var sv = new Dictionary<int, int>();
var st = new Dictionary<int, int>();
foreach (var f in faces)
{
for (int i = 0, c = f.indexesInternal.Length; i < c; i++)
f.indexesInternal[i] = remap[f.indexesInternal[i]];
f.InvalidateCache();
}
foreach (var kvp in remap)
{
int v;
if (sharedVertexLookup.TryGetValue(kvp.Key, out v))
sv.Add(kvp.Value, v);
if (sharedTextureLookup.TryGetValue(kvp.Key, out v))
st.Add(kvp.Value, v);
}
return ProBuilderMesh.Create(
vertices,
faces,
SharedVertex.ToSharedVertices(sv),
st.Count > 0 ? SharedVertex.ToSharedVertices(st) : null,
materials);
}
/// <summary>
/// Break a ProBuilder mesh into multiple meshes if it's vertex count is greater than maxVertexCount.
/// </summary>
/// <returns></returns>
internal static List<ProBuilderMesh> SplitByMaxVertexCount(IList<Vertex> vertices, IList<Face> faces, IList<SharedVertex> sharedVertices, IList<SharedVertex> sharedTextures, uint maxVertexCount = ProBuilderMesh.maxVertexCount)
{
uint vertexCount = (uint)vertices.Count;
uint meshCount = System.Math.Max(1u, vertexCount / maxVertexCount);
var submeshCount = faces.Max(x => x.submeshIndex) + 1;
if (meshCount < 2)
return new List<ProBuilderMesh>() { ProBuilderMesh.Create(vertices, faces, sharedVertices, sharedTextures, new Material[submeshCount]) };
var sharedVertexLookup = new Dictionary<int, int>();
SharedVertex.GetSharedVertexLookup(sharedVertices, sharedVertexLookup);
var sharedTextureLookup = new Dictionary<int, int>();
SharedVertex.GetSharedVertexLookup(sharedTextures, sharedTextureLookup);
var meshes = new List<ProBuilderMesh>();
var mv = new List<Vertex>();
var mf = new List<Face>();
var remap = new Dictionary<int, int>();
foreach (var face in faces)
{
if (mv.Count + face.distinctIndexes.Count > maxVertexCount)
{
// finalize mesh
meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount]));
mv.Clear();
mf.Clear();
remap.Clear();
}
foreach (int i in face.distinctIndexes)
{
mv.Add(vertices[i]);
remap.Add(i, mv.Count - 1);
}
mf.Add(face);
}
if (mv.Count > 0)
meshes.Add(CreateMeshFromSplit(mv, mf, sharedVertexLookup, sharedTextureLookup, remap, new Material[submeshCount]));
return meshes;
}
}
}