Skip to content

Commit

Permalink
Change cubic interpolation of rotations in 3d transform animation tra…
Browse files Browse the repository at this point in the history
…ck so it is predictable.

Keyframe reduce.


Use smooth basis.


Use rotation degrees.


Revert "Use rotation degrees."

This reverts commit 5c43ea0d9001a1d840ddec9cf1e0574ecebd158c.

Try rot_basis.


Remove unused code.


Use rotation quat.


Move function out.


Sort and discard similar times.


Revert to quat beziers.


Revert "Change cubic interpolation of rotations in 3d transform animation track so it is predictable."

This reverts commit 6eb0948284633361d9405b64501bd9e81e60614e.

Don't modify quats.


Enable by default.


Add makima.


Add makima splines.


Change cubic interpolation of rotations in 3d transform animation track so it is predictable.
Log quat has a w component of 0.


Typo.


Exponent map has w as 0.


Use quat exp map.


Interpolate the keys.


Restore quat.


Don't inverse.


Remove zero approx.


Restore older quat.


Use log.


Quat cleanup.


Remove akima.


Add set quat rot log.


Typo missed.


Add quat log.


Quat interpolates!


Typoed.


Add theory.


Remove.


Restore quat log.


Restore old settings.


Update settings.


Restore old code.


Try again.


Don't use degrees internally.


Use degrees.


Cubic again.


Tune defaults.


Always create weight tangents.


Always use auto split tangents.


Always split existing tangents.


Always split angles.


Decrease error tolerance if it is an angle.


Be more intolerant of error.


Unify error


Change step size.


Use code convention.


Remove magic comment.


Vector subtraction is last vs current.


Code convention.


Code conventions.


Restore degs.


Code convention.


Add makima.
Restore.


Checkpoint.


Checkpoint.


Checkpoint.


Checkpoint.


Checkpoint.


Restore rotatation quat.


Revert "Add makima."

This reverts commit 048fdbec4e7a3eba464779baba820e9e6a49ddba.
Add akima.


Remove aikima.


Restore akima.


Revert "Restore rotatation quat."

This reverts commit 4728e1b1d3b67c2da94a0a5c45b346a8cb112b50.

Add exp map.


Revert akima


Checkpoint.


Restore akima.


Remove rotation quat exp map bindings.

Restore saving as quat tracks.
Use linear quat interp.


Force w to be 0.


Meed to set keys.


Restore cubic interp.


Use basis interpolation.


Print reduction.


Use rotation quat.


Use linear interpolation.
  • Loading branch information
fire committed Oct 12, 2021
1 parent 018ca6b commit f4e246c
Show file tree
Hide file tree
Showing 23 changed files with 2,761 additions and 67 deletions.
117 changes: 110 additions & 7 deletions core/math/quat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "quat.h"

#include "core/math/basis.h"
#include "core/math/math_defs.h"
#include "core/print_string.h"

// set_euler_xyz expects a vector containing the Euler angles in the format
Expand Down Expand Up @@ -219,16 +220,118 @@ Quat Quat::slerpni(const Quat &p_to, const real_t &p_weight) const {
invFactor * from.w + newFactor * p_to.w);
}

Quat Quat::cubic_slerp(const Quat &p_b, const Quat &p_pre_a, const Quat &p_post_b, const real_t &p_weight) const {
Quat Quat::cubic_slerp(const Quat &p_q, const Quat &p_prep, const Quat &p_postq, const real_t &p_t) const {
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(!is_normalized(), Quat(), "The start quaternion must be normalized.");
ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quat(), "The end quaternion must be normalized.");
ERR_FAIL_COND_V_MSG(!p_q.is_normalized(), Quat(), "The end quaternion must be normalized.");
#endif
//the only way to do slerp :|
real_t t2 = (1.0 - p_weight) * p_weight * 2;
Quat sp = this->slerp(p_b, p_weight);
Quat sq = p_pre_a.slerpni(p_post_b, p_weight);
return sp.slerpni(sq, t2);
// Modify quaternions for shortest path
// https://math.stackexchange.com/questions/2650188/super-confused-by-squad-algorithm-for-quaternion-interpolation
const Quat q_a = *this;
Quat prep = (q_a - p_prep).length_squared() < (q_a + p_prep).length_squared() ? p_prep : p_prep * -1.0f;
Quat q_b = (q_a - p_q).length_squared() < (q_a + p_q).length_squared() ? p_q : p_q * -1.0f;
Quat postq = (p_q - p_postq).length_squared() < (p_q + p_postq).length_squared() ? p_postq : p_postq * -1.0f;

return prep.spline_segment(q_a, q_b, postq, p_t);
}

Quat Quat::squad(const Quat p_a, const Quat p_b, const Quat p_post, const float p_t) const {
Quat pre = *this;
float slerp_t = 2.0 * p_t * (1.0 - p_t);
Quat slerp_1 = pre.slerpni(p_post, p_t);
Quat slerp_2 = p_a.slerpni(p_b, p_t);
return slerp_1.slerpni(slerp_2, slerp_t);
}

Quat Quat::log() const {
// http://www.cs.jhu.edu/~misha/Fall20/29.pdf Exponential map quat are guaranteed to be rotations
// https://math.stackexchange.com/questions/2552/the-logarithm-of-quaternion
real_t v_norm = Vector3(x, y, z).length();
real_t q_norm = (*this).length();
real_t tolerance = 1e-17;
if (q_norm < tolerance) {
Vector3 vec = Vector3(x, y, z);
vec *= NAN;
return Quat(vec.x, vec.y, vec.z, -INFINITY);
}
if (v_norm < tolerance) {
q_norm = Math::log(q_norm);
// real quaternions - no imaginary part
return Quat(0.0f, 0.0f, 0.0f, Math::log(q_norm));
}
Vector3 vec = Vector3(x, y, z) / v_norm;
vec = acos(w / q_norm) * vec;
return Quat(vec.x, vec.y, vec.z, Math::log(q_norm));
}

Quat Quat::log_map(Quat p_p) const {
// Returns tangent vector
// TODO 2021-06-13 fire Unit test
// Quat q = Quat(Vector3(1.0f, 0.0f, 0.0f), Math_PI);
// Quat log_q = q.log();
// ERR_FAIL_COND_V(!log_q.is_equal_approx(Quat(Math_PI / 2.0f, 0.0f, 0.0f, 0.0f)), Quat());

Quat rot = (*this);
rot = (rot.inverse() * p_p).log();
return rot;
}

Quat Quat::exp_map(Quat p_p) const {
// Returns orientation
// http://www.cs.jhu.edu/~misha/Fall20/29.pdf Exponential map quat are guaranteed to be rotations
// https://math.stackexchange.com/questions/2552/the-logarithm-of-quaternion
// https://github.com/KieranWynn/pyquaternion

// TODO 2021-06-13 fire Unit test
// Quat q = Quat(Vector3(1.0f, 0.0f, 0.0f), Math_PI);
// Quat log_q = q.exp();
// Quat q_multi = Quat(Math::sin(1.f), 0.f, 0.f, Math::cos(1.0f));
// q_multi.x *= Math::exp(0.0f);
// q_multi.y *= Math::exp(0.0f);
// q_multi.z *= Math::exp(0.0f);
// q_multi.w *= Math::exp(0.0f);
// ERR_FAIL_COND_V(!log_q.is_equal_approx(q_multi), Quat());

Quat rot = (*this) * p_p.exp();
Vector3 vec = Vector3(x, y, z);
real_t v_norm = vec.length();
if (Math::is_zero_approx(v_norm)) {
return Quat();
}
rot.normalize();
return rot;
}

Quat Quat::exp() const {
Vector3 vec = Vector3(x, y, z);
real_t v_norm = vec.length();
if (!Math::is_zero_approx(v_norm)) {
vec = vec / v_norm;
}
real_t magnitude = Math::exp(w);
vec = magnitude * sin(v_norm) * vec;
Quat rot = Quat(vec.x, vec.y, vec.z, magnitude * cos(v_norm));
return rot;
}

Quat Quat::intermediate(Quat p_a, Quat p_b) const {
Quat a_inv = p_a.inverse();
Quat c_1 = a_inv * p_b;
Quat c_2 = a_inv * (*this);
c_1 = c_1.log();
c_2 = c_2.log();
Quat c_3 = c_2 + c_1;
c_3 = c_3 * -0.25f;
c_3 = c_3.exp();
Quat r = p_a * c_3;
return r.normalized();
}

Quat Quat::spline_segment(const Quat p_a, const Quat p_b, const Quat p_post, const float p_t) const {
Quat pre = *this;
Quat q_a = pre.intermediate(p_a, p_b);
Quat q_b = p_a.intermediate(p_b, p_post);
return p_a.squad(q_a, q_b, p_b, p_t);
}

Quat::operator String() const {
Expand Down
23 changes: 20 additions & 3 deletions core/math/quat.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Quat {

Quat slerp(const Quat &p_to, const real_t &p_weight) const;
Quat slerpni(const Quat &p_to, const real_t &p_weight) const;
Quat cubic_slerp(const Quat &p_b, const Quat &p_pre_a, const Quat &p_post_b, const real_t &p_weight) const;
Quat cubic_slerp(const Quat &p_q, const Quat &p_prep, const Quat &p_postq, const real_t &p_t) const;

void set_axis_angle(const Vector3 &axis, const real_t &angle);
_FORCE_INLINE_ void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
Expand All @@ -72,8 +72,25 @@ class Quat {
r_axis.z = z * r;
}

void operator*=(const Quat &p_q);
Quat operator*(const Quat &p_q) const;
// Squad (Spherical Spline Quaternions, [Shoemake 1987]) implementation for Unity by Vegard Myklebust.
// Made available under Creative Commons license CC0. License details can be found here:
// https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt
// https://gist.github.com/usefulslug
// Returns a smooth approximation between the current quaternion and post using a and b as 'tangents'
Quat squad(const Quat p_a, const Quat p_b, const Quat p_post, const float p_t) const;
Quat log() const;
Quat exp() const;
Quat exp_map(Quat p_p = Quat()) const;
Quat log_map(Quat p_p = Quat()) const;

// Tries to compute sensible tangent values for the quaternion
Quat intermediate(Quat p_a, Quat p_b) const;

// Returns a quaternion between a and b as part of a smooth squad segment
Quat spline_segment(const Quat p_a, const Quat p_b, const Quat p_post, const float p_t) const;

void operator*=(const Quat &q);
Quat operator*(const Quat &q) const;

Quat operator*(const Vector3 &v) const {
return Quat(w * v.x + y * v.z - z * v.y,
Expand Down
5 changes: 4 additions & 1 deletion editor/animation_track_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5583,7 +5583,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {

} break;
case EDIT_OPTIMIZE_ANIMATION_CONFIRM: {
animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value());
animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value(), optimize_convert_bezier->is_pressed());
_update_tracks();
undo_redo->clear_history();

Expand Down Expand Up @@ -6024,6 +6024,9 @@ AnimationTrackEditor::AnimationTrackEditor() {
optimize_max_angle->set_min(0.0);
optimize_max_angle->set_step(0.1);
optimize_max_angle->set_value(22);
optimize_convert_bezier = memnew(CheckBox);
optimize_vb->add_margin_child(TTR("Convert to bezier curve:"), optimize_convert_bezier);
optimize_convert_bezier->set_pressed(false);

optimize_dialog->get_ok()->set_text(TTR("Optimize"));
optimize_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_OPTIMIZE_ANIMATION_CONFIRM));
Expand Down
1 change: 1 addition & 0 deletions editor/animation_track_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ class AnimationTrackEditor : public VBoxContainer {
SpinBox *optimize_linear_error;
SpinBox *optimize_angular_error;
SpinBox *optimize_max_angle;
CheckBox *optimize_convert_bezier;

ConfirmationDialog *cleanup_dialog;
CheckBox *cleanup_keys;
Expand Down
34 changes: 19 additions & 15 deletions editor/import/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "core/io/resource_saver.h"
#include "editor/editor_node.h"
#include "scene/3d/bone_attachment.h"
#include "scene/3d/collision_shape.h"
#include "scene/3d/mesh_instance.h"
#include "scene/3d/navigation.h"
Expand Down Expand Up @@ -895,21 +896,19 @@ void ResourceImporterScene::_filter_tracks(Node *scene, const String &p_text) {
}
}

void ResourceImporterScene::_optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle) {

if (!scene->has_node(String("AnimationPlayer")))
return;
Node *n = scene->get_node(String("AnimationPlayer"));
ERR_FAIL_COND(!n);
AnimationPlayer *anim = Object::cast_to<AnimationPlayer>(n);
ERR_FAIL_COND(!anim);

List<StringName> anim_names;
anim->get_animation_list(&anim_names);
for (List<StringName>::Element *E = anim_names.front(); E; E = E->next()) {
void ResourceImporterScene::_optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle, bool p_use_convert_bezier) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene);
if (ap) {
List<StringName> anim_names;
ap->get_animation_list(&anim_names);
for (List<StringName>::Element *E = anim_names.front(); E; E = E->next()) {
Ref<Animation> a = ap->get_animation(E->get());
a->optimize(p_max_lin_error, p_max_ang_error, Math::deg2rad(p_max_angle), p_use_convert_bezier);
}
}

Ref<Animation> a = anim->get_animation(E->get());
a->optimize(p_max_lin_error, p_max_ang_error, Math::deg2rad(p_max_angle));
for (int32_t i = 0; i < scene->get_child_count(); i++) {
_optimize_animations(scene->get_child(i), p_max_lin_error, p_max_ang_error, p_max_angle, p_use_convert_bezier);
}
}

Expand Down Expand Up @@ -1162,6 +1161,7 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/location", PROPERTY_HINT_ENUM, "Node,Mesh"), (meshes_out || materials_out) ? 1 : 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "materials/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.material),Files (.tres)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), materials_out ? 1 : 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "materials/keep_on_reimport"), materials_out));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skeleton/point_to_children"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/compress"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/storage", PROPERTY_HINT_ENUM, "Built-In,Files (.mesh),Files (.tres)"), meshes_out ? 1 : 0));
Expand All @@ -1179,6 +1179,7 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in
r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angular_error"), 0.01));
r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angle"), 22));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/remove_unused_tracks"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/convert_bezier/enabled"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clips/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
for (int i = 0; i < 256; i++) {
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/clip_" + itos(i + 1) + "/name"), ""));
Expand Down Expand Up @@ -1364,15 +1365,18 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
bool use_optimizer = p_options["animation/optimizer/enabled"];
float anim_optimizer_linerr = p_options["animation/optimizer/max_linear_error"];
float anim_optimizer_angerr = p_options["animation/optimizer/max_angular_error"];
anim_optimizer_angerr = Math::deg2rad(anim_optimizer_angerr);
float anim_optimizer_maxang = p_options["animation/optimizer/max_angle"];
anim_optimizer_maxang = Math::deg2rad(anim_optimizer_maxang);
int light_bake_mode = p_options["meshes/light_baking"];
bool use_convert_bezier = p_options["animation/optimizer/convert_bezier/enabled"];

Map<Ref<Mesh>, List<Ref<Shape> > > collision_map;

scene = _fix_node(scene, scene, collision_map, LightBakeMode(light_bake_mode));

if (use_optimizer) {
_optimize_animations(scene, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang);
_optimize_animations(scene, anim_optimizer_linerr, anim_optimizer_angerr, anim_optimizer_maxang, use_convert_bezier);
}

Array animation_clips;
Expand Down
3 changes: 2 additions & 1 deletion editor/import/resource_importer_scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ class ResourceImporterScene : public ResourceImporter {
void _create_clips(Node *scene, const Array &p_clips, bool p_bake_all);
void _filter_anim_tracks(Ref<Animation> anim, Set<String> &keep);
void _filter_tracks(Node *scene, const String &p_text);
void _optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle);
void _optimize_animations(Node *scene, float p_max_lin_error, float p_max_ang_error, float p_max_angle, bool p_use_convert_bezier);
void _skeleton_point_to_children(Node *p_scene);

virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL, Variant *r_metadata = NULL);

Expand Down
11 changes: 11 additions & 0 deletions modules/keyframe_reduce/SCsub
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python

Import("env")
Import("env_modules")

env_keyframe_reduce = env_modules.Clone()
thirdparty = "#modules/keyframe_reduce/thirdparty"
env_keyframe_reduce.Prepend(CPPPATH=[thirdparty])

# Godot's own source files
env_keyframe_reduce.add_source_files(env.modules_sources, "*.cpp")
6 changes: 6 additions & 0 deletions modules/keyframe_reduce/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def can_build(env, platform):
return True


def configure(env):
pass
Loading

0 comments on commit f4e246c

Please sign in to comment.