Skip to content

Commit 8487110

Browse files
authored
Merge branch 'main' into issue/870_unsymmetrical
2 parents fcf4cfb + d111194 commit 8487110

File tree

7 files changed

+421
-11
lines changed

7 files changed

+421
-11
lines changed

dart/collision/RaycastOption.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,23 @@ namespace dart {
3636
namespace collision {
3737

3838
//==============================================================================
39-
RaycastOption::RaycastOption(bool enableAllHits, bool sortByClosest)
40-
: mEnableAllHits(enableAllHits), mSortByClosest(sortByClosest)
39+
RaycastOption::RaycastOption(
40+
bool enableAllHits, bool sortByClosest, RaycastFilter filter)
41+
: mEnableAllHits(enableAllHits),
42+
mSortByClosest(sortByClosest),
43+
mFilter(std::move(filter))
4144
{
4245
// Do nothing
4346
}
4447

48+
//==============================================================================
49+
bool RaycastOption::passesFilter(const CollisionObject* object) const
50+
{
51+
if (!mFilter)
52+
return true;
53+
54+
return mFilter(object);
55+
}
56+
4557
} // namespace collision
4658
} // namespace dart

dart/collision/RaycastOption.hpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,35 @@
3535

3636
#include <dart/Export.hpp>
3737

38+
#include <functional>
3839
#include <memory>
3940

4041
#include <cstddef>
4142

4243
namespace dart {
4344
namespace collision {
4445

46+
class CollisionObject;
47+
4548
struct DART_API RaycastOption
4649
{
50+
using RaycastFilter = std::function<bool(const CollisionObject*)>;
51+
4752
/// Constructor
48-
RaycastOption(bool enableAllHits = false, bool sortByClosest = false);
53+
RaycastOption(
54+
bool enableAllHits = false,
55+
bool sortByClosest = false,
56+
RaycastFilter filter = nullptr);
57+
58+
/// Returns true when the filter is not set or allows the object.
59+
bool passesFilter(const CollisionObject* object) const;
4960

5061
bool mEnableAllHits;
5162

5263
bool mSortByClosest;
5364

54-
// TODO(JS): Add filter
65+
/// Optional filter to reject hits from specific collision objects.
66+
RaycastFilter mFilter;
5567
};
5668

5769
} // namespace collision

dart/collision/bullet/BulletCollisionDetector.cpp

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,16 +377,50 @@ bool BulletCollisionDetector::raycast(
377377
const auto btFrom = convertVector3(from);
378378
const auto btTo = convertVector3(to);
379379

380-
if (option.mEnableAllHits) {
380+
const bool needsAllHits
381+
= option.mEnableAllHits || static_cast<bool>(option.mFilter);
382+
383+
if (needsAllHits) {
384+
auto lessFraction = [](const RayHit& a, const RayHit& b) {
385+
return a.mFraction < b.mFraction;
386+
};
387+
381388
auto callback = btCollisionWorld::AllHitsRayResultCallback(btFrom, btTo);
382389
castedGroup->updateEngineData();
383390
collisionWorld->rayTest(btFrom, btTo, callback);
384391

385-
if (result == nullptr)
386-
return callback.hasHit();
392+
if (result == nullptr) {
393+
if (!callback.hasHit())
394+
return false;
395+
396+
if (!option.mFilter)
397+
return true;
398+
399+
for (int i = 0; i < callback.m_collisionObjects.size(); ++i) {
400+
const auto* collObj = static_cast<BulletCollisionObject*>(
401+
callback.m_collisionObjects[i]->getUserPointer());
402+
if (option.passesFilter(collObj))
403+
return true;
404+
}
405+
406+
return false;
407+
}
387408

388409
if (callback.hasHit()) {
389410
reportRayHits(callback, option, *result);
411+
412+
if (!option.mEnableAllHits && !result->mRayHits.empty()) {
413+
if (option.mSortByClosest) {
414+
result->mRayHits.resize(1);
415+
} else {
416+
const auto closest = std::min_element(
417+
result->mRayHits.begin(), result->mRayHits.end(), lessFraction);
418+
const RayHit closestHit = *closest;
419+
result->mRayHits.clear();
420+
result->mRayHits.emplace_back(closestHit);
421+
}
422+
}
423+
390424
return result->hasHit();
391425
} else {
392426
return false;
@@ -765,7 +799,7 @@ RayHit convertRayHit(
765799
//==============================================================================
766800
void reportRayHits(
767801
const btCollisionWorld::ClosestRayResultCallback callback,
768-
const RaycastOption& /*option*/,
802+
const RaycastOption& option,
769803
RaycastResult& result)
770804
{
771805
// This function shouldn't be called if callback has not ray hit.
@@ -779,7 +813,9 @@ void reportRayHits(
779813

780814
result.mRayHits.clear();
781815
result.mRayHits.reserve(1);
782-
result.mRayHits.emplace_back(rayHit);
816+
817+
if (option.passesFilter(rayHit.mCollisionObject))
818+
result.mRayHits.emplace_back(rayHit);
783819
}
784820

785821
//==============================================================================
@@ -807,7 +843,8 @@ void reportRayHits(
807843
callback.m_hitPointWorld[i],
808844
callback.m_hitNormalWorld[i],
809845
callback.m_hitFractions[i]);
810-
result.mRayHits.emplace_back(rayHit);
846+
if (option.passesFilter(rayHit.mCollisionObject))
847+
result.mRayHits.emplace_back(rayHit);
811848
}
812849

813850
if (option.mSortByClosest)

dart/utils/sdf/SdfParser.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ SDFBodyNode readBodyNode(
10811081
// Name attribute
10821082
std::string name = getAttributeString(bodyNodeElement, "name");
10831083
properties.mName = name;
1084+
const std::string bodyName = name;
10841085

10851086
//--------------------------------------------------------------------------
10861087
// gravity
@@ -1108,13 +1109,33 @@ SDFBodyNode readBodyNode(
11081109

11091110
//--------------------------------------------------------------------------
11101111
// inertia
1112+
constexpr double kMinReasonableMass = 1e-9; // 1 microgram
1113+
bool massSpecified = false;
11111114
if (hasElement(bodyNodeElement, "inertial")) {
11121115
const ElementPtr& inertiaElement = getElement(bodyNodeElement, "inertial");
11131116

11141117
// mass
11151118
if (hasElement(inertiaElement, "mass")) {
11161119
double mass = getValueDouble(inertiaElement, "mass");
1120+
if (mass <= 0.0) {
1121+
DART_WARN(
1122+
"[SdfParser] Link [{}] has non-positive mass [{}]. Clamping to {} "
1123+
"to continue parsing.",
1124+
bodyName,
1125+
mass,
1126+
kMinReasonableMass);
1127+
mass = kMinReasonableMass;
1128+
} else if (mass < kMinReasonableMass) {
1129+
DART_WARN(
1130+
"[SdfParser] Link [{}] has a very small mass [{} kg]; clamping to "
1131+
"{} to avoid numerical issues.",
1132+
bodyName,
1133+
mass,
1134+
kMinReasonableMass);
1135+
mass = kMinReasonableMass;
1136+
}
11171137
properties.mInertia.setMass(mass);
1138+
massSpecified = true;
11181139
}
11191140

11201141
// offset
@@ -1137,7 +1158,32 @@ SDFBodyNode readBodyNode(
11371158
double iyz = getValueDouble(moiElement, "iyz");
11381159

11391160
properties.mInertia.setMoment(ixx, iyy, izz, ixy, ixz, iyz);
1161+
} else if (massSpecified) {
1162+
// Keep the inertia physically meaningful by matching the moment scale
1163+
// to the specified mass; geometry is unknown, so use an isotropic guess.
1164+
const double mass = properties.mInertia.getMass();
1165+
properties.mInertia.setMoment(Eigen::Matrix3d::Identity() * mass);
1166+
DART_WARN(
1167+
"[SdfParser] Link [{}] defines <mass> but no <inertia>; using an "
1168+
"isotropic inertia tensor (mass * I). Provide <inertia> for "
1169+
"physically correct behavior.",
1170+
bodyName);
11401171
}
1172+
1173+
if (!massSpecified) {
1174+
DART_WARN(
1175+
"[SdfParser] Link [{}] is missing <mass>; using default mass of 1 "
1176+
"kg. "
1177+
"Specify <inertial><mass> to avoid unstable simulation.",
1178+
bodyName);
1179+
}
1180+
} else {
1181+
DART_WARN(
1182+
"[SdfParser] Link [{}] is missing <inertial>; using default "
1183+
"mass/inertia "
1184+
"(1 kg, unit inertia). Specify <inertial> to avoid unstable "
1185+
"simulation.",
1186+
bodyName);
11411187
}
11421188

11431189
SDFBodyNode sdfBodyNode;

python/stubs/dartpy/collision.pyi

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,20 @@ class RayHit:
458458
class RaycastOption:
459459
mEnableAllHits: bool
460460
mSortByClosest: bool
461+
mFilter: typing.Optional[typing.Callable[[CollisionObject], bool]]
461462
@typing.overload
462463
def __init__(self) -> None:
463464
...
464465
@typing.overload
465466
def __init__(self, enableAllHits: bool) -> None:
466467
...
467468
@typing.overload
468-
def __init__(self, enableAllHits: bool, sortByClosest: bool) -> None:
469+
def __init__(
470+
self,
471+
enableAllHits: bool,
472+
sortByClosest: bool,
473+
filter: typing.Optional[typing.Callable[[CollisionObject], bool]] = None,
474+
) -> None:
469475
...
470476
class RaycastResult:
471477
mRayHits: list[RayHit]

0 commit comments

Comments
 (0)