Skip to content

Devel#29

Merged
rxdu merged 22 commits into
mainfrom
devel
May 3, 2026
Merged

Devel#29
rxdu merged 22 commits into
mainfrom
devel

Conversation

@rxdu
Copy link
Copy Markdown
Owner

@rxdu rxdu commented May 3, 2026

No description provided.

rxdu and others added 22 commits April 26, 2026 20:37
Mechanical rename of the app-shell module so the directory name matches
the central class users instantiate. Reads as `viewer/viewer.hpp` →
`quickviz::Viewer`, the same pattern as `boost::filesystem`.

Renames performed:
- src/imview/ → src/viewer/
- src/viewer/include/imview/ → src/viewer/include/viewer/
- tests/integration/test_imview_integration.cpp → test_viewer_integration.cpp
- All `imview` tokens → `viewer` (CMake target, link directives, includes,
  doc references)
- All `IMVIEW` tokens → `VIEWER` (header guards, IMVIEW_WITH_GLAD →
  VIEWER_WITH_GLAD CMake option)

No behavior changes. Build configures and links cleanly. Pre-existing
PCLLoaderTest.InvalidFileError still fails (unrelated, tracked in TODO).
Library boundary holds: src/ contains no `#include` of sample/ paths.

Part of the visualization-first module reorg; subsequent commits rename
gldraw → scene, split widget into canvas + plot, and merge cvdraw into
a new image module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the 3D rendering module from `gldraw` (tech-flavored) to `scene`
(intent-flavored). Aligns with the visualization-first naming scheme:
users pick the module by what they want to visualize, not by which
backend it uses internally.

Renames performed:
- src/gldraw/ → src/scene/
- src/scene/include/gldraw/ → src/scene/include/scene/
- docs/notes/gldraw_refactor_plan.md → scene_refactor_plan.md
- docs/notes/input_handling_system_for_gldraw.md →
  input_handling_system_for_scene.md
- All `gldraw` tokens → `scene` (CMake target, link directives, includes,
  doc references)
- All `GLDRAW` tokens → `SCENE` (header guards, macros)

The `Gl*` class prefix (GlSceneManager, GlScenePanel, GlViewer) is
deliberately untouched — it correctly signals "this class wraps OpenGL
handles" and is independent of the module name.

No behavior changes. Build configures and links cleanly. Same 2
pre-existing PCL test failures, no new regressions. Library boundary
holds.

Part of the visualization-first module reorg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creates src/plot/ as the home for ImPlot (2D) and ImPlot3D (3D) charting
inside QuickViz panels. Distinct from `scene` (interactive 3D world);
plot is for *charts of data*.

Changes:
- third_party/imcore: build implot3d sources alongside implot
  (implot3d/, implot3d_items, implot3d_meshes, implot3d_demo). Now
  available everywhere imcore is linked.
- New src/plot/ module:
  - plot/plot2d.hpp — re-exports implot/implot.h
  - plot/plot3d.hpp — re-exports implot3d/implot3d.h
  - plot/rt_line_plot_widget.hpp + impl — moved from widget/
  - plot/details/scrolling_plot_buffer.hpp + impl — moved from widget/
  - test_implot_widget moved to plot/test/
- src/widget/CMakeLists.txt: drops the moved files from sources
- src/widget/test/CMakeLists.txt: drops the moved test
- src/CMakeLists.txt: registers plot/

Removed src/plot/test/test_plot_buffer.cpp — an orphaned, stale test
that referenced viewer/data_buffer.hpp (doesn't exist) and namespace
quickviz::swviz (doesn't exist). It wasn't registered in the previous
CMakeLists either; just dead code that travelled with the move.

Build clean. Same 2 pre-existing PCL failures, no new regressions.
Library boundary holds.

Part of the visualization-first module reorg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creates src/canvas/ as the home for 2D vector drawing and immediate
widgets backed by Cairo. Use this module for custom 2D figures,
plots-as-pictures, and annotations rendered into a panel as a texture.

Files moved widget/ → canvas/:
- include/widget/cairo_widget.hpp → include/canvas/cairo_widget.hpp
- include/widget/details/cairo_draw.hpp → include/canvas/details/cairo_draw.hpp
- include/widget/details/cairo_context.hpp → include/canvas/details/cairo_context.hpp
- src/cairo_widget.cpp + src/details/cairo_*.cpp → canvas/src/...
- test_cairo_widget.cpp → canvas/test/

src/widget/CMakeLists.txt: drops Cairo-related sources, deps, and the
PkgConfig::Cairo / Fontconfig links. Module now contains only OpenCV
widgets and is set up to early-return if OpenCV isn't found, since
nothing else remains. The directory will be deleted entirely once those
move to src/image/ in the next commit.

Removed three orphaned, never-registered Cairo tests
(test_cairo_normalize, test_cairo_draw, test_cairo_context). They
referenced an extinct namespace (quickviz::swviz) and headers that
don't exist; 5-year-old dead code.

Build clean. Same 2 pre-existing PCL test failures, no new regressions.
Library boundary holds.

Part of the visualization-first module reorg.

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

Creates src/image/ as the home for image display and annotation backed
by OpenCV. Use this module for camera frames, debug images, or
annotated cv::Mat data inside a panel. Distinct from canvas/ (2D vector
drawing) and plot/ (data charts).

Files moved cvdraw/ → image/:
- cvdraw/include/cvdraw/cvdraw.hpp → image/include/image/image.hpp
- cv_io, cv_canvas, cv_colors, color_maps {hpp,cpp,test} → image/

Files moved widget/ → image/:
- cv_image_widget {hpp,cpp,test} → image/
- buffered_cv_image_widget {hpp,cpp,test} → image/
- details/image_utils {hpp,cpp} → image/details/

src/widget/ and src/cvdraw/ are now empty and deleted entirely. Their
roles distributed to canvas/, plot/, and image/ — each module now has
one job.

src/CMakeLists.txt rewritten: drops widget/ and cvdraw/ entries, adds
image/, and includes a top-of-file map of which module does what.

The image/ module gates on OpenCV with an early `return()` if absent —
so the rest of the build proceeds normally on systems without OpenCV.

The cv_ prefix on file/class names is preserved as an "implementation
backend" marker, parallel to the Gl prefix in scene/. If a future
backend appears (raw libpng, etc.), they can coexist without conflict.

Removed three orphan demo PNGs (basic_colors.png, draw_demo.png,
function_plot_demo.png) — checked-in demo outputs not referenced by
any code.

Build clean. Same 2 pre-existing PCL failures, no new regressions.
Library boundary holds.

Closes the structural part of the visualization-first reorg. Final
module layout:
  core, viewer, scene, plot, canvas, image, pcl_bridge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CLAUDE.md: Project Overview now lists the seven modules with one-line
  taglines, and explicitly notes editor frameworks live in sample/, not
  the library.
- CLAUDE.md: Module Structure section rewrites the directory tree, adds
  the user-intent table ("I want to X → which module"), and explains
  the implementation-prefix convention (Gl in scene/, Cv in image/).
- CLAUDE.md: optional-deps line updated (OpenCV → image, PCL → pcl_bridge).
- TODO.md: reshape work marked complete; new "Module reorg by intent"
  entry added to Recently Completed; status summary refreshed to the
  final layout.

No code changes; closes the visualization-first reorg.

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

GlViewer was named like a peer of Viewer but was actually a higher-level
helper that wraps a Viewer + GlScenePanel + grid + coordinate frame and
runs the event loop. The 17 renderable unit tests use it; nothing in
sample/ does. Yet it lived in the public include path, which made the
naming overlap with Viewer (window + panels primitive) confusing for
anyone scanning scene/'s headers.

Changes:
- Rename src/scene/include/scene/gl_viewer.hpp → scene_app.hpp
- Rename src/scene/src/gl_viewer.cpp → scene_app.cpp
- Class GlViewer → SceneApp throughout (17 test files updated mechanically)
- Header guard QUICKVIZ_GLVIEW_HPP → QUICKVIZ_SCENE_APP_HPP
- Rewrite the file-level docstring from "test helper" to a proper
  quickstart facade description with a usage example.
- Tighten the class docstring; note that for richer apps users should
  drop down to Viewer + panel classes directly.

This positions SceneApp as the "five-line entry point for a 3D viewer"
that was identified as the highest-leverage ergonomic win during the
visualization-first reshape. The class API is unchanged; only the
naming and framing moved. No new code; no speculative additions.

Build clean. Same 2 pre-existing PCL test failures, no new regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous CLAUDE.md had accumulated through several reshapes and
mixed marketing language with hard rules. This rewrite keeps the
load-bearing content and drops the rest.

Structure (14 short sections):
1. Mission — visualization-first, building blocks, generic terminology
2. Module Map — final layout + intent table + quickstart classes
3. Library Boundary — src/ ↛ sample/ rule (CI-enforced)
4. Dependency Direction — DAG diagram + when to refuse cross-module deps
5. Build System — required/optional deps, cmake invocation, key options
6. Code Style — C++17, naming, prefixes, header-guard form
7. Architecture Patterns — scene/selection/tools/panels in 5 lines each
8. Threading Model — GL main-thread rule, background hand-off
9. API Design Principles — small surface, explicit inputs, ownership
10. Error Handling — validate at boundaries, no silent suppression
11. Decision Heuristics — recurring tradeoff cheatsheet
12. Tests — where they live and the coverage bar
13. Commits & Documentation — message form, TODO discipline
14. Where Things Live — quick index of code, samples, docs, CI

Removed: PR Review Checklist (move target: docs/), the long
"Building Blocks Philosophy" lecture (3 lines now do the same job),
the "Refactor Playbook" (over-prescriptive), "Common Tasks" cookbook
(would rot), "Current Development Focus" (TODO.md owns that),
"Platform Support" (README's job), and most marketing-style language.

Net size: 470 → 308 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Small, deterministic data generators so users can explore the library
without supplying their own data files. Returned structures match the
shapes the renderables expect (`PointCloud::SetPoints`, `Mesh::Set...`)
so demos and tests can drop them straight in.

API in core/demo.hpp:
- struct PointCloudData { points, colors }
- struct MeshData       { vertices, indices }
- SpiralCloud(num_points, radius, height, turns)
- PlanarPointGrid(rows, cols, spacing)
- NoiseCloud(num_points, sigma, seed)
- CubeMesh(center, size)
- Trajectory(num_points, scale)

All generators are pure functions of their inputs — no global state, no
I/O, deterministic for the seed/parameters provided. Lives in `core`
because the data is generic (just glm vectors); any module can use it.

8 unit tests (test_demo.cpp) covering size correctness, edge cases
(zero points), determinism, geometric invariants. Total ctest count:
107 → 115; same 2 pre-existing PCL failures, no new regressions.

Used by upcoming sample/quickstart/ to bootstrap a 30-line app without
requiring the user to have data on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A 25-line app (18 lines of meaningful code) that opens a window, draws
a colored 3D helix, and lets you orbit the camera. Built entirely from
the high-level facades that exist for exactly this purpose:

  SceneApp           — 5-line viewer setup
  demo::SpiralCloud  — synthetic data, no file required
  PointCloud         — the renderable

Together they prove the "minimal boilerplate" goal: a roboticist can go
from `git clone` to "I see something on screen" in a single readable
file. No layout boilerplate, no GLFW glue, no shaders.

Files:
- sample/quickstart/main.cpp     — the program
- sample/quickstart/README.md    — annotated walkthrough + when to drop
                                   down to Viewer + Panel directly
- sample/quickstart/CMakeLists.txt — links core, scene, viewer

Registered in sample/CMakeLists.txt; doesn't require PCL (unlike the
other samples), so the build adds the binary even on minimal systems.

Build clean. No new tests; the binary itself is the proof. Library
boundary holds (sample → src is one-way, as enforced by CI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DataStream<T> is the recommended way to bring background-thread data
(sensor frames, robot poses, processed clouds) to the render thread:

  // Producer thread (any thread, any frequency):
  stream.Push(latest_value);

  // Render thread (once per frame):
  T out;
  if (stream.TryPull(out)) renderable->Update(out);

Semantics: latest-only, lossy. Intermediate pushes between two pulls
are silently dropped — visualization rarely cares about yesterday's
frame when today's is in hand. Render thread never blocks. For
lossless delivery (every sample), use RingBuffer<T> directly instead.

Implementation: thin wrapper over the existing DoubleBuffer<T>
primitive in core/buffer/. The novelty is the API shape and the
documented semantics, not the synchronization machinery — that was
already there.

API:
- Push(const T&) / Push(T&&)
- bool TryPull(T& out)               — non-blocking; out-param overload
- std::optional<T> TryPull()         — convenient if(auto v = ...) form

Header-only template in core/data_stream.hpp; no impl file needed.

7 unit tests (test_data_stream.cpp) covering: empty pull, push/pull
round-trip, latest-only drop semantics, move overload, and a
producer/consumer threaded smoke test that asserts the consumer never
sees a value go backwards. ctest count: 115 → 122; same 2 pre-existing
PCL failures, no new regressions.

Closes the third of three "fast onramp for robotics" workstreams
(SceneApp, demo helpers, DataStream). The next sample iteration could
demonstrate DataStream by streaming a moving point cloud, but that's
a follow-up — not part of this commit's scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure the tracker around onramp + extensibility goals identified
during the post-reorg review. Captures concrete priorities in execution
order and pulls deferred items into a deliberate backlog.

Top of the new "Active priorities" stack:
1. sample/streaming_demo — close the DataStream loop with a working
   moving-point-cloud sample (~50 LOC).
2. ROS2 bridge milestone — bridges/ros2/ as the umbrella, with
   converters for PointCloud2, PoseStamped, OccupancyGrid, TFMessage,
   Marker. The single biggest gap between "C++ vis library" and
   "robotics vis library."
3. First standard robotics renderable (Trajectory, OccupancyGrid, or
   TfFrameTree — pick one, ship it cleanly).
4. Diagnostics: HUD overlay + structured logger + visible UI surface
   for shader/asset failures.
5. Documentation site: Doxygen + GitHub Pages + a tutorial chapter
   per existing sample.

Backlog (deferred, not abandoned): layout presets, AppState
persistence, recording/replay for DataStream, plugin/extension system,
the eventual "robotics gold demo" composite sample.

Existing entries preserved: library-hook candidates from sample/editor,
known visualization gaps, smaller cleanups (canvas.cpp split, etc.).

Recently Completed updated with the four post-rewrite commits
(CLAUDE.md rewrite, demo helpers, quickstart sample, DataStream).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verified each open item against current code state. All listed items
are still valid — no removals — but a few entries got sharper with
concrete scope or location data:

- Robotics-renderable milestone: drop the standalone "Trajectory"
  bullet. The existing `Path` renderable already covers 3D motion
  paths; the streaming/time-aware behaviors should extend `Path`,
  not create a new class. `OccupancyGrid` is now flagged as the
  recommended-first item (single-purpose, no overlap, pairs with the
  ROS2 OccupancyGrid converter).

- Structured logger: pinned the actual scope. Audit found ~232
  std::cerr/std::cout occurrences in src/, concentrated in scene
  (179) and viewer (43). The migration is a sed pass after the
  logger lands.

- interactive_scene_manager.cpp cleanup: pointed at the specific
  leftovers — empty `return;`-only HandleMouseInput, a "Legacy
  SelectionManager callback disabled" block, and a stale TODO.

- quickviz_demo_app audit: noted that it uses `Viewer` directly
  (a multi-panel app) and the audit may correctly conclude "no
  change". The actionable part is whether layout presets, when they
  land, would tighten the layout setup.

Re-confirmed still open and unchanged: library hooks (4), selection
coverage gaps (Arrow/Plane/Path/Triangle/Pose), canvas.cpp 2069 LOC,
fonts in core/include/, PCLLoaderTest.InvalidFileError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The reference for "I have data on a background thread, how do I show it?"
Read this before writing your first ROS2 / sensor callback — same shape
applies.

Architecture (also drawn in the README):

  background producer  ─Push─►  DataStream<T>  ─TryPull─►  render thread
                                 (latest-only,
                                  lossy, never blocks)

What it does: a 2000-point colored spiral rotates around Z at 30 Hz on
a background thread; the render thread pulls the latest cloud in a
SetPreDrawCallback and updates a single PointCloud renderable.
Window-close stops the producer cleanly.

Files:
- sample/streaming_demo/main.cpp     — 53 LOC of meaningful code
- sample/streaming_demo/README.md    — threading model, DataStream vs
                                       RingBuffer guidance, "how to
                                       adapt this to a real sensor"
- sample/streaming_demo/CMakeLists.txt — links core, scene, viewer
- sample/CMakeLists.txt: registers the sample alongside quickstart

Demonstrates: DataStream<T> with a non-trivial T, fixed-rate producer,
lossy semantics, render-thread pull pattern, clean atomic-bool
shutdown. Does *not* demonstrate multi-stream, lossless delivery
(that's RingBuffer territory), or ROS2 — those are deliberate
follow-ups, signposted in the README.

Build clean. Same 2 pre-existing PCL failures, no new regressions.
Library boundary holds.

TODO.md: marked the "Next up" entry done; new "Recently Completed"
entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the standard 2D occupancy-grid primitive — the renderable a ROS2
nav_msgs::OccupancyGrid converter will produce once the bridge lands.
Built and shipped now so the renderable exists when the bridge calls it.

API (scene/renderable/occupancy_grid.hpp):

    SetGrid(width, height, resolution, origin, std::vector<int8_t>)
    SetFreeColor(vec4)
    SetOccupiedColor(vec4)
    SetUnknownColor(vec4)

ROS conventions preserved: -1 = unknown, 0..100 = probability of
occupancy (percent). Origin is the world-space position of cell (0, 0)
and the grid extends to origin + (width, height) * resolution.

Implementation:
- One quad in the XY plane, sized at draw time by uExtent.
- R8 single-channel texture with the encoding "byte 0 = unknown
  sentinel, byte 1..255 = occupancy 0..100% (linear)". This keeps the
  texture to 1 byte per cell — a 1000x1000 grid is 1 MiB.
- Fragment shader checks the sentinel and otherwise lerps free→occupied.
- Lazy texture allocation: AllocateGpuResources sets up shader + VAO
  only; the texture is created on the first SetGrid() so resizes are
  handled cleanly.

Defaults: light gray free, black occupied, medium gray unknown — read
naturally on either light or dark scene backgrounds.

Manual visual test (scene/test/renderable/test_occupancy_grid.cpp)
renders a 50x50 grid with a hand-crafted pattern that exercises all
three states: outer occupied border, intermediate diagonal, free
interior, lower-left unknown patch. Reference Grid at the same
resolution for cell alignment verification.

Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new
regressions. Library boundary holds.

Active priorities updated in TODO.md (OccupancyGrid checked off; TfFrameTree
and Trajectory extensions still open under the same milestone).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a renderable tree of named coordinate frames analogous to ROS
tf2. Each frame has a name, a parent (or empty for a root), and a
transform expressed in the parent's coordinates. World transforms are
computed by walking parent chains.

API (scene/renderable/tf_frame_tree.hpp):

    SetFrame(name, parent, transform_in_parent)   // add or update
    RemoveFrame(name) → size_t                    // removes descendants too
    Clear()
    HasFrame(name) → bool
    GetWorldTransform(name) → mat4
    SetAxisLength(float)
    SetShowConnections(bool)
    SetConnectionColor(vec3)

Each frame renders as a colored axis triplet (X=red, Y=green, Z=blue).
Parent → child origin connection lines are drawn in gray when enabled,
giving an at-a-glance view of tree topology.

Implementation:
- Frames stored in unordered_map<name, {parent, local_transform}>
- World transforms recomputed lazily before each draw when dirty
- Cycle detection in GetWorldTransform: bails out and returns identity
- Broken parent chains (parent name referenced but not registered):
  treated as "implicit identity" — frame visualized at its accumulated
  transform up to the missing link, rather than throwing
- Single VAO with position+color vertices, one GL_LINES draw call
- Vertex buffer reused with glBufferSubData when frame count fits;
  reallocates on growth

Visual test fixture (scene/test/renderable/test_tf_frame_tree.cpp)
animates a six-frame robot-like kinematics tree:
  world → base → {lidar, arm_base → arm_link_1 → arm_tip}
The base spins; the arm shoulder oscillates; grandchildren update
without restating the chain because GetWorldTransform walks parents
on every draw.

Build clean. 122 tests pass; same 2 pre-existing PCL failures.
Library boundary holds.

Closes the second item under "first standard robotics renderable" in
TODO.md (`Trajectory` extensions on `Path` remain). The TfFrameTree is
also a prerequisite for the upcoming ROS2 tf2_msgs::TFMessage
converter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User direction: ROS2 (and any future module that depends on ROS msgs)
must be CMake-gated so the library still compiles in environments
without ROS installed. Generalizing to all heavyweight optional SDKs.

CLAUDE.md §4 gains an "Optional external dependencies" subsection with
the pattern (find_package QUIET → early return from CMakeLists, no leak
of optional types into non-gated code) and an explicit ROS2 callout
for current/future contributors.

TODO.md ROS2 milestone:
- Promotes the optional-CMake rule from a checkbox at the bottom to a
  callout at the top of the milestone, since it constrains every
  sub-task.
- Notes that two of the converter targets (OccupancyGrid, TfFrameTree)
  already have their renderable counterparts shipped — the bridge work
  reduces to writing converters into existing renderables.
- Adds an explicit "sample app must also be CMake-gated" line so a
  ROS2 demo doesn't accidentally creep into the always-built sample
  set.

No code change. Same 122 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Triangle is a 44-line test-only renderable — it's used by ~8 test
fixtures (renderable demos, integration tests) but zero non-test code.
It had accumulated in the public scene/renderable/ namespace where it
read like a recommended building block. Move it to where it belongs.

Changes:
- src/scene/include/scene/renderable/triangle.hpp moved to
  src/scene/test/test_utils/test_utils/triangle.hpp
- src/scene/src/renderable/triangle.cpp moved to
  src/scene/test/test_utils/triangle.cpp
- New scene_test_utils STATIC library (publicly links scene) hosts
  test-only renderable helpers. Test executables that previously linked
  scene directly and used Triangle now link scene_test_utils instead;
  scene is provided transitively.
- Update header guard to QUICKVIZ_TEST_UTILS_TRIANGLE_HPP and the
  internal scene/shader_program.hpp include path.
- Sed all `scene/renderable/triangle.hpp` includes → `test_utils/triangle.hpp`
  across 8 test files.
- Drop triangle.cpp from src/scene/CMakeLists.txt sources.
- Add `add_subdirectory(test_utils)` to scene/test/CMakeLists.txt
  before the existing test sub-dirs that consume it.

Walked back a related "extract shared axis-vertex generator" refactor
that I'd suggested. Closer inspection showed the three axis-rendering
classes (CoordinateFrame, Pose, TfFrameTree) draw visually different
shapes (cone arrows vs. plain lines) in different coordinate frames
(local + model matrix vs. pre-transformed world). A shared helper
would force a worse abstraction across genuinely-different
implementations. Documented this in TODO.md.

Added to TODO.md "Smaller cleanups": audit GeometricPrimitive base
class (405-LOC header) for over-scope. Real design review, not a quick
rewrite — likely API-breaking.

Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new
regressions. Library boundary holds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the "Trajectory extensions on existing Path renderable" item
under the "first standard robotics renderable" milestone. The existing
Path already had kVelocity / kTime / kCost color modes, scalar-value
input, and SetAnimationProgress for partial drawing — what was missing
was streaming-friendly machinery for live trajectories and a
correctness bug under subdivided paths.

Three changes, all small:

1. Streaming overload: `AddPoint(const glm::vec3& point, float scalar)`
   appends a control point and its scalar sample together so
   scalar_values_ stays aligned with control_points_ as new samples
   arrive. Padding behavior: if the user previously called the
   no-scalar AddPoint variant, scalar_values_ is back-filled with the
   newly supplied scalar before the push so alignment is preserved.

2. `EnableAutoColorRange(bool)` — when on, the color range used by
   kVelocity / kTime / kCost is computed from the current
   scalar_values_ (min..max) before each color recompute. Avoids the
   manual `SetColorRange` step that streaming trajectories don't have
   a sensible up-front value for. Off by default.

3. ComputePathColors fix: previously each path vertex was mapped to
   `scalar_values_[i]` directly, which is correct for kLineSegments
   (where path_vertices_.size() == control_points_.size()) but
   misaligned for kSmoothCurve / kBezierCurve / kSpline (which produce
   subdivisions × (N-1) + 1 vertices). Now each path vertex's
   fractional position along the path is computed and the scalar
   linearly interpolated between adjacent samples — colors transition
   smoothly along the curve instead of stepping at vertex boundaries.

`ClearPath()` also now clears scalar_values_ alongside the geometry,
so a reset doesn't leave stale scalars behind.

Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new
regressions. Library boundary holds. The new APIs are exercised by
existing test_path.cpp's color-encoding demos and will get real
streaming exercise once the upcoming ROS2 PoseStamped converter and
its sample land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Path was position-only — its arrows showed direction of motion (path
tangent). For real robotics trajectories the heading often differs
from the travel direction (parallel parking, drone yaw, manipulator
end-effector). Adds optional per-control-point orientations so a
single Path can render either a position trajectory (existing
behavior) or a full pose trajectory (new).

API additions, all opt-in:

  void SetOrientations(const std::vector<glm::quat>&);
  bool HasOrientations() const;
  void ClearOrientations();
  const std::vector<glm::quat>& GetOrientations() const;

  void AddPoint(const glm::vec3& point, const glm::quat& orientation);
  void AddPoint(const glm::vec3& point, const glm::quat& orientation,
                float scalar);

  enum class ArrowMode {
    ...
    kPoseArrows,   // NEW
  };

`kPoseArrows` iterates control_points_ (not path_vertices_) and uses
the stored orientation at each point to derive the arrow's basis
vectors. Falls back to identity when orientations_ is shorter than
control_points_, which renders as a +X-aligned arrow — the ROS-style
"X is forward" convention. The other ArrowMode variants are unchanged.

Pyramid-arrow geometry was extracted into a small file-private
EmitPyramidArrow helper used by both the existing tangent-arrow path
and the new pose-arrow path. The user-visible behavior of kEndpoints,
kRegular, and kAll is identical to before.

Streaming overloads pad in lockstep so AddPoint variants can be mixed:
each variant back-fills its own auxiliary array (scalar_values_ or
orientations_) up to control_points_.size() - 1 before pushing the new
sample. ClearPath also clears orientations_ now.

Visual test fixture (test_path.cpp) gains a 9th demo: a 6-pose arc
where the yaw sweeps from 0 to 90° while the path lays along -Y. The
oriented arrows fan out, visibly distinct from the kRegular tangent
arrows in the other demos.

Build clean. 122 tests pass; same 2 pre-existing PCL failures, no new
regressions. Library boundary holds.

Maps onto ROS2 message types the upcoming bridge will produce:
  - geometry_msgs::PoseArray  → SetPoints + SetOrientations
  - nav_msgs::Path            → AddPoint(point, quat) per PoseStamped

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Earlier TODO.md had accreted into tracker + mission statement + style
guide + verbose changelog. Strip to just the tracker so a fresh session
can scan it in under a minute.

Removed:
- Mission section (3 paragraphs of prose) — duplicates CLAUDE.md
- Status Summary — duplicates active priorities
- Notes section about how to use the file — duplicates CLAUDE.md
- Pre-2026 "Recently Completed" entries (Sept 2025, Dec 2024, etc.)
  — older history lives in `git log`
- Verbose feature descriptions in completed bullets — replaced with
  one-line summaries + commit hashes

Kept:
- Active priorities (3 ordered milestones)
- Backlog (deferred items, grouped)
- Library hooks (driven by sample/editor)
- Visualization gaps
- Cleanups
- Recent: current-iteration commits with hashes for `git show`

Each section is single-purpose. Each item is searchable on a relevant
keyword (class name, file path, commit hash). 307 → 122 lines.

The "Recent" section uses commit hashes rather than prose so a fresh
session can `git show <hash>` to retrieve the full context without
having to scroll the tracker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rxdu rxdu merged commit 68b79c4 into main May 3, 2026
10 checks passed
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