use std::f32::consts::PI;
use wrflib::*;
const EPSILON: f32 = 0.0001;
fn look_at(eye: Vec3, at: Vec3, up: Vec3) -> Mat4 {
let forward = (eye - at).normalize();
let left = Vec3::cross(up, forward).normalize();
let up = Vec3::cross(forward, left);
let mut matrix = Mat4::identity();
matrix.v[0] = left.x;
matrix.v[4] = left.y;
matrix.v[8] = left.z;
matrix.v[1] = up.x;
matrix.v[5] = up.y;
matrix.v[9] = up.z;
matrix.v[2] = forward.x;
matrix.v[6] = forward.y;
matrix.v[10] = forward.z;
matrix.v[12] = -left.dot(eye);
matrix.v[13] = -up.dot(eye);
matrix.v[14] = -forward.dot(eye);
matrix
}
#[derive(Clone, Copy, Debug)]
pub struct SphericalAngles {
pub phi: f32,
pub theta: f32,
pub radius: f32,
}
pub enum Coordinates {
Cartesian(Vec3),
Spherical(SphericalAngles),
}
fn cartesian_to_spherical(position: Vec3) -> SphericalAngles {
let radius = position.length();
SphericalAngles { phi: position.z.atan2(position.y), theta: (position.x / radius).asin(), radius }
}
pub struct Viewport3DProps {
pub initial_camera_position: Coordinates,
pub panning_enabled: bool,
pub camera_target: Vec3,
pub vertical_panning_enabled: bool,
}
impl Viewport3DProps {
pub const DEFAULT: Self = Self {
initial_camera_position: Coordinates::Spherical(SphericalAngles { phi: EPSILON, theta: -PI / 2., radius: 50. }),
camera_target: Vec3::all(0.),
panning_enabled: true,
vertical_panning_enabled: true,
};
}
impl Default for Viewport3DProps {
fn default() -> Self {
Self::DEFAULT
}
}
pub struct Viewport3D {
component_base: ComponentBase,
pass: Pass,
clear_color: Vec4,
pub color_texture: Texture,
depth_texture: Texture,
view_2d: View,
view_3d: View,
pub measured_size: Vec2,
camera_position: SphericalAngles,
camera_position_start: Option<SphericalAngles>,
camera_target_offset: Vec3,
camera_target_offset_start: Option<Vec3>,
props: Viewport3DProps,
has_read_props: bool,
}
impl Default for Viewport3D {
fn default() -> Self {
Self {
component_base: Default::default(),
camera_position: SphericalAngles { phi: EPSILON, theta: -PI / 2., radius: 50. },
measured_size: Default::default(),
camera_position_start: Default::default(),
camera_target_offset: Default::default(),
camera_target_offset_start: Default::default(),
pass: Default::default(),
clear_color: Default::default(),
color_texture: Default::default(),
depth_texture: Default::default(),
view_3d: Default::default(),
view_2d: Default::default(),
has_read_props: Default::default(),
props: Default::default(),
}
}
}
impl Viewport3D {
pub fn handle(&mut self, cx: &mut Cx, event: &mut Event) -> Option<PassMatrixMode> {
match event.hits(cx, &self.component_base, HitOpt::default()) {
Event::FingerHover(_fe) => {
}
Event::FingerDown(fe) => {
if self.props.panning_enabled && fe.button == MouseButton::Left {
self.camera_target_offset_start = Some(self.camera_target_offset);
} else if fe.button == MouseButton::Right {
self.camera_position_start = Some(self.camera_position);
}
}
Event::FingerUp(_fe) => {
self.camera_position_start = None;
self.camera_target_offset_start = None;
}
Event::FingerScroll(fe) => {
let min_distance = 1.0; let max_distance = 900.; let zoom_speed = (self.camera_position.radius * (PI / 4.) / max_distance).sin().abs() / 2.0;
self.camera_position.radius =
(self.camera_position.radius + fe.scroll.y * zoom_speed).max(min_distance).min(max_distance);
return Some(self.pass_set_matrix_mode(cx));
}
Event::FingerMove(fe) => {
if let Some(SphericalAngles { phi, theta, radius }) = self.camera_position_start {
let rotate_speed = 1. / 175.;
self.camera_position = SphericalAngles {
theta: (theta - (fe.abs.x - fe.abs_start.x) * rotate_speed) % (PI * 2.),
phi: (phi - (fe.abs.y - fe.abs_start.y) * rotate_speed).clamp(EPSILON, PI - EPSILON),
radius,
};
return Some(self.pass_set_matrix_mode(cx));
} else if let Some(camera_target_offset_start) = self.camera_target_offset_start {
let pan_speed = 0.8;
let mouse_offset = (fe.rel - fe.rel_start) / self.measured_size.y * self.camera_position.radius * pan_speed;
let vertical_offset =
if self.props.vertical_panning_enabled { self.camera_position.phi.to_degrees() } else { 0. };
self.camera_target_offset = Mat4::rotation(vertical_offset, self.camera_position.theta.to_degrees(), 0.)
.transform_vec4(vec4(-mouse_offset.x, 0., -mouse_offset.y, 1.0))
.to_vec3()
+ camera_target_offset_start;
return Some(self.pass_set_matrix_mode(cx));
}
}
_ => (),
}
None
}
fn get_matrix_projection(&self) -> PassMatrixMode {
let SphericalAngles { phi, theta, radius } = self.camera_position;
let eye = radius * vec3(phi.sin() * theta.sin(), phi.cos(), phi.sin() * theta.cos());
PassMatrixMode::Projection {
fov_y: 40.0,
near: 0.1,
far: 1000.0,
cam: look_at(
eye + self.props.camera_target + self.camera_target_offset,
self.props.camera_target + self.camera_target_offset,
vec3(0., 1., 0.),
),
}
}
fn pass_set_matrix_mode(&mut self, cx: &mut Cx) -> PassMatrixMode {
let matrix_mode = self.get_matrix_projection();
self.pass.set_matrix_mode(cx, matrix_mode.clone());
matrix_mode
}
#[must_use]
pub fn skip_draw(&mut self, cx: &mut Cx) -> bool {
if self.measured_size != vec2(cx.get_width_total(), cx.get_height_total()) {
return false;
}
self.draw_viewport_2d(cx);
true
}
pub fn begin_draw(&mut self, cx: &mut Cx, props: Viewport3DProps) {
if !self.has_read_props {
self.camera_position = match props.initial_camera_position {
Coordinates::Cartesian(cartesian) => cartesian_to_spherical(cartesian),
Coordinates::Spherical(spherical) => spherical,
};
self.has_read_props = true;
}
self.props = props;
self.draw_viewport_2d(cx);
self.pass.begin_pass_without_textures(cx);
self.pass.set_size(cx, self.measured_size);
let color_texture_handle = self.color_texture.get_color(cx);
self.pass.add_color_texture(cx, color_texture_handle, ClearColor::ClearWith(self.clear_color));
let depth_texture_handle = self.depth_texture.get_depth(cx);
self.pass.set_depth_texture(cx, depth_texture_handle, ClearDepth::ClearWith(1.0));
self.view_3d.begin_view(cx, LayoutSize::FILL);
}
pub fn end_draw(&mut self, cx: &mut Cx) -> PassMatrixMode {
let matrix_mode = self.pass_set_matrix_mode(cx);
self.view_3d.end_view(cx);
self.pass.end_pass(cx);
matrix_mode
}
fn draw_viewport_2d(&mut self, cx: &mut Cx) {
self.view_2d.begin_view(cx, LayoutSize::FILL);
self.measured_size = vec2(cx.get_width_total(), cx.get_height_total());
let color_texture_handle = self.color_texture.get_color(cx);
let area = ImageIns::draw(cx, Rect { pos: cx.get_box_origin(), size: self.measured_size }, color_texture_handle);
self.component_base.register_component_area(cx, area);
self.view_2d.end_view(cx);
}
}