forked from BilalY/Rasagar
346 lines
14 KiB
C#
346 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|