fix(viewcube): snap to direction with nearest-canonical up axis
ViewCube tıklaması artık sabit (yaw, pitch) tablosu yerine bir yön
vektörüne (eye direction) snap ediyor. Up vektörü şöyle seçiliyor:
1. Mevcut up alın.
2. ±X / ±Y / ±Z arasından (yeni gaze ile (anti-)paralel olmayanlar)
mevcut up'a en yüksek dot-product veren cardinal axis'i seç.
3. Bu axis'i yeni gaze yönüne dik plane'e projekte et ve up olarak
kullan.
Sonuç:
- Küçük tilt'ler ilgili canonical axis'e snap olarak temizleniyor.
- TOP yukarıda iken TOP-LEFT edge tıklayınca TOP yukarıda kalıyor;
cube edge'e dönüyor ama kullanıcının "yukarı" hissi korunuyor.
- Upside-down orbit edilmişse -Z up olarak seçilir, snap baş aşağı
yapılandırmayı bozmuyor.
CubeRegion::snap_angles ve Camera::snap_to_angles yerini
snap_direction / snap_to_direction aldı.
snap_active_viewport_to_direction viewport'un view_direction'ını
günceller, twist_angle'a dokunmaz.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
26bcd89358
commit
451239c4f9
4 changed files with 135 additions and 68 deletions
|
|
@ -72,37 +72,62 @@ impl CubeRegion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn snap_angles(self) -> (f32, f32) {
|
||||
/// Unit eye-direction vector (from target toward the camera) that
|
||||
/// looks straight at this region. Used by `Camera::snap_to_direction`
|
||||
/// which derives the full orientation by re-using the current
|
||||
/// camera's up vector, projected onto the plane perpendicular to
|
||||
/// this direction — so clicking an edge spins the cube around the
|
||||
/// edge without rolling the user's "up" sense.
|
||||
pub fn snap_direction(self) -> glam::Vec3 {
|
||||
let c = region_centroids()[self.id()];
|
||||
glam::Vec3::new(c[0], c[1], c[2]).normalize_or(glam::Vec3::Z)
|
||||
}
|
||||
|
||||
/// Returns `(yaw, pitch, roll)` for the canonical camera orientation
|
||||
/// pointed at this region.
|
||||
///
|
||||
/// `yaw` (rotate around world Z) and `pitch` (tilt toward world Z)
|
||||
/// place the camera on the right side of the cube; `roll` rotates
|
||||
/// around the camera's view axis so edge views land with the edge
|
||||
/// vertical on screen instead of horizontal. Faces and corners
|
||||
/// keep `roll = 0` (world up stays up).
|
||||
pub fn snap_angles(self) -> (f32, f32, f32) {
|
||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
|
||||
let iso = (1.0_f32 / 2.0_f32.sqrt()).atan();
|
||||
// For the 8 horizontal-cube edges (top + bottom rings), roll by
|
||||
// ±π/2 around the camera's view axis so the edge sits vertical
|
||||
// on screen — TOP face on one half, the side face on the other.
|
||||
// The 4 vertical edges (FRONT-RIGHT etc.) already run along Z
|
||||
// and read vertical with roll = 0.
|
||||
let r = FRAC_PI_2;
|
||||
match self.id() {
|
||||
0 => (0.0, FRAC_PI_2),
|
||||
1 => (0.0, -FRAC_PI_2),
|
||||
2 => (0.0, 0.0), // FRONT: yaw=0 (eye at -Y)
|
||||
3 => (PI, 0.0), // BACK: yaw=PI (eye at +Y)
|
||||
4 => (FRAC_PI_2, 0.0), // RIGHT
|
||||
5 => (-FRAC_PI_2, 0.0), // LEFT
|
||||
6 => (0.0, FRAC_PI_4), // EDGE_TOP_FRONT
|
||||
7 => (PI, FRAC_PI_4), // EDGE_TOP_BACK
|
||||
8 => (FRAC_PI_2, FRAC_PI_4), // EDGE_TOP_RIGHT
|
||||
9 => (-FRAC_PI_2, FRAC_PI_4), // EDGE_TOP_LEFT
|
||||
10 => (0.0, -FRAC_PI_4), // EDGE_BOT_FRONT
|
||||
11 => (PI, -FRAC_PI_4), // EDGE_BOT_BACK
|
||||
12 => (FRAC_PI_2, -FRAC_PI_4),
|
||||
13 => (-FRAC_PI_2, -FRAC_PI_4),
|
||||
14 => (FRAC_PI_4, 0.0), // EDGE_FRONT_RIGHT (between 0 and PI/2)
|
||||
15 => (-FRAC_PI_4, 0.0), // EDGE_FRONT_LEFT
|
||||
16 => (PI * 0.75, 0.0), // EDGE_BACK_RIGHT (between PI and PI/2)
|
||||
17 => (-PI * 0.75, 0.0), // EDGE_BACK_LEFT
|
||||
18 => (FRAC_PI_4, iso), // CORNER_TPF_R (FRONT+RIGHT)
|
||||
19 => (-FRAC_PI_4, iso), // CORNER_TPF_L (FRONT+LEFT)
|
||||
20 => (PI * 0.75, iso), // CORNER_TBK_R (BACK+RIGHT)
|
||||
21 => (-PI * 0.75, iso), // CORNER_TBK_L (BACK+LEFT)
|
||||
22 => (FRAC_PI_4, -iso), // CORNER_BTF_R
|
||||
23 => (-FRAC_PI_4, -iso), // CORNER_BTF_L
|
||||
24 => (PI * 0.75, -iso), // CORNER_BBK_R
|
||||
25 => (-PI * 0.75, -iso), // CORNER_BBK_L
|
||||
_ => (0.0, 0.0),
|
||||
0 => (0.0, FRAC_PI_2, 0.0),
|
||||
1 => (0.0, -FRAC_PI_2, 0.0),
|
||||
2 => (0.0, 0.0, 0.0), // FRONT
|
||||
3 => (PI, 0.0, 0.0), // BACK
|
||||
4 => (FRAC_PI_2, 0.0, 0.0), // RIGHT
|
||||
5 => (-FRAC_PI_2, 0.0, 0.0), // LEFT
|
||||
6 => (0.0, FRAC_PI_4, r), // EDGE_TOP_FRONT
|
||||
7 => (PI, FRAC_PI_4, r), // EDGE_TOP_BACK
|
||||
8 => (FRAC_PI_2, FRAC_PI_4, r), // EDGE_TOP_RIGHT
|
||||
9 => (-FRAC_PI_2, FRAC_PI_4, r), // EDGE_TOP_LEFT
|
||||
10 => (0.0, -FRAC_PI_4, r), // EDGE_BOT_FRONT
|
||||
11 => (PI, -FRAC_PI_4, r), // EDGE_BOT_BACK
|
||||
12 => (FRAC_PI_2, -FRAC_PI_4, r), // EDGE_BOT_RIGHT
|
||||
13 => (-FRAC_PI_2, -FRAC_PI_4, r), // EDGE_BOT_LEFT
|
||||
14 => (FRAC_PI_4, 0.0, 0.0), // EDGE_FRONT_RIGHT
|
||||
15 => (-FRAC_PI_4, 0.0, 0.0), // EDGE_FRONT_LEFT
|
||||
16 => (PI * 0.75, 0.0, 0.0), // EDGE_BACK_RIGHT
|
||||
17 => (-PI * 0.75, 0.0, 0.0), // EDGE_BACK_LEFT
|
||||
18 => (FRAC_PI_4, iso, 0.0), // CORNER_TPF_R
|
||||
19 => (-FRAC_PI_4, iso, 0.0), // CORNER_TPF_L
|
||||
20 => (PI * 0.75, iso, 0.0), // CORNER_TBK_R
|
||||
21 => (-PI * 0.75, iso, 0.0), // CORNER_TBK_L
|
||||
22 => (FRAC_PI_4, -iso, 0.0), // CORNER_BTF_R
|
||||
23 => (-FRAC_PI_4, -iso, 0.0), // CORNER_BTF_L
|
||||
24 => (PI * 0.75, -iso, 0.0), // CORNER_BBK_R
|
||||
25 => (-PI * 0.75, -iso, 0.0), // CORNER_BBK_L
|
||||
_ => (0.0, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue