Rework blending method in Variant animation for Int/Array/String

This commit is contained in:
Silc Lizard (Tokage) Renew 2023-11-10 21:29:45 +09:00
parent d5217b68db
commit 80c9533810
7 changed files with 534 additions and 374 deletions

View file

@ -285,6 +285,10 @@ struct _NO_DISCARD_ Rect2 {
return Rect2(Point2(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0)), size.abs());
}
_FORCE_INLINE_ Rect2 round() const {
return Rect2(position.round(), size.round());
}
Vector2 get_support(const Vector2 &p_normal) const {
Vector2 half_extents = size * 0.5f;
Vector2 ofs = position + half_extents;

View file

@ -338,6 +338,9 @@ public:
_FORCE_INLINE_ bool is_num() const {
return type == INT || type == FLOAT;
}
_FORCE_INLINE_ bool is_string() const {
return type == STRING || type == STRING_NAME;
}
_FORCE_INLINE_ bool is_array() const {
return type >= ARRAY;
}

View file

@ -667,6 +667,7 @@ bool AnimationMixer::_update_caches() {
track_value->init_value = reset_anim->track_get_key_value(rt, 0);
}
}
} break;
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
@ -812,6 +813,7 @@ bool AnimationMixer::_update_caches() {
track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
}
}
} break;
case Animation::TYPE_AUDIO: {
TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
@ -868,43 +870,26 @@ bool AnimationMixer::_update_caches() {
track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
// TODO: Currently, misc type cannot be blended. In the future,
// it should have a separate blend weight, just as bool is converted to 0 and 1.
// TODO: Currently, misc type cannot be blended.
// In the future, it should have a separate blend weight, just as bool is converted to 0 and 1.
// Then, it should provide the correct precedence value.
bool skip_update_mode_warning = false;
if (track_value->is_continuous) {
switch (track_value->init_value.get_type()) {
case Variant::NIL:
case Variant::STRING_NAME:
case Variant::NODE_PATH:
case Variant::RID:
case Variant::OBJECT:
case Variant::CALLABLE:
case Variant::SIGNAL:
case Variant::DICTIONARY:
case Variant::ARRAY:
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_STRING_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY: {
WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
track_value->is_continuous = false;
break;
}
default: {
}
if (!Animation::is_variant_interpolatable(track_value->init_value)) {
WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
track_value->is_continuous = false;
skip_update_mode_warning = true;
}
if (track_value->init_value.is_string()) {
WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm.");
}
}
if (was_continuous != track_value->is_continuous) {
WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) {
WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
}
if (was_using_angle != track_value->is_using_angle) {
WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different interpolation types for rotation between some animations may be blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
}
}
@ -950,9 +935,7 @@ bool AnimationMixer::_update_caches() {
void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
_blend_init();
if (_blend_pre_process(p_delta, track_count, track_map)) {
if (!deterministic) {
_blend_calc_total_weight();
}
_blend_calc_total_weight();
_blend_process(p_delta, p_update_only);
_blend_apply();
_blend_post_process();
@ -1024,7 +1007,8 @@ void AnimationMixer::_blend_init() {
} break;
case Animation::TYPE_VALUE: {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
t->value = t->init_value;
t->value = Animation::cast_to_blendwise(t->init_value);
t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0;
} break;
case Animation::TYPE_BEZIER: {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
@ -1111,7 +1095,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
if (!deterministic) {
// If undeterministic, do normalization.
// If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
if (Math::is_zero_approx(track->total_weight)) {
continue;
@ -1434,13 +1418,15 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU);
} else {
if (t->init_value.get_type() == Variant::BOOL) {
value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t());
t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend);
} else {
value = Animation::subtract_variant(value, t->init_value);
t->value = Animation::blend_variant(t->value, value, blend);
value = Animation::cast_to_blendwise(value);
if (t->init_value.is_array()) {
t->element_size = MAX(t->element_size.operator int(), (value.operator Array()).size());
} else if (t->init_value.is_string()) {
real_t length = Animation::subtract_variant((real_t)(value.operator Array()).size(), (real_t)(t->init_value.operator String()).length());
t->element_size = Animation::blend_variant(t->element_size, length, blend);
}
value = Animation::subtract_variant(value, Animation::cast_to_blendwise(t->init_value));
t->value = Animation::blend_variant(t->value, value, blend);
}
} else {
if (seeked) {
@ -1710,11 +1696,19 @@ void AnimationMixer::_blend_apply() {
break; // Don't overwrite the value set by UPDATE_DISCRETE.
}
if (t->init_value.get_type() == Variant::BOOL) {
t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5);
} else {
t->object->set_indexed(t->subpath, t->value);
// Trim unused elements if init array/string is not blended.
if (t->value.is_array()) {
int actual_blended_size = (int)Math::round(Math::abs(t->element_size.operator real_t()));
if (actual_blended_size < (t->value.operator Array()).size()) {
real_t abs_weight = Math::abs(track->total_weight);
if (abs_weight >= 1.0) {
(t->value.operator Array()).resize(actual_blended_size);
} else if (t->init_value.is_string()) {
(t->value.operator Array()).resize(Animation::interpolate_variant((t->init_value.operator String()).length(), actual_blended_size, abs_weight));
}
}
}
t->object->set_indexed(t->subpath, Animation::cast_from_blendwise(t->value, t->init_value.get_type()));
} break;
case Animation::TYPE_BEZIER: {

View file

@ -186,6 +186,7 @@ protected:
Vector<StringName> subpath;
bool is_continuous = false;
bool is_using_angle = false;
Variant element_size;
TrackCacheValue() { type = Animation::TYPE_VALUE; }
};

View file

@ -420,13 +420,8 @@ Variant Tween::interpolate_variant(const Variant &p_initial_val, const Variant &
ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant());
ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant());
// Special case for bool.
if (p_initial_val.get_type() == Variant::BOOL) {
return run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration) >= 0.5;
}
Variant ret = Animation::add_variant(p_initial_val, p_delta_val);
ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration));
ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration), p_initial_val.is_string());
return ret;
}

View file

@ -5476,469 +5476,622 @@ bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_in
}
// Helper math functions for Variant.
bool Animation::is_variant_interpolatable(const Variant p_value) {
Variant::Type type = p_value.get_type();
return (type >= Variant::BOOL && type <= Variant::STRING_NAME) || type == Variant::ARRAY || type >= Variant::PACKED_INT32_ARRAY; // PackedByteArray is unsigned, so it would be better to ignore since blending uses float.
}
Variant Animation::cast_to_blendwise(const Variant p_value) {
switch (p_value.get_type()) {
case Variant::BOOL:
case Variant::INT: {
return p_value.operator real_t();
} break;
case Variant::STRING:
case Variant::STRING_NAME: {
return string_to_array(p_value);
} break;
case Variant::RECT2I: {
return p_value.operator Rect2();
} break;
case Variant::VECTOR2I: {
return p_value.operator Vector2();
} break;
case Variant::VECTOR3I: {
return p_value.operator Vector3();
} break;
case Variant::VECTOR4I: {
return p_value.operator Vector4();
} break;
case Variant::PACKED_INT32_ARRAY: {
return p_value.operator PackedFloat32Array();
} break;
case Variant::PACKED_INT64_ARRAY: {
return p_value.operator PackedFloat64Array();
} break;
default: {
} break;
}
return p_value;
}
Variant Animation::cast_from_blendwise(const Variant p_value, const Variant::Type p_type) {
switch (p_type) {
case Variant::BOOL: {
return p_value.operator real_t() >= 0.5;
} break;
case Variant::INT: {
return (int)Math::round(p_value.operator real_t());
} break;
case Variant::STRING: {
return array_to_string(p_value);
} break;
case Variant::STRING_NAME: {
return StringName(array_to_string(p_value));
} break;
case Variant::RECT2I: {
return Rect2i(p_value.operator Rect2().round());
} break;
case Variant::VECTOR2I: {
return Vector2i(p_value.operator Vector2().round());
} break;
case Variant::VECTOR3I: {
return Vector3i(p_value.operator Vector3().round());
} break;
case Variant::VECTOR4I: {
return Vector4i(p_value.operator Vector4().round());
} break;
case Variant::PACKED_INT32_ARRAY: {
PackedFloat32Array old_val = p_value.operator PackedFloat32Array();
PackedInt32Array new_val;
new_val.resize(old_val.size());
int *new_val_w = new_val.ptrw();
for (int i = 0; i < old_val.size(); i++) {
new_val_w[i] = (int32_t)Math::round(old_val[i]);
}
return new_val;
} break;
case Variant::PACKED_INT64_ARRAY: {
PackedFloat64Array old_val = p_value.operator PackedFloat64Array();
PackedInt64Array new_val;
for (int i = 0; i < old_val.size(); i++) {
new_val.push_back((int64_t)Math::round(old_val[i]));
}
return new_val;
} break;
default: {
} break;
}
return p_value;
}
Variant Animation::string_to_array(const Variant p_value) {
if (!p_value.is_string()) {
return p_value;
};
const String &str = p_value.operator String();
PackedFloat32Array arr;
for (int i = 0; i < str.length(); i++) {
arr.push_back((float)str[i]);
}
return arr;
}
Variant Animation::array_to_string(const Variant p_value) {
if (!p_value.is_array()) {
return p_value;
};
const PackedFloat32Array &arr = p_value.operator PackedFloat32Array();
String str;
for (int i = 0; i < arr.size(); i++) {
char32_t c = (char32_t)Math::round(arr[i]);
if (c == 0 || (c & 0xfffff800) == 0xd800 || c > 0x10ffff) {
c = ' ';
}
str += c;
}
return str;
}
Variant Animation::add_variant(const Variant &a, const Variant &b) {
if (a.get_type() != b.get_type()) {
if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
}
case Variant::BOOL: {
return (a.operator real_t()) + (b.operator real_t()); // It is cast for interpolation.
}
} break;
case Variant::FLOAT: {
return (a.operator real_t()) + (b.operator real_t());
} break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position, ra.size + rb.size);
}
case Variant::RECT2I: {
const Rect2i ra = a.operator Rect2i();
const Rect2i rb = b.operator Rect2i();
return Rect2i(ra.position + rb.position, ra.size + rb.size);
}
} break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal, pa.d + pb.d);
}
} break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position, aa.size + ab.size);
}
} break;
case Variant::BASIS: {
return (a.operator Basis()) * (b.operator Basis());
}
} break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * (b.operator Quaternion());
}
} break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * (b.operator Transform2D());
}
} break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * (b.operator Transform3D());
}
} break;
case Variant::INT:
case Variant::RECT2I:
case Variant::VECTOR2I:
case Variant::VECTOR3I:
case Variant::VECTOR4I:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
// Fallback the interpolatable value which needs casting.
return cast_from_blendwise(add_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
} break;
case Variant::BOOL:
case Variant::STRING:
case Variant::STRING_NAME: {
// Specialized for Tween.
return b;
} break;
case Variant::PACKED_BYTE_ARRAY: {
// Skip.
} break;
default: {
return Variant::evaluate(Variant::OP_ADD, a, b);
}
if (a.is_array()) {
const Array arr_a = a.operator Array();
const Array arr_b = b.operator Array();
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
Array result;
result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
result.resize(min_size);
int i = 0;
for (; i < min_size; i++) {
result[i] = add_variant(arr_a[i], arr_b[i]);
}
if (min_size != max_size) {
// Process with last element of the lesser array.
// This is pretty funny and bizarre, but artists like to use it for polygon animation.
Variant lesser_last;
result.resize(max_size);
if (is_a_larger) {
if (i > 0) {
lesser_last = arr_b[i - 1];
} else {
Variant vz = arr_a[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = add_variant(arr_a[i], lesser_last);
}
} else {
if (i > 0) {
lesser_last = arr_a[i - 1];
} else {
Variant vz = arr_b[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = add_variant(lesser_last, arr_b[i]);
}
}
}
return result;
}
} break;
}
return Variant::evaluate(Variant::OP_ADD, a, b);
}
Variant Animation::subtract_variant(const Variant &a, const Variant &b) {
if (a.get_type() != b.get_type()) {
if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
}
case Variant::BOOL: {
return (a.operator real_t()) - (b.operator real_t()); // It is cast for interpolation.
}
} break;
case Variant::FLOAT: {
return (a.operator real_t()) - (b.operator real_t());
} break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position - rb.position, ra.size - rb.size);
}
case Variant::RECT2I: {
const Rect2i ra = a.operator Rect2i();
const Rect2i rb = b.operator Rect2i();
return Rect2i(ra.position - rb.position, ra.size - rb.size);
}
} break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal - pb.normal, pa.d - pb.d);
}
} break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position - ab.position, aa.size - ab.size);
}
} break;
case Variant::BASIS: {
return (b.operator Basis()).inverse() * (a.operator Basis());
}
} break;
case Variant::QUATERNION: {
return (b.operator Quaternion()).inverse() * (a.operator Quaternion());
}
} break;
case Variant::TRANSFORM2D: {
return (b.operator Transform2D()).affine_inverse() * (a.operator Transform2D());
}
} break;
case Variant::TRANSFORM3D: {
return (b.operator Transform3D()).affine_inverse() * (a.operator Transform3D());
}
} break;
case Variant::INT:
case Variant::RECT2I:
case Variant::VECTOR2I:
case Variant::VECTOR3I:
case Variant::VECTOR4I:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
// Fallback the interpolatable value which needs casting.
return cast_from_blendwise(subtract_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
} break;
case Variant::BOOL:
case Variant::STRING:
case Variant::STRING_NAME: {
// Specialized for Tween.
return a;
} break;
case Variant::PACKED_BYTE_ARRAY: {
// Skip.
} break;
default: {
return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
}
if (a.is_array()) {
const Array arr_a = a.operator Array();
const Array arr_b = b.operator Array();
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
Array result;
result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
result.resize(min_size);
int i = 0;
for (; i < min_size; i++) {
result[i] = subtract_variant(arr_a[i], arr_b[i]);
}
if (min_size != max_size) {
// Process with last element of the lesser array.
// This is pretty funny and bizarre, but artists like to use it for polygon animation.
Variant lesser_last;
result.resize(max_size);
if (is_a_larger) {
if (i > 0) {
lesser_last = arr_b[i - 1];
} else {
Variant vz = arr_a[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = subtract_variant(arr_a[i], lesser_last);
}
} else {
if (i > 0) {
lesser_last = arr_a[i - 1];
} else {
Variant vz = arr_b[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = subtract_variant(lesser_last, arr_b[i]);
}
}
}
return result;
}
} break;
}
return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
}
Variant Animation::blend_variant(const Variant &a, const Variant &b, float c) {
if (a.get_type() != b.get_type()) {
if (a.is_num() && b.is_num()) {
double va = a;
double vb = b;
return va + vb * c;
}
if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
}
case Variant::INT: {
return int64_t((a.operator int64_t()) + (b.operator int64_t()) * c + 0.5);
}
} break;
case Variant::FLOAT: {
return (a.operator double()) + (b.operator double()) * c;
}
return (a.operator real_t()) + (b.operator real_t()) * c;
} break;
case Variant::VECTOR2: {
return (a.operator Vector2()) + (b.operator Vector2()) * c;
}
case Variant::VECTOR2I: {
const Vector2i va = a.operator Vector2i();
const Vector2i vb = b.operator Vector2i();
return Vector2i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5));
}
} break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position * c, ra.size + rb.size * c);
}
case Variant::RECT2I: {
const Rect2i ra = a.operator Rect2i();
const Rect2i rb = b.operator Rect2i();
return Rect2i(int32_t(ra.position.x + rb.position.x * c + 0.5), int32_t(ra.position.y + rb.position.y * c + 0.5), int32_t(ra.size.x + rb.size.x * c + 0.5), int32_t(ra.size.y + rb.size.y * c + 0.5));
}
} break;
case Variant::VECTOR3: {
return (a.operator Vector3()) + (b.operator Vector3()) * c;
}
case Variant::VECTOR3I: {
const Vector3i va = a.operator Vector3i();
const Vector3i vb = b.operator Vector3i();
return Vector3i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5));
}
} break;
case Variant::VECTOR4: {
return (a.operator Vector4()) + (b.operator Vector4()) * c;
}
case Variant::VECTOR4I: {
const Vector4i va = a.operator Vector4i();
const Vector4i vb = b.operator Vector4i();
return Vector4i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5), int32_t(va.w + vb.w * c + 0.5));
}
} break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal * c, pa.d + pb.d * c);
}
} break;
case Variant::COLOR: {
return (a.operator Color()) + (b.operator Color()) * c;
}
} break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position * c, aa.size + ab.size * c);
}
} break;
case Variant::BASIS: {
return (a.operator Basis()) + (b.operator Basis()) * c;
}
} break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * Quaternion().slerp((b.operator Quaternion()), c);
}
} break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * Transform2D().interpolate_with((b.operator Transform2D()), c);
}
} break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * Transform3D().interpolate_with((b.operator Transform3D()), c);
}
} break;
case Variant::BOOL:
case Variant::INT:
case Variant::RECT2I:
case Variant::VECTOR2I:
case Variant::VECTOR3I:
case Variant::VECTOR4I:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
// Fallback the interpolatable value which needs casting.
return cast_from_blendwise(blend_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
} break;
case Variant::STRING:
case Variant::STRING_NAME: {
Array arr_a = cast_to_blendwise(a);
Array arr_b = cast_to_blendwise(b);
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
if (is_a_larger) {
arr_a.resize(mid_size);
} else {
arr_b.resize(mid_size);
}
return cast_from_blendwise(blend_variant(arr_a, arr_b, c), a.get_type());
} break;
case Variant::PACKED_BYTE_ARRAY: {
// Skip.
} break;
default: {
return c < 0.5 ? a : b;
}
if (a.is_array()) {
const Array arr_a = a.operator Array();
const Array arr_b = b.operator Array();
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
Array result;
result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
result.resize(min_size);
int i = 0;
for (; i < min_size; i++) {
result[i] = blend_variant(arr_a[i], arr_b[i], c);
}
if (min_size != max_size) {
// Process with last element of the lesser array.
// This is pretty funny and bizarre, but artists like to use it for polygon animation.
Variant lesser_last;
if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
result.resize(max_size);
if (i > 0) {
lesser_last = arr_b[i - 1];
} else {
Variant vz = arr_a[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = blend_variant(arr_a[i], lesser_last, c);
}
} else if (!is_a_larger && !Math::is_zero_approx(c)) {
result.resize(max_size);
if (i > 0) {
lesser_last = arr_a[i - 1];
} else {
Variant vz = arr_b[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = blend_variant(lesser_last, arr_b[i], c);
}
}
}
return result;
}
} break;
}
return c < 0.5 ? a : b;
}
Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c) {
if (a.get_type() != b.get_type()) {
if (a.is_num() && b.is_num()) {
double va = a;
double vb = b;
return va + (vb - va) * c;
}
Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element) {
if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
}
case Variant::INT: {
const int64_t va = a.operator int64_t();
return int64_t(va + ((b.operator int64_t()) - va) * c);
}
} break;
case Variant::FLOAT: {
const double va = a.operator double();
return va + ((b.operator double()) - va) * c;
}
const real_t va = a.operator real_t();
return va + ((b.operator real_t()) - va) * c;
} break;
case Variant::VECTOR2: {
return (a.operator Vector2()).lerp(b.operator Vector2(), c);
}
case Variant::VECTOR2I: {
const Vector2i va = a.operator Vector2i();
const Vector2i vb = b.operator Vector2i();
return Vector2i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c));
}
} break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position.lerp(rb.position, c), ra.size.lerp(rb.size, c));
}
case Variant::RECT2I: {
const Rect2i ra = a.operator Rect2i();
const Rect2i rb = b.operator Rect2i();
return Rect2i(int32_t(ra.position.x + (rb.position.x - ra.position.x) * c), int32_t(ra.position.y + (rb.position.y - ra.position.y) * c), int32_t(ra.size.x + (rb.size.x - ra.size.x) * c), int32_t(ra.size.y + (rb.size.y - ra.size.y) * c));
}
} break;
case Variant::VECTOR3: {
return (a.operator Vector3()).lerp(b.operator Vector3(), c);
}
case Variant::VECTOR3I: {
const Vector3i va = a.operator Vector3i();
const Vector3i vb = b.operator Vector3i();
return Vector3i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c));
}
} break;
case Variant::VECTOR4: {
return (a.operator Vector4()).lerp(b.operator Vector4(), c);
}
case Variant::VECTOR4I: {
const Vector4i va = a.operator Vector4i();
const Vector4i vb = b.operator Vector4i();
return Vector4i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c), int32_t(va.w + (vb.w - va.w) * c));
}
} break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal.lerp(pb.normal, c), pa.d + (pb.d - pa.d) * c);
}
} break;
case Variant::COLOR: {
return (a.operator Color()).lerp(b.operator Color(), c);
}
} break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position.lerp(ab.position, c), aa.size.lerp(ab.size, c));
}
} break;
case Variant::BASIS: {
return (a.operator Basis()).lerp(b.operator Basis(), c);
}
} break;
case Variant::QUATERNION: {
return (a.operator Quaternion()).slerp(b.operator Quaternion(), c);
}
} break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()).interpolate_with(b.operator Transform2D(), c);
}
} break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()).interpolate_with(b.operator Transform3D(), c);
}
case Variant::STRING: {
// This is pretty funny and bizarre, but artists like to use it for typewriter effects.
const String sa = a.operator String();
const String sb = b.operator String();
String dst;
int sa_len = sa.length();
int sb_len = sb.length();
int csize = sa_len + (sb_len - sa_len) * c;
if (csize == 0) {
return "";
}
dst.resize(csize + 1);
dst[csize] = 0;
int split = csize / 2;
for (int i = 0; i < csize; i++) {
char32_t chr = ' ';
if (i < split) {
if (i < sa.length()) {
chr = sa[i];
} else if (i < sb.length()) {
chr = sb[i];
}
} else {
if (i < sb.length()) {
chr = sb[i];
} else if (i < sa.length()) {
chr = sa[i];
}
}
dst[i] = chr;
}
return dst;
}
case Variant::PACKED_INT32_ARRAY: {
const Vector<int32_t> arr_a = a;
const Vector<int32_t> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<int32_t> v;
v.resize(sz);
{
int32_t *vw = v.ptrw();
const int32_t *ar = arr_a.ptr();
const int32_t *br = arr_b.ptr();
Variant va;
for (int i = 0; i < sz; i++) {
va = interpolate_variant(ar[i], br[i], c);
vw[i] = va;
}
}
return v;
}
}
} break;
case Variant::BOOL:
case Variant::INT:
case Variant::RECT2I:
case Variant::VECTOR2I:
case Variant::VECTOR3I:
case Variant::VECTOR4I:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
const Vector<int64_t> arr_a = a;
const Vector<int64_t> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
// Fallback the interpolatable value which needs casting.
return cast_from_blendwise(interpolate_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
} break;
case Variant::STRING:
case Variant::STRING_NAME: {
Array arr_a = cast_to_blendwise(a);
Array arr_b = cast_to_blendwise(b);
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
if (is_a_larger) {
arr_a.resize(mid_size);
} else {
Vector<int64_t> v;
v.resize(sz);
{
int64_t *vw = v.ptrw();
const int64_t *ar = arr_a.ptr();
const int64_t *br = arr_b.ptr();
Variant va;
for (int i = 0; i < sz; i++) {
va = interpolate_variant(ar[i], br[i], c);
vw[i] = va;
}
}
return v;
arr_b.resize(mid_size);
}
}
case Variant::PACKED_FLOAT32_ARRAY: {
const Vector<float> arr_a = a;
const Vector<float> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<float> v;
v.resize(sz);
{
float *vw = v.ptrw();
const float *ar = arr_a.ptr();
const float *br = arr_b.ptr();
Variant va;
for (int i = 0; i < sz; i++) {
va = interpolate_variant(ar[i], br[i], c);
vw[i] = va;
}
}
return v;
}
}
case Variant::PACKED_FLOAT64_ARRAY: {
const Vector<double> arr_a = a;
const Vector<double> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<double> v;
v.resize(sz);
{
double *vw = v.ptrw();
const double *ar = arr_a.ptr();
const double *br = arr_b.ptr();
Variant va;
for (int i = 0; i < sz; i++) {
va = interpolate_variant(ar[i], br[i], c);
vw[i] = va;
}
}
return v;
}
}
case Variant::PACKED_VECTOR2_ARRAY: {
const Vector<Vector2> arr_a = a;
const Vector<Vector2> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<Vector2> v;
v.resize(sz);
{
Vector2 *vw = v.ptrw();
const Vector2 *ar = arr_a.ptr();
const Vector2 *br = arr_b.ptr();
for (int i = 0; i < sz; i++) {
vw[i] = ar[i].lerp(br[i], c);
}
}
return v;
}
}
case Variant::PACKED_VECTOR3_ARRAY: {
const Vector<Vector3> arr_a = a;
const Vector<Vector3> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<Vector3> v;
v.resize(sz);
{
Vector3 *vw = v.ptrw();
const Vector3 *ar = arr_a.ptr();
const Vector3 *br = arr_b.ptr();
for (int i = 0; i < sz; i++) {
vw[i] = ar[i].lerp(br[i], c);
}
}
return v;
}
}
case Variant::PACKED_COLOR_ARRAY: {
const Vector<Color> arr_a = a;
const Vector<Color> arr_b = b;
int sz = arr_a.size();
if (sz == 0 || arr_b.size() != sz) {
return a;
} else {
Vector<Color> v;
v.resize(sz);
{
Color *vw = v.ptrw();
const Color *ar = arr_a.ptr();
const Color *br = arr_b.ptr();
for (int i = 0; i < sz; i++) {
vw[i] = ar[i].lerp(br[i], c);
}
}
return v;
}
}
return cast_from_blendwise(interpolate_variant(arr_a, arr_b, c, true), a.get_type());
} break;
case Variant::PACKED_BYTE_ARRAY: {
// Skip.
} break;
default: {
return c < 0.5 ? a : b;
}
if (a.is_array()) {
const Array arr_a = a.operator Array();
const Array arr_b = b.operator Array();
int min_size = arr_a.size();
int max_size = arr_b.size();
bool is_a_larger = inform_variant_array(min_size, max_size);
Array result;
result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
result.resize(min_size);
int i = 0;
for (; i < min_size; i++) {
result[i] = interpolate_variant(arr_a[i], arr_b[i], c);
}
if (min_size != max_size) {
// Process with last element of the lesser array.
// This is pretty funny and bizarre, but artists like to use it for polygon animation.
Variant lesser_last;
if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
result.resize(max_size);
if (p_snap_array_element) {
c = 0;
}
if (i > 0) {
lesser_last = arr_b[i - 1];
} else {
Variant vz = arr_a[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = interpolate_variant(arr_a[i], lesser_last, c);
}
} else if (!is_a_larger && !Math::is_zero_approx(c)) {
result.resize(max_size);
if (p_snap_array_element) {
c = 1;
}
if (i > 0) {
lesser_last = arr_a[i - 1];
} else {
Variant vz = arr_b[i];
vz.zero();
lesser_last = vz;
}
for (; i < max_size; i++) {
result[i] = interpolate_variant(lesser_last, arr_b[i], c);
}
}
}
return result;
}
} break;
}
return c < 0.5 ? a : b;
}
bool Animation::inform_variant_array(int &r_min, int &r_max) {
if (r_min <= r_max) {
return false;
}
SWAP(r_min, r_max);
return true;
}
Animation::Animation() {

View file

@ -373,6 +373,8 @@ protected:
static void _bind_methods();
static bool inform_variant_array(int &r_min, int &r_max); // Returns true if max and min are swapped.
public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
@ -487,11 +489,19 @@ public:
void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3);
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
// Helper math functions for Variant.
// Helper functions for Variant.
static bool is_variant_interpolatable(const Variant p_value);
static Variant cast_to_blendwise(const Variant p_value);
static Variant cast_from_blendwise(const Variant p_value, const Variant::Type p_type);
static Variant string_to_array(const Variant p_value);
static Variant array_to_string(const Variant p_value);
static Variant add_variant(const Variant &a, const Variant &b);
static Variant subtract_variant(const Variant &a, const Variant &b);
static Variant blend_variant(const Variant &a, const Variant &b, float c);
static Variant interpolate_variant(const Variant &a, const Variant &b, float c);
static Variant interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element = false);
Animation();
~Animation();