ui基本完毕,修了一大把的bug
This commit is contained in:
+764
@@ -0,0 +1,764 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Class for triangulating a set of 3D points with edge constraints. Supports convex and non-convex polygons
|
||||
/// as well as polygons with holes.
|
||||
/// </summary>
|
||||
public sealed class ConstrainedTriangulator : Triangulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Given an edge E12, E23, E31, this returns the first vertex for that edge (V1, V2, V3, respectively)
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
private static readonly int[] edgeVertex1 = new int[] { 0, 0, 0, V1, V2, V3 };
|
||||
|
||||
/// <summary>
|
||||
/// Given an edge E12, E23, E31, this returns the second vertex for that edge (V2, V3, V1, respectively)
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
private static readonly int[] edgeVertex2 = new int[] { 0, 0, 0, V2, V3, V1 };
|
||||
|
||||
/// <summary>
|
||||
/// Given an edge E12, E23, E31, this returns the vertex opposite that edge (V3, V1, V2, respectively)
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
private static readonly int[] oppositePoint = new int[] { 0, 0, 0, V3, V1, V2 };
|
||||
|
||||
/// <summary>
|
||||
/// Given an edge E12, E23, E31, this returns the next clockwise edge (E23, E31, E12, respectively)
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
private static readonly int[] nextEdge = new int[] { 0, 0, 0, E23, E31, E12 };
|
||||
|
||||
/// <summary>
|
||||
/// Given an edge E12, E23, E31, this returns the previous clockwise edge (E31, E12, E23, respectively)
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
private static readonly int[] previousEdge = new int[] { 0, 0, 0, E31, E12, E23 };
|
||||
|
||||
/// <summary>
|
||||
/// List of edge constraints provided during initialization
|
||||
/// </summary>
|
||||
private List<EdgeConstraint> constraints;
|
||||
|
||||
/// <summary>
|
||||
/// This array maps each vertex to a triangle in the triangulation that contains it. This helps
|
||||
/// speed up the search when looking for intersecting edge. It isn't necessary to keep track of
|
||||
/// every triangle for each vertex.
|
||||
/// </summary>
|
||||
private int[] vertexTriangles;
|
||||
|
||||
/// <summary>
|
||||
/// Flag for each triangle to track whether it has been visited or not when finding the starting edge.
|
||||
/// Define at the class level to prevent unnecessary GC when calling FindStartingEdge multiple times.
|
||||
/// </summary>
|
||||
private bool[] visited;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the triangulator with the vertex data to be triangulated given a set of edge constraints
|
||||
/// </summary>
|
||||
/// <param name="inputPoints">The of points to triangulate.</param>
|
||||
/// <param name="constraints">The list of edge constraints which defines how the vertices in `inputPoints` are connected.</param>
|
||||
/// <param name="normal">The normal of the plane in which the `inputPoints` lie.</param>
|
||||
/// <returns></returns>
|
||||
public ConstrainedTriangulator(List<MeshVertex> inputPoints, List<EdgeConstraint> constraints, Vector3 normal)
|
||||
: base(inputPoints, normal)
|
||||
{
|
||||
this.constraints = constraints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the triangulation
|
||||
/// </summary>
|
||||
/// <returns>Returns an array containing the indices of the triangles, mapped to the list of points passed in during initialization.</returns>
|
||||
public override int[] Triangulate()
|
||||
{
|
||||
// Need at least 3 vertices to triangulate
|
||||
if (N < 3)
|
||||
{
|
||||
return new int[] { };
|
||||
}
|
||||
|
||||
this.AddSuperTriangle();
|
||||
this.NormalizeCoordinates();
|
||||
this.ComputeTriangulation();
|
||||
|
||||
if (constraints.Count > 0)
|
||||
{
|
||||
this.ApplyConstraints();
|
||||
this.DiscardTrianglesViolatingConstraints();
|
||||
}
|
||||
|
||||
this.DiscardTrianglesWithSuperTriangleVertices();
|
||||
|
||||
List<int> triangles = new List<int>(3 * triangleCount);
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
// Add all triangles that don't contain a super-triangle vertex
|
||||
if (!skipTriangle[i])
|
||||
{
|
||||
triangles.Add(triangulation[i, V1]);
|
||||
triangles.Add(triangulation[i, V2]);
|
||||
triangles.Add(triangulation[i, V3]);
|
||||
}
|
||||
}
|
||||
|
||||
return triangles.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applys the edge constraints to the triangulation
|
||||
/// </summary>
|
||||
internal void ApplyConstraints()
|
||||
{
|
||||
visited = new bool[triangulation.GetLength(0)];
|
||||
|
||||
// Map each vertex to a triangle that contains it
|
||||
vertexTriangles = new int[N + 3];
|
||||
for (int i = 0; i < triangulation.GetLength(0); i++)
|
||||
{
|
||||
vertexTriangles[triangulation[i, V1]] = i;
|
||||
vertexTriangles[triangulation[i, V2]] = i;
|
||||
vertexTriangles[triangulation[i, V3]] = i;
|
||||
}
|
||||
|
||||
// Loop through each edge constraint
|
||||
foreach (EdgeConstraint constraint in constraints)
|
||||
{
|
||||
if (constraint.v1 == constraint.v2) continue;
|
||||
|
||||
// We find the edges of the triangulation that intersect the constraint edge and remove them
|
||||
// For each intersecting edge, we identify the triangles that share that edge (which form a quad)
|
||||
// The diagonal of this quad is flipped.
|
||||
Queue<EdgeConstraint> intersectingEdges = FindIntersectingEdges(constraint, vertexTriangles);
|
||||
RemoveIntersectingEdges(constraint, intersectingEdges);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches through the triangulation to find intersecting edges
|
||||
/// </summary>
|
||||
/// <param name="intersectingEdges"></param>
|
||||
internal Queue<EdgeConstraint> FindIntersectingEdges(EdgeConstraint constraint, int[] vertexTriangles)
|
||||
{
|
||||
Queue<EdgeConstraint> intersectingEdges = new Queue<EdgeConstraint>();
|
||||
|
||||
// Need to find the first edge that the constraint crosses.
|
||||
EdgeConstraint startEdge;
|
||||
if (FindStartingEdge(vertexTriangles, constraint, out startEdge))
|
||||
{
|
||||
intersectingEdges.Enqueue(startEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
return intersectingEdges;
|
||||
}
|
||||
|
||||
// Search for all triangles that intersect the constraint. Stop when we find a triangle that contains v_j
|
||||
int t = startEdge.t1;
|
||||
int edgeIndex = startEdge.t1Edge;
|
||||
int lastTriangle = t;
|
||||
bool finalTriangleFound = false;
|
||||
while (!finalTriangleFound)
|
||||
{
|
||||
// Cross the last intersecting edge and inspect the next triangle
|
||||
lastTriangle = t;
|
||||
t = triangulation[t, edgeIndex];
|
||||
|
||||
// Get coordinates of constraint end points and triangle vertices
|
||||
Vector2 v_i = points[constraint.v1].coords;
|
||||
Vector2 v_j = points[constraint.v2].coords;
|
||||
Vector2 v1 = points[triangulation[t, V1]].coords;
|
||||
Vector2 v2 = points[triangulation[t, V2]].coords;
|
||||
Vector2 v3 = points[triangulation[t, V3]].coords;
|
||||
|
||||
// If triangle contains the endpoint of the constraint, the search is done
|
||||
if (TriangleContainsVertex(t, constraint.v2))
|
||||
{
|
||||
finalTriangleFound = true;
|
||||
}
|
||||
// Otherwise, the constraint must intersect one edge of this triangle. Ignore the edge that we entered from
|
||||
else if ((triangulation[t, E12] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v1, v2))
|
||||
{
|
||||
edgeIndex = E12;
|
||||
var edge = new EdgeConstraint(triangulation[t, V1], triangulation[t, V2], t, triangulation[t, E12], edgeIndex);
|
||||
intersectingEdges.Enqueue(edge);
|
||||
}
|
||||
else if ((triangulation[t, E23] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v2, v3))
|
||||
{
|
||||
edgeIndex = E23;
|
||||
var edge = new EdgeConstraint(triangulation[t, V2], triangulation[t, V3], t, triangulation[t, E23], edgeIndex);
|
||||
intersectingEdges.Enqueue(edge);
|
||||
}
|
||||
else if ((triangulation[t, E31] != lastTriangle) && MathUtils.LinesIntersect(v_i, v_j, v3, v1))
|
||||
{
|
||||
edgeIndex = E31;
|
||||
var edge = new EdgeConstraint(triangulation[t, V3], triangulation[t, V1], t, triangulation[t, E31], edgeIndex);
|
||||
intersectingEdges.Enqueue(edge);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't reach this point
|
||||
Debug.LogWarning("Failed to find final triangle, exiting early.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return intersectingEdges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the starting edge for the search to find all edges that intersect the constraint
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint being used to check for intersections</param>
|
||||
internal bool FindStartingEdge(int[] vertexTriangles, EdgeConstraint constraint, out EdgeConstraint startingEdge)
|
||||
{
|
||||
// Initialize out parameter to default value
|
||||
startingEdge = new EdgeConstraint(-1, -1);
|
||||
|
||||
// v_i->v_j are the start/end points of the constraint, respectively
|
||||
int v_i = constraint.v1;
|
||||
int v_j = constraint.v2;
|
||||
|
||||
// Start the search with an initial triangle that contains v_i
|
||||
int tSearch = vertexTriangles[v_i];
|
||||
|
||||
// Reset visited states
|
||||
for (int i = 0; i < visited.Length; i++)
|
||||
{
|
||||
visited[i] = false;
|
||||
}
|
||||
|
||||
// Circle v_i until we find a triangle that contains an edge which intersects the constraint edge
|
||||
// This will be the starting triangle in the search for finding all triangles that intersect the constraint
|
||||
bool intersectionFound = false;
|
||||
bool noCandidatesFound = false;
|
||||
int intersectingEdgeIndex = E12;
|
||||
int tE12, tE23, tE31;
|
||||
while (!intersectionFound && !noCandidatesFound)
|
||||
{
|
||||
visited[tSearch] = true;
|
||||
|
||||
// Triangulation already contains the constraint so we ignore the constraint
|
||||
if (TriangleContainsConstraint(tSearch, constraint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Check if the constraint intersects any edges of this triangle
|
||||
else if (EdgeConstraintIntersectsTriangle(tSearch, constraint, out intersectingEdgeIndex))
|
||||
{
|
||||
intersectionFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tE12 = triangulation[tSearch, E12];
|
||||
tE23 = triangulation[tSearch, E23];
|
||||
tE31 = triangulation[tSearch, E31];
|
||||
|
||||
// If constraint does not intersect this triangle, check adjacent triangles by crossing edges that have v_i as a vertex
|
||||
// Avoid triangles that we have previously visited in the search
|
||||
if (tE12 != OUT_OF_BOUNDS && !visited[tE12] && TriangleContainsVertex(tE12, v_i))
|
||||
{
|
||||
tSearch = tE12;
|
||||
}
|
||||
else if (tE23 != OUT_OF_BOUNDS && !visited[tE23] && TriangleContainsVertex(tE23, v_i))
|
||||
{
|
||||
tSearch = tE23;
|
||||
}
|
||||
else if (tE31 != OUT_OF_BOUNDS && !visited[tE31] && TriangleContainsVertex(tE31, v_i))
|
||||
{
|
||||
tSearch = tE31;
|
||||
}
|
||||
else
|
||||
{
|
||||
noCandidatesFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (intersectionFound)
|
||||
{
|
||||
int v_k = triangulation[tSearch, edgeVertex1[intersectingEdgeIndex]];
|
||||
int v_l = triangulation[tSearch, edgeVertex2[intersectingEdgeIndex]];
|
||||
int triangle2 = triangulation[tSearch, intersectingEdgeIndex];
|
||||
startingEdge = new EdgeConstraint(v_k, v_l, tSearch, triangle2, intersectingEdgeIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the edges from the triangulation that intersect the constraint. Find two triangles that
|
||||
/// share the intersecting edge, swap the diagonal and repeat until no edges intersect the constraint.
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint to check against</param>
|
||||
/// <param name="intersectingEdges">A queue containing the previously found edges that intersect the constraint</param>
|
||||
internal void RemoveIntersectingEdges(EdgeConstraint constraint, Queue<EdgeConstraint> intersectingEdges)
|
||||
{
|
||||
// Remove intersecting edges. Keep track of the new edges that we create
|
||||
List<EdgeConstraint> newEdges = new List<EdgeConstraint>();
|
||||
EdgeConstraint edge, newEdge;
|
||||
|
||||
// Mark the number of times we have been through the loop. If no new edges
|
||||
// have been added after all edges have been visited, stop the loop. Every
|
||||
// time an edge is added to newEdges, reset the counter.
|
||||
int counter = 0;
|
||||
|
||||
// Loop through all intersecting edges until they have been properly resolved
|
||||
// or they have all been visited with no diagonal swaps.
|
||||
while (intersectingEdges.Count > 0 && counter <= intersectingEdges.Count)
|
||||
{
|
||||
edge = intersectingEdges.Dequeue();
|
||||
|
||||
Quad quad;
|
||||
if (FindQuadFromSharedEdge(edge.t1, edge.t1Edge, out quad))
|
||||
{
|
||||
// If the quad is convex, we swap the diagonal (a quad is convex if the diagonals intersect)
|
||||
// Otherwise push it back into the queue so we can swap the diagonal later on.
|
||||
if (MathUtils.LinesIntersect(points[quad.q4].coords,
|
||||
points[quad.q3].coords,
|
||||
points[quad.q1].coords,
|
||||
points[quad.q2].coords))
|
||||
{
|
||||
// Swap diagonals of the convex quads whose diagonals intersect the constraint
|
||||
SwapQuadDiagonal(quad, intersectingEdges, newEdges, constraints);
|
||||
|
||||
// The new diagonal is between Q3 and Q4
|
||||
newEdge = new EdgeConstraint(quad.q3, quad.q4, quad.t1, quad.t2, E31);
|
||||
|
||||
// If the new diagonal still intersects the constraint edge v_i->v_j,
|
||||
// put back on the list of intersecting eddges
|
||||
if (MathUtils.LinesIntersect(points[constraint.v1].coords,
|
||||
points[constraint.v2].coords,
|
||||
points[quad.q3].coords,
|
||||
points[quad.q4].coords))
|
||||
{
|
||||
intersectingEdges.Enqueue(newEdge);
|
||||
}
|
||||
// Otherwise record in list of new edges
|
||||
else
|
||||
{
|
||||
counter = 0;
|
||||
newEdges.Add(newEdge);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intersectingEdges.Enqueue(edge);
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
// If any new edges were formed due to a diagonal being swapped, restore the Delauney condition
|
||||
// of the triangulation while respecting the constraints
|
||||
if (newEdges.Count > 0)
|
||||
{
|
||||
RestoreConstrainedDelauneyTriangulation(constraint, newEdges);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the Delauney triangulation after the constraint has been inserted
|
||||
/// </summary>
|
||||
/// <param name="constraint">The constraint that was added to the triangulation</param>
|
||||
/// <param name="newEdges">The list of new edges that were added</param>
|
||||
internal void RestoreConstrainedDelauneyTriangulation(EdgeConstraint constraint, List<EdgeConstraint> newEdges)
|
||||
{
|
||||
// Iterate over the list of newly created edges and swap non-constraint diagonals until no more swaps take place
|
||||
bool swapOccurred = true;
|
||||
int counter = 0;
|
||||
while (swapOccurred)
|
||||
{
|
||||
counter++;
|
||||
swapOccurred = false;
|
||||
|
||||
for (int i = 0; i < newEdges.Count; i++)
|
||||
{
|
||||
EdgeConstraint edge = newEdges[i];
|
||||
|
||||
// If newly added edge is equal to constraint, we don't want to flip this edge so skip it
|
||||
if (edge == constraint)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Quad quad;
|
||||
if (FindQuadFromSharedEdge(edge.t1, edge.t1Edge, out quad))
|
||||
{
|
||||
if (SwapTest(points[quad.q1].coords, points[quad.q2].coords, points[quad.q3].coords, points[quad.q4].coords))
|
||||
{
|
||||
SwapQuadDiagonal(quad, newEdges, constraints, null);
|
||||
|
||||
// Enqueue the new diagonal
|
||||
int v_m = quad.q3;
|
||||
int v_n = quad.q4;
|
||||
newEdges[i] = new EdgeConstraint(v_m, v_n, quad.t1, quad.t2, E31);
|
||||
|
||||
swapOccurred = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards triangles that violate the any of the edge constraints
|
||||
/// </summary>
|
||||
internal void DiscardTrianglesViolatingConstraints()
|
||||
{
|
||||
// Initialize to all triangles being skipped
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
skipTriangle[i] = true;
|
||||
}
|
||||
|
||||
// Identify the boundary edges
|
||||
HashSet < (int, int) > boundaries = new HashSet < (int, int) > ();
|
||||
for (int i = 0; i < this.constraints.Count; i++)
|
||||
{
|
||||
EdgeConstraint constraint = this.constraints[i];
|
||||
boundaries.Add((constraint.v1, constraint.v2));
|
||||
}
|
||||
|
||||
// Reset visited states
|
||||
for (int i = 0; i < visited.Length; i++)
|
||||
{
|
||||
visited[i] = false;
|
||||
}
|
||||
|
||||
// Search frontier
|
||||
Queue<int> frontier = new Queue<int>();
|
||||
|
||||
int v1, v2, v3;
|
||||
bool boundaryE12, boundaryE23, boundaryE31;
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
// If we've already visited this triangle, skip it
|
||||
if (visited[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
v1 = triangulation[i, V1];
|
||||
v2 = triangulation[i, V2];
|
||||
v3 = triangulation[i, V3];
|
||||
boundaryE12 = boundaries.Contains((v1, v2));
|
||||
boundaryE23 = boundaries.Contains((v2, v3));
|
||||
boundaryE31 = boundaries.Contains((v3, v1));
|
||||
|
||||
// If this triangle has a boundary edge, start searching for adjacent triangles
|
||||
if (boundaryE12 || boundaryE23 || boundaryE31)
|
||||
{
|
||||
skipTriangle[i] = false;
|
||||
|
||||
// Search along edges that are not boundary edges
|
||||
frontier.Clear();
|
||||
if (!boundaryE12)
|
||||
{
|
||||
frontier.Enqueue(triangulation[i, E12]);
|
||||
}
|
||||
if (!boundaryE23)
|
||||
{
|
||||
frontier.Enqueue(triangulation[i, E23]);
|
||||
}
|
||||
if (!boundaryE31)
|
||||
{
|
||||
frontier.Enqueue(triangulation[i, E31]);
|
||||
}
|
||||
|
||||
// Recursively search along all non-boundary edges, marking the
|
||||
// adjacent triangles as "keep"
|
||||
while (frontier.Count > 0)
|
||||
{
|
||||
int k = frontier.Dequeue();
|
||||
|
||||
if (k == OUT_OF_BOUNDS || visited[k])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
skipTriangle[k] = false;
|
||||
visited[k] = true;
|
||||
|
||||
v1 = triangulation[k, V1];
|
||||
v2 = triangulation[k, V2];
|
||||
v3 = triangulation[k, V3];
|
||||
|
||||
// Continue searching along non-boundary edges
|
||||
if (!boundaries.Contains((v1, v2)))
|
||||
{
|
||||
frontier.Enqueue(triangulation[k, E12]);
|
||||
}
|
||||
if (!boundaries.Contains((v2, v3)))
|
||||
{
|
||||
frontier.Enqueue(triangulation[k, E23]);
|
||||
}
|
||||
if (!boundaries.Contains((v3, v1)))
|
||||
{
|
||||
frontier.Enqueue(triangulation[k, E31]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the triangle contains the edge constraint
|
||||
/// </summary>
|
||||
/// <param name="t">The triangle to test</param>
|
||||
/// <param name="constraint">The edge constraint</param>
|
||||
/// <returns>True if the triangle contains one or both of the endpoints of the constraint</returns>
|
||||
internal bool TriangleContainsConstraint(int t, EdgeConstraint constraint)
|
||||
{
|
||||
return (triangulation[t, V1] == constraint.v1 || triangulation[t, V2] == constraint.v1 || triangulation[t, V3] == constraint.v1) &&
|
||||
(triangulation[t, V1] == constraint.v2 || triangulation[t, V2] == constraint.v2 || triangulation[t, V3] == constraint.v2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the edge constraint intersects an edge of triangle `t`
|
||||
/// </summary>
|
||||
/// <param name="t">The triangle to test</param>
|
||||
/// <param name="constraint">The edge constraint</param>
|
||||
/// <param name="intersectingEdgeIndex">The index of the intersecting edge (E12, E23, E31)</param>
|
||||
/// <returns>Returns true if an intersection is found, otherwise false.</returns>
|
||||
internal bool EdgeConstraintIntersectsTriangle(int t, EdgeConstraint constraint, out int intersectingEdgeIndex)
|
||||
{
|
||||
Vector2 v_i = points[constraint.v1].coords;
|
||||
Vector2 v_j = points[constraint.v2].coords;
|
||||
Vector2 v1 = points[triangulation[t, V1]].coords;
|
||||
Vector2 v2 = points[triangulation[t, V2]].coords;
|
||||
Vector2 v3 = points[triangulation[t, V3]].coords;
|
||||
|
||||
if (MathUtils.LinesIntersect(v_i, v_j, v1, v2))
|
||||
{
|
||||
intersectingEdgeIndex = E12;
|
||||
return true;
|
||||
}
|
||||
else if (MathUtils.LinesIntersect(v_i, v_j, v2, v3))
|
||||
{
|
||||
intersectingEdgeIndex = E23;
|
||||
return true;
|
||||
}
|
||||
else if (MathUtils.LinesIntersect(v_i, v_j, v3, v1))
|
||||
{
|
||||
intersectingEdgeIndex = E31;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
intersectingEdgeIndex = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the quad formed by triangle `t1` and the other triangle that shares the intersecting edge
|
||||
/// </summary>
|
||||
/// <param name="t1">Base triangle</param>
|
||||
/// <param name="intersectingEdge">Edge index that is being intersected</param>
|
||||
internal bool FindQuadFromSharedEdge(int t1, int t1SharedEdge, out Quad quad)
|
||||
{
|
||||
// q3
|
||||
// *---------*---------*
|
||||
// \ / \ /
|
||||
// \ t2L / \ t2R /
|
||||
// \ / \ /
|
||||
// \ / t2 \ /
|
||||
// q1 *---------* q2
|
||||
// / \ t1 / \
|
||||
// / \ / \
|
||||
// / t1L \ / t1R \
|
||||
// / \ / \
|
||||
// *---------*---------*
|
||||
// q4
|
||||
|
||||
int q1, q2, q3, q4;
|
||||
int t1L, t1R, t2L, t2R;
|
||||
|
||||
// t2 is adjacent to t1 along t1Edge
|
||||
int t2 = triangulation[t1, t1SharedEdge];
|
||||
int t2SharedEdge;
|
||||
if (FindSharedEdge(t2, t1, out t2SharedEdge))
|
||||
{
|
||||
// Get the top 3 vertices of the quad from t2
|
||||
if (t2SharedEdge == E12)
|
||||
{
|
||||
q2 = triangulation[t2, V1];
|
||||
q1 = triangulation[t2, V2];
|
||||
q3 = triangulation[t2, V3];
|
||||
}
|
||||
else if (t2SharedEdge == E23)
|
||||
{
|
||||
q2 = triangulation[t2, V2];
|
||||
q1 = triangulation[t2, V3];
|
||||
q3 = triangulation[t2, V1];
|
||||
}
|
||||
else // (t2SharedEdge == E31)
|
||||
{
|
||||
q2 = triangulation[t2, V3];
|
||||
q1 = triangulation[t2, V1];
|
||||
q3 = triangulation[t2, V2];
|
||||
}
|
||||
|
||||
// q4 is the point in t1 opposite of the shared edge
|
||||
q4 = triangulation[t1, oppositePoint[t1SharedEdge]];
|
||||
|
||||
// Get the adjacent triangles to make updating adjacency easier
|
||||
t1L = triangulation[t1, previousEdge[t1SharedEdge]];
|
||||
t1R = triangulation[t1, nextEdge[t1SharedEdge]];
|
||||
t2L = triangulation[t2, nextEdge[t2SharedEdge]];
|
||||
t2R = triangulation[t2, previousEdge[t2SharedEdge]];
|
||||
|
||||
quad = new Quad(q1, q2, q3, q4, t1, t2, t1L, t1R, t2L, t2R);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
quad = new Quad();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the diagonal of the quadrilateral q0->q1->q2->q3 formed by t1 and t2
|
||||
/// </summary>
|
||||
/// <param name="">The quad that will have its diagonal swapped</param>
|
||||
internal void SwapQuadDiagonal(Quad quad, IEnumerable<EdgeConstraint> edges1, IEnumerable<EdgeConstraint> edges2, IEnumerable<EdgeConstraint> edges3)
|
||||
{
|
||||
// BEFORE
|
||||
// q3
|
||||
// *---------*---------*
|
||||
// \ / \ /
|
||||
// \ t2L / \ t2R /
|
||||
// \ / \ /
|
||||
// \ / t2 \ /
|
||||
// q1 *---------* q2
|
||||
// / \ t1 / \
|
||||
// / \ / \
|
||||
// / t1L \ / t1R \
|
||||
// / \ / \
|
||||
// *---------*---------*
|
||||
// q4
|
||||
|
||||
// AFTER
|
||||
// q3
|
||||
// *---------*---------*
|
||||
// \ /|\ /
|
||||
// \ t2L / | \ t2R /
|
||||
// \ / | \ /
|
||||
// \ / | \ /
|
||||
// q1 * t1 | t2 * q2
|
||||
// / \ | / \
|
||||
// / \ | / \
|
||||
// / t1L \ | / t1R \
|
||||
// / \|/ \
|
||||
// *---------*---------*
|
||||
// q4
|
||||
|
||||
int t1 = quad.t1;
|
||||
int t2 = quad.t2;
|
||||
int t1R = quad.t1R;
|
||||
int t1L = quad.t1L;
|
||||
int t2R = quad.t2R;
|
||||
int t2L = quad.t2L;
|
||||
|
||||
// Perform the swap. As always, put the new vertex as the first vertex of the triangle
|
||||
triangulation[t1, V1] = quad.q4;
|
||||
triangulation[t1, V2] = quad.q1;
|
||||
triangulation[t1, V3] = quad.q3;
|
||||
|
||||
triangulation[t2, V1] = quad.q4;
|
||||
triangulation[t2, V2] = quad.q3;
|
||||
triangulation[t2, V3] = quad.q2;
|
||||
|
||||
triangulation[t1, E12] = t1L;
|
||||
triangulation[t1, E23] = t2L;
|
||||
triangulation[t1, E31] = t2;
|
||||
|
||||
triangulation[t2, E12] = t1;
|
||||
triangulation[t2, E23] = t2R;
|
||||
triangulation[t2, E31] = t1R;
|
||||
|
||||
// Update adjacency for the adjacent triangles
|
||||
UpdateAdjacency(t2L, t2, t1);
|
||||
UpdateAdjacency(t1R, t1, t2);
|
||||
|
||||
// Now that triangles have moved, need to update edges as well
|
||||
UpdateEdgesAfterSwap(edges1, t1, t2, t1L, t1R, t2L, t2R);
|
||||
UpdateEdgesAfterSwap(edges2, t1, t2, t1L, t1R, t2L, t2R);
|
||||
UpdateEdgesAfterSwap(edges3, t1, t2, t1L, t1R, t2L, t2R);
|
||||
|
||||
// Also need to update the vertexTriangles array since the vertices q1 and q2
|
||||
// may have been referencing t2/t1 respectively and they are no longer.
|
||||
vertexTriangles[quad.q1] = t1;
|
||||
vertexTriangles[quad.q2] = t2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Edges
|
||||
/// </summary>
|
||||
/// <param name="edges"></param>
|
||||
/// <param name="t1"></param>
|
||||
/// <param name="t2"></param>
|
||||
/// <param name="t1L"></param>
|
||||
/// <param name="t1R"></param>
|
||||
/// <param name="t2L"></param>
|
||||
/// <param name="t2R"></param>
|
||||
internal void UpdateEdgesAfterSwap(IEnumerable<EdgeConstraint> edges, int t1, int t2, int t1L, int t1R, int t2L, int t2R)
|
||||
{
|
||||
if (edges == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update edges to reflect changes in triangles
|
||||
foreach (EdgeConstraint edge in edges)
|
||||
{
|
||||
if (edge.t1 == t1 && edge.t2 == t1R)
|
||||
{
|
||||
edge.t1 = t2;
|
||||
edge.t2 = t1R;
|
||||
edge.t1Edge = E31;
|
||||
}
|
||||
else if (edge.t1 == t1 && edge.t2 == t1L)
|
||||
{
|
||||
// Triangles stay the same
|
||||
edge.t1Edge = E12;
|
||||
}
|
||||
else if (edge.t1 == t1R && edge.t2 == t1)
|
||||
{
|
||||
edge.t2 = t2;
|
||||
}
|
||||
else if (edge.t1 == t1L && edge.t2 == t1)
|
||||
{
|
||||
// Unchanged
|
||||
}
|
||||
else if (edge.t1 == t2 && edge.t2 == t2R)
|
||||
{
|
||||
// Triangles stay the same
|
||||
edge.t1Edge = E23;
|
||||
}
|
||||
else if (edge.t1 == t2 && edge.t2 == t2L)
|
||||
{
|
||||
edge.t1 = t1;
|
||||
edge.t2 = t2L;
|
||||
edge.t1Edge = E23;
|
||||
}
|
||||
else if (edge.t1 == t2R && edge.t2 == t2)
|
||||
{
|
||||
// Unchanged
|
||||
}
|
||||
else if (edge.t1 == t2L && edge.t2 == t2)
|
||||
{
|
||||
edge.t2 = t1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dae513e54953d9740a998fa1d26020b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,87 @@
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an edge constraint between two vertices in the triangulation
|
||||
/// </summary>
|
||||
public class EdgeConstraint
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of the first end point of the constraint
|
||||
/// </summary>
|
||||
public int v1;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the second end point of the constraint
|
||||
/// </summary>
|
||||
public int v2;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the triangle prior to the edge crossing (v1 -> v2)
|
||||
/// </summary>
|
||||
public int t1;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the triangle after the edge crossing (v1 -> v2)
|
||||
/// </summary>
|
||||
public int t2;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the edge on the t1 side
|
||||
/// </summary>
|
||||
public int t1Edge;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new edge constraint with the given end points
|
||||
/// </summary>
|
||||
public EdgeConstraint(int v1, int v2)
|
||||
{
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
this.t1 = -1;
|
||||
this.t2 = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new edge constraint and defines triangles on either side of the edge
|
||||
/// </summary>
|
||||
public EdgeConstraint(int v1, int v2, int triangle1, int triangle2, int edge1)
|
||||
{
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
this.t1 = triangle1;
|
||||
this.t2 = triangle2;
|
||||
this.t1Edge = edge1;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is EdgeConstraint)
|
||||
{
|
||||
var other = (EdgeConstraint)obj;
|
||||
return (this.v1 == other.v1 && this.v2 == other.v2) ||
|
||||
(this.v1 == other.v2 && this.v2 == other.v1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return new { v1, v2 }.GetHashCode() + new { v2, v1 }.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(EdgeConstraint lhs, EdgeConstraint rhs)
|
||||
{
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(EdgeConstraint lhs, EdgeConstraint rhs)
|
||||
{
|
||||
return !lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Edge: T{t1}->T{t2} (V{v1}->V{v2})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e465c2db3ee42004bb2588140d2c0275
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,315 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public enum SlicedMeshSubmesh
|
||||
{
|
||||
Default = 0,
|
||||
CutFace = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data structure used for storing mesh data during the fragmenting process
|
||||
/// </summary>
|
||||
public class FragmentData
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertex buffer for the non-cut mesh faces
|
||||
/// </summary>
|
||||
public List<MeshVertex> Vertices;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex buffer for the cut mesh faces
|
||||
/// </summary>
|
||||
public List<MeshVertex> CutVertices;
|
||||
|
||||
/// <summary>
|
||||
/// Index buffer for each submesh
|
||||
/// </summary>
|
||||
public List<int>[] Triangles;
|
||||
|
||||
/// <summary>
|
||||
/// List of edges constraints for the cut-face triangulation
|
||||
/// </summary>
|
||||
public List<EdgeConstraint> Constraints;
|
||||
|
||||
/// <summary>
|
||||
/// Map between vertex indices in the source mesh and new indices for the sliced mesh
|
||||
/// </summary>
|
||||
public int[] IndexMap;
|
||||
|
||||
/// <summary>
|
||||
/// The bounds of the vertex data (must manually call UpdateBounds() to update)
|
||||
/// </summary>
|
||||
public Bounds Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of triangles across all sub meshes
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public int triangleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < this.Triangles.Length; i++)
|
||||
{
|
||||
count += this.Triangles[i].Count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of vertices in the mesh
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public int vertexCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Vertices.Count + this.CutVertices.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new sliced mesh
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the mesh</param>
|
||||
/// <param name="vertexCount">Vertex count used to initialize lists. Initializing lists to approximate size reduces resizes and GC.</param>
|
||||
/// <param name="triangleCount">Triangle count used to initialize lists. Initializing lists to approximate size reduces resizes and GC.</param>
|
||||
public FragmentData(int vertexCount, int triangleCount)
|
||||
{
|
||||
this.Vertices = new List<MeshVertex>(vertexCount);
|
||||
this.CutVertices = new List<MeshVertex>(vertexCount / 10);
|
||||
|
||||
// Store triangles for each submesh separately
|
||||
this.Triangles = new List<int>[] {
|
||||
new List<int>(triangleCount),
|
||||
new List<int>(triangleCount / 10)
|
||||
};
|
||||
|
||||
this.Constraints = new List<EdgeConstraint>();
|
||||
this.IndexMap = new int[vertexCount];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new sliced mesh dataset from source mesh data
|
||||
/// </summary>
|
||||
/// <param name="mesh">The source mesh data.</param>
|
||||
public FragmentData(Mesh mesh)
|
||||
{
|
||||
var positions = mesh.vertices;
|
||||
var normals = mesh.normals;
|
||||
var uv = mesh.uv;
|
||||
|
||||
this.Vertices = new List<MeshVertex>(mesh.vertexCount);
|
||||
this.CutVertices = new List<MeshVertex>(mesh.vertexCount / 10);
|
||||
this.Constraints = new List<EdgeConstraint>();
|
||||
this.IndexMap = new int[positions.Length];
|
||||
|
||||
// Add mesh vertices
|
||||
for (int i = 0; i < positions.Length; i++)
|
||||
{
|
||||
this.Vertices.Add(new MeshVertex(positions[i], normals[i], uv[i]));
|
||||
}
|
||||
|
||||
// Only meshes with one submesh are currently supported
|
||||
this.Triangles = new List<int>[2];
|
||||
this.Triangles[0] = new List<int>(mesh.GetTriangles(0));
|
||||
|
||||
if (mesh.subMeshCount >= 2)
|
||||
{
|
||||
this.Triangles[1] = new List<int>(mesh.GetTriangles(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Triangles[1] = new List<int>(mesh.triangles.Length / 10);
|
||||
}
|
||||
|
||||
this.CalculateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new cut face vertex
|
||||
/// </summary>
|
||||
/// <param name="position">The vertex position</param>
|
||||
/// <param name="normal">The vertex normal</param>
|
||||
/// <param name="uv">The vertex UV coordinates</param>
|
||||
/// <returns>Returns the index of the vertex in the cutVertices array</returns>
|
||||
public void AddCutFaceVertex(Vector3 position, Vector3 normal, Vector2 uv)
|
||||
{
|
||||
var vertex = new MeshVertex(position, normal, uv);
|
||||
|
||||
// Add the vertex to both the normal mesh vertex data and the cut face vertex data
|
||||
// The vertex on the cut face will have different normal/uv coordinates which are
|
||||
// populated with the correct values later in the triangulation process.
|
||||
this.Vertices.Add(vertex);
|
||||
this.CutVertices.Add(vertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new vertex to this mesh that is mapped to the source mesh
|
||||
/// </summary>
|
||||
/// <param name="vertex">Vertex data</param>
|
||||
/// <param name="sourceIndex">Index of the vertex in the source mesh</param>
|
||||
public void AddMappedVertex(MeshVertex vertex, int sourceIndex)
|
||||
{
|
||||
this.Vertices.Add(vertex);
|
||||
this.IndexMap[sourceIndex] = this.Vertices.Count - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indexes of the
|
||||
/// vertices relative to this mesh's list of vertices; no mapping is performed.
|
||||
/// </summary>
|
||||
/// <param name="v1">Index of the first vertex</param>
|
||||
/// <param name="v2">Index of the second vertex</param>
|
||||
/// <param name="v3">Index of the third vertex</param>
|
||||
/// <param name="subMesh">The sub-mesh to add the triangle to</param>
|
||||
public void AddTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh)
|
||||
{
|
||||
this.Triangles[(int)subMesh].Add(v1);
|
||||
this.Triangles[(int)subMesh].Add(v2);
|
||||
this.Triangles[(int)subMesh].Add(v3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new triangle to this mesh. The arguments v1, v2, v3 are the indices of the
|
||||
/// vertices in the original mesh. These vertices are mapped to the indices in the sliced mesh.
|
||||
/// </summary>
|
||||
/// <param name="v1">Index of the first vertex</param>
|
||||
/// <param name="v2">Index of the second vertex</param>
|
||||
/// <param name="v3">Index of the third vertex</param>
|
||||
/// <param name="subMesh">The sub-mesh to add the triangle to</param>
|
||||
public void AddMappedTriangle(int v1, int v2, int v3, SlicedMeshSubmesh subMesh)
|
||||
{
|
||||
this.Triangles[(int)subMesh].Add(IndexMap[v1]);
|
||||
this.Triangles[(int)subMesh].Add(IndexMap[v2]);
|
||||
this.Triangles[(int)subMesh].Add(IndexMap[v3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds coincident vertices on the cut face and welds them together.
|
||||
/// </summary>
|
||||
public void WeldCutFaceVertices()
|
||||
{
|
||||
// Temporary array containing the unique (welded) vertices
|
||||
// Initialize capacity to current number of cut vertices to prevent
|
||||
// unnecessary reallocations
|
||||
List<MeshVertex> weldedVerts = new List<MeshVertex>(CutVertices.Count);
|
||||
|
||||
// We also keep track of the index mapping between the skipped vertices
|
||||
// and the index of the welded vertex so we can update the edges
|
||||
int[] indexMap = new int[CutVertices.Count];
|
||||
|
||||
// Number of welded vertices in the temp array
|
||||
int k = 0;
|
||||
|
||||
// Loop through each vertex, identifying duplicates. Must compare directly
|
||||
// because floating point inconsistencies cause a hash table to be unreliable
|
||||
// for vertices that are very close together but not directly coincident
|
||||
for(int i = 0; i < CutVertices.Count; i++)
|
||||
{
|
||||
bool duplicate = false;
|
||||
for(int j = 0; j < weldedVerts.Count; j++)
|
||||
{
|
||||
if (CutVertices[i].position == weldedVerts[j].position)
|
||||
{
|
||||
indexMap[i] = j;
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicate)
|
||||
{
|
||||
weldedVerts.Add(CutVertices[i]);
|
||||
indexMap[i] = k;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the edges
|
||||
for(int i = 0; i < Constraints.Count; i++)
|
||||
{
|
||||
var edge = Constraints[i];
|
||||
edge.v1 = indexMap[edge.v1];
|
||||
edge.v2 = indexMap[edge.v2];
|
||||
}
|
||||
|
||||
weldedVerts.TrimExcess();
|
||||
|
||||
// Update the cut vertices
|
||||
this.CutVertices = new List<MeshVertex>(weldedVerts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangles for the specified sub mesh
|
||||
/// </summary>
|
||||
/// <param name="subMeshIndex">The index of the submesh</param>
|
||||
/// <returns></returns>
|
||||
public int[] GetTriangles(int subMeshIndex)
|
||||
{
|
||||
return this.Triangles[subMeshIndex].ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the bounds of the mesh data
|
||||
/// </summary>
|
||||
public void CalculateBounds()
|
||||
{
|
||||
float vertexCount = (float)Vertices.Count;
|
||||
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
||||
Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
||||
|
||||
// The cut face does not modify the extents of the object, so we only need to
|
||||
// loop through the original vertices to determine the bounds
|
||||
foreach(MeshVertex vertex in Vertices)
|
||||
{
|
||||
if (vertex.position.x < min.x) min.x = vertex.position.x;
|
||||
if (vertex.position.y < min.y) min.y = vertex.position.y;
|
||||
if (vertex.position.z < min.z) min.z = vertex.position.z;
|
||||
if (vertex.position.x > max.x) max.x = vertex.position.x;
|
||||
if (vertex.position.y > max.y) max.y = vertex.position.y;
|
||||
if (vertex.position.z > max.z) max.z = vertex.position.z;
|
||||
}
|
||||
|
||||
this.Bounds = new Bounds((max + min) / 2f, max - min);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the sliced mesh data into a mesh
|
||||
/// </summary>
|
||||
/// <returns>Returns the mesh object</returns>
|
||||
public Mesh ToMesh()
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
var layout = new[]
|
||||
{
|
||||
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
|
||||
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
|
||||
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
|
||||
};
|
||||
|
||||
mesh.SetIndexBufferParams(triangleCount, IndexFormat.UInt32);
|
||||
mesh.SetVertexBufferParams(vertexCount, layout);
|
||||
mesh.SetVertexBufferData(Vertices, 0, 0, Vertices.Count);
|
||||
mesh.SetVertexBufferData(CutVertices, 0, Vertices.Count, CutVertices.Count);
|
||||
|
||||
mesh.subMeshCount = Triangles.Length;
|
||||
int indexStart = 0;
|
||||
for(int i = 0; i < Triangles.Length; i++)
|
||||
{
|
||||
var subMeshIndexBuffer = Triangles[i];
|
||||
mesh.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, subMeshIndexBuffer.Count);
|
||||
mesh.SetSubMesh(i, new SubMeshDescriptor(indexStart, subMeshIndexBuffer.Count));
|
||||
indexStart += subMeshIndexBuffer.Count;
|
||||
}
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 744e2e7cedf47b848b91e43dbf53e383
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public static class Fragmenter
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the mesh fragments based on the provided options. The generated fragment objects are
|
||||
/// stored as children of `fragmentParent`
|
||||
/// </summary>
|
||||
/// <param name="sourceObject">The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.</param>
|
||||
/// <param name="options">Options for the fragmenter</param>
|
||||
/// <param name="fragmentTemplate">The template GameObject that each fragment will clone</param>
|
||||
/// <param name="parent">The parent transform for the fragment objects</param>
|
||||
/// <param name="saveToDisk">If true, the generated fragment meshes will be saved to disk so they can be re-used in prefabs.</param>
|
||||
/// <param name="saveFolderPath">The save location for the fragments.</param>
|
||||
/// <returns></returns>
|
||||
public static void Fracture(GameObject sourceObject,
|
||||
FractureOptions options,
|
||||
GameObject fragmentTemplate,
|
||||
Transform parent,
|
||||
bool saveToDisk = false,
|
||||
string saveFolderPath = "")
|
||||
{
|
||||
// Define our source mesh data for the fracturing
|
||||
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent<MeshFilter>().sharedMesh);
|
||||
|
||||
// We begin by fragmenting the source mesh, then process each fragment in a FIFO queue
|
||||
// until we achieve the target fragment count.
|
||||
var fragments = new Queue<FragmentData>();
|
||||
fragments.Enqueue(sourceMesh);
|
||||
|
||||
// Subdivide the mesh into multiple fragments until we reach the fragment limit
|
||||
FragmentData topSlice, bottomSlice;
|
||||
while (fragments.Count < options.fragmentCount)
|
||||
{
|
||||
FragmentData meshData = fragments.Dequeue();
|
||||
meshData.CalculateBounds();
|
||||
|
||||
// Select an arbitrary fracture plane normal
|
||||
Vector3 normal = new Vector3(
|
||||
options.xAxis ? Random.Range(-1f, 1f) : 0f,
|
||||
options.yAxis ? Random.Range(-1f, 1f) : 0f,
|
||||
options.zAxis ? Random.Range(-1f, 1f) : 0f);
|
||||
|
||||
// Slice and dice!
|
||||
MeshSlicer.Slice(meshData,
|
||||
normal,
|
||||
meshData.Bounds.center,
|
||||
options.textureScale,
|
||||
options.textureOffset,
|
||||
out topSlice,
|
||||
out bottomSlice);
|
||||
|
||||
fragments.Enqueue(topSlice);
|
||||
fragments.Enqueue(bottomSlice);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach(FragmentData meshData in fragments)
|
||||
{
|
||||
CreateFragment(meshData,
|
||||
sourceObject,
|
||||
fragmentTemplate,
|
||||
parent,
|
||||
saveToDisk,
|
||||
saveFolderPath,
|
||||
options.detectFloatingFragments,
|
||||
ref i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously generates the mesh fragments based on the provided options. The generated fragment objects are
|
||||
/// stored as children of `fragmentParent`
|
||||
/// </summary>
|
||||
/// <param name="sourceObject">The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.</param>
|
||||
/// <param name="options">Options for the fragmenter</param>
|
||||
/// <param name="fragmentTemplate">The template GameObject that each fragment will clone</param>
|
||||
/// <param name="parent">The parent transform for the fragment objects</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerator FractureAsync(GameObject sourceObject,
|
||||
FractureOptions options,
|
||||
GameObject fragmentTemplate,
|
||||
Transform parent,
|
||||
Action onCompletion)
|
||||
{
|
||||
// Define our source mesh data for the fracturing
|
||||
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent<MeshFilter>().sharedMesh);
|
||||
|
||||
// We begin by fragmenting the source mesh, then process each fragment in a FIFO queue
|
||||
// until we achieve the target fragment count.
|
||||
var fragments = new Queue<FragmentData>();
|
||||
fragments.Enqueue(sourceMesh);
|
||||
|
||||
// Subdivide the mesh into multiple fragments until we reach the fragment limit
|
||||
FragmentData topSlice, bottomSlice;
|
||||
while (fragments.Count < options.fragmentCount)
|
||||
{
|
||||
FragmentData meshData = fragments.Dequeue();
|
||||
meshData.CalculateBounds();
|
||||
|
||||
// Select an arbitrary fracture plane normal
|
||||
Vector3 normal = new Vector3(
|
||||
options.xAxis ? Random.Range(-1f, 1f) : 0f,
|
||||
options.yAxis ? Random.Range(-1f, 1f) : 0f,
|
||||
options.zAxis ? Random.Range(-1f, 1f) : 0f);
|
||||
|
||||
// Slice and dice!
|
||||
MeshSlicer.Slice(meshData,
|
||||
normal,
|
||||
meshData.Bounds.center,
|
||||
options.textureScale,
|
||||
options.textureOffset,
|
||||
out topSlice,
|
||||
out bottomSlice);
|
||||
|
||||
// Perform next slice on the next frame
|
||||
yield return null;
|
||||
|
||||
fragments.Enqueue(topSlice);
|
||||
fragments.Enqueue(bottomSlice);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach(FragmentData meshData in fragments)
|
||||
{
|
||||
CreateFragment(meshData,
|
||||
sourceObject,
|
||||
fragmentTemplate,
|
||||
parent,
|
||||
false,
|
||||
"",
|
||||
options.detectFloatingFragments,
|
||||
ref i);
|
||||
}
|
||||
|
||||
onCompletion?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the mesh fragments based on the provided options. The generated fragment objects are
|
||||
/// stored as children of `fragmentParent`
|
||||
/// </summary>
|
||||
/// <param name="sourceObject">The source object to slice. This object must have a MeshFilter, a RigidBody and a Collider.</param>
|
||||
/// <param name="sliceNormal">The normal of the cut plane in the local frame of sourceObject.</param>
|
||||
/// <param name="sliceOrigin">The origin of the cut plane in the local frame of sourceObject.</param>
|
||||
/// <param name="options">Options for the slicer</param>
|
||||
/// <param name="fragmentTemplate">The template GameObject that each slice will clone</param>
|
||||
/// <param name="parent">The parent transform for the fragment objects</param>
|
||||
/// <returns></returns>
|
||||
public static void Slice(GameObject sourceObject,
|
||||
Vector3 sliceNormal,
|
||||
Vector3 sliceOrigin,
|
||||
SliceOptions options,
|
||||
GameObject fragmentTemplate,
|
||||
Transform parent)
|
||||
{
|
||||
// Define our source mesh data for the fracturing
|
||||
FragmentData sourceMesh = new FragmentData(sourceObject.GetComponent<MeshFilter>().sharedMesh);
|
||||
// Subdivide the mesh into multiple fragments until we reach the fragment limit
|
||||
FragmentData topSlice, bottomSlice;
|
||||
|
||||
// Slice and dice!
|
||||
MeshSlicer.Slice(sourceMesh,
|
||||
sliceNormal,
|
||||
sliceOrigin,
|
||||
options.textureScale,
|
||||
options.textureOffset,
|
||||
out topSlice,
|
||||
out bottomSlice);
|
||||
|
||||
int i = 0;
|
||||
CreateFragment(topSlice,
|
||||
sourceObject,
|
||||
fragmentTemplate,
|
||||
parent,
|
||||
false,
|
||||
"",
|
||||
options.detectFloatingFragments,
|
||||
ref i);
|
||||
|
||||
CreateFragment(bottomSlice,
|
||||
sourceObject,
|
||||
fragmentTemplate,
|
||||
parent,
|
||||
false,
|
||||
"",
|
||||
options.detectFloatingFragments,
|
||||
ref i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GameObject from the fragment data
|
||||
/// </summary>
|
||||
/// <param name="fragmentMeshData">Geometry of the fragment produced by the slicer</param>
|
||||
/// <param name="sourceObject">The source object to fragment. This object must have a MeshFilter, a RigidBody and a Collider.</param>
|
||||
/// <param name="fragmentTemplate">The template GameObject that each fragment will clone</param>
|
||||
/// <param name="parent">The parent transform for the fragment objects</param>
|
||||
/// <param name="i">Fragment counter</param>
|
||||
private static void CreateFragment(FragmentData fragmentMeshData,
|
||||
GameObject sourceObject,
|
||||
GameObject fragmentTemplate,
|
||||
Transform parent,
|
||||
bool saveToDisk,
|
||||
string saveFolderPath,
|
||||
bool detectFloatingFragments,
|
||||
ref int i)
|
||||
{
|
||||
// If there is no mesh data, don't create an object
|
||||
if (fragmentMeshData.Triangles.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mesh[] meshes;
|
||||
Mesh fragmentMesh = fragmentMeshData.ToMesh();
|
||||
|
||||
// If the "Detect Floating Fragments" option is enabled, take the fragment mesh and
|
||||
// identify disconnected sets of geometry within it, treating each of these as a
|
||||
// separate physical object
|
||||
if (detectFloatingFragments)
|
||||
{
|
||||
meshes = MeshUtils.FindDisconnectedMeshes(fragmentMesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
meshes = new Mesh[] { fragmentMesh };
|
||||
}
|
||||
|
||||
var parentSize = sourceObject.GetComponent<MeshFilter>().sharedMesh.bounds.size;
|
||||
var parentMass = sourceObject.GetComponent<Rigidbody>().mass;
|
||||
|
||||
for(int k = 0; k < meshes.Length; k++)
|
||||
{
|
||||
GameObject fragment = GameObject.Instantiate(fragmentTemplate, parent);
|
||||
fragment.name = $"Fragment{i}";
|
||||
fragment.transform.localPosition = Vector3.zero;
|
||||
fragment.transform.localRotation = Quaternion.identity;
|
||||
fragment.transform.localScale = sourceObject.transform.localScale;
|
||||
|
||||
meshes[k].name = System.Guid.NewGuid().ToString();
|
||||
|
||||
// Update mesh to the new sliced mesh
|
||||
var meshFilter = fragment.GetComponent<MeshFilter>();
|
||||
meshFilter.sharedMesh = meshes[k];
|
||||
|
||||
var collider = fragment.GetComponent<MeshCollider>();
|
||||
|
||||
// If fragment collisions are disabled, collider will be null
|
||||
collider.sharedMesh = meshes[k];
|
||||
collider.convex = true;
|
||||
collider.sharedMaterial = fragment.GetComponent<Collider>().sharedMaterial;
|
||||
|
||||
// Compute mass of the sliced object by dividing mesh bounds by density
|
||||
var parentRigidBody = sourceObject.GetComponent<Rigidbody>();
|
||||
var rigidBody = fragment.GetComponent<Rigidbody>();
|
||||
|
||||
var size = fragmentMesh.bounds.size;
|
||||
float density = (parentSize.x * parentSize.y * parentSize.z) / parentMass;
|
||||
rigidBody.mass = (size.x * size.y * size.z) / density;
|
||||
|
||||
// This code only compiles for the editor
|
||||
#if UNITY_EDITOR
|
||||
if (saveToDisk)
|
||||
{
|
||||
string path = $"{saveFolderPath}/{meshes[k].name}.asset";
|
||||
AssetDatabase.CreateAsset(meshes[k], path);
|
||||
}
|
||||
#endif
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7d041b547ef59d47846c5a02021d424
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,318 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Class which handles slicing a mesh into two pieces given the origin and normal of the slice plane.
|
||||
/// </summary>
|
||||
public static class MeshSlicer
|
||||
{
|
||||
/// <summary>
|
||||
/// Slices the mesh by the plane specified by `sliceNormal` and `sliceOrigin`
|
||||
/// The sliced mesh data is return via out parameters.
|
||||
/// </summary>
|
||||
/// <param name="meshData"></param>
|
||||
/// <param name="sliceNormal">The normal of the slice plane (points towards the top slice)</param>
|
||||
/// <param name="sliceOrigin">The origin of the slice plane</param>
|
||||
/// <param name="textureScale">Scale factor to apply to UV coordinates</param>
|
||||
/// <param name="textureOffset">Offset to apply to UV coordinates</param>
|
||||
/// <param name="topSlice">Out parameter returning fragment mesh data for slice above the plane</param>
|
||||
/// <param name="bottomSlice">Out parameter returning fragment mesh data for slice below the plane</param>
|
||||
public static void Slice(FragmentData meshData,
|
||||
Vector3 sliceNormal,
|
||||
Vector3 sliceOrigin,
|
||||
Vector2 textureScale,
|
||||
Vector2 textureOffset,
|
||||
out FragmentData topSlice,
|
||||
out FragmentData bottomSlice)
|
||||
{
|
||||
topSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount);
|
||||
bottomSlice = new FragmentData(meshData.vertexCount, meshData.triangleCount);
|
||||
|
||||
// Keep track of what side of the cutting plane each vertex is on
|
||||
bool[] side = new bool[meshData.vertexCount];
|
||||
|
||||
// Go through and identify which vertices are above/below the split plane
|
||||
for (int i = 0; i < meshData.Vertices.Count; i++)
|
||||
{
|
||||
var vertex = meshData.Vertices[i];
|
||||
side[i] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin);
|
||||
var slice = side[i] ? topSlice : bottomSlice;
|
||||
slice.AddMappedVertex(vertex, i);
|
||||
}
|
||||
|
||||
int offset = meshData.Vertices.Count;
|
||||
for (int i = 0; i < meshData.CutVertices.Count; i++)
|
||||
{
|
||||
var vertex = meshData.CutVertices[i];
|
||||
side[i + offset] = vertex.position.IsAbovePlane(sliceNormal, sliceOrigin);
|
||||
var slice = side[i + offset] ? topSlice : bottomSlice;
|
||||
slice.AddMappedVertex(vertex, i + offset);
|
||||
}
|
||||
|
||||
SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.Default);
|
||||
SplitTriangles(meshData, topSlice, bottomSlice, sliceNormal, sliceOrigin, side, SlicedMeshSubmesh.CutFace);
|
||||
|
||||
// Fill in the cut plane for each mesh.
|
||||
// The slice normal points to the "above" mesh, so the face normal for the cut face
|
||||
// on the above mesh is opposite of the slice normal. Conversely, normal for the
|
||||
// cut face on the "below" mesh is in the direction of the slice normal
|
||||
FillCutFaces(topSlice, bottomSlice, -sliceNormal, textureScale, textureOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the cut faces for each sliced mesh. The `sliceNormal` is the normal for the plane and points
|
||||
/// in the direction of `topMeshData`
|
||||
/// </summary>
|
||||
/// <param name="topSlice">Fragment mesh data for slice above the slice plane</param>
|
||||
/// <param name="bottomSlice">Fragment mesh data for slice above the slice plane</param>
|
||||
/// <param name="sliceNormal">Normal of the slice plane (points towards the top slice)</param>
|
||||
/// <param name="textureScale">Scale factor to apply to UV coordinates</param>
|
||||
/// <param name="textureOffset">Offset to apply to UV coordinates</param>
|
||||
private static void FillCutFaces(FragmentData topSlice,
|
||||
FragmentData bottomSlice,
|
||||
Vector3 sliceNormal,
|
||||
Vector2 textureScale,
|
||||
Vector2 textureOffset)
|
||||
{
|
||||
// Since the topSlice and bottomSlice both share the same cut face, we only need to calculate it
|
||||
// once. Then the same vertex/triangle data for the face will be used for both slices, except
|
||||
// with the normals reversed.
|
||||
|
||||
// First need to weld the coincident vertices for the triangulation to work properly
|
||||
topSlice.WeldCutFaceVertices();
|
||||
|
||||
// Need at least 3 vertices to triangulate
|
||||
if (topSlice.CutVertices.Count < 3) return;
|
||||
|
||||
// Triangulate the cut face
|
||||
var triangulator = new ConstrainedTriangulator(topSlice.CutVertices, topSlice.Constraints, sliceNormal);
|
||||
int[] triangles = triangulator.Triangulate();
|
||||
|
||||
// Update normal and UV for the cut face vertices
|
||||
for (int i = 0; i < topSlice.CutVertices.Count; i++)
|
||||
{
|
||||
var vertex = topSlice.CutVertices[i];
|
||||
var point = triangulator.points[i];
|
||||
|
||||
// UV coordinates are based off of the 2D coordinates used for triangulation
|
||||
// During triangulation, coordinates are normalized to [0,1], so need to multiply
|
||||
// by normalization scale factor to get back to the appropritate scale
|
||||
Vector2 uv = new Vector2(
|
||||
(triangulator.normalizationScaleFactor * point.coords.x) * textureScale.x + textureOffset.x,
|
||||
(triangulator.normalizationScaleFactor * point.coords.y) * textureScale.y + textureOffset.y);
|
||||
|
||||
// Update normals and UV coordinates for the cut vertices
|
||||
var topVertex = vertex;
|
||||
topVertex.normal = sliceNormal;
|
||||
topVertex.uv = uv;
|
||||
|
||||
var bottomVertex = vertex;
|
||||
bottomVertex.normal = -sliceNormal;
|
||||
bottomVertex.uv = uv;
|
||||
|
||||
topSlice.CutVertices[i] = topVertex;
|
||||
bottomSlice.CutVertices[i] = bottomVertex;
|
||||
}
|
||||
|
||||
// Add the new triangles to the top/bottom slices
|
||||
int offsetTop = topSlice.Vertices.Count;
|
||||
int offsetBottom = bottomSlice.Vertices.Count;
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
topSlice.AddTriangle(
|
||||
offsetTop + triangles[i],
|
||||
offsetTop + triangles[i + 1],
|
||||
offsetTop + triangles[i + 2],
|
||||
SlicedMeshSubmesh.CutFace);
|
||||
|
||||
bottomSlice.AddTriangle(
|
||||
offsetBottom + triangles[i],
|
||||
offsetBottom + triangles[i + 2], // Swap two vertices so triangles are wound CW
|
||||
offsetBottom + triangles[i + 1],
|
||||
SlicedMeshSubmesh.CutFace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies triangles that are intersected by the slice plane and splits them in two
|
||||
/// </summary>
|
||||
/// <param name="meshData"></param>
|
||||
/// <param name="topSlice">Fragment mesh data for slice above the slice plane</param>
|
||||
/// <param name="bottomSlice">Fragment mesh data for slice above the slice plane</param>
|
||||
/// <param name="sliceNormal">The normal of the slice plane (points towards the top slice)</param>
|
||||
/// <param name="sliceOrigin">The origin of the slice plane</param>
|
||||
/// <param name="side">Array mapping each vertex to either the top/bottom slice</param>
|
||||
/// <param name="subMesh">Index of the sub mesh</param>
|
||||
private static void SplitTriangles(FragmentData meshData,
|
||||
FragmentData topSlice,
|
||||
FragmentData bottomSlice,
|
||||
Vector3 sliceNormal,
|
||||
Vector3 sliceOrigin,
|
||||
bool[] side,
|
||||
SlicedMeshSubmesh subMesh)
|
||||
{
|
||||
int[] triangles = meshData.GetTriangles((int)subMesh);
|
||||
|
||||
// Keep track of vertices that lie on the intersection plane
|
||||
int a, b, c;
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
// Get vertex indexes for this triangle
|
||||
a = triangles[i];
|
||||
b = triangles[i + 1];
|
||||
c = triangles[i + 2];
|
||||
|
||||
// Triangle is contained completely within mesh A
|
||||
if (side[a] && side[b] && side[c])
|
||||
{
|
||||
topSlice.AddMappedTriangle(a, b, c, subMesh);
|
||||
}
|
||||
// Triangle is contained completely within mesh B
|
||||
else if (!side[a] && !side[b] && !side[c])
|
||||
{
|
||||
bottomSlice.AddMappedTriangle(a, b, c, subMesh);
|
||||
}
|
||||
// Triangle is intersected by the slicing plane. Need to subdivide it
|
||||
else
|
||||
{
|
||||
// In these cases, two vertices of the triangle are above the cut plane and one vertex is below
|
||||
if (side[b] && side[c] && !side[a])
|
||||
{
|
||||
SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true);
|
||||
}
|
||||
else if (side[c] && side[a] && !side[b])
|
||||
{
|
||||
SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true);
|
||||
}
|
||||
else if (side[a] && side[b] && !side[c])
|
||||
{
|
||||
SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, true);
|
||||
}
|
||||
// In these cases, two vertices of the triangle are below the cut plane and one vertex is above
|
||||
else if (!side[b] && !side[c] && side[a])
|
||||
{
|
||||
SplitTriangle(b, c, a, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false);
|
||||
}
|
||||
else if (!side[c] && !side[a] && side[b])
|
||||
{
|
||||
SplitTriangle(c, a, b, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false);
|
||||
}
|
||||
else if (!side[a] && !side[b] && side[c])
|
||||
{
|
||||
SplitTriangle(a, b, c, sliceNormal, sliceOrigin, meshData, topSlice, bottomSlice, subMesh, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits triangle defined by the points (v1,v2,v3)
|
||||
/// </summary>
|
||||
/// <param name="v1_idx">Index of first vertex in triangle</param>
|
||||
/// <param name="v2_idx">Index of second vertex in triangle<</param>
|
||||
/// <param name="v3_idx">Index of third vertex in triangle<</param>
|
||||
/// <param name="sliceNormal">The normal of the slice plane (points towards the top slice)</param>
|
||||
/// <param name="sliceOrigin">The origin of the slice plane</param>
|
||||
/// <param name="meshData">Original mesh data</param>
|
||||
/// <param name="topSlice">Mesh data for top slice</param>
|
||||
/// <param name="bottomSlice">Mesh data for bottom slice</param>
|
||||
/// <param name="subMesh">Index of the submesh that the triangle belongs to</param>
|
||||
/// <param name="v3BelowCutPlane">Boolean indicating whether v3 is above or below the slice plane.</param>
|
||||
private static void SplitTriangle(int v1_idx,
|
||||
int v2_idx,
|
||||
int v3_idx,
|
||||
Vector3 sliceNormal,
|
||||
Vector3 sliceOrigin,
|
||||
FragmentData meshData,
|
||||
FragmentData topSlice,
|
||||
FragmentData bottomSlice,
|
||||
SlicedMeshSubmesh subMesh,
|
||||
bool v3BelowCutPlane)
|
||||
{
|
||||
// - `v1`, `v2`, `v3` are the indexes of the triangle relative to the original mesh data
|
||||
// - `v1` and `v2` are on the the side of split plane that belongs to meshA
|
||||
// - `v3` is on the side of the split plane that belongs to meshB
|
||||
// - `vertices`, `normals`, `uv` are the original mesh data used for interpolation
|
||||
//
|
||||
// v3BelowCutPlane = true
|
||||
// ======================
|
||||
//
|
||||
// v1 *_____________* v2 .
|
||||
// \ / /|\ cutNormal
|
||||
// \ / |
|
||||
// ----*-------*---------*--
|
||||
// v13 \ / v23 cutOrigin
|
||||
// \ /
|
||||
// \ /
|
||||
// * v3 triangle normal out of screen
|
||||
//
|
||||
// v3BelowCutPlane = false
|
||||
// =======================
|
||||
//
|
||||
// * v3 .
|
||||
// / \ /|\ cutNormal
|
||||
// v23 / \ v13 |
|
||||
// -----*-----*----------*--
|
||||
// / \ cut origin
|
||||
// / \
|
||||
// v2 *___________* v1 triangle normal out of screen
|
||||
//
|
||||
|
||||
float s13;
|
||||
float s23;
|
||||
Vector3 v13;
|
||||
Vector3 v23;
|
||||
|
||||
MeshVertex v1 = v1_idx < meshData.Vertices.Count ? meshData.Vertices[v1_idx] : meshData.CutVertices[v1_idx - meshData.Vertices.Count];
|
||||
MeshVertex v2 = v2_idx < meshData.Vertices.Count ? meshData.Vertices[v2_idx] : meshData.CutVertices[v2_idx - meshData.Vertices.Count];
|
||||
MeshVertex v3 = v3_idx < meshData.Vertices.Count ? meshData.Vertices[v3_idx] : meshData.CutVertices[v3_idx - meshData.Vertices.Count];
|
||||
|
||||
if (MathUtils.LinePlaneIntersection(v1.position, v3.position, sliceNormal, sliceOrigin, out v13, out s13) &&
|
||||
MathUtils.LinePlaneIntersection(v2.position, v3.position, sliceNormal, sliceOrigin, out v23, out s23))
|
||||
{
|
||||
// Interpolate normals and UV coordinates
|
||||
var norm13 = (v1.normal + s13 * (v3.normal - v1.normal)).normalized;
|
||||
var norm23 = (v2.normal + s23 * (v3.normal - v2.normal)).normalized;
|
||||
var uv13 = v1.uv + s13 * (v3.uv - v1.uv);
|
||||
var uv23 = v2.uv + s23 * (v3.uv - v2.uv);
|
||||
|
||||
// Add vertices/normals/uv for the intersection points to each mesh
|
||||
topSlice.AddCutFaceVertex(v13, norm13, uv13);
|
||||
topSlice.AddCutFaceVertex(v23, norm23, uv23);
|
||||
bottomSlice.AddCutFaceVertex(v13, norm13, uv13);
|
||||
bottomSlice.AddCutFaceVertex(v23, norm23, uv23);
|
||||
|
||||
// Indices for the intersection vertices (for the original mesh data)
|
||||
int index13_A = topSlice.Vertices.Count - 2;
|
||||
int index23_A = topSlice.Vertices.Count - 1;
|
||||
int index13_B = bottomSlice.Vertices.Count - 2;
|
||||
int index23_B = bottomSlice.Vertices.Count - 1;
|
||||
|
||||
if (v3BelowCutPlane)
|
||||
{
|
||||
// Triangle slice above the cutting plane is a quad, so divide into two triangles
|
||||
topSlice.AddTriangle(index23_A, index13_A, topSlice.IndexMap[v2_idx], subMesh);
|
||||
topSlice.AddTriangle(index13_A, topSlice.IndexMap[v1_idx], topSlice.IndexMap[v2_idx], subMesh);
|
||||
|
||||
// One triangle must be added to mesh 2
|
||||
bottomSlice.AddTriangle(bottomSlice.IndexMap[v3_idx], index13_B, index23_B, subMesh);
|
||||
|
||||
// When looking at the cut-face, the edges should wind counter-clockwise
|
||||
topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 2, topSlice.CutVertices.Count - 1));
|
||||
bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 1, bottomSlice.CutVertices.Count - 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Triangle slice above the cutting plane is a simple triangle
|
||||
topSlice.AddTriangle(index13_A, index23_A, topSlice.IndexMap[v3_idx], subMesh);
|
||||
|
||||
// Triangle slice below the cutting plane is a quad, so divide into two triangles
|
||||
bottomSlice.AddTriangle(bottomSlice.IndexMap[v1_idx], bottomSlice.IndexMap[v2_idx], index13_B, subMesh);
|
||||
bottomSlice.AddTriangle(bottomSlice.IndexMap[v2_idx], index23_B, index13_B, subMesh);
|
||||
|
||||
// When looking at the cut-face, the edges should wind counter-clockwise
|
||||
topSlice.Constraints.Add(new EdgeConstraint(topSlice.CutVertices.Count - 1, topSlice.CutVertices.Count - 2));
|
||||
bottomSlice.Constraints.Add(new EdgeConstraint(bottomSlice.CutVertices.Count - 2, bottomSlice.CutVertices.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 838da7c4073cb5544a139058fcac2186
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
/// <summary>
|
||||
/// Data structure containing position/normal/UV data for a single vertex
|
||||
/// </summary>
|
||||
public struct MeshVertex
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector3 normal;
|
||||
public Vector2 uv;
|
||||
|
||||
public MeshVertex(Vector3 position)
|
||||
{
|
||||
this.position = position;
|
||||
this.normal = Vector3.zero;
|
||||
this.uv = Vector2.zero;
|
||||
}
|
||||
|
||||
public MeshVertex(Vector3 position, Vector3 normal, Vector2 uv)
|
||||
{
|
||||
this.position = position;
|
||||
this.normal = normal;
|
||||
this.uv = uv;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is MeshVertex)) return false;
|
||||
|
||||
return ((MeshVertex)obj).position.Equals(this.position);
|
||||
}
|
||||
|
||||
public static bool operator ==(MeshVertex lhs, MeshVertex rhs)
|
||||
{
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(MeshVertex lhs, MeshVertex rhs)
|
||||
{
|
||||
return !lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.position.GetHashCode();
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Position = {position}, Normal = {normal}, UV = {uv}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92df5b8640ebfb243a19d317a09dec51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
/// <summary>
|
||||
/// Data structure that holds triangulation adjacency data for a quad
|
||||
/// </summary>
|
||||
public struct Quad
|
||||
{
|
||||
// q3
|
||||
// *---------*---------*
|
||||
// \ / \ /
|
||||
// \ t2L / \ t2R /
|
||||
// \ / \ /
|
||||
// \ / t2 \ /
|
||||
// q1 *---------* q2
|
||||
// / \ t1 / \
|
||||
// / \ / \
|
||||
// / t1L \ / t1R \
|
||||
// / \ / \
|
||||
// *---------*---------*
|
||||
// q4
|
||||
|
||||
/// <summary>
|
||||
/// The indices of the quad vertices
|
||||
/// </summary>
|
||||
public int q1, q2, q3, q4;
|
||||
|
||||
/// <summary>
|
||||
/// The triangles that make up the quad
|
||||
/// </summary>
|
||||
public int t1, t2;
|
||||
|
||||
/// <summary>
|
||||
/// Triangle adjacency data
|
||||
/// </summary>
|
||||
public int t1L, t1R, t2L, t2R;
|
||||
|
||||
public Quad(int q1, int q2, int q3, int q4, int t1, int t2, int t1L, int t1R, int t2L, int t2R)
|
||||
{
|
||||
this.q1 = q1;
|
||||
this.q2 = q2;
|
||||
this.q3 = q3;
|
||||
this.q4 = q4;
|
||||
this.t1 = t1;
|
||||
this.t2 = t2;
|
||||
this.t1L = t1L;
|
||||
this.t1R = t1R;
|
||||
this.t2L = t2L;
|
||||
this.t2R = t2R;
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public override string ToString()
|
||||
{
|
||||
return $"T{t1}/T{t2} (V{q1},V{q2},V{q3},V{q4})";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1124001d561fa9e4dbaef234fcbb0d10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
/// <summary>
|
||||
/// This data structure is used to represent a point during triangulation.
|
||||
/// </summary>
|
||||
public class TriangulationPoint: IBinSortable
|
||||
{
|
||||
/// <summary>
|
||||
/// 2D coordinates of the point on the triangulation plane
|
||||
/// </summary>
|
||||
public Vector2 coords;
|
||||
|
||||
/// <summary>
|
||||
/// Bin used for sorting points in grid
|
||||
/// </summary>
|
||||
public int bin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Original index prior to sorting
|
||||
/// </summary>
|
||||
public int index = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new triangulation point
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the point in the original point list</param>
|
||||
/// <param name="coords">The 2D coordinates of the point in the triangulation plane</param>
|
||||
public TriangulationPoint(int index, Vector2 coords)
|
||||
{
|
||||
this.index = index;
|
||||
this.coords = coords;
|
||||
}
|
||||
|
||||
[ExcludeFromCoverage]
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{coords} -> {bin}";
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db421ee84ac7f3246925361ae77699bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,599 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for triangulating a set of 3D points. Only supports convex polygons.
|
||||
/// </summary>
|
||||
public class Triangulator
|
||||
{
|
||||
// Constants for triangulation array indices
|
||||
protected const int V1 = 0; // Vertex 1
|
||||
protected const int V2 = 1; // Vertex 2
|
||||
protected const int V3 = 2; // Vertex 3
|
||||
protected const int E12 = 3; // Adjacency data for edge (V1 -> V2)
|
||||
protected const int E23 = 4; // Adjacency data for edge (V2 -> V3)
|
||||
protected const int E31 = 5; // Adjacency data for edge (V3 -> V1)
|
||||
|
||||
// Index for super triangle
|
||||
protected const int SUPERTRIANGLE = 0;
|
||||
|
||||
// Index for out of bounds triangle (boundary edge)
|
||||
protected const int OUT_OF_BOUNDS = -1;
|
||||
|
||||
// Number of points to be triangulated (excluding super triangle vertices)
|
||||
protected int N;
|
||||
|
||||
// Total number of triangles generated during triangulation
|
||||
protected int triangleCount;
|
||||
|
||||
// Triangle vertex and adjacency data
|
||||
// Index 0 = Triangle index
|
||||
// Index 1 = [V1, V2, V3, E12, E23, E32]
|
||||
protected int[, ] triangulation;
|
||||
|
||||
// Points on the plane to triangulate
|
||||
public TriangulationPoint[] points;
|
||||
|
||||
// Array which tracks which triangles should be ignored in the final triangulation
|
||||
protected bool[] skipTriangle;
|
||||
|
||||
// Normal of the plane on which the points lie
|
||||
protected Vector3 normal;
|
||||
|
||||
// Normalization scale factor
|
||||
public float normalizationScaleFactor = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the triangulator with the vertex data to be triangulated
|
||||
/// </summary>
|
||||
/// <param name="inputPoints">The points to triangulate</param>
|
||||
/// <param name="normal">The normal of the triangulation plane</param>
|
||||
public Triangulator(List<MeshVertex> inputPoints, Vector3 normal)
|
||||
{
|
||||
// Need at least three input vertices to triangulate
|
||||
if (inputPoints == null || inputPoints.Count < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.N = inputPoints.Count;
|
||||
this.triangleCount = 2 * N + 1;
|
||||
this.triangulation = new int[triangleCount, 6];
|
||||
this.skipTriangle = new bool[triangleCount];
|
||||
this.points = new TriangulationPoint[N + 3]; // Extra 3 points used to store super triangle
|
||||
this.normal = normal;
|
||||
|
||||
// Choose two points in the plane as one basis vector
|
||||
Vector3 e1 = (inputPoints[0].position - inputPoints[1].position).normalized;
|
||||
Vector3 e2 = normal.normalized;
|
||||
Vector3 e3 = Vector3.Cross(e1, e2).normalized;
|
||||
|
||||
// To find the 2nd basis vector, find the largest component and swap with the smallest, negating the largest
|
||||
|
||||
// Project 3D vertex onto the 2D plane
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var position = inputPoints[i].position;
|
||||
var coords = new Vector2(Vector3.Dot(position, e1), Vector3.Dot(position, e3));
|
||||
this.points[i] = new TriangulationPoint(i, coords);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the triangulation
|
||||
/// </summary>
|
||||
/// <returns>Returns an array containing the indices of the triangles, mapped to the list of points passed in during initialization</returns>
|
||||
public virtual int[] Triangulate()
|
||||
{
|
||||
// Need at least 3 vertices to triangulate
|
||||
if (N < 3)
|
||||
{
|
||||
return new int[] { };
|
||||
}
|
||||
|
||||
this.AddSuperTriangle();
|
||||
this.NormalizeCoordinates();
|
||||
this.ComputeTriangulation();
|
||||
this.DiscardTrianglesWithSuperTriangleVertices();
|
||||
|
||||
List<int> triangles = new List<int>(3 * triangleCount);
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
// Add all triangles that don't contain a super-triangle vertex
|
||||
if (!skipTriangle[i])
|
||||
{
|
||||
triangles.Add(triangulation[i, V1]);
|
||||
triangles.Add(triangulation[i, V2]);
|
||||
triangles.Add(triangulation[i, V3]);
|
||||
}
|
||||
}
|
||||
|
||||
return triangles.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uniformly scales the 2D coordinates of all the points between [0, 1]
|
||||
/// </summary>
|
||||
protected void NormalizeCoordinates()
|
||||
{
|
||||
// 1) Normalize coordinates. Coordinates are scaled so they lie between 0 and 1
|
||||
// The scaling should be uniform so relative positions of points are unchanged
|
||||
|
||||
float xMin = float.MaxValue;
|
||||
float xMax = float.MinValue;
|
||||
float yMin = float.MaxValue;
|
||||
float yMax = float.MinValue;
|
||||
|
||||
// Find min/max points in the set
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var point = points[i];
|
||||
if (point.coords.x < xMin) xMin = point.coords.x;
|
||||
if (point.coords.y < yMin) yMin = point.coords.y;
|
||||
if (point.coords.x > xMax) xMax = point.coords.x;
|
||||
if (point.coords.y > yMax) yMax = point.coords.y;
|
||||
}
|
||||
|
||||
// Normalization coefficient. Using same coefficient for both x & y
|
||||
// ensures uniform scaling
|
||||
normalizationScaleFactor = Mathf.Max(xMax - xMin, yMax - yMin);
|
||||
|
||||
// Normalize each point
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var point = points[i];
|
||||
var normalizedPos = new Vector2(
|
||||
(point.coords.x - xMin) / normalizationScaleFactor,
|
||||
(point.coords.y - yMin) / normalizationScaleFactor);
|
||||
|
||||
points[i].coords = normalizedPos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the points into bins using an ordered grid
|
||||
/// </summary>
|
||||
/// <returns>Returns the array of sorted points</returns>
|
||||
protected TriangulationPoint[] SortPointsIntoBins()
|
||||
{
|
||||
// Compute the number of bins along each axis
|
||||
int n = Mathf.RoundToInt(Mathf.Pow((float) N, 0.25f));
|
||||
|
||||
// Total bin count
|
||||
int binCount = n * n;
|
||||
|
||||
// Assign bin numbers to each point by taking the normalized coordinates
|
||||
// and dividing them into a n x n grid.
|
||||
for (int k = 0; k < N; k++)
|
||||
{
|
||||
var point = this.points[k];
|
||||
int i = (int) (0.99f * n * point.coords.y);
|
||||
int j = (int) (0.99f * n * point.coords.x);
|
||||
point.bin = BinSort.GetBinNumber(i, j, n);
|
||||
}
|
||||
|
||||
return BinSort.Sort<TriangulationPoint>(this.points, N, binCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the triangulation of the point set.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the triangulation was successful</returns>
|
||||
protected bool ComputeTriangulation()
|
||||
{
|
||||
// Index of the current triangle being searched
|
||||
int tSearch = 0;
|
||||
// Index of the last triangle formed
|
||||
int tLast = 0;
|
||||
|
||||
var sortedPoints = SortPointsIntoBins();
|
||||
|
||||
// Loop through each point and insert it into the triangulation
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
TriangulationPoint point = sortedPoints[i];
|
||||
|
||||
// Insert new point into the triangulation. Start by finding the triangle that contains the point `p`
|
||||
// Keep track of how many triangles we visited in case search fails and we get stuck in a loop
|
||||
int counter = 0;
|
||||
bool pointInserted = false;
|
||||
while (!pointInserted)
|
||||
{
|
||||
if (counter++ > tLast || tSearch == OUT_OF_BOUNDS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Get coordinates of triangle vertices
|
||||
var v1 = this.points[triangulation[tSearch, V1]].coords;
|
||||
var v2 = this.points[triangulation[tSearch, V2]].coords;
|
||||
var v3 = this.points[triangulation[tSearch, V3]].coords;
|
||||
|
||||
// Verify that point is on the correct side of each edge of the triangle.
|
||||
// If a point is on the left side of an edge, move to the adjacent triangle and check again. The search
|
||||
// continues until a containing triangle is found or the point is outside of all triangles
|
||||
if (!MathUtils.IsPointOnRightSideOfLine(v1, v2, point.coords))
|
||||
{
|
||||
tSearch = triangulation[tSearch, E12];
|
||||
}
|
||||
else if (!MathUtils.IsPointOnRightSideOfLine(v2, v3, point.coords))
|
||||
{
|
||||
tSearch = triangulation[tSearch, E23];
|
||||
}
|
||||
else if (!MathUtils.IsPointOnRightSideOfLine(v3, v1, point.coords))
|
||||
{
|
||||
tSearch = triangulation[tSearch, E31];
|
||||
}
|
||||
// If it is on the right side of all three edges, it is contained within the triangle (Unity uses CW winding).
|
||||
else
|
||||
{
|
||||
InsertPointIntoTriangle(point, tSearch, tLast);
|
||||
tLast += 2;
|
||||
tSearch = tLast;
|
||||
pointInserted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the triangulation by inserting the super triangle
|
||||
/// </summary>
|
||||
protected void AddSuperTriangle()
|
||||
{
|
||||
// Add new points to the end of the points array
|
||||
this.points[N] = new TriangulationPoint(N, new Vector2(-100f, -100f));
|
||||
this.points[N + 1] = new TriangulationPoint(N + 1, new Vector2(0f, 100f));
|
||||
this.points[N + 2] = new TriangulationPoint(N + 2, new Vector2(100f, -100f));
|
||||
|
||||
// Store supertriangle in the first column of the vertex and adjacency data
|
||||
triangulation[SUPERTRIANGLE, V1] = N;
|
||||
triangulation[SUPERTRIANGLE, V2] = N + 1;
|
||||
triangulation[SUPERTRIANGLE, V3] = N + 2;
|
||||
|
||||
// Zeros signify boundary edges
|
||||
triangulation[SUPERTRIANGLE, E12] = OUT_OF_BOUNDS;
|
||||
triangulation[SUPERTRIANGLE, E23] = OUT_OF_BOUNDS;
|
||||
triangulation[SUPERTRIANGLE, E31] = OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the point `p` into triangle `t`, replacing it with three new triangles
|
||||
/// </summary>
|
||||
/// <param name="p">The index of the point to insert</param>
|
||||
/// <param name="t">The index of the triangle</param>
|
||||
/// <param name="triangleCount">Total number of triangles created so far</param>
|
||||
protected void InsertPointIntoTriangle(TriangulationPoint p, int t, int triangleCount)
|
||||
{
|
||||
// V1
|
||||
// *
|
||||
// /|\
|
||||
// /3|2\
|
||||
// / | \
|
||||
// / | \
|
||||
// / | \
|
||||
// / | \
|
||||
// / t1 | t3 \
|
||||
// / | \
|
||||
// / 1 * 1 \
|
||||
// / __/1\__ \
|
||||
// / __/ \__ \
|
||||
// / 2__/ t2 \__3 \
|
||||
// / _/3 2\_ \
|
||||
// *---------------------------*
|
||||
// V3 V2
|
||||
|
||||
int t1 = t;
|
||||
int t2 = triangleCount + 1;
|
||||
int t3 = triangleCount + 2;
|
||||
|
||||
// Add the vertex & adjacency information for the two new triangles
|
||||
// New vertex is set to first vertex of each triangle to help with
|
||||
// restoring the triangulation later on
|
||||
triangulation[t2, V1] = p.index;
|
||||
triangulation[t2, V2] = triangulation[t, V2];
|
||||
triangulation[t2, V3] = triangulation[t, V3];
|
||||
|
||||
triangulation[t2, E12] = t3;
|
||||
triangulation[t2, E23] = triangulation[t, E23];
|
||||
triangulation[t2, E31] = t1;
|
||||
|
||||
triangulation[t3, V1] = p.index;
|
||||
triangulation[t3, V2] = triangulation[t, V1];
|
||||
triangulation[t3, V3] = triangulation[t, V2];
|
||||
|
||||
triangulation[t3, E12] = t1;
|
||||
triangulation[t3, E23] = triangulation[t, E12];
|
||||
triangulation[t3, E31] = t2;
|
||||
|
||||
// Triangle index remains the same for E12, no need to update adjacency
|
||||
UpdateAdjacency(triangulation[t, E12], t, t3);
|
||||
UpdateAdjacency(triangulation[t, E23], t, t2);
|
||||
|
||||
// Replace existing triangle `t` with `t1`
|
||||
triangulation[t1, V2] = triangulation[t, V3];
|
||||
triangulation[t1, V3] = triangulation[t, V1];
|
||||
triangulation[t1, V1] = p.index;
|
||||
|
||||
triangulation[t1, E23] = triangulation[t, E31];
|
||||
triangulation[t1, E12] = t2;
|
||||
triangulation[t1, E31] = t3;
|
||||
|
||||
// After the triangles have been inserted, restore the Delauney triangulation
|
||||
RestoreDelauneyTriangulation(p, t1, t2, t3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the triangulation to a Delauney triangulation after new triangles have been added.
|
||||
/// </summary>
|
||||
/// <param name="p">Index of the inserted point</param>
|
||||
/// <param name="t1">Index of first triangle to check</param>
|
||||
/// <param name="t2">Index of second triangle to check</param>
|
||||
/// <param name="t3">Index of third triangle to check</param>
|
||||
protected void RestoreDelauneyTriangulation(TriangulationPoint p, int t1, int t2, int t3)
|
||||
{
|
||||
int t4;
|
||||
Stack < (int, int) > s = new Stack < (int, int) > ();
|
||||
|
||||
s.Push((t1, triangulation[t1, E23]));
|
||||
s.Push((t2, triangulation[t2, E23]));
|
||||
s.Push((t3, triangulation[t3, E23]));
|
||||
|
||||
while (s.Count > 0)
|
||||
{
|
||||
// Pop next triangle and its adjacent triangle off the stack
|
||||
// t1 contains the newly added vertex at V1
|
||||
// t2 is adjacent to t1 along the opposite edge of V1
|
||||
(t1, t2) = s.Pop();
|
||||
|
||||
if (t2 == OUT_OF_BOUNDS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// If t2 circumscribes p, the quadrilateral formed by t1+t2 has the
|
||||
// diagonal drawn in the wrong direction and needs to be swapped
|
||||
else if (SwapQuadDiagonalIfNeeded(p.index, t1, t2, out t3, out t4))
|
||||
{
|
||||
// Push newly formed triangles onto the stack to see if their diagonals
|
||||
// need to be swapped
|
||||
s.Push((t1, t3));
|
||||
s.Push((t2, t4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the diagonal of the quadrilateral formed by triangle `t` and the
|
||||
/// triangle adjacent to the edge that is opposite of the newly added point
|
||||
/// </summary>
|
||||
/// <param name="p">The index of the inserted point</param>
|
||||
/// <param name="t1">Index of the triangle containing p</param>
|
||||
/// <param name="t2">Index of the triangle opposite t1 that shares edge E23 with t1</param>
|
||||
/// <param name="t3">Index of triangle adjacent to t1 after swap</param>
|
||||
/// <param name="t4">Index of triangle adjacent to t2 after swap</param>
|
||||
/// <returns>Returns true if the swap was performed. If the swap was not
|
||||
/// performed (e.g. returns false), t3 and t4 are unused.
|
||||
/// </returns>
|
||||
protected bool SwapQuadDiagonalIfNeeded(int p, int t1, int t2, out int t3, out int t4)
|
||||
{
|
||||
// 1) Form quadrilateral from t1 + t2 (q0->q1->q2->q3)
|
||||
// 2) Swap diagonal between q1->q3 to q0->q2
|
||||
//
|
||||
// BEFORE AFTER
|
||||
//
|
||||
// q3 q3
|
||||
// *-------------*-------------* *-------------*-------------*
|
||||
// \ / \ / \ /|\ /
|
||||
// \ t3 / \ t4 / \ t3 /3|2\ t4 /
|
||||
// \ / \ / \ / | \ /
|
||||
// \ / \ / \ / | \ /
|
||||
// \ / t2 \ / \ / | \ /
|
||||
// \ / \ / \ / | \ /
|
||||
// q1 *-------------* q2 q1 * 2 t1 | t2 3 * q2
|
||||
// \2 3/ \ | /
|
||||
// \ / \ | /
|
||||
// \ t1 / \ | /
|
||||
// \ / \ | /
|
||||
// \ / \1|1/
|
||||
// \1/ \|/
|
||||
// * q4 == p * q4 == p
|
||||
//
|
||||
|
||||
// Get the vertices of the quad. The new vertex is always located at V1 of the triangle
|
||||
int q4 = p;
|
||||
int q1, q2, q3;
|
||||
|
||||
// Since t2 might be oriented in any direction, find which edge is adjacent to `t`
|
||||
// The 4th vertex of the quad will be opposite this edge. We also need the two triangles
|
||||
// t3 and t3 that are adjacent to t2 along the other edges since the adjacency information
|
||||
// needs to be updated for those triangles.
|
||||
if (triangulation[t2, E12] == t1)
|
||||
{
|
||||
q1 = triangulation[t2, V2];
|
||||
q2 = triangulation[t2, V1];
|
||||
q3 = triangulation[t2, V3];
|
||||
|
||||
t3 = triangulation[t2, E23];
|
||||
t4 = triangulation[t2, E31];
|
||||
}
|
||||
else if (triangulation[t2, E23] == t1)
|
||||
{
|
||||
q1 = triangulation[t2, V3];
|
||||
q2 = triangulation[t2, V2];
|
||||
q3 = triangulation[t2, V1];
|
||||
|
||||
t3 = triangulation[t2, E31];
|
||||
t4 = triangulation[t2, E12];
|
||||
}
|
||||
else // (triangulation[t2, E31] == t1)
|
||||
{
|
||||
q1 = triangulation[t2, V1];
|
||||
q2 = triangulation[t2, V3];
|
||||
q3 = triangulation[t2, V2];
|
||||
|
||||
t3 = triangulation[t2, E12];
|
||||
t4 = triangulation[t2, E23];
|
||||
}
|
||||
|
||||
// Perform test to see if p lies in the circumcircle of t2
|
||||
if (SwapTest(points[q1].coords, points[q2].coords, points[q3].coords, points[q4].coords))
|
||||
{
|
||||
// Update adjacency for triangles adjacent to t1 and t2
|
||||
UpdateAdjacency(t3, t2, t1);
|
||||
UpdateAdjacency(triangulation[t1, E31], t1, t2);
|
||||
|
||||
// Perform the swap. As always, put the new vertex as the first vertex of the triangle
|
||||
triangulation[t1, V1] = q4;
|
||||
triangulation[t1, V2] = q1;
|
||||
triangulation[t1, V3] = q3;
|
||||
|
||||
triangulation[t2, V1] = q4;
|
||||
triangulation[t2, V2] = q3;
|
||||
triangulation[t2, V3] = q2;
|
||||
|
||||
// Update adjacency information (order of operations is important here since we
|
||||
// are overwriting data).
|
||||
triangulation[t2, E12] = t1;
|
||||
triangulation[t2, E23] = t4;
|
||||
triangulation[t2, E31] = triangulation[t1, E31];
|
||||
|
||||
// triangulation[t1, E12] = t2;
|
||||
triangulation[t1, E23] = t3;
|
||||
triangulation[t1, E31] = t2;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks any triangles that contain super-triangle vertices as discarded
|
||||
/// </summary>
|
||||
protected void DiscardTrianglesWithSuperTriangleVertices()
|
||||
{
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
// Add all triangles that don't contain a super-triangle vertex
|
||||
if (TriangleContainsVertex(i, N) ||
|
||||
TriangleContainsVertex(i, N + 1) ||
|
||||
TriangleContainsVertex(i, N + 2))
|
||||
{
|
||||
skipTriangle[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the triangle formed by points v1->v2->v3 circumscribes point vP
|
||||
/// </summary>
|
||||
/// <param name="v1">Coordinates of 1st vertex of triangle</param>
|
||||
/// <param name="v2">Coordinates of 2nd vertex of triangle</param>
|
||||
/// <param name="v3">Coordinates of 3rd vertex of triangle</param>
|
||||
/// <param name="v4">Coordinates of test point</param>
|
||||
/// <returns> Returns true if the triangle `t` circumscribes the point `p`</returns>
|
||||
protected bool SwapTest(Vector2 v1, Vector2 v2, Vector2 v3, Vector2 v4)
|
||||
{
|
||||
float x13 = v1.x - v3.x;
|
||||
float x23 = v2.x - v3.x;
|
||||
float y13 = v1.y - v3.y;
|
||||
float y23 = v2.y - v3.y;
|
||||
float x14 = v1.x - v4.x;
|
||||
float x24 = v2.x - v4.x;
|
||||
float y14 = v1.y - v4.y;
|
||||
float y24 = v2.y - v4.y;
|
||||
|
||||
float cosA = x13 * x23 + y13 * y23;
|
||||
float cosB = x24 * x14 + y24 * y14;
|
||||
|
||||
if (cosA >= 0 && cosB >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (cosA < 0 && cosB < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float sinA = (x13 * y23 - x23 * y13);
|
||||
float sinB = (x24 * y14 - x14 * y24);
|
||||
float sinAB = sinA * cosB + sinB * cosA;
|
||||
return sinAB < 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the triangle `t` contains the specified vertex
|
||||
/// </summary>
|
||||
/// <param name="t">The index of the triangle</param>
|
||||
/// <param name="v">The index of the vertex</param>
|
||||
/// <returns>Returns true if the triangle `t` contains the vertex `v`</returns>
|
||||
protected bool TriangleContainsVertex(int t, int v)
|
||||
{
|
||||
return triangulation[t, V1] == v || triangulation[t, V2] == v || triangulation[t, V3] == v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the adjacency information in triangle `t`. Any references to `tOld are
|
||||
/// replaced with `tNew`
|
||||
/// </summary>
|
||||
/// <param name="t">The index of the triangle to update</param>
|
||||
/// <param name="tOld">The index to be replaced</param>
|
||||
/// <param name="tNew">The new index to replace with</param>
|
||||
protected void UpdateAdjacency(int t, int tOld, int tNew)
|
||||
{
|
||||
// Boundary edge, no triangle exists
|
||||
int sharedEdge;
|
||||
if (t == OUT_OF_BOUNDS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (FindSharedEdge(t, tOld, out sharedEdge))
|
||||
{
|
||||
triangulation[t, sharedEdge] = tNew;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the edge index for triangle `tOrigin` that is adjacent to triangle `tAdjacent`
|
||||
/// </summary>
|
||||
/// <param name="tOrigin">The origin triangle to search</param>
|
||||
/// <param name="tAdjacent">The triangle index to search for</param>
|
||||
/// <param name="edgeIndex">Edge index returned as an out parameter</param>
|
||||
/// <returns>True if `tOrigin` is adjacent to `tAdjacent` and supplies the
|
||||
/// shared edge index via the out parameter. If `tOrigin` is an invalid index or
|
||||
/// `tAdjacent` is not adjacent to `tOrigin`, returns false.</returns>
|
||||
protected bool FindSharedEdge(int tOrigin, int tAdjacent, out int edgeIndex)
|
||||
{
|
||||
edgeIndex = 0;
|
||||
|
||||
if (tOrigin == OUT_OF_BOUNDS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (triangulation[tOrigin, E12] == tAdjacent)
|
||||
{
|
||||
edgeIndex = E12;
|
||||
return true;
|
||||
}
|
||||
else if (triangulation[tOrigin, E23] == tAdjacent)
|
||||
{
|
||||
edgeIndex = E23;
|
||||
return true;
|
||||
}
|
||||
else if (triangulation[tOrigin, E31] == tAdjacent)
|
||||
{
|
||||
edgeIndex = E31;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8e611cd8a0ddf64a802581a422b9733
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class UnfreezeFragment : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Options for triggering the fracture")]
|
||||
public TriggerOptions triggerOptions;
|
||||
|
||||
[Tooltip("If true, all sibling fragments will be unfrozen if the trigger conditions for this fragment are met.")]
|
||||
public bool unfreezeAll = true;
|
||||
|
||||
[Tooltip("This callback is invoked when the fracturing process has been completed.")]
|
||||
public UnityEvent onFractureCompleted;
|
||||
|
||||
// True if this fragment has already been unfrozen
|
||||
private bool isFrozen = true;
|
||||
|
||||
void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (!this.isFrozen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (collision.contactCount > 0)
|
||||
{
|
||||
// Collision force must exceed the minimum force (F = I / T = F)
|
||||
var contact = collision.contacts[0];
|
||||
var collisionForce = collision.impulse.magnitude / Time.fixedDeltaTime;
|
||||
|
||||
// Colliding object tag must be in the set of allowed collision tags if filtering by tag is enabled
|
||||
bool colliderTagAllowed = triggerOptions.IsTagAllowed(contact.otherCollider.gameObject.tag);
|
||||
|
||||
// Fragment is unfrozen if the colliding object has the correct tag (if tag filtering is enabled)
|
||||
// and the collision force exceeds the minimum collision force.
|
||||
if (collisionForce > triggerOptions.minimumCollisionForce &&
|
||||
(!triggerOptions.filterCollisionsByTag || colliderTagAllowed))
|
||||
{
|
||||
this.Unfreeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerEnter(Collider collider)
|
||||
{
|
||||
if (!this.isFrozen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool tagAllowed = triggerOptions.IsTagAllowed(collider.gameObject.tag);
|
||||
if (!triggerOptions.filterCollisionsByTag || triggerOptions.IsTagAllowed(collider.gameObject.tag))
|
||||
{
|
||||
this.Unfreeze();
|
||||
}
|
||||
}
|
||||
|
||||
private void Unfreeze()
|
||||
{
|
||||
if (this.unfreezeAll)
|
||||
{
|
||||
foreach(UnfreezeFragment fragment in this.transform.parent.GetComponentsInChildren<UnfreezeFragment>())
|
||||
{
|
||||
fragment.UnfreezeThis();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnfreezeThis();
|
||||
}
|
||||
|
||||
if (this.onFractureCompleted != null)
|
||||
{
|
||||
this.onFractureCompleted.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void UnfreezeThis()
|
||||
{
|
||||
this.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.None;
|
||||
this.isFrozen = false;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8db8defab3610854196e7e67bb44cc26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user