use crate::settings::keys::KeyBindings;
use crate::{settings::camera::CameraSettings, utils::built_in_skybox, SceneCameraSettings};
use fyrox::{
core::{
algebra::{Matrix4, Point3, UnitQuaternion, Vector2, Vector3},
math::{plane::Plane, ray::Ray, Matrix4Ext, TriangleDefinition, Vector3Ext},
pool::Handle,
},
gui::message::{KeyCode, MouseButton},
scene::{
base::BaseBuilder,
camera::{Camera, CameraBuilder, Exposure, Projection},
graph::Graph,
mesh::{
buffer::{VertexAttributeUsage, VertexReadTrait},
surface::SurfaceData,
Mesh,
},
node::Node,
pivot::PivotBuilder,
sound::listener::ListenerBuilder,
transform::TransformBuilder,
},
};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
pub const DEFAULT_Z_OFFSET: f32 = -3.0;
pub struct CameraController {
pub pivot: Handle<Node>,
pub camera: Handle<Node>,
pub yaw: f32,
pub pitch: f32,
rotate: bool,
drag_side: f32,
drag_up: f32,
drag: bool,
move_left: bool,
move_right: bool,
move_forward: bool,
move_backward: bool,
move_up: bool,
move_down: bool,
speed_factor: f32,
stack: Vec<Handle<Node>>,
editor_context: PickContext,
scene_context: PickContext,
}
#[derive(Clone)]
pub struct CameraPickResult {
pub position: Vector3<f32>,
pub node: Handle<Node>,
pub toi: f32,
}
#[derive(Default)]
struct PickContext {
pick_list: Vec<CameraPickResult>,
pick_index: usize,
old_selection_hash: u64,
old_cursor_pos: Vector2<f32>,
}
pub struct PickingOptions<'a, F>
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
pub cursor_pos: Vector2<f32>,
pub graph: &'a Graph,
pub editor_objects_root: Handle<Node>,
pub screen_size: Vector2<f32>,
pub editor_only: bool,
pub filter: F,
pub ignore_back_faces: bool,
pub use_picking_loop: bool,
pub only_meshes: bool,
}
impl CameraController {
pub fn new(
graph: &mut Graph,
root: Handle<Node>,
settings: Option<&SceneCameraSettings>,
) -> Self {
let settings = settings.cloned().unwrap_or_default();
let camera;
let pivot = PivotBuilder::new(
BaseBuilder::new()
.with_children(&[{
camera = CameraBuilder::new(
BaseBuilder::new()
.with_children(&[ListenerBuilder::new(BaseBuilder::new()).build(graph)])
.with_name("EditorCamera"),
)
.with_exposure(Exposure::Manual(std::f32::consts::E))
.with_skybox(built_in_skybox())
.with_z_far(512.0)
.build(graph);
camera
}])
.with_name("EditorCameraPivot")
.with_local_transform(
TransformBuilder::new()
.with_local_position(settings.position)
.build(),
),
)
.build(graph);
graph.link_nodes(pivot, root);
Self {
pivot,
camera,
yaw: settings.yaw,
pitch: settings.pitch,
rotate: false,
drag_side: 0.0,
drag_up: 0.0,
drag: false,
move_left: false,
move_right: false,
move_forward: false,
move_backward: false,
move_up: false,
move_down: false,
speed_factor: 1.0,
stack: Default::default(),
editor_context: Default::default(),
scene_context: Default::default(),
}
}
pub fn set_projection(&self, graph: &mut Graph, projection: Projection) {
graph[self.camera]
.as_camera_mut()
.set_projection(projection);
}
pub fn on_mouse_move(&mut self, delta: Vector2<f32>, settings: &CameraSettings) {
if self.rotate {
self.yaw -= delta.x * 0.01;
self.pitch += delta.y * 0.01;
if self.pitch > 90.0f32.to_radians() {
self.pitch = 90.0f32.to_radians();
}
if self.pitch < -90.0f32.to_radians() {
self.pitch = -90.0f32.to_radians();
}
}
if self.drag {
let sign = if settings.invert_dragging { 1.0 } else { -1.0 };
self.drag_side += sign * delta.x * settings.drag_speed;
self.drag_up += sign * delta.y * settings.drag_speed;
}
}
pub fn on_mouse_wheel(&mut self, delta: f32, graph: &mut Graph) {
let camera = graph[self.camera].as_camera_mut();
match *camera.projection_mut() {
Projection::Perspective(_) => {
let look = camera.global_transform().look();
graph[self.pivot]
.local_transform_mut()
.offset(look.scale(delta));
}
Projection::Orthographic(ref mut ortho) => {
ortho.vertical_size = (ortho.vertical_size - delta).max(f32::EPSILON);
}
}
}
pub fn on_mouse_button_up(&mut self, button: MouseButton) {
match button {
MouseButton::Right => {
self.rotate = false;
}
MouseButton::Middle => {
self.drag = false;
}
_ => (),
}
}
pub fn on_mouse_button_down(&mut self, button: MouseButton) {
match button {
MouseButton::Right => {
self.rotate = true;
}
MouseButton::Middle => {
self.drag = true;
}
_ => (),
}
}
#[must_use]
pub fn on_key_up(&mut self, key_bindings: &KeyBindings, key: KeyCode) -> bool {
if key_bindings.move_forward == key {
self.move_forward = false;
true
} else if key_bindings.move_back == key {
self.move_backward = false;
true
} else if key_bindings.move_left == key {
self.move_left = false;
true
} else if key_bindings.move_right == key {
self.move_right = false;
true
} else if key_bindings.move_up == key {
self.move_up = false;
true
} else if key_bindings.move_down == key {
self.move_down = false;
true
} else if key_bindings.slow_down == key || key_bindings.speed_up == key {
self.speed_factor = 1.0;
true
} else {
false
}
}
#[must_use]
pub fn on_key_down(&mut self, key_bindings: &KeyBindings, key: KeyCode) -> bool {
if !self.rotate || self.drag {
return false;
}
if key_bindings.move_forward == key {
self.move_forward = true;
true
} else if key_bindings.move_back == key {
self.move_backward = true;
true
} else if key_bindings.move_left == key {
self.move_left = true;
true
} else if key_bindings.move_right == key {
self.move_right = true;
true
} else if key_bindings.move_up == key {
self.move_up = true;
true
} else if key_bindings.move_down == key {
self.move_down = true;
true
} else if key_bindings.speed_up == key {
self.speed_factor = 2.0;
true
} else if key_bindings.slow_down == key {
self.speed_factor = 0.25;
true
} else {
false
}
}
pub fn position(&self, graph: &Graph) -> Vector3<f32> {
graph[self.pivot].global_position()
}
pub fn update(&mut self, graph: &mut Graph, settings: &CameraSettings, dt: f32) {
let camera = graph[self.camera].as_camera_mut();
match camera.projection_value() {
Projection::Perspective(_) => {
let global_transform = camera.global_transform();
let look = global_transform.look();
let side = global_transform.side();
let up = global_transform.up();
let mut move_vec = Vector3::default();
if self.rotate {
if self.move_forward {
move_vec += look;
}
if self.move_backward {
move_vec -= look;
}
if self.move_left {
move_vec += side;
}
if self.move_right {
move_vec -= side;
}
if self.move_up {
move_vec += up;
}
if self.move_down {
move_vec -= up;
}
}
if let Some(v) = move_vec.try_normalize(std::f32::EPSILON) {
move_vec = v.scale(self.speed_factor * settings.speed * dt);
}
move_vec += side * self.drag_side;
move_vec.y += self.drag_up;
camera
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::x_axis(),
self.pitch,
));
graph[self.pivot]
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::y_axis(),
self.yaw,
))
.offset(move_vec);
}
Projection::Orthographic(_) => {
let mut move_vec = Vector2::<f32>::default();
if self.rotate {
if self.move_left {
move_vec.x += 1.0;
}
if self.move_right {
move_vec.x -= 1.0;
}
if self.move_forward {
move_vec.y += 1.0;
}
if self.move_backward {
move_vec.y -= 1.0;
}
}
move_vec.x += self.drag_side;
move_vec.y += self.drag_up;
if let Some(v) = move_vec.try_normalize(f32::EPSILON) {
move_vec = v.scale(self.speed_factor * settings.speed * dt);
}
camera
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 0.0));
let local_transform = graph[self.pivot].local_transform_mut();
let mut new_position = **local_transform.position();
new_position.z = DEFAULT_Z_OFFSET;
new_position.x += move_vec.x;
new_position.y += move_vec.y;
local_transform
.set_rotation(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), 0.0))
.set_position(new_position);
}
}
self.drag_side = 0.0;
self.drag_up = 0.0;
}
pub fn pick<F>(&mut self, options: PickingOptions<'_, F>) -> Option<CameraPickResult>
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
let PickingOptions {
cursor_pos,
graph,
editor_objects_root,
screen_size,
editor_only,
mut filter,
ignore_back_faces,
use_picking_loop,
only_meshes,
} = options;
if let Some(camera) = graph[self.camera].cast::<Camera>() {
let ray = camera.make_ray(cursor_pos, screen_size);
self.stack.clear();
let context = if editor_only {
self.stack.push(editor_objects_root);
&mut self.editor_context
} else {
self.stack.push(graph.get_root());
&mut self.scene_context
};
context.pick_list.clear();
while let Some(handle) = self.stack.pop() {
if !editor_only && handle == editor_objects_root {
continue;
}
let node = &graph[handle];
self.stack.extend_from_slice(node.children());
if !node.global_visibility() || !filter(handle, node) {
continue;
}
if handle != graph.get_root() {
let object_space_ray =
ray.transform(node.global_transform().try_inverse().unwrap_or_default());
let aabb = node.local_bounding_box();
if let Some(points) = object_space_ray.aabb_intersection_points(&aabb) {
if has_hull(node) {
if let Some((closest_distance, position)) =
precise_ray_test(node, &ray, ignore_back_faces)
{
context.pick_list.push(CameraPickResult {
position,
node: handle,
toi: closest_distance,
});
}
} else if !only_meshes {
let da = points[0].metric_distance(&object_space_ray.origin);
let db = points[1].metric_distance(&object_space_ray.origin);
let closest_distance = da.min(db);
context.pick_list.push(CameraPickResult {
position: transform_vertex(
if da < db { points[0] } else { points[1] },
&node.global_transform(),
),
node: handle,
toi: closest_distance,
});
}
}
}
}
context
.pick_list
.sort_by(|a, b| a.toi.partial_cmp(&b.toi).unwrap());
if use_picking_loop {
let mut hasher = DefaultHasher::new();
for result in context.pick_list.iter() {
result.node.hash(&mut hasher);
}
let selection_hash = hasher.finish();
if selection_hash == context.old_selection_hash
&& cursor_pos == context.old_cursor_pos
{
context.pick_index += 1;
if context.pick_index >= context.pick_list.len() {
context.pick_index = 0;
}
} else {
context.pick_index = 0;
}
context.old_selection_hash = selection_hash;
} else {
context.pick_index = 0;
}
context.old_cursor_pos = cursor_pos;
if !context.pick_list.is_empty() {
if let Some(result) = context.pick_list.get(context.pick_index) {
return Some(result.clone());
}
}
}
None
}
pub fn pick_on_plane(
&self,
plane: Plane,
graph: &Graph,
mouse_position: Vector2<f32>,
viewport_size: Vector2<f32>,
transform: Matrix4<f32>,
) -> Option<Vector3<f32>> {
graph[self.camera]
.as_camera()
.make_ray(mouse_position, viewport_size)
.transform(transform)
.plane_intersection_point(&plane)
}
}
fn read_vertex_position(data: &SurfaceData, i: u32) -> Option<Vector3<f32>> {
data.vertex_buffer
.get(i as usize)
.and_then(|v| v.read_3_f32(VertexAttributeUsage::Position).ok())
}
fn transform_vertex(vertex: Vector3<f32>, transform: &Matrix4<f32>) -> Vector3<f32> {
transform.transform_point(&Point3::from(vertex)).coords
}
fn read_triangle(
data: &SurfaceData,
triangle: &TriangleDefinition,
transform: &Matrix4<f32>,
) -> Option<[Vector3<f32>; 3]> {
let a = transform_vertex(read_vertex_position(data, triangle[0])?, transform);
let b = transform_vertex(read_vertex_position(data, triangle[1])?, transform);
let c = transform_vertex(read_vertex_position(data, triangle[2])?, transform);
Some([a, b, c])
}
fn has_hull(node: &Node) -> bool {
node.query_component_ref::<Mesh>().is_some()
}
fn precise_ray_test(
node: &Node,
ray: &Ray,
ignore_back_faces: bool,
) -> Option<(f32, Vector3<f32>)> {
let mut closest_distance = f32::MAX;
let mut closest_point = None;
if let Some(mesh) = node.query_component_ref::<Mesh>() {
let transform = mesh.global_transform();
for surface in mesh.surfaces().iter() {
let data = surface.data();
let data = data.lock();
for triangle in data
.geometry_buffer
.iter()
.filter_map(|t| read_triangle(&data, t, &transform))
{
if ignore_back_faces {
let normal = (triangle[1] - triangle[0]).cross(&(triangle[2] - triangle[0]));
if normal.dot(&ray.dir) >= 0.0 {
continue;
}
}
if let Some(pt) = ray.triangle_intersection_point(&triangle) {
let distance = ray.origin.sqr_distance(&pt);
if distance < closest_distance {
closest_distance = distance;
closest_point = Some(pt);
}
}
}
}
}
closest_point.map(|pt| (closest_distance, pt))
}