Added player character and step in/out of dialogue in scene 101

This commit is contained in:
2026-08-15 15:22:47 +02:00
parent a42f25d5fa
commit 38ba2b0eff
2019 changed files with 181227 additions and 265 deletions
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d247d7d3f9b23a468d61e433994dd7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Editor")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Tests")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Editor.Tests")]
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ab8e2d6a92a04e42baab6202f8650fb3
timeCreated: 1510169863
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,345 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.ProBuilder;
using System;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides functions for beveling edges.
/// </summary>
public static class Bevel
{
/// <summary>
/// Applies a bevel to a set of edges.
///
/// This is the equivalent of the [Bevel (Edge)](../manual/Edge_Bevel.html) action.
/// </summary>
/// <param name="mesh">Target mesh.</param>
/// <param name="edges">A set of edges to apply bevelling to.</param>
/// <param name="amount">A value from 0 (do not bevel) to 1 (bevel the entire face).</param>
/// <returns>The new faces created to form the bevel.</returns>
public static List<Face> BevelEdges(ProBuilderMesh mesh, IList<Edge> edges, float amount)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
List<EdgeLookup> m_edges = EdgeLookup.GetEdgeLookup(edges, lookup).Distinct().ToList();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
List<FaceRebuildData> appendFaces = new List<FaceRebuildData>();
Dictionary<Face, List<int>> ignore = new Dictionary<Face, List<int>>();
HashSet<int> slide = new HashSet<int>();
int beveled = 0;
Dictionary<int, List<SimpleTuple<FaceRebuildData, List<int>>>> holes = new Dictionary<int, List<SimpleTuple<FaceRebuildData, List<int>>>>();
// test every edge that will be moved along to make sure the bevel distance is appropriate. if it's not, adjust the max bevel amount
// to suit.
Dictionary<int, List<WingedEdge>> spokes = WingedEdge.GetSpokes(wings);
HashSet<int> tested_common = new HashSet<int>();
foreach (EdgeLookup e in m_edges)
{
if (tested_common.Add(e.common.a))
{
foreach (WingedEdge w in spokes[e.common.a])
{
Edge le = w.edge.local;
amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount);
}
}
if (tested_common.Add(e.common.b))
{
foreach (WingedEdge w in spokes[e.common.b])
{
Edge le = w.edge.local;
amount = Mathf.Min(Vector3.Distance(vertices[le.a].position, vertices[le.b].position) - .001f, amount);
}
}
}
if (amount < .001f)
{
Log.Info("Bevel Distance > Available Surface");
return null;
}
// iterate selected edges and move each leading edge back along it's direction
// storing information about adjacent faces in the process
foreach (EdgeLookup lup in m_edges)
{
WingedEdge we = wings.FirstOrDefault(x => x.edge.Equals(lup));
if (we == null || we.opposite == null)
continue;
beveled++;
ignore.AddOrAppend(we.face, we.edge.common.a);
ignore.AddOrAppend(we.face, we.edge.common.b);
ignore.AddOrAppend(we.opposite.face, we.edge.common.a);
ignore.AddOrAppend(we.opposite.face, we.edge.common.b);
// after initial slides go back and split indirect triangles at the intersecting index into two vertices
slide.Add(we.edge.common.a);
slide.Add(we.edge.common.b);
SlideEdge(vertices, we, amount);
SlideEdge(vertices, we.opposite, amount);
appendFaces.AddRange(GetBridgeFaces(vertices, we, we.opposite, holes));
}
if (beveled < 1)
{
Log.Info("Cannot Bevel Open Edges");
return null;
}
// grab the "createdFaces" array now so that the selection returned is just the bridged faces
// then add holes later
var createdFaces = new List<Face>(appendFaces.Select(x => x.face));
Dictionary<Face, List<SimpleTuple<WingedEdge, int>>> sorted = new Dictionary<Face, List<SimpleTuple<WingedEdge, int>>>();
// sort the adjacent but affected faces into winged edge groups where each group contains a set of
// unique winged edges pointing to the same face
foreach (int c in slide)
{
IEnumerable<WingedEdge> matches = wings.Where(x => x.edge.common.Contains(c) && !(ignore.ContainsKey(x.face) && ignore[x.face].Contains(c)));
HashSet<Face> used = new HashSet<Face>();
foreach (WingedEdge match in matches)
{
if (!used.Add(match.face))
continue;
sorted.AddOrAppend(match.face, new SimpleTuple<WingedEdge, int>(match, c));
}
}
// now go through those sorted faces and apply the vertex exploding, keeping track of any holes created
foreach (KeyValuePair<Face, List<SimpleTuple<WingedEdge, int>>> kvp in sorted)
{
// common index & list of vertices it was split into
Dictionary<int, List<int>> appended;
FaceRebuildData f = VertexEditing.ExplodeVertex(vertices, kvp.Value, amount, out appended);
if (f == null)
continue;
appendFaces.Add(f);
foreach (var apv in appended)
{
// organize holes by new face so that later we can compare the winding of the new face to the hole face
// holes are sorted by key: common index value: face, vertex list
holes.AddOrAppend(apv.Key, new SimpleTuple<FaceRebuildData, List<int>>(f, apv.Value));
}
}
FaceRebuildData.Apply(appendFaces, mesh, vertices);
int removed = mesh.DeleteFaces(sorted.Keys).Length;
mesh.sharedTextures = new SharedVertex[0];
mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
// @todo don't rebuild indexes, keep 'em cached
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
lookup = mesh.sharedVertexLookup;
List<HashSet<int>> holesCommonIndexes = new List<HashSet<int>>();
// offset the indexes of holes and cull any potential holes that are less than 3 indexes (not a hole :)
foreach (KeyValuePair<int, List<SimpleTuple<FaceRebuildData, List<int>>>> hole in holes)
{
// less than 3 indexes in hole path; ain't a hole
if (hole.Value.Sum(x => x.item2.Count) < 3)
continue;
HashSet<int> holeCommon = new HashSet<int>();
foreach (SimpleTuple<FaceRebuildData, List<int>> path in hole.Value)
{
int offset = path.item1.Offset() - removed;
for (int i = 0; i < path.item2.Count; i++)
holeCommon.Add(lookup[path.item2[i] + offset]);
}
holesCommonIndexes.Add(holeCommon);
}
List<WingedEdge> modified = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face));
// now go through the holes and create faces for them
vertices = new List<Vertex>(mesh.GetVertices());
List<FaceRebuildData> holeFaces = new List<FaceRebuildData>();
foreach (HashSet<int> h in holesCommonIndexes)
{
// even if a set of hole indexes made it past the initial culling, the distinct part
// may have reduced the index count
if (h.Count < 3)
{
continue;
}
// skip sorting the path if it's just a triangle
if (h.Count < 4)
{
List<Vertex> v = new List<Vertex>(mesh.GetVertices(h.Select(x => sharedIndexes[x][0]).ToList()));
holeFaces.Add(AppendElements.FaceWithVertices(v));
}
// if this hole has > 3 indexes, it needs a tent pole triangulation, which requires sorting into the perimeter order
else
{
List<int> holePath = WingedEdge.SortCommonIndexesByAdjacency(modified, h);
if (holePath != null)
{
List<Vertex> v =
new List<Vertex>(mesh.GetVertices(holePath.Select(x => sharedIndexes[x][0]).ToList()));
holeFaces.AddRange(AppendElements.TentCapWithVertices(v));
}
}
}
FaceRebuildData.Apply(holeFaces, mesh, vertices);
mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
// go through new faces and conform hole normals
// get a hash of just the adjacent and bridge faces
// HashSet<pb_Face> adjacent = new HashSet<pb_Face>(appendFaces.Select(x => x.face));
// and also just the filled holes
HashSet<Face> newFaces = new HashSet<Face>(holeFaces.Select(x => x.face));
newFaces.UnionWith(createdFaces);
// now append filled holes to the full list of added faces
appendFaces.AddRange(holeFaces);
List<WingedEdge> allNewFaceEdges = WingedEdge.GetWingedEdges(mesh, appendFaces.Select(x => x.face));
for (int i = 0; i < allNewFaceEdges.Count && newFaces.Count > 0; i++)
{
WingedEdge wing = allNewFaceEdges[i];
if (newFaces.Contains(wing.face))
{
newFaces.Remove(wing.face);
// find first edge whose opposite face isn't a filled hole* then
// conform normal by that.
// *or is a filled hole but has already been conformed
using (var it = new WingedEdgeEnumerator(wing))
{
while (it.MoveNext())
{
var w = it.Current;
if (w.opposite != null && !newFaces.Contains(w.opposite.face))
{
w.face.submeshIndex = w.opposite.face.submeshIndex;
w.face.uv = new AutoUnwrapSettings(w.opposite.face.uv);
SurfaceTopology.ConformOppositeNormal(w.opposite);
break;
}
}
}
}
}
mesh.ToMesh();
return createdFaces;
}
static readonly int[] k_BridgeIndexesTri = new int[] { 2, 1, 0 };
static List<FaceRebuildData> GetBridgeFaces(
IList<Vertex> vertices,
WingedEdge left,
WingedEdge right,
Dictionary<int, List<SimpleTuple<FaceRebuildData, List<int>>>> holes)
{
List<FaceRebuildData> faces = new List<FaceRebuildData>();
FaceRebuildData rf = new FaceRebuildData();
EdgeLookup a = left.edge;
EdgeLookup b = right.edge;
rf.vertices = new List<Vertex>()
{
vertices[a.local.a],
vertices[a.local.b],
vertices[a.common.a == b.common.a ? b.local.a : b.local.b],
vertices[a.common.a == b.common.a ? b.local.b : b.local.a]
};
Vector3 an = Math.Normal(vertices, left.face.indexesInternal);
Vector3 bn = Math.Normal(rf.vertices, k_BridgeIndexesTri);
int[] triangles = new int[] { 2, 1, 0, 2, 3, 1 };
if (Vector3.Dot(an, bn) < 0f)
System.Array.Reverse(triangles);
rf.face = new Face(
triangles,
left.face.submeshIndex,
AutoUnwrapSettings.tile,
-1,
-1,
-1,
false);
faces.Add(rf);
holes.AddOrAppend(a.common.a, new SimpleTuple<FaceRebuildData, List<int>>(rf, new List<int>() { 0, 2 }));
holes.AddOrAppend(a.common.b, new SimpleTuple<FaceRebuildData, List<int>>(rf, new List<int>() { 1, 3 }));
return faces;
}
static void SlideEdge(IList<Vertex> vertices, WingedEdge we, float amount)
{
we.face.manualUV = true;
we.face.textureGroup = -1;
Edge slide_x = GetLeadingEdge(we, we.edge.common.a);
Edge slide_y = GetLeadingEdge(we, we.edge.common.b);
if (!slide_x.IsValid() || !slide_y.IsValid())
return;
Vertex x = (vertices[slide_x.a] - vertices[slide_x.b]);
x.Normalize();
Vertex y = (vertices[slide_y.a] - vertices[slide_y.b]);
y.Normalize();
// need the pb_Vertex value to be modified, not reassigned in this array (which += does)
vertices[we.edge.local.a].Add(x * amount);
vertices[we.edge.local.b].Add(y * amount);
}
static Edge GetLeadingEdge(WingedEdge wing, int common)
{
if (wing.previous.edge.common.a == common)
return new Edge(wing.previous.edge.local.b, wing.previous.edge.local.a);
else if (wing.previous.edge.common.b == common)
return new Edge(wing.previous.edge.local.a, wing.previous.edge.local.b);
else if (wing.next.edge.common.a == common)
return new Edge(wing.next.edge.local.b, wing.next.edge.local.a);
else if (wing.next.edge.common.b == common)
return new Edge(wing.next.edge.local.a, wing.next.edge.local.b);
return Edge.Empty;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 158fff462a95744008bbad18ab5e029e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,335 @@
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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb36fee1f817e4860a9937f064b1a192
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,625 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;
namespace UnityEngine.ProBuilder.MeshOperations
{
sealed class ConnectFaceRebuildData
{
public FaceRebuildData faceRebuildData;
public List<int> newVertexIndexes;
public ConnectFaceRebuildData(FaceRebuildData faceRebuildData, List<int> newVertexIndexes)
{
this.faceRebuildData = faceRebuildData;
this.newVertexIndexes = newVertexIndexes;
}
}
/// <summary>
/// Utility class for connecting edges, faces, and vertices.
/// </summary>
public static class ConnectElements
{
/// <summary>
/// Inserts new edges on a face starting from the center of each edge to a new vertex in the center of the face.
///
/// This is the equivalent of the [Connect Edges](../manual/Edge_Connect.html) action.
/// </summary>
/// <param name="mesh">Target mesh.</param>
/// <param name="faces">The faces to affect.</param>
/// <returns>The faces created as a result of inserting new edges.</returns>
public static Face[] Connect(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
var split = MeshValidation.EnsureFacesAreComposedOfContiguousTriangles(mesh, faces);
HashSet<Face> mask = new HashSet<Face>(faces);
if (split.Count > 0)
{
foreach (var face in split)
mask.Add(face);
}
IEnumerable<Edge> edges = mask.SelectMany(x => x.edgesInternal);
Edge[] empty;
Face[] res;
Connect(mesh, edges, out res, out empty, true, false, mask);
return res;
}
/// <summary>
/// Inserts new edges in order to connect a set of edges. If a face contains more than two edges to connect, this method inserts
/// a new vertex at the center of the face and connects each edge to the center point.
///
/// This is the equivalent of the [Connect Edges](../manual/Edge_Connect.html) action.
/// </summary>
/// <param name="mesh">The target mesh.</param>
/// <param name="edges">A list of edges to connect.</param>
/// <returns>The faces and edges created as a result of inserting new edges.</returns>
public static SimpleTuple<Face[], Edge[]> Connect(this ProBuilderMesh mesh, IEnumerable<Edge> edges)
{
Edge[] empty;
Face[] faces;
Connect(mesh, edges, out faces, out empty, true, true);
return new SimpleTuple<Face[], Edge[]>(faces, empty);
}
/// <summary>
/// Inserts edges connecting a list of indices.
///
/// This is the equivalent of the [Connect Edges](../manual/Edge_Connect.html) action.
/// </summary>
/// <param name="mesh">The target mesh.</param>
/// <param name="indexes">A list of indices (corresponding to the <see cref="ProBuilderMesh.positions"/> array) to connect to the new edges.</param>
/// <returns>A new array containing the indices of the newly connected positions. This method rebuilds the `indexes` array because it might modify the ordering of the original array.</returns>
public static int[] Connect(this ProBuilderMesh mesh, IList<int> indexes)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (indexes == null)
throw new ArgumentNullException("indexes");
int sharedIndexOffset = mesh.sharedVerticesInternal.Length;
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
HashSet<int> distinct = new HashSet<int>(indexes.Select(x => lookup[x]));
HashSet<int> affected = new HashSet<int>();
foreach (int i in distinct)
affected.UnionWith(mesh.sharedVerticesInternal[i].arrayInternal);
Dictionary<Face, List<int>> splits = new Dictionary<Face, List<int>>();
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
foreach (Face face in mesh.facesInternal)
{
int[] f = face.distinctIndexesInternal;
for (int i = 0; i < f.Length; i++)
{
if (affected.Contains(f[i]))
splits.AddOrAppend(face, f[i]);
}
}
List<ConnectFaceRebuildData> appendFaces = new List<ConnectFaceRebuildData>();
List<Face> successfulSplits = new List<Face>();
HashSet<int> usedTextureGroups = new HashSet<int>(mesh.facesInternal.Select(x => x.textureGroup));
int newTextureGroupIndex = 1;
foreach (KeyValuePair<Face, List<int>> split in splits)
{
Face face = split.Key;
List<ConnectFaceRebuildData> res = split.Value.Count == 2 ?
ConnectIndexesPerFace(face, split.Value[0], split.Value[1], vertices, lookup) :
ConnectIndexesPerFace(face, split.Value, vertices, lookup, sharedIndexOffset++);
if (res == null)
continue;
if (face.textureGroup < 0)
{
while (usedTextureGroups.Contains(newTextureGroupIndex))
newTextureGroupIndex++;
usedTextureGroups.Add(newTextureGroupIndex);
}
foreach (ConnectFaceRebuildData c in res)
{
c.faceRebuildData.face.textureGroup = face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup;
c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv);
c.faceRebuildData.face.smoothingGroup = face.smoothingGroup;
c.faceRebuildData.face.manualUV = face.manualUV;
c.faceRebuildData.face.submeshIndex = face.submeshIndex;
}
successfulSplits.Add(face);
appendFaces.AddRange(res);
}
FaceRebuildData.Apply(appendFaces.Select(x => x.faceRebuildData), mesh, vertices, null);
int removedVertexCount = mesh.DeleteFaces(successfulSplits).Length;
lookup = mesh.sharedVertexLookup;
HashSet<int> newVertexIndexes = new HashSet<int>();
for (int i = 0; i < appendFaces.Count; i++)
for (int n = 0; n < appendFaces[i].newVertexIndexes.Count; n++)
newVertexIndexes.Add(lookup[appendFaces[i].newVertexIndexes[n] + (appendFaces[i].faceRebuildData.Offset() - removedVertexCount)]);
mesh.ToMesh();
return newVertexIndexes.Select(x => mesh.sharedVerticesInternal[x][0]).ToArray();
}
/// <summary>
/// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask.
/// </summary>
/// <param name="mesh"></param>
/// <param name="edges"></param>
/// <param name="addedFaces"></param>
/// <param name="connections"></param>
/// <param name="returnFaces"></param>
/// <param name="returnEdges"></param>
/// <param name="faceMask"></param>
/// <returns></returns>
internal static ActionResult Connect(
this ProBuilderMesh mesh,
IEnumerable<Edge> edges,
out Face[] addedFaces,
out Edge[] connections,
bool returnFaces = false,
bool returnEdges = false,
HashSet<Face> faceMask = null)
{
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
Dictionary<int, int> lookupUV = mesh.sharedTextureLookup;
HashSet<EdgeLookup> distinctEdges = new HashSet<EdgeLookup>(EdgeLookup.GetEdgeLookup(edges, lookup));
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
// map each edge to a face so that we have a list of all touched faces with their to-be-subdivided edges
Dictionary<Face, List<WingedEdge>> touched = new Dictionary<Face, List<WingedEdge>>();
foreach (WingedEdge wing in wings)
{
if (distinctEdges.Contains(wing.edge))
{
List<WingedEdge> faceEdges;
if (touched.TryGetValue(wing.face, out faceEdges))
faceEdges.Add(wing);
else
touched.Add(wing.face, new List<WingedEdge>() { wing });
}
}
Dictionary<Face, List<WingedEdge>> affected = new Dictionary<Face, List<WingedEdge>>();
// weed out edges that won't actually connect to other edges (if you don't play ya' can't stay)
foreach (KeyValuePair<Face, List<WingedEdge>> kvp in touched)
{
if (kvp.Value.Count <= 1)
{
WingedEdge opp = kvp.Value[0].opposite;
if (opp == null)
continue;
List<WingedEdge> opp_list;
if (!touched.TryGetValue(opp.face, out opp_list))
continue;
if (opp_list.Count <= 1)
continue;
}
affected.Add(kvp.Key, kvp.Value);
}
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
List<ConnectFaceRebuildData> results = new List<ConnectFaceRebuildData>();
// just the faces that where connected with > 1 edge
List<Face> connectedFaces = new List<Face>();
HashSet<int> usedTextureGroups = new HashSet<int>(mesh.facesInternal.Select(x => x.textureGroup));
int newTextureGroupIndex = 1;
// do the splits
foreach (KeyValuePair<Face, List<WingedEdge>> split in affected)
{
Face face = split.Key;
List<WingedEdge> targetEdges = split.Value;
int inserts = targetEdges.Count;
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
if (inserts == 1 || (faceMask != null && !faceMask.Contains(face)))
{
ConnectFaceRebuildData c;
if (InsertVertices(face, targetEdges, vertices, out c))
{
Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
c.faceRebuildData.face.Reverse();
results.Add(c);
}
}
else if (inserts > 1)
{
List<ConnectFaceRebuildData> res = inserts == 2 ?
ConnectEdgesInFace(face, targetEdges[0], targetEdges[1], vertices) :
ConnectEdgesInFace(face, targetEdges, vertices);
if (face.textureGroup < 0)
{
while (usedTextureGroups.Contains(newTextureGroupIndex))
newTextureGroupIndex++;
usedTextureGroups.Add(newTextureGroupIndex);
}
if (res == null)
{
connections = null;
addedFaces = null;
return new ActionResult(ActionResult.Status.Failure, "Unable to connect faces");
}
else
{
foreach (ConnectFaceRebuildData c in res)
{
connectedFaces.Add(c.faceRebuildData.face);
Vector3 fn = Math.Normal(c.faceRebuildData.vertices,
c.faceRebuildData.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
c.faceRebuildData.face.Reverse();
c.faceRebuildData.face.textureGroup =
face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup;
c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv);
c.faceRebuildData.face.submeshIndex = face.submeshIndex;
c.faceRebuildData.face.smoothingGroup = face.smoothingGroup;
c.faceRebuildData.face.manualUV = face.manualUV;
}
results.AddRange(res);
}
}
}
FaceRebuildData.Apply(results.Select(x => x.faceRebuildData), mesh, vertices, null);
mesh.sharedTextures = new SharedVertex[0];
int removedVertexCount = mesh.DeleteFaces(affected.Keys).Length;
mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
mesh.ToMesh();
// figure out where the new edges where inserted
if (returnEdges)
{
// offset the newVertexIndexes by whatever the FaceRebuildData did so we can search for the new edges by index
var appended = new HashSet<int>();
for (int n = 0; n < results.Count; n++)
for (int i = 0; i < results[n].newVertexIndexes.Count; i++)
appended.Add((results[n].newVertexIndexes[i] + results[n].faceRebuildData.Offset()) - removedVertexCount);
Dictionary<int, int> lup = mesh.sharedVertexLookup;
IEnumerable<Edge> newEdges = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b));
IEnumerable<EdgeLookup> distNewEdges = EdgeLookup.GetEdgeLookup(newEdges, lup);
connections = distNewEdges.Distinct().Select(x => x.local).ToArray();
}
else
{
connections = null;
}
if (returnFaces)
addedFaces = connectedFaces.ToArray();
else
addedFaces = null;
return new ActionResult(ActionResult.Status.Success, string.Format("Connected {0} Edges", results.Count / 2));
}
/// <summary>
/// Accepts a face and set of edges to split on.
/// </summary>
/// <param name="face"></param>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="vertices"></param>
/// <returns></returns>
static List<ConnectFaceRebuildData> ConnectEdgesInFace(
Face face,
WingedEdge a,
WingedEdge b,
List<Vertex> vertices)
{
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
List<Vertex>[] n_vertices = new List<Vertex>[2]
{
new List<Vertex>(),
new List<Vertex>()
};
List<int>[] n_indexes = new List<int>[2]
{
new List<int>(),
new List<int>()
};
int index = 0;
// creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices[index % 2].Add(vertices[perimeter[i].a]);
if (perimeter[i].Equals(a.edge.local) || perimeter[i].Equals(b.edge.local))
{
Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);
n_indexes[index % 2].Add(n_vertices[index % 2].Count);
n_vertices[index % 2].Add(mix);
index++;
n_indexes[index % 2].Add(n_vertices[index % 2].Count);
n_vertices[index % 2].Add(mix);
}
}
List<ConnectFaceRebuildData> faces = new List<ConnectFaceRebuildData>();
for (int i = 0; i < n_vertices.Length; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
if(f != null)
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
/// <summary>
/// Insert a new vertex at the center of a face and connect the center of all edges to it.
/// </summary>
/// <param name="face"></param>
/// <param name="edges"></param>
/// <param name="vertices"></param>
/// <returns></returns>
static List<ConnectFaceRebuildData> ConnectEdgesInFace(
Face face,
List<WingedEdge> edges,
List<Vertex> vertices)
{
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
int splitCount = edges.Count;
Vertex centroid = Vertex.Average(vertices, face.distinctIndexesInternal);
List<List<Vertex>> n_vertices = ArrayUtility.Fill<List<Vertex>>(x => { return new List<Vertex>(); }, splitCount);
List<List<int>> n_indexes = ArrayUtility.Fill<List<int>>(x => { return new List<int>(); }, splitCount);
HashSet<Edge> edgesToSplit = new HashSet<Edge>(edges.Select(x => x.edge.local));
int index = 0;
// creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices[index % splitCount].Add(vertices[perimeter[i].a]);
if (edgesToSplit.Contains(perimeter[i]))
{
Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);
// split current poly line
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(mix);
// add the centroid vertex
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(centroid);
// advance the poly line index
index = (index + 1) % splitCount;
// then add the edge center vertex and move on
n_vertices[index].Add(mix);
}
}
List<ConnectFaceRebuildData> faces = new List<ConnectFaceRebuildData>();
for (int i = 0; i < n_vertices.Count; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
if (f == null)
{
faces.Clear();
return null;
}
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
static bool InsertVertices(Face face, List<WingedEdge> edges, List<Vertex> vertices, out ConnectFaceRebuildData data)
{
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
List<Vertex> n_vertices = new List<Vertex>();
List<int> newVertexIndexes = new List<int>();
HashSet<Edge> affected = new HashSet<Edge>(edges.Select(x => x.edge.local));
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices.Add(vertices[perimeter[i].a]);
if (affected.Contains(perimeter[i]))
{
newVertexIndexes.Add(n_vertices.Count);
n_vertices.Add(Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f));
}
}
FaceRebuildData res = AppendElements.FaceWithVertices(n_vertices, false);
if (res != null)
{
res.face.textureGroup = face.textureGroup;
res.face.uv = new AutoUnwrapSettings(face.uv);
res.face.smoothingGroup = face.smoothingGroup;
res.face.manualUV = face.manualUV;
res.face.submeshIndex = face.submeshIndex;
data = new ConnectFaceRebuildData(res, newVertexIndexes);
return true;
}
data = null;
return false;
}
static List<ConnectFaceRebuildData> ConnectIndexesPerFace(
Face face,
int a,
int b,
List<Vertex> vertices,
Dictionary<int, int> lookup)
{
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
List<Vertex>[] n_vertices = new List<Vertex>[] {
new List<Vertex>(),
new List<Vertex>()
};
List<int>[] n_sharedIndexes = new List<int>[] {
new List<int>(),
new List<int>()
};
List<int>[] n_indexes = new List<int>[] {
new List<int>(),
new List<int>()
};
int index = 0;
for (int i = 0; i < perimeter.Count; i++)
{
// trying to connect two vertices that are already connected
if (perimeter[i].Contains(a) && perimeter[i].Contains(b))
return null;
int cur = perimeter[i].a;
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
if (cur == a || cur == b)
{
index = (index + 1) % 2;
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
}
}
List<ConnectFaceRebuildData> faces = new List<ConnectFaceRebuildData>();
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
for (int i = 0; i < n_vertices.Length; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
f.sharedIndexes = n_sharedIndexes[i];
Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
f.face.Reverse();
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
static List<ConnectFaceRebuildData> ConnectIndexesPerFace(
Face face,
List<int> indexes,
List<Vertex> vertices,
Dictionary<int, int> lookup,
int sharedIndexOffset)
{
if (indexes.Count < 3)
return null;
List<Edge> perimeter = WingedEdge.SortEdgesByAdjacency(face);
int splitCount = indexes.Count;
List<List<Vertex>> n_vertices = ArrayUtility.Fill<List<Vertex>>(x => { return new List<Vertex>(); }, splitCount);
List<List<int>> n_sharedIndexes = ArrayUtility.Fill<List<int>>(x => { return new List<int>(); }, splitCount);
List<List<int>> n_indexes = ArrayUtility.Fill<List<int>>(x => { return new List<int>(); }, splitCount);
Vertex center = Vertex.Average(vertices, indexes);
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
int index = 0;
for (int i = 0; i < perimeter.Count; i++)
{
int cur = perimeter[i].a;
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
if (indexes.Contains(cur))
{
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(center);
n_sharedIndexes[index].Add(sharedIndexOffset);
index = (index + 1) % splitCount;
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
}
}
List<ConnectFaceRebuildData> faces = new List<ConnectFaceRebuildData>();
for (int i = 0; i < n_vertices.Count; i++)
{
if (n_vertices[i].Count < 3)
continue;
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
f.sharedIndexes = n_sharedIndexes[i];
Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
f.face.Reverse();
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 081df46fba1da4194b5f8c7fa73446e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,166 @@
using UnityEngine;
using System.Collections.Generic;
using System;
using System.ComponentModel;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides functions for removing vertices and triangles from a mesh.
/// </summary>
public static class DeleteElements
{
/// <summary>
/// Deletes the vertices from the specified index array and rebuilds the <see cref="ProBuilderMesh.sharedVertices"/> array.
/// </summary>
/// <remarks>This function does not retriangulate the mesh. This means that you are responsible for ensuring that the indexes
/// deleted by this function are not referenced by any triangles.</remarks>
/// <param name="mesh">The source mesh.</param>
/// <param name="distinctIndexes">A list of vertices to delete. Note that this must not contain duplicates.</param>
public static void DeleteVertices(this ProBuilderMesh mesh, IEnumerable<int> distinctIndexes)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (distinctIndexes == null || !distinctIndexes.Any())
return;
Vertex[] vertices = mesh.GetVertices();
int originalVertexCount = vertices.Length;
int[] offset = new int[originalVertexCount];
List<int> sorted = new List<int>(distinctIndexes);
sorted.Sort();
vertices = vertices.SortedRemoveAt(sorted);
// Add 1 because NearestIndexPriorToValue is 0 indexed.
for (int i = 0; i < originalVertexCount; i++)
offset[i] = ArrayUtility.NearestIndexPriorToValue(sorted, i) + 1;
foreach (Face face in mesh.facesInternal)
{
int[] indexes = face.indexesInternal;
for (int i = 0; i < indexes.Length; i++)
indexes[i] -= offset[indexes[i]];
face.InvalidateCache();
}
// remove from sharedIndexes & shift to account for deletions
var common = mesh.sharedVertexLookup.Where(x => sorted.BinarySearch(x.Key) < 0).Select(y => new KeyValuePair<int, int>(y.Key - offset[y.Key], y.Value));
var commonUV = mesh.sharedTextureLookup.Where(x => sorted.BinarySearch(x.Key) < 0).Select(y => new KeyValuePair<int, int>(y.Key - offset[y.Key], y.Value));
mesh.SetVertices(vertices);
mesh.SetSharedVertices(common);
mesh.SetSharedTextures(commonUV);
}
/// <summary>
/// Removes a face from a mesh.
///
/// This is the equivalent of the [Delete Faces](../manual/Face_Delete.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="face">The face to remove.</param>
/// <returns>An array of vertex indices that ProBuilder deleted when it removed the specified face.</returns>
public static int[] DeleteFace(this ProBuilderMesh mesh, Face face)
{
return DeleteFaces(mesh, new Face[] { face });
}
/// <summary>
/// Deletes a collection of faces from a mesh.
///
/// This is the equivalent of the [Delete Faces](../manual/Face_Delete.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to remove.</param>
/// <returns>An array of vertex indices that ProBuilder deleted when it removed the specified faces.</returns>
public static int[] DeleteFaces(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
return DeleteFaces(mesh, faces.Select(x => System.Array.IndexOf(mesh.facesInternal, x)).ToList());
}
/// <summary>
/// Deletes a list of faces from a mesh.
///
/// This is the equivalent of the [Delete Faces](../manual/Face_Delete.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faceIndexes">The indices of faces to remove (corresponding to the <see cref="ProBuilderMesh.faces"/> collection.</param>
/// <returns>An array of vertex indices that ProBuilder deleted when it removed the specified faces.</returns>
public static int[] DeleteFaces(this ProBuilderMesh mesh, IList<int> faceIndexes)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faceIndexes == null)
throw new ArgumentNullException("faceIndexes");
Face[] faces = new Face[faceIndexes.Count];
for (int i = 0; i < faces.Length; i++)
faces[i] = mesh.facesInternal[faceIndexes[i]];
List<int> indexesToRemove = faces.SelectMany(x => x.distinctIndexesInternal).Distinct().ToList();
indexesToRemove.Sort();
int vertexCount = mesh.positionsInternal.Length;
Face[] nFaces = mesh.facesInternal.RemoveAt(faceIndexes);
var vertices = mesh.GetVertices().SortedRemoveAt(indexesToRemove);
Dictionary<int, int> shiftmap = new Dictionary<int, int>();
for (var i = 0; i < vertexCount; i++)
shiftmap.Add(i, ArrayUtility.NearestIndexPriorToValue<int>(indexesToRemove, i) + 1);
// shift all other face indexes down to account for moved vertex positions
for (var i = 0; i < nFaces.Length; i++)
{
int[] tris = nFaces[i].indexesInternal;
for (var n = 0; n < tris.Length; n++)
tris[n] -= shiftmap[tris[n]];
nFaces[i].indexesInternal = tris;
}
mesh.SetVertices(vertices);
mesh.sharedVerticesInternal = SharedVertex.SortedRemoveAndShift(mesh.sharedVertexLookup, indexesToRemove);
mesh.sharedTextures = SharedVertex.SortedRemoveAndShift(mesh.sharedTextureLookup, indexesToRemove);
mesh.facesInternal = nFaces;
int[] array = indexesToRemove.ToArray();
return array;
}
/// <summary>Obsolete. Use `MeshValidation.RemoveDegenerateTriangles` instead.</summary>
/// <param name="mesh">The source mesh.</param>
/// <returns>The list of removed triangles</returns>
[Obsolete("Use MeshValidation.RemoveDegenerateTriangles")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static int[] RemoveDegenerateTriangles(this ProBuilderMesh mesh)
{
List<int> removed = new List<int>();
MeshValidation.RemoveDegenerateTriangles(mesh, removed);
return removed.ToArray();
}
/// <summary>Obsolete. Use `MeshValidation.RemoveUnusedVertices` instead.</summary>
/// <param name="mesh">The source mesh.</param>
/// <returns>The list of removed vertices</returns>
[Obsolete("Use MeshValidation.RemoveUnusedVertices")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static int[] RemoveUnusedVertices(this ProBuilderMesh mesh)
{
List<int> removed = new List<int>();
MeshValidation.RemoveUnusedVertices(mesh, removed);
return removed.ToArray();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b300db76e48084b5680ab36214d3bea3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,952 @@
using System.Linq;
using System;
using System.Collections.Generic;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides helper functions for working with selected faces, edges, and vertices.
/// </summary>
public static class ElementSelection
{
const int k_MaxHoleIterations = 2048;
/// <summary>
/// Creates a list of <see cref="Face"/> objects where each face is connected to a specific <see cref="Edge"/> in the ProBuilderMesh.
/// </summary>
/// <param name="mesh">The ProBuilder mesh containing the edge.</param>
/// <param name="edge">The edge to evaluate.</param>
/// <param name="neighborFaces">Specify an empty list of faces for the method to fill.</param>
public static void GetNeighborFaces(ProBuilderMesh mesh, Edge edge, List<Face> neighborFaces)
{
var lookup = mesh.sharedVertexLookup;
Edge uni = new Edge(lookup[edge.a], lookup[edge.b]);
Edge e = new Edge(0, 0);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
Edge[] edges = mesh.facesInternal[i].edgesInternal;
for (int n = 0; n < edges.Length; n++)
{
e.a = edges[n].a;
e.b = edges[n].b;
if ((uni.a == lookup[e.a] && uni.b == lookup[e.b]) ||
(uni.a == lookup[e.b] && uni.b == lookup[e.a]))
{
neighborFaces.Add(mesh.facesInternal[i]);
break;
}
}
}
}
/// <summary>
/// Returns a list of <![CDATA[SimpleTuple<Face, Edge>]]> where each face is connected to the passed edge.
/// </summary>
/// <param name="mesh"></param>
/// <param name="edge"></param>
/// <returns></returns>
internal static List<SimpleTuple<Face, Edge>> GetNeighborFaces(ProBuilderMesh mesh, Edge edge)
{
List<SimpleTuple<Face, Edge>> faces = new List<SimpleTuple<Face, Edge>>();
var lookup = mesh.sharedVertexLookup;
Edge uni = new Edge(lookup[edge.a], lookup[edge.b]);
Edge e = new Edge(0, 0);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
Edge[] edges = mesh.facesInternal[i].edgesInternal;
for (int n = 0; n < edges.Length; n++)
{
e.a = edges[n].a;
e.b = edges[n].b;
if ((uni.a == lookup[e.a] && uni.b == lookup[e.b]) ||
(uni.a == lookup[e.b] && uni.b == lookup[e.a]))
{
faces.Add(new SimpleTuple<Face, Edge>(mesh.facesInternal[i], edges[n]));
break;
}
}
}
return faces;
}
/// <summary>
/// Gets all faces connected to each index taking into account shared vertices.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
/// <returns></returns>
internal static List<Face> GetNeighborFaces(ProBuilderMesh mesh, int[] indexes)
{
var lookup = mesh.sharedVertexLookup;
List<Face> neighboring = new List<Face>();
HashSet<int> shared = new HashSet<int>();
foreach (int tri in indexes)
shared.Add(lookup[tri]);
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
int[] dist = mesh.facesInternal[i].distinctIndexesInternal;
for (int n = 0; n < dist.Length; n++)
{
if (shared.Contains(lookup[dist[n]]))
{
neighboring.Add(mesh.facesInternal[i]);
break;
}
}
}
return neighboring;
}
/// <summary>
/// Returns a unique array of Edges connected to the passed vertex indexes.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
/// <returns></returns>
internal static Edge[] GetConnectedEdges(ProBuilderMesh mesh, int[] indexes)
{
var lookup = mesh.sharedVertexLookup;
List<Edge> connectedEdges = new List<Edge>();
HashSet<int> shared = new HashSet<int>();
for (int i = 0; i < indexes.Length; i++)
shared.Add(lookup[indexes[i]]);
HashSet<Edge> used = new HashSet<Edge>();
Edge uni = new Edge(0, 0);
foreach (var face in mesh.facesInternal)
{
foreach (var edge in face.edges)
{
Edge key = new Edge(lookup[edge.a], lookup[edge.b]);
if (shared.Contains(key.a) || shared.Contains(key.b) && !used.Contains(uni))
{
connectedEdges.Add(edge);
used.Add(key);
}
}
}
return connectedEdges.ToArray();
}
/// <summary>
/// Returns all the edges that are on the perimeter of this set of selected faces.
/// </summary>
/// <param name="mesh">The mesh containing the faces.</param>
/// <param name="faces">The faces to search for perimeter edge paths.</param>
/// <returns>A list of the edges on the perimeter of each group of adjacent faces.</returns>
public static IEnumerable<Edge> GetPerimeterEdges(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
List<Edge> faceEdges = faces.SelectMany(x => x.edgesInternal).ToList(); // actual edges
var sharedIndexesDictionary = mesh.sharedVertexLookup;
int edgeCount = faceEdges.Count;
// translate all face edges to universal edges
Dictionary<Edge, List<Edge>> dup = new Dictionary<Edge, List<Edge>>();
List<Edge> list;
for (int i = 0; i < edgeCount; i++)
{
Edge uni = new Edge(sharedIndexesDictionary[faceEdges[i].a], sharedIndexesDictionary[faceEdges[i].b]);
if (dup.TryGetValue(uni, out list))
list.Add(faceEdges[i]);
else
dup.Add(uni, new List<Edge>() { faceEdges[i] });
}
return dup.Where(x => x.Value.Count < 2).Select(x => x.Value[0]);
}
/// <summary>
/// Returns the indexes of perimeter edges in a given element group.
/// </summary>
/// <param name="mesh"></param>
/// <param name="edges"></param>
/// <returns></returns>
internal static int[] GetPerimeterEdges(ProBuilderMesh mesh, IList<Edge> edges)
{
int edgeCount = edges != null ? edges.Count : 0;
// Figure out how many connections each edge has to other edges in the selection
var universal = mesh.GetSharedVertexHandleEdges(edges).ToArray();
int[] connections = new int[universal.Length];
for (int i = 0; i < universal.Length - 1; i++)
{
for (int n = i + 1; n < universal.Length; n++)
{
if (universal[i].a == universal[n].a || universal[i].a == universal[n].b ||
universal[i].b == universal[n].a || universal[i].b == universal[n].b)
{
connections[i]++;
connections[n]++;
}
}
}
int min = Math.Min(connections);
List<int> perimeter = new List<int>();
for (int i = 0; i < connections.Length; i++)
{
if (connections[i] <= min)
perimeter.Add(i);
}
return perimeter.Count != edgeCount ? perimeter.ToArray() : new int[] {};
}
/// <summary>
/// Returns an array of faces where each face has at least one non-shared edge.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <returns></returns>
internal static IEnumerable<Face> GetPerimeterFaces(ProBuilderMesh mesh, IEnumerable<Face> faces)
{
var lookup = mesh.sharedVertexLookup;
Dictionary<Edge, List<Face>> sharedEdges = new Dictionary<Edge, List<Face>>();
/**
* To be considered a perimeter face, at least one edge must not share
* any boundary with another face.
*/
foreach (Face face in faces)
{
foreach (Edge e in face.edgesInternal)
{
Edge edge = new Edge(lookup[e.a], lookup[e.b]);
if (sharedEdges.ContainsKey(edge))
sharedEdges[edge].Add(face);
else
sharedEdges.Add(edge, new List<Face>() { face });
}
}
return sharedEdges.Where(x => x.Value.Count < 2).Select(x => x.Value[0]).Distinct();
}
internal static int[] GetPerimeterVertices(ProBuilderMesh mesh, int[] indexes, Edge[] universal_edges_all)
{
int len = indexes.Length;
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
int[] universal = new int[len];
for (int i = 0; i < len; i++)
universal[i] = mesh.GetSharedVertexHandle(indexes[i]);
int[] connections = new int[indexes.Length];
for (int i = 0; i < indexes.Length - 1; i++)
{
for (int n = i + 1; n < indexes.Length; n++)
{
if (universal_edges_all.Contains(universal[i], universal[n]))
{
connections[i]++;
connections[n]++;
}
}
}
int min = Math.Min(connections);
List<int> perimeter = new List<int>();
for (int i = 0; i < len; i++)
{
if (connections[i] <= min)
perimeter.Add(i);
}
return perimeter.Count < len ? perimeter.ToArray() : new int[] {};
}
static WingedEdge EdgeRingNext(WingedEdge edge)
{
if (edge == null)
return null;
WingedEdge next = edge.next, prev = edge.previous;
int i = 0;
while (next != prev && next != edge)
{
next = next.next;
if (next == prev)
return null;
prev = prev.previous;
i++;
}
if (i % 2 == 0 || next == edge)
next = null;
return next;
}
/// <summary>
/// Iterates through face edges and builds a list using the opposite edge.
/// </summary>
/// <param name="pb"></param>
/// <param name="edges"></param>
/// <returns></returns>
internal static IEnumerable<Edge> GetEdgeRing(ProBuilderMesh pb, IEnumerable<Edge> edges)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(pb);
List<EdgeLookup> edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();
edgeLookup = edgeLookup.Distinct().ToList();
Dictionary<Edge, WingedEdge> wings_dic = new Dictionary<Edge, WingedEdge>();
for (int i = 0; i < wings.Count; i++)
if (!wings_dic.ContainsKey(wings[i].edge.common))
wings_dic.Add(wings[i].edge.common, wings[i]);
HashSet<EdgeLookup> used = new HashSet<EdgeLookup>();
for (int i = 0, c = edgeLookup.Count; i < c; i++)
{
WingedEdge we;
if (!wings_dic.TryGetValue(edgeLookup[i].common, out we) || used.Contains(we.edge))
continue;
WingedEdge cur = we;
while (cur != null)
{
if (!used.Add(cur.edge)) break;
cur = EdgeRingNext(cur);
if (cur != null && cur.opposite != null) cur = cur.opposite;
}
cur = EdgeRingNext(we.opposite);
if (cur != null && cur.opposite != null) cur = cur.opposite;
// run in both directions
while (cur != null)
{
if (!used.Add(cur.edge)) break;
cur = EdgeRingNext(cur);
if (cur != null && cur.opposite != null) cur = cur.opposite;
}
}
return used.Select(x => x.local);
}
/// <summary>
/// Iterates through face edges and builds a list using the opposite edge, iteratively.
/// </summary>
/// <param name="pb">The probuilder mesh</param>
/// <param name="edges">The edges already selected</param>
/// <returns>The new selected edges</returns>
internal static IEnumerable<Edge> GetEdgeRingIterative(ProBuilderMesh pb, IEnumerable<Edge> edges)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(pb);
List<EdgeLookup> edgeLookup = EdgeLookup.GetEdgeLookup(edges, pb.sharedVertexLookup).ToList();
edgeLookup = edgeLookup.Distinct().ToList();
Dictionary<Edge, WingedEdge> wings_dic = new Dictionary<Edge, WingedEdge>();
for (int i = 0; i < wings.Count; i++)
if (!wings_dic.ContainsKey(wings[i].edge.common))
wings_dic.Add(wings[i].edge.common, wings[i]);
HashSet<EdgeLookup> used = new HashSet<EdgeLookup>();
for (int i = 0, c = edgeLookup.Count; i < c; i++)
{
WingedEdge we;
if (!wings_dic.TryGetValue(edgeLookup[i].common, out we))
continue;
WingedEdge cur = we;
if (!used.Contains(cur.edge))
used.Add(cur.edge);
var next = EdgeRingNext(cur);
if (next != null && next.opposite != null && !used.Contains(next.edge))
used.Add(next.edge);
var prev = EdgeRingNext(cur.opposite);
if (prev != null && prev.opposite != null && !used.Contains(prev.edge))
used.Add(prev.edge);
}
return used.Select(x => x.local);
}
/// <summary>
/// Attempts to find edges along an Edge loop.
///
/// http://wiki.blender.org/index.php/Doc:2.4/Manual/Modeling/Meshes/Selecting/Edges says:
/// First check to see if the selected element connects to only 3 other edges.
/// If the edge in question has already been added to the list, the selection ends.
/// Of the 3 edges that connect to the current edge, the ones that share a face with the current edge are eliminated
/// and the remaining edge is added to the list and is made the current edge.
/// </summary>
/// <param name="mesh"></param>
/// <param name="edges"></param>
/// <param name="loop"></param>
/// <returns></returns>
internal static bool GetEdgeLoop(ProBuilderMesh mesh, IEnumerable<Edge> edges, out Edge[] loop)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
IEnumerable<EdgeLookup> m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup);
HashSet<EdgeLookup> sources = new HashSet<EdgeLookup>(m_edgeLookup);
HashSet<EdgeLookup> used = new HashSet<EdgeLookup>();
for (int i = 0; i < wings.Count; i++)
{
if (used.Contains(wings[i].edge) || !sources.Contains(wings[i].edge))
continue;
bool completeLoop = GetEdgeLoopInternal(wings[i], wings[i].edge.common.b, used);
// loop didn't close
if (!completeLoop)
GetEdgeLoopInternal(wings[i], wings[i].edge.common.a, used);
}
loop = used.Select(x => x.local).ToArray();
return true;
}
/// <summary>
/// Attempts to find edges along an Edge loop in an iterative way
///
/// Adds two edges to the selection, one at each extremity
/// </summary>
/// <param name="mesh"></param>
/// <param name="lastEdgesAdded"></param>
/// <param name="loop"></param>
/// <returns></returns>
internal static bool GetEdgeLoopIterative(ProBuilderMesh mesh, IEnumerable<Edge> edges, out Edge[] loop)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
IEnumerable<EdgeLookup> m_edgeLookup = EdgeLookup.GetEdgeLookup(edges, mesh.sharedVertexLookup);
HashSet<EdgeLookup> sources = new HashSet<EdgeLookup>(m_edgeLookup);
HashSet<EdgeLookup> used = new HashSet<EdgeLookup>();
for (int i = 0; i < wings.Count; i++)
{
if (!sources.Contains(wings[i].edge))
continue;
GetEdgeLoopInternalIterative(wings[i], wings[i].edge.common, used);
}
loop = used.Select(x => x.local).ToArray();
return true;
}
static bool GetEdgeLoopInternal(WingedEdge start, int startIndex, HashSet<EdgeLookup> used)
{
int ind = startIndex;
WingedEdge cur = start;
do
{
used.Add(cur.edge);
List<WingedEdge> spokes = GetSpokes(cur, ind, true).DistinctBy(x => x.edge.common).ToList();
cur = null;
if (spokes.Count == 4)
{
cur = spokes[2];
ind = cur.edge.common.a == ind ? cur.edge.common.b : cur.edge.common.a;
}
}
while (cur != null && !used.Contains(cur.edge));
return cur != null;
}
static void GetEdgeLoopInternalIterative(WingedEdge start, Edge edge, HashSet<EdgeLookup> used)
{
int indA = edge.a;
int indB = edge.b;
WingedEdge cur = start;
if (!used.Contains(cur.edge))
used.Add(cur.edge);
List<WingedEdge> spokesA = GetSpokes(cur, indA, true).DistinctBy(x => x.edge.common).ToList();
List<WingedEdge> spokesB = GetSpokes(cur, indB, true).DistinctBy(x => x.edge.common).ToList();
if (spokesA.Count == 4)
{
cur = spokesA[2];
if (!used.Contains(cur.edge))
used.Add(cur.edge);
}
if (spokesB.Count == 4)
{
cur = spokesB[2];
if (!used.Contains(cur.edge))
used.Add(cur.edge);
}
}
static WingedEdge NextSpoke(WingedEdge wing, int pivot, bool opp)
{
if (opp)
return wing.opposite;
if (wing.next.edge.common.Contains(pivot))
return wing.next;
if (wing.previous.edge.common.Contains(pivot))
return wing.previous;
return null;
}
/// <summary>
/// Return all edges connected to @wing with @sharedIndex as the pivot point. The first entry in the list is always the queried wing.
/// </summary>
/// <param name="wing"></param>
/// <param name="sharedIndex"></param>
/// <param name="allowHoles"></param>
/// <returns></returns>
internal static List<WingedEdge> GetSpokes(WingedEdge wing, int sharedIndex, bool allowHoles = false)
{
List<WingedEdge> spokes = new List<WingedEdge>();
WingedEdge cur = wing;
bool opp = false;
do
{
// https://fogbugz.unity3d.com/f/cases/1241105/
if (spokes.Contains(cur))
return spokes;
spokes.Add(cur);
cur = NextSpoke(cur, sharedIndex, opp);
opp = !opp;
// we've looped around as far as it's gon' go
if (cur != null && cur.edge.common.Equals(wing.edge.common))
return spokes;
}
while (cur != null);
if (!allowHoles)
return null;
// if the first loop didn't come back, that means there was a hole in the geo
// do the loop again using the opposite wing
cur = wing.opposite;
opp = false;
List<WingedEdge> fragment = new List<WingedEdge>();
// if mesh is non-manifold this situation could arise
while (cur != null && !cur.edge.common.Equals(wing.edge.common))
{
fragment.Add(cur);
cur = NextSpoke(cur, sharedIndex, opp);
opp = !opp;
}
fragment.Reverse();
spokes.AddRange(fragment);
return spokes;
}
/// <summary>
/// Expand the selected faces to include any face touching the perimeter edges.
/// This corresponds to the [Grow Selection](../manual/Selection_Grow.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to grow out from.</param>
/// <param name="maxAngleDiff">Specify the maximum difference (in degrees) between the normals on the selected face and those on the perimeter face.</param>
/// <returns>The original faces selection, plus any new faces added as a result of the grow operation.</returns>
public static HashSet<Face> GrowSelection(ProBuilderMesh mesh, IEnumerable<Face> faces, float maxAngleDiff = -1f)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, true);
HashSet<Face> source = new HashSet<Face>(faces);
HashSet<Face> neighboring = new HashSet<Face>();
Vector3 srcNormal = Vector3.zero;
bool checkAngle = maxAngleDiff > 0f;
for (int i = 0; i < wings.Count; i++)
{
if (!source.Contains(wings[i].face))
continue;
if (checkAngle)
srcNormal = Math.Normal(mesh, wings[i].face);
using (var it = new WingedEdgeEnumerator(wings[i]))
{
while (it.MoveNext())
{
var w = it.Current;
if (w.opposite != null && !source.Contains(w.opposite.face))
{
if (checkAngle)
{
Vector3 oppNormal = Math.Normal(mesh, w.opposite.face);
if (Vector3.Angle(srcNormal, oppNormal) < maxAngleDiff)
neighboring.Add(w.opposite.face);
}
else
{
neighboring.Add(w.opposite.face);
}
}
}
}
}
return neighboring;
}
static readonly Vector3 Vector3_Zero = new Vector3(0f, 0f, 0f);
internal static void Flood(WingedEdge wing, HashSet<Face> selection)
{
Flood(null, wing, Vector3_Zero, -1f, selection);
}
internal static void Flood(ProBuilderMesh pb, WingedEdge wing, Vector3 wingNrm, float maxAngle, HashSet<Face> selection)
{
WingedEdge next = wing;
do
{
WingedEdge opp = next.opposite;
if (opp != null && !selection.Contains(opp.face))
{
if (maxAngle > 0f)
{
Vector3 oppNormal = Math.Normal(pb, opp.face);
if (Vector3.Angle(wingNrm, oppNormal) < maxAngle)
{
if (selection.Add(opp.face))
Flood(pb, opp, oppNormal, maxAngle, selection);
}
}
else
{
if (selection.Add(opp.face))
Flood(pb, opp, wingNrm, maxAngle, selection);
}
}
next = next.next;
}
while (next != wing);
}
/// <summary>
/// Recursively adds all faces touching any of the selected faces to the selection.
///
/// This corresponds to the [Grow Selection](../manual/Selection_Grow.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The selected faces.</param>
/// <param name="maxAngleDiff">Specify the maximum difference (in degrees) between the normals on the selected face and those on the perimeter face.</param>
/// <returns>The original faces selection, plus any new faces added as a result of the grow operation.</returns>
public static HashSet<Face> FloodSelection(ProBuilderMesh mesh, IList<Face> faces, float maxAngleDiff)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, true);
HashSet<Face> source = new HashSet<Face>(faces);
HashSet<Face> flood = new HashSet<Face>();
for (int i = 0; i < wings.Count; i++)
{
if (!flood.Contains(wings[i].face) && source.Contains(wings[i].face))
{
flood.Add(wings[i].face);
Flood(mesh, wings[i], maxAngleDiff > 0f ? Math.Normal(mesh, wings[i].face) : Vector3_Zero, maxAngleDiff, flood);
}
}
return flood;
}
/// <summary>
/// Finds and returns a face loop.
///
/// This is the equivalent of the [Select Face Loop](../manual/Selection_Loop_Face.html) and
/// [Select Face Ring](../manual/Selection_Ring_Face.html) actions.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to scan for loops.</param>
/// <param name="ring">Toggles between loop and ring. Ring and loop are arbritary with faces, so this parameter just toggles between which gets scanned first.</param>
/// <returns>A collection of faces gathered by extending a ring or loop,</returns>
public static HashSet<Face> GetFaceLoop(ProBuilderMesh mesh, Face[] faces, bool ring = false)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
HashSet<Face> loops = new HashSet<Face>();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
foreach (Face face in faces)
loops.UnionWith(GetFaceLoop(wings, face, ring));
return loops;
}
/// <summary>
/// Finds and returns both a face ring and loop from the selected faces.
/// This is the equivalent of the [Select Face Loop](../manual/Selection_Loop_Face.html) and
/// [Select Face Ring](../manual/Selection_Ring_Face.html) actions.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to scan for ring and loops.</param>
/// <returns>A collection of faces gathered by extending in a ring and loop.</returns>
public static HashSet<Face> GetFaceRingAndLoop(ProBuilderMesh mesh, Face[] faces)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (faces == null)
throw new ArgumentNullException("faces");
HashSet<Face> loops = new HashSet<Face>();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
foreach (Face face in faces)
{
loops.UnionWith(GetFaceLoop(wings, face, true));
loops.UnionWith(GetFaceLoop(wings, face, false));
}
return loops;
}
/// <summary>
/// Get a face loop or ring from a set of winged edges.
/// </summary>
/// <param name="wings"></param>
/// <param name="face"></param>
/// <param name="ring"></param>
/// <returns></returns>
static HashSet<Face> GetFaceLoop(List<WingedEdge> wings, Face face, bool ring)
{
HashSet<Face> loop = new HashSet<Face>();
if (face == null)
return loop;
WingedEdge start = wings.FirstOrDefault(x => x.face == face);
if (start == null)
return loop;
if (ring)
start = start.next ?? start.previous;
for (int i = 0; i < 2; i++)
{
WingedEdge cur = start;
if (i == 1)
{
if (start.opposite != null && start.opposite.face != null)
cur = start.opposite;
else
break;
}
do
{
if (!loop.Add(cur.face))
break;
if (cur.Count() != 4)
break;
// count == 4 assures us next.next is valid, but opposite can still be null
cur = cur.next.next.opposite;
}
while (cur != null && cur.face != null);
}
return loop;
}
/// <summary>
/// Find any holes touching one of the passed vertex indexes.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
/// <returns></returns>
internal static List<List<Edge>> FindHoles(ProBuilderMesh mesh, IEnumerable<int> indexes)
{
HashSet<int> common = mesh.GetSharedVertexHandles(indexes);
List<List<Edge>> holes = new List<List<Edge>>();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh);
foreach (List<WingedEdge> hole in FindHoles(wings, common))
holes.Add(hole.Select(x => x.edge.local).ToList());
return holes;
}
/// <summary>
/// Find any holes touching one of the passed common indexes.
/// </summary>
/// <param name="wings"></param>
/// <param name="common"></param>
/// <returns></returns>
internal static List<List<WingedEdge>> FindHoles(List<WingedEdge> wings, HashSet<int> common)
{
HashSet<WingedEdge> used = new HashSet<WingedEdge>();
List<List<WingedEdge>> holes = new List<List<WingedEdge>>();
for (int i = 0; i < wings.Count; i++)
{
WingedEdge c = wings[i];
// if this edge has been added to a hole already, or the edge isn't in the approved list of indexes,
// or if there's an opposite face, this edge doesn't belong to a hole. move along.
if (c.opposite != null || used.Contains(c) || !(common.Contains(c.edge.common.a) || common.Contains(c.edge.common.b)))
continue;
List<WingedEdge> hole = new List<WingedEdge>();
WingedEdge it = c;
int ind = it.edge.common.a;
int counter = 0;
while (it != null && counter++ < k_MaxHoleIterations)
{
used.Add(it);
hole.Add(it);
ind = it.edge.common.a == ind ? it.edge.common.b : it.edge.common.a;
it = FindNextEdgeInHole(it, ind);
if (it == c)
break;
}
List<SimpleTuple<int, int>> splits = new List<SimpleTuple<int, int>>();
// check previous wings for y == x (closed loop).
for (int n = 0; n < hole.Count; n++)
{
WingedEdge wing = hole[n];
for (int p = n - 1; p > -1; p--)
{
if (wing.edge.common.b == hole[p].edge.common.a)
{
splits.Add(new SimpleTuple<int, int>(p, n));
break;
}
}
}
// create new lists from each segment
// holes paths are nested, with holes
// possibly split between multiple nested
// holes
//
// [2, 0] [5, 3]
// [0, 9] [3, 11]
// [9, 10] [11, 10]
// [10, 7] [10, 2]
// [7, 6] or with split [2, 0]
// [6, 1] nesting -> [0, 9]
// [1, 4] [9, 10]
// [4, 7] <- (y == x) [10, 7]
// [7, 8] [7, 6]
// [8, 5] [6, 1]
// [5, 3] [1, 4]
// [3, 11] [4, 7]
// [11, 10] <- (y == x) [7, 8]
// [10, 2] <- (y == x) [8, 5]
//
// paths may also contain multiple segments non-tiered
int splitCount = splits.Count;
splits.Sort((x, y) => x.item1.CompareTo(y.item1));
int[] shift = new int[splitCount];
// Debug.Log(hole.ToString("\n") + "\n" + splits.ToString("\n"));
for (int n = splitCount - 1; n > -1; n--)
{
int x = splits[n].item1, y = splits[n].item2 - shift[n];
int range = (y - x) + 1;
List<WingedEdge> section = hole.GetRange(x, range);
hole.RemoveRange(x, range);
for (int m = n - 1; m > -1; m--)
if (splits[m].item2 > splits[n].item2)
shift[m] += range;
// verify that this path has at least one index that was asked for
if (splitCount < 2 || section.Any(w => common.Contains(w.edge.common.a)) || section.Any(w => common.Contains(w.edge.common.b)))
holes.Add(section);
}
}
return holes;
}
static WingedEdge FindNextEdgeInHole(WingedEdge wing, int common)
{
WingedEdge next = wing.GetAdjacentEdgeWithCommonIndex(common);
int counter = 0;
while (next != null && next != wing && counter++ < k_MaxHoleIterations)
{
if (next.opposite == null)
return next;
next = next.opposite.GetAdjacentEdgeWithCommonIndex(common);
}
return null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9be31b6177ca0453e928692a20b33b2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,600 @@
using System;
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Contains functions to help with face and edge extrusion.
/// </summary>
public static class ExtrudeElements
{
/// <summary>
/// Extrudes a collection of faces.
///
/// This is the equivalent of the [Extrude Faces](../manual/Face_Extrude.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to extrude.</param>
/// <param name="method">Describes how to extrude the faces (separately or as a group, either from averaged or individual normals).</param>
/// <param name="distance">The distance to extrude faces.</param>
/// <returns>An array of the faces created as a result of the extrusion or null if `faces` is null or empty.</returns>
public static Face[] Extrude(this ProBuilderMesh mesh, IEnumerable<Face> faces, ExtrudeMethod method, float distance)
{
switch (method)
{
case ExtrudeMethod.IndividualFaces:
return ExtrudePerFace(mesh, faces, distance);
default:
return ExtrudeAsGroups(mesh, faces, method == ExtrudeMethod.FaceNormal, distance);
}
}
/// <summary>
/// Extrudes a collection of edges.
///
/// This is the equivalent of the [Extrude Edges](../manual/Edge_Extrude.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="edges">The edges to extrude.</param>
/// <param name="distance">The distance to extrude.</param>
/// <param name="extrudeAsGroup">True to keep any shared vertices when extruding adjacent edges; false to split the shared vertex.</param>
/// <param name="enableManifoldExtrude">True to allow this function to extrude [manifold](../manual/gloss.html#manifold) edges; false to disallow.</param>
/// <returns>The extruded edges, or null if the action failed due to manifold check or an empty edges parameter.</returns>
public static Edge[] Extrude(this ProBuilderMesh mesh, IEnumerable<Edge> edges, float distance, bool extrudeAsGroup, bool enableManifoldExtrude)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (edges == null)
throw new ArgumentNullException("edges");
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
List<Edge> validEdges = new List<Edge>();
List<Face> edgeFaces = new List<Face>();
foreach (Edge e in edges)
{
int faceCount = 0;
Face fa = null;
foreach (Face face in mesh.facesInternal)
{
if (mesh.IndexOf(face.edgesInternal, e) > -1)
{
fa = face;
if (++faceCount > 1)
break;
}
}
if (enableManifoldExtrude || faceCount < 2)
{
validEdges.Add(e);
edgeFaces.Add(fa);
}
}
if (validEdges.Count < 1)
return null;
Vector3[] localVerts = mesh.positionsInternal;
if (!mesh.HasArrays(MeshArrays.Normal))
mesh.Refresh(RefreshMask.Normals);
IList<Vector3> oNormals = mesh.normals;
int[] allEdgeIndexes = new int[validEdges.Count * 2];
int c = 0;
for (int i = 0; i < validEdges.Count; i++)
{
allEdgeIndexes[c++] = validEdges[i].a;
allEdgeIndexes[c++] = validEdges[i].b;
}
List<Edge> extrudedIndexes = new List<Edge>();
// used to set the editor selection to the newly created edges
List<Edge> newEdges = new List<Edge>();
bool hasColors = mesh.HasArrays(MeshArrays.Color);
// build out new faces around validEdges
for (int i = 0; i < validEdges.Count; i++)
{
Edge edge = validEdges[i];
Face face = edgeFaces[i];
// Averages the normals using only vertices that are on the edge
Vector3 xnorm = extrudeAsGroup
? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.a)], allEdgeIndexes, oNormals)
: Math.Normal(mesh, face);
Vector3 ynorm = extrudeAsGroup
? InternalMeshUtility.AverageNormalWithIndexes(sharedIndexes[mesh.GetSharedVertexHandle(edge.b)], allEdgeIndexes, oNormals)
: Math.Normal(mesh, face);
int x_sharedIndex = mesh.GetSharedVertexHandle(edge.a);
int y_sharedIndex = mesh.GetSharedVertexHandle(edge.b);
var positions = new Vector3[4]
{
localVerts[edge.a],
localVerts[edge.b],
localVerts[edge.a] + xnorm.normalized * distance,
localVerts[edge.b] + ynorm.normalized * distance
};
var colors = hasColors
? new Color[4]
{
mesh.colorsInternal[edge.a],
mesh.colorsInternal[edge.b],
mesh.colorsInternal[edge.a],
mesh.colorsInternal[edge.b]
}
: null;
Face newFace = mesh.AppendFace(
positions,
colors,
new Vector2[4],
new Vector4[4],
new Vector4[4],
new Face(new int[6] { 2, 1, 0, 2, 3, 1 }, face.submeshIndex, AutoUnwrapSettings.tile, 0, -1, -1, false),
new int[4] { x_sharedIndex, y_sharedIndex, -1, -1 });
newEdges.Add(new Edge(newFace.indexesInternal[3], newFace.indexesInternal[4]));
extrudedIndexes.Add(new Edge(x_sharedIndex, newFace.indexesInternal[3]));
extrudedIndexes.Add(new Edge(y_sharedIndex, newFace.indexesInternal[4]));
}
// merge extruded vertex indexes with each other
if (extrudeAsGroup)
{
for (int i = 0; i < extrudedIndexes.Count; i++)
{
int val = extrudedIndexes[i].a;
for (int n = 0; n < extrudedIndexes.Count; n++)
{
if (n == i)
continue;
if (extrudedIndexes[n].a == val)
{
mesh.SetVerticesCoincident(new int[] { extrudedIndexes[n].b, extrudedIndexes[i].b });
break;
}
}
}
}
// todo Should only need to invalidate caches on affected faces
foreach (Face f in mesh.facesInternal)
f.InvalidateCache();
return newEdges.ToArray();
}
/// <summary>
/// Splits any shared vertices so that this face may be moved independently of the GameObject.
///
/// This is the equivalent of the [Detach Faces](../manual/Face_Detach.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to split from the mesh.</param>
/// <returns>The faces created forming the detached face group.</returns>
public static List<Face> DetachFaces(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
return DetachFaces(mesh, faces, true);
}
/// <summary>
/// Splits any shared vertices so that this face may be moved independently of the GameObject
/// and optionally deletes the faces on the source geometry.
///
/// This is the equivalent of the [Detach Faces](../manual/Face_Detach.html) action.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The faces to split from the mesh.</param>
/// <param name="deleteSourceFaces">True to delete the faces on the source geometry where the faces were detached; false to keep them.</param>
/// <returns>The faces created forming the detached face group.</returns>
public static List<Face> DetachFaces(this ProBuilderMesh mesh, IEnumerable<Face> faces, bool deleteSourceFaces)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
if (faces == null)
throw new System.ArgumentNullException("faces");
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
int sharedIndexOffset = mesh.sharedVerticesInternal.Length;
var lookup = mesh.sharedVertexLookup;
List<FaceRebuildData> detached = new List<FaceRebuildData>();
foreach (Face face in faces)
{
FaceRebuildData data = new FaceRebuildData();
data.vertices = new List<Vertex>();
data.sharedIndexes = new List<int>();
data.face = new Face(face);
Dictionary<int, int> match = new Dictionary<int, int>();
int[] indexes = new int[face.indexesInternal.Length];
for (int i = 0; i < face.indexesInternal.Length; i++)
{
int local;
if (match.TryGetValue(face.indexesInternal[i], out local))
{
indexes[i] = local;
}
else
{
local = data.vertices.Count;
indexes[i] = local;
match.Add(face.indexesInternal[i], local);
data.vertices.Add(vertices[face.indexesInternal[i]]);
data.sharedIndexes.Add(lookup[face.indexesInternal[i]] + sharedIndexOffset);
}
}
data.face.indexesInternal = indexes.ToArray();
detached.Add(data);
}
FaceRebuildData.Apply(detached, mesh, vertices);
if (deleteSourceFaces)
{
mesh.DeleteFaces(faces);
}
mesh.ToMesh();
return detached.Select(x => x.face).ToList();
}
/// <summary>
/// Extrude each face in faces individually along it's normal by distance.
/// </summary>
/// <param name="pb"></param>
/// <param name="faces"></param>
/// <param name="distance"></param>
/// <returns></returns>
static Face[] ExtrudePerFace(ProBuilderMesh pb, IEnumerable<Face> faces, float distance)
{
Face[] faceArray = faces as Face[] ?? faces.ToArray();
if (!faceArray.Any())
return null;
List<Vertex> vertices = new List<Vertex>(pb.GetVertices());
int sharedIndexMax = pb.sharedVerticesInternal.Length;
int sharedIndexOffset = 0;
int faceIndex = 0;
Dictionary<int, int> lookup = pb.sharedVertexLookup;
Dictionary<int, int> lookupUV = pb.sharedTextureLookup;
Dictionary<int, int> used = new Dictionary<int, int>();
Face[] newFaces = new Face[faceArray.Sum(x => x.edges.Count)];
foreach (Face face in faceArray)
{
face.smoothingGroup = Smoothing.smoothingGroupNone;
face.textureGroup = -1;
Vector3 delta = Math.Normal(pb, face) * distance;
Edge[] edges = face.edgesInternal;
used.Clear();
for (int i = 0; i < edges.Length; i++)
{
int vc = vertices.Count;
int x = edges[i].a, y = edges[i].b;
if (!used.ContainsKey(x))
{
used.Add(x, lookup[x]);
lookup[x] = sharedIndexMax + (sharedIndexOffset++);
}
if (!used.ContainsKey(y))
{
used.Add(y, lookup[y]);
lookup[y] = sharedIndexMax + (sharedIndexOffset++);
}
lookup.Add(vc + 0, used[x]);
lookup.Add(vc + 1, used[y]);
lookup.Add(vc + 2, lookup[x]);
lookup.Add(vc + 3, lookup[y]);
Vertex xx = new Vertex(vertices[x]), yy = new Vertex(vertices[y]);
xx.position += delta;
yy.position += delta;
vertices.Add(new Vertex(vertices[x]));
vertices.Add(new Vertex(vertices[y]));
vertices.Add(xx);
vertices.Add(yy);
Face bridge = new Face(
new int[6] { vc + 0, vc + 1, vc + 2, vc + 1, vc + 3, vc + 2 },
face.submeshIndex,
new AutoUnwrapSettings(face.uv),
face.smoothingGroup,
-1,
-1,
false
);
newFaces[faceIndex++] = bridge;
}
for (int i = 0; i < face.distinctIndexesInternal.Length; i++)
{
vertices[face.distinctIndexesInternal[i]].position += delta;
// Break any UV shared connections
if (lookupUV != null && lookupUV.ContainsKey(face.distinctIndexesInternal[i]))
lookupUV.Remove(face.distinctIndexesInternal[i]);
}
}
pb.SetVertices(vertices);
var fc = pb.faceCount;
var nc = newFaces.Length;
var appended = new Face[fc + nc];
Array.Copy(pb.facesInternal, 0, appended, 0, fc);
Array.Copy(newFaces, 0, appended, fc, nc);
pb.faces = appended;
pb.SetSharedVertices(lookup);
pb.SetSharedTextures(lookupUV);
return newFaces;
}
/// <summary>
/// Extrude faces as groups.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="compensateAngleVertexDistance"></param>
/// <param name="distance"></param>
/// <returns></returns>
static Face[] ExtrudeAsGroups(ProBuilderMesh mesh, IEnumerable<Face> faces, bool compensateAngleVertexDistance, float distance)
{
if (faces == null || !faces.Any())
return null;
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
int sharedIndexMax = mesh.sharedVerticesInternal.Length;
int sharedIndexOffset = 0;
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
Dictionary<int, int> lookupUV = mesh.sharedTextureLookup;
List<Face> newFaces = new List<Face>();
// old triangle index -> old shared index
Dictionary<int, int> oldSharedMap = new Dictionary<int, int>();
// old shared index -> new shared index
Dictionary<int, int> newSharedMap = new Dictionary<int, int>();
// bridge face extruded edges, maps vertex index to new extruded vertex position
Dictionary<int, int> delayPosition = new Dictionary<int, int>();
// used to average the direction of vertices shared by perimeter edges
// key[shared index], value[normal count, normal sum]
Dictionary<int, SimpleTuple<Vector3, Vector3, List<int>>> extrudeMap = new Dictionary<int, SimpleTuple<Vector3, Vector3, List<int>>>();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces, true);
List<HashSet<Face>> groups = GetFaceGroups(wings);
foreach (HashSet<Face> group in groups)
{
Dictionary<EdgeLookup, Face> perimeter = GetPerimeterEdges(group, lookup);
newSharedMap.Clear();
oldSharedMap.Clear();
foreach (var edgeAndFace in perimeter)
{
EdgeLookup edge = edgeAndFace.Key;
Face face = edgeAndFace.Value;
int vc = vertices.Count;
int x = edge.local.a, y = edge.local.b;
if (!oldSharedMap.ContainsKey(x))
{
oldSharedMap.Add(x, lookup[x]);
int newSharedIndex = -1;
if (newSharedMap.TryGetValue(lookup[x], out newSharedIndex))
{
lookup[x] = newSharedIndex;
}
else
{
newSharedIndex = sharedIndexMax + (sharedIndexOffset++);
newSharedMap.Add(lookup[x], newSharedIndex);
lookup[x] = newSharedIndex;
}
}
if (!oldSharedMap.ContainsKey(y))
{
oldSharedMap.Add(y, lookup[y]);
int newSharedIndex = -1;
if (newSharedMap.TryGetValue(lookup[y], out newSharedIndex))
{
lookup[y] = newSharedIndex;
}
else
{
newSharedIndex = sharedIndexMax + (sharedIndexOffset++);
newSharedMap.Add(lookup[y], newSharedIndex);
lookup[y] = newSharedIndex;
}
}
lookup.Add(vc + 0, oldSharedMap[x]);
lookup.Add(vc + 1, oldSharedMap[y]);
lookup.Add(vc + 2, lookup[x]);
lookup.Add(vc + 3, lookup[y]);
delayPosition.Add(vc + 2, x);
delayPosition.Add(vc + 3, y);
vertices.Add(new Vertex(vertices[x]));
vertices.Add(new Vertex(vertices[y]));
// extruded edge will be positioned later
vertices.Add(null);
vertices.Add(null);
Face bridge = new Face(
new int[6] { vc + 0, vc + 1, vc + 2, vc + 1, vc + 3, vc + 2 },
face.submeshIndex,
new AutoUnwrapSettings(face.uv),
Smoothing.smoothingGroupNone,
-1,
-1,
false
);
newFaces.Add(bridge);
}
foreach (Face face in group)
{
// @todo keep together if possible
face.textureGroup = -1;
Vector3 normal = Math.Normal(mesh, face);
for (int i = 0; i < face.distinctIndexesInternal.Length; i++)
{
int idx = face.distinctIndexesInternal[i];
// If this vertex is on the perimeter but not part of a perimeter edge
// move the sharedIndex to match it's new value.
if (!oldSharedMap.ContainsKey(idx) && newSharedMap.ContainsKey(lookup[idx]))
lookup[idx] = newSharedMap[lookup[idx]];
int com = lookup[idx];
// Break any UV shared connections
if (lookupUV != null && lookupUV.ContainsKey(face.distinctIndexesInternal[i]))
lookupUV.Remove(face.distinctIndexesInternal[i]);
// add the normal to the list of normals for this shared vertex
SimpleTuple<Vector3, Vector3, List<int>> dir;
if (extrudeMap.TryGetValue(com, out dir))
{
dir.item1 += normal;
dir.item3.Add(idx);
extrudeMap[com] = dir;
}
else
{
extrudeMap.Add(com, new SimpleTuple<Vector3, Vector3, List<int>>(normal, normal, new List<int>() { idx }));
}
}
}
}
foreach (var kvp in extrudeMap)
{
Vector3 direction = (kvp.Value.item1 / kvp.Value.item3.Count);
direction.Normalize();
// If extruding by face normal extend vertices on seams by the hypotenuse
float modifier = compensateAngleVertexDistance ? Math.Secant(Vector3.Angle(direction, kvp.Value.item2) * Mathf.Deg2Rad) : 1f;
direction.x *= distance * modifier;
direction.y *= distance * modifier;
direction.z *= distance * modifier;
foreach (int i in kvp.Value.item3)
{
vertices[i].position += direction;
}
}
foreach (var kvp in delayPosition)
vertices[kvp.Key] = new Vertex(vertices[kvp.Value]);
mesh.SetVertices(vertices);
var fc = mesh.faceCount;
var nc = newFaces.Count;
var appended = new Face[fc + nc];
Array.Copy(mesh.facesInternal, 0, appended, 0, fc);
for (int i = fc, c = fc + nc; i < c; i++)
appended[i] = newFaces[i - fc];
mesh.faces = appended;
mesh.SetSharedVertices(lookup);
mesh.SetSharedTextures(lookupUV);
return newFaces.ToArray();
}
static List<HashSet<Face>> GetFaceGroups(List<WingedEdge> wings)
{
HashSet<Face> used = new HashSet<Face>();
List<HashSet<Face>> groups = new List<HashSet<Face>>();
foreach (WingedEdge wing in wings)
{
if (used.Add(wing.face))
{
HashSet<Face> group = new HashSet<Face>() { wing.face };
ElementSelection.Flood(wing, group);
foreach (Face f in group)
used.Add(f);
groups.Add(group);
}
}
return groups;
}
static Dictionary<EdgeLookup, Face> GetPerimeterEdges(HashSet<Face> faces, Dictionary<int, int> lookup)
{
Dictionary<EdgeLookup, Face> perimeter = new Dictionary<EdgeLookup, Face>();
HashSet<EdgeLookup> used = new HashSet<EdgeLookup>();
foreach (Face face in faces)
{
foreach (Edge edge in face.edgesInternal)
{
EdgeLookup e = new EdgeLookup(lookup[edge.a], lookup[edge.b], edge.a, edge.b);
if (!used.Add(e))
{
if (perimeter.ContainsKey(e))
perimeter.Remove(e);
}
else
{
perimeter.Add(e, face);
}
}
}
return perimeter;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: caa73878d8fb64ec1b9ba323a2158672
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ArrUtil = UnityEngine.ProBuilder.ArrayUtility;
using UnityEditor;
namespace UnityEngine.ProBuilder.MeshOperations
{
static class InternalMeshUtility
{
/// <summary>
/// Averages shared normals with the mask of all (indexes contained in perimeter edge)
/// </summary>
internal static Vector3 AverageNormalWithIndexes(SharedVertex shared, int[] all, IList<Vector3> norm)
{
Vector3 n = Vector3.zero;
int count = 0;
for (int i = 0; i < all.Length; i++)
{
// this is a point in the perimeter, add it to the average
if (shared.Contains(all[i]))
{
n += norm[all[i]];
count++;
}
}
return (n / (float)count);
}
/// <summary>
/// "ProBuilder-ize" function
/// </summary>
/// <param name="t"></param>
/// <param name="preserveFaces"></param>
/// <returns></returns>
public static ProBuilderMesh CreateMeshWithTransform(Transform t, bool preserveFaces)
{
Mesh m = t.GetComponent<MeshFilter>().sharedMesh;
Vector3[] m_vertices = MeshUtility.GetMeshChannel(t.gameObject, x => x.vertices);
Color[] m_colors = MeshUtility.GetMeshChannel(t.gameObject, x => x.colors);
Vector2[] m_uvs = MeshUtility.GetMeshChannel(t.gameObject, x => x.uv);
List<Vector3> verts = preserveFaces ? new List<Vector3>(m.vertices) : new List<Vector3>();
List<Color> cols = preserveFaces ? new List<Color>(m.colors) : new List<Color>();
List<Vector2> uvs = preserveFaces ? new List<Vector2>(m.uv) : new List<Vector2>();
List<Face> faces = new List<Face>();
for (int n = 0; n < m.subMeshCount; n++)
{
int[] tris = m.GetTriangles(n);
for (int i = 0; i < tris.Length; i += 3)
{
int index = -1;
if (preserveFaces)
{
for (int j = 0; j < faces.Count; j++)
{
if (faces[j].distinctIndexesInternal.Contains(tris[i + 0]) ||
faces[j].distinctIndexesInternal.Contains(tris[i + 1]) ||
faces[j].distinctIndexesInternal.Contains(tris[i + 2]))
{
index = j;
break;
}
}
}
if (index > -1 && preserveFaces)
{
int len = faces[index].indexesInternal.Length;
int[] arr = new int[len + 3];
System.Array.Copy(faces[index].indexesInternal, 0, arr, 0, len);
arr[len + 0] = tris[i + 0];
arr[len + 1] = tris[i + 1];
arr[len + 2] = tris[i + 2];
faces[index].indexesInternal = arr;
}
else
{
int[] faceTris;
if (preserveFaces)
{
faceTris = new int[3]
{
tris[i + 0],
tris[i + 1],
tris[i + 2]
};
}
else
{
verts.Add(m_vertices[tris[i + 0]]);
verts.Add(m_vertices[tris[i + 1]]);
verts.Add(m_vertices[tris[i + 2]]);
cols.Add(m_colors != null ? m_colors[tris[i + 0]] : Color.white);
cols.Add(m_colors != null ? m_colors[tris[i + 1]] : Color.white);
cols.Add(m_colors != null ? m_colors[tris[i + 2]] : Color.white);
uvs.Add(m_uvs[tris[i + 0]]);
uvs.Add(m_uvs[tris[i + 1]]);
uvs.Add(m_uvs[tris[i + 2]]);
faceTris = new int[3] { i + 0, i + 1, i + 2 };
}
faces.Add(
new Face(
faceTris,
n,
AutoUnwrapSettings.tile,
0, // smoothing group
-1, // texture group
-1, // element group
true // manualUV
));
}
}
}
GameObject go = (GameObject)Object.Instantiate(t.gameObject);
go.GetComponent<MeshFilter>().sharedMesh = null;
ProBuilderMesh pb = go.AddComponent<ProBuilderMesh>();
pb.RebuildWithPositionsAndFaces(verts.ToArray(), faces.ToArray());
pb.colorsInternal = cols.ToArray();
pb.textures = uvs;
pb.gameObject.name = t.name;
go.transform.position = t.position;
go.transform.localRotation = t.localRotation;
go.transform.localScale = t.localScale;
pb.CenterPivot(null);
return pb;
}
/// <summary>
/// ProBuilderize in-place function. You must call ToMesh() and Refresh() after
/// returning from this function, as this only creates the pb_Object and sets its
/// fields. This allows you to record the mesh and gameObject for Undo operations.
/// </summary>
/// <param name="pb"></param>
/// <param name="preserveFaces"></param>
/// <returns></returns>
public static bool ResetPbObjectWithMeshFilter(ProBuilderMesh pb, bool preserveFaces)
{
MeshFilter mf = pb.gameObject.GetComponent<MeshFilter>();
if (mf == null || mf.sharedMesh == null)
{
Log.Error(pb.name + " does not have a mesh or Mesh Filter component.");
return false;
}
Mesh m = mf.sharedMesh;
int vertexCount = m.vertexCount;
Vector3[] m_positions = MeshUtility.GetMeshChannel<Vector3[]>(pb.gameObject, x => x.vertices);
Color[] m_colors = MeshUtility.GetMeshChannel<Color[]>(pb.gameObject, x => x.colors);
Vector2[] m_uvs = MeshUtility.GetMeshChannel<Vector2[]>(pb.gameObject, x => x.uv);
List<Vector3> verts = preserveFaces ? new List<Vector3>(m.vertices) : new List<Vector3>();
List<Color> cols = preserveFaces ? new List<Color>(m.colors) : new List<Color>();
List<Vector2> uvs = preserveFaces ? new List<Vector2>(m.uv) : new List<Vector2>();
List<Face> faces = new List<Face>();
MeshRenderer mr = pb.gameObject.GetComponent<MeshRenderer>();
if (mr == null) mr = pb.gameObject.AddComponent<MeshRenderer>();
Material[] sharedMaterials = mr.sharedMaterials;
int mat_length = sharedMaterials.Length;
for (int n = 0; n < m.subMeshCount; n++)
{
int[] tris = m.GetTriangles(n);
for (int i = 0; i < tris.Length; i += 3)
{
int index = -1;
if (preserveFaces)
{
for (int j = 0; j < faces.Count; j++)
{
if (faces[j].distinctIndexesInternal.Contains(tris[i + 0]) ||
faces[j].distinctIndexesInternal.Contains(tris[i + 1]) ||
faces[j].distinctIndexesInternal.Contains(tris[i + 2]))
{
index = j;
break;
}
}
}
if (index > -1 && preserveFaces)
{
int len = faces[index].indexesInternal.Length;
int[] arr = new int[len + 3];
System.Array.Copy(faces[index].indexesInternal, 0, arr, 0, len);
arr[len + 0] = tris[i + 0];
arr[len + 1] = tris[i + 1];
arr[len + 2] = tris[i + 2];
faces[index].indexesInternal = arr;
}
else
{
int[] faceTris;
if (preserveFaces)
{
faceTris = new int[3]
{
tris[i + 0],
tris[i + 1],
tris[i + 2]
};
}
else
{
verts.Add(m_positions[tris[i + 0]]);
verts.Add(m_positions[tris[i + 1]]);
verts.Add(m_positions[tris[i + 2]]);
cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 0]] : Color.white);
cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 1]] : Color.white);
cols.Add(m_colors != null && m_colors.Length == vertexCount ? m_colors[tris[i + 2]] : Color.white);
uvs.Add(m_uvs[tris[i + 0]]);
uvs.Add(m_uvs[tris[i + 1]]);
uvs.Add(m_uvs[tris[i + 2]]);
faceTris = new int[3] { i + 0, i + 1, i + 2 };
}
faces.Add(
new Face(
faceTris,
Math.Clamp(n, 0, mat_length - 1),
AutoUnwrapSettings.tile,
0, // smoothing group
-1, // texture group
-1, // element group
true // manualUV
));
}
}
}
pb.positionsInternal = verts.ToArray();
pb.texturesInternal = uvs.ToArray();
pb.facesInternal = faces.ToArray();
pb.sharedVerticesInternal = SharedVertex.GetSharedVerticesWithPositions(verts.ToArray());
pb.colorsInternal = cols.ToArray();
return true;
}
internal static void FilterUnusedSubmeshIndexes(ProBuilderMesh mesh)
{
var materials = mesh.renderer.sharedMaterials;
var submeshCount = materials.Length;
var used = new bool[submeshCount];
foreach (var face in mesh.facesInternal)
used[Math.Clamp(face.submeshIndex, 0, submeshCount - 1)] = true;
var unused = ArrUtil.AllIndexesOf(used, x => !x);
if (unused.Any())
{
foreach (var face in mesh.facesInternal)
{
var original = face.submeshIndex;
foreach (var index in unused)
if (original > index)
face.submeshIndex--;
}
mesh.renderer.sharedMaterials = ArrUtil.RemoveAt(materials, unused);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9ea06f47bdbe427294162ab3d8e4ef3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,138 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides methods for merging multiple faces of a <see cref="ProBuilderMesh"/> to a single face.
/// </summary>
public static class MergeElements
{
/// <summary>
/// Merges each pair of faces into a single face. Indexes are combined, but otherwise the properties of
/// the first face in the pair take precedence.
///
/// This is the equivalent of the [Merge Faces](../manual/Face_Merge.html) action.
/// </summary>
/// <param name="target">The source mesh.</param>
/// <param name="pairs">The list of face pairs to merge.</param>
/// <param name="collapseCoincidentVertices">True to condense coincident vertex positions on each face.</param>
/// <returns>A list of the new faces created.</returns>
public static List<Face> MergePairs(ProBuilderMesh target, IEnumerable<SimpleTuple<Face, Face>> pairs, bool collapseCoincidentVertices = true)
{
HashSet<Face> remove = new HashSet<Face>();
List<Face> add = new List<Face>();
foreach (SimpleTuple<Face, Face> pair in pairs)
{
Face left = pair.item1;
Face right = pair.item2;
int leftLength = left.indexesInternal.Length;
int rightLength = right.indexesInternal.Length;
int[] indexes = new int[leftLength + rightLength];
System.Array.Copy(left.indexesInternal, 0, indexes, 0, leftLength);
System.Array.Copy(right.indexesInternal, 0, indexes, leftLength, rightLength);
add.Add(new Face(indexes,
left.submeshIndex,
left.uv,
left.smoothingGroup,
left.textureGroup,
left.elementGroup,
left.manualUV));
remove.Add(left);
remove.Add(right);
}
List<Face> faces = target.facesInternal.Where(x => !remove.Contains(x)).ToList();
faces.AddRange(add);
target.faces = faces;
if (collapseCoincidentVertices)
CollapseCoincidentVertices(target, add);
return add;
}
/// <summary>
/// Merges a collection of faces into a single face. This function does not
/// perform any sanity checks: it just merges faces, so the caller must make
/// sure that the input is valid. This method also removes duplicate vertices
/// created as a result of merging previously common vertices.
///
/// This is the equivalent of the [Merge Faces](../manual/Face_Merge.html) action.
/// </summary>
/// <param name="target">The source mesh.</param>
/// <param name="faces">The collection of faces to move.</param>
/// <returns>The single merged Face.</returns>
public static Face Merge(ProBuilderMesh target, IEnumerable<Face> faces)
{
int mergedCount = faces != null ? faces.Count() : 0;
if (mergedCount < 1)
return null;
Face first = faces.First();
Face mergedFace = new Face(faces.SelectMany(x => x.indexesInternal).ToArray(),
first.submeshIndex,
first.uv,
first.smoothingGroup,
first.textureGroup,
first.elementGroup,
first.manualUV);
Face[] rebuiltFaces = new Face[target.facesInternal.Length - mergedCount + 1];
int n = 0;
HashSet<Face> skip = new HashSet<Face>(faces);
foreach (Face f in target.facesInternal)
{
if (!skip.Contains(f))
rebuiltFaces[n++] = f;
}
rebuiltFaces[n] = mergedFace;
target.faces = rebuiltFaces;
CollapseCoincidentVertices(target, new Face[] { mergedFace });
return mergedFace;
}
/// <summary>
/// Condense co-incident vertex positions per-face. vertices must already be marked as shared in the sharedIndexes
/// array to be considered. This method is really only useful after merging faces.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
internal static void CollapseCoincidentVertices(ProBuilderMesh mesh, IEnumerable<Face> faces)
{
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
Dictionary<int, int> matches = new Dictionary<int, int>();
foreach (Face face in faces)
{
matches.Clear();
for (int i = 0; i < face.indexesInternal.Length; i++)
{
int common = lookup[face.indexesInternal[i]];
if (matches.ContainsKey(common))
face.indexesInternal[i] = matches[common];
else
matches.Add(common, face.indexesInternal[i]);
}
face.InvalidateCache();
}
MeshValidation.RemoveUnusedVertices(mesh);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9e3db3de6fd8480bb4e763ca2dcbe03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,251 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// A collection of settings used when importing models to the ProBuilderMesh component.
/// </summary>
[Serializable]
public sealed class MeshImportSettings
{
[SerializeField]
bool m_Quads = true;
[SerializeField]
bool m_Smoothing = true;
[SerializeField]
float m_SmoothingThreshold = 1f;
/// <summary>
/// Gets or sets whether to quadrangilize meshes (convert them to quads if possible).
/// </summary>
public bool quads
{
get { return m_Quads; }
set { m_Quads = value; }
}
// Allow ngons when importing meshes. @todo
// public bool ngons = false;
/// <summary>
/// Gets or sets whether to generate smoothing groups based on mesh normals.
/// </summary>
public bool smoothing
{
get { return m_Smoothing; }
set { m_Smoothing = value; }
}
/// <summary>
/// Gets or sets the allowable degree of difference between face normals when determining smoothing groups.
/// </summary>
public float smoothingAngle
{
get { return m_SmoothingThreshold; }
set { m_SmoothingThreshold = value; }
}
/// <summary>
/// Returns a string representation of the options.
/// </summary>
/// <returns>String formatted as `quads: [quads]\nsmoothing: [smoothing]\nthreshold: [threshold]`.</returns>
public override string ToString()
{
return string.Format("quads: {0}\nsmoothing: {1}\nthreshold: {2}",
quads,
smoothing,
smoothingAngle);
}
}
/// <summary>
/// Responsible for importing UnityEngine.Mesh data to a ProBuilderMesh component.
/// </summary>
public sealed class MeshImporter
{
static readonly MeshImportSettings k_DefaultImportSettings = new MeshImportSettings()
{
quads = true,
smoothing = true,
smoothingAngle = 1f
};
Mesh m_SourceMesh;
Material[] m_SourceMaterials;
ProBuilderMesh m_Destination;
Vertex[] m_Vertices;
/// <summary>
/// Creates a new ProBuilderMesh importer instance from the specified GameObject.
/// </summary>
/// <param name="gameObject">The GameObject to write vertex data to.</param>
public MeshImporter(GameObject gameObject)
{
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
m_SourceMesh = meshFilter.sharedMesh;
if(m_SourceMesh == null)
throw new ArgumentNullException("gameObject", "GameObject does not contain a valid MeshFilter.sharedMesh.");
m_Destination = gameObject.DemandComponent<ProBuilderMesh>();
m_SourceMaterials = gameObject.GetComponent<MeshRenderer>()?.sharedMaterials;
}
/// <summary>
/// Creates a new ProBuilderMesh importer instance from the specified mesh and materials.
/// </summary>
/// <param name="sourceMesh">The Mesh asset to import vertex data from.</param>
/// <param name="sourceMaterials">The materials to assign to the ProBuilderMesh renderer.</param>
/// <param name="destination">The ProBuilderMesh asset to write vertex data to.</param>
public MeshImporter(Mesh sourceMesh, Material[] sourceMaterials, ProBuilderMesh destination)
{
if(sourceMesh == null)
throw new ArgumentNullException("sourceMesh");
if(destination == null)
throw new ArgumentNullException("destination");
m_SourceMesh = sourceMesh;
m_SourceMaterials = sourceMaterials;
m_Destination = destination;
}
/// <summary>Obsolete.</summary>
/// <param name="destination">The ProBuilderMesh asset.</param>
[Obsolete, EditorBrowsable(EditorBrowsableState.Never)]
public MeshImporter(ProBuilderMesh destination)
{
m_Destination = destination;
}
/// <summary>Obsolete.</summary>
/// <param name="go">The GameObject asset.</param>
/// <param name="importSettings">The import settings.</param>
/// <returns>Success/failure</returns>
[Obsolete, EditorBrowsable(EditorBrowsableState.Never)]
public bool Import(GameObject go, MeshImportSettings importSettings = null)
{
try
{
m_SourceMesh = go.GetComponent<MeshFilter>().sharedMesh;
m_SourceMaterials = go.GetComponent<MeshRenderer>()?.sharedMaterials;
Import(importSettings);
}
catch (Exception e)
{
Log.Error(e.ToString());
return false;
}
return true;
}
/// <summary>
/// Imports mesh data from a GameObject's <see cref="UnityEngine.MeshFilter.sharedMesh"/> and
/// <see cref="UnityEngine.Renderer.sharedMaterials"/> properties.
/// </summary>
/// <param name="importSettings">Optional import customization settings.</param>
/// <exception cref="NotSupportedException">Import only supports triangle and quad mesh topologies.</exception>
public void Import(MeshImportSettings importSettings = null)
{
if (importSettings == null)
importSettings = k_DefaultImportSettings;
// When importing the mesh is always split into triangles with no vertices shared
// between faces. In a later step co-incident vertices are collapsed (eg, before
// leaving the Import function).
Vertex[] sourceVertices = m_SourceMesh.GetVertices();
List<Vertex> splitVertices = new List<Vertex>();
List<Face> faces = new List<Face>();
// Fill in Faces array with just the position indexes. In the next step we'll
// figure out smoothing groups & merging
int vertexIndex = 0;
int materialCount = m_SourceMaterials != null ? m_SourceMaterials.Length : 0;
for (int submeshIndex = 0; submeshIndex < m_SourceMesh.subMeshCount; submeshIndex++)
{
switch (m_SourceMesh.GetTopology(submeshIndex))
{
case MeshTopology.Triangles:
{
int[] indexes = m_SourceMesh.GetIndices(submeshIndex);
for (int tri = 0; tri < indexes.Length; tri += 3)
{
faces.Add(new Face(
new int[] { vertexIndex, vertexIndex + 1, vertexIndex + 2 },
Math.Clamp(submeshIndex, 0, materialCount - 1),
AutoUnwrapSettings.tile,
Smoothing.smoothingGroupNone,
-1,
-1,
true));
splitVertices.Add(sourceVertices[indexes[tri]]);
splitVertices.Add(sourceVertices[indexes[tri + 1]]);
splitVertices.Add(sourceVertices[indexes[tri + 2]]);
vertexIndex += 3;
}
}
break;
case MeshTopology.Quads:
{
int[] indexes = m_SourceMesh.GetIndices(submeshIndex);
for (int quad = 0; quad < indexes.Length; quad += 4)
{
faces.Add(new Face(new int[]
{
vertexIndex, vertexIndex + 1, vertexIndex + 2,
vertexIndex + 2, vertexIndex + 3, vertexIndex + 0
},
Math.Clamp(submeshIndex, 0, materialCount - 1),
AutoUnwrapSettings.tile,
Smoothing.smoothingGroupNone,
-1,
-1,
true));
splitVertices.Add(sourceVertices[indexes[quad]]);
splitVertices.Add(sourceVertices[indexes[quad + 1]]);
splitVertices.Add(sourceVertices[indexes[quad + 2]]);
splitVertices.Add(sourceVertices[indexes[quad + 3]]);
vertexIndex += 4;
}
}
break;
default:
throw new NotSupportedException("ProBuilder only supports importing triangle and quad meshes.");
}
}
m_Vertices = splitVertices.ToArray();
m_Destination.Clear();
m_Destination.SetVertices(m_Vertices);
m_Destination.faces = faces;
m_Destination.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(m_Destination.positionsInternal);
m_Destination.sharedTextures = new SharedVertex[0];
if (importSettings.quads)
{
var newFaces = m_Destination.ToQuads(m_Destination.facesInternal, !importSettings.smoothing);
}
if (importSettings.smoothing)
{
Smoothing.ApplySmoothingGroups(m_Destination, m_Destination.facesInternal, importSettings.smoothingAngle, m_Vertices.Select(x => x.normal).ToArray());
// After smoothing has been applied go back and weld coincident vertices created by MergePairs.
MergeElements.CollapseCoincidentVertices(m_Destination, m_Destination.facesInternal);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5963564d485624ab6ad4547f9451dbe6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,102 @@
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Functions for manipulating the transform of a mesh.
/// </summary>
public static class MeshTransform
{
/// <summary>
/// Set the pivot point for a mesh to either the center, or a corner point of the bounding box.
/// </summary>
/// <param name="mesh">The <see cref="ProBuilderMesh"/> to adjust vertices for a new pivot point.</param>
/// <param name="pivotLocation">The new pivot point is either the center of the mesh bounding box, or
/// the bounds center - extents.</param>
internal static void SetPivot(this ProBuilderMesh mesh, PivotLocation pivotLocation)
{
var bounds = mesh.GetBounds();
var pivot = pivotLocation == PivotLocation.Center ? bounds.center : bounds.center - bounds.extents;
SetPivot(mesh, mesh.transform.TransformPoint(pivot));
}
/// <summary>
/// Centers the mesh pivot at the average of a set of vertex positions.
///
/// This is the equivalent of the [Center Pivot](../manual/CenterPivot.html) action.
/// </summary>
/// <param name="mesh">The target mesh.</param>
/// <param name="indexes">The array of indices representing the positions to average in order to find the new pivot.</param>
public static void CenterPivot(this ProBuilderMesh mesh, int[] indexes)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
Vector3 center = Vector3.zero;
if (indexes != null && indexes.Length > 0)
{
Vector3[] positions = mesh.positionsInternal;
if (positions == null || positions.Length < 3)
return;
foreach (int i in indexes)
center += positions[i];
center = mesh.transform.TransformPoint(center / (float)indexes.Length);
}
else
{
center = mesh.transform.TransformPoint(mesh.mesh.bounds.center);
}
Vector3 dir = (mesh.transform.position - center);
mesh.transform.position = center;
mesh.ToMesh();
mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, dir);
mesh.Refresh();
}
/// <summary>
/// Sets the pivot point of a mesh in world space. The Transform component position property is set to worldPosition,
/// while the mesh geometry does not move.
///
/// This is the equivalent of the [Set Pivot (Vertices)](../manual/Vert_SetPivot.html) action.
/// </summary>
/// <param name="mesh">The target mesh.</param>
/// <param name="worldPosition">The new pivot position in world space.</param>
public static void SetPivot(this ProBuilderMesh mesh, Vector3 worldPosition)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
var transform = mesh.transform;
Vector3 offset = transform.position - worldPosition;
transform.position = worldPosition;
mesh.ToMesh();
mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, offset);
mesh.Refresh();
}
/// <summary>
/// Scales vertices and sets `transform.localScale` to `Vector3.one`.
///
/// This is the equivalent of the [Freeze Transform](../manual/Freeze_Transform.html) action.
/// </summary>
/// <param name="mesh">The target mesh.</param>
public static void FreezeScaleTransform(this ProBuilderMesh mesh)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
Vector3[] v = mesh.positionsInternal;
for (var i = 0; i < v.Length; i++)
v[i] = Vector3.Scale(v[i], mesh.transform.localScale);
mesh.transform.localScale = new Vector3(1f, 1f, 1f);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aac023407bdc9df4db32c6aa0c29ef25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,496 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Methods for validating and fixing mesh topology.
/// </summary>
public static class MeshValidation
{
/// <summary>
/// Returns whether any face on a mesh contains [degenerate triangles](../manual/gloss.html#degenerate).
/// </summary>
/// <param name="mesh">The mesh to test for degenerate triangles.</param>
/// <returns>True if any face contains a degenerate triangle, false if no degenerate triangles are found.</returns>
/// <seealso cref="RemoveDegenerateTriangles"/>
public static bool ContainsDegenerateTriangles(this ProBuilderMesh mesh)
{
return ContainsDegenerateTriangles(mesh, mesh.facesInternal);
}
/// <summary>
/// Returns whether any of the specified faces contains [degenerate triangles](../manual/gloss.html#degenerate).
/// </summary>
/// <param name="mesh">The mesh to test for degenerate triangles.</param>
/// <param name="faces">The faces to test for degenerate triangles.</param>
/// <returns>True if any face contains a degenerate triangle, false if no degenerate triangles are found.</returns>
/// <seealso cref="RemoveDegenerateTriangles"/>
public static bool ContainsDegenerateTriangles(this ProBuilderMesh mesh, IList<Face> faces)
{
var positions = mesh.positionsInternal;
foreach (var face in faces)
{
var indices = face.indexesInternal;
for (int i = 0; i < indices.Length; i += 3)
{
float area = Math.TriangleArea(
positions[indices[i + 0]],
positions[indices[i + 1]],
positions[indices[i + 2]]);
if (area <= Mathf.Epsilon)
return true;
}
}
return false;
}
/// <summary>
/// Returns whether the specified face contains [degenerate triangles](../manual/gloss.html#degenerate).
/// </summary>
/// <param name="mesh">The mesh to test for degenerate triangles.</param>
/// <param name="face">The face to test for degenerate triangles.</param>
/// <returns>True if any triangle within the face contains a degenerate triangle, false if no degenerate triangles are found.</returns>
/// <seealso cref="RemoveDegenerateTriangles"/>
public static bool ContainsDegenerateTriangles(this ProBuilderMesh mesh, Face face)
{
var positions = mesh.positionsInternal;
var indices = face.indexesInternal;
for (int i = 0; i < indices.Length; i += 3)
{
float area = Math.TriangleArea(
positions[indices[i + 0]],
positions[indices[i + 1]],
positions[indices[i + 2]]);
if (area <= Mathf.Epsilon)
return true;
}
return false;
}
/// <summary>
/// Checks whether any triangles in a face are disconnected (non-contiguous).
/// </summary>
/// <param name="mesh">The mesh that owns the face to test.</param>
/// <param name="face">The face to test.</param>
/// <returns>True if the face contains split triangles; false if the face is contiguous.</returns>
public static bool ContainsNonContiguousTriangles(this ProBuilderMesh mesh, Face face)
{
Edge current = face.edgesInternal[0], start = current;
int index = current.a;
int count = 1;
while (face.TryGetNextEdge(current, current.b, ref current, ref index)
&& current != start
&& count < face.edgesInternal.Length)
{
count++;
}
return count != face.edgesInternal.Length;
}
/// <summary>
/// Ensures that each face in the specified set is composed of contiguous triangle sets. If a face contains any
/// non-contiguous triangles, this method splits them into as many faces as necessary to ensure that each group
/// of adjacent triangles compose a single face.
/// </summary>
/// <param name="mesh">The mesh that contains the faces to test.</param>
/// <param name="faces">The faces to test for non-contiguous triangles.</param>
/// <returns>
/// A list of any newly created faces as a result of splitting non-contiguous triangles. Returns an
/// empty list if no faces required fixing.
/// </returns>
public static List<Face> EnsureFacesAreComposedOfContiguousTriangles(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
var appended = new List<Face>();
foreach (var face in faces)
{
if (ContainsNonContiguousTriangles(mesh, face))
{
var groups = CollectFaceGroups(mesh, face);
if (groups.Count < 2)
continue;
face.SetIndexes(groups[0].SelectMany(x=>x.indices));
for (int i = 1; i < groups.Count; i++)
{
var duplicate = new Face(face);
duplicate.SetIndexes(groups[i].SelectMany(x => x.indices));
appended.Add(duplicate);
}
}
}
var rebuilt = new List<Face>(mesh.facesInternal);
rebuilt.AddRange(appended);
mesh.faces = rebuilt;
return appended;
}
internal static List<List<Triangle>> CollectFaceGroups(this ProBuilderMesh mesh, Face face)
{
var groups = new List<List<Triangle>>();
var indices = face.indexesInternal;
for (int i = 0; i < indices.Length; i += 3)
{
var triangle = new Triangle(indices[i], indices[i+1], indices[i+2]);
var matched = false;
for(int n = 0; n < groups.Count; n++)
{
// this doesn't account for triangles that are adjacent through coincident vertices
if (groups[n].Any(x => x.IsAdjacent(triangle)))
{
groups[n].Add(triangle);
matched = true;
break;
}
}
if (!matched)
groups.Add(new List<Triangle>() { triangle });
}
return groups;
}
/// <summary>
/// Iterates through all faces in a mesh and removes any triangles with an area less than `float.Epsilon`, or with
/// indices that point to the same vertex. This function also enforces the rule that a face must contain no
/// coincident vertices.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="removed">An optional list to be populated with the removed indices. If no degenerate triangles are found, this list contains no elements.</param>
/// <returns>True if degenerate triangles were found and removed; false if no degenerate triangles were found.</returns>
public static bool RemoveDegenerateTriangles(ProBuilderMesh mesh, List<int> removed = null)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
Dictionary<int, int> m_Lookup = mesh.sharedVertexLookup;
Dictionary<int, int> m_LookupUV = mesh.sharedTextureLookup;
Vector3[] m_Positions = mesh.positionsInternal;
Dictionary<int, int> m_RebuiltLookup = new Dictionary<int, int>(m_Lookup.Count);
Dictionary<int, int> m_RebuiltLookupUV = new Dictionary<int, int>(m_LookupUV.Count);
List<Face> m_RebuiltFaces = new List<Face>(mesh.faceCount);
Dictionary<int, int> m_DuplicateIndexFilter = new Dictionary<int, int>(8);
foreach (Face face in mesh.facesInternal)
{
m_DuplicateIndexFilter.Clear();
List<int> tris = new List<int>();
int[] ind = face.indexesInternal;
for (int i = 0; i < ind.Length; i += 3)
{
float area = Math.TriangleArea(m_Positions[ind[i + 0]], m_Positions[ind[i + 1]], m_Positions[ind[i + 2]]);
if (area > Mathf.Epsilon)
{
// Index in the positions array
int triangleIndexA = ind[i],
triangleIndexB = ind[i+1],
triangleIndexC = ind[i+2];
// Common index (also called SharedIndexHandle)
int sharedIndexA = m_Lookup[triangleIndexA],
sharedIndexB = m_Lookup[triangleIndexB],
sharedIndexC = m_Lookup[triangleIndexC];
// test if there are any duplicates in the triangle
if (!(sharedIndexA == sharedIndexB || sharedIndexA == sharedIndexC || sharedIndexB == sharedIndexC))
{
int index;
// catch case where face has two distinct vertices that are in fact coincident.
if (!m_DuplicateIndexFilter.TryGetValue(sharedIndexA, out index))
m_DuplicateIndexFilter.Add(sharedIndexA, triangleIndexA);
else
triangleIndexA = index;
if (!m_DuplicateIndexFilter.TryGetValue(sharedIndexB, out index))
m_DuplicateIndexFilter.Add(sharedIndexB, triangleIndexB);
else
triangleIndexB = index;
if (!m_DuplicateIndexFilter.TryGetValue(sharedIndexC, out index))
m_DuplicateIndexFilter.Add(sharedIndexC, triangleIndexC);
else
triangleIndexC = index;
tris.Add(triangleIndexA);
tris.Add(triangleIndexB);
tris.Add(triangleIndexC);
if (!m_RebuiltLookup.ContainsKey(triangleIndexA))
m_RebuiltLookup.Add(triangleIndexA, sharedIndexA);
if (!m_RebuiltLookup.ContainsKey(triangleIndexB))
m_RebuiltLookup.Add(triangleIndexB, sharedIndexB);
if (!m_RebuiltLookup.ContainsKey(triangleIndexC))
m_RebuiltLookup.Add(triangleIndexC, sharedIndexC);
if (m_LookupUV.ContainsKey(triangleIndexA) && !m_RebuiltLookupUV.ContainsKey(triangleIndexA))
m_RebuiltLookupUV.Add(triangleIndexA, m_LookupUV[triangleIndexA]);
if (m_LookupUV.ContainsKey(triangleIndexB) && !m_RebuiltLookupUV.ContainsKey(triangleIndexB))
m_RebuiltLookupUV.Add(triangleIndexB, m_LookupUV[triangleIndexB]);
if (m_LookupUV.ContainsKey(triangleIndexC) && !m_RebuiltLookupUV.ContainsKey(triangleIndexC))
m_RebuiltLookupUV.Add(triangleIndexC, m_LookupUV[triangleIndexC]);
}
}
}
if (tris.Count > 0)
{
face.indexesInternal = tris.ToArray();
m_RebuiltFaces.Add(face);
}
}
mesh.faces = m_RebuiltFaces;
mesh.SetSharedVertices(m_RebuiltLookup);
mesh.SetSharedTextures(m_RebuiltLookupUV);
return RemoveUnusedVertices(mesh, removed);
}
/// <summary>
/// Removes vertices that no face references.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="removed">An optional list to be populated with the removed indices. If no vertices are removed, this list contains no elements.</param>
/// <returns>A list of deleted vertex indices.</returns>
public static bool RemoveUnusedVertices(ProBuilderMesh mesh, List<int> removed = null)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
bool saveRemoved = removed != null;
if(saveRemoved)
removed.Clear();
var del = saveRemoved ? removed : new List<int>();
var tris = new HashSet<int>(mesh.facesInternal.SelectMany(x => x.indexes));
for (int i = 0; i < mesh.positionsInternal.Length; i++)
if (!tris.Contains(i))
del.Add(i);
mesh.DeleteVertices(del);
return del.Count > 0;
}
/// <summary>
/// Rebuild a collection of indexes accounting for the removal of a collection of indices.
/// </summary>
/// <param name="indices">The indices to rebuild.</param>
/// <param name="removed">A sorted collection indices that were removed.</param>
/// <returns>A new list of indices pointing to the same vertex as they were prior to the removal of some entries.</returns>
internal static List<int> RebuildIndexes(IEnumerable<int> indices, List<int> removed)
{
var res = new List<int>();
var rmc = removed.Count;
foreach (var index in indices)
{
var nearestIndex = ArrayUtility.NearestIndexPriorToValue(removed, index) + 1;
// don't add back into the indices collection if the index was removed
if (nearestIndex > -1 && nearestIndex < rmc && removed[nearestIndex] == index)
continue;
res.Add(index - nearestIndex);
}
return res;
}
/// <summary>
/// Rebuild a collection of indexes accounting for the removal of a collection of indices.
/// </summary>
/// <param name="edges">The indices to rebuild.</param>
/// <param name="removed">A sorted collection indices that were removed.</param>
/// <returns>A new list of indices pointing to the same vertex as they were prior to the removal of some entries.</returns>
internal static List<Edge> RebuildEdges(IEnumerable<Edge> edges, List<int> removed)
{
var res = new List<Edge>();
var rmc = removed.Count;
foreach (var edge in edges)
{
var nearestIndexA = ArrayUtility.NearestIndexPriorToValue(removed, edge.a) + 1;
var nearestIndexB = ArrayUtility.NearestIndexPriorToValue(removed, edge.b) + 1;
// don't add back into the indices collection if the index was removed
if ((nearestIndexA > -1 && nearestIndexA < rmc && removed[nearestIndexA] == edge.a) ||
(nearestIndexB > -1 && nearestIndexB < rmc && removed[nearestIndexB] == edge.b))
continue;
res.Add(new Edge(edge.a - nearestIndexA, edge.b - nearestIndexB));
}
return res;
}
internal static void RebuildSelectionIndexes(ProBuilderMesh mesh, ref Face[] faces, ref Edge[] edges, ref int[] indices, IEnumerable<int> removed)
{
var rm = removed.ToList();
rm.Sort();
if (faces != null && faces.Length > 0)
faces = faces.Where(x => mesh.facesInternal.Contains(x)).ToArray();
if(edges != null && edges.Length > 0)
edges = RebuildEdges(edges, rm).ToArray();
if(indices != null && indices.Length > 0)
indices = RebuildIndexes(indices, rm).ToArray();
}
/// <summary>
/// Check a mesh for degenerate triangles or unused vertices, and remove them if necessary.
/// </summary>
/// <param name="mesh">The mesh to test.</param>
/// <param name="removedVertices">If fixes were made, this will be set to the number of vertices removed during that process.</param>
/// <returns>Returns true if no problems were found, false if topology issues were discovered and fixed.</returns>
internal static bool EnsureMeshIsValid(ProBuilderMesh mesh, out int removedVertices)
{
removedVertices = 0;
if (ContainsDegenerateTriangles(mesh))
{
var faces = mesh.selectedFacesInternal;
var edges = mesh.selectedEdgesInternal;
var indices = mesh.selectedIndexesInternal;
List<int> removed = new List<int>();
if (RemoveDegenerateTriangles(mesh, removed))
{
mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
RebuildSelectionIndexes(mesh, ref faces, ref edges, ref indices, removed);
mesh.selectedFacesInternal = faces;
mesh.selectedEdgesInternal = edges;
mesh.selectedIndexesInternal = indices;
removedVertices = removed.Count;
return false;
}
}
EnsureValidAttributes(mesh);
return true;
}
enum AttributeValidationStrategy
{
Resize,
Nullify
}
static void EnsureRealNumbers(IList<Vector2> attribute)
{
for (int i = 0, c = attribute?.Count ?? 0; i < c; i++)
attribute[i] = Math.FixNaN(attribute[i]);
}
static void EnsureRealNumbers(IList<Vector3> attribute)
{
for (int i = 0, c = attribute?.Count ?? 0; i < c; i++)
attribute[i] = Math.FixNaN(attribute[i]);
}
static void EnsureRealNumbers(IList<Vector4> attribute)
{
for (int i = 0, c = attribute?.Count ?? 0; i < c; i++)
attribute[i] = Math.FixNaN(attribute[i]);
}
static void EnsureArraySize<T>(ref T[] attribute,
int expectedVertexCount,
AttributeValidationStrategy strategy = AttributeValidationStrategy.Nullify,
T fill = default)
{
if (attribute == null || attribute.Length == expectedVertexCount)
return;
if (strategy == AttributeValidationStrategy.Nullify)
{
attribute = null;
return;
}
int previous = attribute.Length;
Array.Resize(ref attribute, expectedVertexCount);
for (int i = previous - 1; i < expectedVertexCount; i++)
attribute[i] = fill;
}
static void EnsureListSize<T>(ref List<T> attribute,
int expectedVertexCount,
AttributeValidationStrategy strategy = AttributeValidationStrategy.Nullify,
T fill = default)
{
if (attribute == null || attribute.Count == expectedVertexCount)
return;
if (strategy == AttributeValidationStrategy.Nullify)
{
attribute = null;
return;
}
var prev = attribute.Count;
var copy = new List<T>(expectedVertexCount);
for (int i = 0, c = Mathf.Min(prev, expectedVertexCount); i < c; i++)
copy.Add(attribute[i]);
for (int i = copy.Count - 1; i < expectedVertexCount; i++)
copy.Add(fill);
attribute = copy;
}
static void EnsureValidAttributes(ProBuilderMesh mesh)
{
var vertexCount = mesh.vertexCount;
var normals = mesh.normalsInternal;
var colors = mesh.colorsInternal;
var tangents = mesh.tangentsInternal;
var uv0 = mesh.texturesInternal;
var uv2 = mesh.textures2Internal;
var uv3 = mesh.textures3Internal;
EnsureArraySize(ref normals, vertexCount);
EnsureArraySize(ref colors, vertexCount);
EnsureArraySize(ref tangents, vertexCount);
EnsureArraySize(ref normals, vertexCount);
EnsureArraySize(ref uv0, vertexCount);
EnsureListSize(ref uv2, vertexCount);
EnsureListSize(ref uv3, vertexCount);
EnsureRealNumbers(normals);
EnsureRealNumbers(tangents);
EnsureRealNumbers(normals);
EnsureRealNumbers(uv0);
EnsureRealNumbers(uv2);
EnsureRealNumbers(uv3);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12d2df8ff0d1e417e97df69e20c4c81b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,161 @@
using System.Collections.Generic;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Provides a helper function to manage converting triangulated polygons to [quads](../manual/gloss.html#quad).
/// </summary>
public static class QuadUtility
{
/// <summary>
/// Converts the faces to quads if possible.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="faces">The list of faces to process.</param>
/// <param name="smoothing">True to apply smoothing.</param>
/// <returns>A list of the processed faces.</returns>
public static List<Face> ToQuads(this ProBuilderMesh mesh, IList<Face> faces, bool smoothing = true)
{
HashSet<Face> processed = new HashSet<Face>();
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces, true);
// build a lookup of the strength of edge connections between triangle faces
Dictionary<EdgeLookup, float> connections = new Dictionary<EdgeLookup, float>();
for (int i = 0; i < wings.Count; i++)
{
using (var it = new WingedEdgeEnumerator(wings[i]))
{
while (it.MoveNext())
{
var border = it.Current;
if (border.opposite != null && !connections.ContainsKey(border.edge))
{
float score = mesh.GetQuadScore(border, border.opposite);
connections.Add(border.edge, score);
}
}
}
}
List<SimpleTuple<Face, Face>> quads = new List<SimpleTuple<Face, Face>>();
// move through each face and find it's best quad neighbor
foreach (WingedEdge face in wings)
{
if (!processed.Add(face.face))
continue;
float bestScore = 0f;
Face buddy = null;
using (var it = new WingedEdgeEnumerator(face))
{
while (it.MoveNext())
{
var border = it.Current;
if (border.opposite != null && processed.Contains(border.opposite.face))
continue;
float borderScore;
// only add it if the opposite face's best score is also this face
if (connections.TryGetValue(border.edge, out borderScore) &&
borderScore > bestScore &&
face.face == GetBestQuadConnection(border.opposite, connections))
{
bestScore = borderScore;
buddy = border.opposite.face;
}
}
}
if (buddy != null)
{
processed.Add(buddy);
quads.Add(new SimpleTuple<Face, Face>(face.face, buddy));
}
}
// don't collapse coincident vertices if smoothing is enabled, we need the original normals intact
return MergeElements.MergePairs(mesh, quads, smoothing);
}
static Face GetBestQuadConnection(WingedEdge wing, Dictionary<EdgeLookup, float> connections)
{
float score = 0f;
Face face = null;
using (var it = new WingedEdgeEnumerator(wing))
{
while (it.MoveNext())
{
var border = it.Current;
float s = 0f;
if (connections.TryGetValue(border.edge, out s) && s > score)
{
score = connections[border.edge];
face = border.opposite.face;
}
}
}
return face;
}
/**
* Get a weighted value for the quality of a quad composed of two triangles. 0 is terrible, 1 is perfect.
* normalThreshold will discard any quads where the dot product of their normals is less than the threshold.
* @todo Abstract the quad detection to a separate class so it can be applied to pb_Objects.
*/
static float GetQuadScore(this ProBuilderMesh mesh, WingedEdge left, WingedEdge right, float normalThreshold = .9f)
{
Vertex[] vertices = mesh.GetVertices();
int[] quad = WingedEdge.MakeQuad(left, right);
if (quad == null)
return 0f;
// first check normals
Vector3 leftNormal = Math.Normal(vertices[quad[0]].position, vertices[quad[1]].position, vertices[quad[2]].position);
Vector3 rightNormal = Math.Normal(vertices[quad[2]].position, vertices[quad[3]].position, vertices[quad[0]].position);
float score = Vector3.Dot(leftNormal, rightNormal);
if (score < normalThreshold)
return 0f;
// next is right-angle-ness check
Vector3 a = (vertices[quad[1]].position - vertices[quad[0]].position);
Vector3 b = (vertices[quad[2]].position - vertices[quad[1]].position);
Vector3 c = (vertices[quad[3]].position - vertices[quad[2]].position);
Vector3 d = (vertices[quad[0]].position - vertices[quad[3]].position);
a.Normalize();
b.Normalize();
c.Normalize();
d.Normalize();
float da = Mathf.Abs(Vector3.Dot(a, b));
float db = Mathf.Abs(Vector3.Dot(b, c));
float dc = Mathf.Abs(Vector3.Dot(c, d));
float dd = Mathf.Abs(Vector3.Dot(d, a));
score += 1f - ((da + db + dc + dd) * .25f);
// and how close to parallel the opposite sides area
score += Mathf.Abs(Vector3.Dot(a, c)) * .5f;
score += Mathf.Abs(Vector3.Dot(b, d)) * .5f;
// the three tests each contribute 1
return score * .33f;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b8caa3be45dde847b41aafc7d7db796
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEngine;
using System.Collections.Generic;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Subdivide a ProBuilder mesh.
/// </summary>
static class Subdivision
{
/// <summary>
/// Subdivide all faces on the mesh.
/// </summary>
/// <remarks>More accurately, this inserts a vertex at the center of each face and connects each edge at it's center.</remarks>
/// <param name="pb"></param>
/// <returns></returns>
public static ActionResult Subdivide(this ProBuilderMesh pb)
{
return pb.Subdivide(pb.facesInternal) != null ? new ActionResult(ActionResult.Status.Success, "Subdivide") : new ActionResult(ActionResult.Status.Failure, "Subdivide Failed");
}
/// <summary>
/// Subdivide a mesh, optionally restricting to the specified faces.
/// </summary>
/// <param name="pb"></param>
/// <param name="faces">The faces to be affected by subdivision.</param>
/// <returns>The faces created as a result of the subdivision.</returns>
public static Face[] Subdivide(this ProBuilderMesh pb, IList<Face> faces)
{
return ConnectElements.Connect(pb, faces);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08c8ab0f8013d492eb6d0d78a261f745
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,374 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Utilities for working with triangle and quad primitives.
/// </summary>
public static class SurfaceTopology
{
/// <summary>
/// Converts a selection of faces from n-gons to triangles.
///
/// When this method successfully converts a face to triangles, it creates each new triangle as a separate face
/// and deletes the original face.
/// </summary>
/// <param name="mesh">The target mesh.</param>
/// <param name="faces">The faces to convert from quads to triangles.</param>
/// <returns>Any new triangle faces created by breaking faces into individual triangles.</returns>
public static Face[] ToTriangles(this ProBuilderMesh mesh, IList<Face> faces)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
if (faces == null)
throw new System.ArgumentNullException("faces");
List<Vertex> vertices = new List<Vertex>(mesh.GetVertices());
Dictionary<int, int> lookup = mesh.sharedVertexLookup;
List<FaceRebuildData> rebuild = new List<FaceRebuildData>();
foreach (Face face in faces)
{
List<FaceRebuildData> res = BreakFaceIntoTris(face, vertices, lookup);
rebuild.AddRange(res);
}
FaceRebuildData.Apply(rebuild, mesh, vertices, null);
mesh.DeleteFaces(faces);
mesh.ToMesh();
return rebuild.Select(x => x.face).ToArray();
}
static List<FaceRebuildData> BreakFaceIntoTris(Face face, List<Vertex> vertices, Dictionary<int, int> lookup)
{
int[] tris = face.indexesInternal;
int triCount = tris.Length;
List<FaceRebuildData> rebuild = new List<FaceRebuildData>(triCount / 3);
for (int i = 0; i < triCount; i += 3)
{
FaceRebuildData r = new FaceRebuildData();
r.face = new Face(face);
r.face.indexesInternal = new int[] { 0, 1, 2};
r.vertices = new List<Vertex>() {
vertices[tris[i]],
vertices[tris[i + 1]],
vertices[tris[i + 2]]
};
r.sharedIndexes = new List<int>() {
lookup[tris[i]],
lookup[tris[i + 1]],
lookup[tris[i + 2]]
};
rebuild.Add(r);
}
return rebuild;
}
/// <summary>
/// Returns the [winding order](../manual/gloss.html#winding) for a face.
/// </summary>
/// <param name="mesh">The source mesh.</param>
/// <param name="face">The face to test.</param>
/// <returns>The winding order if successful; <see cref="WindingOrder.Unknown"/> if not.</returns>
public static WindingOrder GetWindingOrder(this ProBuilderMesh mesh, Face face)
{
Vector2[] p = Projection.PlanarProject(mesh.positionsInternal, face.distinctIndexesInternal);
return GetWindingOrder(p);
}
static WindingOrder GetWindingOrder(IList<Vertex> vertices, IList<int> indexes)
{
if (vertices == null)
throw new ArgumentNullException("vertices");
if (indexes == null)
throw new ArgumentNullException("indexes");
Vector2[] p = Projection.PlanarProject(vertices.Select(x => x.position).ToArray(), indexes);
return GetWindingOrder(p);
}
/// <summary>
/// Returns the winding order for a set of ordered points.
/// </summary>
/// <remarks>http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order</remarks>
/// <param name="points">A path of points in 2D space.</param>
/// <returns>The winding order if found; <see cref="WindingOrder.Unknown"/> if not.</returns>
public static WindingOrder GetWindingOrder(IList<Vector2> points)
{
if (points == null)
throw new ArgumentNullException("points");
float sum = 0f;
int len = points.Count;
// http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
for (int i = 0; i < len; i++)
{
Vector2 a = points[i];
Vector2 b = i < len - 1 ? points[i + 1] : points[0];
sum += ((b.x - a.x) * (b.y + a.y));
}
return sum == 0f ? WindingOrder.Unknown : (sum > 0f ? WindingOrder.Clockwise : WindingOrder.CounterClockwise);
}
/// <summary>
/// Reverses the orientation of the middle edge in a quad.
/// <![CDATA[
/// ```
/// . _____ _____
/// . |\ | | /|
/// . | \ | => | / |
/// . |____\| |/____|
/// ```
/// ]]>
///
/// This is the equivalent to the [Flip Face Edge](../manual/Face_FlipTri.html) action.
/// </summary>
/// <param name="mesh">The mesh that face belongs to.</param>
/// <param name="face">The target face.</param>
/// <returns>True if successful; false if not. This operation will fail if the face does not contain two triangles with exactly two shared vertices.</returns>
public static bool FlipEdge(this ProBuilderMesh mesh, Face face)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (face == null)
throw new ArgumentNullException("face");
int[] indexes = face.indexesInternal;
if (indexes.Length != 6)
return false;
int[] mode = ArrayUtility.Fill<int>(1, indexes.Length);
for (int x = 0; x < indexes.Length - 1; x++)
{
for (int y = x + 1; y < indexes.Length; y++)
{
if (indexes[x] == indexes[y])
{
mode[x]++;
mode[y]++;
}
}
}
if (mode[0] + mode[1] + mode[2] != 5 ||
mode[3] + mode[4] + mode[5] != 5)
return false;
int i0 = indexes[mode[0] == 1 ? 0 : mode[1] == 1 ? 1 : 2];
int i1 = indexes[mode[3] == 1 ? 3 : mode[4] == 1 ? 4 : 5];
int used = -1;
if (mode[0] == 2)
{
used = indexes[0];
indexes[0] = i1;
}
else if (mode[1] == 2)
{
used = indexes[1];
indexes[1] = i1;
}
else if (mode[2] == 2)
{
used = indexes[2];
indexes[2] = i1;
}
if (mode[3] == 2 && indexes[3] != used)
indexes[3] = i0;
else if (mode[4] == 2 && indexes[4] != used)
indexes[4] = i0;
else if (mode[5] == 2 && indexes[5] != used)
indexes[5] = i0;
face.InvalidateCache();
return true;
}
/// <summary>
/// Ensures that all adjacent face normals are pointing in a uniform direction.
/// This function supports multiple islands of connected faces, but it may not unify each island the same way.
///
/// This is equivalent to the [Conform Normals (Faces)](../manual/Face_ConformNormals.html) action.
/// </summary>
/// <param name="mesh">The mesh that the faces belong to.</param>
/// <param name="faces">The faces to make uniform.</param>
/// <returns>The result of the action.</returns>
public static ActionResult ConformNormals(this ProBuilderMesh mesh, IEnumerable<Face> faces)
{
List<WingedEdge> wings = WingedEdge.GetWingedEdges(mesh, faces);
HashSet<Face> used = new HashSet<Face>();
int count = 0;
// this loop adds support for multiple islands of grouped selections
for (int i = 0; i < wings.Count; i++)
{
if (used.Contains(wings[i].face))
continue;
Dictionary<Face, bool> flags = new Dictionary<Face, bool>();
GetWindingFlags(wings[i], true, flags);
int flip = 0;
foreach (var kvp in flags)
flip += kvp.Value ? 1 : -1;
bool direction = flip > 0;
foreach (var kvp in flags)
{
if (direction != kvp.Value)
{
count++;
kvp.Key.Reverse();
}
}
used.UnionWith(flags.Keys);
}
if (count > 0)
return new ActionResult(ActionResult.Status.Success, count > 1 ? string.Format("Flipped {0} faces", count) : "Flipped 1 face");
else
return new ActionResult(ActionResult.Status.NoChange, "Faces Uniform");
}
static void GetWindingFlags(WingedEdge edge, bool flag, Dictionary<Face, bool> flags)
{
flags.Add(edge.face, flag);
WingedEdge next = edge;
do
{
WingedEdge opp = next.opposite;
if (opp != null && !flags.ContainsKey(opp.face))
{
Edge cea = GetCommonEdgeInWindingOrder(next);
Edge ceb = GetCommonEdgeInWindingOrder(opp);
GetWindingFlags(opp, cea.a == ceb.a ? !flag : flag, flags);
}
next = next.next;
}
while (next != edge);
}
/// <summary>
/// Ensure the opposite face to source matches the winding order.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
internal static ActionResult ConformOppositeNormal(WingedEdge source)
{
if (source == null || source.opposite == null)
return new ActionResult(ActionResult.Status.Failure, "Source edge does not share an edge with another face.");
Edge cea = GetCommonEdgeInWindingOrder(source);
Edge ceb = GetCommonEdgeInWindingOrder(source.opposite);
if (cea.a == ceb.a)
{
source.opposite.face.Reverse();
return new ActionResult(ActionResult.Status.Success, "Reversed target face winding order.");
}
return new ActionResult(ActionResult.Status.NoChange, "Faces already unified.");
}
/// <summary>
/// Iterate a face and return a new common edge where the edge indexes are true to the triangle winding order.
/// </summary>
/// <param name="wing"></param>
/// <returns></returns>
static Edge GetCommonEdgeInWindingOrder(WingedEdge wing)
{
int[] indexes = wing.face.indexesInternal;
int len = indexes.Length;
for (int i = 0; i < len; i += 3)
{
Edge e = wing.edge.local;
int a = indexes[i], b = indexes[i + 1], c = indexes[i + 2];
if (e.a == a && e.b == b)
return wing.edge.common;
else if (e.a == b && e.b == a)
return new Edge(wing.edge.common.b, wing.edge.common.a);
else if (e.a == b && e.b == c)
return wing.edge.common;
else if (e.a == c && e.b == b)
return new Edge(wing.edge.common.b, wing.edge.common.a);
else if (e.a == c && e.b == a)
return wing.edge.common;
else if (e.a == a && e.b == c)
return new Edge(wing.edge.common.b, wing.edge.common.a);
}
return Edge.Empty;
}
/// <summary>
/// Match a target face to the source face. Faces must be adjacent.
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="lookup"></param>
internal static void MatchNormal(Face source, Face target, Dictionary<int, int> lookup)
{
List<EdgeLookup> sourceEdges = EdgeLookup.GetEdgeLookup(source.edgesInternal, lookup).ToList();
List<EdgeLookup> targetEdges = EdgeLookup.GetEdgeLookup(target.edgesInternal, lookup).ToList();
bool superBreak = false;
Edge src, tar;
for (int i = 0; !superBreak && i < sourceEdges.Count; i++)
{
src = sourceEdges[i].common;
for (int n = 0; !superBreak && n < targetEdges.Count; n++)
{
tar = targetEdges[n].common;
if (src.Equals(tar))
{
if (src.a == tar.a)
target.Reverse();
superBreak = true;
}
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 214db8084d63e06499f41c215a8547a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,264 @@
using UnityEngine;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.ProBuilder.Poly2Tri;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// Wrapper around Triangle.NET triangulation methods. https://github.com/zon/triangle
/// </summary>
static class Triangulation
{
static TriangulationContext s_TriangulationContext;
static TriangulationContext triangulationContext
{
get
{
if(s_TriangulationContext == null)
s_TriangulationContext = new DTSweepContext();
return s_TriangulationContext;
}
}
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void ResetStaticsOnLoad()
{
s_TriangulationContext = null;
}
#endif
/// <summary>
/// Given a set of points this method will format the points into a boundary contour and triangulate, returning
/// a set of indexes that corresponds to the original ordering.
/// </summary>
/// <param name="points"></param>
/// <param name="indexes"></param>
/// <param name="convex"></param>
/// <returns></returns>
public static bool SortAndTriangulate(IList<Vector2> points, out List<int> indexes, bool convex = false)
{
IList<Vector2> sorted = Projection.Sort(points, SortMethod.CounterClockwise);
Dictionary<int, int> map = new Dictionary<int, int>();
for (int i = 0; i < sorted.Count; i++)
map.Add(i, points.IndexOf(sorted[i]));
if (!Triangulate(sorted, out indexes, convex))
return false;
for (int i = 0; i < indexes.Count; i++)
indexes[i] = map[indexes[i]];
return true;
}
/// <summary>
/// Attempts to triangulate a set of vertices. If unordered is specified as false vertices will not be re-ordered before triangulation.
/// </summary>
/// <param name="vertices"></param>
/// <param name="triangles"></param>
/// <param name="unordered"></param>
/// <param name="convex"></param>
/// <returns></returns>
public static bool TriangulateVertices(IList<Vertex> vertices, out List<int> triangles, bool unordered = true, bool convex = false)
{
Vector3[] facePoints = new Vector3[vertices.Count];
for (int i = 0; i < vertices.Count; ++i)
facePoints[i] = vertices[i].position;
return TriangulateVertices(facePoints, out triangles, unordered, convex);
}
/// <summary>
/// Attempts to triangulate an ordered set of vertices. Optionally with a set of hole polygons.
/// </summary>
/// <param name="vertices">Ordered set of vertices</param>
/// <param name="triangles">Resulting set of indices. Indices outside the vertices array are hole vertices.
/// When creating a mesh, add all the hole vertices to the vertices array so that the indices are valid.
/// </param>
/// <param name="holes">Jagged array containing sets of vertices that make up holes in the polygon.</param>
/// <returns></returns>
public static bool TriangulateVertices(Vector3[] vertices, out List<int> triangles, Vector3[][] holes = null)
{
triangles = null;
int vertexCount = vertices == null ? 0 : vertices.Length;
if (vertexCount < 3)
return false;
var normal = Projection.FindBestPlane(vertices).normal;
Vector2[] points2d = Projection.PlanarProject(vertices, null, normal);
Vector2[][] holes2d = null;
if (holes != null)
{
holes2d = new Vector2[holes.Length][];
for (int i = 0; i < holes.Length; i++)
{
if(holes[i].Length < 3)
return false;
holes2d[i] = Projection.PlanarProject(holes[i], null, normal);
}
}
return Triangulate(points2d, holes2d, out triangles);
}
public static bool TriangulateVertices(Vector3[] vertices, out List<int> triangles, bool unordered = true, bool convex = false)
{
triangles = null;
int vertexCount = vertices == null ? 0 : vertices.Length;
if (vertexCount < 3)
return false;
if (vertexCount == 3)
{
triangles = new List<int>() { 0, 1, 2 };
return true;
}
Vector2[] points2d = Projection.PlanarProject(vertices);
if (unordered)
return SortAndTriangulate(points2d, out triangles, convex);
return Triangulate(points2d, out triangles, convex);
}
/// <summary>
/// Given a set of points ordered counter-clockwise along a contour, return triangle indexes.
/// </summary>
/// <param name="points"></param>
/// <param name="indexes"></param>
/// <param name="convex">Triangulation may optionally be set to convex, which will result in some a convex shape.</param>
/// <returns></returns>
public static bool Triangulate(IList<Vector2> points, out List<int> indexes, bool convex = false)
{
indexes = new List<int>();
int index = 0;
Triangulatable soup = convex
? new PointSet(points.Select(x => new TriangulationPoint(x.x, x.y, index++)).ToList())
: (Triangulatable) new Polygon(points.Select(x => new PolygonPoint(x.x, x.y, index++)));
try
{
triangulationContext.Clear();
triangulationContext.PrepareTriangulation(soup);
DTSweep.Triangulate((DTSweepContext)triangulationContext);
}
catch (System.Exception e)
{
Log.Info("Triangulation failed: " + e.ToString());
return false;
}
foreach (DelaunayTriangle d in soup.Triangles)
{
if (d.Points[0].Index < 0 || d.Points[1].Index < 0 || d.Points[2].Index < 0)
{
Log.Info("Triangulation failed: Additional vertices were inserted.");
return false;
}
indexes.Add(d.Points[0].Index);
indexes.Add(d.Points[1].Index);
indexes.Add(d.Points[2].Index);
}
WindingOrder originalWinding = SurfaceTopology.GetWindingOrder(points);
// if the re-triangulated first tri doesn't match the winding order of the original
// vertices, flip 'em
if (SurfaceTopology.GetWindingOrder(new Vector2[3]
{
points[indexes[0]],
points[indexes[1]],
points[indexes[2]],
}) != originalWinding)
indexes.Reverse();
return true;
}
/// <summary>
/// Given a set of points ordered counter-clockwise along a contour and a set of holes, return triangle indexes.
/// </summary>
/// <param name="points"></param>
/// <param name="holes"></param>
/// <param name="indexes">Indices outside of the points list index into holes when layed out linearly.
/// {vertices 0,1,2...vertices.length-1, holes 0 values, hole 1 values etc.} </param>
/// <returns></returns>
public static bool Triangulate(IList<Vector2> points, IList<IList<Vector2>> holes, out List<int> indexes)
{
indexes = new List<int>();
int index = 0;
var allPoints = new List<Vector2>(points);
Polygon polygon = new Polygon(points.Select(x => new PolygonPoint(x.x, x.y, index++)));
if (holes != null)
{
for (int i = 0; i < holes.Count; i++)
{
allPoints.AddRange(holes[i]);
var holePolgyon = new Polygon(holes[i].Select(x => new PolygonPoint(x.x, x.y, index++)));
polygon.AddHole(holePolgyon);
}
}
try
{
triangulationContext.Clear();
triangulationContext.PrepareTriangulation(polygon);
DTSweep.Triangulate((DTSweepContext)triangulationContext);
}
catch (System.Exception e)
{
Log.Info("Triangulation failed: " + e.ToString());
return false;
}
foreach (DelaunayTriangle d in polygon.Triangles)
{
if (d.Points[0].Index < 0 || d.Points[1].Index < 0 || d.Points[2].Index < 0)
{
Log.Info("Triangulation failed: Additional vertices were inserted.");
return false;
}
indexes.Add(d.Points[0].Index);
indexes.Add(d.Points[1].Index);
indexes.Add(d.Points[2].Index);
}
WindingOrder originalWinding = SurfaceTopology.GetWindingOrder(points);
// if the re-triangulated first tri doesn't match the winding order of the original
// vertices, flip 'em
if (SurfaceTopology.GetWindingOrder(new Vector2[3]
{
allPoints[indexes[0]],
allPoints[indexes[1]],
allPoints[indexes[2]],
}) != originalWinding)
indexes.Reverse();
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 146403a091a2c4d61976892730fa0c8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 59a80f89e25b748448bb804c542f7cdb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,139 @@
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
static partial class UVEditing
{
/// <summary>
/// Provided two faces, this method will attempt to project @f2 and align its size, rotation, and position to match
/// the shared edge on f1. Returns true on success, false otherwise.
/// </summary>
/// <param name="mesh">The mesh containing the faces</param>
/// <param name="f1">The anchor face</param>
/// <param name="f2">The face to align to the anchor</param>
/// <param name="channel"></param>
/// <returns>true if the autostitching succeeded, else fase</returns>
public static bool AutoStitch(ProBuilderMesh mesh, Face f1, Face f2, int channel)
{
var wings = WingedEdge.GetWingedEdges(mesh, new [] { f1, f2 });
var sharedEdge = wings.FirstOrDefault(x => x.face == f1 && x.opposite != null && x.opposite.face == f2);
if (sharedEdge == null)
return false;
if (f1.manualUV)
f2.manualUV = true;
f1.textureGroup = -1;
f2.textureGroup = -1;
Projection.PlanarProject(mesh, f2);
if (AlignEdges(mesh, f2, sharedEdge.edge.local, sharedEdge.opposite.edge.local, channel))
{
if (!f2.manualUV)
UvUnwrapping.SetAutoAndAlignUnwrapParamsToUVs(mesh, new [] { f2 });
return true;
}
return false;
}
/// <summary>
/// move the UVs to where the edges passed meet
/// </summary>
/// <param name="mesh"></param>
/// <param name="faceToMove"></param>
/// <param name="edgeToAlignTo"></param>
/// <param name="edgeToBeAligned"></param>
/// <param name="channel"></param>
/// <returns></returns>
static bool AlignEdges(ProBuilderMesh mesh, Face faceToMove, Edge edgeToAlignTo, Edge edgeToBeAligned, int channel)
{
Vector2[] uvs = GetUVs(mesh, channel);
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
// Match each edge vertex to the other
int[] matchX = new int[2] { edgeToAlignTo.a, -1 };
int[] matchY = new int[2] { edgeToAlignTo.b, -1 };
int siIndex = mesh.GetSharedVertexHandle(edgeToAlignTo.a);
if (siIndex < 0)
return false;
if (sharedIndexes[siIndex].Contains(edgeToBeAligned.a))
{
matchX[1] = edgeToBeAligned.a;
matchY[1] = edgeToBeAligned.b;
}
else
{
matchX[1] = edgeToBeAligned.b;
matchY[1] = edgeToBeAligned.a;
}
// scale face 2 to match the edge size of f1
float dist_e1 = Vector2.Distance(uvs[edgeToAlignTo.a], uvs[edgeToAlignTo.b]);
float dist_e2 = Vector2.Distance(uvs[edgeToBeAligned.a], uvs[edgeToBeAligned.b]);
float scale = dist_e1 / dist_e2;
// doesn't matter what point we scale around because we'll move it in the next step anyways
foreach (int i in faceToMove.distinctIndexesInternal)
uvs[i] = uvs[i].ScaleAroundPoint(Vector2.zero, Vector2.one * scale);
// Figure out where the center of each edge is so that we can move the f2 edge to match f1's origin
Vector2 f1_center = (uvs[edgeToAlignTo.a] + uvs[edgeToAlignTo.b]) / 2f;
Vector2 f2_center = (uvs[edgeToBeAligned.a] + uvs[edgeToBeAligned.b]) / 2f;
Vector2 diff = f1_center - f2_center;
// Move f2 face to where it's matching edge center is on top of f1's center
foreach (int i in faceToMove.distinctIndexesInternal)
uvs[i] += diff;
// Now that the edge's centers are matching, rotate f2 to match f1's angle
Vector2 angle1 = uvs[matchY[0]] - uvs[matchX[0]];
Vector2 angle2 = uvs[matchY[1]] - uvs[matchX[1]];
float angle = Vector2.Angle(angle1, angle2);
if (Vector3.Cross(angle1, angle2).z < 0)
angle = 360f - angle;
foreach (int i in faceToMove.distinctIndexesInternal)
uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, angle);
float error = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]]));
// now check that the matched UVs are on top of one another if the error allowance is greater than some small value
if (error > .02f)
{
// first try rotating 180 degrees
foreach (int i in faceToMove.distinctIndexesInternal)
uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, 180f);
float e2 = Mathf.Abs(Vector2.Distance(uvs[matchX[0]], uvs[matchX[1]])) + Mathf.Abs(Vector2.Distance(uvs[matchY[0]], uvs[matchY[1]]));
if (e2 < error)
error = e2;
else
{
// flip 'em back around
foreach (int i in faceToMove.distinctIndexesInternal)
uvs[i] = Math.RotateAroundPoint(uvs[i], f1_center, 180f);
}
}
// If successfully aligned, merge the sharedIndexesUV
SplitUVs(mesh, faceToMove.distinctIndexesInternal);
mesh.SetTexturesCoincident(matchX);
mesh.SetTexturesCoincident(matchY);
ApplyUVs(mesh, uvs, channel);
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5f37a0a7c6f54d4995a8f0806351e89
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,414 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// UV actions.
/// </summary>
static partial class UVEditing
{
/// <summary>
/// Get a reference to the mesh UV array at index.
/// </summary>
/// <param name="mesh"></param>
/// <param name="channel">The zero-indexed UV channel.</param>
/// <returns></returns>
internal static Vector2[] GetUVs(ProBuilderMesh mesh, int channel)
{
switch (channel)
{
case 1:
{
Mesh m = mesh.mesh;
if (m == null)
return null;
return mesh.mesh.uv2;
}
case 2:
case 3:
{
if (channel == 2 ? mesh.HasArrays(MeshArrays.Texture2) : mesh.HasArrays(MeshArrays.Texture3))
{
List<Vector4> uvs = new List<Vector4>();
mesh.GetUVs(channel, uvs);
return uvs.Select(x => (Vector2)x).ToArray();
}
return null;
}
default:
return mesh.texturesInternal;
}
}
/// <summary>
/// Sets an array to the appropriate UV channel, but don't refresh the Mesh.
/// </summary>
internal static void ApplyUVs(ProBuilderMesh mesh, Vector2[] uvs, int channel, bool applyToMesh = true)
{
switch (channel)
{
case 0:
mesh.texturesInternal = uvs;
if (applyToMesh && mesh.mesh != null)
mesh.mesh.uv = uvs;
break;
case 1:
if (applyToMesh && mesh.mesh != null)
mesh.mesh.uv2 = uvs;
break;
case 2:
case 3:
int vc = mesh.vertexCount;
if (vc != uvs.Length)
throw new IndexOutOfRangeException("uvs");
List<Vector4> list = new List<Vector4>(vc);
for (int i = 0; i < vc; i++)
list.Add(uvs[i]);
mesh.SetUVs(channel, list);
if (applyToMesh && mesh.mesh != null)
mesh.mesh.SetUVs(channel, list);
break;
}
}
/// <summary>
/// Sews (welds) a UV seam using delta to determine which UVs are close enough to be merged.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
/// <param name="delta"></param>
/// <returns></returns>
public static void SewUVs(this ProBuilderMesh mesh, int[] indexes, float delta)
{
Vector2[] uvs = mesh.texturesInternal;
if (uvs == null || uvs.Length != mesh.vertexCount)
uvs = new Vector2[mesh.vertexCount];
var lookup = mesh.sharedTextureLookup;
for (int i = 0; i < indexes.Length - 1; i++)
{
for (int n = i + 1; n < indexes.Length; n++)
{
int a, b;
if (!lookup.TryGetValue(indexes[i], out a))
lookup.Add(indexes[i], a = lookup.Count);
if (!lookup.TryGetValue(indexes[n], out b))
lookup.Add(indexes[n], b = lookup.Count);
if (a == b)
continue;
if (Vector2.Distance(uvs[indexes[i]], uvs[indexes[n]]) < delta)
{
Vector3 cen = (uvs[indexes[i]] + uvs[indexes[n]]) / 2f;
uvs[indexes[i]] = cen;
uvs[indexes[n]] = cen;
// ToArray prevents delayed execution of linq actions, which cause trouble when modifying the
// dictionary values
var merge = lookup.Where(x => x.Value == b).Select(y => y.Key).ToArray();
foreach (var key in merge)
lookup[key] = a;
}
}
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Similar to Sew, except Collapse just flattens all UVs to the center point no matter the distance.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
public static void CollapseUVs(this ProBuilderMesh mesh, int[] indexes)
{
Vector2[] uvs = mesh.texturesInternal;
// set the shared indexes cache to a unique non-used index
Vector2 cen = Math.Average(ArrayUtility.ValuesWithIndexes(uvs, indexes));
foreach (int i in indexes)
uvs[i] = cen;
mesh.SetTexturesCoincident(indexes);
}
/// <summary>
/// Creates separate entries in shared indexes cache for all passed indexes. If indexes are not present in pb_IntArray[], don't do anything with them.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
public static void SplitUVs(this ProBuilderMesh mesh, IEnumerable<int> indexes)
{
var lookup = mesh.sharedTextureLookup;
var index = lookup.Count;
foreach (var vertex in indexes)
{
int a;
if (lookup.TryGetValue(vertex, out a))
lookup[vertex] = index++;
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Creates separate entries in shared indexes cache for all passed indexes.
/// </summary>
internal static void SplitUVs(ProBuilderMesh mesh, IEnumerable<Face> faces)
{
var lookup = mesh.sharedTextureLookup;
var index = lookup.Count;
foreach(var face in faces)
{
foreach (var vertex in face.distinctIndexesInternal)
{
int a;
if (lookup.TryGetValue(vertex, out a))
lookup[vertex] = index++;
}
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Projects UVs on all passed faces, automatically updating the sharedIndexesUV table as required (only associates
/// vertices that share a seam).
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="channel"></param>
internal static void ProjectFacesAuto(ProBuilderMesh mesh, Face[] faces, int channel)
{
if (faces.Length < 1)
return;
int[] ind = faces.SelectMany(x => x.distinctIndexesInternal).ToArray();
// Get a projection direction by averaging the normals of all selected faces
var projectionDirection = Vector3.zero;
foreach (var face in faces)
{
var nrm = Math.Normal(mesh, face);
projectionDirection += nrm;
}
projectionDirection /= (float) faces.Length;
// project uv coordinates
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, ind, projectionDirection);
// re-assign new projected coords back into full uv array
Vector2[] rebuiltUVs = GetUVs(mesh, channel);
for (int i = 0; i < ind.Length; i++)
rebuiltUVs[ind[i]] = uvs[i];
// and set the msh uv array using the new coordintaes
ApplyUVs(mesh, rebuiltUVs, channel);
// now go trhough and set all adjacent face groups to use matching element groups
foreach (Face f in faces)
{
f.elementGroup = -1;
SplitUVs(mesh, f.distinctIndexesInternal);
}
mesh.SewUVs(faces.SelectMany(x => x.distinctIndexesInternal).ToArray(), .001f);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a box.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="channel"></param>
public static void ProjectFacesBox(ProBuilderMesh mesh, Face[] faces, int channel = 0)
{
Vector2[] uv = GetUVs(mesh, channel);
Dictionary<ProjectionAxis, List<Face>> sorted = new Dictionary<ProjectionAxis, List<Face>>();
for (int i = 0; i < faces.Length; i++)
{
Vector3 nrm = Math.Normal(mesh, faces[i]);
ProjectionAxis axis = Projection.VectorToProjectionAxis(nrm);
if (sorted.ContainsKey(axis))
sorted[axis].Add(faces[i]);
else
sorted.Add(axis, new List<Face>() { faces[i] });
// clean up UV stuff - no shared UV indexes and remove element group
faces[i].elementGroup = -1;
faces[i].manualUV = true;
}
foreach (KeyValuePair<ProjectionAxis, List<Face>> kvp in sorted)
{
int[] distinct = kvp.Value.SelectMany(x => x.distinctIndexesInternal).ToArray();
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, distinct, Projection.ProjectionAxisToVector(kvp.Key));
for (int n = 0; n < distinct.Length; n++)
uv[distinct[n]] = uvs[n];
SplitUVs(mesh, distinct);
}
/* and set the msh uv array using the new coordintaes */
ApplyUVs(mesh, uv, channel);
}
/// <summary>
/// Finds the minimal U and V coordinate of a set of an array of UVs
/// </summary>
internal static Vector2 FindMinimalUV(Vector2[] uvs, int[] indices = null, float xMin = 0f, float yMin = 0f)
{
int nbElements = (indices == null ? uvs.Length : indices.Length);
bool first = (xMin == 0f && yMin == 0f);
for (int i = 0; i < nbElements; ++i)
{
int currentIndex = (indices == null ? i : indices[i]);
if (first)
{
xMin = uvs[currentIndex].x;
yMin = uvs[currentIndex].y;
first = false;
}
else
{
if (uvs[currentIndex].x < xMin)
{
xMin = uvs[currentIndex].x;
}
if (uvs[currentIndex].y < yMin)
{
yMin = uvs[currentIndex].y;
}
}
}
return new Vector2(xMin, yMin);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a box and then place the lower left coordinate at the anchor position.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="lowerLeftAnchor"></param>
/// <param name="channel"></param>
public static void ProjectFacesBox(ProBuilderMesh mesh, Face[] faces, Vector2 lowerLeftAnchor, int channel = 0)
{
Vector2[] uv = GetUVs(mesh, channel);
Dictionary<ProjectionAxis, List<Face>> sorted = new Dictionary<ProjectionAxis, List<Face>>();
for (int i = 0; i < faces.Length; i++)
{
Vector3 nrm = Math.Normal(mesh, faces[i]);
ProjectionAxis axis = Projection.VectorToProjectionAxis(nrm);
if (sorted.ContainsKey(axis))
sorted[axis].Add(faces[i]);
else
sorted.Add(axis, new List<Face>() { faces[i] });
// clean up UV stuff - no shared UV indexes and remove element group
faces[i].elementGroup = -1;
faces[i].manualUV = true;
}
foreach (KeyValuePair<ProjectionAxis, List<Face>> kvp in sorted)
{
int[] distinct = kvp.Value.SelectMany(x => x.distinctIndexesInternal).ToArray();
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, distinct, Projection.ProjectionAxisToVector(kvp.Key));
Vector2 minimalUV = FindMinimalUV(uvs);
for (int n = 0; n < distinct.Length; n++)
uv[distinct[n]] = uvs[n] - minimalUV;
SplitUVs(mesh, distinct);
}
/* and set the msh uv array using the new coordintaes */
ApplyUVs(mesh, uv, channel);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a sphere.
/// </summary>
/// <param name="pb"></param>
/// <param name="indexes"></param>
/// <param name="channel"></param>
public static void ProjectFacesSphere(ProBuilderMesh pb, int[] indexes, int channel = 0)
{
foreach (Face f in pb.facesInternal)
{
if (ArrayUtility.ContainsMatch<int>(f.distinctIndexesInternal, indexes))
{
f.elementGroup = -1;
f.manualUV = true;
}
}
SplitUVs(pb, indexes);
Vector2[] projected = Projection.SphericalProject(pb.positionsInternal, indexes);
Vector2[] uv = GetUVs(pb, channel);
for (int i = 0; i < indexes.Length; i++)
uv[indexes[i]] = projected[i];
/* and set the msh uv array using the new coordintaes */
ApplyUVs(pb, uv, channel);
}
/*
* Returns normalized UV values for a mesh uvs (0,0) - (1,1)
*/
public static Vector2[] FitUVs(Vector2[] uvs)
{
// shift UVs to zeroed coordinates
Vector2 smallestVector2 = Math.SmallestVector2(uvs);
int i;
for (i = 0; i < uvs.Length; i++)
uvs[i] -= smallestVector2;
float scale = Math.MakeNonZero(Math.LargestValue(Math.LargestVector2(uvs)));
for (i = 0; i < uvs.Length; i++)
uvs[i] /= scale;
return uvs;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0ed74e97865f42cfbce844278aa8b52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,333 @@
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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59e0185ce9cbd4517a2762cb16e1f690
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: