viewer: Pan + ZoomExtents + 표준 뷰 단축키

camera.rs:
- pan(dx, dy): Shift+중간버튼으로 target 이동 (화면 평면 이동)
- zoom_extents(mn, mx): E키로 전체 씬 맞춤
- set_standard_view(StandardView): 축 고정 뷰
- StandardView: Top(7) / Front(1) / Right(3) / Left(4) / Iso(Home)

lib.rs:
- shift_pressed 추적 (ModifiersChanged)
- Shift+중간버튼 드래그 → pan
- E → ZoomExtents
- 1/3/4/7/Home → 표준 뷰 스냅
- scene_mn/mx 저장 → zoom_extents에 전달

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-14 20:11:07 +09:00
parent 3359475879
commit 096fc133c4
2 changed files with 114 additions and 13 deletions

View File

@@ -31,7 +31,7 @@ use cimery_kernel::OcctKernel;
#[cfg(not(feature = "occt"))]
use cimery_kernel::PureRustKernel;
use cimery_kernel::GeomKernel;
use camera::Camera;
use camera::{Camera, StandardView};
use glam;
// ─── Vertex ───────────────────────────────────────────────────────────────────
@@ -80,9 +80,13 @@ struct RenderState {
camera_bind_group: wgpu::BindGroup,
// Depth
depth_view: wgpu::TextureView,
// Mouse state
mid_pressed: bool,
last_mouse: winit::dpi::PhysicalPosition<f64>,
// Mouse / keyboard state
mid_pressed: bool,
shift_pressed: bool,
last_mouse: winit::dpi::PhysicalPosition<f64>,
// Scene extents for ZoomExtents
scene_mn: [f32; 3],
scene_mx: [f32; 3],
}
impl RenderState {
@@ -271,6 +275,8 @@ impl RenderState {
cache: None,
});
let (scene_mn, scene_mx) = bridge_scene::scene_extents();
RenderState {
window,
device,
@@ -285,8 +291,11 @@ impl RenderState {
camera_buffer,
camera_bind_group,
depth_view,
mid_pressed: false,
last_mouse: winit::dpi::PhysicalPosition { x: 0.0, y: 0.0 },
mid_pressed: false,
shift_pressed: false,
last_mouse: winit::dpi::PhysicalPosition { x: 0.0, y: 0.0 },
scene_mn,
scene_mx,
}
}
@@ -416,16 +425,56 @@ impl ApplicationHandler for CimeryApp {
match event {
// ── Exit ──────────────────────────────────────────────────────────
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::KeyboardInput {
event: KeyEvent {
physical_key: PhysicalKey::Code(KeyCode::Escape), ..
}, ..
} => event_loop.exit(),
// ── Shift modifier tracking ───────────────────────────────────────
WindowEvent::ModifiersChanged(mods) => {
state.shift_pressed = mods.state().shift_key();
}
// ── Keyboard shortcuts ────────────────────────────────────────────
WindowEvent::KeyboardInput { event: KeyEvent { physical_key, state: key_state, .. }, .. }
if key_state == ElementState::Pressed =>
{
match physical_key {
PhysicalKey::Code(KeyCode::Escape) => event_loop.exit(),
// E → ZoomExtents (fit all)
PhysicalKey::Code(KeyCode::KeyE) => {
state.camera.zoom_extents(state.scene_mn, state.scene_mx);
state.update_camera();
}
// Numpad 7 / 7 → Top view
PhysicalKey::Code(KeyCode::Numpad7) | PhysicalKey::Code(KeyCode::Digit7) => {
state.camera.set_standard_view(StandardView::Top);
state.update_camera();
}
// Numpad 1 / 1 → Front view
PhysicalKey::Code(KeyCode::Numpad1) | PhysicalKey::Code(KeyCode::Digit1) => {
state.camera.set_standard_view(StandardView::Front);
state.update_camera();
}
// Numpad 3 / 3 → Right side view
PhysicalKey::Code(KeyCode::Numpad3) | PhysicalKey::Code(KeyCode::Digit3) => {
state.camera.set_standard_view(StandardView::Right);
state.update_camera();
}
// Numpad 4 / 4 → Left side view
PhysicalKey::Code(KeyCode::Numpad4) | PhysicalKey::Code(KeyCode::Digit4) => {
state.camera.set_standard_view(StandardView::Left);
state.update_camera();
}
// Home → Isometric (default view)
PhysicalKey::Code(KeyCode::Home) => {
state.camera.set_standard_view(StandardView::Iso);
state.update_camera();
}
_ => {}
}
}
// ── Resize ────────────────────────────────────────────────────────
WindowEvent::Resized(sz) => state.resize(sz),
// ── Mouse orbit (middle button drag) ──────────────────────────────
// ── Mouse orbit / pan (middle button drag) ────────────────────────
WindowEvent::MouseInput { button: MouseButton::Middle, state: btn_state, .. } => {
state.mid_pressed = btn_state == ElementState::Pressed;
}
@@ -433,7 +482,13 @@ impl ApplicationHandler for CimeryApp {
if state.mid_pressed {
let dx = (position.x - state.last_mouse.x) as f32;
let dy = (position.y - state.last_mouse.y) as f32;
state.camera.orbit(dx, dy);
if state.shift_pressed {
// Shift + Middle = Pan (translate target)
state.camera.pan(dx, dy);
} else {
// Middle = Orbit
state.camera.orbit(dx, dy);
}
state.update_camera();
}
state.last_mouse = position;