7#include <box3d/box3d.h>
16constexpr uint64_t materialIdMask = 0xFFFFFFFFull;
17constexpr int frictionModeShift = 32;
18constexpr int restitutionModeShift = 35;
19constexpr uint64_t combineModeMask = 0x7ull;
21uint64_t parseCombineMode(
const std::string &mode,
const char *
operation) {
22 if (mode ==
"default")
return 0;
23 if (mode ==
"average")
return 1;
24 if (mode ==
"minimum" || mode ==
"min")
return 2;
25 if (mode ==
"multiply")
return 3;
26 if (mode ==
"maximum" || mode ==
"max")
return 4;
27 throw eve::Exception(
"%s: mode must be default|average|minimum|multiply|maximum",
31std::string combineModeName(uint64_t mode) {
33 case 0:
return "default";
34 case 1:
return "average";
35 case 2:
return "minimum";
36 case 3:
return "multiply";
37 case 4:
return "maximum";
38 default:
return "default";
42b3ShapeDef makeShapeDef(
float density,
float friction,
float restitution,
bool sensor,
43 bool hitEvents,
bool preSolveEvents) {
44 b3ShapeDef def = b3DefaultShapeDef();
45 def.density = density;
46 def.baseMaterial.friction = friction;
48 def.isSensor = sensor;
49 def.enableContactEvents = !sensor;
50 def.enableSensorEvents =
true;
51 def.enableHitEvents = hitEvents && !sensor;
52 def.enablePreSolveEvents = preSolveEvents && !sensor;
56b3HullData *createCheckedHull(
const std::vector<float> &
vertices,
int maxVertices,
59 throw eve::Exception(
"%s: vertices must contain at least four packed XYZ points",
63 if (maxVertices < 4 || maxVertices > 254)
65 std::vector<b3Vec3>
points;
67 for (
size_t i = 0; i <
vertices.size(); i += 3) {
73 b3HullData *hull = b3CreateHull(
points.data(),
static_cast<int>(
points.size()), maxVertices);
75 throw eve::Exception(
"%s: points do not form a valid three-dimensional convex hull",
80b3MeshData *createCheckedMesh(
const std::vector<float> &
vertices,
81 const std::vector<int32_t> &
indices,
bool weldVertices,
82 float weldTolerance,
bool identifyEdges,
bool useMedianSplit,
83 b3Transform transform,
84 const std::vector<uint8_t> *materialIndices,
87 throw eve::Exception(
"%s: vertices must contain at least three packed XYZ points",
92 throw eve::Exception(
"%s: mesh exceeds 1000000 vertices or 2000000 triangles",
94 if (!std::isfinite(weldTolerance) || weldTolerance < 0.f)
96 std::vector<b3Vec3>
points;
98 for (
size_t i = 0; i <
vertices.size(); i += 3) {
103 points.push_back(transform.p + b3RotateVector(transform.q,
point));
109 std::vector<int32_t> mutableIndices =
indices;
111 def.vertices =
points.data();
112 def.indices = mutableIndices.data();
113 def.materialIndices = materialIndices && !materialIndices->empty()
114 ?
const_cast<uint8_t *
>(materialIndices->data())
116 def.vertexCount =
static_cast<int>(
points.size());
117 def.triangleCount =
static_cast<int>(
indices.size() / 3);
118 def.weldVertices = weldVertices;
119 def.weldTolerance = weldTolerance;
120 def.identifyEdges = identifyEdges;
121 def.useMedianSplit = useMedianSplit;
122 b3MeshData *
mesh = b3CreateMesh(&def,
nullptr, 0);
123 if (!
mesh ||
mesh->triangleCount != def.triangleCount) {
130b3HeightFieldData *createCheckedHeightField(
int countX,
int countZ,
float cellSizeX,
132 const std::vector<float> &heights,
133 float globalMin,
float globalMax,
134 bool clockwiseWinding,
const char *
operation) {
135 if (countX < 2 || countZ < 2)
137 const size_t sampleCount =
static_cast<size_t>(countX) *
static_cast<size_t>(countZ);
138 if (sampleCount > 16000000)
140 if (heights.size() != sampleCount)
142 if (!(cellSizeX > 0.f) || !(cellSizeZ > 0.f) || !std::isfinite(cellSizeX) ||
143 !std::isfinite(cellSizeZ))
145 if (!std::isfinite(globalMin) || !std::isfinite(globalMax) || globalMin > globalMax)
147 for (
float height : heights) {
148 if (!std::isfinite(
height) || height < globalMin || height > globalMax)
149 throw eve::Exception(
"%s: every height must be finite and inside global range",
152 std::vector<float> mutableHeights = heights;
153 b3HeightFieldDef def{};
154 def.heights = mutableHeights.data();
155 def.scale = {cellSizeX, 1.f, cellSizeZ};
158 def.globalMinimumHeight = globalMin;
159 def.globalMaximumHeight = globalMax;
160 def.clockwiseWinding = clockwiseWinding;
161 return b3CreateHeightField(&def);
167 float b,
float c, std::vector<float> hullVertices,
int hullMaxVertices,
168 std::vector<float> meshVertices, std::vector<int32_t> meshIndices, b3MeshData *meshData,
169 bool meshWeldVertices,
float meshWeldTolerance,
bool meshIdentifyEdges,
bool meshUseMedianSplit,
170 std::vector<float> heightValues,
int heightCountX,
int heightCountZ,
float heightCellSizeX,
171 float heightCellSizeZ,
float heightGlobalMin,
float heightGlobalMax,
bool heightClockwise,
172 b3HeightFieldData *heightData)
176 runtimeHandle_(runtimeHandle),
181 hullVertices_(
std::move(hullVertices)),
182 hullMaxVertices_(hullMaxVertices),
183 meshVertices_(
std::move(meshVertices)),
184 meshIndices_(
std::move(meshIndices)),
186 meshWeldVertices_(meshWeldVertices),
187 meshWeldTolerance_(meshWeldTolerance),
188 meshIdentifyEdges_(meshIdentifyEdges),
189 meshUseMedianSplit_(meshUseMedianSplit),
190 heightValues_(
std::move(heightValues)),
191 heightCountX_(heightCountX),
192 heightCountZ_(heightCountZ),
193 heightCellSizeX_(heightCellSizeX),
194 heightCellSizeZ_(heightCellSizeZ),
195 heightGlobalMin_(heightGlobalMin),
196 heightGlobalMax_(heightGlobalMax),
197 heightClockwise_(heightClockwise),
198 heightData_(heightData),
201 const int materialCount = b3Shape_GetMeshMaterialCount(shapeId_);
202 meshMaterials_.reserve(
static_cast<size_t>(materialCount));
203 for (
int i = 0; i < materialCount; ++i)
204 meshMaterials_.push_back(b3Shape_GetMeshSurfaceMaterial(shapeId_, i));
211 b3Shape_SetUserData(shapeId_,
nullptr);
212 b3DestroyShape(shapeId_,
true);
216 b3DestroyMesh(meshData_);
220 b3DestroyHeightField(heightData_);
221 heightData_ =
nullptr;
232 if (
isValid()) b3Shape_SetUserData(shapeId_,
nullptr);
244 b3DestroyMesh(meshData_);
248 b3DestroyHeightField(heightData_);
249 heightData_ =
nullptr;
253 b3Shape_SetUserData(shapeId_,
nullptr);
254 b3DestroyShape(shapeId_,
true);
256 b3DestroyMesh(meshData_);
260 b3DestroyHeightField(heightData_);
261 heightData_ =
nullptr;
270void Shape3D::recreate(
bool sensor) {
271 if (!body_ || !body_->
isValid())
272 throw eve::Exception(
"Shape3D: cannot rebuild geometry after body destruction");
274 float density =
isValid() ? b3Shape_GetDensity(shapeId_) : 1.f;
276 isValid() ? b3Shape_GetSurfaceMaterial(shapeId_) : b3DefaultSurfaceMaterial();
278 const int materialCount = b3Shape_GetMeshMaterialCount(shapeId_);
279 meshMaterials_.clear();
280 meshMaterials_.reserve(
static_cast<size_t>(materialCount));
281 for (
int i = 0; i < materialCount; ++i)
282 meshMaterials_.push_back(b3Shape_GetMeshSurfaceMaterial(shapeId_, i));
287 b3Shape_SetUserData(shapeId_,
nullptr);
288 b3DestroyShape(shapeId_,
false);
292 b3DestroyMesh(meshData_);
296 b3DestroyHeightField(heightData_);
297 heightData_ =
nullptr;
300 b3ShapeDef def = makeShapeDef(density,
material.friction,
material.restitution, sensor,
301 hitEventsEnabled_, oneWayEnabled_);
302 def.enableCustomFiltering = contactOverrideFiltering_;
303 def.explosionScale = explosionScale_;
306 const b3Quat localRotation{{localQx_, localQy_, localQz_}, localQw_};
307 const b3Vec3 localPosition{localX_, localY_, localZ_};
310 b3BoxHull box = b3MakeBoxHull(a_, b_, c_);
311 b3Transform transform{localPosition, localRotation};
312 shapeId_ = b3CreateTransformedHullShape(body_->
raw(), &def, &box.base, transform,
313 b3Vec3{1.f, 1.f, 1.f});
318 sphere.center = localPosition;
320 shapeId_ = b3CreateSphereShape(body_->
raw(), &def, &sphere);
325 const b3Vec3
axis = b3RotateVector(localRotation, b3Vec3{0.f, a_, 0.f});
326 capsule.center1 = localPosition -
axis;
327 capsule.center2 = localPosition +
axis;
329 shapeId_ = b3CreateCapsuleShape(body_->
raw(), &def, &capsule);
334 createCheckedHull(hullVertices_, hullMaxVertices_,
"Shape3D.recreate");
335 b3Transform transform{localPosition, localRotation};
336 shapeId_ = b3CreateTransformedHullShape(body_->
raw(), &def, hull, transform,
337 b3Vec3{1.f, 1.f, 1.f});
342 if (b3Body_GetType(body_->
raw()) != b3_staticBody)
343 throw eve::Exception(
"Shape3D.recreate: triangle meshes require a static body");
344 b3Transform transform{localPosition, localRotation};
345 meshData_ = createCheckedMesh(meshVertices_, meshIndices_, meshWeldVertices_,
346 meshWeldTolerance_, meshIdentifyEdges_,
347 meshUseMedianSplit_, transform,
348 &meshMaterialIndices_,
"Shape3D.recreate");
349 if (!meshMaterials_.empty()) {
350 def.materials = meshMaterials_.data();
351 def.materialCount =
static_cast<int>(meshMaterials_.size());
353 shapeId_ = b3CreateMeshShape(body_->
raw(), &def, meshData_, b3Vec3_one);
357 if (b3Body_GetType(body_->
raw()) != b3_staticBody)
358 throw eve::Exception(
"Shape3D.recreate: height fields require a static body");
359 heightData_ = createCheckedHeightField(
360 heightCountX_, heightCountZ_, heightCellSizeX_, heightCellSizeZ_, heightValues_,
361 heightGlobalMin_, heightGlobalMax_, heightClockwise_,
"Shape3D.recreate");
362 shapeId_ = b3CreateHeightFieldShape(body_->
raw(), &def, heightData_);
366 b3Shape_SetUserData(shapeId_,
this);
367 meshMaterialsDirty_ =
false;
372 if (!body_ || !body_->
isValid())
return;
373 if (
isValid() && b3Shape_IsSensor(shapeId_) == sensor)
return;
377void Shape3D::enableContactOverrideFiltering() {
378 if (contactOverrideFiltering_)
return;
379 contactOverrideFiltering_ =
true;
395 return hitEventsEnabled_;
399 float minNormalDot) {
400 if (!std::isfinite(
nx) || !std::isfinite(
ny) || !std::isfinite(
nz) ||
401 !std::isfinite(planeOffset) || !std::isfinite(
margin) ||
402 !std::isfinite(minNormalDot))
403 throw eve::Exception(
"Shape3D.setOneWay: all values must be finite");
404 const float lengthSquared =
nx *
nx +
ny *
ny +
nz *
nz;
405 if (!(lengthSquared > 1e-12f))
406 throw eve::Exception(
"Shape3D.setOneWay: normal must be non-zero");
409 if (minNormalDot < -1.f || minNormalDot > 1.f)
410 throw eve::Exception(
"Shape3D.setOneWay: minNormalDot must be in [-1,1]");
411 const float inverseLength = 1.f / std::sqrt(lengthSquared);
412 oneWayLocalNormalX_ =
nx * inverseLength;
413 oneWayLocalNormalY_ =
ny * inverseLength;
414 oneWayLocalNormalZ_ =
nz * inverseLength;
415 oneWayPlaneOffset_ = planeOffset;
417 oneWayMinNormalDot_ = minNormalDot;
418 oneWayEnabled_ =
true;
423 oneWayEnabled_ =
false;
424 if (
isValid()) b3Shape_EnablePreSolveEvents(shapeId_,
false);
427void Shape3D::refreshOneWayWorldData() {
428 if (!oneWayEnabled_ || !body_ || !body_->
isValid())
return;
429 const b3WorldTransform bodyTransform = b3Body_GetTransform(body_->
raw());
430 const b3Quat localRotation{{localQx_, localQy_, localQz_}, localQw_};
431 const b3Quat worldRotation = b3MulQuat(bodyTransform.q, localRotation);
432 const b3Vec3 localNormal{oneWayLocalNormalX_, oneWayLocalNormalY_, oneWayLocalNormalZ_};
433 const b3Vec3 worldNormal = b3RotateVector(worldRotation, localNormal);
434 const b3Vec3 localPosition{localX_, localY_, localZ_};
435 const b3Vec3 bodyOffset = b3RotateVector(bodyTransform.q, localPosition);
436 const b3Pos shapeOrigin = b3OffsetPos(bodyTransform.p, bodyOffset);
437 const b3Pos planePoint = b3OffsetPos(shapeOrigin, oneWayPlaneOffset_ * worldNormal);
438 oneWayWorldPointX_ = planePoint.x;
439 oneWayWorldPointY_ = planePoint.y;
440 oneWayWorldPointZ_ = planePoint.z;
441 oneWayWorldNormalX_ = worldNormal.x;
442 oneWayWorldNormalY_ = worldNormal.y;
443 oneWayWorldNormalZ_ = worldNormal.z;
446bool Shape3D::allowsOneWayContact(
double pointX,
double pointY,
double pointZ,
float normalX,
447 float normalY,
float normalZ)
const {
448 if (!oneWayEnabled_)
return true;
449 const double side = (pointX - oneWayWorldPointX_) * oneWayWorldNormalX_ +
450 (pointY - oneWayWorldPointY_) * oneWayWorldNormalY_ +
451 (pointZ - oneWayWorldPointZ_) * oneWayWorldNormalZ_;
452 const float alignment = normalX * oneWayWorldNormalX_ + normalY * oneWayWorldNormalY_ +
453 normalZ * oneWayWorldNormalZ_;
454 return side >= -
static_cast<double>(oneWayMargin_) && alignment >= oneWayMinNormalDot_;
474 throw eve::Exception(
"Shape3D.setBoxSize: dimensions must be finite and > 0");
475 if (!body_ || !body_->
isValid())
479 const float halfDepth = 0.5f *
depth;
490 throw eve::Exception(
"Shape3D.setSphereRadius: shape is not a sphere");
492 throw eve::Exception(
"Shape3D.setSphereRadius: radius must be finite and > 0");
493 if (!body_ || !body_->
isValid())
503 throw eve::Exception(
"Shape3D.setCapsuleSize: shape is not a capsule");
507 "Shape3D.setCapsuleSize: height must be finite and >= 0; radius finite and > 0");
508 if (!body_ || !body_->
isValid())
520 throw eve::Exception(
"Shape3D.setConvexHullVertices: shape is not a convex hull");
521 if (!body_ || !body_->
isValid())
522 throw eve::Exception(
"Shape3D.setConvexHullVertices: body destroyed");
523 b3HullData *validated =
524 createCheckedHull(
vertices, maxVertices,
"Shape3D.setConvexHullVertices");
525 b3DestroyHull(validated);
526 if (
vertices == hullVertices_ && maxVertices == hullMaxVertices_)
return;
529 hullMaxVertices_ = maxVertices;
534 const std::vector<int32_t> &
indices,
bool weldVertices,
535 float weldTolerance,
bool identifyEdges,
536 bool useMedianSplit) {
538 throw eve::Exception(
"Shape3D.setTriangleMeshData: shape is not a triangle mesh");
539 if (!body_ || !body_->
isValid())
540 throw eve::Exception(
"Shape3D.setTriangleMeshData: body destroyed");
541 if (b3Body_GetType(body_->
raw()) != b3_staticBody)
542 throw eve::Exception(
"Shape3D.setTriangleMeshData: triangle meshes require a static body");
543 const b3Transform transform{{localX_, localY_, localZ_},
544 b3Quat{{localQx_, localQy_, localQz_}, localQw_}};
545 b3MeshData *validated = createCheckedMesh(
vertices,
indices, weldVertices, weldTolerance,
546 identifyEdges, useMedianSplit, transform,
548 "Shape3D.setTriangleMeshData");
549 b3DestroyMesh(validated);
551 weldVertices == meshWeldVertices_ && weldTolerance == meshWeldTolerance_ &&
552 identifyEdges == meshIdentifyEdges_ && useMedianSplit == meshUseMedianSplit_)
557 meshWeldVertices_ = weldVertices;
558 meshWeldTolerance_ = weldTolerance;
559 meshIdentifyEdges_ = identifyEdges;
560 meshUseMedianSplit_ = useMedianSplit;
561 if (meshMaterialIndices_.size() !=
indices.size() / 3) {
562 meshMaterialIndices_.clear();
563 if (meshMaterials_.size() > 1) meshMaterials_.resize(1);
569 const std::vector<int32_t> &materialIndices) {
572 "Shape3D.setTriangleMeshMaterialIndices: shape is not a triangle mesh");
573 const size_t triangleCount = meshIndices_.size() / 3;
574 if (materialIndices.size() != triangleCount)
576 "Shape3D.setTriangleMeshMaterialIndices: one index is required per triangle");
577 std::vector<uint8_t> encoded;
578 encoded.reserve(materialIndices.size());
579 int requiredCount = 1;
580 for (int32_t
index : materialIndices) {
581 if (index < 0 || index > 254)
583 "Shape3D.setTriangleMeshMaterialIndices: indices must be in [0, 254]");
584 encoded.push_back(
static_cast<uint8_t
>(
index));
585 requiredCount = std::max(requiredCount,
static_cast<int>(
index) + 1);
587 if (encoded == meshMaterialIndices_ &&
588 requiredCount ==
static_cast<int>(meshMaterials_.size()))
591 const auto oldIndices = meshMaterialIndices_;
592 const auto oldMaterials = meshMaterials_;
593 meshMaterialIndices_ = std::move(encoded);
594 if (meshMaterials_.empty()) meshMaterials_.push_back(b3DefaultSurfaceMaterial());
595 meshMaterials_.resize(
static_cast<size_t>(requiredCount), meshMaterials_.front());
596 meshMaterialsDirty_ =
true;
600 meshMaterialIndices_ = oldIndices;
601 meshMaterials_ = oldMaterials;
602 meshMaterialsDirty_ =
true;
611 "Shape3D.getTriangleMeshMaterialIndex: shape is not a triangle mesh");
612 const int triangleCount =
static_cast<int>(meshIndices_.size() / 3);
613 if (triangleIndex < 0 || triangleIndex >= triangleCount)
614 throw eve::Exception(
"Shape3D.getTriangleMeshMaterialIndex: triangle out of range");
615 return meshMaterialIndices_.empty() ? 0 : meshMaterialIndices_[
static_cast<size_t>(triangleIndex)];
619 return kind_ ==
Kind::TriangleMesh ? std::max(1,
static_cast<int>(meshMaterials_.size())) : 0;
623 float rollingResistance,
float tangentX,
624 float tangentY,
float tangentZ,
int materialId,
625 const std::string &frictionMode,
626 const std::string &restitutionMode) {
628 throw eve::Exception(
"Shape3D.setTriangleMeshMaterial: shape is not a triangle mesh");
630 throw eve::Exception(
"Shape3D.setTriangleMeshMaterial: slot out of range");
631 if (!std::isfinite(friction) || friction < 0.f || !std::isfinite(
restitution) ||
632 restitution < 0.f || !std::isfinite(rollingResistance) || rollingResistance < 0.f ||
633 !std::isfinite(tangentX) || !std::isfinite(tangentY) || !std::isfinite(tangentZ))
635 "Shape3D.setTriangleMeshMaterial: numeric values must be finite and non-negative where required");
636 const uint64_t frictionCode =
637 parseCombineMode(frictionMode,
"Shape3D.setTriangleMeshMaterial");
638 const uint64_t restitutionCode =
639 parseCombineMode(restitutionMode,
"Shape3D.setTriangleMeshMaterial");
640 b3SurfaceMaterial
material = b3DefaultSurfaceMaterial();
643 material.rollingResistance = rollingResistance;
644 material.tangentVelocity = {tangentX, tangentY, tangentZ};
645 material.userMaterialId =
static_cast<uint32_t
>(materialId) |
646 (frictionCode << frictionModeShift) |
647 (restitutionCode << restitutionModeShift);
648 if (meshMaterials_.empty()) meshMaterials_.push_back(b3DefaultSurfaceMaterial());
649 meshMaterials_[
static_cast<size_t>(slot)] =
material;
655 throw eve::Exception(
"Shape3D.getTriangleMeshMaterialFriction: slot out of range");
656 return meshMaterials_.empty() ?
getFriction() : meshMaterials_[
static_cast<size_t>(slot)].friction;
661 throw eve::Exception(
"Shape3D.getTriangleMeshMaterialRestitution: slot out of range");
668 "Shape3D.getTriangleMeshMaterialRollingResistance: slot out of range");
670 : meshMaterials_[
static_cast<size_t>(slot)].rollingResistance;
675 throw eve::Exception(
"Shape3D.getTriangleMeshMaterialId: slot out of range");
676 const uint64_t
id = meshMaterials_.empty()
677 ? b3Shape_GetSurfaceMaterial(shapeId_).userMaterialId
678 : meshMaterials_[
static_cast<size_t>(slot)].userMaterialId;
679 return static_cast<int>(
static_cast<uint32_t
>(
id));
684 throw eve::Exception(
"Shape3D.setHeightFieldHeights: shape is not a height field");
685 if (!body_ || !body_->
isValid())
686 throw eve::Exception(
"Shape3D.setHeightFieldHeights: body destroyed");
687 b3HeightFieldData *validated = createCheckedHeightField(
688 heightCountX_, heightCountZ_, heightCellSizeX_, heightCellSizeZ_, heights,
689 heightGlobalMin_, heightGlobalMax_, heightClockwise_,
690 "Shape3D.setHeightFieldHeights");
691 b3DestroyHeightField(validated);
692 if (heights == heightValues_)
return;
694 heightValues_ = heights;
699 const std::vector<float> &heights) {
701 throw eve::Exception(
"Shape3D.setHeightFieldRegion: shape is not a height field");
702 if (
x < 0 ||
z < 0 ||
width < 1 || depth < 1 || x > heightCountX_ -
width ||
703 z > heightCountZ_ -
depth)
704 throw eve::Exception(
"Shape3D.setHeightFieldRegion: region is outside the sample grid");
705 if (heights.size() !=
static_cast<size_t>(
width) *
static_cast<size_t>(
depth))
706 throw eve::Exception(
"Shape3D.setHeightFieldRegion: data size must equal width * depth");
707 std::vector<float>
updated = heightValues_;
708 for (
int row = 0; row <
depth; ++row) {
710 updated[
static_cast<size_t>(
z + row) *
static_cast<size_t>(heightCountX_) +
711 static_cast<size_t>(
x +
column)] =
712 heights[
static_cast<size_t>(row) *
static_cast<size_t>(
width) +
713 static_cast<size_t>(
column)];
721 throw eve::Exception(
"Shape3D.getHeightFieldHeight: shape is not a height field");
722 if (x < 0 || x >= heightCountX_ || z < 0 || z >= heightCountZ_)
723 throw eve::Exception(
"Shape3D.getHeightFieldHeight: sample index out of range");
724 return heightValues_[
static_cast<size_t>(
z) *
static_cast<size_t>(heightCountX_) +
725 static_cast<size_t>(
x)];
730 if (!body_ || !body_->
isValid())
731 throw eve::Exception(
"Shape3D.setLocalTransform: body destroyed");
732 if (!std::isfinite(
px) || !std::isfinite(
py) || !std::isfinite(
pz) ||
733 !std::isfinite(
qx) || !std::isfinite(
qy) || !std::isfinite(
qz) ||
735 throw eve::Exception(
"Shape3D.setLocalTransform: values must be finite");
737 if (!(lengthSquared > 1e-12f))
738 throw eve::Exception(
"Shape3D.setLocalTransform: quaternion must be non-zero");
739 const float inverseLength = 1.f / std::sqrt(lengthSquared);
740 const float normalizedQx =
qx * inverseLength;
741 const float normalizedQy =
qy * inverseLength;
742 const float normalizedQz =
qz * inverseLength;
743 const float normalizedQw =
qw * inverseLength;
745 (
px != 0.f ||
py != 0.f ||
pz != 0.f || normalizedQx != 0.f ||
746 normalizedQy != 0.f || normalizedQz != 0.f || normalizedQw != 1.f))
748 "Shape3D.setLocalTransform: height fields use their Body transform and require an identity local transform");
749 if (localX_ ==
px && localY_ ==
py && localZ_ ==
pz && localQx_ == normalizedQx &&
750 localQy_ == normalizedQy && localQz_ == normalizedQz && localQw_ == normalizedQw)
757 localQx_ = normalizedQx;
758 localQy_ = normalizedQy;
759 localQz_ = normalizedQz;
760 localQw_ = normalizedQw;
775 if (!std::isfinite(friction) || friction < 0.f)
776 throw eve::Exception(
"Shape3D.setFriction: friction must be finite and >= 0");
778 b3Shape_SetFriction(shapeId_, friction);
785 throw eve::Exception(
"Shape3D.setRestitution: restitution must be finite and >= 0");
791 return isValid() ? b3Shape_GetRestitution(shapeId_) : 0.f;
795 if (!std::isfinite(resistance) || resistance < 0.f)
797 "Shape3D.setRollingResistance: resistance must be finite and >= 0");
799 b3SurfaceMaterial
material = b3Shape_GetSurfaceMaterial(shapeId_);
800 material.rollingResistance = resistance;
801 b3Shape_SetSurfaceMaterial(shapeId_,
material);
805 return isValid() ? b3Shape_GetSurfaceMaterial(shapeId_).rollingResistance : 0.f;
809 if (!std::isfinite(
x) || !std::isfinite(
y) || !std::isfinite(
z))
810 throw eve::Exception(
"Shape3D.setTangentVelocity: components must be finite");
812 b3SurfaceMaterial
material = b3Shape_GetSurfaceMaterial(shapeId_);
814 b3Shape_SetSurfaceMaterial(shapeId_,
material);
818 return isValid() ? b3Shape_GetSurfaceMaterial(shapeId_).tangentVelocity.x : 0.f;
822 return isValid() ? b3Shape_GetSurfaceMaterial(shapeId_).tangentVelocity.y : 0.f;
826 return isValid() ? b3Shape_GetSurfaceMaterial(shapeId_).tangentVelocity.z : 0.f;
831 b3SurfaceMaterial
material = b3Shape_GetSurfaceMaterial(shapeId_);
833 (
material.userMaterialId & ~materialIdMask) |
static_cast<uint32_t
>(
id);
834 b3Shape_SetSurfaceMaterial(shapeId_,
material);
838 const uint64_t encoded = parseCombineMode(mode,
"Shape3D.setFrictionCombineMode");
840 b3SurfaceMaterial
material = b3Shape_GetSurfaceMaterial(shapeId_);
841 material.userMaterialId &= ~(combineModeMask << frictionModeShift);
842 material.userMaterialId |= encoded << frictionModeShift;
843 b3Shape_SetSurfaceMaterial(shapeId_,
material);
847 if (!
isValid())
return "default";
848 return combineModeName((b3Shape_GetSurfaceMaterial(shapeId_).userMaterialId >>
849 frictionModeShift) & combineModeMask);
853 const uint64_t encoded = parseCombineMode(mode,
"Shape3D.setRestitutionCombineMode");
855 b3SurfaceMaterial
material = b3Shape_GetSurfaceMaterial(shapeId_);
856 material.userMaterialId &= ~(combineModeMask << restitutionModeShift);
857 material.userMaterialId |= encoded << restitutionModeShift;
858 b3Shape_SetSurfaceMaterial(shapeId_,
material);
862 if (!
isValid())
return "default";
863 return combineModeName((b3Shape_GetSurfaceMaterial(shapeId_).userMaterialId >>
864 restitutionModeShift) & combineModeMask);
869 ?
static_cast<int>(
static_cast<uint32_t
>(
870 b3Shape_GetSurfaceMaterial(shapeId_).userMaterialId))
876 throw eve::Exception(
"Shape3D.setExplosionScale: scale must be finite and >= 0");
877 if (
scale == explosionScale_)
return;
878 explosionScale_ =
scale;
883 if (!std::isfinite(density) || density < 0.f)
884 throw eve::Exception(
"Shape3D.setDensity: density must be finite and >= 0");
886 b3Shape_SetDensity(shapeId_, density,
true);
893 b3Filter
filter = b3Shape_GetFilter(shapeId_);
894 filter.categoryBits = categoryBits;
895 filter.maskBits = maskBits;
896 b3Shape_SetFilter(shapeId_,
filter,
false);
900 return isValid() ? b3Shape_GetFilter(shapeId_).categoryBits : 0;
905 b3Filter
filter = b3Shape_GetFilter(shapeId_);
906 filter.categoryBits = bits;
907 b3Shape_SetFilter(shapeId_,
filter,
true);
912 b3Filter
filter = b3Shape_GetFilter(shapeId_);
914 b3Shape_SetFilter(shapeId_,
filter,
true);
918 return isValid() ? b3Shape_GetFilter(shapeId_).maskBits : 0;
923 b3Filter
filter = b3Shape_GetFilter(shapeId_);
925 b3Shape_SetFilter(shapeId_,
filter,
true);
929 return isValid() ? b3Shape_GetFilter(shapeId_).groupIndex : 0;
937 proxy.points = &
point;
941 b3WorldTransform wt = b3Body_GetTransform(body_->
raw());
943 xf.p = b3Vec3{
static_cast<float>(wt.p.x),
static_cast<float>(wt.p.y),
944 static_cast<float>(wt.p.z)};
950 const b3HullData *hull = b3Shape_GetHull(shapeId_);
951 if (!hull)
return false;
952 return b3OverlapHull(hull, xf, &proxy);
955 b3Sphere sphere = b3Shape_GetSphere(shapeId_);
956 return b3OverlapSphere(&sphere, xf, &proxy);
959 b3Capsule capsule = b3Shape_GetCapsule(shapeId_);
960 return b3OverlapCapsule(&capsule, xf, &proxy);
963 const b3Mesh
mesh = b3Shape_GetMesh(shapeId_);
964 return mesh.data ? b3OverlapMesh(&
mesh, xf, &proxy) :
false;
967 const b3HeightFieldData *heightField = b3Shape_GetHeightField(shapeId_);
968 return heightField ? b3OverlapHeightField(heightField, xf, &proxy) :
false;
ActionParameterOperation operation
std::vector< std::uint32_t > indices
std::array< float, 3 > scale
std::array< std::uint64_t, kPixelChunkSize *kPixelChunkSize > updated
std::vector< Point > vertices
std::shared_ptr< const std::vector< glm::vec2 > > points
EVENGINE_API_FOUNDATION public API.
static constexpr RuntimeHandle invalid() noexcept
Returns the canonical invalid handle.
3D rigid body (Box3D) in meter-space coordinates (+Y up by convention). Owned by a World3D; create pr...
bool isValid() const
True while the underlying Box3D body is still alive.
void setLocalPosition(float x, float y, float z)
Sets local translation while preserving local rotation.
void setDensity(float density)
void invalidate()
Internal: marks the wrapper invalid after destruction.
void setTag(int tag)
User-defined integer tag preserved for the wrapper lifetime.
void setBoxSize(float width, float height, float depth)
Atomically changes full box dimensions in meters.
float getTriangleMeshMaterialRestitution(int slot) const
Restitution of a triangle-mesh material slot.
int getTriangleMeshMaterialId(int slot) const
Application material ID of a triangle-mesh material slot.
void setOneWay(float nx, float ny, float nz, float planeOffset, float margin, float minNormalDot)
Enables a one-way collision plane in shape-local space.
void setTriangleMeshData(const std::vector< float > &vertices, const std::vector< int32_t > &indices, bool weldVertices=true, float weldTolerance=0.001f, bool identifyEdges=true, bool useMedianSplit=false)
Atomically replaces static triangle-mesh source data and rebuilds its BVH.
std::string getKind() const
Kind name including primitives, "convexHull", "triangleMesh", or "heightField".
bool testPoint(float x, float y, float z) const
Point-in-shape test in world meters.
void setHitEventsEnabled(bool enabled)
Enables significant collision hit events for this shape.
void setMaterialId(int id)
Sets a low-32-bit application material identifier exposed by native queries.
int getTriangleMeshMaterialIndex(int triangleIndex) const
Material-slot index assigned to a source triangle.
void setTriangleMeshMaterialIndices(const std::vector< int32_t > &materialIndices)
Atomically assigns one material-slot index per source mesh triangle.
void setMaskBits(uint64_t bits)
Sets collision mask bits without changing the category.
void setTriangleMeshMaterial(int slot, float friction, float restitution, float rollingResistance, float tangentX, float tangentY, float tangentZ, int materialId, const std::string &frictionMode, const std::string &restitutionMode)
Replaces one triangle-mesh material slot without rebuilding geometry.
float getHeightFieldHeight(int x, int z) const
Source height at a grid sample.
void setCategoryBits(uint64_t bits)
Sets collision category bits without changing the mask.
int getTriangleMeshMaterialCount() const
Number of material slots on a triangle mesh, otherwise zero.
float getTangentVelocityY() const
Local-space conveyor velocity Y component.
float getTriangleMeshMaterialRollingResistance(int slot) const
Rolling resistance of a triangle-mesh material slot.
Shape3D(World3D *world, Body3D *body, b3ShapeId shapeId, PhysicsShapeHandle runtimeHandle, Kind kind, float a, float b, float c, std::vector< float > hullVertices={}, int hullMaxVertices=64, std::vector< float > meshVertices={}, std::vector< int32_t > meshIndices={}, b3MeshData *meshData=nullptr, bool meshWeldVertices=true, float meshWeldTolerance=0.001f, bool meshIdentifyEdges=true, bool meshUseMedianSplit=false, std::vector< float > heightValues={}, int heightCountX=0, int heightCountZ=0, float heightCellSizeX=1.f, float heightCellSizeZ=1.f, float heightGlobalMin=0.f, float heightGlobalMax=0.f, bool heightClockwise=false, b3HeightFieldData *heightData=nullptr)
Internal: wraps a Box3D shape (use Body3D::new*Shape).
void setRestitution(float restitution)
void setSphereRadius(float radius)
Atomically changes sphere radius in meters.
void setLocalTransform(float px, float py, float pz, float qx, float qy, float qz, float qw)
Atomically sets the shape transform relative to its Body3D.
void setGroupIndex(int index)
Collision group; negative never collides, positive always collides.
float getTriangleMeshMaterialFriction(int slot) const
Friction of a triangle-mesh material slot.
void setHeightFieldHeights(const std::vector< float > &heights)
Atomically replaces every source height and rebuilds compressed terrain data.
uint64_t getMaskBits() const
Current 64-bit collision mask.
float getFriction() const
float getRestitution() const
uint64_t getCategoryBits() const
void setFrictionCombineMode(const std::string &mode)
Sets friction combine mode: default, average, minimum, multiply, or maximum.
void destroy()
Destroys the shape inside its world.
void setLocalRotation(float qx, float qy, float qz, float qw)
Sets local rotation while preserving local translation.
void setTangentVelocity(float x, float y, float z)
Sets local-space conveyor velocity projected onto contact surfaces.
void setFilterBits(uint64_t categoryBits, uint64_t maskBits)
Collision filter bits used by world ray/query filters (Box3D b3Filter). categoryBits 声明本形状属于哪些类别;mask...
int getMaterialId() const
Low-32-bit application material identifier.
void setCapsuleSize(float height, float radius)
Atomically changes capsule segment height and radius in meters.
void setConvexHullVertices(const std::vector< float > &vertices, int maxVertices=64)
Atomically rebuilds a convex hull from packed local XYZ vertices.
int getGroupIndex() const
void setRestitutionCombineMode(const std::string &mode)
Sets restitution combine mode: default, average, minimum, multiply, or maximum.
float getTangentVelocityZ() const
Local-space conveyor velocity Z component.
std::string getRestitutionCombineMode() const
Current restitution combine mode.
void setHeightFieldRegion(int x, int z, int width, int depth, const std::vector< float > &heights)
Atomically updates a rectangular sample region in row-major order.
void setRollingResistance(float resistance)
Rolling resistance coefficient, finite and non-negative.
void disableOneWay()
Disables one-way filtering and restores ordinary two-sided collision.
void setSensor(bool sensor)
Sensor shapes report contacts but never collide.
std::string getFrictionCombineMode() const
Current friction combine mode.
bool areHitEventsEnabled() const
Whether significant collision hit events are enabled.
void setFriction(float friction)
Material properties.
void setExplosionScale(float scale)
Sets non-negative multiplier applied to geometry-aware explosion impulses.
float getTangentVelocityX() const
Local-space conveyor velocity X component.
float getRollingResistance() const
Current rolling resistance; physically affects spheres and capsules.
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
void updateShapeTag(Shape3D *shape)
Internal: refreshes the tag snapshot for a live shape handle.
void registerShapeHandle(Shape3D *shape)
Internal: snapshots a backend handle for destruction-safe end events.
void forgetShape(Shape3D *shape)
Optional physics backend for vehicle mobility and body attach.
int axis(int64_t a, size_t rank)
Axis.