pub use crate::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct UvCoordinate {
pub u: f32,
pub v: f32,
}
impl From<(f32, f32)> for UvCoordinate {
fn from(value: (f32, f32)) -> Self {
Self {
u: value.0,
v: value.1,
}
}
}
impl From<UvCoordinate> for (f32, f32) {
fn from(value: UvCoordinate) -> Self {
(value.u, value.v)
}
}
impl From<Vec2> for UvCoordinate {
fn from(value: Vec2) -> Self {
Self {
u: value.x,
v: value.y,
}
}
}
impl From<UvCoordinate> for Vec2 {
fn from(value: UvCoordinate) -> Self {
Self {
x: value.u,
y: value.v,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PixelPoint {
pub x: f32,
pub y: f32,
}
impl From<(f32, f32)> for PixelPoint {
fn from(value: (f32, f32)) -> Self {
Self {
x: value.0,
y: value.1,
}
}
}
impl From<PixelPoint> for (f32, f32) {
fn from(value: PixelPoint) -> Self {
(value.x, value.y)
}
}
impl From<Vec2> for PixelPoint {
fn from(value: Vec2) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}
impl From<PixelPoint> for Vec2 {
fn from(value: PixelPoint) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Viewport {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
impl Viewport {
pub fn new_at_origo(width: u32, height: u32) -> Self {
Self {
x: 0,
y: 0,
width,
height,
}
}
pub fn aspect(&self) -> f32 {
self.width as f32 / self.height as f32
}
pub fn intersection(&self, other: impl Into<Self>) -> Self {
let other = other.into();
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let width =
(self.x + self.width as i32 - x).clamp(0, other.x + other.width as i32 - x) as u32;
let height =
(self.y + self.height as i32 - y).clamp(0, other.y + other.height as i32 - y) as u32;
Self {
x,
y,
width,
height,
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ProjectionType {
Orthographic {
height: f32,
},
Perspective {
field_of_view_y: Radians,
},
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Camera {
viewport: Viewport,
projection_type: ProjectionType,
z_near: f32,
z_far: f32,
position: Vec3,
target: Vec3,
up: Vec3,
view: Mat4,
projection: Mat4,
screen2ray: Mat4,
frustrum: [Vec4; 6],
}
impl Camera {
pub fn new_orthographic(
viewport: Viewport,
position: Vec3,
target: Vec3,
up: Vec3,
height: f32,
z_near: f32,
z_far: f32,
) -> Self {
let mut camera = Camera::new(viewport);
camera.set_view(position, target, up);
camera.set_orthographic_projection(height, z_near, z_far);
camera
}
pub fn new_perspective(
viewport: Viewport,
position: Vec3,
target: Vec3,
up: Vec3,
field_of_view_y: impl Into<Radians>,
z_near: f32,
z_far: f32,
) -> Self {
let mut camera = Camera::new(viewport);
camera.set_view(position, target, up);
camera.set_perspective_projection(field_of_view_y, z_near, z_far);
camera
}
pub fn set_perspective_projection(
&mut self,
field_of_view_y: impl Into<Radians>,
z_near: f32,
z_far: f32,
) {
assert!(
z_near >= 0.0 || z_near < z_far,
"Wrong perspective camera parameters"
);
self.z_near = z_near;
self.z_far = z_far;
let field_of_view_y = field_of_view_y.into();
self.projection_type = ProjectionType::Perspective { field_of_view_y };
self.projection =
cgmath::perspective(field_of_view_y, self.viewport.aspect(), z_near, z_far);
self.update_screen2ray();
self.update_frustrum();
}
pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
assert!(z_near < z_far, "Wrong orthographic camera parameters");
self.z_near = z_near;
self.z_far = z_far;
let width = height * self.viewport.aspect();
self.projection_type = ProjectionType::Orthographic { height };
self.projection = cgmath::ortho(
-0.5 * width,
0.5 * width,
-0.5 * height,
0.5 * height,
z_near,
z_far,
);
self.update_screen2ray();
self.update_frustrum();
}
pub fn set_viewport(&mut self, viewport: Viewport) -> bool {
if self.viewport != viewport {
self.viewport = viewport;
match self.projection_type {
ProjectionType::Orthographic { height } => {
self.set_orthographic_projection(height, self.z_near, self.z_far);
}
ProjectionType::Perspective { field_of_view_y } => {
self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
}
}
true
} else {
false
}
}
pub fn set_view(&mut self, position: Vec3, target: Vec3, up: Vec3) {
self.position = position;
self.target = target;
self.up = up.normalize();
self.view = Mat4::look_at_rh(
Point3::from_vec(self.position),
Point3::from_vec(self.target),
self.up,
);
self.update_screen2ray();
self.update_frustrum();
}
pub fn mirror_in_xz_plane(&mut self) {
self.view[1][0] = -self.view[1][0];
self.view[1][1] = -self.view[1][1];
self.view[1][2] = -self.view[1][2];
self.update_screen2ray();
self.update_frustrum();
}
pub fn in_frustum(&self, aabb: &AxisAlignedBoundingBox) -> bool {
if aabb.is_infinite() {
return true;
}
for i in 0..6 {
let mut out = 0;
if self.frustrum[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
out += 1
};
if self.frustrum[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
out += 1
};
if out == 8 {
return false;
}
}
true
}
pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } => {
let coords = self.uv_coordinates_at_pixel(pixel);
self.position_at_uv_coordinates(coords)
}
ProjectionType::Perspective { .. } => *self.position(),
}
}
pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } => {
let coords = coords.into();
let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, -1.0, 1.);
(self.screen2ray * screen_pos).truncate()
}
ProjectionType::Perspective { .. } => *self.position(),
}
}
pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } => self.view_direction(),
ProjectionType::Perspective { .. } => {
let coords = self.uv_coordinates_at_pixel(pixel);
self.view_direction_at_uv_coordinates(coords)
}
}
}
pub fn view_direction_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
match self.projection_type() {
ProjectionType::Orthographic { .. } => self.view_direction(),
ProjectionType::Perspective { .. } => {
let coords = coords.into();
let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
(self.screen2ray * screen_pos).truncate().normalize()
}
}
}
pub fn uv_coordinates_at_pixel(&self, pixel: impl Into<PixelPoint>) -> UvCoordinate {
let pixel = pixel.into();
(
(pixel.x - self.viewport.x as f32) / self.viewport.width as f32,
(pixel.y - self.viewport.y as f32) / self.viewport.height as f32,
)
.into()
}
pub fn uv_coordinates_at_position(&self, position: Vec3) -> UvCoordinate {
let proj = self.projection() * self.view() * position.extend(1.0);
(
0.5 * (proj.x / proj.w.abs() + 1.0),
0.5 * (proj.y / proj.w.abs() + 1.0),
)
.into()
}
pub fn pixel_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> PixelPoint {
let coords = coords.into();
(
coords.u * self.viewport.width as f32 + self.viewport.x as f32,
coords.v * self.viewport.height as f32 + self.viewport.y as f32,
)
.into()
}
pub fn pixel_at_position(&self, position: Vec3) -> PixelPoint {
self.pixel_at_uv_coordinates(self.uv_coordinates_at_position(position))
}
pub fn projection_type(&self) -> &ProjectionType {
&self.projection_type
}
pub fn view(&self) -> &Mat4 {
&self.view
}
pub fn projection(&self) -> &Mat4 {
&self.projection
}
pub fn viewport(&self) -> Viewport {
self.viewport
}
pub fn z_near(&self) -> f32 {
self.z_near
}
pub fn z_far(&self) -> f32 {
self.z_far
}
pub fn position(&self) -> &Vec3 {
&self.position
}
pub fn target(&self) -> &Vec3 {
&self.target
}
pub fn up(&self) -> &Vec3 {
&self.up
}
pub fn view_direction(&self) -> Vec3 {
(self.target - self.position).normalize()
}
pub fn right_direction(&self) -> Vec3 {
self.view_direction().cross(self.up)
}
fn new(viewport: Viewport) -> Camera {
Camera {
viewport,
projection_type: ProjectionType::Orthographic { height: 1.0 },
z_near: 0.0,
z_far: 0.0,
frustrum: [vec4(0.0, 0.0, 0.0, 0.0); 6],
position: vec3(0.0, 0.0, 5.0),
target: vec3(0.0, 0.0, 0.0),
up: vec3(0.0, 1.0, 0.0),
view: Mat4::identity(),
projection: Mat4::identity(),
screen2ray: Mat4::identity(),
}
}
fn update_screen2ray(&mut self) {
let mut v = self.view;
if let ProjectionType::Perspective { .. } = self.projection_type {
v[3] = vec4(0.0, 0.0, 0.0, 1.0);
}
self.screen2ray = (self.projection * v).invert().unwrap();
}
fn update_frustrum(&mut self) {
let m = self.projection * self.view;
self.frustrum = [
vec4(m.x.w + m.x.x, m.y.w + m.y.x, m.z.w + m.z.x, m.w.w + m.w.x),
vec4(m.x.w - m.x.x, m.y.w - m.y.x, m.z.w - m.z.x, m.w.w - m.w.x),
vec4(m.x.w + m.x.y, m.y.w + m.y.y, m.z.w + m.z.y, m.w.w + m.w.y),
vec4(m.x.w - m.x.y, m.y.w - m.y.y, m.z.w - m.z.y, m.w.w - m.w.y),
vec4(m.x.w + m.x.z, m.y.w + m.y.z, m.z.w + m.z.z, m.w.w + m.w.z),
vec4(m.x.w - m.x.z, m.y.w - m.y.z, m.z.w - m.z.z, m.w.w - m.w.z),
];
}
pub fn translate(&mut self, change: &Vec3) {
self.set_view(self.position + change, self.target + change, self.up);
}
pub fn pitch(&mut self, delta: impl Into<Radians>) {
let target = (self.view.invert().unwrap()
* Mat4::from_angle_x(delta)
* self.view
* self.target.extend(1.0))
.truncate();
if (target - self.position).normalize().dot(self.up).abs() < 0.999 {
self.set_view(self.position, target, self.up);
}
}
pub fn yaw(&mut self, delta: impl Into<Radians>) {
let target = (self.view.invert().unwrap()
* Mat4::from_angle_y(delta)
* self.view
* self.target.extend(1.0))
.truncate();
self.set_view(self.position, target, self.up);
}
pub fn roll(&mut self, delta: impl Into<Radians>) {
let up = (self.view.invert().unwrap()
* Mat4::from_angle_z(delta)
* self.view
* (self.up + self.position).extend(1.0))
.truncate()
- self.position;
self.set_view(self.position, self.target, up.normalize());
}
pub fn rotate_around(&mut self, point: &Vec3, x: f32, y: f32) {
let dir = (point - self.position()).normalize();
let right = dir.cross(self.up);
let up = right.cross(dir);
let new_dir = (point - self.position() + right * x - up * y).normalize();
let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
let new_position = (rotation * (self.position() - point).extend(1.0)).truncate() + point;
let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
self.set_view(new_position, new_target, up);
}
pub fn rotate_around_with_fixed_up(&mut self, point: &Vec3, x: f32, y: f32) {
let dir = (point - self.position()).normalize();
let right = dir.cross(self.up);
let up = right.cross(dir);
let new_dir = (point - self.position() + right * x - up * y).normalize();
let up = self.up;
if new_dir.dot(up).abs() < 0.999 {
let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
let new_position =
(rotation * (self.position() - point).extend(1.0)).truncate() + point;
let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
self.set_view(new_position, new_target, up);
}
}
pub fn zoom_towards(
&mut self,
point: &Vec3,
delta: f32,
minimum_distance: f32,
maximum_distance: f32,
) {
let minimum_distance = minimum_distance.max(0.0);
assert!(
minimum_distance < maximum_distance,
"minimum_distance larger than maximum_distance"
);
let position = *self.position();
let distance = point.distance(position);
let direction = (point - position).normalize();
let target = *self.target();
let up = self.up;
let new_distance = (distance - delta).clamp(minimum_distance, maximum_distance);
let new_position = point - direction * new_distance;
self.set_view(new_position, new_position + (target - position), up);
if let ProjectionType::Orthographic { height } = self.projection_type() {
let h = new_distance * height / distance;
let z_near = self.z_near();
let z_far = self.z_far();
self.set_orthographic_projection(h, z_near, z_far);
}
}
}