Ray Test
A ray test is a collision check between the world and a ray. Users specify the starting point, direction, and length. It returns the closest hit along the ray (if any).
Arguments
Single-ray test (rayTest)
Signature (C++):
const RayCollisionList& rayTest(const Vec<3>& start,
const Vec<3>& direction,
double length,
size_t objectId = size_t(-10),
size_t localId = size_t(-10),
CollisionGroup collisionMask = CollisionGroup(-1));
start: ray origin in world frame.direction: ray direction in world frame. It does not need to be normalized.length: ray length in meters. Only hits within [0, length] are returned.objectId/localId: optional self-filter. Collisions with the given object and local body id pair are ignored (useful for sensors attached to robots). The defaults disable this filter.collisionMask: collision mask to filter which groups the ray can hit. The default (-1) allows all groups.
The returned RayCollisionList stores only the closest hit (0 or 1 item).
Example
Only the following line
auto& col = world.rayTest({0,0,5}, direction, 50.);
performs the ray test. The rest of the code is for demonstration only. The ray test stores only the closest hit.
Getting the hit object (example)
To identify what was hit, check the returned RayCollisionList.
const auto& hits = world.rayTest({0, 0, 5}, direction, 50.0);
if (hits.size() == 0) {
std::cout << "No hit\n";
} else {
const auto& hit = hits[0];
const auto* obj = hit.getObject();
if (obj) {
std::cout << "Hit object: " << obj->getName()
<< " (id=" << obj->getIndexInWorld() << ")\n";
}
std::cout << "Hit position: " << hit.getPosition().transpose() << "\n";
const auto body = hit.getCollisionBody();
if (body) {
std::cout << "Collision body: " << body->name << "\n";
}
}
Batch ray tests (lidar)
For spinning lidar-style scans, World::rayTestLidar(...) generates a
rectangular yaw/pitch sampling pattern and performs the scan in one call.
Signature (C++):
void rayTestLidar(
const Mat<3, 3>& rot,
const Vec<3>& pos,
double yawStartAngle,
double yawIncrement,
size_t yawCount,
double pitchStartAngle,
double pitchIncrement,
size_t pitchCount,
double rangeMin,
double rangeMax,
size_t objectId,
size_t localId,
CollisionGroup collisionMask,
std::vector<Vec<3>, AlignedAllocator<Vec<3>, 32>>& scan);
Both angular axes use the same start angle, increment, count convention.
For sample indices \(y\) and \(p\), the angles are
where \(0 \le y < \mathrm{yawCount}\) and
\(0 \le p < \mathrm{pitchCount}\). Increments are signed: use a negative
increment to scan an axis in the decreasing-angle direction. A single-sample
axis normally uses an increment of zero. If either count is zero, scan
is cleared and no rays are cast.
Minimal example for a complete yaw revolution with 16 pitch channels:
constexpr double pi = 3.14159265358979323846;
constexpr size_t yawCount = 1024;
constexpr size_t pitchCount = 16;
constexpr double yawStartAngle = -pi;
constexpr double yawIncrement = 2.0 * pi / double(yawCount);
constexpr double pitchStartAngle = -15.0 * pi / 180.0;
constexpr double pitchEndAngle = 15.0 * pi / 180.0;
constexpr double pitchIncrement =
(pitchEndAngle - pitchStartAngle) / double(pitchCount - 1);
std::vector<raisim::Vec<3>, raisim::AlignedAllocator<raisim::Vec<3>, 32>> scan;
world.rayTestLidar(rot, pos,
yawStartAngle, yawIncrement, yawCount,
pitchStartAngle, pitchIncrement, pitchCount,
rangeMin, rangeMax,
objectId, localId, collisionMask,
scan);
Yaw is the outer loop and pitch is the inner loop. Consequently, rays are generated in this order:
(yaw[0], pitch[0]), (yaw[0], pitch[1]), ...,
(yaw[1], pitch[0]), (yaw[1], pitch[1]), ...
The output scan contains hit points in the sensor frame. It is compact:
only rays that hit contribute a point, so a miss does not create a placeholder
entry and indices after a miss no longer map directly to angular sample indices.
Arguments (batched lidar scan)
rot: sensor orientation (sensor frame to world frame).pos: sensor position in world frame.yawStartAngle,yawIncrement,yawCount: first yaw angle, signed yaw step, and number of yaw samples.pitchStartAngle,pitchIncrement,pitchCount: first pitch angle, signed pitch step, and number of pitch samples.rangeMin,rangeMax: minimum and maximum sensor-relative range in meters. Each collision ray starts atrangeMinand has lengthrangeMax - rangeMin.objectId/localId: optional self-filter (same asrayTest).collisionMask: collision mask to filter which groups the rays can hit.scan: output hit positions in the sensor frame.
Performance notes
rayTestLidar is optimized for repeated structured scans. It caches the
sensor-frame directions and their normalized forms for an unchanged yaw/pitch
pattern. In a sufficiently large, spatially sparse scene it also builds
conservative angular candidate buckets, so each ray normally checks only nearby
bodies. The buckets are reused while the scene, sensor pose, ranges, self-filter,
and collision mask are unchanged; changes to any of those inputs invalidate the
relevant cache. Bucket bounds are conservative and every retained candidate
still goes through an exact shape intersection. Sphere candidates use the same
quadratic roots and range rules as the contact engine but omit the unused
contact-normal calculation. No path approximates hit positions.
Dense or unsupported angular configurations automatically use the exact ray BVH instead. Small scenes use a scan-wide conservative cull and a compact flat candidate list. These fallbacks preserve the same closest-hit and compact-output semantics.
The source-tree ray_lidar_comparison benchmark compares both APIs using
identical rays in a 100%-hit scan and verifies every hit position plus a timed
checksum. Its default 256-by-16 configuration is also a performance regression
check: rayTestLidar must be faster than repeated rayTest calls.
Run it single-threaded with:
OMP_NUM_THREADS=1 ./build-benchmark/benchmark/benchmarks \
--bench ray_lidar_comparison --raisim --repeat=5
The relative speed still depends on scene layout, motion, filters, and scan shape, so benchmark the application workload when making performance decisions.
Details
RayCollisionItem summary
Each hit entry stores:
getObject(): pointer to the hitraisim::Object(or nullptr if none).getPosition(): world-frame hit position (contact point).getCollisionBody(): collision body handle for the hit (may be null for some objects).
RayCollisionList summary
Container semantics:
size(): number of valid hits in the list.operator[](i): random access to the i-th hit.begin(),end(): iterators over valid hits.back(): iterator to the last valid hit.setSize(n): updates the number of valid hits (used internally).resize(n),reserve(n): storage management.
Notes
RayCollisionListis a lightweight container reused across calls. It is cleared and populated byrayTest(...).It contains at most one item because RaiSim keeps only the closest hit along the ray. Use
list.size()to check whether a hit occurred (0 or 1).
API
RayCollisionItem
-
class RayCollisionItem
RayCollisionList
-
class RayCollisionList
Public Functions
-
inline RayCollisionItem &operator[](size_t i)
Access an item by index.
- Parameters:
i – [in] Index into the hit list.
- Returns:
Hit entry at the given index.
-
inline const RayCollisionItem &operator[](size_t i) const
Access an item by index.
- Parameters:
i – [in] Index into the hit list.
- Returns:
Hit entry at the given index.
-
inline size_t size() const
- Returns:
Number of valid hits.
-
inline iterator begin()
- Returns:
Iterator to the first hit.
-
inline iterator end()
- Returns:
Iterator past the last hit.
-
inline iterator back()
- Returns:
Iterator to the last hit.
-
inline void setSize(size_t size)
Set the number of valid hits in the list.
- Parameters:
size – [in] Number of hits.
-
inline void resize(size_t size)
Resize the storage for hit entries.
- Parameters:
size – [in] New storage size.
-
inline void reserve(size_t size)
Reserve storage for hit entries.
- Parameters:
size – [in] Capacity to reserve.
-
template<bool IsConst>
class basic_iterator
-
inline RayCollisionItem &operator[](size_t i)