feat(physics): wire physx sdk into build

This commit is contained in:
2026-04-15 12:22:15 +08:00
parent 5bf258df6d
commit 31f40e2cbb
2044 changed files with 752623 additions and 1 deletions

View File

@@ -0,0 +1,662 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "foundation/PxMemory.h"
#include "GuEdgeList.h"
#include "GuAdjacencies.h"
#include "CmSerialize.h"
#include "CmRadixSort.h"
// PT: code archeology: this initially came from ICE (IceAdjacencies.h/cpp). Consider putting it back the way it was initially.
using namespace physx;
using namespace Gu;
using namespace Cm;
///////////////////////////////////////////////////////////////////////////////
PX_IMPLEMENT_OUTPUT_ERROR
///////////////////////////////////////////////////////////////////////////////
/**
* Flips the winding.
*/
void AdjTriangle::Flip()
{
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
// Call the Triangle method
IndexedTriangle::Flip();
#endif
// Flip links. We flipped vertex references 1 & 2, i.e. links 0 & 1.
physx::PxSwap(mATri[0], mATri[1]);
}
/**
* Computes the number of boundary edges in a triangle.
* \return the number of boundary edges. (0 => 3)
*/
PxU32 AdjTriangle::ComputeNbBoundaryEdges() const
{
// Look for boundary edges
PxU32 Nb = 0;
if(IS_BOUNDARY(mATri[0])) Nb++;
if(IS_BOUNDARY(mATri[1])) Nb++;
if(IS_BOUNDARY(mATri[2])) Nb++;
return Nb;
}
/**
* Computes the number of valid neighbors.
* \return the number of neighbors. (0 => 3)
*/
PxU32 AdjTriangle::ComputeNbNeighbors() const
{
PxU32 Nb = 0;
if(!IS_BOUNDARY(mATri[0])) Nb++;
if(!IS_BOUNDARY(mATri[1])) Nb++;
if(!IS_BOUNDARY(mATri[2])) Nb++;
return Nb;
}
/**
* Checks whether the triangle has a particular neighbor or not.
* \param tref [in] the triangle reference to look for
* \param index [out] the corresponding index in the triangle (NULL if not needed)
* \return true if the triangle has the given neighbor
*/
bool AdjTriangle::HasNeighbor(PxU32 tref, PxU32* index) const
{
// ### could be optimized
if(!IS_BOUNDARY(mATri[0]) && MAKE_ADJ_TRI(mATri[0])==tref) { if(index) *index = 0; return true; }
if(!IS_BOUNDARY(mATri[1]) && MAKE_ADJ_TRI(mATri[1])==tref) { if(index) *index = 1; return true; }
if(!IS_BOUNDARY(mATri[2]) && MAKE_ADJ_TRI(mATri[2])==tref) { if(index) *index = 2; return true; }
return false;
}
Adjacencies::Adjacencies() : mNbFaces(0), mFaces(NULL)
{
}
Adjacencies::~Adjacencies()
{
PX_DELETE_ARRAY(mFaces);
}
/**
* Computes the number of boundary edges.
* \return the number of boundary edges.
*/
PxU32 Adjacencies::ComputeNbBoundaryEdges() const
{
if(!mFaces)
return 0;
// Look for boundary edges
PxU32 Nb = 0;
for(PxU32 i=0;i<mNbFaces;i++)
{
AdjTriangle* CurTri = &mFaces[i];
Nb+=CurTri->ComputeNbBoundaryEdges();
}
return Nb;
}
/**
* Computes the boundary vertices. A boundary vertex is defined as a vertex shared by at least one boundary edge.
* \param nb_verts [in] the number of vertices
* \param bound_status [out] a user-provided array of bool
* \return true if success. The user-array is filled with true or false (boundary vertex / not boundary vertex)
*/
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
bool Adjacencies::GetBoundaryVertices(PxU32 nb_verts, bool* bound_status) const
#else
bool Adjacencies::GetBoundaryVertices(PxU32 nb_verts, bool* bound_status, const IndexedTriangle32* faces) const
#endif
{
// We need the adjacencies
if(!mFaces || !bound_status || !nb_verts)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "Adjacencies::GetBoundaryVertices: NULL parameter!");
#ifndef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
if(!faces)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "Adjacencies::GetBoundaryVertices: NULL parameter!");
#endif
// Init
PxMemZero(bound_status, nb_verts*sizeof(bool));
// Loop through faces
for(PxU32 i=0;i<mNbFaces;i++)
{
AdjTriangle* CurTri = &mFaces[i];
if(IS_BOUNDARY(CurTri->mATri[0]))
{
// Two boundary vertices: 0 - 1
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
PxU32 VRef0 = CurTri->v[0]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = CurTri->v[1]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#else
PxU32 VRef0 = faces[i].mRef[0]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = faces[i].mRef[1]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#endif
}
if(IS_BOUNDARY(CurTri->mATri[1]))
{
// Two boundary vertices: 0 - 2
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
PxU32 VRef0 = CurTri->v[0]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = CurTri->v[2]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#else
PxU32 VRef0 = faces[i].mRef[0]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = faces[i].mRef[2]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#endif
}
if(IS_BOUNDARY(CurTri->mATri[2]))
{
// Two boundary vertices: 1 - 2
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
PxU32 VRef0 = CurTri->v[1]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = CurTri->v[2]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#else
PxU32 VRef0 = faces[i].mRef[1]; if(VRef0>=nb_verts) return false; bound_status[VRef0] = true;
PxU32 VRef1 = faces[i].mRef[2]; if(VRef1>=nb_verts) return false; bound_status[VRef1] = true;
#endif
}
}
return true;
}
/**
* Assigns a new edge code to the counterpart link of a given link.
* \param link [in] the link to modify - shouldn't be a boundary link
* \param edge_nb [in] the new edge number
*/
void Adjacencies::AssignNewEdgeCode(PxU32 link, PxU8 edge_nb)
{
if(!IS_BOUNDARY(link))
{
PxU32 Id = MAKE_ADJ_TRI(link); // Triangle ID
PxU32 Edge = GET_EDGE_NB(link); // Counterpart edge ID
AdjTriangle* Tri = &mFaces[Id]; // Adjacent triangle
// Get link whose edge code is invalid
PxU32 AdjLink = Tri->mATri[Edge]; // Link to ourself (i.e. to 'link')
SET_EDGE_NB(AdjLink, edge_nb); // Assign new edge code
Tri->mATri[Edge] = AdjLink; // Put link back
}
}
/**
* Modifies the existing database so that reference 'vref' of triangle 'curtri' becomes the last one.
* Provided reference must already exist in provided triangle.
* \param cur_tri [in] the triangle
* \param vref [in] the reference
* \return true if success.
*/
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
bool Adjacencies::MakeLastRef(AdjTriangle& cur_tri, PxU32 vref)
#else
bool Adjacencies::MakeLastRef(AdjTriangle& cur_tri, PxU32 vref, IndexedTriangle32* cur_topo)
#endif
{
#ifndef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
if(!cur_topo)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "Adjacencies::MakeLastRef: NULL parameter!");
#endif
// We want pattern (x y vref)
// Edge 0-1 is (x y)
// Edge 0-2 is (x vref)
// Edge 1-2 is (y vref)
// First thing is to scroll the existing references in order for vref to become the last one. Scrolling assures winding order is conserved.
// Edge code need fixing as well:
// The two MSB for each link encode the counterpart edge in adjacent triangle. We swap the link positions, but adjacent triangles remain the
// same. In other words, edge codes are still valid for current triangle since counterpart edges have not been swapped. *BUT* edge codes of
// the three possible adjacent triangles *are* now invalid. We need to fix edge codes, but for adjacent triangles...
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
if(cur_tri.v[0]==vref)
#else
if(cur_topo->mRef[0]==vref)
#endif
{
// Pattern is (vref x y)
// Edge 0-1 is (vref x)
// Edge 0-2 is (vref y)
// Edge 1-2 is (x y)
// Catch original data
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
PxU32 Ref0 = cur_tri.v[0]; PxU32 Link01 = cur_tri.mATri[0];
PxU32 Ref1 = cur_tri.v[1]; PxU32 Link02 = cur_tri.mATri[1];
PxU32 Ref2 = cur_tri.v[2]; PxU32 Link12 = cur_tri.mATri[2];
// Swap
cur_tri.v[0] = Ref1;
cur_tri.v[1] = Ref2;
cur_tri.v[2] = Ref0;
#else
PxU32 Ref0 = cur_topo->mRef[0]; PxU32 Link01 = cur_tri.mATri[0];
PxU32 Ref1 = cur_topo->mRef[1]; PxU32 Link02 = cur_tri.mATri[1];
PxU32 Ref2 = cur_topo->mRef[2]; PxU32 Link12 = cur_tri.mATri[2];
// Swap
cur_topo->mRef[0] = Ref1;
cur_topo->mRef[1] = Ref2;
cur_topo->mRef[2] = Ref0;
#endif
cur_tri.mATri[0] = Link12; // Edge 0-1 now encodes Ref1-Ref2, i.e. previous Link12
cur_tri.mATri[1] = Link01; // Edge 0-2 now encodes Ref1-Ref0, i.e. previous Link01
cur_tri.mATri[2] = Link02; // Edge 1-2 now encodes Ref2-Ref0, i.e. previous Link02
// Fix edge codes
AssignNewEdgeCode(Link01, 1);
AssignNewEdgeCode(Link02, 2);
AssignNewEdgeCode(Link12, 0);
return true;
}
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
else if(cur_tri.v[1]==vref)
#else
else if(cur_topo->mRef[1]==vref)
#endif
{
// Pattern is (x vref y)
// Edge 0-1 is (x vref)
// Edge 0-2 is (x y)
// Edge 1-2 is (vref y)
// Catch original data
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
PxU32 Ref0 = cur_tri.v[0]; PxU32 Link01 = cur_tri.mATri[0];
PxU32 Ref1 = cur_tri.v[1]; PxU32 Link02 = cur_tri.mATri[1];
PxU32 Ref2 = cur_tri.v[2]; PxU32 Link12 = cur_tri.mATri[2];
// Swap
cur_tri.v[0] = Ref2;
cur_tri.v[1] = Ref0;
cur_tri.v[2] = Ref1;
#else
PxU32 Ref0 = cur_topo->mRef[0]; PxU32 Link01 = cur_tri.mATri[0];
PxU32 Ref1 = cur_topo->mRef[1]; PxU32 Link02 = cur_tri.mATri[1];
PxU32 Ref2 = cur_topo->mRef[2]; PxU32 Link12 = cur_tri.mATri[2];
// Swap
cur_topo->mRef[0] = Ref2;
cur_topo->mRef[1] = Ref0;
cur_topo->mRef[2] = Ref1;
#endif
cur_tri.mATri[0] = Link02; // Edge 0-1 now encodes Ref2-Ref0, i.e. previous Link02
cur_tri.mATri[1] = Link12; // Edge 0-2 now encodes Ref2-Ref1, i.e. previous Link12
cur_tri.mATri[2] = Link01; // Edge 1-2 now encodes Ref0-Ref1, i.e. previous Link01
// Fix edge codes
AssignNewEdgeCode(Link01, 2);
AssignNewEdgeCode(Link02, 0);
AssignNewEdgeCode(Link12, 1);
return true;
}
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
else if(cur_tri.v[2]==vref)
#else
else if(cur_topo->mRef[2]==vref)
#endif
{
// Nothing to do, provided reference already is the last one
return true;
}
// Here the provided reference doesn't belong to the provided triangle.
return false;
}
bool Adjacencies::Load(PxInputStream& stream)
{
// Import header
PxU32 Version;
bool Mismatch;
if(!ReadHeader('A', 'D', 'J', 'A', Version, Mismatch, stream))
return false;
// Import adjacencies
mNbFaces = readDword(Mismatch, stream);
mFaces = PX_NEW(AdjTriangle)[mNbFaces];
stream.read(mFaces, sizeof(AdjTriangle)*mNbFaces);
return true;
}
//#ifdef PX_COOKING
//! An edge class used to compute the adjacency structures.
class AdjEdge : public EdgeData, public PxUserAllocated
{
public:
PX_INLINE AdjEdge() {}
PX_INLINE ~AdjEdge() {}
PxU32 mFaceNb; //!< Owner face
};
/**
* Adds a new edge to the database.
* \param ref0 [in] vertex reference for the new edge
* \param ref1 [in] vertex reference for the new edge
* \param face [in] owner face
*/
static void AddEdge(PxU32 ref0, PxU32 ref1, PxU32 face, PxU32& nb_edges, AdjEdge* edges)
{
// Store edge data
edges[nb_edges].Ref0 = ref0;
edges[nb_edges].Ref1 = ref1;
edges[nb_edges].mFaceNb = face;
nb_edges++;
}
/**
* Adds a new triangle to the database.
* \param ref0 [in] vertex reference for the new triangle
* \param ref1 [in] vertex reference for the new triangle
* \param ref2 [in] vertex reference for the new triangle
* \param id [in] triangle index
*/
static void AddTriangle(PxU32 ref0, PxU32 ref1, PxU32 ref2, PxU32 id, AdjTriangle* faces, PxU32& nb_edges, AdjEdge* edges)
{
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
// Store vertex-references
faces[id].v[0] = ref0;
faces[id].v[1] = ref1;
faces[id].v[2] = ref2;
#endif
// Reset links
faces[id].mATri[0] = PX_INVALID_U32;
faces[id].mATri[1] = PX_INVALID_U32;
faces[id].mATri[2] = PX_INVALID_U32;
// Add edge 01 to database
if(ref0<ref1) AddEdge(ref0, ref1, id, nb_edges, edges);
else AddEdge(ref1, ref0, id, nb_edges, edges);
// Add edge 02 to database
if(ref0<ref2) AddEdge(ref0, ref2, id, nb_edges, edges);
else AddEdge(ref2, ref0, id, nb_edges, edges);
// Add edge 12 to database
if(ref1<ref2) AddEdge(ref1, ref2, id, nb_edges, edges);
else AddEdge(ref2, ref1, id, nb_edges, edges);
}
/**
* Updates the links in two adjacent triangles.
* \param first_tri [in] index of the first triangle
* \param second_tri [in] index of the second triangle
* \param ref0 [in] the common edge's first vertex reference
* \param ref1 [in] the common edge's second vertex reference
* \return true if success.
*/
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
static bool UpdateLink(PxU32 first_tri, PxU32 second_tri, PxU32 ref0, PxU32 ref1, AdjTriangle* faces)
#else
static bool UpdateLink(PxU32 first_tri, PxU32 second_tri, PxU32 ref0, PxU32 ref1, AdjTriangle* faces, const ADJACENCIESCREATE& create)
#endif
{
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
AdjTriangle& Tri0 = faces[first_tri]; // Catch the first triangle
AdjTriangle& Tri1 = faces[second_tri]; // Catch the second triangle
// Get the edge IDs. 0xff means input references are wrong.
PxU8 EdgeNb0 = Tri0.FindEdge(ref0, ref1); if(EdgeNb0==0xff) return SetIceError("Adjacencies::UpdateLink: invalid edge reference in first triangle");
PxU8 EdgeNb1 = Tri1.FindEdge(ref0, ref1); if(EdgeNb1==0xff) return SetIceError("Adjacencies::UpdateLink: invalid edge reference in second triangle");
// Update links. The two most significant bits contain the counterpart edge's ID.
Tri0.mATri[EdgeNb0] = second_tri |(PxU32(EdgeNb1)<<30);
Tri1.mATri[EdgeNb1] = first_tri |(PxU32(EdgeNb0)<<30);
#else
IndexedTriangle32 FirstTri, SecondTri;
if(create.DFaces)
{
FirstTri.mRef[0] = create.DFaces[first_tri*3+0];
FirstTri.mRef[1] = create.DFaces[first_tri*3+1];
FirstTri.mRef[2] = create.DFaces[first_tri*3+2];
SecondTri.mRef[0] = create.DFaces[second_tri*3+0];
SecondTri.mRef[1] = create.DFaces[second_tri*3+1];
SecondTri.mRef[2] = create.DFaces[second_tri*3+2];
}
if(create.WFaces)
{
FirstTri.mRef[0] = create.WFaces[first_tri*3+0];
FirstTri.mRef[1] = create.WFaces[first_tri*3+1];
FirstTri.mRef[2] = create.WFaces[first_tri*3+2];
SecondTri.mRef[0] = create.WFaces[second_tri*3+0];
SecondTri.mRef[1] = create.WFaces[second_tri*3+1];
SecondTri.mRef[2] = create.WFaces[second_tri*3+2];
}
// Get the edge IDs. 0xff means input references are wrong.
const PxU8 EdgeNb0 = FirstTri.findEdge(ref0, ref1);
const PxU8 EdgeNb1 = SecondTri.findEdge(ref0, ref1);
if(EdgeNb0==0xff || EdgeNb1==0xff)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "Adjacencies::UpdateLink: invalid edge reference");
// Update links. The two most significant bits contain the counterpart edge's ID.
faces[first_tri].mATri[EdgeNb0] = second_tri |(PxU32(EdgeNb1)<<30);
faces[second_tri].mATri[EdgeNb1] = first_tri |(PxU32(EdgeNb0)<<30);
#endif
return true;
}
/**
* Creates the adjacency structures.
* \return true if success.
*/
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
static bool CreateDatabase(AdjTriangle* faces, PxU32 nb_edges, const AdjEdge* edges)
#else
static bool CreateDatabase(AdjTriangle* faces, PxU32 nb_edges, const AdjEdge* edges, const ADJACENCIESCREATE& create)
#endif
{
RadixSortBuffered Core;
{
// Multiple sorts - this rewritten version uses less ram
// PT: TTP 2994: the mesh has 343000+ edges, so yeah, sure, allocating more than 1mb on the stack causes overflow...
PxU32* VRefs = PX_ALLOCATE(PxU32, nb_edges, "tmp");
// Sort according to mRef0, then mRef1
PxU32 i;
for(i=0;i<nb_edges;i++)
VRefs[i] = edges[i].Ref0;
Core.Sort(VRefs, nb_edges);
for(i=0;i<nb_edges;i++)
VRefs[i] = edges[i].Ref1;
Core.Sort(VRefs, nb_edges);
PX_FREE(VRefs);
}
const PxU32* Sorted = Core.GetRanks();
// Read the list in sorted order, look for similar edges
PxU32 LastRef0 = edges[Sorted[0]].Ref0;
PxU32 LastRef1 = edges[Sorted[0]].Ref1;
PxU32 Count = 0;
PxU32 TmpBuffer[3];
while(nb_edges--)
{
PxU32 SortedIndex = *Sorted++;
PxU32 Face = edges[SortedIndex].mFaceNb; // Owner face
PxU32 Ref0 = edges[SortedIndex].Ref0; // Vertex ref #1
PxU32 Ref1 = edges[SortedIndex].Ref1; // Vertex ref #2
if(Ref0==LastRef0 && Ref1==LastRef1)
{
// Current edge is the same as last one
TmpBuffer[Count++] = Face; // Store face number
// Only works with manifold meshes (i.e. an edge is not shared by more than 2 triangles)
if(Count==3)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "Adjacencies::CreateDatabase: can't work on non-manifold meshes.");
}
else
{
// Here we have a new edge (LastRef0, LastRef1) shared by Count triangles stored in TmpBuffer
if(Count==2)
{
// if Count==1 => edge is a boundary edge: it belongs to a single triangle.
// Hence there's no need to update a link to an adjacent triangle.
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
if(!UpdateLink(TmpBuffer[0], TmpBuffer[1], LastRef0, LastRef1, faces)) return false;
#else
if(!UpdateLink(TmpBuffer[0], TmpBuffer[1], LastRef0, LastRef1, faces, create)) return false;
#endif
}
// Reset for next edge
Count = 0;
TmpBuffer[Count++] = Face;
LastRef0 = Ref0;
LastRef1 = Ref1;
}
}
bool Status = true;
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
if(Count==2) Status = UpdateLink(TmpBuffer[0], TmpBuffer[1], LastRef0, LastRef1, faces);
#else
if(Count==2) Status = UpdateLink(TmpBuffer[0], TmpBuffer[1], LastRef0, LastRef1, faces, create);
#endif
return Status;
}
AdjacenciesBuilder::AdjacenciesBuilder()
{
}
AdjacenciesBuilder::~AdjacenciesBuilder()
{
}
/**
* Initializes the component.
* \param create [in] the creation structure
* \return true if success.
*/
bool AdjacenciesBuilder::Init(const ADJACENCIESCREATE& create)
{
if(!create.NbFaces)
return false;
// Get some bytes
mNbFaces = create.NbFaces;
mFaces = PX_NEW(AdjTriangle)[mNbFaces];
AdjEdge* Edges = PX_NEW(AdjEdge)[mNbFaces*3];
PxU32 NbEdges=0;
// Feed me with triangles.....
for(PxU32 i=0;i<mNbFaces;i++)
{
// Get correct vertex references
const PxU32 Ref0 = create.DFaces ? create.DFaces[i*3+0] : create.WFaces ? create.WFaces[i*3+0] : 0;
const PxU32 Ref1 = create.DFaces ? create.DFaces[i*3+1] : create.WFaces ? create.WFaces[i*3+1] : 1;
const PxU32 Ref2 = create.DFaces ? create.DFaces[i*3+2] : create.WFaces ? create.WFaces[i*3+2] : 2;
// Add a triangle to the database
AddTriangle(Ref0, Ref1, Ref2, i, mFaces, NbEdges, Edges);
}
// At this point of the process we have mFaces & Edges filled with input data. That is:
// - a list of triangles with 3 NULL links (i.e. PX_INVALID_U32)
// - a list of mNbFaces*3 edges, each edge having 2 vertex references and an owner face.
// Here NbEdges should be equal to mNbFaces*3.
PX_ASSERT(NbEdges==mNbFaces*3);
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
bool Status = CreateDatabase(mFaces, NbEdges, Edges);
#else
bool Status = CreateDatabase(mFaces, NbEdges, Edges, create);
#endif
// We don't need the edges anymore
PX_DELETE_ARRAY(Edges);
#ifdef MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
// Now create convex information. This creates coupling between adjacencies & edge-list but in this case it's actually the goal:
// mixing the two structures to save memory.
if(Status && create.Verts)
{
EDGELISTCREATE ELC;
ELC.NbFaces = create.NbFaces;
ELC.DFaces = create.DFaces; // That's where I like having a unified way to do things... We
ELC.WFaces = create.WFaces; // can just directly copy the same pointers.
ELC.FacesToEdges = true;
ELC.Verts = create.Verts;
ELC.Epsilon = create.Epsilon;
EdgeList EL;
if(EL.init(ELC))
{
for(PxU32 i=0;i<mNbFaces;i++)
{
const EdgeTriangleData& ET = EL.getEdgeTriangle(i);
if(EdgeTriangleAC::HasActiveEdge01(ET)) mFaces[i].mATri[EDGE01] |= 0x20000000;
else mFaces[i].mATri[EDGE01] &= ~0x20000000;
if(EdgeTriangleAC::HasActiveEdge20(ET)) mFaces[i].mATri[EDGE02] |= 0x20000000;
else mFaces[i].mATri[EDGE02] &= ~0x20000000;
if(EdgeTriangleAC::HasActiveEdge12(ET)) mFaces[i].mATri[EDGE12] |= 0x20000000;
else mFaces[i].mATri[EDGE12] &= ~0x20000000;
PX_ASSERT((EdgeTriangleAC::HasActiveEdge01(ET) && mFaces[i].HasActiveEdge01()) || (!EdgeTriangleAC::HasActiveEdge01(ET) && !mFaces[i].HasActiveEdge01()));
PX_ASSERT((EdgeTriangleAC::HasActiveEdge20(ET) && mFaces[i].HasActiveEdge20()) || (!EdgeTriangleAC::HasActiveEdge20(ET) && !mFaces[i].HasActiveEdge20()));
PX_ASSERT((EdgeTriangleAC::HasActiveEdge12(ET) && mFaces[i].HasActiveEdge12()) || (!EdgeTriangleAC::HasActiveEdge12(ET) && !mFaces[i].HasActiveEdge12()));
}
}
}
#endif
return Status;
}
/*
bool AdjacenciesBuilder::Save(Stream& stream) const
{
bool PlatformMismatch = PxPlatformMismatch();
// Export header
if(!WriteHeader('A', 'D', 'J', 'A', gVersion, PlatformMismatch, stream))
return false;
// Export adjacencies
// stream.StoreDword(mNbFaces);
WriteDword(mNbFaces, PlatformMismatch, stream);
// stream.StoreBuffer(mFaces, sizeof(AdjTriangle)*mNbFaces);
WriteDwordBuffer((const PxU32*)mFaces, mNbFaces*3, PlatformMismatch, stream);
return true;
}*/
//#endif

View File

@@ -0,0 +1,229 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_ADJACENCIES_H
#define GU_ADJACENCIES_H
#define MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
#include "GuTriangle.h"
#include "common/PxPhysXCommonConfig.h"
#include "foundation/PxIO.h"
namespace physx
{
namespace Gu
{
#ifdef MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
#define ADJ_TRIREF_MASK 0x1fffffff //!< Masks 3 bits
#define IS_CONVEX_EDGE(x) (x & 0x20000000) //!< Returns true for convex edges
#else
#define ADJ_TRIREF_MASK 0x3fffffff //!< Masks 2 bits
#endif
#define MAKE_ADJ_TRI(x) (x & ADJ_TRIREF_MASK) //!< Transforms a link into a triangle reference.
#define GET_EDGE_NB(x) (x>>30) //!< Transforms a link into a counterpart edge ID.
// #define IS_BOUNDARY(x) (x==PX_INVALID_U32) //!< Returns true for boundary edges.
#define IS_BOUNDARY(x) ((x & ADJ_TRIREF_MASK)==ADJ_TRIREF_MASK) //!< Returns true for boundary edges.
// Forward declarations
class Adjacencies;
enum SharedEdgeIndex
{
EDGE01 = 0,
EDGE02 = 1,
EDGE12 = 2
};
/* PX_INLINE void GetEdgeIndices(SharedEdgeIndex edge_index, PxU32& id0, PxU32& id1)
{
if(edge_index==0)
{
id0 = 0;
id1 = 1;
}
else if(edge_index==1)
{
id0 = 0;
id1 = 2;
}
else if(edge_index==2)
{
id0 = 1;
id1 = 2;
}
}*/
//! Sets a new edge code
#define SET_EDGE_NB(link, code) \
link&=ADJ_TRIREF_MASK; \
link|=code<<30; \
//! A triangle class used to compute the adjacency structures.
class AdjTriangle
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
: public IndexedTriangle
#else
: public PxUserAllocated
#endif
{
public:
//! Constructor
PX_INLINE AdjTriangle() {}
//! Destructor
PX_INLINE ~AdjTriangle() {}
/**
* Computes the number of boundary edges in a triangle.
* \return the number of boundary edges. (0 => 3)
*/
PxU32 ComputeNbBoundaryEdges() const;
/**
* Computes the number of valid neighbors.
* \return the number of neighbors. (0 => 3)
*/
PxU32 ComputeNbNeighbors() const;
/**
* Checks whether the triangle has a particular neighbor or not.
* \param tref [in] the triangle reference to look for
* \param index [out] the corresponding index in the triangle (NULL if not needed)
* \return true if the triangle has the given neighbor
*/
bool HasNeighbor(PxU32 tref, PxU32* index=NULL) const;
/**
* Flips the winding.
*/
void Flip();
// Data access
PX_INLINE PxU32 GetLink(SharedEdgeIndex edge_index) const { return mATri[edge_index]; }
PX_INLINE PxU32 GetAdjTri(SharedEdgeIndex edge_index) const { return MAKE_ADJ_TRI(mATri[edge_index]); }
PX_INLINE PxU32 GetAdjEdge(SharedEdgeIndex edge_index) const { return GET_EDGE_NB(mATri[edge_index]); }
PX_INLINE PxIntBool IsBoundaryEdge(SharedEdgeIndex edge_index) const { return IS_BOUNDARY(mATri[edge_index]); }
#ifdef MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
PX_INLINE PxIntBool HasActiveEdge01() const { return PxIntBool(IS_CONVEX_EDGE(mATri[EDGE01])); }
PX_INLINE PxIntBool HasActiveEdge20() const { return PxIntBool(IS_CONVEX_EDGE(mATri[EDGE02])); }
PX_INLINE PxIntBool HasActiveEdge12() const { return PxIntBool(IS_CONVEX_EDGE(mATri[EDGE12])); }
PX_INLINE PxIntBool HasActiveEdge(PxU32 i) const { return PxIntBool(IS_CONVEX_EDGE(mATri[i])); }
#endif
// private:
//! Links/References of adjacent triangles. The 2 most significant bits contains the counterpart edge in the adjacent triangle.
//! mATri[0] refers to edge 0-1
//! mATri[1] refers to edge 0-2
//! mATri[2] refers to edge 1-2
PxU32 mATri[3];
};
//! The adjacencies creation structure.
struct ADJACENCIESCREATE
{
//! Constructor
ADJACENCIESCREATE() : NbFaces(0), DFaces(NULL), WFaces(NULL)
{
#ifdef MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
Verts = NULL;
Epsilon = 0.1f;
// Epsilon = 0.001f;
#endif
}
PxU32 NbFaces; //!< Number of faces in source topo
const PxU32* DFaces; //!< List of faces (dwords) or NULL
const PxU16* WFaces; //!< List of faces (words) or NULL
#ifdef MSH_ADJACENCIES_INCLUDE_CONVEX_BITS
const PxVec3* Verts;
float Epsilon;
#endif
};
class Adjacencies : public PxUserAllocated
{
public:
Adjacencies();
~Adjacencies();
PxU32 mNbFaces; //!< Number of faces involved in the computation.
AdjTriangle* mFaces; //!< A list of AdjTriangles (one/face)
bool Load(PxInputStream& stream);
// Basic mesh walking
PX_INLINE const AdjTriangle* GetAdjacentFace(const AdjTriangle& current_tri, SharedEdgeIndex edge_nb) const
{
// No checkings here, make sure mFaces has been created
// Catch the link
PxU32 Link = current_tri.GetLink(edge_nb);
// Returns NULL for boundary edges
if(IS_BOUNDARY(Link)) return NULL;
// Else transform into face index
PxU32 Id = MAKE_ADJ_TRI(Link);
// Possible counterpart edge is:
// PxU32 Edge = GET_EDGE_NB(Link);
// And returns adjacent triangle
return &mFaces[Id];
}
// Helpers
PxU32 ComputeNbBoundaryEdges() const;
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
bool GetBoundaryVertices(PxU32 nb_verts, bool* bound_status) const;
#else
bool GetBoundaryVertices(PxU32 nb_verts, bool* bound_status, const IndexedTriangle32* faces) const;
#endif
//
#ifdef MSH_ADJACENCIES_INCLUDE_TOPOLOGY
bool MakeLastRef(AdjTriangle& cur_tri, PxU32 vref);
#else
bool MakeLastRef(AdjTriangle& cur_tri, PxU32 vref, IndexedTriangle32* cur_topo);
#endif
private:
// New edge codes assignment
void AssignNewEdgeCode(PxU32 link, PxU8 edge_nb);
};
//#ifdef PX_COOKING
class AdjacenciesBuilder : public Adjacencies
{
public:
AdjacenciesBuilder();
~AdjacenciesBuilder();
bool Init(const ADJACENCIESCREATE& create);
// bool Save(Stream& stream) const;
};
//#endif
}
}
#endif

View File

@@ -0,0 +1,84 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "GuBarycentricCoordinates.h"
using namespace physx;
using namespace aos;
void Gu::barycentricCoordinates(const Vec3VArg p, const Vec3VArg a, const Vec3VArg b, FloatV& v)
{
const Vec3V v0 = V3Sub(a, p);
const Vec3V v1 = V3Sub(b, p);
const Vec3V d = V3Sub(v1, v0);
const FloatV denominator = V3Dot(d, d);
const FloatV numerator = V3Dot(V3Neg(v0), d);
const FloatV zero = FZero();
const FloatV denom = FSel(FIsGrtr(denominator, zero), FRecip(denominator), zero);
v = FMul(numerator, denom);
}
void Gu::barycentricCoordinates(const aos::Vec3VArg p, const aos::Vec3VArg a, const aos::Vec3VArg b, const aos::Vec3VArg c, aos::FloatV& v, aos::FloatV& w)
{
const Vec3V ab = V3Sub(b, a);
const Vec3V ac = V3Sub(c, a);
const Vec3V n = V3Cross(ab, ac);
const VecCrossV crossA = V3PrepareCross(V3Sub(a, p));
const VecCrossV crossB = V3PrepareCross(V3Sub(b, p));
const VecCrossV crossC = V3PrepareCross(V3Sub(c, p));
const Vec3V bCrossC = V3Cross(crossB, crossC);
const Vec3V cCrossA = V3Cross(crossC, crossA);
const Vec3V aCrossB = V3Cross(crossA, crossB);
const FloatV va = V3Dot(n, bCrossC);//edge region of BC, signed area rbc, u = S(rbc)/S(abc) for a
const FloatV vb = V3Dot(n, cCrossA);//edge region of AC, signed area rac, v = S(rca)/S(abc) for b
const FloatV vc = V3Dot(n, aCrossB);//edge region of AB, signed area rab, w = S(rab)/S(abc) for c
const FloatV totalArea = FAdd(va, FAdd(vb, vc));
const FloatV zero = FZero();
const FloatV denom = FSel(FIsEq(totalArea, zero), zero, FRecip(totalArea));
v = FMul(vb, denom);
w = FMul(vc, denom);
}
// v0 = b - a;
// v1 = c - a;
// v2 = p - a;
void Gu::barycentricCoordinates(const Vec3VArg v0, const Vec3VArg v1, const Vec3VArg v2, FloatV& v, FloatV& w)
{
const FloatV d00 = V3Dot(v0, v0);
const FloatV d01 = V3Dot(v0, v1);
const FloatV d11 = V3Dot(v1, v1);
const FloatV d20 = V3Dot(v2, v0);
const FloatV d21 = V3Dot(v2, v1);
const FloatV denom = FRecip(FSub(FMul(d00,d11), FMul(d01, d01)));
v = FMul(FSub(FMul(d11, d20), FMul(d01, d21)), denom);
w = FMul(FSub(FMul(d00, d21), FMul(d01, d20)), denom);
}

View File

@@ -0,0 +1,91 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_BARYCENTRIC_COORDINATES_H
#define GU_BARYCENTRIC_COORDINATES_H
#include "common/PxPhysXCommonConfig.h"
#include "foundation/PxVecMath.h"
namespace physx
{
namespace Gu
{
//calculate the barycentric coorinates for a point in a segment
void barycentricCoordinates(const aos::Vec3VArg p,
const aos::Vec3VArg a,
const aos::Vec3VArg b,
aos::FloatV& v);
//calculate the barycentric coorinates for a point in a triangle
void barycentricCoordinates(const aos::Vec3VArg p,
const aos::Vec3VArg a,
const aos::Vec3VArg b,
const aos::Vec3VArg c,
aos::FloatV& v,
aos::FloatV& w);
void barycentricCoordinates(const aos::Vec3VArg v0,
const aos::Vec3VArg v1,
const aos::Vec3VArg v2,
aos::FloatV& v,
aos::FloatV& w);
PX_INLINE aos::BoolV isValidTriangleBarycentricCoord(const aos::FloatVArg v, const aos::FloatVArg w)
{
using namespace aos;
const FloatV zero = FNeg(FEps());
const FloatV one = FAdd(FOne(), FEps());
const BoolV con0 = BAnd(FIsGrtrOrEq(v, zero), FIsGrtrOrEq(one, v));
const BoolV con1 = BAnd(FIsGrtrOrEq(w, zero), FIsGrtrOrEq(one, w));
const BoolV con2 = FIsGrtr(one, FAdd(v, w));
return BAnd(con0, BAnd(con1, con2));
}
PX_INLINE aos::BoolV isValidTriangleBarycentricCoord2(const aos::Vec4VArg vwvw)
{
using namespace aos;
const Vec4V eps = V4Splat(FEps());
const Vec4V zero = V4Neg(eps);
const Vec4V one = V4Add(V4One(), eps);
const Vec4V v0v1v0v1 = V4PermXZXZ(vwvw);
const Vec4V w0w1w0w1 = V4PermYWYW(vwvw);
const BoolV con0 = BAnd(V4IsGrtrOrEq(v0v1v0v1, zero), V4IsGrtrOrEq(one, v0v1v0v1));
const BoolV con1 = BAnd(V4IsGrtrOrEq(w0w1w0w1, zero), V4IsGrtrOrEq(one, w0w1w0w1));
const BoolV con2 = V4IsGrtr(one, V4Add(v0v1v0v1, w0w1w0w1));
return BAnd(con0, BAnd(con1, con2));
}
} // namespace Gu
}
#endif

View File

@@ -0,0 +1,120 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_BOX_CONVERSION_H
#define GU_BOX_CONVERSION_H
#include "GuBox.h"
#include "foundation/PxMathUtils.h"
#include "foundation/PxMat34.h"
#include "foundation/PxVecMath.h"
namespace physx
{
// PT: builds rot from quat. WARNING: writes 4 bytes after 'dst.rot'.
PX_FORCE_INLINE void buildFrom(Gu::Box& dst, const PxQuat& q)
{
using namespace aos;
const QuatV qV = V4LoadU(&q.x);
Vec3V column0, column1, column2;
QuatGetMat33V(qV, column0, column1, column2);
// PT: TODO: investigate if these overlapping stores are a problem
V4StoreU(Vec4V_From_Vec3V(column0), &dst.rot.column0.x);
V4StoreU(Vec4V_From_Vec3V(column1), &dst.rot.column1.x);
V4StoreU(Vec4V_From_Vec3V(column2), &dst.rot.column2.x);
}
PX_FORCE_INLINE void buildFrom(Gu::Box& dst, const PxVec3& center, const PxVec3& extents, const PxQuat& q)
{
using namespace aos;
// PT: writes 4 bytes after 'rot' but it's safe since we then write 'center' just afterwards
buildFrom(dst, q);
dst.center = center;
dst.extents = extents;
}
PX_FORCE_INLINE void buildMatrixFromBox(PxMat34& mat34, const Gu::Box& box)
{
mat34.m = box.rot;
mat34.p = box.center;
}
// SD: function is now the same as FastVertex2ShapeScaling::transformQueryBounds
// PT: lots of LHS in that one. TODO: revisit...
PX_INLINE Gu::Box transform(const PxMat34& transfo, const Gu::Box& box)
{
Gu::Box ret;
PxMat33& obbBasis = ret.rot;
obbBasis.column0 = transfo.rotate(box.rot.column0 * box.extents.x);
obbBasis.column1 = transfo.rotate(box.rot.column1 * box.extents.y);
obbBasis.column2 = transfo.rotate(box.rot.column2 * box.extents.z);
ret.center = transfo.transform(box.center);
ret.extents = PxOptimizeBoundingBox(obbBasis);
return ret;
}
PX_INLINE Gu::Box transformBoxOrthonormal(const Gu::Box& box, const PxTransform& t)
{
Gu::Box ret;
PxMat33& obbBasis = ret.rot;
obbBasis.column0 = t.rotate(box.rot.column0);
obbBasis.column1 = t.rotate(box.rot.column1);
obbBasis.column2 = t.rotate(box.rot.column2);
ret.center = t.transform(box.center);
ret.extents = box.extents;
return ret;
}
/**
\brief recomputes the OBB after an arbitrary transform by a 4x4 matrix.
\param mtx [in] the transform matrix
\param obb [out] the transformed OBB
*/
PX_INLINE void rotate(const Gu::Box& src, const PxMat34& mtx, Gu::Box& obb)
{
// The extents remain constant
obb.extents = src.extents;
// The center gets x-formed
obb.center = mtx.transform(src.center);
// Combine rotations
obb.rot = mtx.m * src.rot;
}
// PT: TODO: move this to a better place
PX_FORCE_INLINE void getInverse(PxMat33& dstRot, PxVec3& dstTrans, const PxMat33& srcRot, const PxVec3& srcTrans)
{
const PxMat33 invRot = srcRot.getInverse();
dstTrans = invRot.transform(-srcTrans);
dstRot = invRot;
}
}
#endif

View File

@@ -0,0 +1,84 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_EDGECACHE_H
#define GU_EDGECACHE_H
#include "foundation/PxMemory.h"
#include "foundation/PxAllocator.h"
#include "foundation/PxHash.h"
namespace physx
{
namespace Gu
{
class EdgeCache
{
#define NUM_EDGES_IN_CACHE 64 //must be power of 2. 32 lines result in 10% extra work (due to cache misses), 64 lines in 6% extra work, 128 lines in 4%.
public:
EdgeCache()
{
PxMemZero(cacheLines, NUM_EDGES_IN_CACHE*sizeof(CacheLine));
}
PxU32 hash(PxU32 key) const
{
return (NUM_EDGES_IN_CACHE - 1) & PxComputeHash(key); //Only a 16 bit hash would be needed here.
}
bool isInCache(PxU8 vertex0, PxU8 vertex1)
{
PX_ASSERT(vertex1 >= vertex0);
PxU16 key = PxU16((vertex0 << 8) | vertex1);
PxU32 h = hash(key);
CacheLine& cl = cacheLines[h];
if (cl.fullKey == key)
{
return true;
}
else //cache the line now as it's about to be processed
{
cl.fullKey = key;
return false;
}
}
private:
struct CacheLine
{
PxU16 fullKey;
};
CacheLine cacheLines[NUM_EDGES_IN_CACHE];
#undef NUM_EDGES_IN_CACHE
};
}
}
#endif

View File

@@ -0,0 +1,719 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "foundation/PxMemory.h"
#include "geometry/PxTriangle.h"
#include "GuEdgeList.h"
#include "foundation/PxMathUtils.h"
#include "foundation/PxPlane.h"
#include "CmRadixSort.h"
#include "CmSerialize.h"
// PT: code archeology: this initially came from ICE (IceEdgeList.h/cpp). Consider putting it back the way it was initially.
// It makes little sense that something like EdgeList is in GeomUtils but some equivalent class like Adjacencies in is Cooking.
using namespace physx;
using namespace Gu;
using namespace Cm;
///////////////////////////////////////////////////////////////////////////////
PX_IMPLEMENT_OUTPUT_ERROR
///////////////////////////////////////////////////////////////////////////////
EdgeList::EdgeList() :
mNbEdges (0),
mEdges (NULL),
mNbFaces (0),
mEdgeFaces (NULL),
mEdgeToTriangles (NULL),
mFacesByEdges (NULL)
{
}
EdgeList::~EdgeList()
{
PX_FREE(mFacesByEdges);
PX_FREE(mEdgeToTriangles);
PX_FREE(mEdges);
PX_FREE(mEdgeFaces);
}
bool EdgeList::load(PxInputStream& stream)
{
// Import header
PxU32 Version;
bool Mismatch;
if(!ReadHeader('E', 'D', 'G', 'E', Version, Mismatch, stream))
return false;
// Import edges
mNbEdges = readDword(Mismatch, stream);
mEdges = PX_ALLOCATE(EdgeData, mNbEdges, "EdgeData");
stream.read(mEdges, sizeof(EdgeData)*mNbEdges);
mNbFaces = readDword(Mismatch, stream);
mEdgeFaces = PX_ALLOCATE(EdgeTriangleData, mNbFaces, "EdgeTriangleData");
stream.read(mEdgeFaces, sizeof(EdgeTriangleData)*mNbFaces);
mEdgeToTriangles = PX_ALLOCATE(EdgeDescData, mNbEdges, "EdgeDescData");
stream.read(mEdgeToTriangles, sizeof(EdgeDescData)*mNbEdges);
PxU32 LastOffset = mEdgeToTriangles[mNbEdges-1].Offset + mEdgeToTriangles[mNbEdges-1].Count;
mFacesByEdges = PX_ALLOCATE(PxU32, LastOffset, "EdgeList FacesByEdges");
stream.read(mFacesByEdges, sizeof(PxU32)*LastOffset);
return true;
}
/**
* Initializes the edge-list.
* \param create [in] edge-list creation structure
* \return true if success.
*/
bool EdgeList::init(const EDGELISTCREATE& create)
{
const bool FacesToEdges = create.Verts ? true : create.FacesToEdges;
const bool EdgesToFaces = create.Verts ? true : create.EdgesToFaces;
// "FacesToEdges" maps each face to three edges.
if(FacesToEdges && !createFacesToEdges(create.NbFaces, create.DFaces, create.WFaces))
return false;
// "EdgesToFaces" maps each edge to the set of faces sharing this edge
if(EdgesToFaces && !createEdgesToFaces(create.NbFaces, create.DFaces, create.WFaces))
return false;
// Create active edges
if(create.Verts && !computeActiveEdges(create.NbFaces, create.DFaces, create.WFaces, create.Verts, create.Epsilon))
return false;
// Get rid of useless data
if(!create.FacesToEdges)
PX_FREE(mEdgeFaces);
if(!create.EdgesToFaces)
{
PX_FREE(mEdgeToTriangles);
PX_FREE(mFacesByEdges);
}
return true;
}
/**
* Computes FacesToEdges.
* After the call:
* - mNbEdges is updated with the number of non-redundant edges
* - mEdges is a list of mNbEdges edges (one edge is 2 vertex-references)
* - mEdgesRef is a list of nbfaces structures with 3 indexes in mEdges for each face
*
* \param nb_faces [in] a number of triangles
* \param dfaces [in] list of triangles with PxU32 vertex references (or NULL)
* \param wfaces [in] list of triangles with PxU16 vertex references (or NULL)
* \return true if success.
*/
bool EdgeList::createFacesToEdges(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces)
{
if(!nb_faces || (!dfaces && !wfaces))
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "EdgeList::CreateFacesToEdges: NULL parameter!");
if(mEdgeFaces)
return true; // Already computed!
// 1) Get some bytes: I need one EdgesRefs for each face, and some temp buffers
mEdgeFaces = PX_ALLOCATE(EdgeTriangleData, nb_faces, "mEdgeFaces"); // Link faces to edges
PxU32* VRefs0 = PX_ALLOCATE(PxU32, nb_faces*3, "Tmp"); // Temp storage
PxU32* VRefs1 = PX_ALLOCATE(PxU32, nb_faces*3, "Tmp"); // Temp storage
EdgeData* Buffer = PX_ALLOCATE(EdgeData, nb_faces*3, "Tmp"); // Temp storage
// 2) Create a full redundant list of 3 edges / face.
for(PxU32 i=0;i<nb_faces;i++)
{
// Get right vertex-references
const PxU32 Ref0 = dfaces ? dfaces[i*3+0] : wfaces ? wfaces[i*3+0] : 0;
const PxU32 Ref1 = dfaces ? dfaces[i*3+1] : wfaces ? wfaces[i*3+1] : 1;
const PxU32 Ref2 = dfaces ? dfaces[i*3+2] : wfaces ? wfaces[i*3+2] : 2;
// Pre-Sort vertex-references and put them in the lists
if(Ref0<Ref1) { VRefs0[i*3+0] = Ref0; VRefs1[i*3+0] = Ref1; } // Edge 0-1 maps (i%3)
else { VRefs0[i*3+0] = Ref1; VRefs1[i*3+0] = Ref0; } // Edge 0-1 maps (i%3)
if(Ref1<Ref2) { VRefs0[i*3+1] = Ref1; VRefs1[i*3+1] = Ref2; } // Edge 1-2 maps (i%3)+1
else { VRefs0[i*3+1] = Ref2; VRefs1[i*3+1] = Ref1; } // Edge 1-2 maps (i%3)+1
if(Ref2<Ref0) { VRefs0[i*3+2] = Ref2; VRefs1[i*3+2] = Ref0; } // Edge 2-0 maps (i%3)+2
else { VRefs0[i*3+2] = Ref0; VRefs1[i*3+2] = Ref2; } // Edge 2-0 maps (i%3)+2
}
// 3) Sort the list according to both keys (VRefs0 and VRefs1)
Cm::RadixSortBuffered Sorter;
const PxU32* Sorted = Sorter.Sort(VRefs1, nb_faces*3).Sort(VRefs0, nb_faces*3).GetRanks();
// 4) Loop through all possible edges
// - clean edges list by removing redundant edges
// - create EdgesRef list
mNbEdges = 0; // #non-redundant edges
mNbFaces = nb_faces;
PxU32 PreviousRef0 = PX_INVALID_U32;
PxU32 PreviousRef1 = PX_INVALID_U32;
for(PxU32 i=0;i<nb_faces*3;i++)
{
PxU32 Face = Sorted[i]; // Between 0 and nbfaces*3
PxU32 ID = Face % 3; // Get edge ID back.
PxU32 SortedRef0 = VRefs0[Face]; // (SortedRef0, SortedRef1) is the sorted edge
PxU32 SortedRef1 = VRefs1[Face];
if(SortedRef0!=PreviousRef0 || SortedRef1!=PreviousRef1)
{
// New edge found! => stored in temp buffer
Buffer[mNbEdges].Ref0 = SortedRef0;
Buffer[mNbEdges].Ref1 = SortedRef1;
mNbEdges++;
}
PreviousRef0 = SortedRef0;
PreviousRef1 = SortedRef1;
// Create mEdgesRef on the fly
mEdgeFaces[Face/3].mLink[ID] = mNbEdges-1;
}
// 5) Here, mNbEdges==#non redundant edges
mEdges = PX_ALLOCATE(EdgeData, mNbEdges, "EdgeData");
// Create real edges-list.
PxMemCopy(mEdges, Buffer, mNbEdges*sizeof(EdgeData));
// 6) Free ram and exit
PX_FREE(Buffer);
PX_FREE(VRefs1);
PX_FREE(VRefs0);
return true;
}
/**
* Computes EdgesToFaces.
* After the call:
* - mEdgeToTriangles is created
* - mFacesByEdges is created
*
* \param nb_faces [in] a number of triangles
* \param dfaces [in] list of triangles with PxU32 vertex references (or NULL)
* \param wfaces [in] list of triangles with PxU16 vertex references (or NULL)
* \return true if success.
*/
bool EdgeList::createEdgesToFaces(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces)
{
// 1) I need FacesToEdges !
if(!createFacesToEdges(nb_faces, dfaces, wfaces))
return false;
// 2) Get some bytes: one Pair structure / edge
mEdgeToTriangles = PX_ALLOCATE(EdgeDescData, mNbEdges, "EdgeDescData");
PxMemZero(mEdgeToTriangles, sizeof(EdgeDescData)*mNbEdges);
// 3) Create Counters, ie compute the #faces sharing each edge
for(PxU32 i=0;i<nb_faces;i++)
{
mEdgeToTriangles[mEdgeFaces[i].mLink[0]].Count++;
mEdgeToTriangles[mEdgeFaces[i].mLink[1]].Count++;
mEdgeToTriangles[mEdgeFaces[i].mLink[2]].Count++;
}
// 3) Create Radix-like Offsets
mEdgeToTriangles[0].Offset=0;
for(PxU32 i=1;i<mNbEdges;i++)
mEdgeToTriangles[i].Offset = mEdgeToTriangles[i-1].Offset + mEdgeToTriangles[i-1].Count;
const PxU32 LastOffset = mEdgeToTriangles[mNbEdges-1].Offset + mEdgeToTriangles[mNbEdges-1].Count;
// 4) Get some bytes for mFacesByEdges. LastOffset is the number of indices needed.
mFacesByEdges = PX_ALLOCATE(PxU32, LastOffset, "EdgeList FacesByEdges");
// 5) Create mFacesByEdges
for(PxU32 i=0;i<nb_faces;i++)
{
mFacesByEdges[mEdgeToTriangles[mEdgeFaces[i].mLink[0]].Offset++] = i;
mFacesByEdges[mEdgeToTriangles[mEdgeFaces[i].mLink[1]].Offset++] = i;
mFacesByEdges[mEdgeToTriangles[mEdgeFaces[i].mLink[2]].Offset++] = i;
}
// 6) Recompute offsets wasted by 5)
mEdgeToTriangles[0].Offset=0;
for(PxU32 i=1;i<mNbEdges;i++)
mEdgeToTriangles[i].Offset = mEdgeToTriangles[i-1].Offset + mEdgeToTriangles[i-1].Count;
return true;
}
static PX_INLINE PxU32 OppositeVertex(PxU32 r0, PxU32 r1, PxU32 r2, PxU32 vref0, PxU32 vref1)
{
if(vref0==r0)
{
if (vref1==r1) return r2;
else if(vref1==r2) return r1;
}
else if(vref0==r1)
{
if (vref1==r0) return r2;
else if(vref1==r2) return r0;
}
else if(vref0==r2)
{
if (vref1==r1) return r0;
else if(vref1==r0) return r1;
}
return PX_INVALID_U32;
}
bool EdgeList::computeActiveEdges(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces, const PxVec3* verts, float epsilon)
{
if(!verts || (!dfaces && !wfaces))
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "EdgeList::ComputeActiveEdges: NULL parameter!");
PxU32 NbEdges = getNbEdges();
if(!NbEdges)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "ActiveEdges::ComputeConvexEdges: no edges in edge list!");
const EdgeData* Edges = getEdges();
if(!Edges)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "ActiveEdges::ComputeConvexEdges: no edge data in edge list!");
const EdgeDescData* ED = getEdgeToTriangles();
if(!ED)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "ActiveEdges::ComputeConvexEdges: no edge-to-triangle in edge list!");
const PxU32* FBE = getFacesByEdges();
if(!FBE)
return outputError<PxErrorCode::eINVALID_OPERATION>(__LINE__, "ActiveEdges::ComputeConvexEdges: no faces-by-edges in edge list!");
// We first create active edges in a temporaray buffer. We have one bool / edge.
bool* ActiveEdges = PX_ALLOCATE(bool, NbEdges, "bool");
// Loop through edges and look for convex ones
bool* CurrentMark = ActiveEdges;
while(NbEdges--)
{
// Get number of triangles sharing current edge
const PxU32 Count = ED->Count;
// Boundary edges are active => keep them (actually they're silhouette edges directly)
// Internal edges can be active => test them
// Singular edges ? => discard them
bool Active = false;
if(Count==1)
{
Active = true;
}
else if(Count==2)
{
const PxU32 FaceIndex0 = FBE[ED->Offset+0]*3;
const PxU32 FaceIndex1 = FBE[ED->Offset+1]*3;
PxU32 VRef00, VRef01, VRef02;
PxU32 VRef10, VRef11, VRef12;
if(dfaces)
{
VRef00 = dfaces[FaceIndex0+0];
VRef01 = dfaces[FaceIndex0+1];
VRef02 = dfaces[FaceIndex0+2];
VRef10 = dfaces[FaceIndex1+0];
VRef11 = dfaces[FaceIndex1+1];
VRef12 = dfaces[FaceIndex1+2];
}
else //if(wfaces)
{
PX_ASSERT(wfaces);
VRef00 = wfaces[FaceIndex0+0];
VRef01 = wfaces[FaceIndex0+1];
VRef02 = wfaces[FaceIndex0+2];
VRef10 = wfaces[FaceIndex1+0];
VRef11 = wfaces[FaceIndex1+1];
VRef12 = wfaces[FaceIndex1+2];
}
{
// We first check the opposite vertex against the plane
const PxU32 Op = OppositeVertex(VRef00, VRef01, VRef02, Edges->Ref0, Edges->Ref1);
const PxPlane PL1(verts[VRef10], verts[VRef11], verts[VRef12]);
if(PL1.distance(verts[Op])<0.0f) // If opposite vertex is below the plane, i.e. we discard concave edges
{
const PxTriangle T0(verts[VRef00], verts[VRef01], verts[VRef02]);
const PxTriangle T1(verts[VRef10], verts[VRef11], verts[VRef12]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
const float a = PxComputeAngle(N0, N1);
if(fabsf(a)>epsilon)
Active = true;
}
else
{
const PxTriangle T0(verts[VRef00], verts[VRef01], verts[VRef02]);
const PxTriangle T1(verts[VRef10], verts[VRef11], verts[VRef12]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
if(N0.dot(N1) < -0.999f)
Active = true;
}
//Active = true;
}
}
else
{
//Connected to more than 2
//We need to loop through the triangles and count the number of unique triangles (considering back-face triangles as non-unique). If we end up with more than 2 unique triangles,
//then by definition this is an inactive edge. However, if we end up with 2 unique triangles (say like a double-sided tesselated surface), then it depends on the same rules as above
const PxU32 FaceInd0 = FBE[ED->Offset]*3;
PxU32 VRef00, VRef01, VRef02;
PxU32 VRef10=0, VRef11=0, VRef12=0;
if(dfaces)
{
VRef00 = dfaces[FaceInd0+0];
VRef01 = dfaces[FaceInd0+1];
VRef02 = dfaces[FaceInd0+2];
}
else //if(wfaces)
{
PX_ASSERT(wfaces);
VRef00 = wfaces[FaceInd0+0];
VRef01 = wfaces[FaceInd0+1];
VRef02 = wfaces[FaceInd0+2];
}
PxU32 numUniqueTriangles = 1;
bool doubleSided0 = false;
bool doubleSided1 = 0;
for(PxU32 a = 1; a < Count; ++a)
{
const PxU32 FaceInd = FBE[ED->Offset+a]*3;
PxU32 VRef0, VRef1, VRef2;
if(dfaces)
{
VRef0 = dfaces[FaceInd+0];
VRef1 = dfaces[FaceInd+1];
VRef2 = dfaces[FaceInd+2];
}
else //if(wfaces)
{
PX_ASSERT(wfaces);
VRef0 = wfaces[FaceInd+0];
VRef1 = wfaces[FaceInd+1];
VRef2 = wfaces[FaceInd+2];
}
if(((VRef0 != VRef00) && (VRef0 != VRef01) && (VRef0 != VRef02)) ||
((VRef1 != VRef00) && (VRef1 != VRef01) && (VRef1 != VRef02)) ||
((VRef2 != VRef00) && (VRef2 != VRef01) && (VRef2 != VRef02)))
{
//Not the same as trig 0
if(numUniqueTriangles == 2)
{
if(((VRef0 != VRef10) && (VRef0 != VRef11) && (VRef0 != VRef12)) ||
((VRef1 != VRef10) && (VRef1 != VRef11) && (VRef1 != VRef12)) ||
((VRef2 != VRef10) && (VRef2 != VRef11) && (VRef2 != VRef12)))
{
//Too many unique triangles - terminate and mark as inactive
numUniqueTriangles++;
break;
}
else
{
const PxTriangle T0(verts[VRef10], verts[VRef11], verts[VRef12]);
const PxTriangle T1(verts[VRef0], verts[VRef1], verts[VRef2]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
if(N0.dot(N1) < -0.999f)
doubleSided1 = true;
}
}
else
{
VRef10 = VRef0;
VRef11 = VRef1;
VRef12 = VRef2;
numUniqueTriangles++;
}
}
else
{
//Check for double sided...
const PxTriangle T0(verts[VRef00], verts[VRef01], verts[VRef02]);
const PxTriangle T1(verts[VRef0], verts[VRef1], verts[VRef2]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
if(N0.dot(N1) < -0.999f)
doubleSided0 = true;
}
}
if(numUniqueTriangles == 1)
Active = true;
if(numUniqueTriangles == 2)
{
//Potentially active. Let's check the angles between the surfaces...
if(doubleSided0 || doubleSided1)
{
// Plane PL1 = faces[FBE[ED->Offset+1]].PlaneEquation(verts);
const PxPlane PL1(verts[VRef10], verts[VRef11], verts[VRef12]);
// if(PL1.Distance(verts[Op])<-epsilon) Active = true;
//if(PL1.distance(verts[Op])<0.0f) // If opposite vertex is below the plane, i.e. we discard concave edges
//KS - can't test signed distance for concave edges. This is a double-sided poly
{
const PxTriangle T0(verts[VRef00], verts[VRef01], verts[VRef02]);
const PxTriangle T1(verts[VRef10], verts[VRef11], verts[VRef12]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
const float a = PxComputeAngle(N0, N1);
if(fabsf(a)>epsilon)
Active = true;
}
}
else
{
//Not double sided...must have had a bunch of duplicate triangles!!!!
//Treat as normal
const PxU32 Op = OppositeVertex(VRef00, VRef01, VRef02, Edges->Ref0, Edges->Ref1);
// Plane PL1 = faces[FBE[ED->Offset+1]].PlaneEquation(verts);
const PxPlane PL1(verts[VRef10], verts[VRef11], verts[VRef12]);
// if(PL1.Distance(verts[Op])<-epsilon) Active = true;
if(PL1.distance(verts[Op])<0.0f) // If opposite vertex is below the plane, i.e. we discard concave edges
{
const PxTriangle T0(verts[VRef00], verts[VRef01], verts[VRef02]);
const PxTriangle T1(verts[VRef10], verts[VRef11], verts[VRef12]);
PxVec3 N0, N1;
T0.normal(N0);
T1.normal(N1);
const float a = PxComputeAngle(N0, N1);
if(fabsf(a)>epsilon)
Active = true;
}
}
}
else
{
//Lots of triangles all smooshed together. Just activate the edge in this case
Active = true;
}
}
*CurrentMark++ = Active;
ED++;
Edges++;
}
// Now copy bits back into already existing edge structures
// - first in edge triangles
for(PxU32 i=0;i<mNbFaces;i++)
{
EdgeTriangleData& ET = mEdgeFaces[i];
for(PxU32 j=0;j<3;j++)
{
const PxU32 Link = ET.mLink[j];
if(!(Link & MSH_ACTIVE_EDGE_MASK)) // else already active
{
if(ActiveEdges[Link & MSH_EDGE_LINK_MASK])
ET.mLink[j] |= MSH_ACTIVE_EDGE_MASK; // Mark as active
}
}
}
// - then in edge-to-faces
for(PxU32 i=0;i<mNbEdges;i++)
{
if(ActiveEdges[i])
mEdgeToTriangles[i].Flags |= PX_EDGE_ACTIVE;
}
// Free & exit
PX_FREE(ActiveEdges);
if(0) // PT: this is not needed anymore
{
//initially all vertices are flagged to ignore them. (we assume them to be flat)
//for all NONFLAT edges, incl boundary
//unflag 2 vertices in up to 2 trigs as perhaps interesting
//for all CONCAVE edges
//flag 2 vertices in up to 2 trigs to ignore them.
// Handle active vertices
PxU32 MaxIndex = 0;
for(PxU32 i=0;i<nb_faces;i++)
{
PxU32 VRef0, VRef1, VRef2;
if(dfaces)
{
VRef0 = dfaces[i*3+0];
VRef1 = dfaces[i*3+1];
VRef2 = dfaces[i*3+2];
}
else //if(wfaces)
{
PX_ASSERT(wfaces);
VRef0 = wfaces[i*3+0];
VRef1 = wfaces[i*3+1];
VRef2 = wfaces[i*3+2];
}
if(VRef0>MaxIndex) MaxIndex = VRef0;
if(VRef1>MaxIndex) MaxIndex = VRef1;
if(VRef2>MaxIndex) MaxIndex = VRef2;
}
MaxIndex++;
bool* ActiveVerts = PX_ALLOCATE(bool, MaxIndex, "bool");
PxMemZero(ActiveVerts, MaxIndex*sizeof(bool));
PX_ASSERT(dfaces || wfaces);
for(PxU32 i=0;i<mNbFaces;i++)
{
PxU32 VRef[3];
if(dfaces)
{
VRef[0] = dfaces[i*3+0];
VRef[1] = dfaces[i*3+1];
VRef[2] = dfaces[i*3+2];
}
else if(wfaces)
{
VRef[0] = wfaces[i*3+0];
VRef[1] = wfaces[i*3+1];
VRef[2] = wfaces[i*3+2];
}
const EdgeTriangleData& ET = mEdgeFaces[i];
for(PxU32 j=0;j<3;j++)
{
PxU32 Link = ET.mLink[j];
if(Link & MSH_ACTIVE_EDGE_MASK)
{
// Active edge => mark edge vertices as active
PxU32 r0, r1;
if(j==0) { r0=0; r1=1; }
else if(j==1) { r0=1; r1=2; }
else /*if(j==2)*/ { PX_ASSERT(j==2); r0=0; r1=2; }
ActiveVerts[VRef[r0]] = ActiveVerts[VRef[r1]] = true;
}
}
}
/* for(PxU32 i=0;i<mNbFaces;i++)
{
PxU32 VRef[3];
if(dfaces)
{
VRef[0] = dfaces[i*3+0];
VRef[1] = dfaces[i*3+1];
VRef[2] = dfaces[i*3+2];
}
else if(wfaces)
{
VRef[0] = wfaces[i*3+0];
VRef[1] = wfaces[i*3+1];
VRef[2] = wfaces[i*3+2];
}
const EdgeTriangle& ET = mEdgeFaces[i];
for(PxU32 j=0;j<3;j++)
{
PxU32 Link = ET.mLink[j];
if(!(Link & MSH_ACTIVE_EDGE_MASK))
{
// Inactive edge => mark edge vertices as inactive
PxU32 r0, r1;
if(j==0) { r0=0; r1=1; }
if(j==1) { r0=1; r1=2; }
if(j==2) { r0=0; r1=2; }
ActiveVerts[VRef[r0]] = ActiveVerts[VRef[r1]] = false;
}
}
}*/
// Now stuff this into the structure
for(PxU32 i=0;i<mNbFaces;i++)
{
PxU32 VRef[3];
if(dfaces)
{
VRef[0] = dfaces[i*3+0];
VRef[1] = dfaces[i*3+1];
VRef[2] = dfaces[i*3+2];
}
else if(wfaces)
{
VRef[0] = wfaces[i*3+0];
VRef[1] = wfaces[i*3+1];
VRef[2] = wfaces[i*3+2];
}
EdgeTriangleData& ET = mEdgeFaces[i];
for(PxU32 j=0;j<3;j++)
{
const PxU32 Link = ET.mLink[j];
if(!(Link & MSH_ACTIVE_VERTEX_MASK)) // else already active
{
if(ActiveVerts[VRef[j]])
ET.mLink[j] |= MSH_ACTIVE_VERTEX_MASK; // Mark as active
}
}
}
PX_FREE(ActiveVerts);
}
return true;
}

View File

@@ -0,0 +1,177 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_EDGE_LIST_H
#define GU_EDGE_LIST_H
#include "foundation/PxSimpleTypes.h"
#include "common/PxPhysXCommonConfig.h"
#include "foundation/PxIO.h"
#include "foundation/PxVec3.h"
#include "foundation/PxUserAllocated.h"
namespace physx
{
namespace Gu
{
enum EdgeType
{
PX_EDGE_UNDEFINED,
PX_EDGE_BOUNDARY, //!< Edge belongs to a single triangle
PX_EDGE_INTERNAL, //!< Edge belongs to exactly two triangles
PX_EDGE_SINGULAR, //!< Edge belongs to three or more triangles
PX_EDGE_FORCE_DWORD = 0x7fffffff
};
enum EdgeFlag
{
PX_EDGE_ACTIVE = (1<<0)
};
//! Basic edge-data
struct EdgeData
{
PxU32 Ref0; //!< First vertex reference
PxU32 Ref1; //!< Second vertex reference
};
PX_COMPILE_TIME_ASSERT(sizeof(EdgeData) == 8);
//! Basic edge-data using 8-bit references
struct Edge8Data
{
PxU8 Ref0; //!< First vertex reference
PxU8 Ref1; //!< Second vertex reference
};
PX_COMPILE_TIME_ASSERT(sizeof(Edge8Data) == 2);
//! A count/offset pair = an edge descriptor
struct EdgeDescData
{
PxU16 Flags;
PxU16 Count;
PxU32 Offset;
};
PX_COMPILE_TIME_ASSERT(sizeof(EdgeDescData) == 8);
//! Edge<->triangle mapping
struct EdgeTriangleData
{
PxU32 mLink[3];
};
PX_COMPILE_TIME_ASSERT(sizeof(EdgeTriangleData) == 12);
enum
{
MSH_EDGE_LINK_MASK = 0x0fffffff,
MSH_ACTIVE_EDGE_MASK = 0x80000000,
MSH_ACTIVE_VERTEX_MASK = 0x40000000
};
class EdgeTriangleAC
{
public:
PX_INLINE static PxU32 GetEdge01(const EdgeTriangleData& data) { return data.mLink[0] & MSH_EDGE_LINK_MASK; }
PX_INLINE static PxU32 GetEdge12(const EdgeTriangleData& data) { return data.mLink[1] & MSH_EDGE_LINK_MASK; }
PX_INLINE static PxU32 GetEdge20(const EdgeTriangleData& data) { return data.mLink[2] & MSH_EDGE_LINK_MASK; }
PX_INLINE static PxU32 GetEdge(const EdgeTriangleData& data, PxU32 i) { return data.mLink[i] & MSH_EDGE_LINK_MASK; }
PX_INLINE static PxIntBool HasActiveEdge01(const EdgeTriangleData& data) { return PxIntBool(data.mLink[0] & MSH_ACTIVE_EDGE_MASK); }
PX_INLINE static PxIntBool HasActiveEdge12(const EdgeTriangleData& data) { return PxIntBool(data.mLink[1] & MSH_ACTIVE_EDGE_MASK); }
PX_INLINE static PxIntBool HasActiveEdge20(const EdgeTriangleData& data) { return PxIntBool(data.mLink[2] & MSH_ACTIVE_EDGE_MASK); }
PX_INLINE static PxIntBool HasActiveEdge(const EdgeTriangleData& data, PxU32 i) { return PxIntBool(data.mLink[i] & MSH_ACTIVE_EDGE_MASK); }
};
//! The edge-list creation structure.
struct EDGELISTCREATE
{
EDGELISTCREATE() :
NbFaces (0),
DFaces (NULL),
WFaces (NULL),
FacesToEdges (false),
EdgesToFaces (false),
Verts (NULL),
Epsilon (0.1f)
{}
PxU32 NbFaces; //!< Number of faces in source topo
const PxU32* DFaces; //!< List of faces (dwords) or NULL
const PxU16* WFaces; //!< List of faces (words) or NULL
bool FacesToEdges;
bool EdgesToFaces;
const PxVec3* Verts;
float Epsilon;
};
class EdgeList : public PxUserAllocated
{
public:
EdgeList();
~EdgeList();
bool init(const EDGELISTCREATE& create);
bool load(PxInputStream& stream);
PX_FORCE_INLINE PxU32 getNbEdges() const { return mNbEdges; }
PX_FORCE_INLINE const EdgeData* getEdges() const { return mEdges; }
PX_FORCE_INLINE const EdgeData& getEdge(PxU32 edge_index) const { return mEdges[edge_index]; }
PX_FORCE_INLINE PxU32 getNbFaces() const { return mNbFaces; }
PX_FORCE_INLINE const EdgeTriangleData* getEdgeTriangles() const { return mEdgeFaces; }
PX_FORCE_INLINE const EdgeTriangleData& getEdgeTriangle(PxU32 face_index) const { return mEdgeFaces[face_index]; }
PX_FORCE_INLINE const EdgeDescData* getEdgeToTriangles() const { return mEdgeToTriangles; }
PX_FORCE_INLINE const EdgeDescData& getEdgeToTriangles(PxU32 edge_index) const { return mEdgeToTriangles[edge_index]; }
PX_FORCE_INLINE const PxU32* getFacesByEdges() const { return mFacesByEdges; }
PX_FORCE_INLINE PxU32 getFacesByEdges(PxU32 face_index) const { return mFacesByEdges[face_index]; }
private:
// The edge list
PxU32 mNbEdges; //!< Number of edges in the list
EdgeData* mEdges; //!< List of edges
// Faces to edges
PxU32 mNbFaces; //!< Number of faces for which we have data
EdgeTriangleData* mEdgeFaces; //!< Array of edge-triangles referencing mEdges
// Edges to faces
EdgeDescData* mEdgeToTriangles; //!< An EdgeDesc structure for each edge
PxU32* mFacesByEdges; //!< A pool of face indices
bool createFacesToEdges(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces);
bool createEdgesToFaces(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces);
bool computeActiveEdges(PxU32 nb_faces, const PxU32* dfaces, const PxU16* wfaces, const PxVec3* verts, float epsilon);
};
} // namespace Gu
}
#endif

View File

@@ -0,0 +1,221 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "foundation/PxVec3.h"
#include "foundation/PxArray.h"
#include "GuMeshAnalysis.h"
using namespace physx;
using namespace Gu;
PX_FORCE_INLINE PxU64 key(PxI32 a, PxI32 b)
{
if (a < b)
return ((PxU64(a)) << 32) | (PxU64(b));
else
return ((PxU64(b)) << 32) | (PxU64(a));
}
#define INITIAL_VALUE -3
const static PxU32 neighborEdges[3][2] = { { 0, 1 }, { 2, 0 }, { 1, 2 } };
//const static PxU32 triTip[3] = { 2, 1, 0 };
bool MeshAnalyzer::buildTriangleAdjacency(const Triangle* tris, PxU32 numTriangles, PxArray<PxI32>& result, PxHashMap<PxU64, PxI32>& edges)
{
PxU32 l = 4 * numTriangles; //Still factor 4 - waste one entry per triangle to get a power of 2 which allows for bit shift usage instead of modulo
result.clear();
result.resize(l, -1);
for (PxU32 i = 3; i < l; i += 4)
result[i] = INITIAL_VALUE; //Mark the fields that get never accessed because they are just not used, this is useful for debugging
edges.clear();
for (PxU32 i = 0; i < numTriangles; ++i)
{
const Triangle& tri = tris[i];
if (tri[0] < 0)
continue;
for (PxU32 j = 0; j < 3; ++j)
{
PxU64 edge = key(tri[neighborEdges[j][0]], tri[neighborEdges[j][1]]);
if (const PxPair<const PxU64, PxI32>* ptr = edges.find(edge))
{
if (ptr->second < 0)
return false; //Edge shared by more than 2 triangles
if (result[4 * i + j] == -4 || result[ptr->second] == -4)
{
result[4 * i + j] = -4; //Mark as non-manifold edge
result[ptr->second] = -4;
}
else
{
if (result[4 * i + j] != -1 || result[ptr->second] != -1)
{
result[4 * i + j] = -4; //Mark as non-manifold edge
result[ptr->second] = -4;
}
result[4 * i + j] = ptr->second;
result[ptr->second] = 4 * i + j;
}
edges.erase(ptr->first);
edges.insert(edge, -1); //Mark as processed
}
else
edges.insert(edge, 4 * i + j);
}
}
return true;
}
PxI32 indexOf(const Triangle& tri, PxI32 node)
{
if (tri[0] == node) return 0;
if (tri[1] == node) return 1;
if (tri[2] == node) return 2;
return 0xFFFFFFFF;
}
bool MeshAnalyzer::checkConsistentTriangleOrientation(const Triangle* tris, PxU32 numTriangles)
{
PxArray<bool> flip;
PxHashMap<PxU64, PxI32> edges;
PxArray<PxArray<PxU32>> connectedTriangleGroups;
if (!buildConsistentTriangleOrientationMap(tris, numTriangles, flip, edges, connectedTriangleGroups))
return false;
for (PxU32 i = 0; i < flip.size(); ++i)
{
if (flip[i])
return false;
}
return true;
}
bool MeshAnalyzer::buildConsistentTriangleOrientationMap(const Triangle* tris, PxU32 numTriangles, PxArray<bool>& flip,
PxHashMap<PxU64, PxI32>& edges, PxArray<PxArray<PxU32>>& connectedTriangleGroups)
{
PxArray<PxI32> adj;
if (!buildTriangleAdjacency(tris, numTriangles, adj, edges))
return false;
PxU32 l = numTriangles;
PxArray<bool> done;
done.resize(l, false);
flip.clear();
flip.resize(l, false);
PxU32 seedIndex = 0;
PxArray<PxI32> stack;
while (true)
{
if (stack.size() == 0)
{
while (seedIndex < done.size() && done[seedIndex])
++seedIndex;
if (seedIndex == done.size())
break;
done[seedIndex] = true;
flip[seedIndex] = false;
stack.pushBack(seedIndex);
PxArray<PxU32> currentGroup;
currentGroup.pushBack(seedIndex);
connectedTriangleGroups.pushBack(currentGroup);
}
PxI32 index = stack.popBack();
bool f = flip[index];
const Triangle& tri = tris[index];
for (PxU32 i = 0; i < 3; ++i)
{
if (adj[4 * index + i] >= 0 && !done[adj[4 * index + i] >> 2])
{
PxI32 neighborTriIndex = adj[4 * index + i] >> 2;
done[neighborTriIndex] = true;
connectedTriangleGroups[connectedTriangleGroups.size() - 1].pushBack(neighborTriIndex);
const Triangle& neighborTri = tris[neighborTriIndex];
PxI32 j = indexOf(neighborTri, tri[neighborEdges[i][0]]);
flip[neighborTriIndex] = (neighborTri[(j + 1) % 3] == tri[neighborEdges[i][1]]) != f;
stack.pushBack(neighborTriIndex);
}
}
}
return true;
}
bool MeshAnalyzer::makeTriOrientationConsistent(Triangle* tris, PxU32 numTriangles, bool invertOrientation)
{
PxHashMap<PxU64, PxI32> edges;
PxArray<bool> flipTriangle;
PxArray<PxArray<PxU32>> connectedTriangleGroups;
if (!buildConsistentTriangleOrientationMap(tris, numTriangles, flipTriangle, edges, connectedTriangleGroups))
return false;
for (PxU32 i = 0; i < flipTriangle.size(); ++i)
{
Triangle& t = tris[i];
if (flipTriangle[i] != invertOrientation)
PxSwap(t[0], t[1]);
}
return true;
}
bool MeshAnalyzer::checkMeshWatertightness(const Triangle* tris, PxU32 numTriangles, bool treatInconsistentWindingAsNonWatertight)
{
PxArray<bool> flip;
PxHashMap<PxU64, PxI32> edges;
PxArray<PxArray<PxU32>> connectedTriangleGroups;
if (!MeshAnalyzer::buildConsistentTriangleOrientationMap(tris, numTriangles, flip, edges, connectedTriangleGroups))
return false;
if (treatInconsistentWindingAsNonWatertight)
{
for (PxU32 i = 0; i < flip.size(); ++i)
{
if (flip[i])
return false;
}
}
for (PxHashMap<PxU64, PxI32>::Iterator iter = edges.getIterator(); !iter.done(); ++iter)
{
if (iter->second >= 0)
return false;
}
return true;
}

View File

@@ -0,0 +1,166 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_MESH_ANALYSIS_H
#define GU_MESH_ANALYSIS_H
#include "common/PxPhysXCommonConfig.h"
#include "GuTriangle.h"
#include "foundation/PxHashMap.h"
#include "foundation/PxSort.h"
namespace physx
{
namespace Gu
{
using Triangle = Gu::IndexedTriangleT<PxI32>;
class MeshAnalyzer
{
struct Range
{
PxI32 start;
PxI32 end; //Exclusive
Range(PxI32 start_, PxI32 end_)
{
start = start_;
end = end_;
}
PxI32 Length() const { return end - start; }
};
template<typename T, typename S>
static void splitRanges(PxArray<Range>& mergeRanges, const PxArray<PxI32>& indexer, const T* points, PxI32 dimIndex, S tol)
{
PxArray<Range> newMergeRanges;
for (PxU32 i = 0; i < mergeRanges.size(); ++i)
{
const Range& r = mergeRanges[i];
PxI32 start = r.start;
for (PxI32 j = r.start + 1; j < r.end; ++j)
{
//PxF64 delta = PxAbs(points[start][dimIndex] - points[j - 1][dimIndex]);
S delta = PxAbs(points[indexer[j]][dimIndex] - points[indexer[j - 1]][dimIndex]);
if (delta > tol)
{
if (j - start > 1)
newMergeRanges.pushBack(Range(start, j));
start = j;
}
}
if (r.end - start > 1)
newMergeRanges.pushBack(Range(start, r.end));
}
mergeRanges.clear();
for (PxU32 i = 0; i < newMergeRanges.size(); ++i)
mergeRanges.pushBack(newMergeRanges[i]);
}
template<typename T>
struct Comparer
{
const T* points;
PxU32 dimension;
Comparer(const T* points_, const PxU32 dimension_) : points(points_), dimension(dimension_) {}
bool operator()(const PxI32& a, const PxI32& b) const
{
return points[a][dimension] > points[b][dimension];
}
private:
PX_NOCOPY(Comparer)
};
public:
template<typename T, typename S>
static void mapDuplicatePoints(const T* points, const PxU32 nbPoints, PxArray<PxI32>& result, S duplicateDistanceManhattanMetric = static_cast<S>(1e-6))
{
result.reserve(nbPoints);
result.forceSize_Unsafe(nbPoints);
PxArray<PxI32> indexer;
indexer.reserve(nbPoints);
indexer.forceSize_Unsafe(nbPoints);
for (PxU32 i = 0; i < nbPoints; ++i)
{
indexer[i] = i;
result[i] = i;
}
Comparer<T> comparer(points, 0);
PxSort(indexer.begin(), indexer.size(), comparer);
PxArray<Range> mergeRanges;
mergeRanges.pushBack(Range(0, nbPoints));
splitRanges<T>(mergeRanges, indexer, points, 0, duplicateDistanceManhattanMetric);
comparer.dimension = 1;
for (PxU32 i = 0; i < mergeRanges.size(); ++i)
{
const Range& r = mergeRanges[i];
PxSort(indexer.begin() + r.start, r.Length(), comparer);
}
splitRanges<T>(mergeRanges, indexer, points, 1, duplicateDistanceManhattanMetric);
comparer.dimension = 2;
for (PxU32 i = 0; i < mergeRanges.size(); ++i)
{
const Range& r = mergeRanges[i];
PxSort(indexer.begin() + r.start, r.Length(), comparer);
}
splitRanges<T>(mergeRanges, indexer, points, 2, duplicateDistanceManhattanMetric);
//Merge the ranges
for (PxU32 i = 0; i < mergeRanges.size(); ++i)
{
const Range& r = mergeRanges[i];
PxSort(indexer.begin() + r.start, r.Length());
for (PxI32 j = r.start + 1; j < r.end; ++j)
result[indexer[j]] = result[indexer[r.start]];
}
}
PX_PHYSX_COMMON_API static bool buildTriangleAdjacency(const Triangle* tris, PxU32 numTriangles, PxArray<PxI32>& result, PxHashMap<PxU64, PxI32>& edges);
PX_PHYSX_COMMON_API static bool checkConsistentTriangleOrientation(const Triangle* tris, PxU32 numTriangles);
PX_PHYSX_COMMON_API static bool buildConsistentTriangleOrientationMap(const Triangle* tris, PxU32 numTriangles, PxArray<bool>& flipMap,
PxHashMap<PxU64, PxI32>& edges, PxArray<PxArray<PxU32>>& connectedTriangleGroups);
PX_PHYSX_COMMON_API static bool makeTriOrientationConsistent(Triangle* tris, PxU32 numTriangles, bool invertOrientation = false);
PX_PHYSX_COMMON_API static bool checkMeshWatertightness(const Triangle* tris, PxU32 numTriangles, bool treatInconsistentWindingAsNonWatertight = true);
};
}
}
#endif

View File

@@ -0,0 +1,237 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "foundation/PxVec3.h"
#include "foundation/PxMemory.h"
#include "foundation/PxAllocator.h"
#include "foundation/PxBitUtils.h"
#include "GuMeshCleaner.h"
using namespace physx;
using namespace Gu;
struct Indices
{
PxU32 mRef[3];
PX_FORCE_INLINE bool operator!=(const Indices&v) const { return mRef[0] != v.mRef[0] || mRef[1] != v.mRef[1] || mRef[2] != v.mRef[2]; }
};
static PX_FORCE_INLINE PxU32 getHashValue(const PxVec3& v)
{
const PxU32* h = reinterpret_cast<const PxU32*>(&v.x);
const PxU32 f = (h[0]+h[1]*11-(h[2]*17)) & 0x7fffffff; // avoid problems with +-0
return (f>>22)^(f>>12)^(f);
}
static PX_FORCE_INLINE PxU32 getHashValue(const Indices& v)
{
// const PxU32* h = v.mRef;
// const PxU32 f = (h[0]+h[1]*11-(h[2]*17)) & 0x7fffffff; // avoid problems with +-0
// return (f>>22)^(f>>12)^(f);
PxU32 a = v.mRef[0];
PxU32 b = v.mRef[1];
PxU32 c = v.mRef[2];
a=a-b; a=a-c; a=a^(c >> 13);
b=b-c; b=b-a; b=b^(a << 8);
c=c-a; c=c-b; c=c^(b >> 13);
a=a-b; a=a-c; a=a^(c >> 12);
b=b-c; b=b-a; b=b^(a << 16);
c=c-a; c=c-b; c=c^(b >> 5);
a=a-b; a=a-c; a=a^(c >> 3);
b=b-c; b=b-a; b=b^(a << 10);
c=c-a; c=c-b; c=c^(b >> 15);
return c;
}
MeshCleaner::MeshCleaner(PxU32 nbVerts, const PxVec3* srcVerts, PxU32 nbTris, const PxU32* srcIndices, PxF32 meshWeldTolerance, PxF32 areaLimit)
{
PxVec3* cleanVerts = PX_ALLOCATE(PxVec3, nbVerts, "MeshCleaner");
PX_ASSERT(cleanVerts);
PxU32* indices = PX_ALLOCATE(PxU32, (nbTris*3), "MeshCleaner");
PxU32* remapTriangles = PX_ALLOCATE(PxU32, nbTris, "MeshCleaner");
PxU32* vertexIndices = NULL;
if(meshWeldTolerance!=0.0f)
{
vertexIndices = PX_ALLOCATE(PxU32, nbVerts, "MeshCleaner");
const PxF32 weldTolerance = 1.0f / meshWeldTolerance;
// snap to grid
for(PxU32 i=0; i<nbVerts; i++)
{
vertexIndices[i] = i;
cleanVerts[i] = PxVec3( PxFloor(srcVerts[i].x*weldTolerance + 0.5f),
PxFloor(srcVerts[i].y*weldTolerance + 0.5f),
PxFloor(srcVerts[i].z*weldTolerance + 0.5f));
}
}
else
{
PxMemCopy(cleanVerts, srcVerts, nbVerts*sizeof(PxVec3));
}
const PxU32 maxNbElems = PxMax(nbTris, nbVerts);
const PxU32 hashSize = PxNextPowerOfTwo(maxNbElems);
const PxU32 hashMask = hashSize-1;
PxU32* hashTable = PX_ALLOCATE(PxU32, (hashSize + maxNbElems), "MeshCleaner");
PX_ASSERT(hashTable);
PxMemSet(hashTable, 0xff, hashSize * sizeof(PxU32));
PxU32* const next = hashTable + hashSize;
PxU32* remapVerts = PX_ALLOCATE(PxU32, nbVerts, "MeshCleaner");
PxMemSet(remapVerts, 0xff, nbVerts * sizeof(PxU32));
for(PxU32 i=0;i<nbTris*3;i++)
{
const PxU32 vref = srcIndices[i];
if(vref<nbVerts)
remapVerts[vref] = 0;
}
PxU32 nbCleanedVerts = 0;
for(PxU32 i=0;i<nbVerts;i++)
{
if(remapVerts[i]==0xffffffff)
continue;
const PxVec3& v = cleanVerts[i];
const PxU32 hashValue = getHashValue(v) & hashMask;
PxU32 offset = hashTable[hashValue];
while(offset!=0xffffffff && cleanVerts[offset]!=v)
offset = next[offset];
if(offset==0xffffffff)
{
remapVerts[i] = nbCleanedVerts;
cleanVerts[nbCleanedVerts] = v;
if(vertexIndices)
vertexIndices[nbCleanedVerts] = i;
next[nbCleanedVerts] = hashTable[hashValue];
hashTable[hashValue] = nbCleanedVerts++;
}
else remapVerts[i] = offset;
}
// PT: area = ((p0 - p1).cross(p0 - p2)).magnitude() * 0.5
// area < areaLimit
// <=> ((p0 - p1).cross(p0 - p2)).magnitude() < areaLimit * 2.0
// <=> ((p0 - p1).cross(p0 - p2)).magnitudeSquared() < (areaLimit * 2.0)^2
const PxF32 limit = areaLimit * areaLimit * 4.0f;
PxU32 nbCleanedTris = 0;
for(PxU32 i=0;i<nbTris;i++)
{
PxU32 vref0 = *srcIndices++;
PxU32 vref1 = *srcIndices++;
PxU32 vref2 = *srcIndices++;
if(vref0>=nbVerts || vref1>=nbVerts || vref2>=nbVerts)
continue;
// PT: you can still get zero-area faces when the 3 vertices are perfectly aligned
const PxVec3& p0 = srcVerts[vref0];
const PxVec3& p1 = srcVerts[vref1];
const PxVec3& p2 = srcVerts[vref2];
const float area2 = ((p0 - p1).cross(p0 - p2)).magnitudeSquared();
if(area2<=limit)
continue;
vref0 = remapVerts[vref0];
vref1 = remapVerts[vref1];
vref2 = remapVerts[vref2];
if(vref0==vref1 || vref1==vref2 || vref2==vref0)
continue;
indices[nbCleanedTris*3+0] = vref0;
indices[nbCleanedTris*3+1] = vref1;
indices[nbCleanedTris*3+2] = vref2;
remapTriangles[nbCleanedTris] = i;
nbCleanedTris++;
}
PX_FREE(remapVerts);
PxU32 nbToGo = nbCleanedTris;
nbCleanedTris = 0;
PxMemSet(hashTable, 0xff, hashSize * sizeof(PxU32));
Indices* const I = reinterpret_cast<Indices*>(indices);
bool idtRemap = true;
for(PxU32 i=0;i<nbToGo;i++)
{
const Indices& v = I[i];
const PxU32 hashValue = getHashValue(v) & hashMask;
PxU32 offset = hashTable[hashValue];
while(offset!=0xffffffff && I[offset]!=v)
offset = next[offset];
if(offset==0xffffffff)
{
const PxU32 originalIndex = remapTriangles[i];
PX_ASSERT(nbCleanedTris<=i);
remapTriangles[nbCleanedTris] = originalIndex;
if(originalIndex!=nbCleanedTris)
idtRemap = false;
I[nbCleanedTris] = v;
next[nbCleanedTris] = hashTable[hashValue];
hashTable[hashValue] = nbCleanedTris++;
}
}
PX_FREE(hashTable);
if(vertexIndices)
{
for(PxU32 i=0;i<nbCleanedVerts;i++)
cleanVerts[i] = srcVerts[vertexIndices[i]];
PX_FREE(vertexIndices);
}
mNbVerts = nbCleanedVerts;
mNbTris = nbCleanedTris;
mVerts = cleanVerts;
mIndices = indices;
if(idtRemap)
{
PX_FREE(remapTriangles);
mRemap = NULL;
}
else
{
mRemap = remapTriangles;
}
}
MeshCleaner::~MeshCleaner()
{
PX_FREE(mRemap);
PX_FREE(mIndices);
PX_FREE(mVerts);
}

View File

@@ -0,0 +1,54 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_MESH_CLEANER_H
#define GU_MESH_CLEANER_H
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
namespace Gu
{
class MeshCleaner
{
public:
MeshCleaner(PxU32 nbVerts, const PxVec3* verts, PxU32 nbTris, const PxU32* indices, PxF32 meshWeldTolerance, PxF32 areaLimit);
~MeshCleaner();
PxU32 mNbVerts;
PxU32 mNbTris;
PxVec3* mVerts;
PxU32* mIndices;
PxU32* mRemap;
};
}
}
#endif

View File

@@ -0,0 +1,332 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "GuQuantizer.h"
#include "foundation/PxVec3.h"
#include "foundation/PxBounds3.h"
#include "foundation/PxUserAllocated.h"
#include "foundation/PxAllocator.h"
#include "foundation/PxArray.h"
using namespace physx;
using namespace Gu;
PxU32 kmeans_cluster3d(const PxVec3* input, // an array of input 3d data points.
PxU32 inputSize, // the number of input data points.
PxU32 clumpCount, // the number of clumps you wish to product.
PxVec3* outputClusters, // The output array of clumps 3d vectors, should be at least 'clumpCount' in size.
PxU32* outputIndices, // A set of indices which remaps the input vertices to clumps; should be at least 'inputSize'
float errorThreshold=0.01f, // The error threshold to converge towards before giving up.
float collapseDistance=0.01f); // distance so small it is not worth bothering to create a new clump.
template <class Vec,class Type >
PxU32 kmeans_cluster(const Vec* input,
PxU32 inputCount,
PxU32 clumpCount,
Vec* clusters,
PxU32* outputIndices,
Type threshold, // controls how long it works to converge towards a least errors solution.
Type collapseDistance) // distance between clumps to consider them to be essentially equal.
{
PxU32 convergeCount = 64; // maximum number of iterations attempting to converge to a solution..
PxU32* counts = PX_ALLOCATE(PxU32, clumpCount, "PxU32");
Type error=0;
if ( inputCount <= clumpCount ) // if the number of input points is less than our clumping size, just return the input points.
{
clumpCount = inputCount;
for (PxU32 i=0; i<inputCount; i++)
{
if ( outputIndices )
{
outputIndices[i] = i;
}
clusters[i] = input[i];
counts[i] = 1;
}
}
else
{
PxVec3* centroids = PX_ALLOCATE(PxVec3, clumpCount, "PxVec3");
// Take a sampling of the input points as initial centroid estimates.
for (PxU32 i=0; i<clumpCount; i++)
{
PxU32 index = (i*inputCount)/clumpCount;
PX_ASSERT( index < inputCount );
clusters[i] = input[index];
}
// Here is the main convergence loop
Type old_error = FLT_MAX; // old and initial error estimates are max Type
error = FLT_MAX;
do
{
old_error = error; // preserve the old error
// reset the counts and centroids to current cluster location
for (PxU32 i=0; i<clumpCount; i++)
{
counts[i] = 0;
centroids[i] = PxVec3(PxZero);
}
error = 0;
// For each input data point, figure out which cluster it is closest too and add it to that cluster.
for (PxU32 i=0; i<inputCount; i++)
{
Type min_distance = FLT_MAX;
// find the nearest clump to this point.
for (PxU32 j=0; j<clumpCount; j++)
{
const Type distance = (input[i] - clusters[j]).magnitudeSquared();
if ( distance < min_distance )
{
min_distance = distance;
outputIndices[i] = j; // save which clump this point indexes
}
}
const PxU32 index = outputIndices[i]; // which clump was nearest to this point.
centroids[index]+=input[i];
counts[index]++; // increment the counter indicating how many points are in this clump.
error+=min_distance; // save the error accumulation
}
// Now, for each clump, compute the mean and store the result.
for (PxU32 i=0; i<clumpCount; i++)
{
if ( counts[i] ) // if this clump got any points added to it...
{
const Type recip = 1.0f / Type(counts[i]); // compute the average (center of those points)
centroids[i]*=recip; // compute the average center of the points in this clump.
clusters[i] = centroids[i]; // store it as the new cluster.
}
}
// decrement the convergence counter and bail if it is taking too long to converge to a solution.
convergeCount--;
if (convergeCount == 0 )
{
break;
}
if ( error < threshold ) // early exit if our first guess is already good enough (if all input points are the same)
break;
} while ( PxAbs(error - old_error) > threshold ); // keep going until the error is reduced by this threshold amount.
PX_FREE(centroids);
}
// ok..now we prune the clumps if necessary.
// The rules are; first, if a clump has no 'counts' then we prune it as it's unused.
// The second, is if the centroid of this clump is essentially the same (based on the distance tolerance)
// as an existing clump, then it is pruned and all indices which used to point to it, now point to the one
// it is closest too.
PxU32 outCount = 0; // number of clumps output after pruning performed.
Type d2 = collapseDistance*collapseDistance; // squared collapse distance.
for (PxU32 i=0; i<clumpCount; i++)
{
if ( counts[i] == 0 ) // if no points ended up in this clump, eliminate it.
continue;
// see if this clump is too close to any already accepted clump.
bool add = true;
PxU32 remapIndex = outCount; // by default this clump will be remapped to its current index.
for (PxU32 j=0; j<outCount; j++)
{
Type distance = (clusters[i] - clusters[j]).magnitudeSquared();
if ( distance < d2 )
{
remapIndex = j;
add = false; // we do not add this clump
break;
}
}
// If we have fewer output clumps than input clumps so far, then we need to remap the old indices to the new ones.
if ( outputIndices )
{
if ( outCount != i || !add ) // we need to remap indices! everything that was index 'i' now needs to be remapped to 'outCount'
{
for (PxU32 j=0; j<inputCount; j++)
{
if ( outputIndices[j] == i )
{
outputIndices[j] = remapIndex; //
}
}
}
}
if ( add )
{
clusters[outCount] = clusters[i];
outCount++;
}
}
PX_FREE(counts);
clumpCount = outCount;
return clumpCount;
}
PxU32 kmeans_cluster3d( const PxVec3* input, // an array of input 3d data points.
PxU32 inputSize, // the number of input data points.
PxU32 clumpCount, // the number of clumps you wish to produce
PxVec3* outputClusters, // The output array of clumps 3d vectors, should be at least 'clumpCount' in size.
PxU32* outputIndices, // A set of indices which remaps the input vertices to clumps; should be at least 'inputSize'
float errorThreshold, // The error threshold to converge towards before giving up.
float collapseDistance) // distance so small it is not worth bothering to create a new clump.
{
return kmeans_cluster< PxVec3, float >(input, inputSize, clumpCount, outputClusters, outputIndices, errorThreshold, collapseDistance);
}
class QuantizerImpl : public Quantizer, public PxUserAllocated
{
public:
QuantizerImpl()
{
mScale = PxVec3(1.0f, 1.0f, 1.0f);
mCenter = PxVec3(0.0f, 0.0f, 0.0f);
}
// Use the k-means quantizer, similar results, but much slower.
virtual const PxVec3* kmeansQuantize3D(PxU32 vcount,
const PxVec3* vertices,
PxU32 stride,
bool denormalizeResults,
PxU32 maxVertices,
PxU32& outVertsCount)
{
const PxVec3* ret = NULL;
outVertsCount = 0;
mNormalizedInput.clear();
mQuantizedOutput.clear();
if ( vcount > 0 )
{
normalizeInput(vcount,vertices, stride);
PxVec3* quantizedOutput = PX_ALLOCATE(PxVec3, vcount, "PxVec3");
PxU32* quantizedIndices = PX_ALLOCATE(PxU32, vcount, "PxU32");
outVertsCount = kmeans_cluster3d(&mNormalizedInput[0], vcount, maxVertices, quantizedOutput, quantizedIndices, 0.01f, 0.0001f );
if ( outVertsCount > 0 )
{
if ( denormalizeResults )
{
for (PxU32 i=0; i<outVertsCount; i++)
{
PxVec3 v( quantizedOutput[i] );
v = v.multiply(mScale) + mCenter;
mQuantizedOutput.pushBack(v);
}
}
else
{
for (PxU32 i=0; i<outVertsCount; i++)
{
const PxVec3& v( quantizedOutput[i] );
mQuantizedOutput.pushBack(v);
}
}
ret = &mQuantizedOutput[0];
}
PX_FREE(quantizedOutput);
PX_FREE(quantizedIndices);
}
return ret;
}
virtual void release()
{
PX_DELETE_THIS;
}
virtual const PxVec3& getDenormalizeScale() const
{
return mScale;
}
virtual const PxVec3& getDenormalizeCenter() const
{
return mCenter;
}
private:
void normalizeInput(PxU32 vcount, const PxVec3* vertices, PxU32 stride)
{
const char* vtx = reinterpret_cast<const char *> (vertices);
mNormalizedInput.clear();
mQuantizedOutput.clear();
PxBounds3 bounds;
bounds.setEmpty();
for (PxU32 i=0; i<vcount; i++)
{
const PxVec3& v = *reinterpret_cast<const PxVec3 *> (vtx);
vtx += stride;
bounds.include(v);
}
mCenter = bounds.getCenter();
PxVec3 dim = bounds.getDimensions();
dim *= 1.001f;
mScale = dim*0.5f;
for (PxU32 i = 0; i < 3; i++)
{
if(dim[i] == 0)
mScale[i] = 1.0f;
}
PxVec3 recip;
recip.x = 1.0f / mScale.x;
recip.y = 1.0f / mScale.y;
recip.z = 1.0f / mScale.z;
vtx = reinterpret_cast<const char *> (vertices);
for (PxU32 i=0; i<vcount; i++)
{
PxVec3 v = *reinterpret_cast<const PxVec3 *> (vtx);
vtx += stride;
v = (v - mCenter).multiply(recip);
mNormalizedInput.pushBack(v);
}
}
virtual ~QuantizerImpl()
{
}
private:
PxVec3 mScale;
PxVec3 mCenter;
PxArray<PxVec3> mNormalizedInput;
PxArray<PxVec3> mQuantizedOutput;
};
Quantizer* physx::Gu::createQuantizer()
{
return PX_NEW(QuantizerImpl);
}

View File

@@ -0,0 +1,75 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_QUANTIZER_H
#define GU_QUANTIZER_H
#include "foundation/PxVec3.h"
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
namespace Gu
{
//////////////////////////////////////////////////////////////////////////
// K-means quantization class
// see http://en.wikipedia.org/wiki/K-means_clustering
// implementation from John Ratcliff http://codesuppository.blogspot.ch/2010/12/k-means-clustering-algorithm.html
class Quantizer
{
public:
// quantize the input vertices
virtual const PxVec3* kmeansQuantize3D( PxU32 vcount,
const PxVec3* vertices,
PxU32 stride,
bool denormalizeResults,
PxU32 maxVertices,
PxU32& outVertsCount) = 0;
// returns the denormalized scale
virtual const PxVec3& getDenormalizeScale() const = 0;
// returns the denormalized center
virtual const PxVec3& getDenormalizeCenter() const = 0;
// release internal data
virtual void release() = 0;
protected:
virtual ~Quantizer()
{
}
};
// creates the quantizer class
Quantizer * createQuantizer();
}
}
#endif

View File

@@ -0,0 +1,57 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "GuSeparatingAxes.h"
using namespace physx;
bool Gu::SeparatingAxes::addAxis(const PxVec3& axis)
{
PxU32 numAxes = getNumAxes();
const PxVec3* PX_RESTRICT axes = getAxes();
const PxVec3* PX_RESTRICT axes_end = axes + numAxes;
while(axes<axes_end)
{
if(PxAbs(axis.dot(*axes))>0.9999f)
return false;
axes++;
}
#ifdef SEP_AXIS_FIXED_MEMORY
if(mNbAxes<SEP_AXIS_FIXED_MEMORY)
{
mAxes[mNbAxes++] = axis;
return true;
}
return false;
#else
mAxes.pushBack(axis);
return true;
#endif
}

View File

@@ -0,0 +1,90 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_SEPARATINGAXES_H
#define GU_SEPARATINGAXES_H
#include "foundation/PxVec3.h"
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
namespace Gu
{
// PT: this is a number of axes. Multiply by sizeof(PxVec3) for size in bytes.
#define SEP_AXIS_FIXED_MEMORY 256
// This class holds a list of potential separating axes.
// - the orientation is irrelevant so V and -V should be the same vector
// - the scale is irrelevant so V and n*V should be the same vector
// - a given separating axis should appear only once in the class
#if PX_VC
#pragma warning(push)
#pragma warning( disable : 4251 ) // class needs to have dll-interface to be used by clients of class
#endif
class SeparatingAxes
{
public:
PX_INLINE SeparatingAxes() : mNbAxes(0) {}
bool addAxis(const PxVec3& axis);
PX_FORCE_INLINE const PxVec3* getAxes() const
{
return mAxes;
}
PX_FORCE_INLINE PxU32 getNumAxes() const
{
return mNbAxes;
}
PX_FORCE_INLINE void reset()
{
mNbAxes = 0;
}
private:
PxU32 mNbAxes;
PxVec3 mAxes[SEP_AXIS_FIXED_MEMORY];
};
#if PX_VC
#pragma warning(pop)
#endif
enum PxcSepAxisType
{
SA_NORMAL0, // Normal of object 0
SA_NORMAL1, // Normal of object 1
SA_EE // Cross product of edges
};
}
}
#endif

View File

@@ -0,0 +1,112 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "GuVertexReducer.h"
#include "foundation/PxAllocator.h"
#include "CmRadixSort.h"
using namespace physx;
using namespace Gu;
using namespace Cm;
// PT: code archeology: this initially came from ICE (IceVertexCloud.h/cpp). Consider dropping it.
ReducedVertexCloud::ReducedVertexCloud(const PxVec3* verts, PxU32 nb_verts) : mNbRVerts(0), mRVerts(NULL), mXRef(NULL)
{
mVerts = verts;
mNbVerts = nb_verts;
}
ReducedVertexCloud::~ReducedVertexCloud()
{
clean();
}
ReducedVertexCloud& ReducedVertexCloud::clean()
{
PX_FREE(mXRef);
PX_FREE(mRVerts);
return *this;
}
/**
* Reduction method. Use this to create a minimal vertex cloud.
* \param rc [out] result structure
* \return true if success
* \warning This is not about welding nearby vertices, here we look for real redundant ones.
*/
bool ReducedVertexCloud::reduce(REDUCEDCLOUD* rc)
{
clean();
mXRef = PX_ALLOCATE(PxU32, mNbVerts, "mXRef");
float* f = PX_ALLOCATE(float, mNbVerts, "tmp");
for(PxU32 i=0;i<mNbVerts;i++)
f[i] = mVerts[i].x;
RadixSortBuffered Radix;
Radix.Sort(reinterpret_cast<const PxU32*>(f), mNbVerts, RADIX_UNSIGNED);
for(PxU32 i=0;i<mNbVerts;i++)
f[i] = mVerts[i].y;
Radix.Sort(reinterpret_cast<const PxU32*>(f), mNbVerts, RADIX_UNSIGNED);
for(PxU32 i=0;i<mNbVerts;i++)
f[i] = mVerts[i].z;
const PxU32* Sorted = Radix.Sort(reinterpret_cast<const PxU32*>(f), mNbVerts, RADIX_UNSIGNED).GetRanks();
PX_FREE(f);
mNbRVerts = 0;
const PxU32 Junk[] = {PX_INVALID_U32, PX_INVALID_U32, PX_INVALID_U32};
const PxU32* Previous = Junk;
mRVerts = PX_ALLOCATE(PxVec3, mNbVerts, "PxVec3");
PxU32 Nb = mNbVerts;
while(Nb--)
{
const PxU32 Vertex = *Sorted++; // Vertex number
const PxU32* current = reinterpret_cast<const PxU32*>(&mVerts[Vertex]);
if(current[0]!=Previous[0] || current[1]!=Previous[1] || current[2]!=Previous[2])
mRVerts[mNbRVerts++] = mVerts[Vertex];
Previous = current;
mXRef[Vertex] = mNbRVerts-1;
}
if(rc)
{
rc->CrossRef = mXRef;
rc->NbRVerts = mNbRVerts;
rc->RVerts = mRVerts;
}
return true;
}

View File

@@ -0,0 +1,77 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_VERTEX_REDUCER_H
#define GU_VERTEX_REDUCER_H
#include "foundation/PxVec3.h"
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
namespace Gu
{
//! Vertex cloud reduction result structure
struct REDUCEDCLOUD
{
// Out
PxVec3* RVerts; //!< Reduced list
PxU32 NbRVerts; //!< Reduced number of vertices
PxU32* CrossRef; //!< nb_verts remapped indices
};
class ReducedVertexCloud
{
public:
ReducedVertexCloud(const PxVec3* verts, PxU32 nb_verts);
~ReducedVertexCloud();
ReducedVertexCloud& clean();
bool reduce(REDUCEDCLOUD* rc=NULL);
PX_FORCE_INLINE PxU32 getNbVerts() const { return mNbVerts; }
PX_FORCE_INLINE PxU32 getNbReducedVerts() const { return mNbRVerts; }
PX_FORCE_INLINE const PxVec3* getReducedVerts() const { return mRVerts; }
PX_FORCE_INLINE const PxVec3& getReducedVertex(PxU32 i) const { return mRVerts[i]; }
PX_FORCE_INLINE const PxU32* getCrossRefTable() const { return mXRef; }
private:
// Original vertex cloud
PxU32 mNbVerts; //!< Number of vertices
const PxVec3* mVerts; //!< List of vertices (pointer copy)
// Reduced vertex cloud
PxU32 mNbRVerts; //!< Reduced number of vertices
PxVec3* mRVerts; //!< Reduced list of vertices
PxU32* mXRef; //!< Cross-reference table (used to remap topologies)
};
}
}
#endif