Merge pull request #54050 from reduz/animation-compression
This commit is contained in:
commit
24fdedfe94
12 changed files with 1860 additions and 95 deletions
|
@ -189,6 +189,15 @@ Quaternion::operator String() const {
|
|||
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")";
|
||||
}
|
||||
|
||||
Vector3 Quaternion::get_axis() const {
|
||||
real_t r = ((real_t)1) / Math::sqrt(1 - w * w);
|
||||
return Vector3(x * r, y * r, z * r);
|
||||
}
|
||||
|
||||
float Quaternion::get_angle() const {
|
||||
return 2 * Math::acos(w);
|
||||
}
|
||||
|
||||
Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) {
|
||||
#ifdef MATH_CHECKS
|
||||
ERR_FAIL_COND_MSG(!p_axis.is_normalized(), "The axis Vector3 must be normalized.");
|
||||
|
|
|
@ -72,6 +72,9 @@ public:
|
|||
Quaternion slerpni(const Quaternion &p_to, const real_t &p_weight) const;
|
||||
Quaternion cubic_slerp(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const;
|
||||
|
||||
Vector3 get_axis() const;
|
||||
float get_angle() const;
|
||||
|
||||
_FORCE_INLINE_ void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
|
||||
r_angle = 2 * Math::acos(w);
|
||||
real_t r = ((real_t)1) / Math::sqrt(1 - w * w);
|
||||
|
|
|
@ -32,9 +32,9 @@
|
|||
#define VECTOR3_H
|
||||
|
||||
#include "core/math/math_funcs.h"
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector3i.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class Basis;
|
||||
|
||||
struct Vector3 {
|
||||
|
@ -103,6 +103,31 @@ struct Vector3 {
|
|||
Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const;
|
||||
Vector3 move_toward(const Vector3 &p_to, const real_t p_delta) const;
|
||||
|
||||
_FORCE_INLINE_ Vector2 octahedron_encode() const {
|
||||
Vector3 n = *this;
|
||||
n /= Math::abs(n.x) + Math::abs(n.y) + Math::abs(n.z);
|
||||
Vector2 o;
|
||||
if (n.z >= 0.0) {
|
||||
o.x = n.x;
|
||||
o.y = n.y;
|
||||
} else {
|
||||
o.x = (1.0 - Math::abs(n.y)) * (n.x >= 0.0 ? 1.0 : -1.0);
|
||||
o.y = (1.0 - Math::abs(n.x)) * (n.y >= 0.0 ? 1.0 : -1.0);
|
||||
}
|
||||
o.x = o.x * 0.5 + 0.5;
|
||||
o.y = o.y * 0.5 + 0.5;
|
||||
return o;
|
||||
}
|
||||
|
||||
static _FORCE_INLINE_ Vector3 octahedron_decode(const Vector2 &p_oct) {
|
||||
Vector2 f(p_oct.x * 2.0 - 1.0, p_oct.y * 2.0 - 1.0);
|
||||
Vector3 n(f.x, f.y, 1.0f - Math::abs(f.x) - Math::abs(f.y));
|
||||
float t = CLAMP(-n.z, 0.0, 1.0);
|
||||
n.x += n.x >= 0 ? -t : t;
|
||||
n.y += n.y >= 0 ? -t : t;
|
||||
return n.normalized();
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Vector3 cross(const Vector3 &p_b) const;
|
||||
_FORCE_INLINE_ real_t dot(const Vector3 &p_b) const;
|
||||
Basis outer(const Vector3 &p_b) const;
|
||||
|
|
|
@ -1581,6 +1581,8 @@ static void _register_variant_builtin_methods() {
|
|||
bind_method(Vector3, bounce, sarray("n"), varray());
|
||||
bind_method(Vector3, reflect, sarray("n"), varray());
|
||||
bind_method(Vector3, sign, sarray(), varray());
|
||||
bind_method(Vector3, octahedron_encode, sarray(), varray());
|
||||
bind_static_method(Vector3, octahedron_decode, sarray("uv"), varray());
|
||||
|
||||
/* Vector3i */
|
||||
|
||||
|
@ -1617,6 +1619,8 @@ static void _register_variant_builtin_methods() {
|
|||
bind_method(Quaternion, slerpni, sarray("to", "weight"), varray());
|
||||
bind_method(Quaternion, cubic_slerp, sarray("b", "pre_a", "post_b", "weight"), varray());
|
||||
bind_method(Quaternion, get_euler, sarray(), varray());
|
||||
bind_method(Quaternion, get_axis, sarray(), varray());
|
||||
bind_method(Quaternion, get_angle, sarray(), varray());
|
||||
|
||||
/* Color */
|
||||
|
||||
|
|
|
@ -215,6 +215,14 @@
|
|||
Clear the animation (clear all tracks and reset all).
|
||||
</description>
|
||||
</method>
|
||||
<method name="compress">
|
||||
<return type="void" />
|
||||
<argument index="0" name="page_size" type="int" default="8192" />
|
||||
<argument index="1" name="fps" type="int" default="120" />
|
||||
<argument index="2" name="split_tolerance" type="float" default="4.0" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="copy_track">
|
||||
<return type="void" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
|
@ -371,6 +379,12 @@
|
|||
Insert a generic key in a given track.
|
||||
</description>
|
||||
</method>
|
||||
<method name="track_is_compressed" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="track_is_enabled" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
|
|
|
@ -90,6 +90,16 @@
|
|||
Returns the dot product of two quaternions.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_angle" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_axis" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_euler" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<description>
|
||||
|
|
|
@ -208,6 +208,17 @@
|
|||
Returns the vector scaled to unit length. Equivalent to [code]v / v.length()[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="octahedron_decode" qualifiers="static">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="uv" type="Vector2" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="octahedron_encode" qualifiers="const">
|
||||
<return type="Vector2" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="operator !=" qualifiers="operator">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
|
|
|
@ -2039,7 +2039,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2;
|
||||
update_mode_rect.size = update_icon->get_size();
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
|
||||
if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
|
||||
draw_texture(update_icon, update_mode_rect.position);
|
||||
}
|
||||
// Make it easier to click.
|
||||
|
@ -2081,7 +2081,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
|
||||
interp_mode_rect.size = icon->get_size();
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
|
||||
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
|
||||
draw_texture(icon, interp_mode_rect.position);
|
||||
}
|
||||
// Make it easier to click.
|
||||
|
@ -2091,7 +2091,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
ofs += icon->get_width() + hsep;
|
||||
interp_mode_rect.size.x += hsep;
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
|
||||
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
|
||||
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
|
||||
interp_mode_rect.size.x += down_icon->get_width();
|
||||
} else {
|
||||
|
@ -2114,7 +2114,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
|
||||
loop_mode_rect.size = icon->get_size();
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
|
||||
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
|
||||
draw_texture(icon, loop_mode_rect.position);
|
||||
}
|
||||
|
||||
|
@ -2124,7 +2124,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
ofs += icon->get_width() + hsep;
|
||||
loop_mode_rect.size.x += hsep;
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
|
||||
if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
|
||||
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
|
||||
loop_mode_rect.size.x += down_icon->get_width();
|
||||
} else {
|
||||
|
@ -2139,7 +2139,7 @@ void AnimationTrackEdit::_notification(int p_what) {
|
|||
{
|
||||
// Erase.
|
||||
|
||||
Ref<Texture2D> icon = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"));
|
||||
Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons"));
|
||||
|
||||
remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2;
|
||||
remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
|
||||
|
@ -2711,60 +2711,63 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
|||
|
||||
// Check keyframes.
|
||||
|
||||
float scale = timeline->get_zoom_scale();
|
||||
int limit = timeline->get_name_limit();
|
||||
int limit_end = get_size().width - timeline->get_buttons_width();
|
||||
// Left Border including space occupied by keyframes on t=0.
|
||||
int limit_start_hitbox = limit - type_icon->get_width();
|
||||
if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
|
||||
|
||||
if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
|
||||
int key_idx = -1;
|
||||
float key_distance = 1e20;
|
||||
float scale = timeline->get_zoom_scale();
|
||||
int limit = timeline->get_name_limit();
|
||||
int limit_end = get_size().width - timeline->get_buttons_width();
|
||||
// Left Border including space occupied by keyframes on t=0.
|
||||
int limit_start_hitbox = limit - type_icon->get_width();
|
||||
|
||||
// Select should happen in the opposite order of drawing for more accurate overlap select.
|
||||
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
|
||||
Rect2 rect = get_key_rect(i, scale);
|
||||
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
|
||||
offset = offset * scale + limit;
|
||||
rect.position.x += offset;
|
||||
if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
|
||||
int key_idx = -1;
|
||||
float key_distance = 1e20;
|
||||
|
||||
if (rect.has_point(pos)) {
|
||||
if (is_key_selectable_by_distance()) {
|
||||
float distance = ABS(offset - pos.x);
|
||||
if (key_idx == -1 || distance < key_distance) {
|
||||
// Select should happen in the opposite order of drawing for more accurate overlap select.
|
||||
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
|
||||
Rect2 rect = get_key_rect(i, scale);
|
||||
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
|
||||
offset = offset * scale + limit;
|
||||
rect.position.x += offset;
|
||||
|
||||
if (rect.has_point(pos)) {
|
||||
if (is_key_selectable_by_distance()) {
|
||||
float distance = ABS(offset - pos.x);
|
||||
if (key_idx == -1 || distance < key_distance) {
|
||||
key_idx = i;
|
||||
key_distance = distance;
|
||||
}
|
||||
} else {
|
||||
// First one does it.
|
||||
key_idx = i;
|
||||
key_distance = distance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key_idx != -1) {
|
||||
if (mb->is_command_pressed() || mb->is_shift_pressed()) {
|
||||
if (editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("deselect_key"), key_idx);
|
||||
} else {
|
||||
emit_signal(SNAME("select_key"), key_idx, false);
|
||||
moving_selection_attempt = true;
|
||||
select_single_attempt = -1;
|
||||
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
} else {
|
||||
// First one does it.
|
||||
key_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("select_key"), key_idx, true);
|
||||
select_single_attempt = -1;
|
||||
} else {
|
||||
select_single_attempt = key_idx;
|
||||
}
|
||||
|
||||
if (key_idx != -1) {
|
||||
if (mb->is_command_pressed() || mb->is_shift_pressed()) {
|
||||
if (editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("deselect_key"), key_idx);
|
||||
} else {
|
||||
emit_signal(SNAME("select_key"), key_idx, false);
|
||||
moving_selection_attempt = true;
|
||||
select_single_attempt = -1;
|
||||
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
} else {
|
||||
if (!editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("select_key"), key_idx, true);
|
||||
select_single_attempt = -1;
|
||||
} else {
|
||||
select_single_attempt = key_idx;
|
||||
}
|
||||
|
||||
moving_selection_attempt = true;
|
||||
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
|
||||
accept_event();
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2996,6 +2999,9 @@ void AnimationTrackEdit::set_in_group(bool p_enable) {
|
|||
}
|
||||
|
||||
void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselection) {
|
||||
if (animation->track_is_compressed(track)) {
|
||||
return; // Compressed keyframes can't be edited
|
||||
}
|
||||
// Left Border including space occupied by keyframes on t=0.
|
||||
int limit_start_hitbox = timeline->get_name_limit() - type_icon->get_width();
|
||||
Rect2 select_rect(limit_start_hitbox, 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height);
|
||||
|
@ -3339,6 +3345,10 @@ void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag, bool
|
|||
}
|
||||
|
||||
void AnimationTrackEditor::_track_remove_request(int p_track) {
|
||||
if (animation->track_is_compressed(p_track)) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Compressed tracks can't be edited or removed. Re-import the animation with compression disabled in order to edit."));
|
||||
return;
|
||||
}
|
||||
int idx = p_track;
|
||||
if (idx >= 0 && idx < animation->get_track_count()) {
|
||||
undo_redo->create_action(TTR("Remove Anim Track"));
|
||||
|
|
|
@ -950,6 +950,13 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool use_compression = node_settings["compression/enabled"];
|
||||
int anim_compression_page_size = node_settings["compression/page_size"];
|
||||
|
||||
if (use_compression) {
|
||||
_compress_animations(ap, anim_compression_page_size);
|
||||
}
|
||||
}
|
||||
|
||||
return p_node;
|
||||
|
@ -1149,6 +1156,15 @@ void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_
|
|||
}
|
||||
}
|
||||
|
||||
void ResourceImporterScene::_compress_animations(AnimationPlayer *anim, int p_page_size_kb) {
|
||||
List<StringName> anim_names;
|
||||
anim->get_animation_list(&anim_names);
|
||||
for (const StringName &E : anim_names) {
|
||||
Ref<Animation> a = anim->get_animation(E);
|
||||
a->compress(p_page_size_kb * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceImporterScene::get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const {
|
||||
switch (p_category) {
|
||||
case INTERNAL_IMPORT_CATEGORY_NODE: {
|
||||
|
@ -1212,6 +1228,8 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
|
|||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_linear_error"), 0.05));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error"), 0.01));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angle"), 22));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compression/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compression/page_size", PROPERTY_HINT_RANGE, "4,512,1,suffix:kb"), 8));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/position", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/rotation", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
|
||||
|
@ -1320,13 +1338,16 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
|
|||
}
|
||||
} break;
|
||||
case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
|
||||
if (p_option.begins_with("animation/optimizer/") && p_option != "animation/optimizer/enabled" && !bool(p_options["animation/optimizer/enabled"])) {
|
||||
if (p_option.begins_with("optimizer/") && p_option != "optimizer/enabled" && !bool(p_options["optimizer/enabled"])) {
|
||||
return false;
|
||||
}
|
||||
if (p_option.begins_with("compression/") && p_option != "compression/enabled" && !bool(p_options["compression/enabled"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_option.begins_with("animation/slice_")) {
|
||||
int max_slice = p_options["animation/slices/amount"];
|
||||
int slice = p_option.get_slice("/", 1).get_slice("_", 1).to_int() - 1;
|
||||
if (p_option.begins_with("slice_")) {
|
||||
int max_slice = p_options["slices/amount"];
|
||||
int slice = p_option.get_slice("_", 1).to_int() - 1;
|
||||
if (slice >= max_slice) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -261,6 +261,7 @@ public:
|
|||
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
|
||||
void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all);
|
||||
void _optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle);
|
||||
void _compress_animations(AnimationPlayer *anim, int p_page_size_kb);
|
||||
|
||||
Node *pre_import(const String &p_source_file);
|
||||
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) override;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -32,6 +32,7 @@
|
|||
#define ANIMATION_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
#define ANIM_MIN_LENGTH 0.001
|
||||
|
||||
|
@ -98,7 +99,7 @@ private:
|
|||
|
||||
struct PositionTrack : public Track {
|
||||
Vector<TKey<Vector3>> positions;
|
||||
|
||||
int32_t compressed_track = -1;
|
||||
PositionTrack() { type = TYPE_POSITION_3D; }
|
||||
};
|
||||
|
||||
|
@ -106,7 +107,7 @@ private:
|
|||
|
||||
struct RotationTrack : public Track {
|
||||
Vector<TKey<Quaternion>> rotations;
|
||||
|
||||
int32_t compressed_track = -1;
|
||||
RotationTrack() { type = TYPE_ROTATION_3D; }
|
||||
};
|
||||
|
||||
|
@ -114,6 +115,7 @@ private:
|
|||
|
||||
struct ScaleTrack : public Track {
|
||||
Vector<TKey<Vector3>> scales;
|
||||
int32_t compressed_track = -1;
|
||||
ScaleTrack() { type = TYPE_SCALE_3D; }
|
||||
};
|
||||
|
||||
|
@ -121,6 +123,7 @@ private:
|
|||
|
||||
struct BlendShapeTrack : public Track {
|
||||
Vector<TKey<float>> blend_shapes;
|
||||
int32_t compressed_track = -1;
|
||||
BlendShapeTrack() { type = TYPE_BLEND_SHAPE; }
|
||||
};
|
||||
|
||||
|
@ -230,6 +233,89 @@ private:
|
|||
real_t step = 0.1;
|
||||
bool loop = false;
|
||||
|
||||
/* Animation compression page format (version 1):
|
||||
*
|
||||
* Animation uses bitwidth based compression separated into small pages. The intention is that pages fit easily in the cache, so decoding is cache efficient.
|
||||
* The page-based nature also makes future animation streaming from disk possible.
|
||||
*
|
||||
* Actual format:
|
||||
*
|
||||
* num_compressed_tracks = bounds.size()
|
||||
* header : (x num_compressed_tracks)
|
||||
* -------
|
||||
* timeline_keys_offset : uint32_t - offset to time keys
|
||||
* timeline_size : uint32_t - amount of time keys
|
||||
* data_keys_offset : uint32_t offset to key data
|
||||
*
|
||||
* time key (uint32_t):
|
||||
* ------------------
|
||||
* frame : bits 0-15 - time offset of key, computed as: page.time_offset + frame * (1.0/fps)
|
||||
* data_key_offset : bits 16-27 - offset to key data, computed as: data_keys_offset * 4 + data_key_offset
|
||||
* data_key_count : bits 28-31 - amount of data keys pointed to, computed as: data_key_count+1 (max 16)
|
||||
*
|
||||
* data key:
|
||||
* ---------
|
||||
* X / Blend Shape : uint16_t - X coordinate of XYZ vector key, or Blend Shape value. If Blend shape, Y and Z are not present and can be ignored.
|
||||
* Y : uint16_t
|
||||
* Z : uint16_t
|
||||
* If data_key_count+1 > 1 (if more than 1 key is stored):
|
||||
* data_bitwidth : uint16_t - This is only present if data_key_count > 1. Contains delta bitwidth information.
|
||||
* X / Blend Shape delta bitwidth: bits 0-3 -
|
||||
* if 0, nothing is present for X (use the first key-value for subsequent keys),
|
||||
* else assume the number of bits present for each element (+ 1 for sign). Assumed always 16 bits, delta max signed 15 bits, with underflow and overflow supported.
|
||||
* Y delta bitwidth : bits 4-7
|
||||
* Z delta bitwidth : bits 8-11
|
||||
* FRAME delta bitwidth : 12-15 bits - always present (obviously), actual bitwidth is FRAME+1
|
||||
* Data key is 4 bytes long for Blend Shapes, 8 bytes long for pos/rot/scale.
|
||||
*
|
||||
* delta keys:
|
||||
* -----------
|
||||
* Compressed format is packed in the following format after the data key, containing delta keys one after the next in a tightly bit packed fashion.
|
||||
* FRAME bits -> X / Blend Shape Bits (if bitwidth > 0) -> Y Bits (if not Blend Shape and Y Bitwidth > 0) -> Z Bits (if not Blend Shape and Z Bitwidth > 0)
|
||||
*
|
||||
* data key format:
|
||||
* ----------------
|
||||
* Decoding keys means starting from the base key and going key by key applying deltas until the proper position is reached needed for interpolation.
|
||||
* Resulting values are uint32_t
|
||||
* data for X / Blend Shape, Y and Z must be normalized first: unorm = float(data) / 65535.0
|
||||
* **Blend Shape**: (unorm * 2.0 - 1.0) * Compression::BLEND_SHAPE_RANGE
|
||||
* **Pos/Scale**: unorm_vec3 * bounds[track].size + bounds[track].position
|
||||
* **Rotation**: Quaternion(Vector3::octahedron_decode(unorm_vec3.xy),unorm_vec3.z * Math_PI * 2.0)
|
||||
* **Frame**: page.time_offset + frame * (1.0/fps)
|
||||
*/
|
||||
|
||||
struct Compression {
|
||||
enum {
|
||||
MAX_DATA_TRACK_SIZE = 16384,
|
||||
BLEND_SHAPE_RANGE = 8, // - 8.0 to 8.0
|
||||
FORMAT_VERSION = 1
|
||||
};
|
||||
struct Page {
|
||||
Vector<uint8_t> data;
|
||||
double time_offset;
|
||||
};
|
||||
|
||||
uint32_t fps = 120;
|
||||
LocalVector<Page> pages;
|
||||
LocalVector<AABB> bounds; //used by position and scale tracks (which contain index to track and index to bounds).
|
||||
bool enabled = false;
|
||||
} compression;
|
||||
|
||||
Vector3i _compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key = -1, float p_time = 0.0);
|
||||
bool _rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const;
|
||||
bool _pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const;
|
||||
bool _blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const;
|
||||
template <uint32_t COMPONENTS>
|
||||
bool _fetch_compressed(uint32_t p_compressed_track, double p_time, Vector3i &r_current_value, double &r_current_time, Vector3i &r_next_value, double &r_next_time, uint32_t *key_index = nullptr) const;
|
||||
template <uint32_t COMPONENTS>
|
||||
bool _fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const;
|
||||
int _get_compressed_key_count(uint32_t p_compressed_track) const;
|
||||
template <uint32_t COMPONENTS>
|
||||
void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const;
|
||||
_FORCE_INLINE_ Quaternion _uncompress_quaternion(const Vector3i &p_value) const;
|
||||
_FORCE_INLINE_ Vector3 _uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const;
|
||||
_FORCE_INLINE_ float _uncompress_blend_shape(const Vector3i &p_value) const;
|
||||
|
||||
// bind helpers
|
||||
private:
|
||||
Vector<int> _value_track_get_key_indices(int p_track, double p_time, double p_delta) const {
|
||||
|
@ -305,6 +391,7 @@ public:
|
|||
Variant track_get_key_value(int p_track, int p_key_idx) const;
|
||||
double track_get_key_time(int p_track, int p_key_idx) const;
|
||||
real_t track_get_key_transition(int p_track, int p_key_idx) const;
|
||||
bool track_is_compressed(int p_track) const;
|
||||
|
||||
int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position);
|
||||
Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const;
|
||||
|
@ -375,6 +462,7 @@ public:
|
|||
void clear();
|
||||
|
||||
void optimize(real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125);
|
||||
void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests
|
||||
|
||||
Animation();
|
||||
~Animation();
|
||||
|
|
Loading…
Reference in a new issue