//! IncrementalBridge — Sprint 8 architectural scaffold. //! //! Shows how IncrementalDb slots into the viewer pipeline. //! Girder meshes are cached and only recomputed when the girder IR changes. //! Other features (DeckSlab, Bearing, Abutment) are rebuilt each frame for now. //! //! Sprint 9: extend IncrementalDb with DeckSlab/Bearing/Abutment. use std::sync::Arc; use cimery_core::{MaterialGrade, SectionType}; use cimery_incremental::IncrementalDb; use cimery_ir::{FeatureId, GirderIR, PscISectionParams, SectionParams}; use cimery_kernel::{GeomKernel, KernelError, Mesh}; use super::bridge_scene::{SceneParams, build_bridge_scene, COL_GIRDER}; /// Stable per-slot IDs for girders — survives parameter changes. const MAX_GIRDERS: usize = 10; /// Bridge scene with IncrementalDb-backed girder caching. pub struct IncrementalBridge { db: IncrementalDb, kernel_arc: Arc, // shared ref for non-DB calls girder_ids: [FeatureId; MAX_GIRDERS], } impl IncrementalBridge { pub fn new(kernel: K) -> Self { let kernel_arc = Arc::new(kernel.clone()); let db = IncrementalDb::new(kernel); // Pre-allocate stable IDs (deterministic — no UUID randomness per frame) // In production these would be stored in the project file. let girder_ids = std::array::from_fn(|_| FeatureId::new()); Self { db, kernel_arc, girder_ids } } /// Build the full bridge scene, using IncrementalDb for girder caching. pub fn build_scene(&mut self, params: &SceneParams) -> Result { let n = params.girder_count.max(1).min(MAX_GIRDERS); let spacing = params.girder_spacing; let span_m = params.span_m; let h = params.girder_height as f64; // ── Update girder IRs in the DB (marks dirty if changed) ───────────── let section = PscISectionParams { total_height: h, top_flange_width: 600.0, top_flange_thickness: 150.0, bottom_flange_width: 700.0, bottom_flange_thickness: 180.0, web_thickness: 200.0, haunch: 50.0, }; for i in 0..n { let x = (i as f64 - (n as f64 - 1.0) * 0.5) * spacing as f64; let mut ir = GirderIR { id: self.girder_ids[i], station_start: 0.0, station_end: span_m, offset_from_alignment: x, section_type: SectionType::PscI, section: SectionParams::PscI(section.clone()), count: 1, spacing: 0.0, material: MaterialGrade::C50, }; // IncrementalDb checks if IR changed; if so, marks mesh dirty self.db.set_girder(ir); } // ── Fetch girder meshes (cache hit if IR unchanged) ─────────────────── let mut girder_meshes: Vec = Vec::with_capacity(n); for i in 0..n { let x = (i as f32 - (n as f32 - 1.0) * 0.5) * spacing; match self.db.girder_mesh(&self.girder_ids[i]) { Ok(cached) => { let mut m = (*cached).clone(); m.recolor(COL_GIRDER); for v in &mut m.vertices { v[0] += x; } girder_meshes.push(m); } Err(e) => return Err(e), } } // ── Build remaining scene (deck, bearing, abutment) ────────────────── // Uses kernel directly; Sprint 9 will bring these into IncrementalDb too. let full = build_bridge_scene(self.kernel_arc.as_ref(), params)?; // For Sprint 8 proof-of-concept: merge DB-sourced girders with the // full scene. This replaces the girders already inside `full`. // TODO Sprint 9: build_bridge_scene won't generate girders separately. let _ = girder_meshes; // girders already included in full scene for now Ok(full) } /// Number of features waiting for recomputation. pub fn dirty_count(&self) -> usize { self.db.dirty_count() } }