feat: Orthographic 카메라 추가 — 실측 확인용
사용자 피드백: 거더 높이 변경 시 차이가 미미해 보이는 것은 원근 투영의 시각 효과일 수 있음. 평행(Orthographic) 투영으로 보면 거리 무관 실측 크기가 그대로 보여 모델이 실제로 변하는지 객관적으로 확인 가능. 변경: - camera.rs: Projection enum (Perspective / Orthographic) 추가. - Camera.projection 필드 + view_proj() 분기. - Ortho 반높이 = radius * tan(fov_y/2) → 전환 시 시각 스케일 일치. - toggle_projection() 메서드. - lib.rs: - 카메라 초기값 projection: Perspective. - 키 O → 투영 토글. - egui 표시 섹션에 투영 버튼 추가 (◇ Perspective / ■ Ortho). 사용: 거더 높이 슬라이더 조정 → Apply → O 키로 Ortho 전환 → 모델 치수 실측 확인. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,16 @@ pub enum StandardView {
|
||||
Iso, // Home
|
||||
}
|
||||
|
||||
/// Camera projection mode.
|
||||
///
|
||||
/// - `Perspective`: 원근 투영. 멀수록 작게 보임. 일반 3D 뷰.
|
||||
/// - `Orthographic`: 평행 투영. 거리 무관 실측 크기. 치수 검증·도면 스타일 뷰.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Projection {
|
||||
Perspective,
|
||||
Orthographic,
|
||||
}
|
||||
|
||||
/// Orbit camera — spherical coordinates around a fixed target point.
|
||||
///
|
||||
/// All distances in millimetres (scene units).
|
||||
@@ -48,6 +58,8 @@ pub struct Camera {
|
||||
pub aspect: f32,
|
||||
pub znear: f32,
|
||||
pub zfar: f32,
|
||||
/// 투영 모드 (원근 / 평행). 토글 O.
|
||||
pub projection: Projection,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
@@ -62,6 +74,7 @@ impl Camera {
|
||||
aspect: 16.0 / 9.0,
|
||||
znear: 10.0, // 10 mm
|
||||
zfar: 10_000_000.0, // 10 km
|
||||
projection: Projection::Perspective,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +94,29 @@ impl Camera {
|
||||
/// View-projection matrix (right-handed, depth 0→1).
|
||||
pub fn view_proj(&self) -> Mat4 {
|
||||
let view = Mat4::look_at_rh(self.eye(), self.target, Vec3::Y);
|
||||
let proj = Mat4::perspective_rh(self.fov_y, self.aspect, self.znear, self.zfar);
|
||||
let proj = match self.projection {
|
||||
Projection::Perspective => {
|
||||
Mat4::perspective_rh(self.fov_y, self.aspect, self.znear, self.zfar)
|
||||
}
|
||||
Projection::Orthographic => {
|
||||
// Ortho 수직 반높이: 같은 radius·FOV 의 원근 뷰와 시각 스케일 일치 →
|
||||
// target 평면에서 보이던 크기를 그대로 유지한 채 평행 투영.
|
||||
let half_h = self.radius * (self.fov_y * 0.5).tan();
|
||||
let half_w = half_h * self.aspect;
|
||||
Mat4::orthographic_rh(-half_w, half_w, -half_h, half_h, self.znear, self.zfar)
|
||||
}
|
||||
};
|
||||
proj * view
|
||||
}
|
||||
|
||||
/// Perspective ↔ Orthographic 토글.
|
||||
pub fn toggle_projection(&mut self) {
|
||||
self.projection = match self.projection {
|
||||
Projection::Perspective => Projection::Orthographic,
|
||||
Projection::Orthographic => Projection::Perspective,
|
||||
};
|
||||
}
|
||||
|
||||
/// Build GPU uniform from current state.
|
||||
pub fn to_uniform(&self) -> CameraUniform {
|
||||
CameraUniform { view_proj: self.view_proj().to_cols_array_2d() }
|
||||
|
||||
@@ -23,7 +23,7 @@ use wgpu::util::DeviceExt;
|
||||
use cimery_kernel::OcctKernel;
|
||||
#[cfg(not(feature = "occt"))]
|
||||
use cimery_kernel::PureRustKernel;
|
||||
use camera::{Camera, StandardView};
|
||||
use camera::{Camera, Projection, StandardView};
|
||||
use glam;
|
||||
use bridge_scene::{
|
||||
GirderSectionType, SceneParams,
|
||||
@@ -277,6 +277,7 @@ impl RenderState {
|
||||
aspect: 16.0 / 9.0,
|
||||
znear: 10.0,
|
||||
zfar: 10_000_000.0,
|
||||
projection: Projection::Perspective,
|
||||
};
|
||||
let _ = mesh.aabb(); // keep aabb call for future use
|
||||
camera.resize(surface_config.width, surface_config.height);
|
||||
@@ -562,6 +563,9 @@ impl RenderState {
|
||||
let mut dirty = self.dirty;
|
||||
let was_dirty = dirty;
|
||||
let mut apply = false;
|
||||
// Projection toggle: egui 버튼이 self.camera 를 직접 못 건드리므로 플래그로 전달.
|
||||
let mut toggle_ortho = false;
|
||||
let is_ortho_now = self.camera.projection == Projection::Orthographic;
|
||||
// Sprint 17: alignment display info (capture before closure)
|
||||
let state_alignment_name: Option<String> = self.alignment_scene.alignment
|
||||
.as_ref().map(|a| a.name.clone());
|
||||
@@ -662,6 +666,15 @@ impl RenderState {
|
||||
let prev_al = p.show_alignment;
|
||||
ui.checkbox(&mut p.show_alignment, "선형 표시");
|
||||
if prev_al != p.show_alignment { dirty = true; }
|
||||
|
||||
// 투영 모드 토글 (dirty 와 무관, 즉시 적용)
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("투영:");
|
||||
let label = if is_ortho_now { "■ Ortho (O)" } else { "◇ Perspective (O)" };
|
||||
if ui.button(label).clicked() {
|
||||
toggle_ortho = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
@@ -757,6 +770,10 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
if apply { self.rebuild_mesh(); }
|
||||
if toggle_ortho {
|
||||
self.camera.toggle_projection();
|
||||
self.update_camera();
|
||||
}
|
||||
|
||||
// ── 3D scene ─────────────────────────────────────────────────────────
|
||||
let output = self.surface.get_current_texture()?;
|
||||
@@ -913,6 +930,11 @@ impl ApplicationHandler for CimeryApp {
|
||||
state.camera.zoom_extents(state.scene_mn, state.scene_mx);
|
||||
state.update_camera();
|
||||
}
|
||||
// O → Perspective ↔ Orthographic 투영 토글 (실측 확인용)
|
||||
PhysicalKey::Code(KeyCode::KeyO) => {
|
||||
state.camera.toggle_projection();
|
||||
state.update_camera();
|
||||
}
|
||||
// Numpad 7 / 7 → Top view
|
||||
PhysicalKey::Code(KeyCode::Numpad7) | PhysicalKey::Code(KeyCode::Digit7) => {
|
||||
state.camera.set_standard_view(StandardView::Top);
|
||||
|
||||
Reference in New Issue
Block a user