Skip to content

feat(viz): drone-relative LiDAR scan frame - #75

Merged
WomB0ComB0 merged 1 commit into
mainfrom
feat/webgpu-lidar-rotation
Apr 28, 2026
Merged

WomB0ComB0 merged 1 commit into
mainfrom
feat/webgpu-lidar-rotation

Conversation

@WomB0ComB0

Copy link
Copy Markdown
Member

Summary

LidarScan.scan(origin, rot?) now accepts an optional quaternion. The
canonical scan pattern (built in a drone-local frame at construction)
is rotated by rot per call so the cone yaws / pitches / rolls with
the drone
. Pass identity (or omit rot) for the previous
world-axis-aligned behaviour.

This is item #1 from the loose-ends list — smallest visible-impact PR.
The PR #72 demo had the cone reading correctly when a drone hovers
with identity orientation, but visibly wrong the moment the drone
yaws. With this PR, the LiDAR cloud follows the drone's heading.

What changed

client/webgpu/lidar.ts:

  • _rays[i].direction is now an independent mutable Vec3 (no longer
    aliases the canonical dirs[i]).
  • scan() gains optional rot?: Quat second arg. When provided, each
    direction is rotated via the standard Rodrigues form:
    v' = v + 2 * q.xyz × (q.xyz × v + q.w * v) (used by gl-matrix /
    Three.js — valid for unit quaternions). When omitted, dirs[i] is
    copied verbatim.
  • Hit positions are computed from the rotated rays[i].direction so
    they match the actual ray that was traced, not the canonical
    pattern.

client/effects.ts:

  • _updateLidar passes drone.rot if it looks well-formed
    (length-4 array). Falls back to undefined (world-axis scan) for
    any malformed-or-missing rotation.

Performance

~12 k tuple writes per 4096-ray scan (3 per ray × 4096) plus ~30 FLOPS
each when rotating. At 1 Hz that's ~120 KFLOPS — comfortably below
noise level. No new GPU buffer allocations; no GC pressure (still
mutating pre-allocated tuples in place from PR #73).

Bundle impact

Main JS Cap
Pre-PR (#74 baseline) 808,316 B 819,200 B
This PR 808,803 B 819,200 B

+487 B for the rotation branch + the slightly larger ray-prep loop.

Test plan

  • npm run typecheck passes
  • npm run build passes (no new warnings)
  • dotnet build -c Release passes (pre-push hook)
  • In dev, spawn a drone that yaws or pitches; observe the cyan
    LiDAR cloud rotating with the drone (cone follows the drone's
    heading, not stuck to world axes).
  • On a non-WebGPU browser: no point cloud, no errors. Graceful
    fallback unchanged.
  • Pass an invalid rot (e.g. null, missing field): _updateLidar
    detects the length-mismatch and falls back to world-axis scan
    with no crash.

What's deferred

  • Mast / gimbal offsets (mount LiDAR not at drone origin but offset
    by a fixed body-frame vector). Trivial extension once a sensor
    mount spec lands.
  • Per-drone-class scan parameters (different vehicles → different
    scan resolutions/ranges). Currently one global LidarScan instance.
  • Multi-drone scans (each drone runs its own LiDAR). The
    ring-buffered LosQueryManager (lidar slot, capacity 4096,
    slotCount 3) is sized for one scan in flight at a time; multi-drone
    needs either a higher slotCount or per-drone managers.

🤖 Generated with Claude Code

LidarScan.scan(origin, rot?) now accepts an optional quaternion. The
canonical scan pattern (built in a drone-local frame at construction)
is rotated by `rot` per call so the cone yaws / pitches / rolls with
the drone. Pass identity (or omit `rot`) for the existing world-axis-
aligned behaviour.

Why now:
- The LiDAR demo cone landed in PR #72 was world-axis-aligned, which
  reads correctly when a drone is hovering with identity orientation
  but looks visibly wrong as soon as the drone yaws. Mounting the
  scan on the drone's `rot` quaternion takes the cone with it —
  smallest visible-impact PR on the loose-ends list.

Implementation:
- `_rays[i].direction` is now an independent mutable Vec3 tuple
  instead of aliasing the canonical `dirs[i]`. Each scan() either
  copies dirs[i] (when no rot) or applies the Rodrigues rotation
  formula (when rot is provided) into that tuple. ~12k tuple writes
  per 4096-ray scan — negligible.
- effects.ts:_updateLidar passes drone.rot if it looks well-formed
  (length-4 array), otherwise undefined.

Bundle: main 808,803 B (under 819,200 cap, +487 bytes for the
rotation branch + the slightly larger ray-prep loop).

Validation: typecheck, vite build, dotnet Release all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@WomB0ComB0 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 4 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0edc439a-3733-4050-8d5e-f389998bb317

📥 Commits

Reviewing files that changed from the base of the PR and between 273af6b and 5953f41.

📒 Files selected for processing (2)
  • src/ResQ.Viz.Web/client/effects.ts
  • src/ResQ.Viz.Web/client/webgpu/lidar.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/webgpu-lidar-rotation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@WomB0ComB0
WomB0ComB0 merged commit 78c42d5 into main Apr 28, 2026
37 checks passed
@WomB0ComB0
WomB0ComB0 deleted the feat/webgpu-lidar-rotation branch April 28, 2026 23:35

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables orientation-aware LiDAR scans by allowing an optional rotation quaternion to be passed to the scan method, ensuring the scan pattern follows the drone's yaw, pitch, and roll. The implementation uses the Rodrigues rotation formula to transform pre-allocated ray directions in real-time. Feedback was provided to optimize the rotation loop by pre-calculating doubled quaternion components, which reduces the number of multiplications performed per ray.

Comment thread src/ResQ.Viz.Web/client/webgpu/lidar.ts
WomB0ComB0 added a commit that referenced this pull request Apr 29, 2026
…p) (#76)

* perf(viz): hoist `2*q.xyz` outside the LiDAR rotation loop

Addresses Gemini's medium-priority comment on PR #75: the Rodrigues
form `v' = v + 2 * q.xyz × (q.xyz × v + q.w * v)` had `2 *` evaluated
inside the loop. Pre-computing `qx2 = qx*2`, `qy2`, `qz2` once outside
the loop and using them in the final cross-product writes saves 3
multiplications per ray ≈ 12k per 4096-ray scan.

Mathematical equivalence — `2 * (qy * tz - qz * ty)` becomes
`qy2 * tz - qz2 * ty`. Same numerical result.

Tiny perf win, but a clean follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(viz): use 3x3 rotation matrix for LiDAR scan rotation

Replace the per-ray Rodrigues form with a quaternion-derived 3x3
rotation matrix computed once outside the loop. The rotation is
constant for every ray in a scan, so the matrix-vector multiply (9 mul
+ 6 add per ray) beats Rodrigues (15 mul + 12 add per ray) — saves
~24k mults on a 4096-ray scan beyond what `1d48a2c` already trimmed.
Output equivalent for unit quaternions; observable behaviour
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant