新增动态水物理插件

This commit is contained in:
Bob.Song
2026-02-27 17:44:21 +08:00
parent a6e061d9ce
commit 60744d113d
2218 changed files with 698551 additions and 189 deletions

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c12907e7fed180445b9b2a55cd5c2a4b
folderAsset: yes
timeCreated: 1492277071
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
internal static class Constants
{
/// <summary>
/// The default plane distance tolerance
/// </summary>
internal const double DefaultPlaneDistanceTolerance = 1e-10;
/// <summary>
/// The starting delta dot product in simplex
/// </summary>
internal const double StartingDeltaDotProductInSimplex = 0.5;
/// <summary>
/// The connector table size
/// </summary>
internal const int ConnectorTableSize = 2017;
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 532c29ad0c844ee4e8aadc87e89b7aff
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,152 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// Factory class for computing convex hulls.
/// </summary>
public static class ConvexHull
{
/// <summary>
/// Creates a convex hull of the input data.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TFace"> The type of the t face. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <returns>
/// ConvexHull&lt;TVertex, TFace&gt;.
/// </returns>
public static ConvexHull<TVertex, TFace> Create<TVertex, TFace>(IList<TVertex> data,
double PlaneDistanceTolerance = Constants.DefaultPlaneDistanceTolerance)
where TVertex : IVertex
where TFace : ConvexFace<TVertex, TFace>, new()
{
return ConvexHull<TVertex, TFace>.Create(data, PlaneDistanceTolerance);
}
/// <summary>
/// Creates a convex hull of the input data.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <returns>
/// ConvexHull&lt;TVertex, DefaultConvexFace&lt;TVertex&gt;&gt;.
/// </returns>
public static ConvexHull<TVertex, DefaultConvexFace<TVertex>> Create<TVertex>(IList<TVertex> data,
double PlaneDistanceTolerance = Constants.DefaultPlaneDistanceTolerance)
where TVertex : IVertex
{
return ConvexHull<TVertex, DefaultConvexFace<TVertex>>.Create(data, PlaneDistanceTolerance);
}
/// <summary>
/// Creates a convex hull of the input data.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <returns>
/// ConvexHull&lt;DefaultVertex, DefaultConvexFace&lt;DefaultVertex&gt;&gt;.
/// </returns>
public static ConvexHull<DefaultVertex, DefaultConvexFace<DefaultVertex>> Create(IList<double[]> data,
double PlaneDistanceTolerance = Constants.DefaultPlaneDistanceTolerance)
{
List<DefaultVertex> points = data.Select(p => new DefaultVertex { Position = p, }).ToList();
return ConvexHull<DefaultVertex, DefaultConvexFace<DefaultVertex>>.Create(points, PlaneDistanceTolerance);
}
}
/// <summary>
/// Representation of a convex hull.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TFace"> The type of the t face. </typeparam>
public class ConvexHull<TVertex, TFace>
where TVertex : IVertex
where TFace : ConvexFace<TVertex, TFace>, new()
{
/// <summary>
/// Can only be created using a factory method.
/// </summary>
internal ConvexHull()
{
}
/// <summary>
/// Vertices of the convex hull.
/// </summary>
/// <value> The points. </value>
public IEnumerable<TVertex> Points { get; internal set; }
/// <summary>
/// Faces of the convex hull.
/// </summary>
/// <value> The faces. </value>
public IEnumerable<TFace> Faces { get; internal set; }
/// <summary>
/// Creates the convex hull.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <returns>
/// ConvexHull&lt;TVertex, TFace&gt;.
/// </returns>
/// <exception cref="System.ArgumentNullException"> The supplied data is null. </exception>
/// <exception cref="ArgumentNullException"> data </exception>
public static ConvexHull<TVertex, TFace> Create(IList<TVertex> data, double PlaneDistanceTolerance)
{
if (data == null)
{
throw new ArgumentNullException("The supplied data is null.");
}
return ConvexHullAlgorithm.GetConvexHull<TVertex, TFace>(data, PlaneDistanceTolerance);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 761f2eca68a8d2c4fbd8f0d07be2153e
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f5157676f6e47df4e8abeabb013e44b1
folderAsset: yes
timeCreated: 1490199232
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,341 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A more lightweight alternative to List of T.
/// On clear, only resets the count and does not clear the references
/// =&gt; this works because of the ObjectManager.
/// Includes a stack functionality.
/// </summary>
/// <typeparam name="T"> </typeparam>
internal class SimpleList<T>
{
/// <summary>
/// The count
/// </summary>
public int Count;
/// <summary>
/// The capacity
/// </summary>
private int capacity;
/// <summary>
/// The items
/// </summary>
private T[] items;
/// <summary>
/// Get the i-th element.
/// </summary>
/// <param name="i"> The i. </param>
/// <returns> T. </returns>
public T this[int i]
{
get { return items[i]; }
set { items[i] = value; }
}
/// <summary>
/// Size matters.
/// </summary>
private void EnsureCapacity()
{
if (capacity == 0)
{
capacity = 32;
items = new T[32];
}
else
{
T[] newItems = new T[capacity * 2];
Array.Copy(items, newItems, capacity);
capacity = 2 * capacity;
items = newItems;
}
}
/// <summary>
/// Adds a vertex to the buffer.
/// </summary>
/// <param name="item"> The item. </param>
public void Add(T item)
{
if (Count + 1 > capacity)
{
EnsureCapacity();
}
items[Count++] = item;
}
/// <summary>
/// Pushes the value to the back of the list.
/// </summary>
/// <param name="item"> The item. </param>
public void Push(T item)
{
if (Count + 1 > capacity)
{
EnsureCapacity();
}
items[Count++] = item;
}
/// <summary>
/// Pops the last value from the list.
/// </summary>
/// <returns> T. </returns>
public T Pop()
{
return items[--Count];
}
/// <summary>
/// Sets the Count to 0, otherwise does nothing.
/// </summary>
public void Clear()
{
Count = 0;
}
}
/// <summary>
/// A fancy name for a list of integers.
/// </summary>
/// <seealso cref="NWH.DWP2.MiConvexHull.SimpleList{System.Int32}" />
internal class IndexBuffer : SimpleList<int>
{
}
/// <summary>
/// A priority based linked list.
/// </summary>
internal sealed class FaceList
{
/// <summary>
/// The last
/// </summary>
private ConvexFaceInternal last;
/// <summary>
/// Get the first element.
/// </summary>
/// <value> The first. </value>
public ConvexFaceInternal First { get; private set; }
/// <summary>
/// Adds the element to the beginning.
/// </summary>
/// <param name="face"> The face. </param>
private void AddFirst(ConvexFaceInternal face)
{
face.InList = true;
First.Previous = face;
face.Next = First;
First = face;
}
/// <summary>
/// Adds a face to the list.
/// </summary>
/// <param name="face"> The face. </param>
public void Add(ConvexFaceInternal face)
{
if (face.InList)
{
if (First.VerticesBeyond.Count < face.VerticesBeyond.Count)
{
Remove(face);
AddFirst(face);
}
return;
}
face.InList = true;
if (First != null && First.VerticesBeyond.Count < face.VerticesBeyond.Count)
{
First.Previous = face;
face.Next = First;
First = face;
}
else
{
if (last != null)
{
last.Next = face;
}
face.Previous = last;
last = face;
if (First == null)
{
First = face;
}
}
}
/// <summary>
/// Removes the element from the list.
/// </summary>
/// <param name="face"> The face. </param>
public void Remove(ConvexFaceInternal face)
{
if (!face.InList)
{
return;
}
face.InList = false;
if (face.Previous != null)
{
face.Previous.Next = face.Next;
}
else if ( /*first == face*/ face.Previous == null)
{
First = face.Next;
}
if (face.Next != null)
{
face.Next.Previous = face.Previous;
}
else if ( /*last == face*/ face.Next == null)
{
last = face.Previous;
}
face.Next = null;
face.Previous = null;
}
}
/// <summary>
/// Connector list.
/// </summary>
internal sealed class ConnectorList
{
/// <summary>
/// The last
/// </summary>
private FaceConnector last;
/// <summary>
/// Get the first element.
/// </summary>
/// <value> The first. </value>
public FaceConnector First { get; private set; }
/// <summary>
/// Adds the element to the beginning.
/// </summary>
/// <param name="connector"> The connector. </param>
private void AddFirst(FaceConnector connector)
{
First.Previous = connector;
connector.Next = First;
First = connector;
}
/// <summary>
/// Adds a face to the list.
/// </summary>
/// <param name="element"> The element. </param>
public void Add(FaceConnector element)
{
if (last != null)
{
last.Next = element;
}
element.Previous = last;
last = element;
if (First == null)
{
First = element;
}
}
/// <summary>
/// Removes the element from the list.
/// </summary>
/// <param name="connector"> The connector. </param>
public void Remove(FaceConnector connector)
{
if (connector.Previous != null)
{
connector.Previous.Next = connector.Next;
}
else if ( /*first == face*/ connector.Previous == null)
{
First = connector.Next;
}
if (connector.Next != null)
{
connector.Next.Previous = connector.Previous;
}
else if ( /*last == face*/ connector.Next == null)
{
last = connector.Previous;
}
connector.Next = null;
connector.Previous = null;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 290297a1af25922458e84d0d4d3748ac
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A convex face representation containing adjacency information.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TFace"> The type of the t face. </typeparam>
public abstract class ConvexFace<TVertex, TFace>
where TVertex : IVertex
where TFace : ConvexFace<TVertex, TFace>
{
/// <summary>
/// Adjacency. Array of length "dimension".
/// If F = Adjacency[i] then the vertices shared with F are Vertices[j] where j != i.
/// In the context of triangulation, can be null (indicates the cell is at boundary).
/// </summary>
/// <value> The adjacency. </value>
public TFace[] Adjacency { get; set; }
/// <summary>
/// The vertices stored in clockwise order for dimensions 2 - 4, in higher dimensions the order is arbitrary.
/// Unless I accidentally switch some index somewhere in which case the order is CCW. Either way, it is consistent.
/// 3D Normal = (V[1] - V[0]) x (V[2] - V[1]).
/// </summary>
/// <value> The vertices. </value>
public TVertex[] Vertices { get; set; }
/// <summary>
/// The normal vector of the face. Null if used in triangulation.
/// </summary>
/// <value> The normal. </value>
public double[] Normal { get; set; }
}
/// <summary>
/// A default convex face representation that inherits from ConvexFace with self-referencing type parameter.
/// </summary>
/// <typeparam name="TVertex"> The type of the vertex. </typeparam>
public class DefaultConvexFace<TVertex> : ConvexFace<TVertex, DefaultConvexFace<TVertex>>
where TVertex : IVertex
{
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f4d8d869c3901dd40b08d3ae58f3ace3
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,845 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/*
* This part of the implementation handles initialization of the convex hull algorithm:
* - Constructor & Process initiation
* - Determine the dimension by looking at length of Position vector of 10 random data points from the input.
* - Identify bounding box points in each direction.
* - Pick (Dimension + 1) points from the extremes and construct the initial simplex.
*/
/// <summary>
/// Class ConvexHullAlgorithm.
/// </summary>
internal partial class ConvexHullAlgorithm
{
/// <summary>
/// Serializes the vertices into the 1D array, Positions. The 1D array has much quicker access in C#.
/// </summary>
private void SerializeVerticesToPositions()
{
int index = 0;
if (IsLifted) // "Lifted" means that the last dimension is the sum of the squares of the others.
{
foreach (IVertex v in Vertices)
{
double parabolaTerm = 0.0; // the lifted term is a sum of squares.
int origNumDim = NumOfDimensions - 1;
for (int i = 0; i < origNumDim; i++)
{
double coordinate = v.Position[i];
Positions[index++] = coordinate;
parabolaTerm += coordinate * coordinate;
}
Positions[index++] = parabolaTerm;
}
}
else
{
foreach (IVertex v in Vertices)
{
for (int i = 0; i < NumOfDimensions; i++)
{
Positions[index++] = v.Position[i];
}
}
}
}
/// <summary>
/// Finds the bounding box points.
/// </summary>
private void FindBoundingBoxPoints()
{
indexOfDimensionWithLeastExtremes = -1;
int minNumExtremes = int.MaxValue;
for (int i = 0; i < NumOfDimensions; i++)
{
List<int> minIndices = new();
List<int> maxIndices = new();
double min = double.PositiveInfinity, max = double.NegativeInfinity;
for (int j = 0; j < NumberOfVertices; j++)
{
double v = GetCoordinate(j, i);
double difference = min - v;
if (difference >= PlaneDistanceTolerance)
{
// you found a better solution than before, clear out the list and store new value
min = v;
minIndices.Clear();
minIndices.Add(j);
}
else if (difference > 0)
{
// you found a solution slightly better than before, clear out those that are no longer on the
// list and store new value
min = v;
minIndices.RemoveAll(index => min - GetCoordinate(index, i) > PlaneDistanceTolerance);
minIndices.Add(j);
}
else if (difference > -PlaneDistanceTolerance)
{
//same or almost as good as current limit, so store it
minIndices.Add(j);
}
difference = v - max;
if (difference >= PlaneDistanceTolerance)
{
// you found a better solution than before, clear out the list and store new value
max = v;
maxIndices.Clear();
maxIndices.Add(j);
}
else if (difference > 0)
{
// you found a solution slightly better than before, clear out those that are no longer on the
// list and store new value
max = v;
maxIndices.RemoveAll(index => min - GetCoordinate(index, i) > PlaneDistanceTolerance);
maxIndices.Add(j);
}
else if (difference > -PlaneDistanceTolerance)
{
//same or almost as good as current limit, so store it
maxIndices.Add(j);
}
}
minima[i] = min;
maxima[i] = max;
minIndices.AddRange(maxIndices);
if (minIndices.Count < minNumExtremes)
{
minNumExtremes = minIndices.Count;
indexOfDimensionWithLeastExtremes = i;
}
boundingBoxPoints[i] = minIndices;
}
}
/// <summary>
/// Shifts and scales the Positions to avoid future errors. This does not alter the original data.
/// </summary>
private void ShiftAndScalePositions()
{
int positionsLength = Positions.Length;
if (IsLifted)
{
int origNumDim = NumOfDimensions - 1;
double parabolaScale = 2 / (minima.Sum(x => Math.Abs(x)) + maxima.Sum(x => Math.Abs(x))
- Math.Abs(maxima[origNumDim]) - Math.Abs(minima[origNumDim]));
// the parabolascale is 1 / average of the sum of the other dimensions.
// multiplying this by the parabola will scale it back to be on near similar size to the
// other dimensions. Without this, the term is much larger than the others, which causes
// problems for roundoff error and finding the normal of faces.
minima[origNumDim] *= parabolaScale; // change the extreme values as well
maxima[origNumDim] *= parabolaScale;
// it is done here because
for (int i = origNumDim; i < positionsLength; i += NumOfDimensions)
{
Positions[i] *= parabolaScale;
}
}
double[] shiftAmount = new double[NumOfDimensions];
for (int i = 0; i < NumOfDimensions; i++)
// now the entire model is shifted to all positive numbers...plus some more.
// why?
// 1) to avoid dealing with a point at the origin {0,0,...,0} which causes problems
// for future normal finding
// 2) note that wierd shift that is used (max - min - min). This is to avoid scaling
// issues. this shift means that the minima in a dimension will always be a positive
// number (no points at zero), and the minima [in a given dimension] will always be
// half of the maxima. 'Half' is much preferred to 'thousands of times'
// Think of the first term as the range (max - min), then the second term avoids cases
// where there are both positive and negative numbers.
{
shiftAmount[i] = maxima[i] - minima[i] - minima[i];
}
for (int i = 0; i < positionsLength; i++)
{
Positions[i] += shiftAmount[i % NumOfDimensions];
}
}
/// <summary>
/// Find the (dimension+1) initial points and create the simplexes.
/// Creates the initial simplex of n+1 vertices by using points from the bounding box.
/// Special care is taken to ensure that the vertices chosen do not result in a degenerate shape
/// where vertices are collinear (co-planar, etc). This would technically be resolved when additional
/// vertices are checked in the main loop, but: 1) a degenerate simplex would not eliminate any other
/// vertices (thus no savings there), 2) the creation of the face normal is prone to error.
/// </summary>
private void CreateInitialSimplex()
{
List<int> initialPoints = FindInitialPoints();
#region Create the first faces from (dimension + 1) vertices.
int[] faces = new int[NumOfDimensions + 1];
for (int i = 0; i < NumOfDimensions + 1; i++)
{
int[] vertices = new int[NumOfDimensions];
for (int j = 0, k = 0; j <= NumOfDimensions; j++)
{
if (i != j)
{
vertices[k++] = initialPoints[j];
}
}
ConvexFaceInternal newFace = FacePool[ObjectManager.GetFace()];
newFace.Vertices = vertices;
Array.Sort(vertices);
MathHelper.CalculateFacePlane(newFace, Center);
faces[i] = newFace.Index;
}
// update the adjacency (check all pairs of faces)
for (int i = 0; i < NumOfDimensions; i++)
for (int j = i + 1; j < NumOfDimensions + 1; j++)
{
UpdateAdjacency(FacePool[faces[i]], FacePool[faces[j]]);
}
#endregion
#region Init the vertex beyond buffers.
foreach (int faceIndex in faces)
{
ConvexFaceInternal face = FacePool[faceIndex];
FindBeyondVertices(face);
if (face.VerticesBeyond.Count == 0)
{
ConvexFaces.Add(face.Index); // The face is on the hull
}
else
{
UnprocessedFaces.Add(face);
}
}
#endregion
// Set all vertices to false (unvisited).
foreach (int vertex in initialPoints)
{
VertexVisited[vertex] = false;
}
}
/// <summary>
/// Finds (dimension + 1) initial points.
/// </summary>
/// <param name="extremes"> </param>
/// <returns> </returns>
private List<int> FindInitialPoints()
{
double bigNumber = maxima.Sum() * NumOfDimensions * NumberOfVertices;
// the first two points are taken from the dimension that had the fewest extremes
// well, in most cases there will only be 2 in all dimensions: one min and one max
// but a lot of engineering part shapes are nice and square and can have hundreds of
// parallel vertices at the extremes
int vertex1 =
boundingBoxPoints[indexOfDimensionWithLeastExtremes].First(); // these are min and max vertices along
int vertex2 =
boundingBoxPoints[indexOfDimensionWithLeastExtremes].Last(); // the dimension that had the fewest points
boundingBoxPoints[indexOfDimensionWithLeastExtremes].RemoveAt(0);
boundingBoxPoints[indexOfDimensionWithLeastExtremes]
.RemoveAt(boundingBoxPoints[indexOfDimensionWithLeastExtremes].Count - 1);
List<int> initialPoints = new() { vertex1, vertex2, };
VertexVisited[vertex1] = VertexVisited[vertex2] = true;
CurrentVertex = vertex1;
UpdateCenter();
CurrentVertex = vertex2;
UpdateCenter();
double[][] edgeVectors = new double[NumOfDimensions][];
edgeVectors[0] = MathHelper.VectorBetweenVertices(vertex2, vertex1);
// now the remaining vertices are just combined in one big list
List<int> extremes = boundingBoxPoints.SelectMany(x => x).ToList();
// otherwise find the remaining points by maximizing the initial simplex volume
int index = 1;
while (index < NumOfDimensions && extremes.Any())
{
int bestVertex = -1;
double[] bestEdgeVector = { };
double maxVolume = 0.0;
for (int i = extremes.Count - 1; i >= 0; i--)
{
// count backwards in order to remove potential duplicates
int vIndex = extremes[i];
if (initialPoints.Contains(vIndex))
{
extremes.RemoveAt(i);
}
else
{
edgeVectors[index] = MathHelper.VectorBetweenVertices(vIndex, vertex1);
double volume = MathHelper.GetSimplexVolume(edgeVectors, index, bigNumber);
if (maxVolume < volume)
{
maxVolume = volume;
bestVertex = vIndex;
bestEdgeVector = edgeVectors[index];
}
}
}
extremes.Remove(bestVertex);
if (bestVertex == -1)
{
break;
}
initialPoints.Add(bestVertex);
edgeVectors[index++] = bestEdgeVector;
CurrentVertex = bestVertex;
UpdateCenter();
}
// hmm, there are not enough points on the bounding box to make a simplex. It is rare but entirely possibly.
// As an extreme, the bounding box can be made in n dimensions from only 2 unique points. When we can't find
// enough unique points, we start again with ALL the vertices. The following is a near replica of the code
// above, but instead of extremes, we consider "allVertices".
if (initialPoints.Count <= NumOfDimensions)
{
List<int> allVertices = Enumerable.Range(0, NumberOfVertices).ToList();
while (index < NumOfDimensions && allVertices.Any())
{
int bestVertex = -1;
double[] bestEdgeVector = { };
double maxVolume = 0.0;
for (int i = allVertices.Count - 1; i >= 0; i--)
{
// count backwards in order to remove potential duplicates
int vIndex = allVertices[i];
if (initialPoints.Contains(vIndex))
{
allVertices.RemoveAt(i);
}
else
{
edgeVectors[index] = MathHelper.VectorBetweenVertices(vIndex, vertex1);
double volume = MathHelper.GetSimplexVolume(edgeVectors, index, bigNumber);
if (maxVolume < volume)
{
maxVolume = volume;
bestVertex = vIndex;
bestEdgeVector = edgeVectors[index];
}
}
}
allVertices.Remove(bestVertex);
if (bestVertex == -1)
{
break;
}
initialPoints.Add(bestVertex);
edgeVectors[index++] = bestEdgeVector;
CurrentVertex = bestVertex;
UpdateCenter();
}
}
if (initialPoints.Count <= NumOfDimensions)
{
throw new ArgumentException("The input data is degenerate. It appears to exist in " + NumOfDimensions +
" dimensions, but it is a " + (NumOfDimensions - 1) +
" dimensional set (i.e. the point of collinear,"
+ " coplanar, or co-hyperplanar.)");
}
return initialPoints;
}
/// <summary>
/// Check if 2 faces are adjacent and if so, update their AdjacentFaces array.
/// </summary>
/// <param name="l"> The l. </param>
/// <param name="r"> The r. </param>
private void UpdateAdjacency(ConvexFaceInternal l, ConvexFaceInternal r)
{
int[] lv = l.Vertices;
int[] rv = r.Vertices;
int i;
// reset marks on the 1st face
for (i = 0; i < lv.Length; i++)
{
VertexVisited[lv[i]] = false;
}
// mark all vertices on the 2nd face
for (i = 0; i < rv.Length; i++)
{
VertexVisited[rv[i]] = true;
}
// find the 1st false index
for (i = 0; i < lv.Length; i++)
{
if (!VertexVisited[lv[i]])
{
break;
}
}
// no vertex was marked
if (i == NumOfDimensions)
{
return;
}
// check if only 1 vertex wasn't marked
for (int j = i + 1; j < lv.Length; j++)
{
if (!VertexVisited[lv[j]])
{
return;
}
}
// if we are here, the two faces share an edge
l.AdjacentFaces[i] = r.Index;
// update the adj. face on the other face - find the vertex that remains marked
for (i = 0; i < lv.Length; i++)
{
VertexVisited[lv[i]] = false;
}
for (i = 0; i < rv.Length; i++)
{
if (VertexVisited[rv[i]])
{
break;
}
}
r.AdjacentFaces[i] = l.Index;
}
/// <summary>
/// Used in the "initialization" code.
/// </summary>
/// <param name="face"> The face. </param>
private void FindBeyondVertices(ConvexFaceInternal face)
{
IndexBuffer beyondVertices = face.VerticesBeyond;
MaxDistance = double.NegativeInfinity;
FurthestVertex = 0;
for (int i = 0; i < NumberOfVertices; i++)
{
if (VertexVisited[i])
{
continue;
}
IsBeyond(face, beyondVertices, i);
}
face.FurthestVertex = FurthestVertex;
}
#region Starting functions and constructor
/// <summary>
/// The main function for the Convex Hull algorithm. It is static, but it creates
/// an instantiation of this class in order to allow for parallel execution.
/// Following this simple function, the constructor and the main function "FindConvexHull" is listed.
/// </summary>
/// <typeparam name="TVertex"> The type of the vertices in the data. </typeparam>
/// <typeparam name="TFace"> The desired type of the faces. </typeparam>
/// <param name="data"> The data is the vertices as a collection of IVertices. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <returns>
/// MIConvexHull.ConvexHull&lt;TVertex, TFace&gt;.
/// </returns>
internal static ConvexHull<TVertex, TFace> GetConvexHull<TVertex, TFace>(IList<TVertex> data,
double PlaneDistanceTolerance)
where TFace : ConvexFace<TVertex, TFace>, new()
where TVertex : IVertex
{
ConvexHullAlgorithm ch = new(data.Cast<IVertex>().ToArray(), false, PlaneDistanceTolerance);
ch.GetConvexHull();
return new ConvexHull<TVertex, TFace>
{
Points = ch.GetHullVertices(data),
Faces = ch.GetConvexFaces<TVertex, TFace>(),
};
}
/// <summary>
/// Initializes a new instance of the <see cref="ConvexHullAlgorithm" /> class.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="lift"> if set to <c> true </c> [lift]. </param>
/// <param name="PlaneDistanceTolerance"> The plane distance tolerance. </param>
/// <exception cref="System.InvalidOperationException"> Dimension of the input must be 2 or greater. </exception>
/// <exception cref="System.ArgumentException">
/// There are too few vertices (m) for the n-dimensional space. (m must be greater +
/// than the n, but m is + NumberOfVertices + and n is + NumOfDimensions
/// </exception>
/// <exception cref="InvalidOperationException">
/// PointTranslationGenerator cannot be null if PointTranslationType is enabled.
/// or
/// Dimension of the input must be 2 or greater.
/// </exception>
/// <exception cref="ArgumentException">
/// There are too few vertices (m) for the n-dimensional space. (m must be greater " +
/// "than the n, but m is " + NumberOfVertices + " and n is " + Dimension
/// </exception>
private ConvexHullAlgorithm(IVertex[] vertices, bool lift, double PlaneDistanceTolerance)
{
IsLifted = lift;
Vertices = vertices;
NumberOfVertices = vertices.Length;
NumOfDimensions = DetermineDimension();
if (IsLifted)
{
NumOfDimensions++;
}
if (NumOfDimensions < 2)
{
throw new InvalidOperationException("Dimension of the input must be 2 or greater.");
}
if (NumberOfVertices <= NumOfDimensions)
{
throw new ArgumentException(
"There are too few vertices (m) for the n-dimensional space. (m must be greater " +
"than the n, but m is " + NumberOfVertices + " and n is " + NumOfDimensions);
}
this.PlaneDistanceTolerance = PlaneDistanceTolerance;
UnprocessedFaces = new FaceList();
ConvexFaces = new IndexBuffer();
FacePool = new ConvexFaceInternal[(NumOfDimensions + 1) * 10]; // must be initialized before object manager
AffectedFaceFlags = new bool[(NumOfDimensions + 1) * 10];
ObjectManager = new ObjectManager(this);
Center = new double[NumOfDimensions];
TraverseStack = new IndexBuffer();
UpdateBuffer = new int[NumOfDimensions];
UpdateIndices = new int[NumOfDimensions];
EmptyBuffer = new IndexBuffer();
AffectedFaceBuffer = new IndexBuffer();
ConeFaceBuffer = new SimpleList<DeferredFace>();
SingularVertices = new HashSet<int>();
BeyondBuffer = new IndexBuffer();
ConnectorTable = new ConnectorList[Constants.ConnectorTableSize];
for (int i = 0; i < Constants.ConnectorTableSize; i++)
{
ConnectorTable[i] = new ConnectorList();
}
VertexVisited = new bool[NumberOfVertices];
Positions = new double[NumberOfVertices * NumOfDimensions];
boundingBoxPoints = new List<int>[NumOfDimensions];
minima = new double[NumOfDimensions];
maxima = new double[NumOfDimensions];
MathHelper = new MathHelper(NumOfDimensions, Positions);
}
/// <summary>
/// Check the dimensionality of the input data.
/// </summary>
/// <returns> System.Int32. </returns>
/// <exception cref="ArgumentException"> Invalid input data (non-uniform dimension). </exception>
private int DetermineDimension()
{
Random r = new();
List<int> dimensions = new();
for (int i = 0; i < 10; i++)
{
dimensions.Add(Vertices[r.Next(NumberOfVertices)].Position.Length);
}
int dimension = dimensions.Min();
if (dimension != dimensions.Max())
{
throw new ArgumentException("Invalid input data (non-uniform dimension).");
}
return dimension;
}
/// <summary>
/// Gets/calculates the convex hull. This is
/// </summary>
private void GetConvexHull()
{
// accessing a 1D array is quicker than a jagged array, so the first step is to make this array
SerializeVerticesToPositions();
// next the bounding box extremes are found. This is used to shift, scale and find the starting simplex.
FindBoundingBoxPoints();
// the positions are shifted to avoid divide by zero problems
// and if Delaunay or Voronoi, then the parabola terms are scaled back to match the size of the other coords
ShiftAndScalePositions();
// Find the (dimension+1) initial points and create the simplexes.
CreateInitialSimplex();
// Now, the main loop. These initial faces of a simplex are replaced and expanded
// outwards to make the convex hull and faces.
while (UnprocessedFaces.First != null)
{
ConvexFaceInternal currentFace = UnprocessedFaces.First;
CurrentVertex = currentFace.FurthestVertex;
UpdateCenter();
// The affected faces get tagged
TagAffectedFaces(currentFace);
// Create the cone from the currentVertex and the affected faces horizon.
if (!SingularVertices.Contains(CurrentVertex) && CreateCone())
{
CommitCone();
}
else
{
HandleSingular();
}
// Need to reset the tags
int count = AffectedFaceBuffer.Count;
for (int i = 0; i < count; i++)
{
AffectedFaceFlags[AffectedFaceBuffer[i]] = false;
}
}
}
#endregion
#region Fields
/// <summary>
/// Corresponds to the dimension of the data.
/// When the "lifted" hull is computed, Dimension is automatically incremented by one.
/// </summary>
internal readonly int NumOfDimensions;
/// <summary>
/// Are we on a paraboloid?
/// </summary>
private readonly bool IsLifted;
/// <summary>
/// Explained in ConvexHullComputationConfig.
/// </summary>
private readonly double PlaneDistanceTolerance;
/*
* Representation of the input vertices.
*
* - In the algorithm, a vertex is represented by its index in the Vertices array.
* This makes the algorithm a lot faster (up to 30%) than using object reference everywhere.
* - Positions are stored as a single array of values. Coordinates for vertex with index i
* are stored at indices <i * Dimension, (i + 1) * Dimension)
* - VertexMarks are used by the algorithm to help identify a set of vertices that is "above" (or "beyond")
* a specific face.
*/
/// <summary>
/// The vertices
/// </summary>
private readonly IVertex[] Vertices;
/// <summary>
/// The positions
/// </summary>
private readonly double[] Positions;
/// <summary>
/// The vertex marks
/// </summary>
private readonly bool[] VertexVisited;
private readonly int NumberOfVertices;
/*
* The triangulation faces are represented in a single pool for objects that are being reused.
* This allows for represent the faces as integers and significantly speeds up many computations.
* - AffectedFaceFlags are used to mark affected faces/
*/
/// <summary>
/// The face pool
/// </summary>
internal ConvexFaceInternal[] FacePool;
/// <summary>
/// The affected face flags
/// </summary>
internal bool[] AffectedFaceFlags;
/// <summary>
/// Used to track the size of the current hull in the Update/RollbackCenter functions.
/// </summary>
private int ConvexHullSize;
/// <summary>
/// A list of faces that that are not a part of the final convex hull and still need to be processed.
/// </summary>
private readonly FaceList UnprocessedFaces;
/// <summary>
/// A list of faces that form the convex hull.
/// </summary>
private readonly IndexBuffer ConvexFaces;
/// <summary>
/// The vertex that is currently being processed.
/// </summary>
private int CurrentVertex;
/// <summary>
/// A helper variable to determine the furthest vertex for a particular convex face.
/// </summary>
private double MaxDistance;
/// <summary>
/// A helper variable to help determine the index of the vertex that is furthest from the face that is currently being
/// processed.
/// </summary>
private int FurthestVertex;
/// <summary>
/// The centroid of the currently computed hull.
/// </summary>
private readonly double[] Center;
/*
* Helper arrays to store faces for adjacency update.
* This is just to prevent unnecessary allocations.
*/
/// <summary>
/// The update buffer
/// </summary>
private readonly int[] UpdateBuffer;
/// <summary>
/// The update indices
/// </summary>
private readonly int[] UpdateIndices;
/// <summary>
/// Used to determine which faces need to be updated at each step of the algorithm.
/// </summary>
private readonly IndexBuffer TraverseStack;
/// <summary>
/// Used for VerticesBeyond for faces that are on the convex hull.
/// </summary>
private readonly IndexBuffer EmptyBuffer;
/// <summary>
/// Used to determine which vertices are "above" (or "beyond") a face
/// </summary>
private IndexBuffer BeyondBuffer;
/// <summary>
/// Stores faces that are visible from the current vertex.
/// </summary>
private readonly IndexBuffer AffectedFaceBuffer;
/// <summary>
/// Stores faces that form a "cone" created by adding new vertex.
/// </summary>
private readonly SimpleList<DeferredFace> ConeFaceBuffer;
/// <summary>
/// Stores a list of "singular" (or "generate", "planar", etc.) vertices that cannot be part of the hull.
/// </summary>
private readonly HashSet<int> SingularVertices;
/// <summary>
/// The connector table helps to determine the adjacency of convex faces.
/// Hashing is used instead of pairwise comparison. This significantly speeds up the computations,
/// especially for higher dimensions.
/// </summary>
private readonly ConnectorList[] ConnectorTable;
/// <summary>
/// Manages the memory allocations and storage of unused objects.
/// Saves the garbage collector a lot of work.
/// </summary>
private readonly ObjectManager ObjectManager;
/// <summary>
/// Helper class for handling math related stuff.
/// </summary>
private readonly MathHelper MathHelper;
private readonly List<int>[] boundingBoxPoints;
private int indexOfDimensionWithLeastExtremes;
private readonly double[] minima;
private readonly double[] maxima;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b06a7aa17755adf42828257767d0271a
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,683 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System.Collections.Generic;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/*
* Main part of the algorithm
* Basic idea:
* - Create the initial hull (done in Initialize.cs)
*
* For each face there are "vertices beyond" which are "visible" from it.
* If there are no such vertices, the face is on the hull.
*
* - While there is at least one face with at least one "vertex beyond":
* * Pick the furthest beyond vertex
* * For this vertex:
* > find all faces that are visible from it (TagAffectedFaces)
* > remove them and replace them with a "cone" created by the vertex and the boundary
* of the affected faces, and for each new face, compute "beyond vertices"
* (CreateCone + CommitCone)
*
* + Implement it in way that is fast, but hard to understand and maintain.
*/
/// <summary>
/// Class ConvexHullAlgorithm.
/// </summary>
internal partial class ConvexHullAlgorithm
{
/// <summary>
/// Tags all faces seen from the current vertex with 1.
/// </summary>
/// <param name="currentFace"> The current face. </param>
private void TagAffectedFaces(ConvexFaceInternal currentFace)
{
AffectedFaceBuffer.Clear();
AffectedFaceBuffer.Add(currentFace.Index);
TraverseAffectedFaces(currentFace.Index);
}
/// <summary>
/// Recursively traverse all the relevant faces.
/// </summary>
/// <param name="currentFace"> The current face. </param>
private void TraverseAffectedFaces(int currentFace)
{
TraverseStack.Clear();
TraverseStack.Push(currentFace);
AffectedFaceFlags[currentFace] = true;
while (TraverseStack.Count > 0)
{
ConvexFaceInternal top = FacePool[TraverseStack.Pop()];
for (int i = 0; i < NumOfDimensions; i++)
{
int adjFace = top.AdjacentFaces[i];
if (!AffectedFaceFlags[adjFace] &&
MathHelper.GetVertexDistance(CurrentVertex, FacePool[adjFace]) >= PlaneDistanceTolerance)
{
AffectedFaceBuffer.Add(adjFace);
AffectedFaceFlags[adjFace] = true;
TraverseStack.Push(adjFace);
}
}
}
}
/// <summary>
/// Creates a new deferred face.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="faceIndex"> Index of the face. </param>
/// <param name="pivot"> The pivot. </param>
/// <param name="pivotIndex"> Index of the pivot. </param>
/// <param name="oldFace"> The old face. </param>
/// <returns> DeferredFace. </returns>
private DeferredFace MakeDeferredFace(ConvexFaceInternal face, int faceIndex, ConvexFaceInternal pivot,
int pivotIndex, ConvexFaceInternal oldFace)
{
DeferredFace ret = ObjectManager.GetDeferredFace();
ret.Face = face;
ret.FaceIndex = faceIndex;
ret.Pivot = pivot;
ret.PivotIndex = pivotIndex;
ret.OldFace = oldFace;
return ret;
}
/// <summary>
/// Connect faces using a connector.
/// </summary>
/// <param name="connector"> The connector. </param>
private void ConnectFace(FaceConnector connector)
{
uint index = connector.HashCode % Constants.ConnectorTableSize;
ConnectorList list = ConnectorTable[index];
for (FaceConnector current = list.First; current != null; current = current.Next)
{
if (FaceConnector.AreConnectable(connector, current, NumOfDimensions))
{
list.Remove(current);
FaceConnector.Connect(current, connector);
current.Face = null;
connector.Face = null;
ObjectManager.DepositConnector(current);
ObjectManager.DepositConnector(connector);
return;
}
}
list.Add(connector);
}
/// <summary>
/// Removes the faces "covered" by the current vertex and adds the newly created ones.
/// </summary>
/// <returns> <c> true </c> if XXXX, <c> false </c> otherwise. </returns>
private bool CreateCone()
{
int currentVertexIndex = CurrentVertex;
ConeFaceBuffer.Clear();
for (int fIndex = 0; fIndex < AffectedFaceBuffer.Count; fIndex++)
{
int oldFaceIndex = AffectedFaceBuffer[fIndex];
ConvexFaceInternal oldFace = FacePool[oldFaceIndex];
// Find the faces that need to be updated
int updateCount = 0;
for (int i = 0; i < NumOfDimensions; i++)
{
int af = oldFace.AdjacentFaces[i];
if (!AffectedFaceFlags[af]) // Tag == false when oldFaces does not contain af
{
UpdateBuffer[updateCount] = af;
UpdateIndices[updateCount] = i;
++updateCount;
}
}
for (int i = 0; i < updateCount; i++)
{
ConvexFaceInternal adjacentFace = FacePool[UpdateBuffer[i]];
int oldFaceAdjacentIndex = 0;
int[] adjFaceAdjacency = adjacentFace.AdjacentFaces;
for (int j = 0; j < adjFaceAdjacency.Length; j++)
{
if (oldFaceIndex == adjFaceAdjacency[j])
{
oldFaceAdjacentIndex = j;
break;
}
}
int forbidden = UpdateIndices[i]; // Index of the face that corresponds to this adjacent face
int oldVertexIndex;
int[] vertices;
int newFaceIndex = ObjectManager.GetFace();
ConvexFaceInternal newFace = FacePool[newFaceIndex];
vertices = newFace.Vertices;
for (int j = 0; j < NumOfDimensions; j++)
{
vertices[j] = oldFace.Vertices[j];
}
oldVertexIndex = vertices[forbidden];
int orderedPivotIndex;
// correct the ordering
if (currentVertexIndex < oldVertexIndex)
{
orderedPivotIndex = 0;
for (int j = forbidden - 1; j >= 0; j--)
{
if (vertices[j] > currentVertexIndex)
{
vertices[j + 1] = vertices[j];
}
else
{
orderedPivotIndex = j + 1;
break;
}
}
}
else
{
orderedPivotIndex = NumOfDimensions - 1;
for (int j = forbidden + 1; j < NumOfDimensions; j++)
{
if (vertices[j] < currentVertexIndex)
{
vertices[j - 1] = vertices[j];
}
else
{
orderedPivotIndex = j - 1;
break;
}
}
}
vertices[orderedPivotIndex] = CurrentVertex;
if (!MathHelper.CalculateFacePlane(newFace, Center))
{
return false;
}
ConeFaceBuffer.Add(MakeDeferredFace(newFace, orderedPivotIndex, adjacentFace, oldFaceAdjacentIndex,
oldFace));
}
}
return true;
}
/// <summary>
/// Commits a cone and adds a vertex to the convex hull.
/// </summary>
private void CommitCone()
{
// Fill the adjacency.
for (int i = 0; i < ConeFaceBuffer.Count; i++)
{
DeferredFace face = ConeFaceBuffer[i];
ConvexFaceInternal newFace = face.Face;
ConvexFaceInternal adjacentFace = face.Pivot;
ConvexFaceInternal oldFace = face.OldFace;
int orderedPivotIndex = face.FaceIndex;
newFace.AdjacentFaces[orderedPivotIndex] = adjacentFace.Index;
adjacentFace.AdjacentFaces[face.PivotIndex] = newFace.Index;
// let there be a connection.
for (int j = 0; j < NumOfDimensions; j++)
{
if (j == orderedPivotIndex)
{
continue;
}
FaceConnector connector = ObjectManager.GetConnector();
connector.Update(newFace, j, NumOfDimensions);
ConnectFace(connector);
}
// the id adjacent face on the hull? If so, we can use simple method to find beyond vertices.
if (adjacentFace.VerticesBeyond.Count == 0)
{
FindBeyondVertices(newFace, oldFace.VerticesBeyond);
}
// it is slightly more effective if the face with the lower number of beyond vertices comes first.
else if (adjacentFace.VerticesBeyond.Count < oldFace.VerticesBeyond.Count)
{
FindBeyondVertices(newFace, adjacentFace.VerticesBeyond, oldFace.VerticesBeyond);
}
else
{
FindBeyondVertices(newFace, oldFace.VerticesBeyond, adjacentFace.VerticesBeyond);
}
// This face will definitely lie on the hull
if (newFace.VerticesBeyond.Count == 0)
{
ConvexFaces.Add(newFace.Index);
UnprocessedFaces.Remove(newFace);
ObjectManager.DepositVertexBuffer(newFace.VerticesBeyond);
newFace.VerticesBeyond = EmptyBuffer;
}
else // Add the face to the list
{
UnprocessedFaces.Add(newFace);
}
// recycle the object.
ObjectManager.DepositDeferredFace(face);
}
// Recycle the affected faces.
for (int fIndex = 0; fIndex < AffectedFaceBuffer.Count; fIndex++)
{
int face = AffectedFaceBuffer[fIndex];
UnprocessedFaces.Remove(FacePool[face]);
ObjectManager.DepositFace(face);
}
}
/// <summary>
/// Check whether the vertex v is beyond the given face. If so, add it to beyondVertices.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="beyondVertices"> The beyond vertices. </param>
/// <param name="v"> The v. </param>
private void IsBeyond(ConvexFaceInternal face, IndexBuffer beyondVertices, int v)
{
double distance = MathHelper.GetVertexDistance(v, face);
if (distance >= PlaneDistanceTolerance)
{
if (distance > MaxDistance)
{
// If it's within the tolerance distance, use the lex. larger point
if (distance - MaxDistance < PlaneDistanceTolerance)
{
// todo: why is this LexCompare necessary. Would seem to favor x over y over z (etc.)?
if (LexCompare(v, FurthestVertex) > 0)
{
MaxDistance = distance;
FurthestVertex = v;
}
}
else
{
MaxDistance = distance;
FurthestVertex = v;
}
}
beyondVertices.Add(v);
}
}
/// <summary>
/// Compares the values of two vertices. The return value (-1, 0 or +1) are found
/// by first checking the first coordinate and then progressing through the rest.
/// In this way {2, 8} will be a "-1" (less than) {3, 1}.
/// </summary>
/// <param name="u"> The base vertex index, u. </param>
/// <param name="v"> The compared vertex index, v. </param>
/// <returns> System.Int32. </returns>
private int LexCompare(int u, int v)
{
int uOffset = u * NumOfDimensions, vOffset = v * NumOfDimensions;
for (int i = 0; i < NumOfDimensions; i++)
{
double x = Positions[uOffset + i], y = Positions[vOffset + i];
int comp = x.CompareTo(y);
if (comp != 0)
{
return comp;
}
}
return 0;
}
/// <summary>
/// Used by update faces.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="beyond"> The beyond. </param>
/// <param name="beyond1"> The beyond1. </param>
private void FindBeyondVertices(ConvexFaceInternal face, IndexBuffer beyond, IndexBuffer beyond1)
{
IndexBuffer beyondVertices = BeyondBuffer;
MaxDistance = double.NegativeInfinity;
FurthestVertex = 0;
int v;
for (int i = 0; i < beyond1.Count; i++)
{
VertexVisited[beyond1[i]] = true;
}
VertexVisited[CurrentVertex] = false;
for (int i = 0; i < beyond.Count; i++)
{
v = beyond[i];
if (v == CurrentVertex)
{
continue;
}
VertexVisited[v] = false;
IsBeyond(face, beyondVertices, v);
}
for (int i = 0; i < beyond1.Count; i++)
{
v = beyond1[i];
if (VertexVisited[v])
{
IsBeyond(face, beyondVertices, v);
}
}
face.FurthestVertex = FurthestVertex;
// Pull the old switch a roo (switch the face beyond buffers)
IndexBuffer temp = face.VerticesBeyond;
face.VerticesBeyond = beyondVertices;
if (temp.Count > 0)
{
temp.Clear();
}
BeyondBuffer = temp;
}
/// <summary>
/// Finds the beyond vertices.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="beyond"> The beyond. </param>
private void FindBeyondVertices(ConvexFaceInternal face, IndexBuffer beyond)
{
IndexBuffer beyondVertices = BeyondBuffer;
MaxDistance = double.NegativeInfinity;
FurthestVertex = 0;
int v;
for (int i = 0; i < beyond.Count; i++)
{
v = beyond[i];
if (v == CurrentVertex)
{
continue;
}
IsBeyond(face, beyondVertices, v);
}
face.FurthestVertex = FurthestVertex;
// Pull the old switch a roo (switch the face beyond buffers)
IndexBuffer temp = face.VerticesBeyond;
face.VerticesBeyond = beyondVertices;
if (temp.Count > 0)
{
temp.Clear();
}
BeyondBuffer = temp;
}
/// <summary>
/// Recalculates the centroid of the current hull.
/// </summary>
private void UpdateCenter()
{
for (int i = 0; i < NumOfDimensions; i++)
{
Center[i] *= ConvexHullSize;
}
ConvexHullSize += 1;
double f = 1.0 / ConvexHullSize;
int co = CurrentVertex * NumOfDimensions;
for (int i = 0; i < NumOfDimensions; i++)
{
Center[i] = f * (Center[i] + Positions[co + i]);
}
}
/// <summary>
/// Removes the last vertex from the center.
/// </summary>
private void RollbackCenter()
{
for (int i = 0; i < NumOfDimensions; i++)
{
Center[i] *= ConvexHullSize;
}
ConvexHullSize -= 1;
double f = ConvexHullSize > 0 ? 1.0 / ConvexHullSize : 0.0;
int co = CurrentVertex * NumOfDimensions;
for (int i = 0; i < NumOfDimensions; i++)
{
Center[i] = f * (Center[i] - Positions[co + i]);
}
}
/// <summary>
/// Handles singular vertex.
/// </summary>
private void HandleSingular()
{
RollbackCenter();
SingularVertices.Add(CurrentVertex);
// This means that all the affected faces must be on the hull and that all their "vertices beyond" are
// singular.
for (int fIndex = 0; fIndex < AffectedFaceBuffer.Count; fIndex++)
{
ConvexFaceInternal face = FacePool[AffectedFaceBuffer[fIndex]];
IndexBuffer vb = face.VerticesBeyond;
for (int i = 0; i < vb.Count; i++)
{
SingularVertices.Add(vb[i]);
}
ConvexFaces.Add(face.Index);
UnprocessedFaces.Remove(face);
ObjectManager.DepositVertexBuffer(face.VerticesBeyond);
face.VerticesBeyond = EmptyBuffer;
}
}
/// <summary>
/// Get a vertex coordinate. In order to reduce speed, all vertex coordinates
/// have been placed in a single array.
/// </summary>
/// <param name="vIndex"> The vertex index. </param>
/// <param name="dimension"> The index of the dimension. </param>
/// <returns> System.Double. </returns>
private double GetCoordinate(int vIndex, int dimension)
{
return Positions[vIndex * NumOfDimensions + dimension];
}
#region Returning the Results in the proper format
/// <summary>
/// Gets the hull vertices.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <param name="data"> The data. </param>
/// <returns> TVertex[]. </returns>
private TVertex[] GetHullVertices<TVertex>(IList<TVertex> data)
{
int cellCount = ConvexFaces.Count;
int hullVertexCount = 0;
for (int i = 0; i < NumberOfVertices; i++)
{
VertexVisited[i] = false;
}
for (int i = 0; i < cellCount; i++)
{
int[] vs = FacePool[ConvexFaces[i]].Vertices;
for (int j = 0; j < vs.Length; j++)
{
int v = vs[j];
if (!VertexVisited[v])
{
VertexVisited[v] = true;
hullVertexCount++;
}
}
}
TVertex[] result = new TVertex[hullVertexCount];
for (int i = 0; i < NumberOfVertices; i++)
{
if (VertexVisited[i])
{
result[--hullVertexCount] = data[i];
}
}
return result;
}
/// <summary>
/// Finds the convex hull and creates the TFace objects.
/// </summary>
/// <typeparam name="TFace"> The type of the t face. </typeparam>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <returns> TFace[]. </returns>
private TFace[] GetConvexFaces<TVertex, TFace>()
where TFace : ConvexFace<TVertex, TFace>, new()
where TVertex : IVertex
{
IndexBuffer faces = ConvexFaces;
int cellCount = faces.Count;
TFace[] cells = new TFace[cellCount];
for (int i = 0; i < cellCount; i++)
{
ConvexFaceInternal face = FacePool[faces[i]];
TVertex[] vertices = new TVertex[NumOfDimensions];
for (int j = 0; j < NumOfDimensions; j++)
{
vertices[j] = (TVertex)Vertices[face.Vertices[j]];
}
cells[i] = new TFace
{
Vertices = vertices,
Adjacency = new TFace[NumOfDimensions],
Normal = IsLifted ? null : face.Normal,
};
face.Tag = i;
}
for (int i = 0; i < cellCount; i++)
{
ConvexFaceInternal face = FacePool[faces[i]];
TFace cell = cells[i];
for (int j = 0; j < NumOfDimensions; j++)
{
if (face.AdjacentFaces[j] < 0)
{
continue;
}
cell.Adjacency[j] = cells[FacePool[face.AdjacentFaces[j]].Tag];
}
// Fix the vertex orientation.
if (face.IsNormalFlipped)
{
TVertex tempVert = cell.Vertices[0];
cell.Vertices[0] = cell.Vertices[NumOfDimensions - 1];
cell.Vertices[NumOfDimensions - 1] = tempVert;
TFace tempAdj = cell.Adjacency[0];
cell.Adjacency[0] = cell.Adjacency[NumOfDimensions - 1];
cell.Adjacency[NumOfDimensions - 1] = tempAdj;
}
}
return cells;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5481d13618b5f4b42b5b7b22beac2e69
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,257 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// For deferred face addition.
/// </summary>
internal sealed class DeferredFace
{
/// <summary>
/// The faces.
/// </summary>
public ConvexFaceInternal Face, Pivot, OldFace;
/// <summary>
/// The indices.
/// </summary>
public int FaceIndex, PivotIndex;
}
/// <summary>
/// A helper class used to connect faces.
/// </summary>
internal sealed class FaceConnector
{
/// <summary>
/// The edge to be connected.
/// </summary>
public int EdgeIndex;
/// <summary>
/// The face.
/// </summary>
public ConvexFaceInternal Face;
/// <summary>
/// The hash code computed from indices.
/// </summary>
public uint HashCode;
/// <summary>
/// Next node in the list.
/// </summary>
public FaceConnector Next;
/// <summary>
/// Prev node in the list.
/// </summary>
public FaceConnector Previous;
/// <summary>
/// The vertex indices.
/// </summary>
public int[] Vertices;
/// <summary>
/// Ctor.
/// </summary>
/// <param name="dimension"> The dimension. </param>
public FaceConnector(int dimension)
{
Vertices = new int[dimension - 1];
}
/// <summary>
/// Updates the connector.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="edgeIndex"> Index of the edge. </param>
/// <param name="dim"> The dim. </param>
public void Update(ConvexFaceInternal face, int edgeIndex, int dim)
{
Face = face;
EdgeIndex = edgeIndex;
uint hashCode = 23;
unchecked
{
int[] vs = face.Vertices;
int i, c = 0;
for (i = 0; i < edgeIndex; i++)
{
Vertices[c++] = vs[i];
hashCode += 31 * hashCode + (uint)vs[i];
}
for (i = edgeIndex + 1; i < vs.Length; i++)
{
Vertices[c++] = vs[i];
hashCode += 31 * hashCode + (uint)vs[i];
}
}
HashCode = hashCode;
}
/// <summary>
/// Can two faces be connected.
/// </summary>
/// <param name="a"> a. </param>
/// <param name="b"> The b. </param>
/// <param name="dim"> The dim. </param>
/// <returns> <c> true </c> if XXXX, <c> false </c> otherwise. </returns>
public static bool AreConnectable(FaceConnector a, FaceConnector b, int dim)
{
if (a.HashCode != b.HashCode)
{
return false;
}
int[] av = a.Vertices;
int[] bv = b.Vertices;
for (int i = 0; i < av.Length; i++)
{
if (av[i] != bv[i])
{
return false;
}
}
return true;
}
/// <summary>
/// Connect two faces.
/// </summary>
/// <param name="a"> a. </param>
/// <param name="b"> The b. </param>
public static void Connect(FaceConnector a, FaceConnector b)
{
a.Face.AdjacentFaces[a.EdgeIndex] = b.Face.Index;
b.Face.AdjacentFaces[b.EdgeIndex] = a.Face.Index;
}
}
/// <summary>
/// This internal class manages the faces of the convex hull. It is a
/// separate class from the desired user class.
/// </summary>
internal sealed class ConvexFaceInternal
{
/// <summary>
/// Gets or sets the adjacent face data.
/// </summary>
public int[] AdjacentFaces;
/// <summary>
/// The furthest vertex.
/// </summary>
public int FurthestVertex;
/// <summary>
/// Index of the face inside the pool.
/// </summary>
public int Index;
/// <summary>
/// Is it present in the list.
/// </summary>
public bool InList;
/// <summary>
/// Is the normal flipped?
/// </summary>
public bool IsNormalFlipped;
/// <summary>
/// Next node in the list.
/// </summary>
public ConvexFaceInternal Next;
/// <summary>
/// Gets or sets the normal vector.
/// </summary>
public double[] Normal;
/// <summary>
/// Face plane constant element.
/// </summary>
public double Offset;
//public int UnprocessedIndex;
/// <summary>
/// Prev node in the list.
/// </summary>
public ConvexFaceInternal Previous;
/// <summary>
/// Used to traverse affected faces and create the Delaunay representation.
/// </summary>
public int Tag;
/// <summary>
/// Gets or sets the vertices.
/// </summary>
public int[] Vertices;
/// <summary>
/// Gets or sets the vertices beyond.
/// </summary>
public IndexBuffer VerticesBeyond;
/// <summary>
/// Initializes a new instance of the <see cref="ConvexFaceInternal" /> class.
/// </summary>
/// <param name="dimension"> The dimension. </param>
/// <param name="index"> The index. </param>
/// <param name="beyondList"> The beyond list. </param>
public ConvexFaceInternal(int dimension, int index, IndexBuffer beyondList)
{
Index = index;
AdjacentFaces = new int[dimension];
VerticesBeyond = beyondList;
Normal = new double[dimension];
Vertices = new int[dimension];
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f2a919e301ea5c34da8dd90dc882abdf
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,543 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A helper class mostly for normal computation. If convex hulls are computed
/// in higher dimensions, it might be a good idea to add a specific
/// FindNormalVectorND function.
/// </summary>
internal class MathHelper
{
/// <summary>
/// The dimension
/// </summary>
private readonly int Dimension;
/// <summary>
/// The matrix pivots
/// </summary>
private readonly int[] matrixPivots;
/// <summary>
/// The n d matrix
/// </summary>
private readonly double[] nDMatrix;
/// <summary>
/// The n d normal helper vector
/// </summary>
private readonly double[] nDNormalHelperVector;
/// <summary>
/// The nt x
/// </summary>
private readonly double[] ntX;
/// <summary>
/// The nt y
/// </summary>
private readonly double[] ntY;
/// <summary>
/// The nt z
/// </summary>
private readonly double[] ntZ;
/// <summary>
/// The position data
/// </summary>
private readonly double[] PositionData;
/// <summary>
/// Initializes a new instance of the <see cref="MathHelper" /> class.
/// </summary>
/// <param name="dimension"> The dimension. </param>
/// <param name="positions"> The positions. </param>
internal MathHelper(int dimension, double[] positions)
{
PositionData = positions;
Dimension = dimension;
ntX = new double[Dimension];
ntY = new double[Dimension];
ntZ = new double[Dimension];
nDNormalHelperVector = new double[Dimension];
nDMatrix = new double[Dimension * Dimension];
matrixPivots = new int[Dimension];
}
/// <summary>
/// Calculates the normal and offset of the hyper-plane given by the face's vertices.
/// </summary>
/// <param name="face"> The face. </param>
/// <param name="center"> The center. </param>
/// <returns> <c> true </c> if XXXX, <c> false </c> otherwise. </returns>
internal bool CalculateFacePlane(ConvexFaceInternal face, double[] center)
{
int[] vertices = face.Vertices;
double[] normal = face.Normal;
FindNormalVector(vertices, normal);
if (double.IsNaN(normal[0]))
{
return false;
}
double offset = 0.0;
double centerDistance = 0.0;
int fi = vertices[0] * Dimension;
for (int i = 0; i < Dimension; i++)
{
double n = normal[i];
offset += n * PositionData[fi + i];
centerDistance += n * center[i];
}
face.Offset = -offset;
centerDistance -= offset;
if (centerDistance > 0)
{
for (int i = 0; i < Dimension; i++)
{
normal[i] = -normal[i];
}
face.Offset = offset;
face.IsNormalFlipped = true;
}
else
{
face.IsNormalFlipped = false;
}
return true;
}
/// <summary>
/// Check if the vertex is "visible" from the face.
/// The vertex is "over face" if the return value is &gt; Constants.PlaneDistanceTolerance.
/// </summary>
/// <param name="v"> The v. </param>
/// <param name="f"> The f. </param>
/// <returns> The vertex is "over face" if the result is positive. </returns>
internal double GetVertexDistance(int v, ConvexFaceInternal f)
{
double[] normal = f.Normal;
int x = v * Dimension;
double distance = f.Offset;
for (int i = 0; i < normal.Length; i++)
{
distance += normal[i] * PositionData[x + i];
}
return distance;
}
/// <summary>
/// Returns the vector the between vertices.
/// </summary>
/// <param name="fromIndex"> From index. </param>
/// <param name="toIndex"> To index. </param>
/// <param name="target"> The target. </param>
/// <returns> </returns>
internal double[] VectorBetweenVertices(int toIndex, int fromIndex)
{
double[] target = new double[Dimension];
VectorBetweenVertices(toIndex, fromIndex, target);
return target;
}
/// <summary>
/// Returns the vector the between vertices.
/// </summary>
/// <param name="fromIndex"> From index. </param>
/// <param name="toIndex"> To index. </param>
/// <param name="target"> The target. </param>
/// <returns> </returns>
private void VectorBetweenVertices(int toIndex, int fromIndex, double[] target)
{
int u = toIndex * Dimension, v = fromIndex * Dimension;
for (int i = 0; i < Dimension; i++)
{
target[i] = PositionData[u + i] - PositionData[v + i];
}
}
// Modified from Math.NET
// Copyright (c) 2009-2013 Math.NET
/// <summary>
/// Lus the factor.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="order"> The order. </param>
/// <param name="ipiv"> The ipiv. </param>
/// <param name="vecLUcolj"> The vec l ucolj. </param>
private static void LUFactor(double[] data, int order, int[] ipiv, double[] vecLUcolj)
{
// Initialize the pivot matrix to the identity permutation.
for (int i = 0; i < order; i++)
{
ipiv[i] = i;
}
// Outer loop.
for (int j = 0; j < order; j++)
{
int indexj = j * order;
int indexjj = indexj + j;
// Make a copy of the j-th column to localize references.
for (int i = 0; i < order; i++)
{
vecLUcolj[i] = data[indexj + i];
}
// Apply previous transformations.
for (int i = 0; i < order; i++)
{
// Most of the time is spent in the following dot product.
int kmax = Math.Min(i, j);
double s = 0.0;
for (int k = 0; k < kmax; k++)
{
s += data[k * order + i] * vecLUcolj[k];
}
data[indexj + i] = vecLUcolj[i] -= s;
}
// Find pivot and exchange if necessary.
int p = j;
for (int i = j + 1; i < order; i++)
{
if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p]))
{
p = i;
}
}
if (p != j)
{
for (int k = 0; k < order; k++)
{
int indexk = k * order;
int indexkp = indexk + p;
int indexkj = indexk + j;
double temp = data[indexkp];
data[indexkp] = data[indexkj];
data[indexkj] = temp;
}
ipiv[j] = p;
}
// Compute multipliers.
if ((j < order) & (data[indexjj] != 0.0))
{
for (int i = j + 1; i < order; i++)
{
data[indexj + i] /= data[indexjj];
}
}
}
}
#region Find the normal vector of the face
/// <summary>
/// Finds normal vector of a hyper-plane given by vertices.
/// Stores the results to normalData.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="normalData"> The normal data. </param>
private void FindNormalVector(int[] vertices, double[] normalData)
{
switch (Dimension)
{
case 2:
FindNormalVector2D(vertices, normalData);
break;
case 3:
FindNormalVector3D(vertices, normalData);
break;
case 4:
FindNormalVector4D(vertices, normalData);
break;
default:
FindNormalVectorND(vertices, normalData);
break;
}
}
/// <summary>
/// Finds 2D normal vector.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="normal"> The normal. </param>
private void FindNormalVector2D(int[] vertices, double[] normal)
{
VectorBetweenVertices(vertices[1], vertices[0], ntX);
double nx = -ntX[1];
double ny = ntX[0];
double norm = Math.Sqrt(nx * nx + ny * ny);
double f = 1.0 / norm;
normal[0] = f * nx;
normal[1] = f * ny;
}
/// <summary>
/// Finds 3D normal vector.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="normal"> The normal. </param>
private void FindNormalVector3D(int[] vertices, double[] normal)
{
VectorBetweenVertices(vertices[1], vertices[0], ntX);
VectorBetweenVertices(vertices[2], vertices[1], ntY);
double nx = ntX[1] * ntY[2] - ntX[2] * ntY[1];
double ny = ntX[2] * ntY[0] - ntX[0] * ntY[2];
double nz = ntX[0] * ntY[1] - ntX[1] * ntY[0];
double norm = Math.Sqrt(nx * nx + ny * ny + nz * nz);
double f = 1.0 / norm;
normal[0] = f * nx;
normal[1] = f * ny;
normal[2] = f * nz;
}
/// <summary>
/// Finds 4D normal vector.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="normal"> The normal. </param>
private void FindNormalVector4D(int[] vertices, double[] normal)
{
VectorBetweenVertices(vertices[1], vertices[0], ntX);
VectorBetweenVertices(vertices[2], vertices[1], ntY);
VectorBetweenVertices(vertices[3], vertices[2], ntZ);
double[] x = ntX;
double[] y = ntY;
double[] z = ntZ;
// This was generated using Mathematica
double nx = x[3] * (y[2] * z[1] - y[1] * z[2])
+ x[2] * (y[1] * z[3] - y[3] * z[1])
+ x[1] * (y[3] * z[2] - y[2] * z[3]);
double ny = x[3] * (y[0] * z[2] - y[2] * z[0])
+ x[2] * (y[3] * z[0] - y[0] * z[3])
+ x[0] * (y[2] * z[3] - y[3] * z[2]);
double nz = x[3] * (y[1] * z[0] - y[0] * z[1])
+ x[1] * (y[0] * z[3] - y[3] * z[0])
+ x[0] * (y[3] * z[1] - y[1] * z[3]);
double nw = x[2] * (y[0] * z[1] - y[1] * z[0])
+ x[1] * (y[2] * z[0] - y[0] * z[2])
+ x[0] * (y[1] * z[2] - y[2] * z[1]);
double norm = Math.Sqrt(nx * nx + ny * ny + nz * nz + nw * nw);
double f = 1.0 / norm;
normal[0] = f * nx;
normal[1] = f * ny;
normal[2] = f * nz;
normal[3] = f * nw;
}
/// <summary>
/// Finds the normal vector nd.
/// </summary>
/// <param name="vertices"> The vertices. </param>
/// <param name="normal"> The normal. </param>
private void FindNormalVectorND(int[] vertices, double[] normal)
{
/* We need to solve the matrix A n = B where
* - A contains coordinates of vertices as columns
* - B is vector with all 1's. Really, it should be the distance of
* the plane from the origin, but - since we're not worried about that
* here and we will normalize the normal anyway - all 1's suffices.
*/
int[] iPiv = matrixPivots;
double[] data = nDMatrix;
double norm = 0.0;
// Solve determinants by replacing x-th column by all 1.
for (int x = 0; x < Dimension; x++)
{
for (int i = 0; i < Dimension; i++)
{
int offset = vertices[i] * Dimension;
for (int j = 0; j < Dimension; j++)
{
// maybe I got the i/j mixed up here regarding the representation Math.net uses...
// ...but it does not matter since Det(A) = Det(Transpose(A)).
data[Dimension * i + j] = j == x ? 1.0 : PositionData[offset + j];
}
}
LUFactor(data, Dimension, iPiv, nDNormalHelperVector);
double coord = 1.0;
for (int i = 0; i < Dimension; i++)
{
if (iPiv[i] != i)
{
coord *= -data[Dimension * i + i]; // the determinant sign changes on row swap.
}
else
{
coord *= data[Dimension * i + i];
}
}
normal[x] = coord;
norm += coord * coord;
}
// Normalize the result
double f = 1.0 / Math.Sqrt(norm);
for (int i = 0; i < normal.Length; i++)
{
normal[i] *= f;
}
}
#endregion
#region Simplex Volume
/// <summary>
/// Gets the simplex volume. Prior to having enough edge vectors, the method pads the remaining with all
/// "other numbers". So, yes, this method is not really finding the volume. But a relative volume-like measure. It
/// uses the magnitude of the determinant as the volume stand-in following the Cayley-Menger theorem.
/// </summary>
/// <param name="edgeVectors"> The edge vectors. </param>
/// <param name="lastIndex"> The last index. </param>
/// <returns> </returns>
internal double GetSimplexVolume(double[][] edgeVectors, int lastIndex, double bigNumber)
{
double[] A = new double[Dimension * Dimension];
int index = 0;
for (int i = 0; i < Dimension; i++)
for (int j = 0; j < Dimension; j++)
{
if (i <= lastIndex)
{
A[index++] = edgeVectors[i][j];
}
else
{
A[index] = Math.Pow(-1, index) * index++ / bigNumber;
}
}
// this last term is used for all the vertices in the comparison for the yet determined vertices
// the idea is to come up with sets of numbers that are orthogonal so that an non-zero value will result
// and to choose smallish numbers since the choice of vectors will affect what the end volume is.
// A better way (todo?) is to solve a smaller matrix. However, cases were found in which the obvious smaller
// vector
// (the upper left) had too many zeros. So, one would need to find the right subset. Indeed choosing a
// subset
// biases the first dimensions of the others. Perhaps a larger volume would be created from a different
// vertex
// if another subset of dimensions were used.
return Math.Abs(DeterminantDestructive(A));
}
/// <summary>
/// Determinants the destructive.
/// </summary>
/// <param name="buff"> The buff. </param>
/// <returns> System.Double. </returns>
private double DeterminantDestructive(double[] A)
{
switch (Dimension)
{
case 0:
return 0.0;
case 1:
return A[0];
case 2:
return A[0] * A[3] - A[1] * A[2];
case 3:
return A[0] * A[4] * A[8] + A[1] * A[5] * A[6] + A[2] * A[3] * A[7]
- A[0] * A[5] * A[7] - A[1] * A[3] * A[8] - A[2] * A[4] * A[6];
default:
{
int[] iPiv = new int[Dimension];
double[] helper = new double[Dimension];
LUFactor(A, Dimension, iPiv, helper);
double det = 1.0;
for (int i = 0; i < iPiv.Length; i++)
{
det *= A[Dimension * i + i];
if (iPiv[i] != i)
{
det *= -1; // the determinant sign changes on row swap.
}
}
return det;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2ce11a64f3a349f448712e37f44bbbf3
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,260 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A helper class for object allocation/storage.
/// This helps the GC a lot as it prevents the creation of about 75% of
/// new face objects (in the case of ConvexFaceInternal). In the case of
/// FaceConnectors and DefferedFaces, the difference is even higher (in most
/// cases O(1) vs O(number of created faces)).
/// </summary>
internal class ObjectManager
{
/// <summary>
/// The deferred face stack
/// </summary>
private readonly SimpleList<DeferredFace> DeferredFaceStack;
/// <summary>
/// The dimension
/// </summary>
private readonly int Dimension;
/// <summary>
/// The empty buffer stack
/// </summary>
private readonly SimpleList<IndexBuffer> EmptyBufferStack;
/// <summary>
/// The free face indices
/// </summary>
private readonly IndexBuffer FreeFaceIndices;
/// <summary>
/// The hull
/// </summary>
private readonly ConvexHullAlgorithm Hull;
/// <summary>
/// The connector stack
/// </summary>
private FaceConnector ConnectorStack;
/// <summary>
/// The face pool
/// </summary>
private ConvexFaceInternal[] FacePool;
/// <summary>
/// The face pool capacity
/// </summary>
private int FacePoolCapacity;
/// <summary>
/// The face pool size
/// </summary>
private int FacePoolSize;
/// <summary>
/// Create the manager.
/// </summary>
/// <param name="hull"> The hull. </param>
public ObjectManager(ConvexHullAlgorithm hull)
{
Dimension = hull.NumOfDimensions;
Hull = hull;
FacePool = hull.FacePool;
FacePoolSize = 0;
FacePoolCapacity = hull.FacePool.Length;
FreeFaceIndices = new IndexBuffer();
EmptyBufferStack = new SimpleList<IndexBuffer>();
DeferredFaceStack = new SimpleList<DeferredFace>();
}
/// <summary>
/// Return the face to the pool for later use.
/// </summary>
/// <param name="faceIndex"> Index of the face. </param>
public void DepositFace(int faceIndex)
{
ConvexFaceInternal face = FacePool[faceIndex];
int[] af = face.AdjacentFaces;
for (int i = 0; i < af.Length; i++)
{
af[i] = -1;
}
FreeFaceIndices.Push(faceIndex);
}
/// <summary>
/// Reallocate the face pool, including the AffectedFaceFlags
/// </summary>
private void ReallocateFacePool()
{
ConvexFaceInternal[] newPool = new ConvexFaceInternal[2 * FacePoolCapacity];
bool[] newTags = new bool[2 * FacePoolCapacity];
Array.Copy(FacePool, newPool, FacePoolCapacity);
Buffer.BlockCopy(Hull.AffectedFaceFlags, 0, newTags, 0, FacePoolCapacity * sizeof(bool));
FacePoolCapacity = 2 * FacePoolCapacity;
Hull.FacePool = newPool;
FacePool = newPool;
Hull.AffectedFaceFlags = newTags;
}
/// <summary>
/// Create a new face and put it in the pool.
/// </summary>
/// <returns> System.Int32. </returns>
private int CreateFace()
{
int index = FacePoolSize;
ConvexFaceInternal face = new(Dimension, index, GetVertexBuffer());
FacePoolSize++;
if (FacePoolSize > FacePoolCapacity)
{
ReallocateFacePool();
}
FacePool[index] = face;
return index;
}
/// <summary>
/// Return index of an unused face or creates a new one.
/// </summary>
/// <returns> System.Int32. </returns>
public int GetFace()
{
if (FreeFaceIndices.Count > 0)
{
return FreeFaceIndices.Pop();
}
return CreateFace();
}
/// <summary>
/// Store a face connector in the "embedded" linked list.
/// </summary>
/// <param name="connector"> The connector. </param>
public void DepositConnector(FaceConnector connector)
{
if (ConnectorStack == null)
{
connector.Next = null;
ConnectorStack = connector;
}
else
{
connector.Next = ConnectorStack;
ConnectorStack = connector;
}
}
/// <summary>
/// Get an unused face connector. If none is available, create it.
/// </summary>
/// <returns> FaceConnector. </returns>
public FaceConnector GetConnector()
{
if (ConnectorStack == null)
{
return new FaceConnector(Dimension);
}
FaceConnector ret = ConnectorStack;
ConnectorStack = ConnectorStack.Next;
ret.Next = null;
return ret;
}
/// <summary>
/// Deposit the index buffer.
/// </summary>
/// <param name="buffer"> The buffer. </param>
public void DepositVertexBuffer(IndexBuffer buffer)
{
buffer.Clear();
EmptyBufferStack.Push(buffer);
}
/// <summary>
/// Get a store index buffer or create a new instance.
/// </summary>
/// <returns> IndexBuffer. </returns>
public IndexBuffer GetVertexBuffer()
{
return EmptyBufferStack.Count != 0 ? EmptyBufferStack.Pop() : new IndexBuffer();
}
/// <summary>
/// Deposit the deferred face.
/// </summary>
/// <param name="face"> The face. </param>
public void DepositDeferredFace(DeferredFace face)
{
DeferredFaceStack.Push(face);
}
/// <summary>
/// Get the deferred face.
/// </summary>
/// <returns> DeferredFace. </returns>
public DeferredFace GetDeferredFace()
{
return DeferredFaceStack.Count != 0 ? DeferredFaceStack.Pop() : new DeferredFace();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3d85ac2afa1f0644483ae367df38e2c3
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// An interface for a structure with nD position.
/// </summary>
public interface IVertex
{
/// <summary>
/// Position of the vertex.
/// </summary>
/// <value> The position. </value>
double[] Position { get; }
}
/// <summary>
/// "Default" vertex.
/// </summary>
/// <seealso cref="NWH.DWP2.MiConvexHull.IVertex" />
public class DefaultVertex : IVertex
{
/// <summary>
/// Position of the vertex.
/// </summary>
/// <value> The position. </value>
public double[] Position { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 55cca5d242b599546bef109b0dcb77b8
timeCreated: 1490199237
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010 David Sehnal, Matthew Campbell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a52199677cb510241956083f04f1f2f4
timeCreated: 1490199238
licenseType: Store
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,184 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System.Collections.Generic;
using System.Linq;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// Simple interface to unify different types of triangulations in the future.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
public interface ITriangulation<TVertex, TCell>
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
{
/// <summary>
/// Triangulation simplexes. For 2D - triangles, 3D - tetrahedrons, etc ...
/// </summary>
/// <value> The cells. </value>
IEnumerable<TCell> Cells { get; }
}
/// <summary>
/// Factory class for creating triangulations.
/// </summary>
public static class Triangulation
{
/// <summary>
/// Creates the Delaunay triangulation of the input data.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> ITriangulation&lt;TVertex, DefaultTriangulationCell&lt;TVertex&gt;&gt;. </returns>
public static ITriangulation<TVertex, DefaultTriangulationCell<TVertex>> CreateDelaunay<TVertex>(
IList<TVertex> data)
where TVertex : IVertex
{
return DelaunayTriangulation<TVertex, DefaultTriangulationCell<TVertex>>.Create(data);
}
/// <summary>
/// Creates the Delaunay triangulation of the input data.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> ITriangulation&lt;DefaultVertex, DefaultTriangulationCell&lt;DefaultVertex&gt;&gt;. </returns>
public static ITriangulation<DefaultVertex, DefaultTriangulationCell<DefaultVertex>> CreateDelaunay(
IList<double[]> data)
{
List<DefaultVertex> points = data.Select(p => new DefaultVertex { Position = p, }).ToList();
return DelaunayTriangulation<DefaultVertex, DefaultTriangulationCell<DefaultVertex>>.Create(points);
}
/// <summary>
/// Creates the Delaunay triangulation of the input data.
/// </summary>
/// <typeparam name="TVertex"> </typeparam>
/// <typeparam name="TFace"> </typeparam>
/// <param name="data"> </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> </returns>
public static ITriangulation<TVertex, TFace> CreateDelaunay<TVertex, TFace>(IList<TVertex> data)
where TVertex : IVertex
where TFace : TriangulationCell<TVertex, TFace>, new()
{
return DelaunayTriangulation<TVertex, TFace>.Create(data);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> </typeparam>
/// <typeparam name="TCell"> </typeparam>
/// <typeparam name="TEdge"> </typeparam>
/// <param name="data"> </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> </returns>
public static VoronoiMesh<TVertex, TCell, TEdge> CreateVoronoi<TVertex, TCell, TEdge>(IList<TVertex> data)
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
where TEdge : VoronoiEdge<TVertex, TCell>, new()
{
return VoronoiMesh<TVertex, TCell, TEdge>.Create(data);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> </typeparam>
/// <param name="data"> </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> </returns>
public static
VoronoiMesh
<TVertex, DefaultTriangulationCell<TVertex>, VoronoiEdge<TVertex, DefaultTriangulationCell<TVertex>>>
CreateVoronoi<TVertex>(IList<TVertex> data)
where TVertex : IVertex
{
return
VoronoiMesh
<TVertex, DefaultTriangulationCell<TVertex>, VoronoiEdge<TVertex, DefaultTriangulationCell<TVertex>>
>.Create(data);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <param name="data"> </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> </returns>
public static
VoronoiMesh
<DefaultVertex, DefaultTriangulationCell<DefaultVertex>,
VoronoiEdge<DefaultVertex, DefaultTriangulationCell<DefaultVertex>>>
CreateVoronoi(IList<double[]> data)
{
List<DefaultVertex> points = data.Select(p => new DefaultVertex { Position = p.ToArray(), }).ToList();
return
VoronoiMesh
<DefaultVertex, DefaultTriangulationCell<DefaultVertex>,
VoronoiEdge<DefaultVertex, DefaultTriangulationCell<DefaultVertex>>>.Create(points);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> </typeparam>
/// <typeparam name="TCell"> </typeparam>
/// <param name="data"> </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> </returns>
public static VoronoiMesh<TVertex, TCell, VoronoiEdge<TVertex, TCell>> CreateVoronoi<TVertex, TCell>(
IList<TVertex> data)
where TVertex : IVertex
where TCell : TriangulationCell<TVertex, TCell>, new()
{
return VoronoiMesh<TVertex, TCell, VoronoiEdge<TVertex, TCell>>.Create(data);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 895b4d962c8ab954badafb72a29ec508
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 600e58bff5c3123429999ef4addc1d01
folderAsset: yes
timeCreated: 1490199232
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System.Collections.Generic;
using System.Linq;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/*
* Code here handles triangulation related stuff.
*/
/// <summary>
/// Class ConvexHullAlgorithm.
/// </summary>
internal partial class ConvexHullAlgorithm
{
/// <summary>
/// Computes the Delaunay triangulation.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
/// <param name="data"> The data. </param>
/// <returns> TCell[]. </returns>
internal static TCell[] GetDelaunayTriangulation<TVertex, TCell>(IList<TVertex> data)
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
{
ConvexHullAlgorithm ch = new(data.Cast<IVertex>().ToArray(), true, Constants.DefaultPlaneDistanceTolerance);
ch.GetConvexHull();
ch.RemoveUpperFaces();
return ch.GetConvexFaces<TVertex, TCell>();
}
/// <summary>
/// Removes up facing Tetrahedrons from the triangulation.
/// </summary>
private void RemoveUpperFaces()
{
IndexBuffer delaunayFaces = ConvexFaces;
int dimension = NumOfDimensions - 1;
// Remove the "upper" faces
for (int i = delaunayFaces.Count - 1; i >= 0; i--)
{
int candidateIndex = delaunayFaces[i];
ConvexFaceInternal candidate = FacePool[candidateIndex];
if (candidate.Normal[dimension] >= 0.0)
{
for (int fi = 0; fi < candidate.AdjacentFaces.Length; fi++)
{
int af = candidate.AdjacentFaces[fi];
if (af >= 0)
{
ConvexFaceInternal face = FacePool[af];
for (int j = 0; j < face.AdjacentFaces.Length; j++)
{
if (face.AdjacentFaces[j] == candidateIndex)
{
face.AdjacentFaces[j] = -1;
}
}
}
}
delaunayFaces[i] = delaunayFaces[delaunayFaces.Count - 1];
delaunayFaces.Pop();
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f82a31334d2c0d04cb8f8cf0a1040eec
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
using System.Collections.Generic;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// Calculation and representation of Delaunay triangulation.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
/// <seealso cref="NWH.DWP2.MiConvexHull.ITriangulation{TVertex, TCell}" />
public class DelaunayTriangulation<TVertex, TCell> : ITriangulation<TVertex, TCell>
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
{
/// <summary>
/// Can only be created using a factory method.
/// </summary>
private DelaunayTriangulation()
{
}
/// <summary>
/// Cells of the triangulation.
/// </summary>
/// <value> The cells. </value>
public IEnumerable<TCell> Cells { get; private set; }
/// <summary>
/// Creates the Delaunay triangulation of the input data.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default ConvexHullComputationConfig is used. </param>
/// <returns> DelaunayTriangulation&lt;TVertex, TCell&gt;. </returns>
/// <exception cref="ArgumentNullException"> data </exception>
public static DelaunayTriangulation<TVertex, TCell> Create(IList<TVertex> data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
if (data.Count == 0)
{
return new DelaunayTriangulation<TVertex, TCell> { Cells = new TCell[0], };
}
TCell[] cells = ConvexHullAlgorithm.GetDelaunayTriangulation<TVertex, TCell>(data);
return new DelaunayTriangulation<TVertex, TCell> { Cells = cells, };
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 88239ca96afb6c34a952168af355e2f1
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// Representation of the triangulation cell. Pretty much the same as ConvexFace,
/// just wanted to distinguish the two.
/// To declare your own face type, use class Face : DelaunayFace(of Vertex, of Face)
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
/// <seealso cref="NWH.DWP2.MiConvexHull.ConvexFace{TVertex, TCell}" />
public abstract class TriangulationCell<TVertex, TCell> : ConvexFace<TVertex, TCell>
where TVertex : IVertex
where TCell : ConvexFace<TVertex, TCell>
{
}
/// <summary>
/// Default triangulation cell that inherits from TriangulationCell with self-referencing type parameter.
/// </summary>
/// <typeparam name="TVertex"> The type of the vertex. </typeparam>
public class DefaultTriangulationCell<TVertex> : TriangulationCell<TVertex, DefaultTriangulationCell<TVertex>>
where TVertex : IVertex
{
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: aea7d87dcfb14b14ab63eb32dd4dac36
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,116 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A class representing an (undirected) edge of the Voronoi graph.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
public class VoronoiEdge<TVertex, TCell>
where TVertex : IVertex
where TCell : TriangulationCell<TVertex, TCell>
{
/// <summary>
/// Create an instance of the edge.
/// </summary>
public VoronoiEdge()
{
}
/// <summary>
/// Create an instance of the edge.
/// </summary>
/// <param name="source"> The source. </param>
/// <param name="target"> The target. </param>
public VoronoiEdge(TCell source, TCell target)
{
Source = source;
Target = target;
}
/// <summary>
/// Source of the edge.
/// </summary>
/// <value> The source. </value>
public TCell Source { get; internal set; }
/// <summary>
/// Target of the edge.
/// </summary>
/// <value> The target. </value>
public TCell Target { get; internal set; }
/// <summary>
/// ...
/// </summary>
/// <param name="obj"> The object to compare with the current object. </param>
/// <returns>
/// <c> true </c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise,
/// <c> false </c>.
/// </returns>
public override bool Equals(object obj)
{
VoronoiEdge<TVertex, TCell> other = obj as VoronoiEdge<TVertex, TCell>;
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return (Source == other.Source && Target == other.Target)
|| (Source == other.Target && Target == other.Source);
}
/// <summary>
/// ...
/// </summary>
/// <returns> A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. </returns>
public override int GetHashCode()
{
int hash = 23;
hash = hash * 31 + Source.GetHashCode();
return hash * 31 + Target.GetHashCode();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d2b67703e22414246bc396d0d8a8f971
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,231 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
/******************************************************************************
*
* The MIT License (MIT)
*
* MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*****************************************************************************/
#region
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// A factory class for creating a Voronoi mesh.
/// </summary>
public static class VoronoiMesh
{
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
/// <typeparam name="TEdge"> The type of the t edge. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> VoronoiMesh&lt;TVertex, TCell, TEdge&gt;. </returns>
public static VoronoiMesh<TVertex, TCell, TEdge> Create<TVertex, TCell, TEdge>(IList<TVertex> data)
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
where TEdge : VoronoiEdge<TVertex, TCell>, new()
{
return VoronoiMesh<TVertex, TCell, TEdge>.Create(data);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns>
/// VoronoiMesh&lt;TVertex, DefaultTriangulationCell&lt;TVertex&gt;, VoronoiEdge&lt;TVertex,
/// DefaultTriangulationCell&lt;TVertex&gt;&gt;&gt;.
/// </returns>
public static
VoronoiMesh
<TVertex, DefaultTriangulationCell<TVertex>, VoronoiEdge<TVertex, DefaultTriangulationCell<TVertex>>>
Create<TVertex>(IList<TVertex> data)
where TVertex : IVertex
{
return
VoronoiMesh
<TVertex, DefaultTriangulationCell<TVertex>, VoronoiEdge<TVertex, DefaultTriangulationCell<TVertex>>
>.Create(data);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns>
/// VoronoiMesh&lt;DefaultVertex, DefaultTriangulationCell&lt;DefaultVertex&gt;, VoronoiEdge&lt;DefaultVertex,
/// DefaultTriangulationCell&lt;DefaultVertex&gt;&gt;&gt;.
/// </returns>
public static
VoronoiMesh
<DefaultVertex, DefaultTriangulationCell<DefaultVertex>,
VoronoiEdge<DefaultVertex, DefaultTriangulationCell<DefaultVertex>>>
Create(IList<double[]> data)
{
List<DefaultVertex> points = data.Select(p => new DefaultVertex { Position = p.ToArray(), }).ToList();
return
VoronoiMesh
<DefaultVertex, DefaultTriangulationCell<DefaultVertex>,
VoronoiEdge<DefaultVertex, DefaultTriangulationCell<DefaultVertex>>>.Create(points);
}
/// <summary>
/// Create the voronoi mesh.
/// </summary>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> VoronoiMesh&lt;TVertex, TCell, VoronoiEdge&lt;TVertex, TCell&gt;&gt;. </returns>
public static VoronoiMesh<TVertex, TCell, VoronoiEdge<TVertex, TCell>> Create<TVertex, TCell>(
IList<TVertex> data)
where TVertex : IVertex
where TCell : TriangulationCell<TVertex, TCell>, new()
{
return VoronoiMesh<TVertex, TCell, VoronoiEdge<TVertex, TCell>>.Create(data);
}
}
/// <summary>
/// A representation of a voronoi mesh.
/// </summary>
/// <typeparam name="TEdge"> The type of the t edge. </typeparam>
/// <typeparam name="TVertex"> The type of the t vertex. </typeparam>
/// <typeparam name="TCell"> The type of the t cell. </typeparam>
public class VoronoiMesh<TVertex, TCell, TEdge>
where TCell : TriangulationCell<TVertex, TCell>, new()
where TVertex : IVertex
where TEdge : VoronoiEdge<TVertex, TCell>, new()
{
/// <summary>
/// Can only be created using a factory method.
/// </summary>
private VoronoiMesh()
{
}
/// <summary>
/// Vertices of the diagram.
/// </summary>
/// <value> The vertices. </value>
public IEnumerable<TCell> Vertices { get; private set; }
/// <summary>
/// Edges connecting the cells.
/// The same information can be retrieved Cells' Adjacency.
/// </summary>
/// <value> The edges. </value>
public IEnumerable<TEdge> Edges { get; private set; }
/// <summary>
/// Create a Voronoi diagram of the input data.
/// </summary>
/// <param name="data"> The data. </param>
/// <param name="config"> If null, default TriangulationComputationConfig is used. </param>
/// <returns> VoronoiMesh&lt;TVertex, TCell, TEdge&gt;. </returns>
/// <exception cref="ArgumentNullException"> data </exception>
public static VoronoiMesh<TVertex, TCell, TEdge> Create(IList<TVertex> data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
DelaunayTriangulation<TVertex, TCell> t = DelaunayTriangulation<TVertex, TCell>.Create(data);
List<TCell> vertices = t.Cells.ToList();
HashSet<TEdge> edges = new(new EdgeComparer());
foreach (TCell f in vertices)
{
for (int i = 0; i < f.Adjacency.Length; i++)
{
TCell af = f.Adjacency[i];
if (af != null)
{
edges.Add(new TEdge { Source = f, Target = af, });
}
}
}
return new VoronoiMesh<TVertex, TCell, TEdge>
{
Vertices = vertices,
Edges = edges.ToList(),
};
}
/// <summary>
/// This is probably not needed, but might make things a tiny bit faster.
/// </summary>
/// <seealso cref="System.Collections.Generic.IEqualityComparer{TEdge}" />
private class EdgeComparer : IEqualityComparer<TEdge>
{
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <param name="x"> The first object of type <paramref name="T" /> to compare. </param>
/// <param name="y"> The second object of type <paramref name="T" /> to compare. </param>
/// <returns> true if the specified objects are equal; otherwise, false. </returns>
public bool Equals(TEdge x, TEdge y)
{
return (x.Source == y.Source && x.Target == y.Target) || (x.Source == y.Target && x.Target == y.Source);
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <param name="obj"> The <see cref="T:System.Object" /> for which a hash code is to be returned. </param>
/// <returns> A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. </returns>
public int GetHashCode(TEdge obj)
{
return obj.Source.GetHashCode() ^ obj.Target.GetHashCode();
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7967e35aa5e1f4a44a6d126181a5bc69
timeCreated: 1490199238
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using UnityEngine;
#endregion
namespace NWH.DWP2.MiConvexHull
{
/// <summary>
/// Unity-specific vertex implementation for convex hull calculations.
/// </summary>
public class Vertex : IVertex
{
/// <summary>
/// Initializes a new vertex with specified coordinates.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="z">Z coordinate.</param>
public Vertex(double x, double y, double z)
{
Position = new double[3] { x, y, z, };
}
/// <summary>
/// Initializes a new vertex from a Unity Vector3.
/// </summary>
/// <param name="ver">Unity Vector3 position.</param>
public Vertex(Vector3 ver)
{
Position = new double[3] { ver.x, ver.y, ver.z, };
}
/// <summary>
/// Position of the vertex in 3D space.
/// </summary>
public double[] Position { get; set; }
/// <summary>
/// Converts the vertex position to a Unity Vector3.
/// </summary>
/// <returns>Unity Vector3 representation of the vertex position.</returns>
public Vector3 ToVec()
{
return new Vector3((float)Position[0], (float)Position[1], (float)Position[2]);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a7aea071fc0ba4547849896a0f7f2cc2
timeCreated: 1490199448
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 19d46e96be20776428af8cb44d2a5620
folderAsset: yes
timeCreated: 1493253685
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections;
#endregion
namespace NWH.DWP2.MeshDecimation
{
/// <summary>
/// Comparer for sorting vertices by their edge collapse cost during mesh decimation.
/// </summary>
public class Comparer : IComparer
{
private Vert vx;
private Vert vy;
/// <summary>
/// Compares two vertices based on their edge collapse cost.
/// </summary>
/// <param name="x">First vertex to compare.</param>
/// <param name="y">Second vertex to compare.</param>
/// <returns>-1 if x has lower cost, 0 if equal, 1 if x has higher cost.</returns>
public int Compare(object x, object y)
{
vx = (Vert)x;
vy = (Vert)y;
if (vx == vy)
{
return 0;
}
if (vx.cost < vy.cost)
{
return -1;
}
return 1;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1cbe772204b3d5040821696552854402
timeCreated: 1493253685
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,76 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#endregion
namespace NWH.DWP2.MeshDecimation
{
/// <summary>
/// Tracks mesh decimation history for undo/redo operations during progressive mesh simplification.
/// </summary>
public class History
{
/// <summary>
/// Unique identifier for this history entry.
/// </summary>
public int id;
/// <summary>
/// List of triangle indices that were removed during this step.
/// </summary>
public List<int> removedTriangles = new();
/// <summary>
/// List of vertex replacement operations performed during this step.
/// </summary>
public List<ArrayList> replacedVertex = new();
/// <summary>
/// Records a triangle removal operation.
/// </summary>
/// <param name="f">Index of the triangle that was removed.</param>
public void RemovedTriangle(int f)
{
removedTriangles.Add(f);
}
/// <summary>
/// Records a vertex replacement operation including original and new vertex data.
/// </summary>
/// <param name="f">Face index.</param>
/// <param name="u">Original vertex index in the triangle.</param>
/// <param name="v">Vertex ID being replaced.</param>
/// <param name="normal">Original vertex normal.</param>
/// <param name="uv">Original texture coordinates.</param>
/// <param name="newV">New vertex ID.</param>
/// <param name="newNormal">New vertex normal.</param>
/// <param name="newUv">New texture coordinates.</param>
public void ReplaceVertex(int f, int u, int v, Vector3 normal, Vector2 uv, int newV, Vector3 newNormal,
Vector2 newUv)
{
ArrayList list = new();
list.Insert(0, f);
list.Insert(1, u);
list.Insert(2, v);
list.Insert(3, normal);
list.Insert(4, uv);
list.Insert(5, newV);
list.Insert(6, newNormal);
list.Insert(7, newUv);
replacedVertex.Add(list);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 19e51ea37ef473d498bef5c1c4f3ebc5
timeCreated: 1493253685
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,900 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#endregion
// Based on:
// Progressive Mesh type Polygon Reduction Algorithm
// by Stan Melax (c) 1998
// http://www.melax.com/polychop/
namespace NWH.DWP2.MeshDecimation
{
/// <summary>
/// Progressive mesh decimation implementation using edge collapse algorithm.
/// Reduces mesh complexity while preserving overall shape and texture coordinates.
/// </summary>
public class MeshDecimate
{
/// <summary>
/// Whether to recalculate smooth normals after decimation.
/// </summary>
public bool bRecalculateNormals;
/// <summary>
/// Final vertex normals after decimation.
/// </summary>
public Vector3[] finalNormals;
/// <summary>
/// Final triangle indices after decimation.
/// </summary>
public int[] finalTriangles;
/// <summary>
/// Final texture coordinates after decimation.
/// </summary>
public Vector2[] finalUVs;
/// <summary>
/// Final vertex positions after decimation.
/// </summary>
public Vector3[] finalVertices;
/// <summary>
/// Last target vertex count from progressive mesh operation.
/// </summary>
public int lastTarget;
/// <summary>
/// Whether to lock selected vertices from being collapsed.
/// </summary>
public bool lockSelPoint = true;
/// <summary>
/// Size in bytes of LOD history data for progressive mesh.
/// </summary>
public float lodDataSize;
/// <summary>
/// Whether pre-calculation of decimation data has been completed.
/// </summary>
public bool preCalculateDone;
/// <summary>
/// Target ratio of vertices to keep (0.0 to 1.0).
/// </summary>
public float ratio = 0.5f;
/// <summary>
/// List of vertices marked as selected (protected from collapse if locked).
/// </summary>
public List<Vector3> selectedVertices = new();
/// <summary>
/// Smoothing angle threshold in degrees for normal calculation.
/// </summary>
public float smoothAngle = 45.0f;
private List<Vert> cache = new();
private int cacheSize;
private History[] collapseHistory;
private int currentcnt;
private Vert[] myLODVertices;
private Tri[] myTriangles;
private Vector3[] originalNormals;
private int[] originalTriangles;
private Vector2[] originalUVs;
private Vector3[] originalVertices;
private int searchIndex;
private int[] sharedTriangles;
private Vector3[] sharedVertices;
private float smoothAngleDot;
private int[] triOrder;
private float ComputeEdgeCollapseCosts(Vert u, Vert v)
{
int i;
int j;
Tri faceU;
Tri faceV;
float edgelength = (v.position - u.position).sqrMagnitude;
float cost = 0;
// find the "vFaces" triangles that are on the edge uv
List<Tri> vFaces = new();
int uFaceCount = u.face.Count;
for (i = 0; i < uFaceCount; ++i)
{
faceU = u.face[i];
if (faceU.HasVertex(v))
{
vFaces.Add(faceU);
}
}
// use the triangle facing most away from the sides
// to determine our curvature term
int vFaceCount = vFaces.Count;
for (i = 0; i < uFaceCount; ++i)
{
float mindot = 1; // curve for face i and closer side to it
faceU = u.face[i];
Vector3 faceN = faceU.normal;
for (j = 0; j < vFaceCount; ++j)
{
// use dot product of face normals. '^' defined in vector
faceV = vFaces[j];
Vector3 ns = faceV.normal;
float dot = (1 - (faceN.x * ns.x + faceN.y * ns.y + faceN.z * ns.z)) * 0.5f;
if (dot < mindot)
{
mindot = dot;
}
}
if (mindot > cost)
{
cost = mindot;
}
}
if (u.IsBorder() && vFaceCount > 1)
{
cost = 1.0f;
}
// texture UV check
// if neighbor face has different uv
// means that shouldn't be collapsed.
// set its priority as higher cost.
int found = 0;
for (i = 0; i < uFaceCount; ++i)
{
faceU = u.face[i];
Vector2 uv = faceU.uvAt(u);
for (j = 0; j < vFaceCount; ++j)
{
faceV = vFaces[j];
if (uv == faceV.uvAt(u))
{
break;
}
}
if (j == vFaceCount)
{
++found;
}
}
// all neighbor faces share same uv
// so set u as higher cost.
if (found > 0)
{
cost = 1.0f;
}
if (u.selected && lockSelPoint)
{
cost = 6553.5f;
}
// the more coplanar the lower the curvature term
// cost 0 means u and v are on the same plane.
return edgelength * cost;
}
private void ComputeEdgeCostAtVertex(Vert v)
{
// compute the edge collapse cost for all edges that start
// from vertex v. Since we are only interested in reducing
// the object by selecting the min cost edge at each step, we
// only cache the cost of the least cost edge at this vertex
// (in member variable collapse) as well as the value of the
// cost (in member variable cost).
if (v.neighbor.Count == 0)
{
// v doesn't have neighbors so it costs nothing to collapse
v.collapse = null;
v.cost = 0; //-0.01f;
return;
}
v.cost = 65535;
v.collapse = null;
// search all neighboring edges for "least cost" edge
int neighborCount = v.neighbor.Count;
float cost;
for (int i = 0; i < neighborCount; ++i)
{
cost = ComputeEdgeCollapseCosts(v, v.neighbor[i]);
if (cost < v.cost)
{
v.collapse = v.neighbor[i]; // candidate for edge collapse
v.cost = cost; // cost of the collapse
}
}
}
private void ComputeAllEdgeCollapseCosts()
{
// For all the edges, compute the difference it would make
// to the model if it was collapsed. The least of these
// per vertex is cached in each vertex object.
int count = myLODVertices.Length;
for (int i = 0; i < count; ++i)
{
Vert v = myLODVertices[i];
ComputeEdgeCostAtVertex(v);
cache.Insert(i, v);
}
}
private void UnCollapse(History his)
{
int i;
int n;
Tri t;
List<int> l = his.removedTriangles;
n = l.Count;
for (i = 0; i < n; ++i)
{
myTriangles[l[i]].deleted = false;
}
List<ArrayList> list = his.replacedVertex;
n = list.Count;
for (i = 0; i < n; ++i)
{
ArrayList tmp = list[i];
t = myTriangles[(int)tmp[0]];
int changedIndex = (int)tmp[1];
if (changedIndex == 0)
{
t.v0 = myLODVertices[(int)tmp[2]];
t.vn0 = (Vector3)tmp[3];
t.uv0 = (Vector2)tmp[4];
}
else if (changedIndex == 1)
{
t.v1 = myLODVertices[(int)tmp[2]];
t.vn1 = (Vector3)tmp[3];
t.uv1 = (Vector2)tmp[4];
}
else
{
t.v2 = myLODVertices[(int)tmp[2]];
t.vn2 = (Vector3)tmp[3];
t.uv2 = (Vector2)tmp[4];
}
}
}
private void Collapse(History his)
{
int i;
int n;
Tri t;
List<int> l = his.removedTriangles;
n = l.Count;
for (i = 0; i < n; ++i)
{
myTriangles[l[i]].deleted = true;
}
List<ArrayList> list = his.replacedVertex;
n = list.Count;
for (i = 0; i < n; ++i)
{
ArrayList tmp = list[i];
t = myTriangles[(int)tmp[0]];
int changedIndex = (int)tmp[1];
if (changedIndex == 0)
{
t.v0 = myLODVertices[(int)tmp[5]];
t.vn0 = (Vector3)tmp[6];
t.uv0 = (Vector2)tmp[7];
}
else if (changedIndex == 1)
{
t.v1 = myLODVertices[(int)tmp[5]];
t.vn1 = (Vector3)tmp[6];
t.uv1 = (Vector2)tmp[7];
}
else
{
t.v2 = myLODVertices[(int)tmp[5]];
t.vn2 = (Vector3)tmp[6];
t.uv2 = (Vector2)tmp[7];
}
}
}
private void CollapseTest()
{
Vert u = cache[searchIndex++];
Vert v = u.collapse;
// which Vert will be collapsed.
History his = new();
collapseHistory[currentcnt - 1] = his;
// u is a vertex all by itself so just delete it
if (v != null && v.deleted)
{
u.RemoveVert();
return;
}
if (v == null)
{
u.RemoveVert();
return;
}
int i;
int j;
Tri uFace;
Tri vFace;
int vFaceCount;
int neighborCount = u.neighbor.Count;
Vert[] neighbors = new Vert[neighborCount];
int count = u.face.Count;
// make tmp a list of all the neighbors of u
for (i = 0; i < neighborCount; ++i)
{
neighbors[i] = u.neighbor[i];
}
// make a list and add face to the list if it has v.
List<Tri> vFaces = new();
for (i = 0; i < count; ++i)
{
uFace = u.face[i];
if (uFace.HasVertex(v))
{
vFaces.Add(uFace);
}
}
vFaceCount = vFaces.Count;
// delete triangles on edge uv:
for (i = u.face.Count - 1; i >= 0; --i)
{
try
{
uFace = u.face[i];
if (uFace.HasVertex(v))
{
uFace.RemoveTriangle(his);
}
}
catch
{
}
}
// update remaining triangles to have v instead of u
Vector2 u_uv;
Vector2 foundUV = new();
Vector3 foundVN = new();
for (i = u.face.Count - 1; i >= 0; --i)
{
uFace = u.face[i];
if (!uFace.deleted)
{
u_uv = uFace.uvAt(u);
for (j = 0; j < vFaceCount; ++j)
{
vFace = vFaces[j];
if (u_uv == vFace.uvAt(u))
{
foundUV = vFace.uvAt(v);
foundVN = vFace.normalAt(v);
break;
}
}
uFace.ReplaceVertex(u, v, foundUV, foundVN, his);
}
}
u.RemoveVert();
// recompute the edge collapse costs in neighborhood
Vert neighbor;
float oldCost;
for (i = 0; i < neighborCount; ++i)
{
neighbor = neighbors[i];
oldCost = neighbor.cost;
ComputeEdgeCostAtVertex(neighbor);
if (oldCost > neighbor.cost)
{
SortLeft(neighbor);
}
else
{
SortRight(neighbor);
}
}
}
private void SortRight(Vert v)
{
int cacheIndex = cache.IndexOf(v);
if (cacheIndex == cacheSize - 1)
{
return;
}
float cost = v.cost;
Vert c2 = cache[cacheIndex + 1];
if (cost == c2.cost)
{
return;
}
int maxIndex = cacheSize - 2;
while (cost > c2.cost && cacheIndex < maxIndex)
{
cache[cacheIndex++] = c2;
c2 = cache[cacheIndex + 1];
}
if (cost > c2.cost)
{
cache[cacheIndex++] = c2;
}
cache[cacheIndex] = v;
}
private void SortLeft(Vert v)
{
int cacheIndex = cache.IndexOf(v);
if (cacheIndex == searchIndex)
{
return;
}
float cost = v.cost;
Vert c2 = cache[cacheIndex - 1];
if (cost == c2.cost)
{
return;
}
while (cost < c2.cost && cacheIndex > searchIndex + 2)
{
cache[cacheIndex--] = c2;
c2 = cache[cacheIndex - 1];
}
if (cost < c2.cost)
{
cache[cacheIndex--] = c2;
}
cache[cacheIndex] = v;
}
/// <summary>
/// Pre-calculates all edge collapse operations and builds the progressive mesh history.
/// Must be called once before Calculate can be used.
/// </summary>
/// <param name="tmpMesh">Input mesh to decimate.</param>
public void PreCalculate(Mesh tmpMesh)
{
int i;
int j;
smoothAngleDot = 1 - smoothAngle / 90.0f;
int[] tris = tmpMesh.triangles;
originalTriangles = tmpMesh.triangles;
originalVertices = tmpMesh.vertices;
if (tmpMesh.uv.Length > 0)
{
originalUVs = tmpMesh.uv;
}
else
{
List<Vector2> uvs = new();
foreach (Vector2 nr in tmpMesh.normals)
{
uvs.Add(nr);
}
originalUVs = uvs.ToArray();
}
originalNormals = tmpMesh.normals;
int triNum = tris.Length;
int vertNum = originalVertices.Length;
List<Vector3> newVertices = new();
int n;
int foundAt = -1;
int indice;
Vector3 v;
for (i = 0; i < triNum; ++i)
{
indice = tris[i];
v = originalVertices[indice];
n = newVertices.Count;
foundAt = -1;
for (j = 0; j < n; ++j)
{
if (newVertices[j] == v)
{
foundAt = j;
break;
}
}
if (foundAt != -1)
{
tris[i] = foundAt;
}
else
{
tris[i] = n;
newVertices.Insert(n, v);
}
}
sharedTriangles = tris;
sharedVertices = newVertices.ToArray();
myTriangles = new Tri[sharedTriangles.Length / 3];
myLODVertices = new Vert[sharedVertices.Length];
ComputeProgressiveMesh();
preCalculateDone = true;
// calculate triangle remove order
triOrder = new int[myTriangles.Length];
n = collapseHistory.Length;
int cnt = 0;
for (i = 0; i < n; ++i)
{
History his = collapseHistory[i];
List<int> list = his.removedTriangles;
int m = list.Count;
for (j = 0; j < m; ++j)
{
triOrder[cnt++] = list[j];
}
}
}
/// <summary>
/// Calculates the decimated mesh at the current ratio.
/// Applies progressive mesh simplification and outputs final geometry arrays.
/// PreCalculate must be called first.
/// </summary>
/// <param name="tmpMesh">Mesh reference (used for validation).</param>
public void Calculate(Mesh tmpMesh)
{
ProgressiveMesh(ratio);
int i;
int j;
int foundAt = -1;
Vector3 v = new();
Vector3 vn = new();
Vector3 dvn = new();
Vector2 vuv = new();
//History his = new History();
int cnt = 0;
int vertsCount = myLODVertices.Length;
int trisCount = myTriangles.Length;
int reducedTriCount = 0;
foreach (Tri t in myTriangles)
{
if (t.deleted)
{
continue;
}
++reducedTriCount;
}
int minTriCount = reducedTriCount * 3;
int[] tris = new int[minTriCount];
Vector3[] verts = new Vector3[minTriCount];
Vector2[] uvs = new Vector2[minTriCount];
Vector3[] norms = new Vector3[minTriCount];
int[] indices = new int[minTriCount];
for (i = 0; i < reducedTriCount; ++i)
{
Tri tri = myTriangles[triOrder[i]];
int cnt1 = cnt + 1;
int cnt2 = cnt + 2;
Vert v0 = tri.v0;
Vert v1 = tri.v1;
Vert v2 = tri.v2;
verts[cnt] = v0.position;
verts[cnt1] = v1.position;
verts[cnt2] = v2.position;
tris[cnt] = cnt;
tris[cnt1] = cnt1;
tris[cnt2] = cnt2;
uvs[cnt] = tri.uv0;
uvs[cnt1] = tri.uv1;
uvs[cnt2] = tri.uv2;
norms[cnt] = tri.vn0;
norms[cnt1] = tri.vn1;
norms[cnt2] = tri.vn2;
indices[cnt] = tri.defaultIndex0;
indices[cnt1] = tri.defaultIndex1;
indices[cnt2] = tri.defaultIndex2;
cnt += 3;
}
int triNum = tris.Length;
List<Vector3> newVertices = new();
List<Vector2> newUVs = new();
List<Vector3> newNormals = new();
List<Vector3> newDNormals = new();
if (bRecalculateNormals)
{
for (i = 0; i < triNum; ++i)
{
v = verts[i];
vuv = uvs[i];
vn = norms[i];
int n = newVertices.Count;
foundAt = -1;
for (j = 0; j < n; ++j)
{
if (newVertices[j] == v && newUVs[j] == vuv &&
Vector3.Dot(newNormals[j], vn) > smoothAngleDot)
{
foundAt = j;
break;
}
}
if (foundAt != -1)
{
tris[i] = foundAt;
}
else
{
tris[i] = n;
newVertices[n] = v;
newUVs[n] = vuv;
newNormals[n] = vn;
newDNormals[n] = dvn;
}
}
}
else
{
for (i = 0; i < triNum; ++i)
{
v = verts[i];
vuv = uvs[i];
vn = norms[i];
dvn = originalNormals[indices[i]];
int n = newVertices.Count;
foundAt = -1;
for (j = 0; j < n; ++j)
{
if (newVertices[j] == v && newUVs[j] == vuv && newDNormals[j] == dvn)
{
foundAt = j;
break;
}
}
if (foundAt != -1)
{
tris[i] = foundAt;
}
else
{
tris[i] = n;
newVertices.Insert(n, v);
newUVs.Insert(n, vuv);
newNormals.Insert(n, vn);
newDNormals.Insert(n, dvn);
}
}
}
finalVertices = newVertices.ToArray();
finalNormals = newNormals.ToArray();
finalUVs = newUVs.ToArray();
finalTriangles = tris;
}
private void ComputeProgressiveMesh()
{
int i;
int j;
int n;
Tri t;
int vertexCount = sharedVertices.Length;
int triangleCount = sharedTriangles.Length;
Array.Clear(myLODVertices, 0, myLODVertices.Length);
for (i = 0; i < vertexCount; ++i)
{
Vector3 dv = sharedVertices[i];
bool sel = false;
n = selectedVertices.Count;
for (j = 0; j < n; ++j)
{
if (selectedVertices[j] == dv)
{
sel = true;
break;
}
}
myLODVertices[i] = new Vert(dv, i, sel);
}
// new myTris
Array.Clear(myTriangles, 0, myTriangles.Length);
int cnt = 0;
for (i = 0; i < triangleCount; i += 3)
{
t = new Tri(cnt,
myLODVertices[sharedTriangles[i]],
myLODVertices[sharedTriangles[i + 1]],
myLODVertices[sharedTriangles[i + 2]],
originalUVs[originalTriangles[i]],
originalUVs[originalTriangles[i + 1]],
originalUVs[originalTriangles[i + 2]]);
t.SetDefaultIndices(originalTriangles[i], originalTriangles[i + 1], originalTriangles[i + 2]);
if (bRecalculateNormals)
{
t.vn0 = t.vn1 = t.vn2 = t.normal;
}
else
{
t.vn0 = originalNormals[originalTriangles[i]];
t.vn1 = originalNormals[originalTriangles[i + 1]];
t.vn2 = originalNormals[originalTriangles[i + 2]];
}
myTriangles[cnt] = t;
++cnt;
}
cache = new List<Vert>();
cacheSize = vertexCount;
if (bRecalculateNormals)
{
RecalculateNormal(); // set normals for vertex.
}
ComputeAllEdgeCollapseCosts(); // cache all edge collapse costs
//System.Array.Sort(cache, myComparer); // lower cost to the left.
cache = cache.OrderBy(p => p.cost).ToList();
collapseHistory = new History[vertexCount];
currentcnt = myLODVertices.Length + 1;
searchIndex = 0;
while (--currentcnt > 0)
{
CollapseTest();
}
// LOD Data size calculation
n = collapseHistory.Length;
int tmpBytes = 0;
History tmpHis;
for (i = 0; i < n; ++i)
{
tmpHis = collapseHistory[i];
tmpBytes += (
tmpHis.removedTriangles.Count * 2 +
tmpHis.replacedVertex.Count * 14
) * 4;
}
lodDataSize = tmpBytes;
}
private void ProgressiveMesh(float ratio)
{
int i;
int target = Mathf.FloorToInt(ratio * sharedVertices.Length);
if (lastTarget < target)
{
for (i = lastTarget; i < target; ++i)
{
UnCollapse(collapseHistory[i]);
}
}
else
{
for (i = lastTarget - 1; i >= target; --i)
{
Collapse(collapseHistory[i]);
}
}
lastTarget = target;
}
private void RecalculateNormal()
{
int n = myTriangles.Length;
for (int i = 0; i < n; ++i)
{
Tri f = myTriangles[i];
if (f.deleted)
{
continue;
}
f.RecalculateAvgNormals(smoothAngleDot);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9ef7548c9b42f29448ab0c06b6c82feb
timeCreated: 1493253685
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,447 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections.Generic;
using UnityEngine;
#endregion
namespace NWH.DWP2.MeshDecimation
{
/// <summary>
/// Represents a triangle in the mesh decimation algorithm.
/// Stores triangle vertices, normals, UVs, and adjacency information.
/// </summary>
public class Tri
{
/// <summary>
/// Original mesh vertex index for vertex 0.
/// </summary>
public int defaultIndex0;
/// <summary>
/// Original mesh vertex index for vertex 1.
/// </summary>
public int defaultIndex1;
/// <summary>
/// Original mesh vertex index for vertex 2.
/// </summary>
public int defaultIndex2;
/// <summary>
/// Whether this triangle has been deleted.
/// </summary>
public bool deleted;
/// <summary>
/// Unique identifier for this triangle.
/// </summary>
public int id;
/// <summary>
/// Face normal vector.
/// </summary>
public Vector3 normal;
/// <summary>
/// Texture coordinate for vertex 0.
/// </summary>
public Vector2 uv0;
/// <summary>
/// Texture coordinate for vertex 1.
/// </summary>
public Vector2 uv1;
/// <summary>
/// Texture coordinate for vertex 2.
/// </summary>
public Vector2 uv2;
/// <summary>
/// First vertex of the triangle.
/// </summary>
public Vert v0;
/// <summary>
/// Second vertex of the triangle.
/// </summary>
public Vert v1;
/// <summary>
/// Third vertex of the triangle.
/// </summary>
public Vert v2;
/// <summary>
/// Vertex normal for vertex 0.
/// </summary>
public Vector3 vn0;
/// <summary>
/// Vertex normal for vertex 1.
/// </summary>
public Vector3 vn1;
/// <summary>
/// Vertex normal for vertex 2.
/// </summary>
public Vector3 vn2;
/// <summary>
/// Initializes a new triangle with vertices, texture coordinates, and establishes vertex relationships.
/// </summary>
/// <param name="id">Unique identifier for this triangle.</param>
/// <param name="v0">First vertex.</param>
/// <param name="v1">Second vertex.</param>
/// <param name="v2">Third vertex.</param>
/// <param name="uv0">Texture coordinate for first vertex.</param>
/// <param name="uv1">Texture coordinate for second vertex.</param>
/// <param name="uv2">Texture coordinate for third vertex.</param>
public Tri(int id, Vert v0, Vert v1, Vert v2, Vector2 uv0, Vector2 uv1, Vector2 uv2)
{
this.id = id;
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.uv0 = uv0;
this.uv1 = uv1;
this.uv2 = uv2;
RecalculateNormal();
v0.AddFace(this);
v1.AddFace(this);
v2.AddFace(this);
v0.AddNeighbor(v1);
v0.AddNeighbor(v2);
v1.AddNeighbor(v0);
v1.AddNeighbor(v2);
v2.AddNeighbor(v0);
v2.AddNeighbor(v1);
}
/// <summary>
/// Sets the original mesh vertex indices for this triangle.
/// </summary>
/// <param name="n0">Original index for vertex 0.</param>
/// <param name="n1">Original index for vertex 1.</param>
/// <param name="n2">Original index for vertex 2.</param>
public void SetDefaultIndices(int n0, int n1, int n2)
{
defaultIndex0 = n0;
defaultIndex1 = n1;
defaultIndex2 = n2;
}
/// <summary>
/// Removes this triangle and updates vertex relationships.
/// Records the removal in history for potential undo operations.
/// </summary>
/// <param name="his">History object to record the removal.</param>
public void RemoveTriangle(History his)
{
v0.RemoveFace(this);
v1.RemoveFace(this);
v2.RemoveFace(this);
v0.RemoveIfNonNeighbor(v1);
v0.RemoveIfNonNeighbor(v2);
v1.RemoveIfNonNeighbor(v0);
v1.RemoveIfNonNeighbor(v2);
v2.RemoveIfNonNeighbor(v1);
v2.RemoveIfNonNeighbor(v0);
deleted = true;
his.RemovedTriangle(id);
}
/// <summary>
/// Gets the texture coordinate for a given vertex.
/// </summary>
/// <param name="v">Vertex to query.</param>
/// <returns>Texture coordinate for the vertex, or zero vector if not found.</returns>
public Vector2 uvAt(Vert v)
{
Vector3 vec = v.position;
if (vec == v0.position)
{
return uv0;
}
if (vec == v1.position)
{
return uv1;
}
if (vec == v2.position)
{
return uv2;
}
return new Vector2();
}
/// <summary>
/// Gets the vertex normal for a given vertex.
/// </summary>
/// <param name="v">Vertex to query.</param>
/// <returns>Vertex normal for the vertex, or zero vector if not found.</returns>
public Vector3 normalAt(Vert v)
{
Vector3 vec = v.position;
if (vec == v0.position)
{
return vn0;
}
if (vec == v1.position)
{
return vn1;
}
if (vec == v2.position)
{
return vn2;
}
return new Vector3();
}
/// <summary>
/// Sets the texture coordinate for a given vertex.
/// </summary>
/// <param name="v">Vertex to update.</param>
/// <param name="newuv">New texture coordinate.</param>
public void setUV(Vert v, Vector2 newuv)
{
Vector3 vec = v.position;
if (vec == v0.position)
{
uv0 = newuv;
}
else if (vec == v1.position)
{
uv1 = newuv;
}
else if (vec == v2.position)
{
uv2 = newuv;
}
}
/// <summary>
/// Sets the vertex normal for a given vertex.
/// </summary>
/// <param name="v">Vertex to update.</param>
/// <param name="newNormal">New vertex normal.</param>
public void setVN(Vert v, Vector3 newNormal)
{
Vector3 vec = v.position;
if (vec == v0.position)
{
vn0 = newNormal;
}
else if (vec == v1.position)
{
vn1 = newNormal;
}
else if (vec == v2.position)
{
vn2 = newNormal;
}
}
/// <summary>
/// Checks if this triangle contains a given vertex.
/// </summary>
/// <param name="v">Vertex to check.</param>
/// <returns>True if the triangle contains the vertex, false otherwise.</returns>
public bool HasVertex(Vert v)
{
Vector3 vec = v.position;
return vec == v0.position || vec == v1.position || vec == v2.position;
}
/// <summary>
/// Recalculates the face normal using cross product of edge vectors.
/// </summary>
public void RecalculateNormal()
{
Vector3 v1pos = v1.position;
normal = Vector3.Cross(v1pos - v0.position, v2.position - v1pos);
if (normal.magnitude == 0)
{
return;
}
normal.Normalize();
}
/// <summary>
/// Recalculates averaged vertex normals based on adjacent face normals.
/// Only called when normal recalculation is enabled. Smooths normals even at UV seams.
/// </summary>
/// <param name="smoothAngleDot">Dot product threshold for normal smoothing.</param>
public void RecalculateAvgNormals(float smoothAngleDot)
{
int i;
List<Tri> flist = new();
List<Tri> slist = new();
int n = flist.Count;
Tri f;
Vector3 fn;
flist = v0.face;
slist.Clear();
for (i = 0; i < n; ++i)
{
f = flist[i];
fn = f.normal;
if (fn.x * normal.x + fn.y * normal.y + fn.z * normal.z > smoothAngleDot)
{
vn0 += fn;
slist.Add(f);
}
}
vn0.Normalize();
n = slist.Count;
for (i = 0; i < n; ++i)
{
f = slist[i];
f.setVN(v0, vn0);
}
flist = v1.face;
n = flist.Count;
slist.Clear();
for (i = 0; i < n; ++i)
{
f = flist[i];
fn = f.normal;
if (fn.x * normal.x + fn.y * normal.y + fn.z * normal.z > smoothAngleDot)
{
vn1 += fn;
slist.Add(f);
}
}
vn1.Normalize();
n = slist.Count;
for (i = 0; i < n; ++i)
{
f = slist[i];
f.setVN(v1, vn1);
}
flist = v2.face;
n = flist.Count;
slist.Clear();
for (i = 0; i < n; ++i)
{
f = flist[i];
fn = f.normal;
if (fn.x * normal.x + fn.y * normal.y + fn.z * normal.z > smoothAngleDot)
{
vn2 += fn;
slist.Add(f);
}
}
vn2.Normalize();
n = slist.Count;
for (i = 0; i < n; ++i)
{
f = slist[i];
f.setVN(v2, vn2);
}
}
/// <summary>
/// Replaces a vertex in this triangle with a new vertex and updates all relationships.
/// Records the replacement in history for potential undo operations.
/// </summary>
/// <param name="vo">Old vertex to be replaced.</param>
/// <param name="vnew">New vertex to replace with.</param>
/// <param name="newUV">New texture coordinate.</param>
/// <param name="newVN">New vertex normal.</param>
/// <param name="his">History object to record the replacement.</param>
public void ReplaceVertex(Vert vo, Vert vnew, Vector2 newUV, Vector3 newVN, History his)
{
Vector3 vec = vo.position;
Vert changedVertex = v2;
int changedVertexId = 2;
Vector3 changedNormal = vn2;
Vector2 changedUV = uv2;
if (vec == v0.position)
{
changedVertex = v0;
changedVertexId = 0;
changedNormal = vn0;
changedUV = uv0;
v0 = vnew;
vn0 = newVN;
uv0 = newUV;
}
else if (vec == v1.position)
{
changedVertex = v1;
changedVertexId = 1;
changedNormal = vn1;
changedUV = uv1;
v1 = vnew;
vn1 = newVN;
uv1 = newUV;
}
else
{
v2 = vnew;
vn2 = newVN;
uv2 = newUV;
}
vo.RemoveFace(this);
vnew.AddFace(this);
vo.RemoveIfNonNeighbor(v0);
v0.RemoveIfNonNeighbor(vo);
vo.RemoveIfNonNeighbor(v1);
v1.RemoveIfNonNeighbor(vo);
vo.RemoveIfNonNeighbor(v2);
v2.RemoveIfNonNeighbor(vo);
v0.AddNeighbor(v1);
v0.AddNeighbor(v2);
v1.AddNeighbor(v0);
v1.AddNeighbor(v2);
v2.AddNeighbor(v0);
v2.AddNeighbor(v1);
RecalculateNormal();
his.ReplaceVertex(id, changedVertexId, changedVertex.id, changedNormal, changedUV, vnew.id, newVN, newUV);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7f2030e27607db841ac51b6972665e52
timeCreated: 1493253685
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,221 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections.Generic;
using UnityEngine;
#endregion
namespace NWH.DWP2.MeshDecimation
{
/// <summary>
/// Represents a vertex in the mesh decimation algorithm.
/// Stores vertex data and relationships for edge collapse operations.
/// </summary>
public class Vert
{
/// <summary>
/// Target vertex for edge collapse operation.
/// </summary>
public Vert collapse;
/// <summary>
/// Edge collapse cost for this vertex.
/// </summary>
public float cost;
/// <summary>
/// Whether this vertex has been deleted.
/// </summary>
public bool deleted;
/// <summary>
/// List of triangles that use this vertex.
/// </summary>
public List<Tri> face = new();
/// <summary>
/// Unique identifier for this vertex.
/// </summary>
public int id;
/// <summary>
/// List of neighboring vertices connected by edges.
/// </summary>
public List<Vert> neighbor = new();
/// <summary>
/// 3D position of the vertex.
/// </summary>
public Vector3 position;
/// <summary>
/// Whether this vertex is marked as selected (prevents collapse if locked).
/// </summary>
public bool selected;
/// <summary>
/// Initializes a new vertex with position, ID, and selection status.
/// </summary>
/// <param name="position">3D position of the vertex.</param>
/// <param name="id">Unique identifier.</param>
/// <param name="selected">Whether the vertex is selected.</param>
public Vert(Vector3 position, int id, bool selected)
{
this.position = position;
this.id = id;
this.selected = selected;
cost = 0f;
collapse = null;
}
/// <summary>
/// Removes this vertex and breaks all neighbor connections.
/// </summary>
public void RemoveVert()
{
Vert nb;
while (neighbor.Count > 0)
{
nb = neighbor[0];
nb.neighbor.Remove(this);
neighbor.Remove(nb);
}
deleted = true;
}
/// <summary>
/// Checks if this vertex is on the mesh border.
/// A vertex is on the border if any of its edges is shared by only one triangle.
/// </summary>
/// <returns>True if the vertex is on the border, false otherwise.</returns>
public bool IsBorder()
{
int j;
int n = neighbor.Count;
Vert nb;
int face_len;
Tri f;
int count = 0;
for (int i = 0; i < n; ++i)
{
count = 0;
nb = neighbor[i];
face_len = face.Count;
for (j = 0; j < face_len; ++j)
{
f = face[j];
if (f.HasVertex(nb))
{
++count;
}
}
if (count == 1)
{
return true;
}
}
return false;
}
/// <summary>
/// Adds a triangle to this vertex's face list.
/// </summary>
/// <param name="f">Triangle to add.</param>
public void AddFace(Tri f)
{
face.Add(f);
}
/// <summary>
/// Removes a triangle from this vertex's face list.
/// </summary>
/// <param name="f">Triangle to remove.</param>
public void RemoveFace(Tri f)
{
face.Remove(f);
}
/// <summary>
/// Adds a vertex to the neighbor list if not already present.
/// </summary>
/// <param name="v">Vertex to add as neighbor.</param>
public void AddNeighbor(Vert v)
{
int i;
int foundAt = -1;
int n = neighbor.Count;
for (i = 0; i < n; ++i)
{
if (neighbor[i] == v)
{
foundAt = i;
break;
}
}
if (foundAt == -1)
{
neighbor.Add(v);
}
}
/// <summary>
/// Removes a vertex from the neighbor list if they no longer share any faces.
/// </summary>
/// <param name="v">Vertex to potentially remove from neighbors.</param>
public void RemoveIfNonNeighbor(Vert v)
{
int i;
int foundAt = -1;
int n = neighbor.Count;
Tri f;
for (i = 0; i < n; ++i)
{
if (neighbor[i] == v)
{
foundAt = i;
break;
}
}
if (foundAt == -1)
{
return;
}
n = face.Count;
for (i = 0; i < n; ++i)
{
f = face[i];
if (f.HasVertex(v))
{
return;
}
}
neighbor.Remove(v);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b4d9a59a9ba4f0049a08fd05de710c61
timeCreated: 1493253685
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: