aboutsummaryrefslogtreecommitdiff
path: root/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp
diff options
context:
space:
mode:
authorgit perforce import user <a@b>2016-10-25 12:29:14 -0600
committerSheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees>2016-10-25 18:56:37 -0500
commit3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch)
treefa6485c169e50d7415a651bf838f5bcd0fd3bfbd /APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp
downloadphysx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz
physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip
Initial commit:
PhysX 3.4.0 Update @ 21294896 APEX 1.4.0 Update @ 21275617 [CL 21300167]
Diffstat (limited to 'APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp')
-rw-r--r--APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp b/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp
new file mode 100644
index 00000000..f8b87cec
--- /dev/null
+++ b/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * NVIDIA CORPORATION and its licensors retain all intellectual property
+ * and proprietary rights in and to this software, related documentation
+ * and any modifications thereto. Any use, reproduction, disclosure or
+ * distribution of this software and related documentation without an express
+ * license agreement from NVIDIA CORPORATION is strictly prohibited.
+ */
+
+#include "ApexUsingNamespace.h"
+#include "PxSimpleTypes.h"
+#include "PxErrorCallback.h"
+
+#include "authoring/ApexCSGHull.h"
+#include "authoring/ApexCSGSerialization.h"
+#include "ApexSharedSerialization.h"
+#include <stdio.h>
+
+#ifndef WITHOUT_APEX_AUTHORING
+
+#define EPS ((Real)1.0e-6)
+
+#define SOFT_ASSERT(x) //PX_ASSERT(x)
+
+using namespace nvidia;
+
+
+/* Local utilities */
+
+static int compareEdgeFirstFaceIndices(const void* a, const void* b)
+{
+ return (int)((const ApexCSG::Hull::Edge*)a)->m_indexF1 - (int)((const ApexCSG::Hull::Edge*)b)->m_indexF1;
+}
+
+
+/* Hull */
+
+void ApexCSG::Hull::intersect(const ApexCSG::Plane& plane, ApexCSG::Real distanceTol)
+{
+ if (isEmptySet())
+ {
+ return;
+ }
+
+ if (isAllSpace())
+ {
+ Plane& newFace = faces.insert();
+ newFace = plane;
+ allSpace = false;
+ return;
+ }
+
+ const Dir planeNormal = plane.normal();
+
+ if (edges.size() == 0)
+ {
+ PX_ASSERT(vectors.size() == 0);
+ PX_ASSERT(faces.size() == 1 || faces.size() == 2);
+
+ bool addFace = true;
+ const uint32_t newFaceIndex = faces.size();
+
+ // Nothing but a plane or two.
+ for (uint32_t i = 0; i < newFaceIndex; ++i)
+ {
+ Plane& faceI = faces[i];
+ const Dir normalI = faceI.normal();
+ Dir edgeDir = normalI ^ planeNormal;
+ const Real e2 = edgeDir | edgeDir; // = 1-cosAngle*cosAngle
+ const Real cosAngle = normalI | planeNormal;
+ if (e2 < EPS * EPS)
+ {
+ if (cosAngle > 0)
+ {
+ // Parallel case
+ if (plane.d() <= faceI.d())
+ {
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+ return; // Plane is outside of an existing plane
+ }
+ faceI = plane; // Plane is inside an existing plane - replace
+ addFace = false;
+ }
+ else
+ {
+ // Antiparallel case
+ if (-plane.d() < faceI.d() + distanceTol)
+ {
+ setToEmptySet(); // Halfspaces are mutually exclusive
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Intersecting case - add an edge
+ const Real recipE2 = (Real)1 / e2;
+ edgeDir *= physx::PxSqrt(recipE2);
+ const Pos orig = Pos((Real)0) + (recipE2 * (faceI.d() * cosAngle - plane.d())) * planeNormal + (recipE2 * (plane.d() * cosAngle - faceI.d())) * normalI;
+ if (vectors.size() == 0)
+ {
+ vectors.resize(newFaceIndex + 1);
+ vectors[newFaceIndex] = edgeDir;
+ }
+ vectors[i] = orig;
+ Edge& newEdge = edges.insert();
+ newEdge.m_indexV0 = i;
+ newEdge.m_indexV1 = newFaceIndex; // We will have as many edges as old faces. This will be the edge direction vector index.
+ if (i == 0)
+ {
+ newEdge.m_indexF1 = i;
+ newEdge.m_indexF2 = newFaceIndex;
+ }
+ else
+ {
+ // Keep the orientation and edge directions the same
+ newEdge.m_indexF1 = newFaceIndex;
+ newEdge.m_indexF2 = i;
+ }
+ }
+ }
+
+ if (addFace)
+ {
+ Plane& newFace = faces.insert();
+ newFace = plane;
+ }
+
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+ return;
+ }
+
+ // Hull has edges. If they are lines, and parallel to the plane, this will continue to be the case.
+ if (vertexCount == 0)
+ {
+ PX_ASSERT(vectors.size() >= 2);
+ const Dir edgeDir = getDir(edges[0]); // All line edges will be in the same direction
+ PX_ASSERT(vectors[vectors.size() - 1][3] == (Real)0); // The last vector should be a direction (the edge direction);
+ const Real den = edgeDir | planeNormal;
+ if (physx::PxAbs(den) <= EPS)
+ {
+ // Lines are parallel to plane
+ // This is essentially a 2-d algorithm, edges->vertices, faces->edges
+ bool negClassEmpty = true;
+ bool posClassEmpty = true;
+ int8_t* edgeClass = (int8_t*)PxAlloca((edges.size() + 2) * sizeof(int8_t)); // Adding 2 for "edges at infinity"
+ struct FaceEdge
+ {
+ int32_t e1, e2;
+ };
+ FaceEdge* faceEdges = (FaceEdge*)PxAlloca(sizeof(FaceEdge) * faces.size() + 1);// Adding 1 for possible new face
+ uint32_t* faceIndices = (uint32_t*)PxAlloca(sizeof(uint32_t) * (faces.size() + 1)); // Adding 1 for possible new face
+ uint32_t* faceRemap = (uint32_t*)PxAlloca(sizeof(uint32_t) * (faces.size() + 1)); // Adding 1 for possible new face
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ FaceEdge& faceEdge = faceEdges[i];
+ faceEdge.e2 = faceEdge.e1 = 0x80000000; // Indicates unset
+ faceIndices[i] = i;
+ faceRemap[i] = i;
+ }
+
+ // Classify the actual edges
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ // The inequalities are set up so that when distanceTol = 0, class 0 is empty and classes -1 and 1 are disjoint
+ Edge& edge = edges[i];
+ PX_ASSERT(faceEdges[edge.m_indexF1].e2 == INT32_MIN);
+ faceEdges[edge.m_indexF1].e2 = (int32_t)i;
+ PX_ASSERT(faceEdges[edge.m_indexF2].e1 == INT32_MIN);
+ faceEdges[edge.m_indexF2].e1 = (int32_t)i;
+ const Real dist = plane.distance(getV0(edge));
+ if (dist < -distanceTol)
+ {
+ edgeClass[i] = -1;
+ negClassEmpty = false;
+ }
+ else if (dist < distanceTol)
+ {
+ edgeClass[i] = 0;
+ }
+ else
+ {
+ edgeClass[i] = 1;
+ posClassEmpty = false;
+ }
+ }
+
+ // Also classify fictitious "edges at infinity" for a complete description
+ uint32_t halfInfinitePlaneCount = 0;
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ Plane& face = faces[i];
+ FaceEdge& faceEdge = faceEdges[i];
+ PX_ASSERT(faceEdge.e2 != INT32_MIN || faceEdge.e1 != INT32_MIN);
+ if (faceEdge.e2 == INT32_MIN || faceEdge.e1 == INT32_MIN)
+ {
+ PX_ASSERT(halfInfinitePlaneCount < 2);
+ if (halfInfinitePlaneCount < 2)
+ {
+ const int32_t classIndex = (int32_t)(edges.size() + halfInfinitePlaneCount++);
+
+ Real cosTheta = (edgeDir ^ face.normal()) | planeNormal;
+ uint32_t realEdgeIndex;
+ if (faceEdge.e2 == INT32_MIN)
+ {
+ faceEdge.e2 = -classIndex; // Negating => fictitious edge.
+ realEdgeIndex = (uint32_t)faceEdge.e1;
+ }
+ else
+ {
+ faceEdge.e1 = -classIndex; // Negating => fictitious edge.
+ cosTheta *= -1;
+ realEdgeIndex = (uint32_t)faceEdge.e2;
+ }
+
+ if (cosTheta < -EPS)
+ {
+ edgeClass[classIndex] = -1;
+ negClassEmpty = false;
+ }
+ else if (cosTheta < EPS)
+ {
+ const Real d = plane.distance(getV0(edges[realEdgeIndex]));
+ if (d < -distanceTol)
+ {
+ edgeClass[classIndex] = -1;
+ negClassEmpty = false;
+ }
+ else if (d < distanceTol)
+ {
+ edgeClass[classIndex] = 0;
+ }
+ else
+ {
+ edgeClass[classIndex] = 1;
+ posClassEmpty = false;
+ }
+ }
+ else
+ {
+ edgeClass[classIndex] = 1;
+ posClassEmpty = false;
+ }
+ }
+ }
+ }
+
+ if (negClassEmpty)
+ {
+ setToEmptySet();
+ return;
+ }
+ if (posClassEmpty)
+ {
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+ return;
+ }
+
+ vectors.popBack(); // We will keep the line origins contiguous, and place the edge direction at the end of the array later
+
+ bool addFace = false;
+ FaceEdge newFaceEdge = { (int32_t)0x80000000, (int32_t)0x80000000 };
+ const uint32_t newFaceIndex = faces.size();
+
+ for (uint32_t i = faces.size(); i--;)
+ {
+ int32_t clippedEdgeIndex = (int32_t)edges.size();
+ Plane& face = faces[i];
+ FaceEdge& faceEdge = faceEdges[i];
+ PX_ASSERT(faceEdge.e2 != INT32_MIN && faceEdge.e1 != INT32_MIN);
+ uint32_t newEdgeIndex = 0xFFFFFFFF;
+ int32_t orig1Index = faceEdge.e2;
+ int32_t orig2Index = faceEdge.e1;
+ switch (edgeClass[physx::PxAbs(faceEdge.e2)])
+ {
+ case 1:
+ if (edgeClass[physx::PxAbs(faceEdge.e1)] == -1)
+ {
+ // Clip face
+ PX_ASSERT(((face.normal() ^ planeNormal) | edgeDir) > 0);
+ PX_ASSERT(newFaceEdge.e1 == INT32_MIN);
+ newFaceEdge.e1 = clippedEdgeIndex;
+ newEdgeIndex = edges.size();
+ Edge& newEdge = edges.insert();
+ newEdge.m_indexF1 = i;
+ newEdge.m_indexF2 = newFaceIndex;
+ faceEdge.e2 = clippedEdgeIndex;
+ addFace = true;
+ }
+ else
+ {
+ // Eliminate face
+ faces.replaceWithLast(i);
+ faceEdges[i] = faceEdges[faces.size()];
+ faceIndices[i] = faceIndices[faces.size()];
+ faceRemap[faceIndices[faces.size()]] = i;
+#ifdef _DEBUG // Should not be necessary, but helps with debugging
+ faceIndices[faces.size()] = 0xFFFFFFFF;
+ faceRemap[i] = 0xFFFFFFFF;
+#endif
+ }
+ break;
+ case 0:
+ if (edgeClass[physx::PxAbs(faceEdge.e1)] == -1)
+ {
+ // Keep this face, and use this edge on the new face
+ clippedEdgeIndex = faceEdge.e2;
+ PX_ASSERT(newFaceEdge.e1 == INT32_MIN);
+ newFaceEdge.e1 = faceEdge.e2;
+ edges[(uint32_t)faceEdge.e2].m_indexF2 = newFaceIndex;
+ addFace = true;
+ }
+ else
+ {
+ // Eliminate face
+ faces.replaceWithLast(i);
+ faceEdges[i] = faceEdges[faces.size()];
+ faceIndices[i] = faceIndices[faces.size()];
+ faceRemap[faceIndices[faces.size()]] = i;
+#ifdef _DEBUG // Should not be necessary, but helps with debugging
+ faceIndices[faces.size()] = 0xFFFFFFFF;
+ faceRemap[i] = 0xFFFFFFFF;
+#endif
+ }
+ break;
+ case -1:
+ switch (edgeClass[physx::PxAbs(faceEdge.e1)])
+ {
+ case 1: // Clip face
+ {
+ PX_ASSERT(((planeNormal ^ face.normal()) | edgeDir) > 0);
+ PX_ASSERT(newFaceEdge.e2 == INT32_MIN);
+ newFaceEdge.e2 = clippedEdgeIndex;
+ newEdgeIndex = edges.size();
+ Edge& newEdge = edges.insert();
+ newEdge.m_indexF1 = newFaceIndex;
+ newEdge.m_indexF2 = i;
+ faceEdge.e1 = clippedEdgeIndex;
+ addFace = true;
+ }
+ break;
+ case 0: // Keep this face, and use this edge on the new face
+ clippedEdgeIndex = faceEdge.e1;
+ PX_ASSERT(newFaceEdge.e2 == INT32_MIN);
+ newFaceEdge.e2 = faceEdge.e1;
+ edges[(uint32_t)faceEdge.e1].m_indexF1 = newFaceIndex;
+ addFace = true;
+ break;
+ }
+ }
+
+ if (newEdgeIndex < edges.size())
+ {
+ Edge& newEdge = edges[newEdgeIndex];
+ newEdge.m_indexV0 = vectors.size();
+ newEdge.m_indexV1 = 0xFFFFFFFF; // Will be replaced below
+ Pos& newOrig = *(Pos*)&vectors.insert();
+ if (orig1Index >= 0)
+ {
+ const Edge& e1 = edges[(uint32_t)orig1Index];
+ const Pos& o1 = getV0(e1);
+ const Real d1 = plane.distance(o1);
+ if (orig2Index >= 0)
+ {
+ const Edge& e2 = edges[(uint32_t)orig2Index];
+ const Pos& o2 = getV0(e2);
+ const Real d2 = plane.distance(o2);
+ newOrig = o1 + (d1 / (d1 - d2)) * (o2 - o1);
+
+ }
+ else
+ {
+ const Dir tangent = face.normal() ^ edgeDir;
+ const Real cosTheta = tangent | planeNormal;
+ PX_ASSERT(physx::PxAbs(cosTheta) > EPS);
+ newOrig = o1 - tangent * (d1 / cosTheta);
+ }
+ }
+ else
+ {
+ const Dir tangent = edgeDir ^ face.normal();
+ const Real cosTheta = tangent | planeNormal;
+ PX_ASSERT(physx::PxAbs(cosTheta) > EPS);
+ if (orig2Index >= 0)
+ {
+ const Edge& e2 = edges[(uint32_t)orig2Index];
+ const Pos& o2 = getV0(e2);
+ const Real d2 = plane.distance(o2);
+ newOrig = o2 - tangent * (d2 / cosTheta);
+ }
+ else
+ {
+ PX_ALWAYS_ASSERT(); // Should not have any full planes
+ }
+ }
+ }
+ }
+
+ if (addFace)
+ {
+ faceRemap[newFaceIndex] = faces.size();
+ faceIndices[faces.size()] = newFaceIndex;
+ faceEdges[faces.size()] = newFaceEdge;
+ Plane& newFace = faces.insert();
+ newFace = plane;
+ }
+
+ if (faces.size() == 0)
+ {
+ setToEmptySet();
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+ return;
+ }
+
+ // Replacing edge direction, and re-indexing
+ const uint32_t edgeDirIndex = vectors.size();
+ vectors.pushBack(edgeDir);
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ edges[i].m_indexV1 = edgeDirIndex;
+ }
+
+ // Eliminate unused edges (and remap face indices in remaining edges)
+ uint8_t* edgeMarks = (uint8_t*)PxAlloca(sizeof(uint8_t) * edges.size());
+ memset(edgeMarks, 0, sizeof(uint8_t)*edges.size());
+
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ FaceEdge& faceEdge = faceEdges[i];
+ if (faceEdge.e2 >= 0)
+ {
+ edgeMarks[faceEdge.e2] = 1;
+ }
+ if (faceEdge.e1 >= 0)
+ {
+ edgeMarks[faceEdge.e1] = 1;
+ }
+ }
+
+ for (uint32_t i = edges.size(); i--;)
+ {
+ if (edgeMarks[i] == 0)
+ {
+ edges.replaceWithLast(i);
+ }
+ else
+ {
+ Edge& edge = edges[i];
+ PX_ASSERT(faceRemap[edge.m_indexF1] != 0xFFFFFFFF);
+ if (faceRemap[edge.m_indexF1] != 0xFFFFFFFF)
+ {
+ edge.m_indexF1 = faceRemap[edge.m_indexF1];
+ }
+ PX_ASSERT(faceRemap[edge.m_indexF2] != 0xFFFFFFFF);
+ if (faceRemap[edge.m_indexF2] != 0xFFFFFFFF)
+ {
+ edge.m_indexF2 = faceRemap[edge.m_indexF2];
+ }
+ }
+ }
+
+ // Eliminate unused vectors (and remap vertex indices in edges)
+ int32_t* vectorOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * vectors.size());
+ memset(vectorOffsets, 0, sizeof(int32_t)*vectors.size());
+
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Edge& edge = edges[i];
+ ++vectorOffsets[edge.m_indexV0];
+ ++vectorOffsets[edge.m_indexV1];
+ }
+
+ uint32_t vectorCount = 0;
+ for (uint32_t i = 0; i < vectors.size(); ++i)
+ {
+ const bool copy = vectorOffsets[i] > 0;
+ vectorOffsets[i] = (int32_t)vectorCount - (int32_t)i;
+ if (copy)
+ {
+ vectors[vectorCount++] = vectors[i];
+ }
+ }
+ vectors.resize(vectorCount);
+
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Edge& edge = edges[i];
+ edge.m_indexV0 += vectorOffsets[edge.m_indexV0];
+ edge.m_indexV1 += vectorOffsets[edge.m_indexV1];
+ }
+
+ PX_ASSERT(vectors.size() == edges.size() + 1);
+
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+
+ return;
+ }
+ }
+
+ // The hull will have vertices
+
+ // Compare vertex positions to input plane
+ bool negClassEmpty = true;
+ bool posClassEmpty = true;
+ int8_t* vertexClass = (int8_t*)PxAlloca(vertexCount * sizeof(int8_t));
+ for (uint32_t i = 0; i < vertexCount; ++i)
+ {
+ // The inequalities are set up so that when distanceTol = 0, class 0 is empty and classes -1 and 1 are disjoint
+ const Real dist = plane.distance(getVertex(i));
+ if (dist < -distanceTol)
+ {
+ vertexClass[i] = -1;
+ negClassEmpty = false;
+ }
+ else if (dist < distanceTol)
+ {
+ vertexClass[i] = 0;
+ }
+ else
+ {
+ vertexClass[i] = 1;
+ posClassEmpty = false;
+ }
+ }
+
+ if (vertexCount != 0)
+ {
+ // Also test "points at infinity" for better culling
+ for (uint32_t i = vertexCount; i < vectors.size(); ++i)
+ {
+ if (vectors[i][3] < (Real)0.5)
+ {
+ const Real cosTheta = plane | vectors[i];
+ if (cosTheta < -EPS)
+ {
+ negClassEmpty = false;
+ }
+ else if (cosTheta >= EPS)
+ {
+ posClassEmpty = false;
+ }
+ }
+ }
+
+ if (negClassEmpty)
+ {
+ setToEmptySet();
+ return;
+ }
+ if (posClassEmpty)
+ {
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+ return;
+ }
+ }
+
+ // Intersect new plane against edges.
+ const uint32_t newFaceIndex = faces.size();
+
+ // Potential number of new edges is the number of faces
+ Edge* newEdges = (Edge*)PxAlloca((newFaceIndex + 1) * sizeof(Edge));
+ memset(newEdges, 0xFF, (newFaceIndex + 1)*sizeof(Edge));
+
+ bool addFace = false;
+
+ for (uint32_t i = edges.size(); i--;)
+ {
+ Edge& edge = edges[i];
+ uint32_t clippedVertexIndex = vectors.size();
+ bool createNewEdge = false;
+ switch (getType(edge))
+ {
+ case EdgeType::LineSegment:
+ switch (vertexClass[edge.m_indexV0])
+ {
+ case -1:
+ switch (vertexClass[edge.m_indexV1])
+ {
+ case 1:
+ {
+ // Clip this edge.
+ const Pos& v0 = getV0(edge);
+ const Real d0 = plane.distance(v0);
+ const Pos& v1 = getV1(edge);
+ const Real d1 = plane.distance(v1);
+ const Pos clipVertex = v0 + (d0 / (d0 - d1)) * (v1 - v0);
+ edge.m_indexV1 = clippedVertexIndex;
+ vectors.pushBack(clipVertex);
+ createNewEdge = true;
+ break;
+ }
+ case 0:
+ createNewEdge = true;
+ clippedVertexIndex = edge.m_indexV1;
+ break;
+ }
+ break;
+ case 0:
+ if (vertexClass[edge.m_indexV1] == -1)
+ {
+ createNewEdge = true;
+ clippedVertexIndex = edge.m_indexV0;
+ }
+ else
+ {
+ // Eliminate this edge
+ edges.replaceWithLast(i);
+ }
+ break;
+ case 1:
+ if (vertexClass[edge.m_indexV1] == -1)
+ {
+ // Clip this edge.
+ const Pos& v0 = getV0(edge);
+ const Real d0 = plane.distance(v0);
+ const Pos& v1 = getV1(edge);
+ const Real d1 = plane.distance(v1);
+ const Pos clipVertex = v1 + (d1 / (d1 - d0)) * (v0 - v1);
+ edge.m_indexV0 = clippedVertexIndex;
+ vectors.pushBack(clipVertex);
+ createNewEdge = true;
+ }
+ else
+ {
+ // Eliminate this edge
+ edges.replaceWithLast(i);
+ }
+ break;
+ }
+ break;
+ case EdgeType::Ray:
+ {
+ const Dir& edgeDir = getDir(edge);
+ const Real den = edgeDir | planeNormal;
+ switch (vertexClass[edge.m_indexV0])
+ {
+ case -1:
+ if (den > EPS)
+ {
+ // Clip this edge.
+ const Pos& v0 = getV0(edge);
+ const Real d0 = plane.distance(v0);
+ const Pos clipVertex = v0 - edgeDir * (d0 / den);
+ edge.m_indexV1 = clippedVertexIndex;
+ vectors.pushBack(clipVertex);
+ createNewEdge = true;
+ }
+ break;
+ case 0:
+ if (den < -EPS)
+ {
+ createNewEdge = true;
+ clippedVertexIndex = edge.m_indexV0;
+ }
+ else
+ {
+ // Eliminate this edge
+ edges.replaceWithLast(i);
+ }
+ break;
+ case 1:
+ if (den < -EPS)
+ {
+ // Clip this edge.
+ const Pos& v0 = getV0(edge);
+ const Real d0 = plane.distance(v0);
+ const Pos clipVertex = v0 - edgeDir * (d0 / den);
+ edge.m_indexV0 = clippedVertexIndex;
+ vectors.pushBack(clipVertex);
+ createNewEdge = true;
+ }
+ else
+ {
+ // Eliminate this edge
+ edges.replaceWithLast(i);
+ }
+ break;
+ }
+ }
+ break;
+ case EdgeType::Line:
+ {
+ const Pos& point = getV0(edge);
+ const Dir& edgeDir = getDir(edge);
+ const Real h = plane.distance(point);
+ const Real den = edgeDir | planeNormal;
+ PX_ASSERT(physx::PxAbs(den) >= EPS);
+ // Clip this edge
+ clippedVertexIndex = edge.m_indexV0; // Re-use this vector (will become a vertex)
+ vectors[clippedVertexIndex] = point - edgeDir * (h / den);
+ if (den > 0) // Make sure the ray points in the correct direction
+ {
+ vectors[edge.m_indexV1] *= -(Real)1;
+ nvidia::swap(edge.m_indexF1, edge.m_indexF2);
+ }
+ createNewEdge = true;
+ }
+ break;
+ }
+
+ if (createNewEdge)
+ {
+ if (newEdges[edge.m_indexF1].m_indexV0 == 0xFFFFFFFF)
+ {
+ newEdges[edge.m_indexF1].m_indexV0 = clippedVertexIndex;
+ PX_ASSERT(newEdges[edge.m_indexF1].m_indexV1 == 0xFFFFFFFF);
+ newEdges[edge.m_indexF1].m_indexV1 = vectors.size();
+ Dir newDir = planeNormal ^ faces[edge.m_indexF1].normal();
+ newDir.normalize();
+ if ((newDir | faces[edge.m_indexF2].normal()) > 0)
+ {
+ newDir *= -(Real)1;
+ newEdges[edge.m_indexF1].m_indexF1 = edge.m_indexF1;
+ newEdges[edge.m_indexF1].m_indexF2 = newFaceIndex;
+ }
+ else
+ {
+ newEdges[edge.m_indexF1].m_indexF1 = newFaceIndex;
+ newEdges[edge.m_indexF1].m_indexF2 = edge.m_indexF1;
+ }
+ vectors.pushBack(newDir);
+ addFace = true;
+ }
+ else
+ {
+ PX_ASSERT(newEdges[edge.m_indexF1].m_indexV1 != 0xFFFFFFFF && vectors[newEdges[edge.m_indexF1].m_indexV1][3] == (Real)0);
+ newEdges[edge.m_indexF1].m_indexV1 = clippedVertexIndex;
+ }
+ if (newEdges[edge.m_indexF2].m_indexV0 == 0xFFFFFFFF)
+ {
+ newEdges[edge.m_indexF2].m_indexV0 = clippedVertexIndex;
+ PX_ASSERT(newEdges[edge.m_indexF2].m_indexV1 == 0xFFFFFFFF);
+ newEdges[edge.m_indexF2].m_indexV1 = vectors.size();
+ Dir newDir = faces[edge.m_indexF2].normal() ^ planeNormal;
+ newDir.normalize();
+ if ((newDir | faces[edge.m_indexF1].normal()) > 0)
+ {
+ newDir *= -(Real)1;
+ newEdges[edge.m_indexF2].m_indexF1 = newFaceIndex;
+ newEdges[edge.m_indexF2].m_indexF2 = edge.m_indexF2;
+ }
+ else
+ {
+ newEdges[edge.m_indexF2].m_indexF1 = edge.m_indexF2;
+ newEdges[edge.m_indexF2].m_indexF2 = newFaceIndex;
+ }
+ vectors.pushBack(newDir);
+ addFace = true;
+ }
+ else
+ {
+ PX_ASSERT(newEdges[edge.m_indexF2].m_indexV1 != 0xFFFFFFFF && vectors[newEdges[edge.m_indexF2].m_indexV1][3] == (Real)0);
+ newEdges[edge.m_indexF2].m_indexV1 = clippedVertexIndex;
+ }
+ }
+ }
+
+ if (addFace)
+ {
+ Plane& newFace = faces.insert();
+ newFace = plane;
+ }
+
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ Edge& newEdge = newEdges[i];
+ if (newEdge.m_indexV0 != 0xFFFFFFFF)
+ {
+ if (newEdge.m_indexV0 != newEdge.m_indexV1) // Skip split vertices
+ {
+ edges.pushBack(newEdge);
+ }
+ }
+ }
+
+ // Now eliminate unused faces and vectors
+ int32_t* vectorOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * vectors.size());
+ int32_t* faceOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * faces.size());
+ memset(vectorOffsets, 0, sizeof(int32_t)*vectors.size());
+ memset(faceOffsets, 0, sizeof(int32_t)*faces.size());
+
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Edge& edge = edges[i];
+ ++vectorOffsets[edge.m_indexV0];
+ ++vectorOffsets[edge.m_indexV1];
+ ++faceOffsets[edge.m_indexF1];
+ ++faceOffsets[edge.m_indexF2];
+ }
+
+ uint32_t vectorCount = 0;
+ for (uint32_t i = 0; i < vectors.size(); ++i)
+ {
+ const bool copy = vectorOffsets[i] > 0;
+ vectorOffsets[i] = (int32_t)vectorCount - (int32_t)i;
+ if (copy)
+ {
+ vectors[vectorCount++] = vectors[i];
+ }
+ }
+ vectors.resize(vectorCount);
+
+ uint32_t faceCount = 0;
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ const bool copy = faceOffsets[i] > 0;
+ faceOffsets[i] = (int32_t)faceCount - (int32_t)i;
+ if (copy)
+ {
+ faces[faceCount++] = faces[i];
+ }
+ }
+ faces.resize(faceCount);
+
+ PX_ASSERT(faceCount != 1); // Single faces would have been handled above.
+
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Edge& edge = edges[i];
+ edge.m_indexV0 += vectorOffsets[edge.m_indexV0];
+ edge.m_indexV1 += vectorOffsets[edge.m_indexV1];
+ edge.m_indexF1 += faceOffsets[edge.m_indexF1];
+ edge.m_indexF2 += faceOffsets[edge.m_indexF2];
+ }
+
+ if (faces.size() == 0)
+ {
+ setToEmptySet();
+ }
+
+ // Now sort vectors, vertices before directions. We will need to keep track of the mapping to re-index the edges.
+ uint32_t* vectorIndices = (uint32_t*)PxAlloca(sizeof(uint32_t) * vectors.size());
+ uint32_t* vectorRemap = (uint32_t*)PxAlloca(sizeof(uint32_t) * vectors.size());
+ for (uint32_t i = 0; i < vectors.size(); ++i)
+ {
+ vectorIndices[i] = vectorRemap[i] = i;
+ }
+ int32_t firstDirIndex = -1;
+ int32_t lastPosIndex = (int32_t)vectors.size();
+ for (;;)
+ {
+ // Correct this
+ while (++firstDirIndex < (int32_t)vectors.size())
+ {
+ if (vectors[(uint32_t)firstDirIndex][3] < (Real)0.5) // Should be 0 or 1, but in case some f.p. inexactness creeps in...
+ {
+ break;
+ }
+ }
+ while (--lastPosIndex >= 0)
+ {
+ if (vectors[(uint32_t)lastPosIndex][3] >= (Real)0.5) // Should be 0 or 1, but in case some f.p. inexactness creeps in...
+ {
+ break;
+ }
+ }
+ if (firstDirIndex > lastPosIndex)
+ {
+ break; // All's good
+ }
+ // Fix this - swap vectors, and update map
+ nvidia::swap(vectors[(uint32_t)firstDirIndex], vectors[(uint32_t)lastPosIndex]);
+ nvidia::swap(vectorIndices[(uint32_t)firstDirIndex], vectorIndices[(uint32_t)lastPosIndex]);
+ vectorRemap[vectorIndices[(uint32_t)firstDirIndex]] = (uint32_t)firstDirIndex;
+ vectorRemap[vectorIndices[(uint32_t)lastPosIndex]] = (uint32_t)lastPosIndex;
+ }
+ vertexCount = (uint32_t)firstDirIndex; // Correct vertex count
+
+ // Correct edge indices
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Edge& edge = edges[i];
+ edge.m_indexV0 = vectorRemap[edge.m_indexV0];
+ edge.m_indexV1 = vectorRemap[edge.m_indexV1];
+ }
+
+ SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8));
+}
+
+ApexCSG::Real ApexCSG::Hull::calculateVolume() const
+{
+ if (isEmptySet())
+ {
+ return (Real)0;
+ }
+
+ if (edges.size() == 0 || vectors.size() > vertexCount)
+ {
+ return MAX_REAL;
+ }
+
+ // Volume is finite
+ PX_ASSERT(vertexCount != 0);
+
+ // Work relative to an internal point, for better accuracy
+ Vec4Real centroid((Real)0);
+ for (uint32_t i = 0; i < vertexCount; ++i)
+ {
+ centroid += vectors[i];
+ }
+ centroid /= (Real)vertexCount;
+
+ // Create a doubled edge list
+ const uint32_t edgeCount = edges.size();
+ const uint32_t edgeListSize = 2 * edgeCount;
+ Edge* edgeList = (Edge*)PxAlloca(edgeListSize * sizeof(Edge));
+ for (uint32_t i = 0; i < edgeCount; ++i)
+ {
+ edgeList[i] = edges[i];
+ edgeList[i + edgeCount] = edges[i];
+ nvidia::swap(edgeList[i + edgeCount].m_indexF1, edgeList[i + edgeCount].m_indexF2);
+ nvidia::swap(edgeList[i + edgeCount].m_indexV0, edgeList[i + edgeCount].m_indexV1);
+ }
+
+ // Sort edgeList by first face index
+ qsort(edgeList, edgeListSize, sizeof(Edge), compareEdgeFirstFaceIndices);
+
+ // A scratchpad for edge vertex locations
+ uint32_t* vertex0Locations = (uint32_t*)PxAlloca(vertexCount * sizeof(uint32_t));
+ memset(vertex0Locations, 0xFF, vertexCount * sizeof(uint32_t));
+
+ Real volume = 0;
+
+ // Edges are grouped by first face index - each group describes the polygonal face.
+ uint32_t groupStop = 0;
+ do
+ {
+ const uint32_t groupStart = groupStop;
+ const uint32_t faceIndex = edgeList[groupStart].m_indexF1;
+ while (++groupStop < edgeListSize && edgeList[groupStop].m_indexF1 == faceIndex) {}
+ // Evaluate group
+ if (groupStop - groupStart >= 3)
+ {
+ // Mark first vertex locations within group
+ for (uint32_t i = groupStart; i < groupStop; ++i)
+ {
+ Edge& edge = edgeList[i];
+ SOFT_ASSERT(vertex0Locations[edge.m_indexV0] == 0xFFFFFFFF);
+ vertex0Locations[edge.m_indexV0] = i;
+ }
+ const Dir d0 = vectors[edgeList[groupStart].m_indexV0] - centroid;
+ uint32_t i1 = edgeList[groupStart].m_indexV1;
+ Dir d1 = vectors[i1] - centroid;
+ const uint32_t tetCount = groupStop - groupStart - 2;
+ for (uint32_t i = 0; i < tetCount; ++i)
+ {
+ const uint32_t nextEdgeIndex = vertex0Locations[i1];
+ SOFT_ASSERT(nextEdgeIndex != 0xFFFFFFFF);
+ if (nextEdgeIndex == 0xFFFFFFFF)
+ {
+ break;
+ }
+ const uint32_t i2 = edgeList[nextEdgeIndex].m_indexV1;
+ const Dir d2 = vectors[i2] - centroid;
+ const Real tripleProduct = d0 | (d1 ^ d2);
+ SOFT_ASSERT(tripleProduct > -EPS_REAL);
+ volume += tripleProduct; // 6 times volume
+ i1 = i2;
+ d1 = d2;
+ }
+ // Unmark first vertex locations
+ for (uint32_t i = groupStart; i < groupStop; ++i)
+ {
+ Edge& edge = edgeList[i];
+ vertex0Locations[edge.m_indexV0] = 0xFFFFFFFF;
+ }
+ }
+ }
+ while (groupStop < edgeListSize);
+
+ volume *= (Real)0.166666666666666667;
+
+ return volume;
+}
+
+void ApexCSG::Hull::serialize(physx::PxFileBuf& stream) const
+{
+ apex::serialize(stream, faces);
+ apex::serialize(stream, edges);
+ apex::serialize(stream, vectors);
+ stream << vertexCount;
+ stream << allSpace;
+ stream << emptySet;
+}
+
+void ApexCSG::Hull::deserialize(physx::PxFileBuf& stream, uint32_t version)
+{
+ setToAllSpace();
+
+ apex::deserialize(stream, version, faces);
+ apex::deserialize(stream, version, edges);
+ apex::deserialize(stream, version, vectors);
+ stream >> vertexCount;
+ stream >> allSpace;
+ stream >> emptySet;
+}
+
+bool ApexCSG::Hull::testConsistency(ApexCSG::Real distanceTol, ApexCSG::Real angleTol) const
+{
+ bool halfInfiniteEdges = false;
+ for (uint32_t j = 0; j < edges.size(); ++j)
+ {
+ if (edges[j].m_indexV0 < vertexCount && edges[j].m_indexV1 >= vertexCount)
+ {
+ halfInfiniteEdges = true;
+ break;
+ }
+ }
+
+ for (uint32_t i = 0; i < vertexCount; ++i)
+ {
+ uint32_t count = 0;
+ for (uint32_t j = 0; j < edges.size(); ++j)
+ {
+ if (edges[j].m_indexV0 == i)
+ {
+ ++count;
+ }
+ if (edges[j].m_indexV1 == i)
+ {
+ ++count;
+ }
+ if (edges[j].m_indexV1 < vertexCount && edges[j].m_indexV0 == edges[j].m_indexV1)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: 0-length edge found.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ if (count < 3)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: vertex connected to fewer than 3 edges.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+
+ if (edges.size() == 0)
+ {
+ if (faces.size() >= 3)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: face count 3 or greater, but with no edges.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ if (vertexCount > 0)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has vertices but no edges.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ return true;
+ }
+
+ bool halfInfiniteFaces = false;
+
+ for (uint32_t i = 0; i < faces.size(); ++i)
+ {
+ uint32_t count = 0;
+ for (uint32_t j = 0; j < edges.size(); ++j)
+ {
+ if (edges[j].m_indexF1 == i)
+ {
+ ++count;
+ }
+ if (edges[j].m_indexF2 == i)
+ {
+ ++count;
+ }
+ if (edges[j].m_indexF1 == edges[j].m_indexF2)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge connecting face to itself.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ if (count == 0)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: face has no edges", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ if (count == 1)
+ {
+ halfInfiniteFaces = true;
+ }
+ }
+
+ for (uint32_t i = 0; i < edges.size(); ++i)
+ {
+ Real d;
+ const Edge& edge = edges[i];
+ if (edge.m_indexV1 >= vertexCount)
+ {
+ // Edge is a line or ray
+ d = faces[edge.m_indexF1].distance(getV0(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray origin not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF1].normal() | getDir(edge);
+ if (physx::PxAbs(d) > angleTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray direction not perpendicular to face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF2].distance(getV0(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray origin not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF2].normal() | getDir(edge);
+ if (physx::PxAbs(d) > angleTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray direction not perpendicular to face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ else
+ {
+ // Edge is a line segment
+ if (edge.m_indexV0 >= vertexCount)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge has a line/ray origin but a real destination point.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF1].distance(getV0(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 0 not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF1].distance(getV1(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 1 not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF2].distance(getV0(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 0 not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ d = faces[edge.m_indexF2].distance(getV1(edge));
+ if (physx::PxAbs(d) > distanceTol)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 1 not on face.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ }
+
+ if (vertexCount == 0)
+ {
+ if (!halfInfiniteFaces)
+ {
+ if (faces.size() != edges.size())
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has edges but no vertices, with no half-infinite faces. Face count should equal edge count but does not.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ else
+ {
+ if (faces.size() != edges.size() + 1)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has edges but no vertices, with half-infinite faces. Face count should be one more than edge count but is not.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ const Edge& edge0 = edges[0];
+ for (uint32_t i = 1; i < edges.size(); ++i)
+ {
+ const Edge& edge = edges[i];
+ Dir e = getDir(edge0) ^ getDir(edge);
+ if ((e | e) > (Real)EPS * EPS)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: prism edges not all facing the same direction.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ }
+ else
+ {
+ const uint32_t c = faces.size() - edges.size() + vertexCount;
+ if (!halfInfiniteEdges)
+ {
+ if (c != 2)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: bounded hull with faces, vertices and edges does not obey Euler's formula.", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ else
+ {
+ if (c != 1)
+ {
+ GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: unbounded hull with faces, vertices and edges does not obey Euler's formula (with a vertex at infinity).", __FILE__, __LINE__);
+ PX_ALWAYS_ASSERT();
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+#endif \ No newline at end of file