fyrox_scripts/
camera.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys.
22//! See [`FlyingCameraController`] docs for more info and usage examples.
23
24use fyrox::{
25    core::{
26        algebra::{UnitQuaternion, UnitVector3, Vector3},
27        impl_component_provider,
28        math::curve::{Curve, CurveKey, CurveKeyKind},
29        math::Vector3Ext,
30        reflect::prelude::*,
31        uuid_provider,
32        variable::InheritableVariable,
33        visitor::prelude::*,
34    },
35    event::{DeviceEvent, ElementState, Event, WindowEvent},
36    gui::{key::KeyBinding, message::KeyCode},
37    script::{ScriptContext, ScriptTrait},
38    utils,
39};
40use std::ops::Range;
41
42/// Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys.
43/// 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
44/// node (or one if its parent nodes).
45#[derive(Visit, Reflect, Debug, Clone)]
46pub struct FlyingCameraController {
47    #[reflect(description = "Current yaw of the camera pivot (in radians).")]
48    #[visit(optional)]
49    pub yaw: InheritableVariable<f32>,
50
51    #[reflect(description = "Current pitch of the camera (in radians).")]
52    #[visit(optional)]
53    pub pitch: InheritableVariable<f32>,
54
55    #[reflect(description = "Maximum speed of the camera.")]
56    #[visit(optional)]
57    pub speed: InheritableVariable<f32>,
58
59    #[reflect(description = "Mouse sensitivity.")]
60    #[visit(optional)]
61    pub sensitivity: InheritableVariable<f32>,
62
63    #[reflect(description = "Angular limit of the pitch of the camera (in radians).")]
64    #[visit(optional)]
65    pub pitch_limit: InheritableVariable<Range<f32>>,
66
67    // KeyBinding belongs to fyrox-ui which is unideal, this is only used here because it has built-in
68    // property editor, so it will be shown in the editor correctly. It might be worth to create a
69    // separate property editor for this instead to be able to use KeyCode here.
70    #[reflect(description = "A key, that corresponds to forward movement.")]
71    #[visit(optional)]
72    pub move_forward_key: InheritableVariable<KeyBinding>,
73
74    #[reflect(description = "A key, that corresponds to backward movement.")]
75    #[visit(optional)]
76    pub move_backward_key: InheritableVariable<KeyBinding>,
77
78    #[reflect(description = "A key, that corresponds to left movement.")]
79    #[visit(optional)]
80    pub move_left_key: InheritableVariable<KeyBinding>,
81
82    #[reflect(description = "A key, that corresponds to right movement.")]
83    #[visit(optional)]
84    pub move_right_key: InheritableVariable<KeyBinding>,
85
86    #[reflect(
87        description = "A curve, that defines a how speed of the camera changes when accelerating to the \
88    max speed."
89    )]
90    #[visit(optional)]
91    pub acceleration_curve: InheritableVariable<Curve>,
92
93    #[reflect(
94        description = "A curve, that defines a how speed of the camera changes when decelerating to the \
95    zero speed."
96    )]
97    #[visit(optional)]
98    pub deceleration_curve: InheritableVariable<Curve>,
99
100    #[reflect(
101        description = "Amount of time (in seconds) during which the camera will accelerate to the max speed.",
102        min_value = 0.0
103    )]
104    #[visit(optional)]
105    pub acceleration_time: InheritableVariable<f32>,
106
107    #[reflect(
108        description = "Amount of time (in seconds) during which the camera will decelerate to the zero speed.",
109        min_value = 0.0
110    )]
111    #[visit(optional)]
112    pub deceleration_time: InheritableVariable<f32>,
113
114    #[reflect(
115        description = "A coefficient, that defines how fast the camera will respond to pressed keys.",
116        min_value = 0.01,
117        max_value = 1.0
118    )]
119    #[visit(optional)]
120    pub reactivity: InheritableVariable<f32>,
121
122    #[reflect(hidden)]
123    #[visit(optional)]
124    pub velocity: InheritableVariable<Vector3<f32>>,
125
126    #[reflect(hidden)]
127    #[visit(optional)]
128    pub target_velocity: InheritableVariable<Vector3<f32>>,
129
130    #[reflect(hidden)]
131    #[visit(skip)]
132    pub acceleration_coeff: f32,
133
134    #[reflect(hidden)]
135    #[visit(skip)]
136    pub move_forward: bool,
137
138    #[reflect(hidden)]
139    #[visit(skip)]
140    pub move_backward: bool,
141
142    #[reflect(hidden)]
143    #[visit(skip)]
144    pub move_left: bool,
145
146    #[reflect(hidden)]
147    #[visit(skip)]
148    pub move_right: bool,
149}
150
151impl Default for FlyingCameraController {
152    fn default() -> Self {
153        Self {
154            yaw: Default::default(),
155            pitch: Default::default(),
156            speed: 5.0.into(),
157            sensitivity: 0.7.into(),
158            pitch_limit: ((-89.9f32).to_radians()..89.9f32.to_radians()).into(),
159            move_forward_key: KeyBinding::Some(KeyCode::KeyW).into(),
160            move_backward_key: KeyBinding::Some(KeyCode::KeyS).into(),
161            move_left_key: KeyBinding::Some(KeyCode::KeyA).into(),
162            move_right_key: KeyBinding::Some(KeyCode::KeyD).into(),
163            acceleration_curve: Curve::from(vec![
164                CurveKey::new(
165                    0.0,
166                    0.0,
167                    CurveKeyKind::Cubic {
168                        left_tangent: 0.0,
169                        right_tangent: 0.0,
170                    },
171                ),
172                CurveKey::new(
173                    1.0,
174                    1.0,
175                    CurveKeyKind::Cubic {
176                        left_tangent: 0.0,
177                        right_tangent: 0.0,
178                    },
179                ),
180            ])
181            .into(),
182            deceleration_curve: Curve::from(vec![
183                CurveKey::new(
184                    0.0,
185                    0.0,
186                    CurveKeyKind::Cubic {
187                        left_tangent: 0.0,
188                        right_tangent: 0.0,
189                    },
190                ),
191                CurveKey::new(
192                    1.0,
193                    1.0,
194                    CurveKeyKind::Cubic {
195                        left_tangent: 0.0,
196                        right_tangent: 0.0,
197                    },
198                ),
199            ])
200            .into(),
201            acceleration_time: 0.25.into(),
202            deceleration_time: 1.0.into(),
203            velocity: Default::default(),
204            target_velocity: Default::default(),
205            acceleration_coeff: 0.0,
206            reactivity: 0.3.into(),
207            move_forward: false,
208            move_backward: false,
209            move_left: false,
210            move_right: false,
211        }
212    }
213}
214
215impl_component_provider!(FlyingCameraController);
216uuid_provider!(FlyingCameraController = "8d9e2feb-8c61-482c-8ba4-b0b13b201113");
217
218impl ScriptTrait for FlyingCameraController {
219    fn on_os_event(&mut self, event: &Event<()>, context: &mut ScriptContext) {
220        match event {
221            Event::WindowEvent {
222                event: WindowEvent::KeyboardInput { event, .. },
223                ..
224            } => {
225                for (binding, state) in [
226                    (&self.move_forward_key, &mut self.move_forward),
227                    (&self.move_backward_key, &mut self.move_backward),
228                    (&self.move_left_key, &mut self.move_left),
229                    (&self.move_right_key, &mut self.move_right),
230                ] {
231                    if let KeyBinding::Some(key_code) = **binding {
232                        if utils::translate_key_from_ui(key_code) == event.physical_key {
233                            *state = event.state == ElementState::Pressed;
234                        }
235                    }
236                }
237            }
238            Event::DeviceEvent {
239                event: DeviceEvent::MouseMotion { delta, .. },
240                ..
241            } => {
242                let speed = *self.sensitivity * context.dt;
243                *self.yaw -= (delta.0 as f32) * speed;
244                *self.pitch = (*self.pitch + delta.1 as f32 * speed)
245                    .max(self.pitch_limit.start)
246                    .min(self.pitch_limit.end);
247            }
248            _ => {}
249        }
250    }
251
252    fn on_update(&mut self, context: &mut ScriptContext) {
253        let mut new_velocity = Vector3::default();
254
255        let this = &mut context.scene.graph[context.handle];
256
257        if self.move_forward {
258            new_velocity += this.look_vector();
259        }
260        if self.move_backward {
261            new_velocity -= this.look_vector();
262        }
263        if self.move_left {
264            new_velocity += this.side_vector();
265        }
266        if self.move_right {
267            new_velocity -= this.side_vector();
268        }
269
270        if let Some(new_normalized_velocity) = new_velocity.try_normalize(f32::EPSILON) {
271            self.acceleration_coeff = (self.acceleration_coeff
272                + context.dt / self.acceleration_time.max(context.dt))
273            .min(1.0);
274            *self.target_velocity = new_normalized_velocity.scale(
275                *self.speed
276                    * self.acceleration_curve.value_at(self.acceleration_coeff)
277                    * context.dt,
278            );
279        } else {
280            self.acceleration_coeff = (self.acceleration_coeff
281                - context.dt / self.deceleration_time.max(context.dt))
282            .max(0.0);
283            if let Some(normalized_velocity) = self.target_velocity.try_normalize(f32::EPSILON) {
284                *self.target_velocity = normalized_velocity.scale(
285                    *self.speed
286                        * self.deceleration_curve.value_at(self.acceleration_coeff)
287                        * context.dt,
288                );
289            } else {
290                *self.target_velocity = Vector3::zeros();
291            }
292        }
293
294        self.velocity
295            .follow(&self.target_velocity, *self.reactivity);
296
297        let yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), *self.yaw);
298        this.local_transform_mut()
299            .set_rotation(
300                UnitQuaternion::from_axis_angle(
301                    &UnitVector3::new_normalize(yaw * Vector3::x()),
302                    *self.pitch,
303                ) * yaw,
304            )
305            .offset(*self.velocity);
306    }
307}