받침 중심 정렬 + 부재별 색상 구분

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:
minsung
2026-04-14 20:26:40 +09:00
parent eac079f46c
commit 3645b85828
7 changed files with 66 additions and 31 deletions

View File

@@ -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<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
spacing: 0.0,
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));
}
@@ -76,8 +83,8 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
cross_slope: 2.0,
material: MaterialGrade::C40,
};
let deck_mesh = kernel.deck_slab_mesh(&deck_ir)?;
// Slab Y=0 is its top face; place it so bottom aligns with girder top
let mut deck_mesh = kernel.deck_slab_mesh(&deck_ir)?;
deck_mesh.recolor(COL_DECK);
parts.push(translate(deck_mesh, 0.0, GIRDER_H + 220.0, 0.0));
// ── Bearings ───────────────────────────────────────────────────────────────
@@ -94,9 +101,9 @@ pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError
total_height: BEARING_H as f64,
capacity_vertical: 1_500.0,
};
let mesh = kernel.bearing_mesh(&bearing_ir)?;
// Place bearing centred under each girder, top at Y=0 (girder soffit)
parts.push(translate(mesh, x - 175.0, -BEARING_H, z - 225.0));
let mut mesh = kernel.bearing_mesh(&bearing_ir)?;
mesh.recolor(COL_BEARING);
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(),
material: MaterialGrade::C40,
};
let mesh = kernel.abutment_mesh(&abut_ir)?;
// Place abutment: breast wall top at Y = -(BEARING_H)
let mut mesh = kernel.abutment_mesh(&abut_ir)?;
mesh.recolor(COL_ABUTMENT);
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));
}

View File

@@ -33,18 +33,20 @@ use glam;
// ─── Vertex ───────────────────────────────────────────────────────────────────
/// Per-vertex data sent to GPU: 3D position + surface normal.
/// Per-vertex data sent to GPU: position + normal + base color.
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct Vertex {
position: [f32; 3],
normal: [f32; 3],
position: [f32; 3],
normal: [f32; 3],
base_color: [f32; 3], // material base colour (modulated by lighting)
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
0 => 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<Vertex> = mesh.vertices.iter().zip(mesh.normals.iter())
.map(|(p, n)| Vertex { position: *p, normal: *n })
let verts: Vec<Vertex> = 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 {

View File

@@ -9,13 +9,15 @@ struct CameraUniform {
var<uniform> camera: CameraUniform;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) base_color: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_pos: vec4<f32>,
@location(0) world_normal: vec3<f32>,
@location(1) base_color: vec3<f32>,
};
@vertex
@@ -23,17 +25,16 @@ fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.clip_pos = camera.view_proj * vec4<f32>(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<f32> {
// Simple directional light — PSC concrete beige
let light = normalize(vec3<f32>(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<f32>(0.80, 0.76, 0.65); // concrete grey-beige
let col = base_col * (ambient + 0.70 * diffuse);
let light = normalize(vec3<f32>(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<f32>(col, 1.0);
}