받침 중심 정렬 + 부재별 색상 구분
bearing.rs: X 중심, Y 하방향 (-h to 0) 기하학 수정 bridge_scene.rs: 받침 X 오프셋 제거 (girder 정렬) Mesh: colors 필드 추가 + recolor() 메서드 sweep.rs / occt.rs: 기본 콘크리트 색 자동 채움 bridge_scene: 부재별 색상 (거더/슬래브/받침/교대) shader.wgsl: base_color 입력 → 조명 계산에 적용 선택(Selection) 기능은 계획대로 별도 Sprint에 구현. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,18 @@ pub fn build_bearing_mesh(ir: &BearingIR) -> Result<Mesh, KernelError> {
|
|||||||
if ir.plan_length <= 0.0 || ir.plan_width <= 0.0 || ir.total_height <= 0.0 {
|
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()));
|
return Err(KernelError::InvalidInput("bearing dimensions must be positive".into()));
|
||||||
}
|
}
|
||||||
// Centred box: plan_length × total_height × plan_width
|
// Box: X=[-l/2, +l/2] centred, Y=[-h, 0] (top at Y=0 = girder soffit), Z=[0, w]
|
||||||
// X = along span (plan_length), Y = height, Z = transverse (plan_width)
|
// Caller translates to girder X offset; Y=0 means bearing top sits at soffit.
|
||||||
let l = ir.plan_length as f32;
|
let l = ir.plan_length as f32;
|
||||||
let h = ir.total_height as f32;
|
let h = ir.total_height as f32;
|
||||||
let w = ir.plan_width 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)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -39,6 +39,19 @@ pub struct Mesh {
|
|||||||
pub indices: Vec<u32>,
|
pub indices: Vec<u32>,
|
||||||
/// Per-vertex normals (unit vectors).
|
/// Per-vertex normals (unit vectors).
|
||||||
pub normals: Vec<[f32; 3]>,
|
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 {
|
impl Mesh {
|
||||||
@@ -127,7 +140,8 @@ impl GeomKernel for StubKernel {
|
|||||||
0, 1, 5, 0, 5, 4, 3, 7, 6, 3, 6, 2,
|
0, 1, 5, 0, 5, 4, 3, 7, 6, 3, 6, 2,
|
||||||
];
|
];
|
||||||
let normals = vec![[0.0_f32, 1.0, 0.0]; vertices.len()];
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ mod inner {
|
|||||||
.map(|&i| i as u32)
|
.map(|&i| i as u32)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Mesh { vertices, normals, indices })
|
let colors = vec![crate::COLOR_CONCRETE; vertices.len()];
|
||||||
|
Ok(Mesh { vertices, normals, indices, colors })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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]);
|
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 ───────────────────────────────────────────────────────
|
// ─── Convenience shapes ───────────────────────────────────────────────────────
|
||||||
@@ -115,11 +116,13 @@ pub fn merge_meshes(meshes: Vec<Mesh>) -> Mesh {
|
|||||||
let mut vertices: Vec<[f32; 3]> = Vec::new();
|
let mut vertices: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut normals: Vec<[f32; 3]> = Vec::new();
|
let mut normals: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut indices: Vec<u32> = Vec::new();
|
let mut indices: Vec<u32> = Vec::new();
|
||||||
|
let mut colors: Vec<[f32; 3]> = Vec::new();
|
||||||
for m in meshes {
|
for m in meshes {
|
||||||
let base = vertices.len() as u32;
|
let base = vertices.len() as u32;
|
||||||
vertices.extend_from_slice(&m.vertices);
|
vertices.extend_from_slice(&m.vertices);
|
||||||
normals.extend_from_slice(&m.normals);
|
normals.extend_from_slice(&m.normals);
|
||||||
|
colors.extend_from_slice(&m.colors);
|
||||||
indices.extend(m.indices.iter().map(|i| i + base));
|
indices.extend(m.indices.iter().map(|i| i + base));
|
||||||
}
|
}
|
||||||
Mesh { vertices, normals, indices }
|
Mesh { vertices, normals, indices, colors }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ use cimery_ir::{
|
|||||||
};
|
};
|
||||||
use cimery_kernel::{GeomKernel, KernelError, Mesh};
|
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 ─────────────────────────────────────────────────────────────────
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn translate(mut mesh: Mesh, dx: f32, dy: f32, dz: f32) -> Mesh {
|
fn translate(mut mesh: Mesh, dx: f32, dy: f32, dz: f32) -> Mesh {
|
||||||
@@ -58,7 +64,8 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
|
|||||||
spacing: 0.0,
|
spacing: 0.0,
|
||||||
material: MaterialGrade::C50,
|
material: MaterialGrade::C50,
|
||||||
};
|
};
|
||||||
let mesh = kernel.girder_mesh(&ir)?;
|
let mut mesh = kernel.girder_mesh(&ir)?;
|
||||||
|
mesh.recolor(COL_GIRDER);
|
||||||
parts.push(translate(mesh, x, 0.0, 0.0));
|
parts.push(translate(mesh, x, 0.0, 0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +83,8 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
|
|||||||
cross_slope: 2.0,
|
cross_slope: 2.0,
|
||||||
material: MaterialGrade::C40,
|
material: MaterialGrade::C40,
|
||||||
};
|
};
|
||||||
let deck_mesh = kernel.deck_slab_mesh(&deck_ir)?;
|
let mut deck_mesh = kernel.deck_slab_mesh(&deck_ir)?;
|
||||||
// Slab Y=0 is its top face; place it so bottom aligns with girder top
|
deck_mesh.recolor(COL_DECK);
|
||||||
parts.push(translate(deck_mesh, 0.0, GIRDER_H + 220.0, 0.0));
|
parts.push(translate(deck_mesh, 0.0, GIRDER_H + 220.0, 0.0));
|
||||||
|
|
||||||
// ── Bearings ───────────────────────────────────────────────────────────────
|
// ── Bearings ───────────────────────────────────────────────────────────────
|
||||||
@@ -94,9 +101,9 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
|
|||||||
total_height: BEARING_H as f64,
|
total_height: BEARING_H as f64,
|
||||||
capacity_vertical: 1_500.0,
|
capacity_vertical: 1_500.0,
|
||||||
};
|
};
|
||||||
let mesh = kernel.bearing_mesh(&bearing_ir)?;
|
let mut mesh = kernel.bearing_mesh(&bearing_ir)?;
|
||||||
// Place bearing centred under each girder, top at Y=0 (girder soffit)
|
mesh.recolor(COL_BEARING);
|
||||||
parts.push(translate(mesh, x - 175.0, -BEARING_H, z - 225.0));
|
parts.push(translate(mesh, x, 0.0, z - 225.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +127,9 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
|
|||||||
wing_wall_right: wing.clone(),
|
wing_wall_right: wing.clone(),
|
||||||
material: MaterialGrade::C40,
|
material: MaterialGrade::C40,
|
||||||
};
|
};
|
||||||
let mesh = kernel.abutment_mesh(&abut_ir)?;
|
let mut mesh = kernel.abutment_mesh(&abut_ir)?;
|
||||||
// Place abutment: breast wall top at Y = -(BEARING_H)
|
mesh.recolor(COL_ABUTMENT);
|
||||||
let y = -(BEARING_H + abut_ir.breast_wall_height as f32);
|
let y = -(BEARING_H + abut_ir.breast_wall_height as f32);
|
||||||
// Abutment geometry is already centred at X=0; only Y and Z offset needed.
|
|
||||||
parts.push(translate(mesh, 0.0, y, z));
|
parts.push(translate(mesh, 0.0, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,18 +33,20 @@ use glam;
|
|||||||
|
|
||||||
// ─── Vertex ───────────────────────────────────────────────────────────────────
|
// ─── Vertex ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Per-vertex data sent to GPU: 3D position + surface normal.
|
/// Per-vertex data sent to GPU: position + normal + base color.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
position: [f32; 3],
|
position: [f32; 3],
|
||||||
normal: [f32; 3],
|
normal: [f32; 3],
|
||||||
|
base_color: [f32; 3], // material base colour (modulated by lighting)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vertex {
|
impl Vertex {
|
||||||
const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
|
const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
|
||||||
0 => Float32x3, // position
|
0 => Float32x3, // position
|
||||||
1 => Float32x3, // normal
|
1 => Float32x3, // normal
|
||||||
|
2 => Float32x3, // base_color
|
||||||
];
|
];
|
||||||
|
|
||||||
fn desc() -> wgpu::VertexBufferLayout<'static> {
|
fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||||
@@ -151,8 +153,10 @@ impl RenderState {
|
|||||||
let mesh = bridge_scene::build_bridge_scene(&PureRustKernel)
|
let mesh = bridge_scene::build_bridge_scene(&PureRustKernel)
|
||||||
.expect("PureRustKernel bridge scene");
|
.expect("PureRustKernel bridge scene");
|
||||||
|
|
||||||
let verts: Vec<Vertex> = mesh.vertices.iter().zip(mesh.normals.iter())
|
let verts: Vec<Vertex> = mesh.vertices.iter()
|
||||||
.map(|(p, n)| Vertex { position: *p, normal: *n })
|
.zip(mesh.normals.iter())
|
||||||
|
.zip(mesh.colors.iter())
|
||||||
|
.map(|((p, n), c)| Vertex { position: *p, normal: *n, base_color: *c })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ var<uniform> camera: CameraUniform;
|
|||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) normal: vec3<f32>,
|
@location(1) normal: vec3<f32>,
|
||||||
|
@location(2) base_color: vec3<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) clip_pos: vec4<f32>,
|
@builtin(position) clip_pos: vec4<f32>,
|
||||||
@location(0) world_normal: vec3<f32>,
|
@location(0) world_normal: vec3<f32>,
|
||||||
|
@location(1) base_color: vec3<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
@@ -23,17 +25,16 @@ fn vs_main(in: VertexInput) -> VertexOutput {
|
|||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_pos = camera.view_proj * vec4<f32>(in.position, 1.0);
|
out.clip_pos = camera.view_proj * vec4<f32>(in.position, 1.0);
|
||||||
out.world_normal = in.normal;
|
out.world_normal = in.normal;
|
||||||
|
out.base_color = in.base_color;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
// Simple directional light — PSC concrete beige
|
|
||||||
let light = normalize(vec3<f32>(0.577, 0.577, -0.577));
|
let light = normalize(vec3<f32>(0.577, 0.577, -0.577));
|
||||||
let n = normalize(in.world_normal);
|
let n = normalize(in.world_normal);
|
||||||
let diffuse = max(dot(n, light), 0.0);
|
let diffuse = max(dot(n, light), 0.0);
|
||||||
let ambient = 0.30;
|
let ambient = 0.30;
|
||||||
let base_col = vec3<f32>(0.80, 0.76, 0.65); // concrete grey-beige
|
let col = in.base_color * (ambient + 0.70 * diffuse);
|
||||||
let col = base_col * (ambient + 0.70 * diffuse);
|
|
||||||
return vec4<f32>(col, 1.0);
|
return vec4<f32>(col, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user