diff --git a/cimery/crates/kernel/src/bearing.rs b/cimery/crates/kernel/src/bearing.rs index bdd999a..ea436dc 100644 --- a/cimery/crates/kernel/src/bearing.rs +++ b/cimery/crates/kernel/src/bearing.rs @@ -7,12 +7,18 @@ pub fn build_bearing_mesh(ir: &BearingIR) -> Result { if ir.plan_length <= 0.0 || ir.plan_width <= 0.0 || ir.total_height <= 0.0 { return Err(KernelError::InvalidInput("bearing dimensions must be positive".into())); } - // Centred box: plan_length × total_height × plan_width - // X = along span (plan_length), Y = height, Z = transverse (plan_width) + // Box: X=[-l/2, +l/2] centred, Y=[-h, 0] (top at Y=0 = girder soffit), Z=[0, w] + // Caller translates to girder X offset; Y=0 means bearing top sits at soffit. let l = ir.plan_length as f32; let h = ir.total_height as f32; let w = ir.plan_width as f32; - Ok(sweep::centred_box(-l/2.0, 0.0, l/2.0, h, w)) + let profile = vec![ + [-l * 0.5, -h ], + [ l * 0.5, -h ], + [ l * 0.5, 0.0], + [-l * 0.5, 0.0], + ]; + Ok(sweep::sweep_profile_flat(&profile, w)) } #[cfg(test)] diff --git a/cimery/crates/kernel/src/lib.rs b/cimery/crates/kernel/src/lib.rs index 6d8effc..6c33e7e 100644 --- a/cimery/crates/kernel/src/lib.rs +++ b/cimery/crates/kernel/src/lib.rs @@ -39,6 +39,19 @@ pub struct Mesh { pub indices: Vec, /// Per-vertex normals (unit vectors). pub normals: Vec<[f32; 3]>, + /// Per-vertex base colour [R, G, B] in linear sRGB. + /// Default: concrete grey. Set by scene compositor per feature type. + pub colors: Vec<[f32; 3]>, +} + +/// Default concrete grey (PSC-I standard colour). +pub const COLOR_CONCRETE: [f32; 3] = [0.80, 0.76, 0.65]; + +impl Mesh { + /// Repaint all vertices with a single colour. + pub fn recolor(&mut self, c: [f32; 3]) { + self.colors = vec![c; self.vertices.len()]; + } } impl Mesh { @@ -127,7 +140,8 @@ impl GeomKernel for StubKernel { 0, 1, 5, 0, 5, 4, 3, 7, 6, 3, 6, 2, ]; let normals = vec![[0.0_f32, 1.0, 0.0]; vertices.len()]; - Ok(Mesh { vertices, indices, normals }) + let colors = vec![COLOR_CONCRETE; vertices.len()]; + Ok(Mesh { vertices, indices, normals, colors }) } } diff --git a/cimery/crates/kernel/src/occt.rs b/cimery/crates/kernel/src/occt.rs index b4ec817..18da86c 100644 --- a/cimery/crates/kernel/src/occt.rs +++ b/cimery/crates/kernel/src/occt.rs @@ -141,7 +141,8 @@ mod inner { .map(|&i| i as u32) .collect(); - Ok(Mesh { vertices, normals, indices }) + let colors = vec![crate::COLOR_CONCRETE; vertices.len()]; + Ok(Mesh { vertices, normals, indices, colors }) } } diff --git a/cimery/crates/kernel/src/sweep.rs b/cimery/crates/kernel/src/sweep.rs index bd9a1e0..60f605b 100644 --- a/cimery/crates/kernel/src/sweep.rs +++ b/cimery/crates/kernel/src/sweep.rs @@ -66,7 +66,8 @@ pub fn sweep_profile_flat(profile: &[[f32; 2]], span: f32) -> Mesh { push_tri(cen_b, [profile[i][0],profile[i][1],span], [profile[j][0],profile[j][1],span]); } - Mesh { vertices, normals, indices } + let colors = vec![crate::COLOR_CONCRETE; vertices.len()]; + Mesh { vertices, normals, indices, colors } } // ─── Convenience shapes ─────────────────────────────────────────────────────── @@ -115,11 +116,13 @@ pub fn merge_meshes(meshes: Vec) -> Mesh { let mut vertices: Vec<[f32; 3]> = Vec::new(); let mut normals: Vec<[f32; 3]> = Vec::new(); let mut indices: Vec = Vec::new(); + let mut colors: Vec<[f32; 3]> = Vec::new(); for m in meshes { let base = vertices.len() as u32; vertices.extend_from_slice(&m.vertices); normals.extend_from_slice(&m.normals); + colors.extend_from_slice(&m.colors); indices.extend(m.indices.iter().map(|i| i + base)); } - Mesh { vertices, normals, indices } + Mesh { vertices, normals, indices, colors } } diff --git a/cimery/crates/viewer/src/bridge_scene.rs b/cimery/crates/viewer/src/bridge_scene.rs index 7352dd0..95a56e1 100644 --- a/cimery/crates/viewer/src/bridge_scene.rs +++ b/cimery/crates/viewer/src/bridge_scene.rs @@ -16,6 +16,12 @@ use cimery_ir::{ }; use cimery_kernel::{GeomKernel, KernelError, Mesh}; +// ── Part colours (linear sRGB) ────────────────────────────────────────────── +pub const COL_GIRDER: [f32; 3] = [0.85, 0.82, 0.72]; // light concrete +pub const COL_DECK: [f32; 3] = [0.72, 0.70, 0.62]; // slightly darker slab +pub const COL_BEARING: [f32; 3] = [0.30, 0.30, 0.35]; // dark rubber/steel +pub const COL_ABUTMENT: [f32; 3] = [0.65, 0.60, 0.50]; // brown concrete + // ─── Helpers ───────────────────────────────────────────────────────────────── fn translate(mut mesh: Mesh, dx: f32, dy: f32, dz: f32) -> Mesh { @@ -58,7 +64,8 @@ pub fn build_bridge_scene(kernel: &K) -> Result(kernel: &K) -> Result(kernel: &K) -> Result(kernel: &K) -> Result Float32x3, // position 1 => Float32x3, // normal + 2 => Float32x3, // base_color ]; fn desc() -> wgpu::VertexBufferLayout<'static> { @@ -151,8 +153,10 @@ impl RenderState { let mesh = bridge_scene::build_bridge_scene(&PureRustKernel) .expect("PureRustKernel bridge scene"); - let verts: Vec = mesh.vertices.iter().zip(mesh.normals.iter()) - .map(|(p, n)| Vertex { position: *p, normal: *n }) + let verts: Vec = mesh.vertices.iter() + .zip(mesh.normals.iter()) + .zip(mesh.colors.iter()) + .map(|((p, n), c)| Vertex { position: *p, normal: *n, base_color: *c }) .collect(); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { diff --git a/cimery/crates/viewer/src/shader.wgsl b/cimery/crates/viewer/src/shader.wgsl index 0215a2b..6cac3d8 100644 --- a/cimery/crates/viewer/src/shader.wgsl +++ b/cimery/crates/viewer/src/shader.wgsl @@ -9,13 +9,15 @@ struct CameraUniform { var camera: CameraUniform; struct VertexInput { - @location(0) position: vec3, - @location(1) normal: vec3, + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) base_color: vec3, }; struct VertexOutput { @builtin(position) clip_pos: vec4, @location(0) world_normal: vec3, + @location(1) base_color: vec3, }; @vertex @@ -23,17 +25,16 @@ fn vs_main(in: VertexInput) -> VertexOutput { var out: VertexOutput; out.clip_pos = camera.view_proj * vec4(in.position, 1.0); out.world_normal = in.normal; + out.base_color = in.base_color; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - // Simple directional light — PSC concrete beige - let light = normalize(vec3(0.577, 0.577, -0.577)); - let n = normalize(in.world_normal); - let diffuse = max(dot(n, light), 0.0); - let ambient = 0.30; - let base_col = vec3(0.80, 0.76, 0.65); // concrete grey-beige - let col = base_col * (ambient + 0.70 * diffuse); + let light = normalize(vec3(0.577, 0.577, -0.577)); + let n = normalize(in.world_normal); + let diffuse = max(dot(n, light), 0.0); + let ambient = 0.30; + let col = in.base_color * (ambient + 0.70 * diffuse); return vec4(col, 1.0); }