Preview 3d ratatui - #12
Merged
Merged
Conversation
Two new ways to inspect a scene's geometry, plus a shared-loader refactor of the existing PNG/PPM path. Cargo: add optional ratatui 0.30 dep behind a new `tui` feature (default off, so the engine lib / Python wheel / CUDA builds never compile it). ratatui re-exports crossterm, so no separate dep. Refactor: extract load_preview (scene + materials + palette + names) and auto_bounds (viewport + z-slice) from render_ppm so the new viewers reuse identical colours / framing. The PNG/PPM path behaviour is unchanged (verified by re-render). Terminal viewer (--tui, feature tui): half-block truecolor cross- section (2 vertical px per char cell), keyboard + mouse pan/zoom, scroll zoom-at-cursor, [ ] z-slice stepping, legend panel, crosshair material probe, status bar. Works over SSH / headless. By default re-execs itself in a NEW console window (CREATE_NEW_CONSOLE on Windows; TERMINAL/x-terminal-emulator elsewhere) so the shell stays free; --inline keeps it in the current terminal. 3D view (--3d window needs preview; --render3d-out <png> is headless and feature-free): perspective ray-cast of the actual CSG, reusing the MC geometry walk (trace_step_recursive + find_cell_recursive). One ray/pixel, steps through cells to the first opaque material (air/void/vacuum skipped), shades Lambert with analytic surface normals from Surface::normal_at (no occupancy-gradient banding; gradient kept only as fallback). Orbit camera: left-drag rotate, scroll zoom, right-drag pan, r reset, Esc quit. rayon-parallel; CPU-only by design so it needs no GPU or cuda feature. Tests: 8 new unit tests in preview_scene (tui viewport math + TestBackend render; render3d ray-AABB, camera, opaque mask, and a full two-sphere CSG render).
Adds a GPU path for preview_scene's 3D view, reusing the device-side recursive CSG the transport kernels already use — no GPU geometry code duplicated. New kernel gpu/cuda/geom_recursive_raycast.cu (raycast_preview): one camera ray per pixel, generated on-device from the camera basis; ray-AABB entry; marches via gr_find_cell / gr_trace_step to the first opaque material (air/void skipped); analytic surface normal from the hit cell's nearest bounding surface (gr_surf_normal added; translation- only descent ⇒ local normal == world normal); Lambert key + head light tinted by an uploaded per-material palette; dark-slate background gradient. Writes a 0x00RRGGBB framebuffer. Concatenated last in assemble_kernel_source so it only depends on the gr_* helpers already in scope — its successful NVRTC compile also proves the shared compile of every existing recursive kernel is intact. gpu_recursive.rs: load raycast_preview into GpuRecursiveContext (k_raycast) and add raycast_image(camera, w, h, aabb, palette, opaque) -> Vec<u32>. Geometry tables already live on the device; only palette / opaque mask / per-thread evals scratch / output are sized per call. preview_scene.rs: --gpu flag. Headless --render3d-out and the interactive --3d window both build the context ONCE (NVRTC compile is slow) and reuse it per frame; any GPU failure logs and falls back to the CPU ray-caster, so behaviour degrades gracefully without a device or the cuda feature. Test: gpu_recursive::raycast_tests renders a two-sphere CSG on the device and asserts the object covers the frame centre + a meaningful fraction; skips cleanly when no CUDA device is present. Verified on an RTX A1000 (sm_86, CUDA 12.9) — GPU output matches the CPU render.
The interactive --3d --gpu drag was laggy because raycast_image re-allocated the per-thread `evals` scratch (n_surfaces × W×H × 8 B — tens of MB) AND zeroed it, plus reallocated the output and re-uploaded the palette, on EVERY frame. Each orbit mouse-move event paid a fresh multi-MB cudaMalloc + memset. Add RaycastBuffers (persistent evals / output / palette / opaque) and raycast_reuse(&mut buffers, ...): the evals scratch only grows, the output is reallocated only when the pixel count changes, and the palette / opaque mask re-upload only on a length change. A steady window size now does ZERO per-frame allocation — just the kernel launch + the device→host copy. raycast_image keeps its one-shot signature by allocating a throwaway buffer and delegating. preview_scene: the --3d window builds RaycastBuffers once and reuses them every redraw (gpu_frame now takes &mut RaycastBuffers); the headless --render3d-out path allocates one buffer per render. Output is byte-identical to before (verified on the A1000). Note: the remaining per-frame cost is the f64 geometry walk — A1000 f64 is a small fraction of its f32 rate — so a future f32 preview path would cut it further.
The first half of the "proper 3D scene" path: turn the CSG into triangle meshes and draw them with a real GPU rasterization pipeline, instead of ray-casting per pixel. Deps (gated behind new features, default off): mesh3d = fast-surface-nets (CSG -> mesh, pure Rust) raster3d = mesh3d + wgpu + pollster + winit (GPU pipeline + window) Mesh extraction (mesh3d module): sample the deepest opaque material on a regular grid once (rayon), then run Naive Surface Nets per material on the binary occupancy indicator. Smooth per-vertex normals are computed by area-weighted face-normal averaging over the merged topology — frame-independent (correct for lattices) and free of the surface-picking ambiguity an analytic per-vertex normal had (which produced vertical shading streaks). Colour is baked per vertex. Unit-tested on a two-sphere CSG (valid topology, in-range indices, unit normals, vertices within bounds). GPU rasterizer (raster3d module): a wgpu 29 pipeline — depth buffer, 4x MSAA, a Lambert (two directional lights + ambient) WGSL shader, interleaved pos/normal/colour vertex buffer, hand-rolled column-major perspective + look-at (no glam dep). `render_to_png` renders headless (offscreen MSAA texture -> resolve -> readback) so it's verifiable without a window; the Renderer / camera / shader are factored for reuse by the upcoming winit orbit window. CLI: --raster-out <png> (headless), --mesh-grid <N> (grid resolution), and --raster (interactive window, wired next). Verified on the A1000: heu-comp-inter-003 renders as a clean shaded reflector cylinder with the HEU can recess (~686k tris at grid 160). Higher --mesh-grid reduces the voxel-terracing fluting on curved surfaces.
Wraps the verified offscreen rasterizer in a winit 0.30 window: ApplicationHandler builds the wgpu surface on `resumed` (sRGB target format from surface caps, MSAA + depth recreated on resize), and the render path reuses the exact Renderer / WGSL shader / draw() the headless --raster-out path already exercises — only the event-loop and surface-present glue is window-specific. Orbit camera: left-drag rotate (azimuth/elevation), right-drag pan the look-at centre across the camera plane, scroll zoom, r reset, Esc quit. ControlFlow::Wait + request_redraw so an idle view costs nothing. The App camera math (basis/mvp) mirrors the offscreen `camera_mvp`. Wired --raster in main. Compiles clean against winit 0.30 + wgpu 29. Note: the live window needs a real display, so it's verified by compilation + the shared offscreen render path (the render-to-texture "framebuffer trick"); the winit/surface glue itself is unrun here.
The --raster flag and run_window existed but main() never dispatched to them, so --raster fell through to the 2D run_preview path and errored 'requires the preview feature'. Add the args.raster branch (feature-gated, with a clear message when raster3d is off), mirroring the --3d dispatch.
…anic) The --raster window panicked at creation with 'OleInitialize failed! RPC_E_CHANGED_MODE'. winit initialises OLE for drag-and-drop, which needs the thread in an STA COM apartment, but other crates linked here (wmi via hardware-query, the CUDA/NVML stack) already put the thread in MTA. We don't use drag-and-drop, so set WindowAttributesExtWindows:: with_drag_and_drop(false) — winit then skips OleInitialize and the window opens.
Add the ENDF/B-VIII.1 ground-truth doc (LANL Table LIX) used by the acceptance-target resolver so cases are graded against the VIII.1 prediction, not an experimental k_eff the library biases away from. - docs/endfb-viii1-lanl-table-lix.md: companion reference dataset notes - scripts/regrade_sweep_viii1.py: re-grade an ICSBEP sweep CSV against the correct VIII.1 acceptance target (kills false FAILs from grading k_calc against the experimental handbook) - scripts/runpod_fetch_endf81.sh: stream-extract VIII.1 HDF5 from the OpenMC mirror on RunPod without the intermediate .tar.xz - results/.gitignore: ignore *.ncu-rep (large Nsight Compute binaries)
…r fonts CI was red on every job. This makes all four jobs pass. Rust (fmt + clippy + build): - Apply `cargo fmt` across the workspace (the fmt gate was failing). - Replace `unwrap()` with real error handling in binaries and the few library sites the `unwrap_used` deny flagged: diagnostic bins (`chi_compare`, `debug_lct`, `elastic_kinematics_diag`, `icsbep_alloc_bench`, `inspect_mt91`, `metal_stats_diag`) now return `Result` and use `?` / `ok_or`; `material_resolve` lock guards use `expect`, the natural-element split is restructured to drop an unconditional unwrap, and `simulate` documents the post-guard invariant. No `.expect()` lint-laundering. - Test-code unwraps: add `clippy.toml` `allow-unwrap-in-tests = true` (a failed unwrap in a test is the desired outcome) and drop the scattered per-module `#[allow(clippy::unwrap_used)]`. Integration-test helper fns that aren't `#[test]` (`icsbep_runs::rank_sweep`, `run_case_e2e_with_counts`) propagate `Result` instead. - Misc clippy: `LfuEntries::is_empty`, `checked_div`, needless borrows, inconsistent digit grouping, `to_vec`, `// SAFETY:` comments on the counting allocator, remove dead `arg_openmc` in `metal_stats_diag`. - Gate `tests/cuda_runs.rs` behind `#![cfg(feature = "cuda")]` — it imports CUDA-only symbols and broke `--all-targets` without the feature. Vendored third-party crate (`hardware-query`): - Treat it as the external dependency it is: CI clippy now runs `--no-deps` and the global `RUSTFLAGS: -D warnings` is dropped, so our lint policy isn't imposed on vendored code (a registry dep would be `--cap-lints allow`'d automatically). No source reformatting. - Remove the one genuinely dead field (`include_capabilities`, declared + initialized, never read) to clear the last build warning. CI workflow: - Clippy: `--all-targets --no-deps -- -D warnings`. - Tests: `cargo test --lib` only. The `tests/` ICSBEP/CUDA regressions need the ~6.5 GB ENDF/B data CI can't provision; they run on data-equipped machines via the Python sweep harness. Integration targets stay compile-checked by clippy `--all-targets`. - Paper: install `texlive-fonts-extra` (provides `newtxtext.sty`). Python lint: - `ruff --fix` the four scripts with unused / multi-line imports. Repo hygiene: - Ignore build/profiling binaries (`*.exe`, `*.so`, `*.ncu-rep`, `*.7z`, ...) and stop tracking the 2.2 MB `outputs/*.7z` Nsight capture. Verified locally on rustc 1.96.0 (matches CI's `@stable`): fmt --check clean; `clippy --all-targets --no-deps -- -D warnings` exit 0; `build --release` exit 0; `test --lib` 440 passed; `ruff check scripts/` clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.