feat(viewer): stage thumbnail grid, per-model albedo, camera zoom fix
Route stage containers to stage_models (decode both single + stage paths, keep whichever yields more geometry) — fixes stages showing the dummy-root "black cube". Present a stage's sub-models as a thumbnail grid: each recentred and uniformly scaled to a fixed cell so a 1-unit prop and a 3000-unit structure are equally visible; skybox-plane / stray-anchor models (>20000u) are culled. Each sub-model is textured by matching its name to the container's `_col` albedo. Fix the orbit camera: zoom radius was hard-capped at 50u with fixed clip planes, trapping the camera inside multi-thousand-unit stages. Zoom limits and near/far planes now scale to the framed model. Adds docs/HANDOFF-stage-mesh-2026-07-12.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,10 @@ pub struct OrbitCamera {
|
||||
pub orbit_sensitivity: f32,
|
||||
pub zoom_sensitivity: f32,
|
||||
pub pan_sensitivity: f32,
|
||||
/// Zoom limits — set when framing a model so large scenes can be pulled back
|
||||
/// far enough (the old fixed 0.5..50 cap trapped the camera inside big stages).
|
||||
pub min_radius: f32,
|
||||
pub max_radius: f32,
|
||||
}
|
||||
|
||||
impl Default for OrbitCamera {
|
||||
@@ -44,6 +48,8 @@ impl Default for OrbitCamera {
|
||||
orbit_sensitivity: 0.005,
|
||||
zoom_sensitivity: 0.3,
|
||||
pan_sensitivity: 0.003,
|
||||
min_radius: 0.05,
|
||||
max_radius: 500.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,19 +60,26 @@ fn spawn_camera(mut commands: Commands) {
|
||||
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
// Wide near/far so both tiny weapons and multi-thousand-unit stages fit;
|
||||
// the planes are re-scaled to the zoom distance each frame (below).
|
||||
Projection::Perspective(PerspectiveProjection {
|
||||
near: 0.05,
|
||||
far: 100_000.0,
|
||||
..default()
|
||||
}),
|
||||
transform,
|
||||
orbit,
|
||||
));
|
||||
}
|
||||
|
||||
fn orbit_camera(
|
||||
mut query: Query<(&mut OrbitCamera, &mut Transform)>,
|
||||
mut query: Query<(&mut OrbitCamera, &mut Transform, &mut Projection)>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut mouse_motion: EventReader<MouseMotion>,
|
||||
mut scroll: EventReader<MouseWheel>,
|
||||
) {
|
||||
let Ok((mut cam, mut transform)) = query.get_single_mut() else { return };
|
||||
let Ok((mut cam, mut transform, mut projection)) = query.get_single_mut() else { return };
|
||||
|
||||
let mut delta_motion = Vec2::ZERO;
|
||||
for ev in mouse_motion.read() {
|
||||
@@ -99,13 +112,20 @@ fn orbit_camera(
|
||||
|
||||
// Zoom (scroll)
|
||||
cam.radius -= scroll_delta * cam.zoom_sensitivity * cam.radius;
|
||||
cam.radius = cam.radius.clamp(0.5, 50.0);
|
||||
cam.radius = cam.radius.clamp(cam.min_radius, cam.max_radius);
|
||||
|
||||
// Reset (R key)
|
||||
if keys.just_pressed(KeyCode::KeyR) {
|
||||
*cam = OrbitCamera::default();
|
||||
}
|
||||
|
||||
// Keep the clip planes proportional to the zoom distance so depth precision
|
||||
// stays usable across scales (tiny prop → whole stage grid).
|
||||
if let Projection::Perspective(p) = projection.as_mut() {
|
||||
p.near = (cam.radius * 0.02).clamp(0.02, 50.0);
|
||||
p.far = (cam.radius * 50.0).max(2000.0);
|
||||
}
|
||||
|
||||
*transform = orbit_transform(&cam);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user