fyrox_animation/container.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//! Track data container is a flexible source of data for numeric parameters, it is built using a set
22//! of parametric curves. See [`TrackDataContainer`] docs for more info.
23
24use crate::{
25 core::{
26 algebra::{Vector2, Vector3, Vector4},
27 math::curve::Curve,
28 math::{quat_from_euler, RotationOrder},
29 reflect::prelude::*,
30 visitor::prelude::*,
31 },
32 value::TrackValue,
33};
34
35/// The kind of track output value, the animation system works only with numeric properties and the number
36/// of variants is small.
37#[derive(Clone, Copy, Debug, Visit, Reflect, PartialEq, Eq)]
38pub enum TrackValueKind {
39 /// A real number. Requires only 1 parametric curve.
40 Real,
41
42 /// A 2-dimensional vector of real values. Requires 2 parametric curves, where `X = 0` and `Y = 1`.
43 Vector2,
44
45 /// A 3-dimensional vector of real values. Requires 3 parametric curves, where `X = 0`, `Y = 1`, `Z = 2`.
46 Vector3,
47
48 /// A 4-dimensional vector of real values. Requires 4 parametric curves, where `X = 0`, `Y = 1`, `Z = 2`, `W = 3`.
49 Vector4,
50
51 /// A quaternion that represents some rotation. Requires 3 parametric curves, where `XAngle = 0`, `YAngle = 1`,
52 /// `ZAngle = 2`. The order of rotations is `XYZ`. This triple of curves forms Euler angles which are interpolated
53 /// and then converted to a quaternion.
54 UnitQuaternion,
55}
56
57impl TrackValueKind {
58 /// Returns count of elementary components of a value kind. For example: Vector3 consists of 3 components where
59 /// each component has its own parametric curve.
60 pub fn components_count(self) -> usize {
61 match self {
62 TrackValueKind::Real => 1,
63 TrackValueKind::Vector2 => 2,
64 TrackValueKind::Vector3 => 3,
65 TrackValueKind::Vector4 => 4,
66 TrackValueKind::UnitQuaternion => {
67 // Euler angles
68 3
69 }
70 }
71 }
72}
73
74impl Default for TrackValueKind {
75 fn default() -> Self {
76 Self::Vector3
77 }
78}
79
80/// Interpolation mode for track data.
81#[derive(Visit, Reflect, Debug, Clone, Default, PartialEq)]
82pub enum InterpolationMode {
83 /// Default interpolation mode.
84 #[default]
85 Default,
86 /// This mode forces the engine to use short-path angle interpolation.
87 ShortPath,
88}
89
90/// Container for a track data. Strictly speaking, it is just a set of parametric curves which can be
91/// fetched at a given time position simultaneously, producing a value of desired type. Which type of
92/// value is produced is defined by [`TrackValueKind`] enumeration. Usually a container contains up to
93/// 4 curves (the largest type supported is [`Vector4`]).
94///
95/// Each component is bound to a specific curve. For example, in case of [`Vector3`] its components bound
96/// to the following curve indices: `X = 0`, `Y = 1`, `Z = 2`. This order cannot be changed.
97#[derive(Visit, Reflect, Debug, Clone, Default, PartialEq)]
98pub struct TrackDataContainer {
99 curves: Vec<Curve>,
100 kind: TrackValueKind,
101 /// Interpolation mode.
102 #[visit(optional)] // Backward compatibility.
103 pub mode: InterpolationMode,
104}
105
106impl TrackDataContainer {
107 /// Creates a new container, that is able to produce values defined by [`TrackValueKind`] input parameter.
108 /// The number of curves in the output container is defined by [`TrackValueKind::components_count`], for
109 /// example [`Vector3`] has 3 components (X, Y, Z). An empty container can be created using [`Self::default`]
110 /// method.
111 pub fn new(kind: TrackValueKind) -> Self {
112 Self {
113 kind,
114 // Do not use `vec![Default::default(); kind.components_count()]` here because
115 // it clones a curve that was created in first macro argument which leads to
116 // non-unique ids of the curves.
117 curves: (0..kind.components_count())
118 .map(|_| Curve::default())
119 .collect(),
120 mode: Default::default(),
121 }
122 }
123
124 /// Adds a new curve to the container. Keep in mind, that the actual useful amount of curves has soft limit
125 /// of four due to [`TrackValueKind`], any excessive curves will be ignored.
126 pub fn add_curve(&mut self, curve: Curve) {
127 self.curves.push(curve)
128 }
129
130 /// Tries to borrow a curve at a given index.
131 pub fn curve(&self, index: usize) -> Option<&Curve> {
132 self.curves.get(index)
133 }
134
135 /// Tries to borrow a curve at a given index.
136 pub fn curve_mut(&mut self, index: usize) -> Option<&mut Curve> {
137 self.curves.get_mut(index)
138 }
139
140 /// Returns a reference to curves container.
141 pub fn curves_ref(&self) -> &[Curve] {
142 &self.curves
143 }
144
145 /// Tries to borrow a curve at a given index.
146 pub fn curves_mut(&mut self) -> &mut [Curve] {
147 &mut self.curves
148 }
149
150 /// Sets new kind of output value. Keep in mind, that the curves will remain unchanged, if you need
151 /// to re-use the container you might need to re-create/re-fill the curves too.
152 pub fn set_value_kind(&mut self, kind: TrackValueKind) {
153 self.kind = kind;
154 }
155
156 /// Returns the kind of output value produced by the container.
157 pub fn value_kind(&self) -> TrackValueKind {
158 self.kind
159 }
160
161 #[inline(always)]
162 fn fetch_vector2(&self, time: f32) -> Option<TrackValue> {
163 if self.curves.len() < 2 {
164 return None;
165 }
166
167 // SAFETY: The indices are guaranteed to be correct by the above check.
168 unsafe {
169 Some(TrackValue::Vector2(Vector2::new(
170 self.curves.get_unchecked(0).value_at(time),
171 self.curves.get_unchecked(1).value_at(time),
172 )))
173 }
174 }
175
176 #[inline(always)]
177 fn fetch_vector3(&self, time: f32) -> Option<TrackValue> {
178 if self.curves.len() < 3 {
179 return None;
180 }
181
182 unsafe {
183 Some(TrackValue::Vector3(Vector3::new(
184 self.curves.get_unchecked(0).value_at(time),
185 self.curves.get_unchecked(1).value_at(time),
186 self.curves.get_unchecked(2).value_at(time),
187 )))
188 }
189 }
190
191 #[inline(always)]
192 fn fetch_vector4(&self, time: f32) -> Option<TrackValue> {
193 if self.curves.len() < 4 {
194 return None;
195 }
196
197 // SAFETY: The indices are guaranteed to be correct by the above check.
198 unsafe {
199 Some(TrackValue::Vector4(Vector4::new(
200 self.curves.get_unchecked(0).value_at(time),
201 self.curves.get_unchecked(1).value_at(time),
202 self.curves.get_unchecked(2).value_at(time),
203 self.curves.get_unchecked(3).value_at(time),
204 )))
205 }
206 }
207
208 #[inline(always)]
209 fn fetch_quaternion(&self, time: f32) -> Option<TrackValue> {
210 if self.curves.len() < 3 {
211 return None;
212 }
213
214 // SAFETY: The indices are guaranteed to be correct by the above check.
215 unsafe {
216 let x_curve = self.curves.get_unchecked(0);
217 let y_curve = self.curves.get_unchecked(1);
218 let z_curve = self.curves.get_unchecked(2);
219
220 // Convert Euler angles to quaternion
221 let (x, y, z) = match self.mode {
222 InterpolationMode::Default => (
223 x_curve.value_at(time),
224 y_curve.value_at(time),
225 z_curve.value_at(time),
226 ),
227 InterpolationMode::ShortPath => (
228 x_curve.angle_at(time),
229 y_curve.angle_at(time),
230 z_curve.angle_at(time),
231 ),
232 };
233
234 Some(TrackValue::UnitQuaternion(quat_from_euler(
235 Vector3::new(x, y, z),
236 RotationOrder::XYZ,
237 )))
238 }
239 }
240
241 /// Tries to get a value at a given time. The method could fail if the internal set of curves is malformed
242 /// and cannot produce a desired value (for example, [`Vector3`] can be fetched only if the amount of curves
243 /// is 3).
244 pub fn fetch(&self, time: f32) -> Option<TrackValue> {
245 match self.kind {
246 TrackValueKind::Real => Some(TrackValue::Real(self.curves.first()?.value_at(time))),
247 TrackValueKind::Vector2 => self.fetch_vector2(time),
248 TrackValueKind::Vector3 => self.fetch_vector3(time),
249 TrackValueKind::Vector4 => self.fetch_vector4(time),
250 TrackValueKind::UnitQuaternion => self.fetch_quaternion(time),
251 }
252 }
253
254 /// Find a right-most key on one of the curves in the container and returns its position. This position
255 /// can be treated as a maximum "length" of the container.
256 pub fn time_length(&self) -> f32 {
257 let mut length = 0.0;
258 for curve in self.curves.iter() {
259 let max_location = curve.max_location();
260 if max_location > length {
261 length = max_location;
262 }
263 }
264 length
265 }
266}