Added player character and step in/out of dialogue in scene 101
This commit is contained in:
+1546
File diff suppressed because it is too large
Load Diff
+11
@@ -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")]
|
||||
+13
@@ -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:
|
||||
+335
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb36fee1f817e4860a9937f064b1a192
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+625
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 081df46fba1da4194b5f8c7fa73446e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+166
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b300db76e48084b5680ab36214d3bea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+952
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9be31b6177ca0453e928692a20b33b2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+600
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: caa73878d8fb64ec1b9ba323a2158672
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+288
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9ea06f47bdbe427294162ab3d8e4ef3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+138
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9e3db3de6fd8480bb4e763ca2dcbe03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+251
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5963564d485624ab6ad4547f9451dbe6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+102
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aac023407bdc9df4db32c6aa0c29ef25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+496
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08c8ab0f8013d492eb6d0d78a261f745
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+374
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 214db8084d63e06499f41c215a8547a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+264
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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:
|
||||
+139
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5f37a0a7c6f54d4995a8f0806351e89
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+414
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0ed74e97865f42cfbce844278aa8b52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+333
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59e0185ce9cbd4517a2762cb16e1f690
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user