190 lines
4.9 KiB
Rust
190 lines
4.9 KiB
Rust
use crate::components::{CodeType, Program, Snippet};
|
|
use crate::errors::Result;
|
|
use crate::graphics::AttaWithProgram;
|
|
|
|
use super::Transform;
|
|
|
|
use glow::HasContext;
|
|
use nalgebra::{Matrix4, Quaternion, Translation3, Unit, UnitQuaternion, Vector3};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TrackballModel {
|
|
rotation: UnitQuaternion<f32>,
|
|
count: usize,
|
|
model: Matrix4<f32>,
|
|
renorm_count: usize,
|
|
trackball_size: f32,
|
|
x: f32,
|
|
y: f32,
|
|
theta: f32,
|
|
phi: f32,
|
|
}
|
|
|
|
impl TrackballModel {
|
|
pub fn new(theta: f32, phi: f32) -> Self {
|
|
let mut trackball = Self {
|
|
rotation: UnitQuaternion::identity(),
|
|
count: 0,
|
|
model: Matrix4::identity(),
|
|
renorm_count: 97,
|
|
trackball_size: 0.8,
|
|
x: 0.0,
|
|
y: 0.0,
|
|
theta,
|
|
phi,
|
|
};
|
|
trackball.set_orientation(theta, phi);
|
|
trackball
|
|
}
|
|
|
|
fn drag_to(&mut self, x: f32, y: f32, dx: f32, dy: f32) {
|
|
let q = self.rotate(x, y, dx, dy);
|
|
self.rotation *= q;
|
|
self.count += 1;
|
|
if self.count > self.renorm_count {
|
|
self.rotation = UnitQuaternion::new_normalize(*self.rotation.clone());
|
|
self.count = 0;
|
|
}
|
|
self.model = self.rotation.to_homogeneous();
|
|
}
|
|
|
|
fn model(&self) -> &Matrix4<f32> {
|
|
&self.model
|
|
}
|
|
|
|
fn theta(&self) -> f32 {
|
|
self.theta
|
|
}
|
|
|
|
fn set_theta(&mut self, theta: f32) {
|
|
self.set_orientation(theta % 360.0, self.phi % 360.0);
|
|
}
|
|
|
|
fn phi(&self) -> f32 {
|
|
self.phi
|
|
}
|
|
|
|
fn set_phi(&mut self, phi: f32) {
|
|
self.set_orientation(self.theta % 360.0, phi % 360.0);
|
|
}
|
|
|
|
fn get_orientation(&self) -> (f32, f32) {
|
|
let q = self.rotation.quaternion();
|
|
let ax = (2.0 * (q.w * q.i + q.j * q.k) / (1.0 - 2.0 * (q.i * q.i + q.j * q.j))).atan()
|
|
* 180.0
|
|
/ std::f32::consts::PI;
|
|
let az = (2.0 * (q.w * q.k + q.i * q.j) / (1.0 - 2.0 * (q.j * q.j + q.k * q.k))).atan()
|
|
* 180.0
|
|
/ std::f32::consts::PI;
|
|
(-az, ax)
|
|
}
|
|
|
|
fn set_orientation(&mut self, theta: f32, phi: f32) {
|
|
self.theta = theta;
|
|
self.phi = phi;
|
|
|
|
let angle = self.theta * (std::f32::consts::PI / 180.0);
|
|
let sine = (0.5 * angle).sin();
|
|
let xrot =
|
|
UnitQuaternion::from_quaternion(Quaternion::new((0.5 * angle).cos(), sine, 0.0, 0.0));
|
|
|
|
let angle = self.phi * (std::f32::consts::PI / 180.0);
|
|
let sine = (0.5 * angle).sin();
|
|
let zrot =
|
|
UnitQuaternion::from_quaternion(Quaternion::new((0.5 * angle).cos(), 0.0, 0.0, sine));
|
|
|
|
self.rotation = xrot * zrot;
|
|
self.model = self.rotation.to_homogeneous();
|
|
}
|
|
|
|
fn project(&self, r: f32, x: f32, y: f32) -> f32 {
|
|
let d = (x * x + y * y).sqrt();
|
|
if d < r * 0.70710678118654752440 {
|
|
(r * r - d * d).sqrt()
|
|
} else {
|
|
let t = r / 1.41421356237309504880;
|
|
t * t / d
|
|
}
|
|
}
|
|
|
|
fn rotate(&self, x: f32, y: f32, dx: f32, dy: f32) -> UnitQuaternion<f32> {
|
|
if dx == 0.0 && dy == 0.0 {
|
|
return UnitQuaternion::identity();
|
|
}
|
|
|
|
let last = Vector3::new(x, y, self.project(self.trackball_size, x, y));
|
|
let new = Vector3::new(
|
|
x + dx,
|
|
y + dy,
|
|
self.project(self.trackball_size, x + dx, y + dy),
|
|
);
|
|
|
|
let a = new.cross(&last);
|
|
let d = last - new;
|
|
let t = d.norm() / (2.0 * self.trackball_size);
|
|
let t = t.clamp(-1.0, 1.0);
|
|
|
|
let phi = 2.0 * t.asin();
|
|
UnitQuaternion::from_axis_angle(&Unit::new_normalize(a), phi)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Trackball {
|
|
snippet: Snippet,
|
|
model: TrackballModel,
|
|
}
|
|
|
|
impl Trackball {
|
|
pub fn new() -> Result<Self> {
|
|
let snippets = Snippet::new(
|
|
"trackball",
|
|
CodeType::<&'static str>::Path("transform/trackball.glsl".into()),
|
|
false,
|
|
None,
|
|
)?;
|
|
|
|
let model = TrackballModel::new(45.0, 45.0);
|
|
|
|
Ok(Self {
|
|
snippet: snippets,
|
|
model,
|
|
})
|
|
}
|
|
|
|
pub fn on_mouse_drag(&mut self, x: f32, y: f32, dx: f32, dy: f32) {
|
|
self.model.drag_to(x, y, dx, dy);
|
|
}
|
|
|
|
pub fn model(&self) -> &Matrix4<f32> {
|
|
self.model.model()
|
|
}
|
|
}
|
|
|
|
impl Transform for Trackball {
|
|
fn snippet(&self) -> &Snippet {
|
|
&self.snippet
|
|
}
|
|
}
|
|
|
|
impl AttaWithProgram for Trackball {
|
|
fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result<()> {
|
|
unsafe {
|
|
let l = program.get_uniform_location(gl, "trackball_model");
|
|
gl.uniform_matrix_4_f32_slice(l.as_ref(), false, self.model.model().as_slice());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_trackball() {
|
|
// let trackball = Trackball::new().unwrap();
|
|
// println!("{}", trackball.snippet);
|
|
}
|
|
}
|