OldBlueWater/BlueWater/Assets/NWH/Dynamic Water Physics 2/Scripts/Lib/MIConvexHull/ConvexHull/MathHelper.cs

528 lines
19 KiB
C#
Raw Normal View History

2023-12-19 02:31:29 +00:00
/******************************************************************************
*
* 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.
*
*****************************************************************************/
using System;
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
}
}