Skip to main content

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::graph::SceneGraph;
25use fyrox::plugin::error::GameResult;
26use fyrox::{
27    core::{
28        algebra::{UnitQuaternion, UnitVector3, Vector3},
29        impl_component_provider,
30        math::curve::{Curve, CurveKey, CurveKeyKind},
31        math::Vector3Ext,
32        reflect::prelude::*,
33        uuid_provider,
34        variable::InheritableVariable,
35        visitor::prelude::*,
36    },
37    event::{DeviceEvent, ElementState, Event, WindowEvent},
38    gui::{key::KeyBinding, message::KeyCode},
39    script::{ScriptContext, ScriptTrait},
40    utils,
41};
42use std::ops::Range;
43
44/// Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys.
45/// 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
46/// node (or one if its parent nodes).
47#[derive(Visit, Reflect, Debug, Clone)]
48pub struct FlyingCameraController {
49    /// Current yaw of the camera pivot (in radians).
50    #[visit(optional)]
51    pub yaw: InheritableVariable<f32>,
52
53    /// Current pitch of the camera (in radians).
54    #[visit(optional)]
55    pub pitch: InheritableVariable<f32>,
56
57    /// Maximum speed of the camera.
58    #[visit(optional)]
59    pub speed: InheritableVariable<f32>,
60
61    /// Mouse sensitivity.
62    #[visit(optional)]
63    pub sensitivity: InheritableVariable<f32>,
64
65    /// Angular limit of the pitch of the camera (in radians).
66    #[visit(optional)]
67    pub pitch_limit: InheritableVariable<Range<f32>>,
68
69    // KeyBinding belongs to fyrox-ui which is unideal, this is only used here because it has built-in
70    // property editor, so it will be shown in the editor correctly. It might be worth to create a
71    // separate property editor for this instead to be able to use KeyCode here.
72    /// A key, that corresponds to forward movement.
73    #[visit(optional)]
74    pub move_forward_key: InheritableVariable<KeyBinding>,
75
76    /// A key, that corresponds to backward movement.
77    #[visit(optional)]
78    pub move_backward_key: InheritableVariable<KeyBinding>,
79
80    /// A key, that corresponds to left movement.
81    #[visit(optional)]
82    pub move_left_key: InheritableVariable<KeyBinding>,
83
84    /// A key, that corresponds to right movement.
85    #[visit(optional)]
86    pub move_right_key: InheritableVariable<KeyBinding>,
87
88    /// A curve, that defines a how speed of the camera changes when accelerating to the max speed.
89    #[visit(optional)]
90    pub acceleration_curve: InheritableVariable<Curve>,
91
92    /// A curve, that defines a how speed of the camera changes when decelerating to the zero speed.
93    #[visit(optional)]
94    pub deceleration_curve: InheritableVariable<Curve>,
95
96    /// Amount of time (in seconds) during which the camera will accelerate to the max speed.
97    #[reflect(min_value = 0.0)]
98    #[visit(optional)]
99    pub acceleration_time: InheritableVariable<f32>,
100
101    /// Amount of time (in seconds) during which the camera will decelerate to the zero speed.
102    #[reflect(min_value = 0.0)]
103    #[visit(optional)]
104    pub deceleration_time: InheritableVariable<f32>,
105
106    /// A coefficient, that defines how fast the camera will respond to pressed keys.
107    #[reflect(min_value = 0.01, max_value = 1.0)]
108    #[visit(optional)]
109    pub reactivity: InheritableVariable<f32>,
110
111    #[reflect(hidden)]
112    #[visit(optional)]
113    pub velocity: InheritableVariable<Vector3<f32>>,
114
115    #[reflect(hidden)]
116    #[visit(optional)]
117    pub target_velocity: InheritableVariable<Vector3<f32>>,
118
119    #[reflect(hidden)]
120    #[visit(skip)]
121    pub acceleration_coeff: f32,
122
123    #[reflect(hidden)]
124    #[visit(skip)]
125    pub move_forward: bool,
126
127    #[reflect(hidden)]
128    #[visit(skip)]
129    pub move_backward: bool,
130
131    #[reflect(hidden)]
132    #[visit(skip)]
133    pub move_left: bool,
134
135    #[reflect(hidden)]
136    #[visit(skip)]
137    pub move_right: bool,
138}
139
140impl Default for FlyingCameraController {
141    fn default() -> Self {
142        Self {
143            yaw: Default::default(),
144            pitch: Default::default(),
145            speed: 5.0.into(),
146            sensitivity: 0.7.into(),
147            pitch_limit: ((-89.9f32).to_radians()..89.9f32.to_radians()).into(),
148            move_forward_key: KeyBinding::Some(KeyCode::KeyW).into(),
149            move_backward_key: KeyBinding::Some(KeyCode::KeyS).into(),
150            move_left_key: KeyBinding::Some(KeyCode::KeyA).into(),
151            move_right_key: KeyBinding::Some(KeyCode::KeyD).into(),
152            acceleration_curve: Curve::from(vec![
153                CurveKey::new(
154                    0.0,
155                    0.0,
156                    CurveKeyKind::Cubic {
157                        left_tangent: 0.0,
158                        right_tangent: 0.0,
159                    },
160                ),
161                CurveKey::new(
162                    1.0,
163                    1.0,
164                    CurveKeyKind::Cubic {
165                        left_tangent: 0.0,
166                        right_tangent: 0.0,
167                    },
168                ),
169            ])
170            .into(),
171            deceleration_curve: Curve::from(vec![
172                CurveKey::new(
173                    0.0,
174                    0.0,
175                    CurveKeyKind::Cubic {
176                        left_tangent: 0.0,
177                        right_tangent: 0.0,
178                    },
179                ),
180                CurveKey::new(
181                    1.0,
182                    1.0,
183                    CurveKeyKind::Cubic {
184                        left_tangent: 0.0,
185                        right_tangent: 0.0,
186                    },
187                ),
188            ])
189            .into(),
190            acceleration_time: 0.25.into(),
191            deceleration_time: 1.0.into(),
192            velocity: Default::default(),
193            target_velocity: Default::default(),
194            acceleration_coeff: 0.0,
195            reactivity: 0.3.into(),
196            move_forward: false,
197            move_backward: false,
198            move_left: false,
199            move_right: false,
200        }
201    }
202}
203
204impl_component_provider!(FlyingCameraController);
205uuid_provider!(FlyingCameraController = "8d9e2feb-8c61-482c-8ba4-b0b13b201113");
206
207impl ScriptTrait for FlyingCameraController {
208    fn on_os_event(&mut self, event: &Event<()>, context: &mut ScriptContext) -> GameResult {
209        match event {
210            Event::WindowEvent {
211                event: WindowEvent::KeyboardInput { event, .. },
212                ..
213            } => {
214                for (binding, state) in [
215                    (&self.move_forward_key, &mut self.move_forward),
216                    (&self.move_backward_key, &mut self.move_backward),
217                    (&self.move_left_key, &mut self.move_left),
218                    (&self.move_right_key, &mut self.move_right),
219                ] {
220                    if let KeyBinding::Some(key_code) = **binding {
221                        if utils::translate_key_from_ui(key_code) == event.physical_key {
222                            *state = event.state == ElementState::Pressed;
223                        }
224                    }
225                }
226            }
227            Event::DeviceEvent {
228                event: DeviceEvent::MouseMotion { delta, .. },
229                ..
230            } => {
231                let speed = *self.sensitivity * context.dt;
232                *self.yaw -= (delta.0 as f32) * speed;
233                *self.pitch = (*self.pitch + delta.1 as f32 * speed)
234                    .max(self.pitch_limit.start)
235                    .min(self.pitch_limit.end);
236            }
237            _ => {}
238        }
239
240        Ok(())
241    }
242
243    fn on_update(&mut self, context: &mut ScriptContext) -> GameResult {
244        let mut new_velocity = Vector3::default();
245
246        let this = context.scene.graph.try_get_node_mut(context.handle)?;
247
248        if self.move_forward {
249            new_velocity += this.look_vector();
250        }
251        if self.move_backward {
252            new_velocity -= this.look_vector();
253        }
254        if self.move_left {
255            new_velocity += this.side_vector();
256        }
257        if self.move_right {
258            new_velocity -= this.side_vector();
259        }
260
261        if let Some(new_normalized_velocity) = new_velocity.try_normalize(f32::EPSILON) {
262            self.acceleration_coeff = (self.acceleration_coeff
263                + context.dt / self.acceleration_time.max(context.dt))
264            .min(1.0);
265            *self.target_velocity = new_normalized_velocity.scale(
266                *self.speed
267                    * self.acceleration_curve.value_at(self.acceleration_coeff)
268                    * context.dt,
269            );
270        } else {
271            self.acceleration_coeff = (self.acceleration_coeff
272                - context.dt / self.deceleration_time.max(context.dt))
273            .max(0.0);
274            if let Some(normalized_velocity) = self.target_velocity.try_normalize(f32::EPSILON) {
275                *self.target_velocity = normalized_velocity.scale(
276                    *self.speed
277                        * self.deceleration_curve.value_at(self.acceleration_coeff)
278                        * context.dt,
279                );
280            } else {
281                *self.target_velocity = Vector3::zeros();
282            }
283        }
284
285        self.velocity
286            .follow(&self.target_velocity, *self.reactivity);
287
288        let yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), *self.yaw);
289        this.local_transform_mut()
290            .set_rotation(
291                UnitQuaternion::from_axis_angle(
292                    &UnitVector3::new_normalize(yaw * Vector3::x()),
293                    *self.pitch,
294                ) * yaw,
295            )
296            .offset(*self.velocity);
297
298        Ok(())
299    }
300}