use crate::{Delta, Frame, Plane, Scope};
use core::fmt::Debug;
use nalgebra::{Point3, RealField, UnitQuaternion};
pub trait Clamp<N: Copy + RealField>: Send + Sync + Debug + 'static {
#[must_use]
fn loops(&self) -> usize {
100
}
#[must_use]
fn target(&self, frame: &Frame<N>) -> Option<Plane<N>>;
#[must_use]
fn eye(&self, frame: &Frame<N>) -> Option<Plane<N>>;
#[must_use]
fn up(&self, frame: &Frame<N>) -> Option<Plane<N>>;
#[allow(clippy::too_many_lines)]
#[must_use]
fn compute(
&self,
frame: &Frame<N>,
scope: &Scope<N>,
delta: &Delta<N>,
) -> Option<(Delta<N>, usize)> {
match delta {
Delta::Frame => None,
&Delta::First {
pitch: _,
yaw: _,
yaw_axis,
} => {
let eye = frame.eye();
let distance = frame.distance();
let pitch_axis = frame.pitch_axis();
let old_target = frame.target() - eye;
let mut min_delta = *delta;
let mut loops = 0;
loop {
let frame = min_delta.transform(frame);
let mut bound = false;
if let Some(plane) = self.target(&frame) {
bound = true;
let center = plane.project_point(&eye);
let height = distance - (center - eye).norm();
let radius = (height * (distance * (N::one() + N::one()) - height)).sqrt();
let new_target = (plane.project_point(frame.target()) - center)
.normalize()
.scale(radius);
let new_target = center + new_target;
let new_target = new_target - eye;
let pitch_plane = Plane::with_point(pitch_axis, &eye);
let old_pitch_target = pitch_plane.project_vector(&old_target);
let new_pitch_target = pitch_plane.project_vector(&new_target);
let pitch = pitch_plane.angle_between(&old_pitch_target, &new_pitch_target);
let pitch_rot = UnitQuaternion::from_axis_angle(&pitch_axis, pitch);
let old_target = pitch_rot * old_target;
let yaw_plane = Plane::with_point(yaw_axis, &eye);
let old_yaw_target = yaw_plane.project_vector(&old_target);
let new_yaw_target = yaw_plane.project_vector(&new_target);
let yaw = yaw_plane.angle_between(&old_yaw_target, &new_yaw_target);
#[allow(clippy::no_effect_underscore_binding)]
let _min_delta = Delta::First {
pitch,
yaw,
yaw_axis,
};
min_delta = Delta::Frame;
}
if bound {
if loops == self.loops() {
break;
}
loops += 1;
} else {
break;
}
}
(min_delta != *delta).then_some((min_delta, loops))
}
Delta::Track { vec: _ } => {
let old_frame = frame;
let old_target = frame.target();
let old_rot_inverse = frame.view().rotation.inverse();
let mut min_delta = *delta;
let mut loops = 0;
loop {
let frame = min_delta.transform(old_frame);
let mut bound = false;
if let Some(plane) = self.target(&frame) {
bound = true;
let new_target = plane.project_point(frame.target());
let vec = old_rot_inverse * (new_target - old_target);
min_delta = Delta::Track { vec };
}
let frame = min_delta.transform(old_frame);
if let Some(_plane) = self.up(&frame) {
bound = true;
min_delta = Delta::Frame;
}
if bound {
if loops == self.loops() {
break;
}
loops += 1;
} else {
break;
}
}
(min_delta != *delta).then_some((min_delta, loops))
}
&Delta::Orbit { rot: _, pos } => {
if pos != Point3::origin() {
return Some((Delta::Frame, 0));
}
let old_frame = frame;
let distance = frame.distance();
let target = frame.target();
let old_rot_inverse = frame.view().rotation.inverse();
let old_eye = old_rot_inverse * (frame.eye() - target);
let mut min_delta = *delta;
let mut loops = 0;
loop {
let mut bound = false;
let frame = min_delta.transform(old_frame);
if let Some(plane) = self.eye(&frame) {
bound = true;
let center = plane.project_point(target);
let height = distance - (center - target).norm();
let radius = (height * (distance * (N::one() + N::one()) - height)).sqrt();
let new_eye = (plane.project_point(&frame.eye()) - center)
.normalize()
.scale(radius);
let new_eye = center + new_eye;
let new_eye = old_rot_inverse * (new_eye - target);
let rot = UnitQuaternion::rotation_between(&old_eye, &new_eye)
.unwrap_or_default();
min_delta = Delta::Orbit { rot, pos };
}
let frame = min_delta.transform(old_frame);
if let Some(_plane) = self.up(&frame) {
bound = true;
min_delta = Delta::Frame;
}
if bound {
if loops == self.loops() {
break;
}
loops += 1;
} else {
break;
}
}
(min_delta != *delta).then_some((min_delta, loops))
}
Delta::Slide { vec: _ } => {
let old_frame = frame;
let old_target = frame.target();
let old_rot_inverse = frame.view().rotation.inverse();
let old_eye = frame.eye();
let mut min_delta = *delta;
let mut loops = 0;
loop {
let frame = min_delta.transform(old_frame);
let mut bound = false;
if let Some(plane) = self.target(&frame) {
bound = true;
let new_target = plane.project_point(frame.target());
let vec = old_rot_inverse * (new_target - old_target);
min_delta = Delta::Slide { vec };
}
let frame = min_delta.transform(old_frame);
if let Some(plane) = self.eye(&frame) {
bound = true;
let new_eye = plane.project_point(&frame.eye());
let vec = old_rot_inverse * (new_eye - old_eye);
min_delta = Delta::Slide { vec };
}
if bound {
if loops == self.loops() {
break;
}
loops += 1;
} else {
break;
}
}
(min_delta != *delta).then_some((min_delta, loops))
}
&Delta::Scale { mut rat, pos } => {
let old_zat = frame.distance();
let mut min_delta = *delta;
let mut loops = 0;
loop {
let frame = min_delta.transform(frame);
let mut bound = false;
if let Some(_plane) = self.eye(&frame) {
bound = true;
min_delta = Delta::Frame;
}
if scope.scale() {
let (znear, _zfar) = scope.clip_planes(N::zero());
let min_zat = -znear * (N::one() + N::default_epsilon().sqrt());
let new_zat = old_zat * rat;
if new_zat < min_zat {
bound = true;
rat = min_zat / old_zat;
min_delta = Delta::Scale { rat, pos };
}
}
if bound {
if loops == self.loops() {
break;
}
loops += 1;
} else {
break;
}
}
(min_delta != *delta).then_some((min_delta, loops))
}
}
}
}