1//
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions
4// are met:
5// * Redistributions of source code must retain the above copyright
6// notice, this list of conditions and the following disclaimer.
7// * Redistributions in binary form must reproduce the above copyright
8// notice, this list of conditions and the following disclaimer in the
9// documentation and/or other materials provided with the distribution.
10// * Neither the name of NVIDIA CORPORATION nor the names of its
11// contributors may be used to endorse or promote products derived
12// from this software without specific prior written permission.
13//
14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
15// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
18// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25//
26// Copyright (c) 2008-2021 NVIDIA Corporation. All rights reserved.
27// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
28// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
29
30
31#include "GuConvexMesh.h"
32#include "PsFoundation.h"
33#include "PsMathUtils.h"
34#include "Cooking.h"
35
36#include "GuHillClimbing.h"
37#include "GuBigConvexData2.h"
38#include "GuInternal.h"
39#include "GuSerialize.h"
40#include "VolumeIntegration.h"
41
42///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
43
44#include "VolumeIntegration.h"
45#include "ConvexHullBuilder.h"
46#include "ConvexMeshBuilder.h"
47#include "BigConvexDataBuilder.h"
48
49#include "CmUtils.h"
50#include "PsVecMath.h"
51
52using namespace physx;
53using namespace Gu;
54using namespace Ps::aos;
55
56///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
57
58ConvexMeshBuilder::ConvexMeshBuilder(const bool buildGRBData) : hullBuilder(&mHullData, buildGRBData), mBigConvexData(NULL), mMass(0.0f), mInertia(PxIdentity)
59{
60}
61
62ConvexMeshBuilder::~ConvexMeshBuilder()
63{
64 PX_DELETE_AND_RESET(mBigConvexData);
65}
66
67///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
68// load the mesh data from given polygons
69bool ConvexMeshBuilder::build(const PxConvexMeshDesc& desc, PxU32 gaussMapVertexLimit, bool validateOnly, ConvexHullLib* hullLib)
70{
71 if(!desc.isValid())
72 {
73 Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, messageFmt: "Gu::ConvexMesh::loadFromDesc: desc.isValid() failed!");
74 return false;
75 }
76
77 if(!loadConvexHull(desc, hullLib))
78 return false;
79
80 // Compute local bounds (*after* hull has been created)
81 PxBounds3 minMaxBounds;
82 computeBoundsAroundVertices(bounds&: minMaxBounds, nbVerts: mHullData.mNbHullVertices, verts: hullBuilder.mHullDataHullVertices);
83 mHullData.mAABB = CenterExtents(minMaxBounds);
84
85 if(mHullData.mNbHullVertices > gaussMapVertexLimit)
86 {
87 if(!computeGaussMaps())
88 {
89 return false;
90 }
91 }
92
93 if(validateOnly)
94 return true;
95
96// TEST_INTERNAL_OBJECTS
97 computeInternalObjects();
98//~TEST_INTERNAL_OBJECTS
99
100 return true;
101}
102
103///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
104
105PX_COMPILE_TIME_ASSERT(sizeof(PxMaterialTableIndex)==sizeof(PxU16));
106bool ConvexMeshBuilder::save(PxOutputStream& stream, bool platformMismatch) const
107{
108 // Export header
109 if(!writeHeader(a: 'C', b: 'V', c: 'X', d: 'M', PX_CONVEX_VERSION, mismatch: platformMismatch, stream))
110 return false;
111
112 // Export serialization flags
113 PxU32 serialFlags = 0;
114
115 writeDword(value: serialFlags, mismatch: platformMismatch, stream);
116
117 if(!hullBuilder.save(stream, platformMismatch))
118 return false;
119
120 // Export local bounds
121// writeFloat(geomEpsilon, platformMismatch, stream);
122 writeFloat(value: 0.0f, mismatch: platformMismatch, stream);
123 writeFloat(value: mHullData.mAABB.getMin(axis: 0), mismatch: platformMismatch, stream);
124 writeFloat(value: mHullData.mAABB.getMin(axis: 1), mismatch: platformMismatch, stream);
125 writeFloat(value: mHullData.mAABB.getMin(axis: 2), mismatch: platformMismatch, stream);
126 writeFloat(value: mHullData.mAABB.getMax(axis: 0), mismatch: platformMismatch, stream);
127 writeFloat(value: mHullData.mAABB.getMax(axis: 1), mismatch: platformMismatch, stream);
128 writeFloat(value: mHullData.mAABB.getMax(axis: 2), mismatch: platformMismatch, stream);
129
130 // Export mass info
131 writeFloat(value: mMass, mismatch: platformMismatch, stream);
132 writeFloatBuffer(src: reinterpret_cast<const PxF32*>(&mInertia), nb: 9, mismatch: platformMismatch, stream);
133 writeFloatBuffer(src: &mHullData.mCenterOfMass.x, nb: 3, mismatch: platformMismatch, stream);
134
135 // Export gaussmaps
136 if(mBigConvexData)
137 {
138 writeFloat(value: 1.0f, mismatch: platformMismatch, stream); //gauss map flag true
139 BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices);
140 SVMB.save(stream, platformMismatch);
141 }
142 else
143 writeFloat(value: -1.0f, mismatch: platformMismatch, stream); //gauss map flag false
144
145// TEST_INTERNAL_OBJECTS
146 writeFloat(value: mHullData.mInternal.mRadius, mismatch: platformMismatch, stream);
147 writeFloat(value: mHullData.mInternal.mExtents[0], mismatch: platformMismatch, stream);
148 writeFloat(value: mHullData.mInternal.mExtents[1], mismatch: platformMismatch, stream);
149 writeFloat(value: mHullData.mInternal.mExtents[2], mismatch: platformMismatch, stream);
150//~TEST_INTERNAL_OBJECTS
151 return true;
152}
153
154//////////////////////////////////////////////////////////////////////////
155// instead of saving the data into stream, we copy the mesh data
156// into internal Gu::ConvexMesh.
157bool ConvexMeshBuilder::copy(Gu::ConvexHullInitData& hullData)
158{
159 // hull builder data copy
160 PxU32 nb = 0;
161 hullBuilder.copy(hullData&: hullData.mHullData, nb);
162 hullData.mNb = nb;
163
164 hullData.mInertia = mInertia;
165 hullData.mMass = mMass;
166
167 // mass props
168 hullData.mHullData.mAABB = mHullData.mAABB;
169 hullData.mHullData.mCenterOfMass = mHullData.mCenterOfMass;
170
171 // big convex data
172 if(mBigConvexData)
173 {
174 hullData.mHullData.mBigConvexRawData = &mBigConvexData->mData;
175 hullData.mBigConvexData = mBigConvexData;
176 mBigConvexData = NULL;
177 }
178 else
179 {
180 hullData.mHullData.mBigConvexRawData = NULL;
181 hullData.mBigConvexData = NULL;
182 }
183
184 // internal data
185 hullData.mHullData.mInternal.mRadius = mHullData.mInternal.mRadius;
186 hullData.mHullData.mInternal.mExtents[0] = mHullData.mInternal.mExtents[0];
187 hullData.mHullData.mInternal.mExtents[1] = mHullData.mInternal.mExtents[1];
188 hullData.mHullData.mInternal.mExtents[2] = mHullData.mInternal.mExtents[2];
189
190 return true;
191}
192
193///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
194// compute mass and inertia of the convex mesh
195void ConvexMeshBuilder::computeMassInfo(bool lowerPrecision)
196{
197 if(mMass <= 0.0f) //not yet computed.
198 {
199 PxIntegrals integrals;
200 PxConvexMeshDesc meshDesc;
201 meshDesc.points.count = mHullData.mNbHullVertices;
202 meshDesc.points.data = hullBuilder.mHullDataHullVertices;
203 meshDesc.points.stride = sizeof(PxVec3);
204
205 meshDesc.polygons.data = hullBuilder.mHullDataPolygons;
206 meshDesc.polygons.stride = sizeof(Gu::HullPolygonData);
207 meshDesc.polygons.count = hullBuilder.mHull->mNbPolygons;
208
209 meshDesc.indices.data = hullBuilder.mHullDataVertexData8;
210
211 // using the centroid of the convex for the volume integration solved accuracy issues in cases where the inertia tensor
212 // ended up close to not being positive definite and after a few further transforms the diagonalized inertia tensor ended
213 // up with negative values.
214 PxVec3 mean(0.0f);
215 for(PxU32 i=0; i < mHullData.mNbHullVertices; i++)
216 mean += hullBuilder.mHullDataHullVertices[i];
217 mean *= (1.0f / mHullData.mNbHullVertices);
218
219 bool status = lowerPrecision ?
220 computeVolumeIntegralsEberlySIMD(mesh: meshDesc, density: 1.0f, integrals, origin: mean) : computeVolumeIntegralsEberly(mesh: meshDesc, density: 1.0f, integrals, origin: mean);
221 if(status)
222 {
223
224 integrals.getOriginInertia(inertia&: reinterpret_cast<PxMat33&>(mInertia));
225 mHullData.mCenterOfMass = integrals.COM;
226
227 //note: the mass will be negative for an inside-out mesh!
228 if(mInertia.column0.isFinite() && mInertia.column1.isFinite() && mInertia.column2.isFinite()
229 && mHullData.mCenterOfMass.isFinite() && PxIsFinite(f: PxReal(integrals.mass)))
230 {
231 if (integrals.mass < 0)
232 {
233 Ps::getFoundation().error(PX_WARN, messageFmt: "Gu::ConvexMesh: Mesh has a negative volume! Is it open or do (some) faces have reversed winding? (Taking absolute value.)");
234 integrals.mass = -integrals.mass;
235 mInertia = -mInertia;
236 }
237
238 mMass = PxReal(integrals.mass); //set mass to valid value.
239 return;
240 }
241 }
242 Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, messageFmt: "Gu::ConvexMesh: Error computing mesh mass properties!\n");
243 }
244}
245
246///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
247#if PX_VC
248#pragma warning(push)
249#pragma warning(disable:4996) // permitting use of gatherStrided until we have a replacement.
250#endif
251
252bool ConvexMeshBuilder::loadConvexHull(const PxConvexMeshDesc& desc, ConvexHullLib* hullLib)
253{
254 // gather points
255 PxVec3* geometry = reinterpret_cast<PxVec3*>(PxAlloca(sizeof(PxVec3)*desc.points.count));
256 Cooking::gatherStrided(src: desc.points.data, dst: geometry, nbElem: desc.points.count, elemSize: sizeof(PxVec3), stride: desc.points.stride);
257
258 PxU32* topology = NULL;
259
260 // gather indices
261 // store the indices into topology if we have the polygon data
262 if(desc.indices.data)
263 {
264 topology = reinterpret_cast<PxU32*>(PxAlloca(sizeof(PxU32)*desc.indices.count));
265 if (desc.flags & PxConvexFlag::e16_BIT_INDICES)
266 {
267 // conversion; 16 bit index -> 32 bit index & stride
268 PxU32* dest = topology;
269 const PxU32* pastLastDest = topology + desc.indices.count;
270 const PxU8* source = reinterpret_cast<const PxU8*>(desc.indices.data);
271 while (dest < pastLastDest)
272 {
273 const PxU16 * trig16 = reinterpret_cast<const PxU16*>(source);
274 *dest++ = *trig16;
275 source += desc.indices.stride;
276 }
277 }
278 else
279 {
280 Cooking::gatherStrided(src: desc.indices.data, dst: topology, nbElem: desc.indices.count, elemSize: sizeof(PxU32), stride: desc.indices.stride);
281 }
282 }
283
284 // gather polygons
285 PxHullPolygon* hullPolygons = NULL;
286 if(desc.polygons.data)
287 {
288 hullPolygons = reinterpret_cast<PxHullPolygon*>(PxAlloca(sizeof(PxHullPolygon)*desc.polygons.count));
289 Cooking::gatherStrided(src: desc.polygons.data,dst: hullPolygons,nbElem: desc.polygons.count,elemSize: sizeof(PxHullPolygon),stride: desc.polygons.stride);
290
291 // if user polygons, make sure the largest one is the first one
292 if (!hullLib)
293 {
294 PxU32 largestPolygon = 0;
295 for (PxU32 i = 1; i < desc.polygons.count; i++)
296 {
297 if(hullPolygons[i].mNbVerts > hullPolygons[largestPolygon].mNbVerts)
298 largestPolygon = i;
299 }
300 if(largestPolygon != 0)
301 {
302 PxHullPolygon movedPolygon = hullPolygons[0];
303 hullPolygons[0] = hullPolygons[largestPolygon];
304 hullPolygons[largestPolygon] = movedPolygon;
305 }
306 }
307 }
308
309 const bool doValidation = desc.flags & PxConvexFlag::eDISABLE_MESH_VALIDATION ? false : true;
310 if(!hullBuilder.init(nbVerts: desc.points.count, verts: geometry, indices: topology, nbIndices: desc.indices.count, nbPolygons: desc.polygons.count, hullPolygons, doValidation, hullLib))
311 {
312 Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, messageFmt: "Gu::ConvexMesh::loadConvexHull: convex hull init failed!");
313 return false;
314 }
315 computeMassInfo(lowerPrecision: desc.flags & PxConvexFlag::eFAST_INERTIA_COMPUTATION);
316
317 return true;
318}
319
320#if PX_VC
321#pragma warning(pop)
322#endif
323
324///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
325// compute polygons from given triangles. This is support function used in extensions. We do not accept triangles as an input for convex mesh desc.
326bool ConvexMeshBuilder::computeHullPolygons(const PxU32& nbVerts,const PxVec3* verts, const PxU32& nbTriangles, const PxU32* triangles, PxAllocatorCallback& inAllocator,
327 PxU32& outNbVerts, PxVec3*& outVertices , PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& polygons)
328{
329 if(!hullBuilder.computeHullPolygons(nbVerts,verts,nbTriangles,triangles))
330 {
331 Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, messageFmt: "ConvexMeshBuilder::computeHullPolygons: compute convex hull polygons failed. Provided triangles dont form a convex hull.");
332 return false;
333 }
334
335 outNbVerts = hullBuilder.mHull->mNbHullVertices;
336 nbPolygons = hullBuilder.mHull->mNbPolygons;
337
338 outVertices = reinterpret_cast<PxVec3*>(inAllocator.allocate(size: outNbVerts*sizeof(PxVec3),typeName: "PxVec3",__FILE__,__LINE__));
339 PxMemCopy(dest: outVertices,src: hullBuilder.mHullDataHullVertices,count: outNbVerts*sizeof(PxVec3));
340
341 nbIndices = 0;
342 for (PxU32 i = 0; i < nbPolygons; i++)
343 {
344 nbIndices += hullBuilder.mHullDataPolygons[i].mNbVerts;
345 }
346
347 indices = reinterpret_cast<PxU32*>(inAllocator.allocate(size: nbIndices*sizeof(PxU32),typeName: "PxU32",__FILE__,__LINE__));
348 for (PxU32 i = 0; i < nbIndices; i++)
349 {
350 indices[i] = hullBuilder.mHullDataVertexData8[i];
351 }
352
353 polygons = reinterpret_cast<PxHullPolygon*>(inAllocator.allocate(size: nbPolygons*sizeof(PxHullPolygon),typeName: "PxHullPolygon",__FILE__,__LINE__));
354
355 for (PxU32 i = 0; i < nbPolygons; i++)
356 {
357 const Gu::HullPolygonData& polygonData = hullBuilder.mHullDataPolygons[i];
358 PxHullPolygon& outPolygon = polygons[i];
359 outPolygon.mPlane[0] = polygonData.mPlane.n.x;
360 outPolygon.mPlane[1] = polygonData.mPlane.n.y;
361 outPolygon.mPlane[2] = polygonData.mPlane.n.z;
362 outPolygon.mPlane[3] = polygonData.mPlane.d;
363
364 outPolygon.mNbVerts = polygonData.mNbVerts;
365 outPolygon.mIndexBase = polygonData.mVRef8;
366
367 for (PxU32 j = 0; j < polygonData.mNbVerts; j++)
368 {
369 PX_ASSERT(indices[outPolygon.mIndexBase + j] == hullBuilder.mHullDataVertexData8[polygonData.mVRef8+j]);
370 }
371 }
372
373 return true;
374}
375
376///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
377// compute big convex data
378bool ConvexMeshBuilder::computeGaussMaps()
379{
380 // The number of polygons is limited to 256 because the gaussmap encode 256 polys maximum
381
382 PxU32 density = 16;
383 // density = 64;
384 // density = 8;
385 // density = 2;
386
387 PX_DELETE(mBigConvexData);
388 PX_NEW_SERIALIZED(mBigConvexData,BigConvexData);
389 BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices);
390 // valencies we need to compute first, they are needed for min/max precompute
391 SVMB.computeValencies(meshBuilder: hullBuilder);
392 SVMB.precompute(subdiv: density);
393
394 return true;
395}
396
397///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
398// TEST_INTERNAL_OBJECTS
399
400static void ComputeInternalExtent(Gu::ConvexHullData& data, const Gu::HullPolygonData* hullPolys)
401{
402 const PxVec3 e = data.mAABB.getMax() - data.mAABB.getMin();
403
404 // PT: For that formula, see %SDKRoot%\InternalDocumentation\Cooking\InternalExtents.png
405 const float r = data.mInternal.mRadius / sqrtf(x: 3.0f);
406
407 const float epsilon = 1E-7f;
408
409 const PxU32 largestExtent = Ps::largestAxis(v: e);
410 PxU32 e0 = Ps::getNextIndex3(i: largestExtent);
411 PxU32 e1 = Ps::getNextIndex3(i: e0);
412 if(e[e0] < e[e1])
413 Ps::swap<PxU32>(x&: e0,y&: e1);
414
415 data.mInternal.mExtents[0] = FLT_MAX;
416 data.mInternal.mExtents[1] = FLT_MAX;
417 data.mInternal.mExtents[2] = FLT_MAX;
418
419 // PT: the following code does ray-vs-plane raycasts.
420
421 // find the largest box along the largest extent, with given internal radius
422 for(PxU32 i = 0; i < data.mNbPolygons; i++)
423 {
424 // concurrent with search direction
425 const float d = hullPolys[i].mPlane.n[largestExtent];
426 if((-epsilon < d && d < epsilon))
427 continue;
428
429 const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(v: data.mCenterOfMass);
430 const float denBase = 1.0f/hullPolys[i].mPlane.n[largestExtent];
431 const float numn0 = r * hullPolys[i].mPlane.n[e0];
432 const float numn1 = r * hullPolys[i].mPlane.n[e1];
433
434 float num = numBase - numn0 - numn1;
435 float ext = PxMax(a: fabsf(x: num*denBase), b: r);
436 if(ext < data.mInternal.mExtents[largestExtent])
437 data.mInternal.mExtents[largestExtent] = ext;
438
439 num = numBase - numn0 + numn1;
440 ext = PxMax(a: fabsf(x: num *denBase), b: r);
441 if(ext < data.mInternal.mExtents[largestExtent])
442 data.mInternal.mExtents[largestExtent] = ext;
443
444 num = numBase + numn0 + numn1;
445 ext = PxMax(a: fabsf(x: num *denBase), b: r);
446 if(ext < data.mInternal.mExtents[largestExtent])
447 data.mInternal.mExtents[largestExtent] = ext;
448
449 num = numBase + numn0 - numn1;
450 ext = PxMax(a: fabsf(x: num *denBase), b: r);
451 if(ext < data.mInternal.mExtents[largestExtent])
452 data.mInternal.mExtents[largestExtent] = ext;
453 }
454
455 // Refine the box along e0,e1
456 for(PxU32 i = 0; i < data.mNbPolygons; i++)
457 {
458 const float denumAdd = hullPolys[i].mPlane.n[e0] + hullPolys[i].mPlane.n[e1];
459 const float denumSub = hullPolys[i].mPlane.n[e0] - hullPolys[i].mPlane.n[e1];
460
461 const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(v: data.mCenterOfMass);
462 const float numn0 = data.mInternal.mExtents[largestExtent] * hullPolys[i].mPlane.n[largestExtent];
463
464 if(!(-epsilon < denumAdd && denumAdd < epsilon))
465 {
466 float num = numBase - numn0;
467 float ext = PxMax(a: fabsf(x: num/ denumAdd), b: r);
468 if(ext < data.mInternal.mExtents[e0])
469 data.mInternal.mExtents[e0] = ext;
470
471 num = numBase + numn0;
472 ext = PxMax(a: fabsf(x: num / denumAdd), b: r);
473 if(ext < data.mInternal.mExtents[e0])
474 data.mInternal.mExtents[e0] = ext;
475 }
476
477 if(!(-epsilon < denumSub && denumSub < epsilon))
478 {
479 float num = numBase - numn0;
480 float ext = PxMax(a: fabsf(x: num / denumSub), b: r);
481 if(ext < data.mInternal.mExtents[e0])
482 data.mInternal.mExtents[e0] = ext;
483
484 num = numBase + numn0;
485 ext = PxMax(a: fabsf(x: num / denumSub), b: r);
486 if(ext < data.mInternal.mExtents[e0])
487 data.mInternal.mExtents[e0] = ext;
488 }
489 }
490 data.mInternal.mExtents[e1] = data.mInternal.mExtents[e0];
491}
492
493//////////////////////////////////////////////////////////////////////////
494// compute internal objects, get the internal extent and radius
495void ConvexMeshBuilder::computeInternalObjects()
496{
497 const Gu::HullPolygonData* hullPolys = hullBuilder.mHullDataPolygons;
498 Gu::ConvexHullData& data = mHullData;
499
500 // compute the internal radius
501 data.mInternal.mRadius = FLT_MAX;
502 for(PxU32 i=0;i<data.mNbPolygons;i++)
503 {
504 const float dist = fabsf(x: hullPolys[i].mPlane.distance(p: data.mCenterOfMass));
505 if(dist<data.mInternal.mRadius)
506 data.mInternal.mRadius = dist;
507 }
508
509 ComputeInternalExtent(data, hullPolys);
510}
511
512//~TEST_INTERNAL_OBJECTS
513

source code of qtquick3dphysics/src/3rdparty/PhysX/source/physxcooking/src/convex/ConvexMeshBuilder.cpp