diff --git a/fyrox-scripts/src/camera.rs b/fyrox-scripts/src/camera.rs index 6789d5623..495eba5c1 100644 --- a/fyrox-scripts/src/camera.rs +++ b/fyrox-scripts/src/camera.rs @@ -1,11 +1,12 @@ //! Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys. //! See [`FlyingCameraController`] docs for more info and usage examples. -use fyrox::core::curve::{Curve, CurveKey, CurveKeyKind}; use fyrox::{ + core::algebra::UnitVector3, core::{ algebra::{UnitQuaternion, Vector3}, - pool::Handle, + curve::{Curve, CurveKey, CurveKeyKind}, + math::Vector3Ext, reflect::prelude::*, uuid::{uuid, Uuid}, variable::InheritableVariable, @@ -15,52 +16,123 @@ use fyrox::{ event::{DeviceEvent, ElementState, Event, WindowEvent}, gui::{key::KeyBinding, message::KeyCode}, impl_component_provider, - scene::node::Node, script::{ScriptContext, ScriptTrait}, utils, }; use std::ops::Range; /// Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys. -/// Use it, if you need to create a sort of "spectator" camera. +/// Use it, if you need to create a sort of "spectator" camera. To use it, all you need to do is to assign it to your camera +/// node (or one if its parent nodes). #[derive(Visit, Reflect, Debug, Clone)] pub struct FlyingCameraController { - pub camera: InheritableVariable>, + #[reflect(description = "Current yaw of the camera pivot (in radians).")] + #[visit(optional)] pub yaw: InheritableVariable, + + #[reflect(description = "Current pitch of the camera (in radians).")] + #[visit(optional)] pub pitch: InheritableVariable, + + #[reflect(description = "Maximum speed of the camera.")] + #[visit(optional)] pub speed: InheritableVariable, + + #[reflect(description = "Mouse sensitivity.")] + #[visit(optional)] pub sensitivity: InheritableVariable, + + #[reflect(description = "Angular limit of the pitch of the camera (in radians).")] + #[visit(optional)] pub pitch_limit: InheritableVariable>, // KeyBinding belongs to fyrox-ui which is unideal, this is only used here because it has built-in // property editor, so it will be shown in the editor correctly. It might be worth to create a // separate property editor for this instead to be able to use KeyCode here. + #[reflect(description = "A key, that corresponds to forward movement.")] + #[visit(optional)] pub move_forward_key: InheritableVariable, + + #[reflect(description = "A key, that corresponds to backward movement.")] + #[visit(optional)] pub move_backward_key: InheritableVariable, + + #[reflect(description = "A key, that corresponds to left movement.")] + #[visit(optional)] pub move_left_key: InheritableVariable, + + #[reflect(description = "A key, that corresponds to right movement.")] + #[visit(optional)] pub move_right_key: InheritableVariable, + #[reflect( + description = "A curve, that defines a how speed of the camera changes when accelerating to the \ + max speed." + )] + #[visit(optional)] pub acceleration_curve: InheritableVariable, + + #[reflect( + description = "A curve, that defines a how speed of the camera changes when decelerating to the \ + zero speed." + )] + #[visit(optional)] pub deceleration_curve: InheritableVariable, + #[reflect( + description = "Amount of time (in seconds) during which the camera will accelerate to the max speed.", + min_value = 0.0 + )] + #[visit(optional)] + pub acceleration_time: InheritableVariable, + + #[reflect( + description = "Amount of time (in seconds) during which the camera will decelerate to the zero speed.", + min_value = 0.0 + )] + #[visit(optional)] + pub deceleration_time: InheritableVariable, + + #[reflect( + description = "A coefficient, that defines how fast the camera will respond to pressed keys.", + min_value = 0.01, + max_value = 1.0 + )] + #[visit(optional)] + pub reactivity: InheritableVariable, + + #[reflect(hidden)] + #[visit(optional)] + pub velocity: InheritableVariable>, + + #[reflect(hidden)] + #[visit(optional)] + pub target_velocity: InheritableVariable>, + #[reflect(hidden)] #[visit(skip)] - move_forward: bool, + pub acceleration_coeff: f32, + #[reflect(hidden)] #[visit(skip)] - move_backward: bool, + pub move_forward: bool, + #[reflect(hidden)] #[visit(skip)] - move_left: bool, + pub move_backward: bool, + #[reflect(hidden)] #[visit(skip)] - move_right: bool, + pub move_left: bool, + + #[reflect(hidden)] + #[visit(skip)] + pub move_right: bool, } impl Default for FlyingCameraController { fn default() -> Self { Self { - camera: Default::default(), yaw: Default::default(), pitch: Default::default(), speed: 5.0.into(), @@ -92,7 +164,7 @@ impl Default for FlyingCameraController { deceleration_curve: Curve::from(vec![ CurveKey::new( 0.0, - 1.0, + 0.0, CurveKeyKind::Cubic { left_tangent: 0.0, right_tangent: 0.0, @@ -100,7 +172,7 @@ impl Default for FlyingCameraController { ), CurveKey::new( 1.0, - 0.0, + 1.0, CurveKeyKind::Cubic { left_tangent: 0.0, right_tangent: 0.0, @@ -108,6 +180,12 @@ impl Default for FlyingCameraController { ), ]) .into(), + acceleration_time: 0.25.into(), + deceleration_time: 1.0.into(), + velocity: Default::default(), + target_velocity: Default::default(), + acceleration_coeff: 0.0, + reactivity: 0.3.into(), move_forward: false, move_backward: false, move_left: false, @@ -157,40 +235,59 @@ impl ScriptTrait for FlyingCameraController { } fn on_update(&mut self, context: &mut ScriptContext) { - if let Some(pivot) = context.scene.graph.try_get_mut(*self.camera) { - pivot - .local_transform_mut() - .set_rotation(UnitQuaternion::from_axis_angle( - &Vector3::x_axis(), - *self.pitch, - )); - } + let mut new_velocity = Vector3::default(); let this = &mut context.scene.graph[context.handle]; - this.local_transform_mut() - .set_rotation(UnitQuaternion::from_axis_angle( - &Vector3::y_axis(), - *self.yaw, - )); - - let mut velocity = Vector3::default(); if self.move_forward { - velocity += this.look_vector(); + new_velocity += this.look_vector(); } if self.move_backward { - velocity -= this.look_vector(); + new_velocity -= this.look_vector(); } if self.move_left { - velocity += this.side_vector(); + new_velocity += this.side_vector(); } if self.move_right { - velocity -= this.side_vector(); + new_velocity -= this.side_vector(); } - if let Some(normalized_velocity) = velocity.try_normalize(f32::EPSILON) { - this.local_transform_mut() - .offset(normalized_velocity.scale(*self.speed * context.dt)); + + if let Some(new_normalized_velocity) = new_velocity.try_normalize(f32::EPSILON) { + self.acceleration_coeff = (self.acceleration_coeff + + context.dt / self.acceleration_time.max(context.dt)) + .min(1.0); + *self.target_velocity = new_normalized_velocity.scale( + *self.speed + * self.acceleration_curve.value_at(self.acceleration_coeff) + * context.dt, + ); + } else { + self.acceleration_coeff = (self.acceleration_coeff + - context.dt / self.deceleration_time.max(context.dt)) + .max(0.0); + if let Some(normalized_velocity) = self.target_velocity.try_normalize(f32::EPSILON) { + *self.target_velocity = normalized_velocity.scale( + *self.speed + * self.deceleration_curve.value_at(self.acceleration_coeff) + * context.dt, + ); + } else { + *self.target_velocity = Vector3::zeros(); + } } + + self.velocity + .follow(&self.target_velocity, *self.reactivity); + + let yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), *self.yaw); + this.local_transform_mut() + .set_rotation( + UnitQuaternion::from_axis_angle( + &UnitVector3::new_normalize(yaw * Vector3::x()), + *self.pitch, + ) * yaw, + ) + .offset(*self.velocity); } fn id(&self) -> Uuid {