/****************************************************************************** * * 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 { /// /// 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. /// internal class MathHelper { /// /// The dimension /// private readonly int Dimension; /// /// The matrix pivots /// private readonly int[] matrixPivots; /// /// The n d matrix /// private readonly double[] nDMatrix; /// /// The n d normal helper vector /// private readonly double[] nDNormalHelperVector; /// /// The nt x /// private readonly double[] ntX; /// /// The nt y /// private readonly double[] ntY; /// /// The nt z /// private readonly double[] ntZ; /// /// The position data /// private readonly double[] PositionData; /// /// Initializes a new instance of the class. /// /// The dimension. /// The positions. 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]; } /// /// Calculates the normal and offset of the hyper-plane given by the face's vertices. /// /// The face. /// The center. /// true if XXXX, false otherwise. 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; } /// /// Check if the vertex is "visible" from the face. /// The vertex is "over face" if the return value is > Constants.PlaneDistanceTolerance. /// /// The v. /// The f. /// The vertex is "over face" if the result is positive. 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; } /// /// Returns the vector the between vertices. /// /// From index. /// To index. /// The target. /// internal double[] VectorBetweenVertices(int toIndex, int fromIndex) { double[] target = new double[Dimension]; VectorBetweenVertices(toIndex, fromIndex, target); return target; } /// /// Returns the vector the between vertices. /// /// From index. /// To index. /// The target. /// 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 /// /// Lus the factor. /// /// The data. /// The order. /// The ipiv. /// The vec l ucolj. 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 /// /// Finds normal vector of a hyper-plane given by vertices. /// Stores the results to normalData. /// /// The vertices. /// The normal data. 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; } } /// /// Finds 2D normal vector. /// /// The vertices. /// The normal. 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; } /// /// Finds 3D normal vector. /// /// The vertices. /// The normal. 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; } /// /// Finds 4D normal vector. /// /// The vertices. /// The normal. 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; } /// /// Finds the normal vector nd. /// /// The vertices. /// The normal. 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 /// /// 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. /// /// The edge vectors. /// The last index. /// 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)); } /// /// Determinants the destructive. /// /// The buff. /// System.Double. 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 } }