Files
nightclub-arcadia/NightclubArcadia/Packages/com.unity.probuilder/Runtime/Core/SelectPathFaces.cs
T

195 lines
7.2 KiB
C#

using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder
{
static class SelectPathFaces
{
static int[] s_cachedPredecessors;
static int s_cachedStart;
static ProBuilderMesh s_cachedMesh;
static int s_cachedFacesCount;
static List<WingedEdge> s_cachedWings;
static Dictionary<Face, int> s_cachedFacesIndex = new Dictionary<Face, int>();
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void ResetStaticsOnLoad()
{
s_cachedPredecessors = null;
s_cachedStart = -1;
s_cachedMesh = null;
s_cachedFacesCount = -1;
s_cachedWings = null;
s_cachedFacesIndex.Clear();
}
#endif
/// <summary>
/// Calculates the indexes of all faces in the shortest path between start and end
/// </summary>
/// <param name="start">The index of the starting face</param>
/// <param name="end">The index of the ending face</param>
/// <param name="mesh">The mesh of the object</param>
/// <returns>The indexes of all faces </returns>
public static List<int> GetPath(ProBuilderMesh mesh, int start, int end)
{
if (mesh == null)
throw new System.ArgumentException("Parameter cannot be null", "mesh");
if (start < 0 || start > mesh.faceCount - 1)
throw new System.ArgumentException("Parameter is out of bounds", "start");
if (end < 0 || end > mesh.faceCount - 1)
throw new System.ArgumentException("Parameter is out of bounds", "end");
List<int> path;
if (start == s_cachedStart && mesh == s_cachedMesh && mesh.faceCount == s_cachedFacesCount)
return GetMinimalPath(s_cachedPredecessors, start, end);
var predecessors = Dijkstra(mesh, start);
path = GetMinimalPath(predecessors, start, end);
s_cachedPredecessors = predecessors;
s_cachedStart = start;
s_cachedMesh = mesh;
return path;
}
/// <summary>
/// Builds a list of predecessors from a given face index to all other faces
/// Uses the Djikstra pathfinding algorithm
/// </summary>
/// <param name="mesh">The mesh of the object</param>
/// <param name="start">The index of the starting face</param>
/// <returns>A list of predecessors from a face index to all other faces</returns>
static int[] Dijkstra(ProBuilderMesh mesh, int start)
{
HashSet<int> visited = new HashSet<int>();
HashSet<int> toVisit = new HashSet<int>();
if (s_cachedMesh != mesh || s_cachedFacesCount != mesh.faceCount)
{
s_cachedWings = WingedEdge.GetWingedEdges(mesh, true);
s_cachedFacesIndex.Clear();
s_cachedFacesCount = mesh.faceCount;
for (int i = 0; i < mesh.facesInternal.Length; i++)
{
s_cachedFacesIndex.Add(mesh.facesInternal[i], i);
}
}
int wingCount = s_cachedWings.Count;
float[] weights = new float[wingCount];
int[] predecessors = new int[wingCount];
for (int i = 0; i < wingCount; i++)
{
weights[i] = float.MaxValue;
predecessors[i] = -1;
}
int current = start;
weights[current] = 0;
visited.Add(current);
// Construct the paths between the start face and every other faces
while (visited.Count < wingCount)
{
var currentWing = s_cachedWings[current];
var otherWing = currentWing;
// Update the weight array for each face next to the current one
do
{
var opposite = otherWing.opposite;
if (opposite == null)
{
otherWing = otherWing.next;
continue;
}
var idx = s_cachedFacesIndex[opposite.face];
var weight = GetWeight(current, idx, mesh);
// Change the predecessor and weight if the new path found if shorter
if (weights[current] + weight < weights[idx])
{
weights[idx] = weights[current] + weight;
predecessors[idx] = current;
}
// Add the face to the ones we can visit next, if not yet visited
if (!toVisit.Contains(idx) && !visited.Contains(idx))
{
toVisit.Add(idx);
}
otherWing = otherWing.next;
} while (otherWing != currentWing);
// This means there is an isolated face
if (toVisit.Count == 0)
{
return predecessors;
}
// Look for the next face to visit, choosing the one with less weight
float min = float.MaxValue;
foreach (var i in toVisit)
{
if (weights[i] < min)
{
min = weights[i];
current = i;
}
}
visited.Add(current);
toVisit.Remove(current);
}
return predecessors;
}
static float GetWeight(int face1, int face2, ProBuilderMesh mesh)
{
const float baseCost = 10f;
const float normalMult = 2f;
const float distMult = 1f;
// Calculates the difference between the normals of the faces
var n1 = Math.Normal(mesh, mesh.facesInternal[face1]);
var n2 = Math.Normal(mesh, mesh.facesInternal[face2]);
float normalCost = (1f - Vector3.Dot(n1.normalized, n2.normalized)) * normalMult;
// Calculates the distance between the center of the faces
Vector3 p1 = Vector3.zero;
Vector3 p2 = Vector3.zero;
foreach (var point in mesh.facesInternal[face1].indexesInternal)
{
p1 += mesh.positionsInternal[point] / mesh.facesInternal[face1].indexesInternal.Length;
}
foreach (var point in mesh.facesInternal[face2].indexesInternal)
{
p2 += mesh.positionsInternal[point] / mesh.facesInternal[face2].indexesInternal.Length;
}
float distCost = (p2 - p1).magnitude * distMult;
return baseCost + distCost + normalCost;
}
static List<int> GetMinimalPath(int[] predecessors, int start, int end)
{
if (predecessors[end] == -1)
{
return null;
}
Stack<int> list = new Stack<int>();
int a = end;
while (a != start)
{
list.Push(a);
a = predecessors[a];
}
return list.ToList();
}
}
}