mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-11 22:50:08 +00:00
added non-euler-angles-based rotation track
- fixed gimbal lock in GLTF animations
This commit is contained in:
@@ -260,9 +260,15 @@ fn type_id_to_supported_type(property_type: TypeId) -> Option<(TrackValueKind, V
|
||||
} else if property_type == TypeId::of::<Vector4<bool>>() {
|
||||
Some((TrackValueKind::Vector4, ValueType::Vector4Bool))
|
||||
} else if property_type == TypeId::of::<UnitQuaternion<f32>>() {
|
||||
Some((TrackValueKind::UnitQuaternion, ValueType::UnitQuaternionF32))
|
||||
Some((
|
||||
TrackValueKind::UnitQuaternionEuler,
|
||||
ValueType::UnitQuaternionF32,
|
||||
))
|
||||
} else if property_type == TypeId::of::<UnitQuaternion<f64>>() {
|
||||
Some((TrackValueKind::UnitQuaternion, ValueType::UnitQuaternionF64))
|
||||
Some((
|
||||
TrackValueKind::UnitQuaternionEuler,
|
||||
ValueType::UnitQuaternionF64,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1436,12 +1442,15 @@ impl TrackList {
|
||||
| TrackValueKind::Vector4 => {
|
||||
["X", "Y", "Z", "W"].get(i).unwrap_or(&"_")
|
||||
}
|
||||
TrackValueKind::UnitQuaternion => match i {
|
||||
TrackValueKind::UnitQuaternionEuler => match i {
|
||||
0 => "Pitch",
|
||||
1 => "Yaw",
|
||||
2 => "Roll",
|
||||
_ => "Unknown",
|
||||
},
|
||||
TrackValueKind::UnitQuaternion => {
|
||||
["W", "X", "Y", "Z"].get(i).unwrap_or(&"_")
|
||||
}
|
||||
};
|
||||
|
||||
let curve_view = TreeBuilder::new(
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::{
|
||||
},
|
||||
value::TrackValue,
|
||||
};
|
||||
use fyrox_core::algebra::{Quaternion, UnitQuaternion};
|
||||
|
||||
/// The kind of track output value, the animation system works only with numeric properties and the number
|
||||
/// of variants is small.
|
||||
@@ -51,6 +52,10 @@ pub enum TrackValueKind {
|
||||
/// A quaternion that represents some rotation. Requires 3 parametric curves, where `XAngle = 0`, `YAngle = 1`,
|
||||
/// `ZAngle = 2`. The order of rotations is `XYZ`. This triple of curves forms Euler angles which are interpolated
|
||||
/// and then converted to a quaternion.
|
||||
UnitQuaternionEuler,
|
||||
|
||||
/// A quaternion that represents some rotation. Requires 4 parametric curves for each component.
|
||||
/// The order is XYZW.
|
||||
UnitQuaternion,
|
||||
}
|
||||
|
||||
@@ -63,10 +68,11 @@ impl TrackValueKind {
|
||||
TrackValueKind::Vector2 => 2,
|
||||
TrackValueKind::Vector3 => 3,
|
||||
TrackValueKind::Vector4 => 4,
|
||||
TrackValueKind::UnitQuaternion => {
|
||||
TrackValueKind::UnitQuaternionEuler => {
|
||||
// Euler angles
|
||||
3
|
||||
}
|
||||
TrackValueKind::UnitQuaternion => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +104,6 @@ pub enum InterpolationMode {
|
||||
pub struct TrackDataContainer {
|
||||
curves: Vec<Curve>,
|
||||
kind: TrackValueKind,
|
||||
/// Interpolation mode.
|
||||
#[visit(optional)] // Backward compatibility.
|
||||
pub mode: InterpolationMode,
|
||||
}
|
||||
|
||||
impl TrackDataContainer {
|
||||
@@ -117,7 +120,6 @@ impl TrackDataContainer {
|
||||
curves: (0..kind.components_count())
|
||||
.map(|_| Curve::default())
|
||||
.collect(),
|
||||
mode: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +208,7 @@ impl TrackDataContainer {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn fetch_quaternion(&self, time: f32) -> Option<TrackValue> {
|
||||
fn fetch_quaternion_euler(&self, time: f32) -> Option<TrackValue> {
|
||||
if self.curves.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
@@ -218,18 +220,11 @@ impl TrackDataContainer {
|
||||
let z_curve = self.curves.get_unchecked(2);
|
||||
|
||||
// Convert Euler angles to quaternion
|
||||
let (x, y, z) = match self.mode {
|
||||
InterpolationMode::Default => (
|
||||
x_curve.value_at(time),
|
||||
y_curve.value_at(time),
|
||||
z_curve.value_at(time),
|
||||
),
|
||||
InterpolationMode::ShortPath => (
|
||||
x_curve.angle_at(time),
|
||||
y_curve.angle_at(time),
|
||||
z_curve.angle_at(time),
|
||||
),
|
||||
};
|
||||
let (x, y, z) = (
|
||||
x_curve.value_at(time),
|
||||
y_curve.value_at(time),
|
||||
z_curve.value_at(time),
|
||||
);
|
||||
|
||||
Some(TrackValue::UnitQuaternion(quat_from_euler(
|
||||
Vector3::new(x, y, z),
|
||||
@@ -238,6 +233,33 @@ impl TrackDataContainer {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn fetch_quaternion(&self, time: f32) -> Option<TrackValue> {
|
||||
if self.curves.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// SAFETY: The indices are guaranteed to be correct by the above check.
|
||||
unsafe {
|
||||
let x_curve = self.curves.get_unchecked(0);
|
||||
let y_curve = self.curves.get_unchecked(1);
|
||||
let z_curve = self.curves.get_unchecked(2);
|
||||
let w_curve = self.curves.get_unchecked(3);
|
||||
|
||||
// Convert Euler angles to quaternion
|
||||
let (x, y, z, w) = (
|
||||
x_curve.value_at(time),
|
||||
y_curve.value_at(time),
|
||||
z_curve.value_at(time),
|
||||
w_curve.value_at(time),
|
||||
);
|
||||
|
||||
Some(TrackValue::UnitQuaternion(UnitQuaternion::from_quaternion(
|
||||
Quaternion::new(w, x, y, z),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to get a value at a given time. The method could fail if the internal set of curves is malformed
|
||||
/// and cannot produce a desired value (for example, [`Vector3`] can be fetched only if the amount of curves
|
||||
/// is 3).
|
||||
@@ -247,6 +269,7 @@ impl TrackDataContainer {
|
||||
TrackValueKind::Vector2 => self.fetch_vector2(time),
|
||||
TrackValueKind::Vector3 => self.fetch_vector3(time),
|
||||
TrackValueKind::Vector4 => self.fetch_vector4(time),
|
||||
TrackValueKind::UnitQuaternionEuler => self.fetch_quaternion_euler(time),
|
||||
TrackValueKind::UnitQuaternion => self.fetch_quaternion(time),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ impl Track {
|
||||
/// Creates a new track that is responsible in animating a rotation property of a scene node.
|
||||
pub fn new_rotation() -> Self {
|
||||
Self {
|
||||
frames: TrackDataContainer::new(TrackValueKind::UnitQuaternion),
|
||||
frames: TrackDataContainer::new(TrackValueKind::UnitQuaternionEuler),
|
||||
binding: ValueBinding::Rotation,
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
use super::iter::*;
|
||||
use super::simplify::*;
|
||||
use crate::core::algebra::{Quaternion, Unit, UnitQuaternion, Vector3};
|
||||
use crate::core::log::Log;
|
||||
use crate::core::math::curve::{Curve, CurveKey, CurveKeyKind};
|
||||
use crate::core::pool::Handle;
|
||||
@@ -118,7 +117,10 @@ impl ImportedTarget {
|
||||
Some(node.local_transform().position().data.as_slice().into())
|
||||
}
|
||||
ImportedBinding::Rotation => Some(
|
||||
quaternion_to_euler(*node.local_transform().rotation().get_value_ref())
|
||||
node.local_transform()
|
||||
.rotation()
|
||||
.get_value_ref()
|
||||
.coords
|
||||
.data
|
||||
.as_slice()
|
||||
.into(),
|
||||
@@ -144,7 +146,7 @@ impl ImportedTrack {
|
||||
target,
|
||||
curves: match target.binding {
|
||||
ImportedBinding::Position => <[Vec<CurveKey>; 3]>::default().into(),
|
||||
ImportedBinding::Rotation => <[Vec<CurveKey>; 3]>::default().into(),
|
||||
ImportedBinding::Rotation => <[Vec<CurveKey>; 4]>::default().into(),
|
||||
ImportedBinding::Scale => <[Vec<CurveKey>; 3]>::default().into(),
|
||||
ImportedBinding::Weight(_) => <[Vec<CurveKey>; 1]>::default().into(),
|
||||
},
|
||||
@@ -428,49 +430,6 @@ fn count_morph_targets(target: &gltf::Node) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn array_to_euler(quaternion: [f32; 4]) -> Vector3<f32> {
|
||||
quaternion_to_euler(Unit::new_normalize(Quaternion::from(quaternion)))
|
||||
}
|
||||
|
||||
// X - pitch, Y - yaw, Z - roll
|
||||
// Or maybe X - roll, Y - yaw, Z - pitch
|
||||
// Or maybe X - roll, Y - pitch, Z - yaw
|
||||
fn quaternion_to_euler(q: UnitQuaternion<f32>) -> Vector3<f32> {
|
||||
let (roll, pitch, yaw) = q.euler_angles();
|
||||
//let (x, y, z) = (pitch, yaw, roll);
|
||||
let (x, y, z) = (roll, pitch, yaw);
|
||||
Vector3::new(x, y, z)
|
||||
}
|
||||
|
||||
fn quaternion_to_euler_tangents(
|
||||
in_tangent: [f32; 4],
|
||||
value: [f32; 4],
|
||||
out_tangent: [f32; 4],
|
||||
) -> (Vector3<f32>, Vector3<f32>) {
|
||||
let a: UnitQuaternion<f32> = Unit::new_normalize(quaternion_subtract(value, in_tangent).into());
|
||||
let v: UnitQuaternion<f32> = Unit::new_normalize(value.into());
|
||||
let b: UnitQuaternion<f32> = Unit::new_normalize(quaternion_add(value, out_tangent).into());
|
||||
(
|
||||
quaternion_to_euler(a.rotation_to(&v)),
|
||||
quaternion_to_euler(v.rotation_to(&b)),
|
||||
)
|
||||
}
|
||||
|
||||
fn quaternion_add(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
|
||||
let mut r = [0f32; 4];
|
||||
for i in 0..4 {
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
r
|
||||
}
|
||||
fn quaternion_subtract(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
|
||||
let mut r = [0f32; 4];
|
||||
for i in 0..4 {
|
||||
r[i] = a[i] - b[i];
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn import_sampler<'a, 's, F>(
|
||||
channel: &'a Channel,
|
||||
target_handle: Handle<Node>,
|
||||
@@ -670,11 +629,11 @@ where
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut track = ImportedTrack::new(target);
|
||||
for i in 0..3 {
|
||||
for i in 0..4 {
|
||||
let curve_keys: Vec<CurveKey> = inputs
|
||||
.clone()
|
||||
.zip(out_iter.clone())
|
||||
.map(|(time, o)| CurveKey::new(time, array_to_euler(o)[i], kind.clone()))
|
||||
.map(|(time, o)| CurveKey::new(time, o[i], kind.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
track.curves[i] = curve_keys;
|
||||
}
|
||||
@@ -701,11 +660,10 @@ where
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut track = ImportedTrack::new(target);
|
||||
for i in 0..3 {
|
||||
for i in 0..4 {
|
||||
let curve_keys: Vec<CurveKey> = iter_cubic_data(inputs.clone(), out_iter.clone())
|
||||
.map(|(time, o)| {
|
||||
let (in_tang, out_tang) = quaternion_to_euler_tangents(o[0], o[1], o[2]);
|
||||
let value = array_to_euler(o[1]);
|
||||
let (in_tang, value, out_tang) = (o[0], o[1], o[2]);
|
||||
CurveKey::new(
|
||||
time,
|
||||
value[i],
|
||||
|
||||
Reference in New Issue
Block a user