Changelog: v2.10
v2.10.0 (unreleased)
Rebuild applications after updating (Camera, RaisimObject, RayraiWindow, raisim::World, ArticulatedSystem,
RGBCamera, DepthCamera, SpinningLidar and InertialMeasurementUnit ABI changes).
rayrai
Fix: animated mesh scales in
rayrai_offroad_buggycould discard loaded assets and repeatedly restart asynchronous mesh loading, making the buggy flicker. Scale, local pose and link-attachment edits now retain loaded mesh geometry and refresh bounds/shadows in both bound-World and snapshot rendering. Resizing a capsule or replacing a mesh file keeps unrelated assets alive. Therayrai_animated_mesh_reuse_testregression test and its--benchmarkmode cover asset reuse; see Physics snapshots and Performance notes and the C++ API reference.Camera
getRawImage()now applies the configured measurement noise in both sensor-setter andCUSTOM_BUFFERmodes. GPU LiDAR applies its configured range/angle errors and dropout. RawreadSceneColorBgra()andreadLinearDepth()remain clean. Readbacks preserve application packing, draw-buffer and viewport state; software OpenGL uses exact CPU half-float conversion.New: physics snapshots, for rendering on a thread of its own while physics continues.
raisin::PhysicsSnapshotCapture::capture(world)copies a World’s render state (object and link poses, shapes and appearance, heightmap and deformable vertices, spatial tendons, and the.rscenescene) into an immutable, sharedraisin::PhysicsSnapshot, without locks or OpenGL work;RayraiWindow::applyPhysicsSnapshot()shows it on the render thread, andclearPhysicsSnapshot()shows the bound World again. Renders of an applied snapshot, shadows included, never read a World.Cameracan be built from copiedRGBCameraPropertiesorDepthCameraProperties, andCamera::readSceneColorBgra()andreadLinearDepth()read an image back into application buffers without a RaiSim sensor. A snapshot an application built or edited itself is checked: an object without its description or mesh, malformed vertex blocks, or indices past the vertices raisestd::invalid_argument, and the viewer keeps showing the last complete frame. See Physics snapshots.Heightmap and deformable vertices travel in immutable blocks shared between frames (
PhysicsMeshSnapshot::blocks), one block per visual row of a heightmap. A capture reads only the heightmap rows whose revision advanced (HeightMap::getHeightRowRevisions()) and refills the visual rows they reach, and a viewer uploads the rows that differ from the ones it shows, so an edit costs what it changes, whatever frames the viewer skipped.Heightmap meshes are built faster: each sample position is computed once instead of once per neighboring vertex (the vertices are bit-identical).
Fix: heightmap rows updated in place by a visual patch lost their secondary texture coordinates, which a whole build copies from the primary ones.
Fix: when the viewer stopped showing a World’s
.rscenescene (another World,swapWorld(), or a snapshot without the scene), the scene’s local fog volumes, decals, irradiance volumes and reflection probes stayed. A scene’s reflection probes capture the objects of the frame that shows the scene.A World bound to the viewer is drawn the same way: each render copies it (
updateObjectLists()) and draws the shadow maps and the image from that copy, so one frame always shows one physics state. Images are unchanged (every feature showcase image matches the previous version up to its run-to-run noise); batched spheres now turn with their body, as unbatched ones already did. Moving objects, heightmap edits and deformable terrain got cheaper, unchanged objects slightly dearer (about 7 ns each per render).rayrai_world_sync_benchmark, 320x240 frames on one Ryzen 9 3950X core with an RTX 2070 SUPER: 1,000 boxes with 100 moving 398 → 366 µs per frame, a 257x257 heightmap with a 9x9 visual patch edited every frame 469 → 400 µs, the same edit on a 1025x1025 heightmap 6.23 → 5.11 ms (its copy 2.03 → 0.59 ms), a 257x257 deformable terrain under a sliding plate 458 → 384 µs, 16 integrated ANYmals unchanged at 1.51 ms, and 1,000 idle static boxes 4.7 → 11.8 µs. An upscaled viewer frame (viewerRenderScalebelow 1) copies the World once.Picking ids are numbered in World order, so the picking view (and
04_picking_idsof the feature showcase) no longer changes from run to run.Fix:
setObjectHidden()stayed in effect only until the object’s appearance or pose changed, which showed a hidden single body again. Hiding is now kept untilsetObjectHidden(object, false).Fix: rayrai cleared
HeightMap::isUpdated()and the heightmap’s visual dirty region, whichRaisimServerstreams heightmap edits from, so a viewer and a server on one World could each miss edits the other read first. rayrai now leaves these flags alone; it notices heightmap edits by their render revision.A camera following an object (
Camera::targetObject) follows the object’s position in the rendered frame; the camera never reads the object. A fixed distance locked before the first frame (setCameraFixedDistance(true)right aftersetTargetObject()) is measured from the object on that frame instead of from the origin. The camera lets the object go when it is removed, when another object takes its address, or when the viewer shows another World.Camera::releaseTarget()clears the target and both locks.renderWithExternalCamera()andrenderDepthPlaneDistance()overloads that take a RaiSim sensor, and the GPU lidar, read the live World; they throwstd::invalid_argumentwhile an applied snapshot is shown.RaisimObject(internal) holds a copy of its object’s snapshot state: it is built from aPhysicsObjectSnapshot,updateAppearance(),updateHeightMapVisual(),isValid()andinvalidate()were removed,draw()no longer takes a render serial, andsourceObject()andcullingBounds()were added.raisin::toGlm()moved fromRayraiWindow.hpptohelper.hpp, whichRayraiWindow.hppincludes; rayrai uses it for every RaiSim-to-GLM conversion.helper.hppaddsHeightmapVisualRowsOfSourceRows()andFillHeightmapVisualRowsRaisim()for filling chosen visual rows of a heightmap.Build:
RayraiWindow.cppshares its post-processing call, world shadow caster drawing, frame cache keys and light hashing between the viewer and external cameras and between shadow passes, instead of keeping copies (20,440 → 19,607 lines).New
rayrai_world_sync_benchmarkmeasures the per-frame copy and a small frame of a bound World; every timed step changes the World.
RaiSim
Faster primitive contacts with dense triangle meshes: sphere, capsule and cylinder queries skip BVH nodes outside their reach or unable to deepen a full contact set; box queries also cull nodes separated from a padded oriented box. Candidate order, contact limits and merging are preserved. See Collision Detection and Colliders.
Fix: box-mesh GJK/EPA fallback could report penetration for a separated triangle. Separating-plane and SAT checks now reject triangles beyond the 0.2 mm tolerance before the fallback.
New source benchmarks:
mesh_primitive_proximitymeasures dense G1 hand meshes near five primitives, andg1_hand_self_collisionmeasures a fixed-base, 29-DoF G1 with intermittent hand self-contact. Both compare RaiSim and MuJoCo and report tail timings and contact counts; RaiSim also reports result signatures. See Benchmark.Sensor seed zero now derives a stream from the sensor identity and owning object’s stable id.
World::setSensorNoiseSeed(uint64_t)decorrelates environments with one call; XML and checkpoints preserve it. Nonzero per-sensor seeds remain explicit, reproducible streams, mixed with the World seed. Worlds with matching seeds and object creation order replay the same noise.Python bindings expose all noise model fields, resets, manual noise application, IMU channel properties and delivery timestamps, and the World sensor seed. Depth properties retain mutable access; RGB, LiDAR and IMU getters return copies for setter-based edits. RGB
getImageBuffer()returns PythonbytesandsetImageBuffer()accepts them, preserving all byte values including alpha; the legacy character-list setter remains accepted. The stale binding to the removed internal contact fieldMappInv_redis removed;MappInv_iremains available.RaiSimGymTorch assigns distinct World sensor noise seeds per environment, including when user seed hooks override the base. Per-environment user seeds are assigned without the shared increment race; overflowing integer seed ranges are rejected before applying any hooks.
World sensor reseeding preserves IMU measurements, filters, bias, acquisition clock and delayed samples.
InertialMeasurementUnit::resetNoise()restarts only its random stream. Python RGB, LiDAR and IMU properties are copied for validated setter-based edits; depth retains its mutable API.IMU latency queues grow gradually and retain at most 65,536 samples. Capacity or allocation failures drop new samples with a warning and an observable
getDroppedMeasurementCount(), preserving physics stepping and pending deliveries.IMU delivery latency must be between 0 and 60 seconds; larger values are rejected before changing the model. Test RealSense resources explicitly disable camera noise, matching shipped assets.
New active sensor models: RGB read, shot and row noise; depth bias, distance-dependent noise, quantization and dropout; LiDAR range bias/noise, angular noise, quantization and dropout. Models run on CPU captures, Rayrai captures and server TCP commits, with independent seeds, reset and checkpoint replay. Removed LiDAR hits shrink the scan. Manual buffer setters remain unchanged. See Sensor noise.
Migration: camera noise settings that were previously metadata now affect captures. Shipped RealSense definitions explicitly use
type="none"to preserve clean images. Third-party URDF files with nonzero noise must settype="none"to opt out. Camera<noise>is optional; unsupported type strings, includinggaussian_quantized, now raisestd::invalid_argument.IMUs acquire the completed physics state without enabling inverse dynamics. If an application relied on the loader enabling it for frame-acceleration or inverse-dynamics queries, call
setComputeInverseDynamics(true)explicitly. IMU acquisition/filtering does not overwrite those queries’ acceleration storage.IMUs add per-channel bias, white-noise density, bias random walk, first-order bandwidth, quantization, per-axis saturation and delivery latency. Saturation now clamps each axis instead of scaling the vector norm: (40, 40, 0) with a 50 m/s² limit remains unchanged. Orientation starts at identity and remains ground truth, not an attitude estimate.
getUpdateTimeStamp()is the delivered sample’s acquisition time and stays -1 before first delivery;getDeliveryTimeStamp()reports availability.update(World&)now acquires measurements instead of doing nothing. InvalidsetProperties()calls throw before changing the model; invalid legacy mutable edits restore the last valid model with a warning.DepthCamera::depthToPointCloud()divides optical depth by the ray’s forward component, fixing fisheye point-cloud scale; non-forward rays produce NaNs. Active depth models normalize non-positive/out-of-clip returns to NaN; disabled models preserve raw buffers.After RK4 integration,
getGeneralizedAcceleration()reports the completed step’s velocity change divided by its time step.New:
World::getLifetimeIdentity()identifies a World for its whole lifetime, even when a later World reuses its address, andWorld::getSharedRscene()shares its immutable.rscenescene.Visual-only heightmap edits (
updateVisualHeight(),updateVisualHeightPatch()) and deformable integration advanceObject::getRenderRevision().New:
HeightMap::getHeightRowRevisions(), one revision per sample row that advances whenever a height in the row changes (by any update method, a checkpoint restore, or a deformable terrain sinking). UnlikeisUpdated()and the visual dirty region, the revisions are never cleared, so any number of renderers can follow them. Whole-map updates advance only the rows whose heights differ.updateVisualHeightPatch()that leaves every sample at its value (bit for bit) reports no update:isUpdated(), the visual dirty region and the render revision stay as they were.Fix:
ArticulatedSystem::solveIK()that applied a solution moved the links without advancinggetRenderRevision(), so renderers that cache by that revision could keep drawing the old pose.Fix: a URDF
<sphere>,<cylinder>or<capsule>left the shape parameters it does not set (VisObject::visShapeParam,CollisionDefinition::shapeParam) uninitialized; they are now zero.