1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use nalgebra::{convert, Matrix4, Point2, RealField};

/// Scene wrt enclosing viewing frustum.
///
/// Implements [`Default`] and can be created with `Scene::default()`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Scene<N: RealField> {
	/// Fixed quantity wrt field of view.
	///
	/// Default is fixed vertical field of view of π/4.
	fov: Fixed<N>,
	/// Clip plane distances.
	///
	/// Near and far clip plane distances from either target or eye whether [`Self::oim`]. Default
	/// is `(1e-1, 1e+6)` measured from eye.
	zcp: (N, N),
	/// Object inspection mode.
	///
	/// Scales clip plane distances by measuring from target instead of eye. Default is `false`.
	oim: bool,
	/// Orthographic projection mode.
	///
	/// Computes scale-identical orthographic instead of perspective projection. Default is `false`.
	opm: bool,
}

impl<N: RealField> Default for Scene<N> {
	fn default() -> Self {
		Self {
			fov: Fixed::default(),
			zcp: (convert(1e-1), convert(1e+6)),
			oim: false,
			opm: false,
		}
	}
}

impl<N: RealField> Scene<N> {
	/// Fixed quantity wrt field of view, see [`Self::set_fov()`].
	pub fn fov(&self) -> Fixed<N> {
		self.fov
	}
	/// Sets fixed quantity wrt field of view.
	///
	/// Default is fixed vertical field of view of π/4.
	///
	/// ```
	/// use nalgebra::Point2;
	/// use trackball::Scene;
	///
	/// // Current screen size.
	/// let max = Point2::new(800, 600);
	/// // Default scene with fixed vertical field of view of π/4:
	/// //
	/// //   * Increasing width increases horizontal field of view (more can be seen).
	/// //   * Increasing height scales scene zooming in as vertical field of view is fixed.
	/// let mut scene = Scene::default();
	/// // Unfix vertical field of view by fixing current unit per pixel on focus plane at distance
	/// // from eye of one, that is effectively `upp` divided by `zat` to make it scale-independant:
	/// //
	/// //   * Increasing width increases horizontal field of view (more can be seen).
	/// //   * Increasing height increases vertical field of view (more can be seen).
	/// scene.set_fov(scene.fov().to_upp(&max.cast::<f32>()));
	/// ```
	pub fn set_fov(&mut self, fov: impl Into<Fixed<N>>) {
		self.fov = fov.into();
	}
	/// Clip plane distances from eye regardless of [`Self::scale()`] wrt to distance between eye
	/// and target.
	///
	/// Default is `(1e-1, 1e+6)` measured from eye.
	pub fn clip_planes(&self, zat: N) -> (N, N) {
		if self.oim {
			let (znear, zfar) = self.zcp;
			(zat - znear, zat + zfar)
		} else {
			self.zcp
		}
	}
	/// Sets clip plane distances from target or eye whether [`Self::scale()`].
	///
	/// Default is `(1e-1, 1e+6)` measured from eye.
	pub fn set_clip_planes(&mut self, znear: N, zfar: N) {
		self.zcp = (znear, zfar);
	}
	/// Object inspection mode.
	///
	/// Scales clip plane distances by measuring from target instead of eye. Default is `false`.
	pub fn scale(&self) -> bool {
		self.oim
	}
	/// Sets object inspection mode.
	///
	/// Scales clip plane distances by measuring from target instead of eye. Default is `false`.
	pub fn set_scale(&mut self, oim: bool) {
		self.oim = oim
	}
	/// Orthographic projection mode.
	///
	/// Computes scale-identical orthographic instead of perspective projection. Default is `false`.
	pub fn ortho(&self) -> bool {
		self.opm
	}
	/// Sets orthographic projection mode.
	///
	/// Computes scale-identical orthographic instead of perspective projection. Default is `false`.
	pub fn set_ortho(&mut self, opm: bool) {
		self.opm = opm
	}
	/// Projection transformation and unit per pixel on focus plane wrt distance between eye and
	/// target and maximum position in screen space.
	pub fn projection_and_upp(&self, zat: N, max: &Point2<N>) -> (Matrix4<N>, N) {
		let (znear, zfar) = self.clip_planes(zat);
		if self.opm {
			let (max, upp) = self.fov.max_and_upp(zat, max);
			let mat = Matrix4::new_orthographic(-max.x, max.x, -max.y, max.y, znear, zfar);
			(mat, upp)
		} else {
			let fov = self.fov.to_ver(max).into_inner();
			let (max, upp) = self.fov.max_and_upp(zat, max);
			let mat = Matrix4::new_perspective(max.x / max.y, fov, znear, zfar);
			(mat, upp)
		}
	}
}

/// Fixed quantity wrt field of view.
///
///   * Implements [`Default`] and can be created with `Fixed::default()` returning
///     `Fixed::Ver(N::frac_pi_4())`.
///   * Implements `From<N>` and can be created with `N::into()` returning `Fixed::Ver()`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Fixed<N: RealField> {
	/// Fixed horizontal field of view aka Vert- scaling.
	Hor(N),
	/// Fixed vertical field of view aka Hor+ scaling.
	Ver(N),
	/// Fixed unit per pixel on focus plane at distance from eye of one aka Pixel-based scaling.
	Upp(N),
}

impl<N: RealField> Default for Fixed<N> {
	fn default() -> Self {
		N::frac_pi_4().into()
	}
}

impl<N: RealField> From<N> for Fixed<N> {
	fn from(fov: N) -> Self {
		Self::Ver(fov)
	}
}

impl<N: RealField> Fixed<N> {
	/// Converts to fixed horizontal field of view wrt maximum position in screen space.
	pub fn to_hor(self, max: &Point2<N>) -> Self {
		let two = N::one() + N::one();
		Self::Hor(match self {
			Self::Hor(fov) => fov,
			Self::Ver(fov) => (max.x / max.y * (fov / two).tan()).atan() * two,
			Self::Upp(upp) => (max.x / two * upp).atan() * two,
		})
	}
	/// Converts to fixed vertical field of view wrt maximum position in screen space.
	pub fn to_ver(self, max: &Point2<N>) -> Self {
		let two = N::one() + N::one();
		Self::Ver(match self {
			Self::Hor(fov) => (max.y / max.x * (fov / two).tan()).atan() * two,
			Self::Ver(fov) => fov,
			Self::Upp(upp) => (max.y / two * upp).atan() * two,
		})
	}
	/// Converts to fixed unit per pixel on focus plane at distance from eye of one wrt maximum
	/// position in screen space.
	pub fn to_upp(self, max: &Point2<N>) -> Self {
		let two = N::one() + N::one();
		Self::Upp(match self {
			Self::Hor(fov) => (fov / two).tan() * two / max.x,
			Self::Ver(fov) => (fov / two).tan() * two / max.y,
			Self::Upp(upp) => upp,
		})
	}
	/// Maximum position in camera space and unit per pixel on focus plane wrt distance between
	/// eye and target and maximum position in screen space.
	pub fn max_and_upp(&self, zat: N, max: &Point2<N>) -> (Point2<N>, N) {
		let two = N::one() + N::one();
		match *self {
			Self::Hor(fov) => {
				let x = zat * (fov / two).tan();
				let y = max.y / max.x * x;
				(Point2::new(x, y), x * two / max.x)
			}
			Self::Ver(fov) => {
				let y = zat * (fov / two).tan();
				let x = max.x / max.y * y;
				(Point2::new(x, y), y * two / max.y)
			}
			Self::Upp(upp) => {
				let upp = upp * zat;
				(max / two * upp, upp)
			}
		}
	}
	/// Underlying quantity.
	pub fn into_inner(self) -> N {
		match self {
			Self::Hor(fov) => fov,
			Self::Ver(fov) => fov,
			Self::Upp(upp) => upp,
		}
	}
}