Skip to content

Add CpuLidarSensor for physics-based lidar without rendering#593

Open
apojomovsky wants to merge 22 commits intogazebosim:mainfrom
apojomovsky:feature/cpu-lidar
Open

Add CpuLidarSensor for physics-based lidar without rendering#593
apojomovsky wants to merge 22 commits intogazebosim:mainfrom
apojomovsky:feature/cpu-lidar

Conversation

@apojomovsky
Copy link

@apojomovsky apojomovsky commented Feb 19, 2026

🎉 New feature

This PR is 2 out of 3 that implement: #26

Summary

Add CpuLidarSensor, a new sensor class that extends gz::sensors::Sensor directly (not RenderingSensor), receives raycast results from a system plugin, applies noise, and publishes LaserScan and PointCloudPacked messages over gz-transport. Zero dependency on gz-rendering.

The existing GpuLidarSensor requires Ogre2 and a GPU, making it unusable on headless cloud machines, containers, and CI/CD environments. Gazebo Classic had a CPU-based RaySensor backed by the physics engine; this is the new-architecture
equivalent.

CpuLidarSensor extends Sensor instead of the existing Lidar class because Lidar extends RenderingSensor, which would pull in gz-rendering as a transitive dependency. The SDF parsing, noise application, and message publishing logic is reimplemented without that dependency.

GenerateRays() produces entity-frame ray pairs from the SDF lidar configuration. SetRaycastResults() accepts fractions from the physics engine and converts them to ranges (fraction × ray_length). Misses (NaN fraction) produce +inf per REP-117. For 3D scans with multiple vertical layers, LaserScan publishes the middle ring as a 2D slice while PointCloudPacked publishes all rings.

Test it

colcon build --merge-install --packages-select gz-sensors \
  --cmake-args '-DBUILD_TESTING=ON'
colcon test --packages-select gz-sensors \
  --ctest-args -R INTEGRATION_cpu_lidar_sensor

Checklist

  • Signed all commits for DCO
  • Added a screen capture or video to the PR description that demonstrates the feature
  • Added tests
  • Added example and/or tutorial
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • Consider updating Python bindings (if the library has them)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • Updated Bazel files (if adding new files). Created an issue otherwise.
  • While waiting for a review on your PR, please help review another open pull request to support the maintainers
  • Was GenAI used to generate this PR? If so, make sure to add "Generated-by" to your commits. (See this policy for more info.)

Generated-by: Claude Opus 4.6

Disclaimer: The code here was reviewed, tested, and profiled by hand by the author.

Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining Signed-off-by and Generated-by messages.

@apojomovsky apojomovsky requested a review from ahcorde February 20, 2026 15:56
@apojomovsky apojomovsky changed the base branch from gz-sensors10 to main February 20, 2026 20:22
@peci1
Copy link
Contributor

peci1 commented Feb 20, 2026

Would it make sense to directly think about multiple-return lidars when implementing this?

Just today I've asked about it :) #594 .

You now have the whole implementation in your head. How much work do you think this would bring?

- New CpuLidarSensor class extending Sensor (no rendering dependency)
- Load() validates sdf::SensorType::LIDAR
- Integration test verifies sensor creation via SensorFactory
- New gz-sensors-cpu_lidar CMake component

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
- Parse sdf::Lidar from SDF in Load()
- Expose accessors: AngleMin/Max, VerticalAngleMin/Max,
  RangeMin/Max, RayCount, VerticalRayCount
- Test verifies all parsed values match SDF input

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
- GenerateRays() converts SDF lidar parameters (angles, samples, range)
  into (start, end) vector pairs in entity frame
- Tests verify single-layer and multi-layer ray generation
- Ray directions use spherical coordinates (azimuth + inclination)

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
- RayResult struct holds hit point, fraction, normal
- SetRaycastResults() converts fractions to ranges
- No-hit (NaN fraction) maps to +inf per REP-117
- Out-of-range values clamped to ±inf
- Ranges() accessor for computed range data

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
- Advertise LaserScan on sensor topic in Load()
- Update() populates and publishes LaserScan message with
  ranges, angles, frame_id, timestamp
- HasConnections() checks publisher subscribers
- Test verifies message fields and range values via transport

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
…scan

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
…d math

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
…eline

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Update() could be called before SetRaycastResults() when transport
subscribers connect before the first physics step completes.
Accessing the empty ranges vector caused a segfault in the
PointCloud publishing path. Return early if ranges haven't been
populated yet.

Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Amp <amp@ampcode.com>

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Opus 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Claude Sonnet 4.6

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Generated-by: Copilot

Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Signed-off-by: Alexis Pojomovsky <apojomovsky@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

4 participants