// // 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-2019 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 "NpShapeManager.h" #include "NpFactory.h" #include "NpActor.h" #include "NpScene.h" #include "NpPtrTableStorageManager.h" #include "NpRigidDynamic.h" #include "NpArticulationLink.h" #include "SqPruningStructure.h" #include "ScbRigidObject.h" #include "ScBodySim.h" #include "GuBounds.h" #include "CmUtils.h" #include "PsAlloca.h" using namespace physx; using namespace Sq; using namespace Gu; using namespace Cm; static PX_FORCE_INLINE bool isSceneQuery(const NpShape& shape) { return shape.getFlagsFast() & PxShapeFlag::eSCENE_QUERY_SHAPE; } NpShapeManager::NpShapeManager() : mSqCompoundId(INVALID_PRUNERHANDLE), mPruningStructure(NULL) { } // PX_SERIALIZATION NpShapeManager::NpShapeManager(const PxEMPTY) : mShapes (PxEmpty), mSceneQueryData (PxEmpty) { } NpShapeManager::~NpShapeManager() { PX_ASSERT(!mPruningStructure); PtrTableStorageManager& sm = NpFactory::getInstance().getPtrTableStorageManager(); mShapes.clear(sm); mSceneQueryData.clear(sm); } void NpShapeManager::preExportDataReset() { //Clearing SceneQueryPruner handles to avoid stale references after deserialization and for deterministic serialization output. //Multi shape cases handled in exportExtraData if (getNbShapes() == 1) { setPrunerData(0, Sq::PrunerData(SQ_INVALID_PRUNER_DATA)); } } void NpShapeManager::exportExtraData(PxSerializationContext& stream) { mShapes.exportExtraData(stream); //Clearing SceneQueryPruner handles to avoid stale references after deserialization and for deterministic serialization output. //For single shape, it's handled on exportData. PxU32 numShapes = getNbShapes(); if (numShapes > 1) { stream.alignData(PX_SERIAL_ALIGN); for (PxU32 i = 0; i < numShapes; i++) { void* data = reinterpret_cast(Sq::PrunerData(SQ_INVALID_PRUNER_DATA)); stream.writeData(&data, sizeof(void*)); } } } void NpShapeManager::importExtraData(PxDeserializationContext& context) { mShapes.importExtraData(context); mSceneQueryData.importExtraData(context); } //~PX_SERIALIZATION void NpShapeManager::attachShape(NpShape& shape, PxRigidActor& actor) { PX_ASSERT(!mPruningStructure); PtrTableStorageManager& sm = NpFactory::getInstance().getPtrTableStorageManager(); const PxU32 index = getNbShapes(); mShapes.add(&shape, sm); mSceneQueryData.add(reinterpret_cast(size_t(SQ_INVALID_PRUNER_DATA)), sm); NpScene* scene = NpActor::getAPIScene(actor); if(scene && isSceneQuery(shape)) setupSceneQuery(scene->getSceneQueryManagerFast(), actor, index); Scb::RigidObject& ro = static_cast(NpActor::getScbFromPxActor(actor)); ro.onShapeAttach(shape.getScbShape()); PX_ASSERT(!shape.isExclusive() || shape.getActor()==NULL); shape.onActorAttach(actor); } bool NpShapeManager::detachShape(NpShape& s, PxRigidActor& actor, bool wakeOnLostTouch) { PX_ASSERT(!mPruningStructure); const PxU32 index = mShapes.find(&s); if(index==0xffffffff) return false; NpScene* scene = NpActor::getAPIScene(actor); if(scene && isSceneQuery(s)) { scene->getSceneQueryManagerFast().removePrunerShape(mSqCompoundId, getPrunerData(index)); // if this is the last shape of a compound shape, we have to remove the compound id // and in case of a dynamic actor, remove it from the active list if(isSqCompound() && (mShapes.getCount() == 1)) { mSqCompoundId = INVALID_PRUNERHANDLE; const PxType actorType = actor.getConcreteType(); const bool isDynamic = actorType == PxConcreteType::eRIGID_DYNAMIC || actorType == PxConcreteType::eARTICULATION_LINK; if(isDynamic) { // for PxRigidDynamic and PxArticulationLink we need to remove the compound rigid flag and remove them from active list if(actor.is()) const_cast(static_cast(actor)).getScbBodyFast().getScBody().getSim()->disableCompound(); else { if(actor.is()) const_cast(static_cast(actor)).getScbBodyFast().getScBody().getSim()->disableCompound(); } } } } Scb::RigidObject& ro = static_cast(NpActor::getScbFromPxActor(actor)); ro.onShapeDetach(s.getScbShape(), wakeOnLostTouch, (s.getRefCount() == 1)); PtrTableStorageManager& sm = NpFactory::getInstance().getPtrTableStorageManager(); mShapes.replaceWithLast(index, sm); mSceneQueryData.replaceWithLast(index, sm); s.onActorDetach(); return true; } void NpShapeManager::detachAll(NpScene* scene, const PxRigidActor& actor) { // assumes all SQ data has been released, which is currently the responsbility of the owning actor const PxU32 nbShapes = getNbShapes(); NpShape*const *shapes = getShapes(); if(scene) teardownAllSceneQuery(scene->getSceneQueryManagerFast(), actor); // actor cleanup in Scb/Sc will remove any outstanding references corresponding to sim objects, so we don't need to do that here. for(PxU32 i=0;ionActorDetach(); PtrTableStorageManager& sm = NpFactory::getInstance().getPtrTableStorageManager(); mShapes.clear(sm); mSceneQueryData.clear(sm); } PxU32 NpShapeManager::getShapes(PxShape** buffer, PxU32 bufferSize, PxU32 startIndex) const { return getArrayOfPointers(buffer, bufferSize, startIndex, getShapes(), getNbShapes()); } PxBounds3 NpShapeManager::getWorldBounds(const PxRigidActor& actor) const { PxBounds3 bounds(PxBounds3::empty()); const PxU32 nbShapes = getNbShapes(); const PxTransform actorPose = actor.getGlobalPose(); NpShape*const* PX_RESTRICT shapes = getShapes(); for(PxU32 i=0;igetScbShape().getGeometry(), actorPose * shapes[i]->getLocalPoseFast())); return bounds; } void NpShapeManager::clearShapesOnRelease(Scb::Scene& s, PxRigidActor& r) { PX_ASSERT(static_cast(NpActor::getScbFromPxActor(r)).isSimDisabledInternally()); const PxU32 nbShapes = getNbShapes(); NpShape*const* PX_RESTRICT shapes = getShapes(); for(PxU32 i=0;igetScbShape(); scbShape.checkUpdateOnRemove(&s); #if PX_SUPPORT_PVD s.getScenePvdClient().releasePvdInstance(&scbShape, r); #else PX_UNUSED(r); #endif } } void NpShapeManager::releaseExclusiveUserReferences() { // when the factory is torn down, release any shape owner refs that are still outstanding const PxU32 nbShapes = getNbShapes(); NpShape*const* PX_RESTRICT shapes = getShapes(); for(PxU32 i=0;iisExclusiveFast() && shapes[i]->getRefCount()>1) shapes[i]->release(); } } void NpShapeManager::setupSceneQuery(SceneQueryManager& sqManager, const PxRigidActor& actor, const NpShape& shape) { PX_ASSERT(shape.getFlags() & PxShapeFlag::eSCENE_QUERY_SHAPE); const PxU32 index = mShapes.find(&shape); PX_ASSERT(index!=0xffffffff); setupSceneQuery(sqManager, actor, index); } void NpShapeManager::teardownSceneQuery(SceneQueryManager& sqManager, const NpShape& shape) { const PxU32 index = mShapes.find(&shape); PX_ASSERT(index!=0xffffffff); teardownSceneQuery(sqManager, index); } void NpShapeManager::setupAllSceneQuery(NpScene* scene, const PxRigidActor& actor, bool hasPrunerStructure, const PxBounds3* bounds, const Gu::BVHStructure* bvhStructure) { PX_ASSERT(scene); // shouldn't get here unless we're in a scene SceneQueryManager& sqManager = scene->getSceneQueryManagerFast(); const PxU32 nbShapes = getNbShapes(); NpShape*const *shapes = getShapes(); // if BVH structure was provided, we add shapes into compound pruner if(bvhStructure) { addBVHStructureShapes(sqManager, actor, bvhStructure); } else { const PxType actorType = actor.getConcreteType(); const bool isDynamic = actorType == PxConcreteType::eRIGID_DYNAMIC || actorType == PxConcreteType::eARTICULATION_LINK; for(PxU32 i=0;igetNbBounds()); mSqCompoundId = static_cast(NpActor::getScbFromPxActor(actor).getActorCore()).getRigidID(); sqManager.addCompoundShape(*bvhStructure, mSqCompoundId, actor.getGlobalPose(), prunerData, scbShapes, scbActor); numSqShapes = 0; for(PxU32 i = 0; i < nbShapes; i++) { const NpShape& shape = *getShapes()[i]; if(isSceneQuery(shape)) setPrunerData(i, prunerData[numSqShapes++]); } } void NpShapeManager::addPrunerShape(SceneQueryManager& sqManager, PxU32 index, const NpShape& shape, const PxRigidActor& actor, bool dynamic, const PxBounds3* bound, bool hasPrunerStructure) { const Scb::Shape& scbShape = shape.getScbShape(); const Scb::Actor& scbActor = NpActor::getScbFromPxActor(actor); setPrunerData(index, sqManager.addPrunerShape(scbShape, scbActor, dynamic, mSqCompoundId, bound, hasPrunerStructure)); } void NpShapeManager::setupSceneQuery(SceneQueryManager& sqManager, const PxRigidActor& actor, PxU32 index) { const PxType actorType = actor.getConcreteType(); const bool isDynamic = actorType == PxConcreteType::eRIGID_DYNAMIC || actorType == PxConcreteType::eARTICULATION_LINK; addPrunerShape(sqManager, index, *(getShapes()[index]), actor, isDynamic, NULL, false); } void NpShapeManager::teardownSceneQuery(SceneQueryManager& sqManager, PxU32 index) { sqManager.removePrunerShape(mSqCompoundId, getPrunerData(index)); setPrunerData(index, SQ_INVALID_PRUNER_DATA); } #if PX_ENABLE_DEBUG_VISUALIZATION #include "GuHeightFieldUtil.h" #include "geometry/PxGeometryQuery.h" #include "geometry/PxMeshQuery.h" #include "GuConvexEdgeFlags.h" #include "GuMidphaseInterface.h" static const PxU32 gCollisionShapeColor = PxU32(PxDebugColor::eARGB_MAGENTA); static void visualizeSphere(const PxSphereGeometry& geometry, RenderOutput& out, const PxTransform& absPose) { out << gCollisionShapeColor; // PT: no need to output this for each segment! out << absPose << DebugCircle(100, geometry.radius); PxMat44 rotPose(absPose); Ps::swap(rotPose.column1, rotPose.column2); rotPose.column1 = -rotPose.column1; out << rotPose << DebugCircle(100, geometry.radius); Ps::swap(rotPose.column0, rotPose.column2); rotPose.column0 = -rotPose.column0; out << rotPose << DebugCircle(100, geometry.radius); } static void visualizePlane(const PxPlaneGeometry& /*geometry*/, RenderOutput& out, const PxTransform& absPose) { PxMat44 rotPose(absPose); Ps::swap(rotPose.column1, rotPose.column2); rotPose.column1 = -rotPose.column1; Ps::swap(rotPose.column0, rotPose.column2); rotPose.column0 = -rotPose.column0; out << rotPose << gCollisionShapeColor; // PT: no need to output this for each segment! for(PxReal radius = 2.0f; radius < 20.0f ; radius += 2.0f) out << DebugCircle(100, radius*radius); } static void visualizeCapsule(const PxCapsuleGeometry& geometry, RenderOutput& out, const PxTransform& absPose) { out << gCollisionShapeColor; out.outputCapsule(geometry.radius, geometry.halfHeight, absPose); } static void visualizeBox(const PxBoxGeometry& geometry, RenderOutput& out, const PxTransform& absPose) { out << gCollisionShapeColor; out << absPose << DebugBox(geometry.halfExtents); } static void visualizeConvexMesh(const PxConvexMeshGeometry& geometry, RenderOutput& out, const PxTransform& absPose) { const ConvexMesh* convexMesh = static_cast(geometry.convexMesh); const ConvexHullData& hullData = convexMesh->getHull(); const PxVec3* vertices = hullData.getHullVertices(); const PxU8* indexBuffer = hullData.getVertexData8(); const PxU32 nbPolygons = convexMesh->getNbPolygonsFast(); const PxMat44 m44(PxMat33(absPose.q) * geometry.scale.toMat33(), absPose.p); out << m44 << gCollisionShapeColor; // PT: no need to output this for each segment! for(PxU32 i=0; i(indices); ref0 = dtriangles[i*3+0]; ref1 = dtriangles[i*3+1]; ref2 = dtriangles[i*3+2]; } else { const PxU16* wtriangles = reinterpret_cast(indices); ref0 = wtriangles[i*3+0]; ref1 = wtriangles[i*3+1]; ref2 = wtriangles[i*3+2]; } wp[0] = vertices[ref0]; wp[1] = vertices[ref1]; wp[2] = vertices[ref2]; } static void getTriangle(const Gu::TriangleMesh& mesh, PxU32 i, PxVec3* wp, const PxVec3* vertices, const void* indices, const Matrix34& absPose, bool has16BitIndices) { PxVec3 localVerts[3]; getTriangle(mesh, i, localVerts, vertices, indices, has16BitIndices); wp[0] = absPose.transform(localVerts[0]); wp[1] = absPose.transform(localVerts[1]); wp[2] = absPose.transform(localVerts[2]); } static void visualizeActiveEdges(RenderOutput& out, const Gu::TriangleMesh& mesh, PxU32 nbTriangles, const PxU32* results, const Matrix34& absPose) { const PxU8* extraTrigData = mesh.getExtraTrigData(); PX_ASSERT(extraTrigData); const PxVec3* vertices = mesh.getVerticesFast(); const void* indices = mesh.getTrianglesFast(); out << PxU32(PxDebugColor::eARGB_YELLOW); // PT: no need to output this for each segment! const bool has16Bit = mesh.has16BitIndices(); for(PxU32 i=0; i(geometry.triangleMesh); const PxMat44 midt(PxIdentity); const Matrix34 absPose(PxMat33(pose.q) * geometry.scale.toMat33(), pose.p); PxU32 nbTriangles = triangleMesh->getNbTrianglesFast(); const PxU32 nbVertices = triangleMesh->getNbVerticesFast(); const PxVec3* vertices = triangleMesh->getVerticesFast(); const void* indices = triangleMesh->getTrianglesFast(); const bool has16Bit = triangleMesh->has16BitIndices(); // PT: TODO: don't render the same edge multiple times PxU32* results = NULL; if(useCullBox) { const Gu::Box worldBox( (cullbox.maximum + cullbox.minimum)*0.5f, (cullbox.maximum - cullbox.minimum)*0.5f, PxMat33(PxIdentity)); // PT: TODO: use the callback version here to avoid allocating this huge array results = reinterpret_cast(PX_ALLOC_TEMP(sizeof(PxU32)*nbTriangles, "tmp triangle indices")); LimitedResults limitedResults(results, nbTriangles, 0); Midphase::intersectBoxVsMesh(worldBox, *triangleMesh, pose, geometry.scale, &limitedResults); nbTriangles = limitedResults.mNbResults; if(visualizeShapes) { const PxU32 scolor = gCollisionShapeColor; out << midt << scolor; // PT: no need to output this for each segment! PxDebugLine* segments = out.reserveSegments(nbTriangles*3); for(PxU32 i=0; i(PX_ALLOC(sizeof(PxVec3)*nbVertices, "PxVec3")); for(PxU32 i=0;igetExtraTrigData()) visualizeActiveEdges(out, *triangleMesh, nbTriangles, results, absPose); if(results) PX_FREE(results); } static void visualizeHeightField(const PxHeightFieldGeometry& hfGeometry, RenderOutput& out, const PxTransform& absPose, const PxBounds3& cullbox, bool useCullBox) { const HeightField* heightfield = static_cast(hfGeometry.heightField); // PT: TODO: the debug viz for HFs is minimal at the moment... const PxU32 scolor = gCollisionShapeColor; const PxMat44 midt = PxMat44(PxIdentity); HeightFieldUtil hfUtil(hfGeometry); const PxU32 nbRows = heightfield->getNbRowsFast(); const PxU32 nbColumns = heightfield->getNbColumnsFast(); const PxU32 nbVerts = nbRows * nbColumns; const PxU32 nbTriangles = 2 * nbVerts; out << midt << scolor; // PT: no need to output the same matrix/color for each triangle if(useCullBox) { const PxTransform pose0((cullbox.maximum + cullbox.minimum)*0.5f); const PxBoxGeometry boxGeometry((cullbox.maximum - cullbox.minimum)*0.5f); PxU32* results = reinterpret_cast(PX_ALLOC(sizeof(PxU32)*nbTriangles, "tmp triangle indices")); bool overflow = false; PxU32 nbTouchedTris = PxMeshQuery::findOverlapHeightField(boxGeometry, pose0, hfGeometry, absPose, results, nbTriangles, 0, overflow); PxDebugLine* segments = out.reserveSegments(nbTouchedTris*3); for(PxU32 i=0; iisValidTriangle(index) && heightfield->getTriangleMaterial(index) != PxHeightFieldMaterial::eHOLE) { outputTriangle(segments, currentTriangle.verts[0], currentTriangle.verts[1], currentTriangle.verts[2], scolor); segments+=3; } } PX_FREE(results); } else { // PT: transform vertices only once PxVec3* tmpVerts = reinterpret_cast(PX_ALLOC(sizeof(PxVec3)*nbVerts, "PxVec3")); // PT: TODO: optimize the following line for(PxU32 i=0;igetVertex(i))); for(PxU32 i=0; iisValidTriangle(i) && heightfield->getTriangleMaterial(i) != PxHeightFieldMaterial::eHOLE) { PxU32 vi0, vi1, vi2; heightfield->getTriangleVertexIndices(i, vi0, vi1, vi2); PxDebugLine* segments = out.reserveSegments(3); outputTriangle(segments, tmpVerts[vi0], tmpVerts[vi1], tmpVerts[vi2], scolor); } } PX_FREE(tmpVerts); } } static void visualize(const PxGeometry& geometry, RenderOutput& out, const PxTransform& absPose, const PxBounds3& cullbox, const PxReal fscale, bool visualizeShapes, bool visualizeEdges, bool useCullBox) { // triangle meshes can render active edges or face normals, but for other types we can just early out if there are no collision shapes if(!visualizeShapes && geometry.getType() != PxGeometryType::eTRIANGLEMESH) return; switch(geometry.getType()) { case PxGeometryType::eSPHERE: visualizeSphere(static_cast(geometry), out, absPose); break; case PxGeometryType::eBOX: visualizeBox(static_cast(geometry), out, absPose); break; case PxGeometryType::ePLANE: visualizePlane(static_cast(geometry), out, absPose); break; case PxGeometryType::eCAPSULE: visualizeCapsule(static_cast(geometry), out, absPose); break; case PxGeometryType::eCONVEXMESH: visualizeConvexMesh(static_cast(geometry), out, absPose); break; case PxGeometryType::eTRIANGLEMESH: visualizeTriangleMesh(static_cast(geometry), out, absPose, cullbox, fscale, visualizeShapes, visualizeEdges, useCullBox); break; case PxGeometryType::eHEIGHTFIELD: visualizeHeightField(static_cast(geometry), out, absPose, cullbox, useCullBox); break; case PxGeometryType::eINVALID: break; case PxGeometryType::eGEOMETRY_COUNT: break; } } void NpShapeManager::visualize(RenderOutput& out, NpScene* scene, const PxRigidActor& actor) { const PxReal scale = scene->getVisualizationParameter(PxVisualizationParameter::eSCALE); if(!scale) return; const PxU32 nbShapes = getNbShapes(); NpShape*const* PX_RESTRICT shapes = getShapes(); const bool visualizeCompounds = (nbShapes>1) && scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_COMPOUNDS)!=0.0f; // PT: moved all these out of the loop, no need to grab them once per shape const PxBounds3& cullbox = scene->getScene().getVisualizationCullingBox(); const bool visualizeAABBs = scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_AABBS)!=0.0f; const bool visualizeShapes = scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_SHAPES)!=0.0f; const bool visualizeEdges = scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_EDGES)!=0.0f; const float fNormals = scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_FNORMALS); const bool visualizeFNormals = fNormals!=0.0f; const bool visualizeCollision = visualizeShapes || visualizeFNormals || visualizeEdges; const bool useCullBox = !cullbox.isEmpty(); const bool needsShapeBounds0 = visualizeCompounds || (visualizeCollision && useCullBox); const PxReal collisionAxes = scale * scene->getVisualizationParameter(PxVisualizationParameter::eCOLLISION_AXES); const PxReal fscale = scale * fNormals; const PxTransform actorPose = actor.getGlobalPose(); PxBounds3 compoundBounds(PxBounds3::empty()); for(PxU32 i=0;igetScbShape(); const PxTransform absPose = actorPose * scbShape.getShape2Actor(); const PxGeometry& geom = scbShape.getGeometry(); const bool shapeDebugVizEnabled = scbShape.getFlags() & PxShapeFlag::eVISUALIZATION; const bool needsShapeBounds = needsShapeBounds0 || (visualizeAABBs && shapeDebugVizEnabled); const PxBounds3 currentShapeBounds = needsShapeBounds ? Gu::computeBounds(geom, absPose) : PxBounds3::empty(); if(shapeDebugVizEnabled) { if(visualizeAABBs) out << PxU32(PxDebugColor::eARGB_YELLOW) << PxMat44(PxIdentity) << DebugBox(currentShapeBounds); if(collisionAxes != 0.0f) out << PxMat44(absPose) << DebugBasis(PxVec3(collisionAxes), 0xcf0000, 0x00cf00, 0x0000cf); if(visualizeCollision) { if(!useCullBox || cullbox.intersects(currentShapeBounds)) ::visualize(geom, out, absPose, cullbox, fscale, visualizeShapes, visualizeEdges, useCullBox); } } if(visualizeCompounds) compoundBounds.include(currentShapeBounds); } if(visualizeCompounds && !compoundBounds.isEmpty()) out << gCollisionShapeColor << PxMat44(PxIdentity) << DebugBox(compoundBounds); } #endif // PX_ENABLE_DEBUG_VISUALIZATION