TurnBasedStrategyCourse/Library/PackageCache/com.unity.probuilder@5.1.0/Runtime/MeshOperations/VertexEditing.cs

334 lines
14 KiB
C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using UnityEngine.ProBuilder;
using UnityEngine.ProBuilder.KdTree;
using UnityEngine.ProBuilder.KdTree.Math;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Methods for merging and splitting common (or shared) vertices.
/// </summary>
public static class VertexEditing
{
/// <summary>
/// Collapses all specified indices to a single shared index.
///
/// This is equivalent to the [Collapse Vertices](../manual/Vert_Collapse.html) action.
/// </summary>
/// <remarks>
/// Retains vertex normals.
/// </remarks>
/// <param name="mesh">Target mesh.</param>
/// <param name="indexes">The indexes to merge to a single shared vertex.</param>
/// <param name="collapseToFirst">True to collapse the vertices onto the first vertex position; false to merge all vertices to the average position.</param>
/// <returns>The first available local index created as a result of the merge, or -1 if action failed.</returns>
public static int MergeVertices(this ProBuilderMesh mesh, int[] indexes, bool collapseToFirst = false)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (indexes == null)
throw new ArgumentNullException("indexes");
Vertex[] vertices = mesh.GetVertices();
Vertex cen = collapseToFirst ? vertices[indexes[0]] : Vertex.Average(vertices, indexes);
mesh.SetVerticesCoincident(indexes);
UVEditing.SplitUVs(mesh, indexes);
int sharedVertexHandle = mesh.GetSharedVertexHandle(indexes[0]);
mesh.SetSharedVertexValues(sharedVertexHandle, cen);
SharedVertex merged = mesh.sharedVerticesInternal[sharedVertexHandle];
List<int> removedIndexes = new List<int>();
MeshValidation.RemoveDegenerateTriangles(mesh, removedIndexes);
// get a non-deleted index to work with
int ind = -1;
for (int i = 0; i < merged.Count; i++)
if (!removedIndexes.Contains(merged[i]))
ind = merged[i];
int res = ind;
for (int i = 0; i < removedIndexes.Count; i++)
if (ind > removedIndexes[i])
res--;
return res;
}
/// <summary>
/// Splits the vertices referenced by edge from their shared indices so that each vertex moves independently.
///
/// This corresponds to the [Split Vertices](../manual/Vert_Split.html) action.
/// </summary>
/// <remarks>
/// This is equivalent to calling `SplitVertices(mesh, new int[] { edge.x, edge.y });`.
/// </remarks>
/// <param name="mesh">The source mesh.</param>
/// <param name="edge">The edge to query for vertex indexes.</param>
/// <seealso cref="SplitVertices(UnityEngine.ProBuilder.ProBuilderMesh,System.Collections.Generic.IEnumerable{int})"/>
public static void SplitVertices(this ProBuilderMesh mesh, Edge edge)
{
SplitVertices(mesh, new int[] { edge.a, edge.b });
}
/// <summary>
/// Splits vertices from their shared indices so that each vertex moves independently.
///
/// This corresponds to the [Split Vertices](../manual/Vert_Split.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="vertices">A list of vertex indices to split.</param>
/// <seealso cref="ProBuilderMesh.sharedVertices"/>
public static void SplitVertices(this ProBuilderMesh mesh, IEnumerable<int> vertices)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (vertices == null)
throw new ArgumentNullException("vertices");
// ToDictionary always sets the universal indexes in ascending order from 0+.
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
int max = lookup.Count;
foreach (int i in vertices)
lookup[i] = ++max;
mesh.SetSharedVertices(lookup);
}
/// <summary>
/// Similar to Merge vertices, expect that this method only collapses vertices within a specified distance
/// of one another (typically `Mathf.Epsilon` is used).
///
/// This is equivalent to the [Weld Vertices](../manual/Vert_Weld.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="indexes">The vertex indices to consider. For example, to weld the entire object, set this value to the return value from `pb.faces.SelectMany(x => x.indexes)`.</param>
/// <param name="neighborRadius">The minimum distance between vertices to consider them for welding.</param>
/// <returns>The indices of any new vertices created by a weld.</returns>
public static int[] WeldVertices(this ProBuilderMesh mesh, IEnumerable<int> indexes, float neighborRadius)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (indexes == null)
throw new ArgumentNullException("indexes");
Vertex[] vertices = mesh.GetVertices();
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
HashSet<int> common = mesh.GetSharedVertexHandles(indexes);
int vertexCount = common.Count;
// Make assumption that there will rarely be a time when a single weld encompasses more than 32 vertices.
// If a radial search returns neighbors matching the max count, the search is re-done and maxNearestNeighbors
// is set to the resulting length. This will be slow, but in most cases shouldn't happen ever, or if it does,
// should only happen once or twice.
int maxNearestNeighbors = System.Math.Min(32, common.Count);
// 3 dimensions, duplicate entries allowed
KdTree<float, int> tree = new KdTree<float, int>(3, new FloatMath(), AddDuplicateBehavior.Collect);
foreach (int i in common)
{
Vector3 v = vertices[sharedIndexes[i][0]].position;
tree.Add(new float[] { v.x, v.y, v.z }, i);
}
float[] point = new float[3] { 0, 0, 0 };
Dictionary<int, int> remapped = new Dictionary<int, int>();
Dictionary<int, Vector3> averages = new Dictionary<int, Vector3>();
int index = sharedIndexes.Length;
foreach (int commonIndex in common)
{
// already merged with another
if (remapped.ContainsKey(commonIndex))
continue;
Vector3 v = vertices[sharedIndexes[commonIndex][0]].position;
point[0] = v.x;
point[1] = v.y;
point[2] = v.z;
// Radial search at each point
KdTreeNode<float, int>[] neighbors = tree.RadialSearch(point, neighborRadius, maxNearestNeighbors);
// if first radial search filled the entire allotment reset the max neighbor count to 1.5x.
// the result hopefully preventing double-searches in the next iterations.
if (maxNearestNeighbors < vertexCount && neighbors.Length >= maxNearestNeighbors)
{
neighbors = tree.RadialSearch(point, neighborRadius, vertexCount);
maxNearestNeighbors = System.Math.Min(vertexCount, neighbors.Length + neighbors.Length / 2);
}
Vector3 avg = Vector3.zero;
float count = 0;
for (int neighborIndex = 0; neighborIndex < neighbors.Length; neighborIndex++)
{
// common index of this neighbor
int c = neighbors[neighborIndex].Value;
// if it's already been added to another, skip it
if (remapped.ContainsKey(c))
continue;
avg.x += neighbors[neighborIndex].Point[0];
avg.y += neighbors[neighborIndex].Point[1];
avg.z += neighbors[neighborIndex].Point[2];
remapped.Add(c, index);
count++;
if (neighbors[neighborIndex].Duplicates != null)
{
for (int duplicateIndex = 0; duplicateIndex < neighbors[neighborIndex].Duplicates.Count; duplicateIndex++)
remapped.Add(neighbors[neighborIndex].Duplicates[duplicateIndex], index);
}
}
avg.x /= count;
avg.y /= count;
avg.z /= count;
averages.Add(index, avg);
index++;
}
var welds = new int[remapped.Count];
int n = 0;
var lookup = mesh.sharedVertexLookup;
foreach (var kvp in remapped)
{
SharedVertex tris = sharedIndexes[kvp.Key];
welds[n++] = tris[0];
for (int i = 0; i < tris.Count; i++)
{
lookup[tris[i]] = kvp.Value;
vertices[tris[i]].position = averages[kvp.Value];
}
}
mesh.SetSharedVertices(lookup);
mesh.SetVertices(vertices);
return welds;
}
/// <summary>
/// Split a common index on a face into two vertices and slide each vertex backwards along it's feeding edge by distance.
/// This method does not perform any input validation, so make sure edgeAndCommonIndex is distinct and all winged edges belong
/// to the same face.
///<pre>
/// `appendedVertices` is common index and a list of the new face indexes it was split into.
///
/// _ _ _ _ _ _ _
/// | /
/// | -> |
/// | |
/// </pre>
/// </summary>
/// <param name="vertices"></param>
/// <param name="edgeAndCommonIndex"></param>
/// <param name="distance"></param>
/// <param name="appendedVertices"></param>
/// <returns></returns>
internal static FaceRebuildData ExplodeVertex(
IList<Vertex> vertices,
IList<SimpleTuple<WingedEdge, int>> edgeAndCommonIndex,
float distance,
out Dictionary<int, List<int>> appendedVertices)
{
Face face = edgeAndCommonIndex.FirstOrDefault().item1.face;
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
appendedVertices = new Dictionary<int, List<int>>();
Vector3 oldNormal = Math.Normal(vertices, face.indexesInternal);
// store local and common index of split points
Dictionary<int, int> toSplit = new Dictionary<int, int>();
foreach (SimpleTuple<WingedEdge, int> v in edgeAndCommonIndex)
{
if (v.item2 == v.item1.edge.common.a)
toSplit.Add(v.item1.edge.local.a, v.item2);
else
toSplit.Add(v.item1.edge.local.b, v.item2);
}
int pc = perimeter.Count;
List<Vertex> n_vertices = new List<Vertex>();
for (int i = 0; i < pc; i++)
{
int index = perimeter[i].b;
// split this index into two
if (toSplit.ContainsKey(index))
{
// a --- b --- c
Vertex a = vertices[perimeter[i].a];
Vertex b = vertices[perimeter[i].b];
Vertex c = vertices[perimeter[(i + 1) % pc].b];
Vertex leading_dir = a - b;
Vertex following_dir = c - b;
leading_dir.Normalize();
following_dir.Normalize();
Vertex leading_insert = vertices[index] + leading_dir * distance;
Vertex following_insert = vertices[index] + following_dir * distance;
appendedVertices.AddOrAppend(toSplit[index], n_vertices.Count);
n_vertices.Add(leading_insert);
appendedVertices.AddOrAppend(toSplit[index], n_vertices.Count);
n_vertices.Add(following_insert);
}
else
{
n_vertices.Add(vertices[index]);
}
}
List<int> triangles;
if (Triangulation.TriangulateVertices(n_vertices, out triangles, false))
{
FaceRebuildData data = new FaceRebuildData();
data.vertices = n_vertices;
data.face = new Face(face);
Vector3 newNormal = Math.Normal(n_vertices, triangles);
if (Vector3.Dot(oldNormal, newNormal) < 0f)
triangles.Reverse();
data.face.indexesInternal = triangles.ToArray();
return data;
}
return null;
}
static Edge AlignEdgeWithDirection(EdgeLookup edge, int commonIndex)
{
if (edge.common.a == commonIndex)
return new Edge(edge.local.a, edge.local.b);
else
return new Edge(edge.local.b, edge.local.a);
}
}
}