// 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 "geometry/PxMeshScale.h" #include "GuHeightFieldUtil.h" #include "GuSweepSharedTests.h" #include "GuHeightField.h" #include "GuEntityReport.h" #include "foundation/PxIntrinsics.h" #include "CmScaling.h" using namespace physx; void Gu::HeightFieldUtil::computeLocalBounds(PxBounds3& bounds) const { const PxMeshScale scale(PxVec3(mHfGeom->rowScale, mHfGeom->heightScale, mHfGeom->columnScale), PxQuat(PxIdentity)); const PxMat33 mat33 = Cm::toMat33(scale); bounds.minimum = mat33.transform(mHeightField->getData().mAABB.getMin()); bounds.maximum = mat33.transform(mHeightField->getData().mAABB.getMax()); // PT: HFs will assert in Gu::intersectRayAABB2() if we don't deal with that const float deltaY = GU_MIN_AABB_EXTENT*0.5f - (bounds.maximum.y - bounds.minimum.y); if(deltaY>0.0f) { bounds.maximum.y += deltaY*0.6f; bounds.minimum.y -= deltaY*0.6f; } } static PX_FORCE_INLINE bool reportTriangle(Gu::OverlapReport& callback, PxU32 material, PxU32* PX_RESTRICT indexBuffer, const PxU32 bufferSize, PxU32& indexBufferUsed, PxU32 triangleIndex) { if(material != PxHeightFieldMaterial::eHOLE) { indexBuffer[indexBufferUsed++] = triangleIndex; if(indexBufferUsed >= bufferSize) { if(!callback.reportTouchedTris(indexBufferUsed, indexBuffer)) return false; indexBufferUsed = 0; } } return true; } void Gu::HeightFieldUtil::overlapAABBTriangles(const PxBounds3& bounds, OverlapReport& callback, PxU32 batchSize) const { PX_ASSERT(batchSize<=HF_OVERLAP_REPORT_BUFFER_SIZE); PX_ASSERT(!bounds.isEmpty()); PxBounds3 localBounds = bounds; localBounds.minimum.x *= mOneOverRowScale; localBounds.minimum.y *= mOneOverHeightScale; localBounds.minimum.z *= mOneOverColumnScale; localBounds.maximum.x *= mOneOverRowScale; localBounds.maximum.y *= mOneOverHeightScale; localBounds.maximum.z *= mOneOverColumnScale; if(mHfGeom->rowScale < 0.0f) PxSwap(localBounds.minimum.x, localBounds.maximum.x); if(mHfGeom->columnScale < 0.0f) PxSwap(localBounds.minimum.z, localBounds.maximum.z); // early exit for aabb does not overlap in XZ plane // DO NOT MOVE: since rowScale / columnScale may be negative this has to be done after scaling localBounds const PxU32 nbRows = mHeightField->getNbRowsFast(); const PxU32 nbColumns = mHeightField->getNbColumnsFast(); if(localBounds.minimum.x > float(nbRows - 1)) return; if(localBounds.minimum.z > float(nbColumns - 1)) return; if(localBounds.maximum.x < 0.0f) return; if(localBounds.maximum.z < 0.0f) return; const PxU32 minRow = mHeightField->getMinRow(localBounds.minimum.x); const PxU32 maxRow = mHeightField->getMaxRow(localBounds.maximum.x); const PxU32 minColumn = mHeightField->getMinColumn(localBounds.minimum.z); const PxU32 maxColumn = mHeightField->getMaxColumn(localBounds.maximum.z); const PxU32 deltaColumn = maxColumn - minColumn; const PxU32 maxNbTriangles = 2 * deltaColumn * (maxRow - minRow); if(!maxNbTriangles) return; const PxU32 bufferSize = batchSize<=HF_OVERLAP_REPORT_BUFFER_SIZE ? batchSize : HF_OVERLAP_REPORT_BUFFER_SIZE; PxU32 indexBuffer[HF_OVERLAP_REPORT_BUFFER_SIZE]; PxU32 indexBufferUsed = 0; PxU32 offset = minRow * nbColumns + minColumn; const PxReal miny = localBounds.minimum.y; const PxReal maxy = localBounds.maximum.y; const PxU32 columnStride = nbColumns - deltaColumn; for(PxU32 row=minRow; rowgetHeight(offset); const PxReal h1 = mHeightField->getHeight(offset + 1); const PxReal h2 = mHeightField->getHeight(offset + nbColumns); const PxReal h3 = mHeightField->getHeight(offset + nbColumns + 1); const bool bmax = maxy < h0 && maxy < h1 && maxy < h2 && maxy < h3; const bool bmin = miny > h0 && miny > h1 && miny > h2 && miny > h3; if(!(bmax || bmin)) { if(!reportTriangle(callback, mHeightField->getMaterialIndex0(offset), indexBuffer, bufferSize, indexBufferUsed, offset << 1)) return; if(!reportTriangle(callback, mHeightField->getMaterialIndex1(offset), indexBuffer, bufferSize, indexBufferUsed, (offset << 1) + 1)) return; } offset++; } offset += columnStride; } if(indexBufferUsed > 0) callback.reportTouchedTris(indexBufferUsed, indexBuffer); } PxU32 Gu::HeightFieldUtil::getTriangle(const PxTransform& pose, PxTriangle& worldTri, PxU32* _vertexIndices, PxU32* adjacencyIndices, PxTriangleID triangleIndex, bool worldSpaceTranslation, bool worldSpaceRotation) const { #if PX_CHECKED if (!mHeightField->isValidTriangle(triangleIndex)) { PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "HeightFieldShape::getTriangle: Invalid triangle index!"); return 0; } #endif PxVec3 handedness(1.0f); // Vector to invert normal coordinates according to the heightfield scales bool wrongHanded = false; if (mHfGeom->columnScale < 0) { wrongHanded = !wrongHanded; handedness.z = -1.0f; } if (mHfGeom->rowScale < 0) { wrongHanded = !wrongHanded; handedness.x = -1.0f; } /* if (0) // ptchernev: Iterating over triangles becomes a pain. { if (mHeightField.getTriangleMaterial(triangleIndex) == mHfGeom.holeMaterialIndex) { PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "HeightFieldShape::getTriangle: Non-existing triangle (triangle has hole material)!"); return 0; } }*/ PxU32 vertexIndices[3]; mHeightField->getTriangleVertexIndices(triangleIndex, vertexIndices[0], vertexIndices[1+wrongHanded], vertexIndices[2-wrongHanded]); if(adjacencyIndices) { mHeightField->getTriangleAdjacencyIndices( triangleIndex, vertexIndices[0], vertexIndices[1+wrongHanded], vertexIndices[2-wrongHanded], adjacencyIndices[wrongHanded ? 2 : 0], adjacencyIndices[1], adjacencyIndices[wrongHanded ? 0 : 2]); } if(_vertexIndices) { _vertexIndices[0] = vertexIndices[0]; _vertexIndices[1] = vertexIndices[1]; _vertexIndices[2] = vertexIndices[2]; } if (worldSpaceRotation) { if (worldSpaceTranslation) { for (PxU32 vi = 0; vi < 3; vi++) worldTri.verts[vi] = hf2worldp(pose, mHeightField->getVertex(vertexIndices[vi])); } else { for (PxU32 vi = 0; vi < 3; vi++) { // TTP 2390 // local space here is rotated (but not translated) world space worldTri.verts[vi] = pose.q.rotate(hf2shapep(mHeightField->getVertex(vertexIndices[vi]))); } } } else { const PxVec3 offset = worldSpaceTranslation ? pose.p : PxVec3(0.0f); for (PxU32 vi = 0; vi < 3; vi++) worldTri.verts[vi] = hf2shapep(mHeightField->getVertex(vertexIndices[vi])) + offset; } return PxU32(mHeightField->getTriangleMaterial(triangleIndex) != PxHeightFieldMaterial::eHOLE); }