Files
ParaWiki/cimery/crates/viewer/src/bridge_scene.rs
minsung 3645b85828 받침 중심 정렬 + 부재별 색상 구분
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>
2026-04-14 20:26:40 +09:00

147 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Full bridge scene compositor — Sprint 4.
//!
//! Builds a single merged mesh for a simple 40 m single-span PSC-I girder bridge:
//! - 5 × PSC-I Girder (2500 mm c/c)
//! - 1 × Deck Slab (12000 mm wide)
//! - 10 × Elastomeric Bearing (5 per abutment)
//! - 2 × Abutment (start & end)
//!
//! Positions are in the same coordinate space as the girder mesh:
//! X = transverse (right = +), Y = vertical (up = +), Z = along span.
use cimery_core::{AbutmentType, BearingType, MaterialGrade, SectionType};
use cimery_ir::{
AbutmentIR, BearingIR, DeckSlabIR, FeatureId, GirderIR,
PscISectionParams, SectionParams, WingWallIR,
};
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 {
for v in &mut mesh.vertices {
v[0] += dx;
v[1] += dy;
v[2] += dz;
}
mesh
}
fn merge(meshes: Vec<Mesh>) -> Mesh {
cimery_kernel::sweep::merge_meshes(meshes)
}
// ─── Scene builder ────────────────────────────────────────────────────────────
/// Build a complete bridge scene mesh using the provided kernel.
pub fn build_bridge_scene<K: GeomKernel>(kernel: &K) -> Result<Mesh, KernelError> {
const SPAN_M: f64 = 40.0;
const SPAN_MM: f32 = 40_000.0;
const N_GIRDERS: usize = 5;
const SPACING: f32 = 2_500.0; // mm c/c
const GIRDER_H: f32 = 1_800.0; // mm
const BEARING_H: f32 = 60.0; // mm
let mut parts: Vec<Mesh> = Vec::new();
// ── Girders ────────────────────────────────────────────────────────────────
for i in 0..N_GIRDERS {
let x = (i as f32 - (N_GIRDERS as f32 - 1.0) * 0.5) * SPACING;
let ir = GirderIR {
id: FeatureId::new(),
station_start: 0.0,
station_end: SPAN_M,
offset_from_alignment: x as f64,
section_type: SectionType::PscI,
section: SectionParams::PscI(PscISectionParams::kds_standard()),
count: 1,
spacing: 0.0,
material: MaterialGrade::C50,
};
let mut mesh = kernel.girder_mesh(&ir)?;
mesh.recolor(COL_GIRDER);
parts.push(translate(mesh, x, 0.0, 0.0));
}
// ── Deck Slab ──────────────────────────────────────────────────────────────
// KDS: min 220 mm, width = (N-1)*spacing + 2 × cantilever
let half_width = ((N_GIRDERS as f32 - 1.0) * SPACING) * 0.5 + 1_000.0; // 1 m cantilever
let deck_ir = DeckSlabIR {
id: FeatureId::new(),
station_start: 0.0,
station_end: SPAN_M,
width_left: half_width as f64,
width_right: half_width as f64,
thickness: 220.0,
haunch_depth: 0.0,
cross_slope: 2.0,
material: MaterialGrade::C40,
};
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 ───────────────────────────────────────────────────────────────
// 5 per abutment, one under each girder
for &z in &[0.0_f32, SPAN_MM] {
for i in 0..N_GIRDERS {
let x = (i as f32 - (N_GIRDERS as f32 - 1.0) * 0.5) * SPACING;
let bearing_ir = BearingIR {
id: FeatureId::new(),
station: if z < 1.0 { 0.0 } else { SPAN_M },
bearing_type: BearingType::Elastomeric,
plan_length: 350.0,
plan_width: 450.0,
total_height: BEARING_H as f64,
capacity_vertical: 1_500.0,
};
let mut mesh = kernel.bearing_mesh(&bearing_ir)?;
mesh.recolor(COL_BEARING);
parts.push(translate(mesh, x, 0.0, z - 225.0));
}
}
// ── Abutments ──────────────────────────────────────────────────────────────
let wing = WingWallIR { length: 5_000.0, height: 2_500.0, thickness: 500.0 };
let total_w = (N_GIRDERS as f64 - 1.0) * SPACING as f64 + 3_000.0; // incl. overhangs
for &(station, z) in &[(0.0f64, -800.0_f32), (SPAN_M, SPAN_MM)] {
let abut_ir = AbutmentIR {
id: FeatureId::new(),
station,
skew_angle: 0.0,
abutment_type: AbutmentType::ReverseT,
breast_wall_height: (GIRDER_H + BEARING_H) as f64,
breast_wall_thickness: 800.0,
breast_wall_width: total_w,
footing_length: 4_000.0,
footing_width: total_w + 1_000.0,
footing_thickness: 1_000.0,
wing_wall_left: wing.clone(),
wing_wall_right: wing.clone(),
material: MaterialGrade::C40,
};
let mut mesh = kernel.abutment_mesh(&abut_ir)?;
mesh.recolor(COL_ABUTMENT);
let y = -(BEARING_H + abut_ir.breast_wall_height as f32);
parts.push(translate(mesh, 0.0, y, z));
}
Ok(merge(parts))
}
/// Bounding box of the full bridge scene (for camera setup).
pub fn scene_extents() -> ([f32; 3], [f32; 3]) {
const SPAN_MM: f32 = 40_000.0;
const HALF_W: f32 = 6_500.0;
const TOP_Y: f32 = 2_020.0; // top of slab
const BOT_Y: f32 = -3_000.0; // footing bottom approx
([-HALF_W, BOT_Y, -2_000.0], [HALF_W, TOP_Y, SPAN_MM + 2_000.0])
}