Skip to content

Commit

Permalink
Add curve reduce.
Browse files Browse the repository at this point in the history
  • Loading branch information
fire committed Aug 2, 2023
1 parent 170ba33 commit 110adc8
Show file tree
Hide file tree
Showing 23 changed files with 2,857 additions and 117 deletions.
154 changes: 146 additions & 8 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"

real_t Quat::angle_to(const Quat &p_to) const {
Expand Down Expand Up @@ -222,16 +223,153 @@ 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(!is_normalized(), Quat(), "The start Quat must be normalized.");
ERR_FAIL_COND_V_MSG(!p_q.is_normalized(), Quat(), "The end Quat must be normalized.");
#endif
//the only way to do slerp :|
real_t t2 = (1 - 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);
Quat from_q = *this;
Quat pre_q = p_prep;
Quat to_q = p_q;
Quat post_q = p_postq;

// Align flip phases.
from_q = Basis(from_q).get_rotation_quat();
pre_q = Basis(pre_q).get_rotation_quat();
to_q = Basis(to_q).get_rotation_quat();
post_q = Basis(post_q).get_rotation_quat();

// Flip Quats to shortest path if necessary.
bool flip1 = from_q.dot(pre_q) < 0;
pre_q = flip1 ? -pre_q : pre_q;
bool flip2 = from_q.dot(to_q) < 0;
to_q = flip2 ? -to_q : to_q;
bool flip3 = flip2 ? to_q.dot(post_q) <= 0 : to_q.dot(post_q) < 0;
post_q = flip3 ? -post_q : post_q;

// Calc by Expmap in from_q space.
Quat ln_from = Quat(0, 0, 0, 0);
Quat ln_to = (from_q.inverse() * to_q).log();
Quat ln_pre = (from_q.inverse() * pre_q).log();
Quat ln_post = (from_q.inverse() * post_q).log();
Quat ln = Quat(0, 0, 0, 0);
ln.x = cubic_interpolate(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_t);
ln.y = cubic_interpolate(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_t);
ln.z = cubic_interpolate(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_t);
Quat q1 = from_q * ln.exp();

// Calc by Expmap in to_q space.
ln_from = (to_q.inverse() * from_q).log();
ln_to = Quat(0, 0, 0, 0);
ln_pre = (to_q.inverse() * pre_q).log();
ln_post = (to_q.inverse() * post_q).log();
ln = Quat(0, 0, 0, 0);
ln.x = cubic_interpolate(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_t);
ln.y = cubic_interpolate(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_t);
ln.z = cubic_interpolate(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_t);
Quat q2 = to_q * ln.exp();

// To cancel error made by Expmap ambiguity, do blending.
return q1.slerp(q2, 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
31 changes: 27 additions & 4 deletions core/math/quat.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@ class _NO_DISCARD_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;
static _ALWAYS_INLINE_ float cubic_interpolate(float p_from, float p_to, float p_pre, float p_post, float p_weight) {
return 0.5f *
((p_from * 2.0f) +
(-p_pre + p_to) * p_weight +
(2.0f * p_pre - 5.0f * p_from + 4.0f * p_to - p_post) * (p_weight * p_weight) +
(-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * p_weight));
}
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 {
r_angle = 2 * Math::acos(w);
Expand All @@ -71,8 +77,25 @@ class _NO_DISCARD_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 @@ -5622,7 +5622,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 @@ -6142,6 +6142,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 @@ -439,6 +439,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
33 changes: 19 additions & 14 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 @@ -863,20 +864,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;
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);
}
}
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()) {
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 @@ -1110,7 +1110,8 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in
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, "meshes/octahedral_compression"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/compress", PROPERTY_HINT_FLAGS, "Vertex,Normal,Tangent,Color,TexUV,TexUV2,Bones,Weights,Index"), VS::ARRAY_COMPRESS_DEFAULT >> VS::ARRAY_COMPRESS_BASE));
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));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Enable,Gen Lightmaps", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
Expand All @@ -1127,6 +1128,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 @@ -1323,15 +1325,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;
List<Pair<NodePath, Node *>> node_renames;
scene = _fix_node(scene, scene, collision_map, LightBakeMode(light_bake_mode), node_renames);

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 = nullptr, Variant *r_metadata = nullptr);

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 110adc8

Please sign in to comment.