summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/math/basis.cpp12
-rw-r--r--core/math/basis.h24
-rw-r--r--core/math/math_funcs.h21
-rw-r--r--core/math/vector2.cpp7
-rw-r--r--core/math/vector2.h24
-rw-r--r--core/math/vector3.cpp8
-rw-r--r--core/math/vector3.h25
-rw-r--r--core/variant/variant.cpp19
-rw-r--r--core/variant/variant.h1
-rw-r--r--core/variant/variant_call.cpp2
-rw-r--r--core/variant/variant_utility.cpp52
-rw-r--r--doc/classes/@GlobalScope.xml32
-rw-r--r--doc/classes/Vector2.xml10
-rw-r--r--doc/classes/Vector3.xml10
-rw-r--r--editor/animation_bezier_editor.cpp17
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp12
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_codegen.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp66
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp8
-rw-r--r--modules/gdscript/gdscript_function.h1
-rw-r--r--modules/gdscript/gdscript_vm.cpp16
-rw-r--r--scene/3d/node_3d.cpp148
-rw-r--r--scene/3d/node_3d.h30
-rw-r--r--scene/resources/animation.cpp17
-rw-r--r--scene/resources/curve.cpp38
-rw-r--r--scene/resources/gradient.h12
27 files changed, 441 insertions, 175 deletions
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 65353d8118..ce5e9aa9b3 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -365,12 +365,12 @@ Basis Basis::rotated_local(const Vector3 &p_axis, real_t p_angle) const {
return (*this) * Basis(p_axis, p_angle);
}
-Basis Basis::rotated(const Vector3 &p_euler) const {
- return Basis(p_euler) * (*this);
+Basis Basis::rotated(const Vector3 &p_euler, EulerOrder p_order) const {
+ return Basis::from_euler(p_euler, p_order) * (*this);
}
-void Basis::rotate(const Vector3 &p_euler) {
- *this = rotated(p_euler);
+void Basis::rotate(const Vector3 &p_euler, EulerOrder p_order) {
+ *this = rotated(p_euler, p_order);
}
Basis Basis::rotated(const Quaternion &p_quaternion) const {
@@ -935,9 +935,9 @@ void Basis::set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Ve
rotate(p_axis, p_angle);
}
-void Basis::set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale) {
+void Basis::set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order) {
_set_diagonal(p_scale);
- rotate(p_euler);
+ rotate(p_euler, p_order);
}
void Basis::set_quaternion_scale(const Quaternion &p_quaternion, const Vector3 &p_scale) {
diff --git a/core/math/basis.h b/core/math/basis.h
index 9cce22510b..4be325cdd2 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -56,6 +56,15 @@ struct _NO_DISCARD_ Basis {
_FORCE_INLINE_ real_t determinant() const;
+ enum EulerOrder {
+ EULER_ORDER_XYZ,
+ EULER_ORDER_XZY,
+ EULER_ORDER_YXZ,
+ EULER_ORDER_YZX,
+ EULER_ORDER_ZXY,
+ EULER_ORDER_ZYX
+ };
+
void from_z(const Vector3 &p_z);
void rotate(const Vector3 &p_axis, real_t p_angle);
@@ -64,21 +73,12 @@ struct _NO_DISCARD_ Basis {
void rotate_local(const Vector3 &p_axis, real_t p_angle);
Basis rotated_local(const Vector3 &p_axis, real_t p_angle) const;
- void rotate(const Vector3 &p_euler);
- Basis rotated(const Vector3 &p_euler) const;
+ void rotate(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ);
+ Basis rotated(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ) const;
void rotate(const Quaternion &p_quaternion);
Basis rotated(const Quaternion &p_quaternion) const;
- enum EulerOrder {
- EULER_ORDER_XYZ,
- EULER_ORDER_XZY,
- EULER_ORDER_YXZ,
- EULER_ORDER_YZX,
- EULER_ORDER_ZXY,
- EULER_ORDER_ZYX
- };
-
Vector3 get_euler_normalized(EulerOrder p_order = EULER_ORDER_YXZ) const;
void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const;
void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const;
@@ -119,7 +119,7 @@ struct _NO_DISCARD_ Basis {
Vector3 get_scale_local() const;
void set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Vector3 &p_scale);
- void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale);
+ void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order = EULER_ORDER_YXZ);
void set_quaternion_scale(const Quaternion &p_quaternion, const Vector3 &p_scale);
// transposed dot products
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index c8a55341aa..53deb9bd42 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -253,6 +253,27 @@ public:
(-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * p_weight));
}
+ static _ALWAYS_INLINE_ double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ double omt = (1.0 - p_t);
+ double omt2 = omt * omt;
+ double omt3 = omt2 * omt;
+ double t2 = p_t * p_t;
+ double t3 = t2 * p_t;
+
+ return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3;
+ }
+ static _ALWAYS_INLINE_ float bezier_interpolate(float p_start, float p_control_1, float p_control_2, float p_end, float p_t) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ float omt = (1.0f - p_t);
+ float omt2 = omt * omt;
+ float omt3 = omt2 * omt;
+ float t2 = p_t * p_t;
+ float t3 = t2 * p_t;
+
+ return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0f + p_control_2 * omt * t2 * 3.0f + p_end * t3;
+ }
+
static _ALWAYS_INLINE_ double lerp_angle(double p_from, double p_to, double p_weight) {
double difference = fmod(p_to - p_from, Math_TAU);
double distance = fmod(2.0 * difference, Math_TAU) - difference;
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index a27227905c..d9b5d55454 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -152,13 +152,6 @@ Vector2 Vector2::limit_length(const real_t p_len) const {
return v;
}
-Vector2 Vector2::cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const {
- Vector2 res = *this;
- res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight);
- res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight);
- return res;
-}
-
Vector2 Vector2::move_toward(const Vector2 &p_to, const real_t p_delta) const {
Vector2 v = *this;
Vector2 vd = p_to - v;
diff --git a/core/math/vector2.h b/core/math/vector2.h
index bd67299f33..91d3d3a56b 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -113,7 +113,9 @@ struct _NO_DISCARD_ Vector2 {
_FORCE_INLINE_ Vector2 lerp(const Vector2 &p_to, const real_t p_weight) const;
_FORCE_INLINE_ Vector2 slerp(const Vector2 &p_to, const real_t p_weight) const;
- Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const;
+ _FORCE_INLINE_ Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const;
+ _FORCE_INLINE_ Vector2 bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const;
+
Vector2 move_toward(const Vector2 &p_to, const real_t p_delta) const;
Vector2 slide(const Vector2 &p_normal) const;
@@ -261,6 +263,26 @@ Vector2 Vector2::slerp(const Vector2 &p_to, const real_t p_weight) const {
return rotated(angle * p_weight) * (result_length / start_length);
}
+Vector2 Vector2::cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const {
+ Vector2 res = *this;
+ res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight);
+ res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight);
+ return res;
+}
+
+Vector2 Vector2::bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const {
+ Vector2 res = *this;
+
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - p_t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = p_t * p_t;
+ real_t t3 = t2 * p_t;
+
+ return res * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3;
+}
+
Vector2 Vector2::direction_to(const Vector2 &p_to) const {
Vector2 ret(p_to.x - x, p_to.y - y);
ret.normalize();
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index f94f39b7f2..d71d365053 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -85,14 +85,6 @@ Vector3 Vector3::limit_length(const real_t p_len) const {
return v;
}
-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 res = *this;
- res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight);
- res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight);
- res.z = Math::cubic_interpolate(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight);
- return res;
-}
-
Vector3 Vector3::move_toward(const Vector3 &p_to, const real_t p_delta) const {
Vector3 v = *this;
Vector3 vd = p_to - v;
diff --git a/core/math/vector3.h b/core/math/vector3.h
index 8891532f42..970416234d 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -104,7 +104,9 @@ struct _NO_DISCARD_ Vector3 {
_FORCE_INLINE_ Vector3 lerp(const Vector3 &p_to, const real_t p_weight) const;
_FORCE_INLINE_ Vector3 slerp(const Vector3 &p_to, const real_t p_weight) const;
- Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const;
+ _FORCE_INLINE_ Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const;
+ _FORCE_INLINE_ Vector3 bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const;
+
Vector3 move_toward(const Vector3 &p_to, const real_t p_delta) const;
Vector2 octahedron_encode() const;
@@ -227,6 +229,27 @@ Vector3 Vector3::slerp(const Vector3 &p_to, const real_t p_weight) const {
return rotated(cross(p_to).normalized(), angle * p_weight) * (result_length / start_length);
}
+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 res = *this;
+ res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight);
+ res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight);
+ res.z = Math::cubic_interpolate(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight);
+ return res;
+}
+
+Vector3 Vector3::bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const {
+ Vector3 res = *this;
+
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - p_t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = p_t * p_t;
+ real_t t3 = t2 * p_t;
+
+ return res * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3;
+}
+
real_t Vector3::distance_to(const Vector3 &p_to) const {
return (p_to - *this).length();
}
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 75aec9a619..ae92d7b5c4 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -3327,13 +3327,20 @@ Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Varia
void Variant::static_assign(const Variant &p_variant) {
}
-bool Variant::is_shared() const {
- switch (type) {
+bool Variant::is_type_shared(Variant::Type p_type) {
+ switch (p_type) {
case OBJECT:
- return true;
case ARRAY:
- return true;
case DICTIONARY:
+ case PACKED_BYTE_ARRAY:
+ case PACKED_INT32_ARRAY:
+ case PACKED_INT64_ARRAY:
+ case PACKED_FLOAT32_ARRAY:
+ case PACKED_FLOAT64_ARRAY:
+ case PACKED_STRING_ARRAY:
+ case PACKED_VECTOR2_ARRAY:
+ case PACKED_VECTOR3_ARRAY:
+ case PACKED_COLOR_ARRAY:
return true;
default: {
}
@@ -3342,6 +3349,10 @@ bool Variant::is_shared() const {
return false;
}
+bool Variant::is_shared() const {
+ return is_type_shared(type);
+}
+
void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) {
switch (error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 83d244145b..872b374b13 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -297,6 +297,7 @@ public:
static String get_type_name(Variant::Type p_type);
static bool can_convert(Type p_type_from, Type p_type_to);
static bool can_convert_strict(Type p_type_from, Type p_type_to);
+ static bool is_type_shared(Variant::Type p_type);
bool is_ref_counted() const;
_FORCE_INLINE_ bool is_num() const {
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 19c0e35777..a4bb7630d6 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1592,6 +1592,7 @@ static void _register_variant_builtin_methods() {
bind_method(Vector2, lerp, sarray("to", "weight"), varray());
bind_method(Vector2, slerp, sarray("to", "weight"), varray());
bind_method(Vector2, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray());
+ bind_method(Vector2, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray());
bind_method(Vector2, max_axis_index, sarray(), varray());
bind_method(Vector2, min_axis_index, sarray(), varray());
bind_method(Vector2, move_toward, sarray("to", "delta"), varray());
@@ -1679,6 +1680,7 @@ static void _register_variant_builtin_methods() {
bind_method(Vector3, lerp, sarray("to", "weight"), varray());
bind_method(Vector3, slerp, sarray("to", "weight"), varray());
bind_method(Vector3, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray());
+ bind_method(Vector3, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray());
bind_method(Vector3, move_toward, sarray("to", "delta"), varray());
bind_method(Vector3, dot, sarray("with"), varray());
bind_method(Vector3, cross, sarray("with"), varray());
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 7fabdcbc82..d1b5e285d2 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -231,6 +231,10 @@ struct VariantUtilityFunctions {
return Math::cubic_interpolate(from, to, pre, post, weight);
}
+ static inline double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) {
+ return Math::bezier_interpolate(p_start, p_control_1, p_control_2, p_end, p_t);
+ }
+
static inline double lerp_angle(double from, double to, double weight) {
return Math::lerp_angle(from, to, weight);
}
@@ -267,6 +271,52 @@ struct VariantUtilityFunctions {
return Math::db2linear(db);
}
+ static inline Variant wrap(const Variant &p_x, const Variant &p_min, const Variant &p_max, Callable::CallError &r_error) {
+ Variant::Type x_type = p_x.get_type();
+ if (x_type != Variant::INT && x_type != Variant::FLOAT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = x_type;
+ return Variant();
+ }
+
+ Variant::Type min_type = p_min.get_type();
+ if (min_type != Variant::INT && min_type != Variant::FLOAT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = x_type;
+ return Variant();
+ }
+
+ Variant::Type max_type = p_max.get_type();
+ if (max_type != Variant::INT && max_type != Variant::FLOAT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 2;
+ r_error.expected = x_type;
+ return Variant();
+ }
+
+ Variant value;
+
+ switch (x_type) {
+ case Variant::INT: {
+ if (x_type != min_type || x_type != max_type) {
+ value = wrapf((double)p_x, (double)p_min, (double)p_max);
+ } else {
+ value = wrapi((int)p_x, (int)p_min, (int)p_max);
+ }
+ } break;
+ case Variant::FLOAT: {
+ value = wrapf((double)p_x, (double)p_min, (double)p_max);
+ } break;
+ default:
+ break;
+ }
+
+ r_error.error = Callable::CallError::CALL_OK;
+ return value;
+ }
+
static inline int64_t wrapi(int64_t value, int64_t min, int64_t max) {
return Math::wrapi(value, min, max);
}
@@ -1204,6 +1254,7 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(cubic_interpolate, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(bezier_interpolate, sarray("start", "control_1", "control_2", "end", "t"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(lerp_angle, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(inverse_lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(range_lerp, sarray("value", "istart", "istop", "ostart", "ostop"), Variant::UTILITY_FUNC_TYPE_MATH);
@@ -1216,6 +1267,7 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(linear2db, sarray("lin"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(db2linear, sarray("db"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDVR3(wrap, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(wrapi, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(wrapf, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index ebda0794ce..1a0253a28b 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -106,6 +106,17 @@
[/codeblock]
</description>
</method>
+ <method name="bezier_interpolate">
+ <return type="float" />
+ <argument index="0" name="start" type="float" />
+ <argument index="1" name="control_1" type="float" />
+ <argument index="2" name="control_2" type="float" />
+ <argument index="3" name="end" type="float" />
+ <argument index="4" name="t" type="float" />
+ <description>
+ Returns the point at the given [code]t[/code] on a one-dimnesional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points.
+ </description>
+ </method>
<method name="bytes2var">
<return type="Variant" />
<argument index="0" name="bytes" type="PackedByteArray" />
@@ -1040,6 +1051,27 @@
A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed the weak reference may return the object even if there are no strong references to it.
</description>
</method>
+ <method name="wrap">
+ <return type="Variant" />
+ <argument index="0" name="value" type="Variant" />
+ <argument index="1" name="min" type="Variant" />
+ <argument index="2" name="max" type="Variant" />
+ <description>
+ Wraps the [Variant] [code]value[/code] between [code]min[/code] and [code]max[/code].
+ Usable for creating loop-alike behavior or infinite surfaces.
+ Variant types [int] and [float] (real) are supported. If any of the argument is [float] the result will be [float], otherwise it is [int].
+ [codeblock]
+ var a = wrap(4, 5, 10)
+ # a is 9 (int)
+
+ var a = wrap(7, 5, 10)
+ # a is 7 (int)
+
+ var a = wrap(10.5, 5, 10)
+ # a is 5.5 (float)
+ [/codeblock]
+ </description>
+ </method>
<method name="wrapf">
<return type="float" />
<argument index="0" name="value" type="float" />
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index 6ccc0fc6a6..454db51919 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -85,6 +85,16 @@
Returns the aspect ratio of this vector, the ratio of [member x] to [member y].
</description>
</method>
+ <method name="bezier_interpolate" qualifiers="const">
+ <return type="Vector2" />
+ <argument index="0" name="control_1" type="Vector2" />
+ <argument index="1" name="control_2" type="Vector2" />
+ <argument index="2" name="end" type="Vector2" />
+ <argument index="3" name="t" type="float" />
+ <description>
+ Returns the point at the given [code]t[/code] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points.
+ </description>
+ </method>
<method name="bounce" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="n" type="Vector2" />
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index d907ceb52b..c181720a66 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -61,6 +61,16 @@
Returns the unsigned minimum angle to the given vector, in radians.
</description>
</method>
+ <method name="bezier_interpolate" qualifiers="const">
+ <return type="Vector3" />
+ <argument index="0" name="control_1" type="Vector3" />
+ <argument index="1" name="control_2" type="Vector3" />
+ <argument index="2" name="end" type="Vector3" />
+ <argument index="3" name="t" type="float" />
+ <description>
+ Returns the point at the given [code]t[/code] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points.
+ </description>
+ </method>
<method name="bounce" qualifiers="const">
<return type="Vector3" />
<argument index="0" name="n" type="Vector3" />
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index f0650ee446..391cd009f1 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -44,17 +44,6 @@ float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {
return h;
}
-static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
- /* Formula from Wikipedia article on Bezier curves. */
- real_t omt = (1.0 - t);
- real_t omt2 = omt * omt;
- real_t omt3 = omt2 * omt;
- real_t t2 = t * t;
- real_t t3 = t2 * t;
-
- return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
-}
-
void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
float scale = timeline->get_zoom_scale();
@@ -151,7 +140,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
for (int k = 0; k < iterations; k++) {
float middle = (low + high) / 2;
- Vector2 interp = _bezier_interp(middle, start, out_handle, in_handle, end);
+ Vector2 interp = start.bezier_interpolate(out_handle, in_handle, end, middle);
if (interp.x < t) {
low = middle;
@@ -161,8 +150,8 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
}
//interpolate the result:
- Vector2 low_pos = _bezier_interp(low, start, out_handle, in_handle, end);
- Vector2 high_pos = _bezier_interp(high, start, out_handle, in_handle, end);
+ Vector2 low_pos = start.bezier_interpolate(out_handle, in_handle, end, low);
+ Vector2 high_pos = start.bezier_interpolate(out_handle, in_handle, end, high);
float c = (t - low_pos.x) / (high_pos.x - low_pos.x);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 3d5a39bf38..6a1effd680 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -1336,6 +1336,18 @@ void GDScriptByteCodeGenerator::write_endif() {
if_jmp_addrs.pop_back();
}
+void GDScriptByteCodeGenerator::write_jump_if_shared(const Address &p_value) {
+ append(GDScriptFunction::OPCODE_JUMP_IF_SHARED, 1);
+ append(p_value);
+ if_jmp_addrs.push_back(opcodes.size());
+ append(0); // Jump destination, will be patched.
+}
+
+void GDScriptByteCodeGenerator::write_end_jump_if_shared() {
+ patch_jump(if_jmp_addrs.back()->get());
+ if_jmp_addrs.pop_back();
+}
+
void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) {
Address counter(Address::LOCAL_VARIABLE, add_local("@counter_pos", p_iterator_type), p_iterator_type);
Address container(Address::LOCAL_VARIABLE, add_local("@container_pos", p_list_type), p_list_type);
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 6ee8fda533..f4b402fc96 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -479,6 +479,8 @@ public:
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;
virtual void write_endif() override;
+ virtual void write_jump_if_shared(const Address &p_value) override;
+ virtual void write_end_jump_if_shared() override;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override;
virtual void write_for() override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 326b66a295..81fa265aca 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -140,6 +140,8 @@ public:
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;
virtual void write_endif() = 0;
+ virtual void write_jump_if_shared(const Address &p_value) = 0;
+ virtual void write_end_jump_if_shared() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0;
virtual void write_for() = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 16a0b17d61..1daf8ff7a9 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1056,13 +1056,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Set back the values into their bases.
for (const ChainInfo &info : set_chain) {
- if (!info.is_named) {
- gen->write_set(info.base, info.key, assigned);
- if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
- gen->pop_temporary();
+ bool known_type = assigned.type.has_type;
+ bool is_shared = Variant::is_type_shared(assigned.type.builtin_type);
+
+ if (!known_type) {
+ // Jump shared values since they are already updated in-place.
+ gen->write_jump_if_shared(assigned);
+ }
+ if (known_type && !is_shared) {
+ if (!info.is_named) {
+ gen->write_set(info.base, info.key, assigned);
+ if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ gen->pop_temporary();
+ }
+ } else {
+ gen->write_set_named(info.base, info.name, assigned);
}
- } else {
- gen->write_set_named(info.base, info.name, assigned);
+ }
+ if (!known_type) {
+ gen->write_end_jump_if_shared();
}
if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
gen->pop_temporary();
@@ -1070,19 +1082,35 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
assigned = info.base;
}
- // If this is a class member property, also assign to it.
- // This allow things like: position.x += 2.0
- if (assign_class_member_property != StringName()) {
- gen->write_set_member(assigned, assign_class_member_property);
- }
- // Same as above but for members
- if (is_member_property) {
- if (member_property_has_setter && !member_property_is_in_setter) {
- Vector<GDScriptCodeGenerator::Address> args;
- args.push_back(assigned);
- gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
- } else {
- gen->write_assign(target_member_property, assigned);
+ bool known_type = assigned.type.has_type;
+ bool is_shared = Variant::is_type_shared(assigned.type.builtin_type);
+
+ if (!known_type || !is_shared) {
+ // If this is a class member property, also assign to it.
+ // This allow things like: position.x += 2.0
+ if (assign_class_member_property != StringName()) {
+ if (!known_type) {
+ gen->write_jump_if_shared(assigned);
+ }
+ gen->write_set_member(assigned, assign_class_member_property);
+ if (!known_type) {
+ gen->write_end_jump_if_shared();
+ }
+ } else if (is_member_property) {
+ // Same as above but for script members.
+ if (!known_type) {
+ gen->write_jump_if_shared(assigned);
+ }
+ if (member_property_has_setter && !member_property_is_in_setter) {
+ Vector<GDScriptCodeGenerator::Address> args;
+ args.push_back(assigned);
+ gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args);
+ } else {
+ gen->write_assign(target_member_property, assigned);
+ }
+ if (!known_type) {
+ gen->write_end_jump_if_shared();
+ }
}
}
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index dc114f2eff..726f0efe2b 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -838,6 +838,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 1;
} break;
+ case OPCODE_JUMP_IF_SHARED: {
+ text += "jump-if-shared ";
+ text += DADDR(1);
+ text += " to ";
+ text += itos(_code_ptr[ip + 2]);
+
+ incr = 3;
+ } break;
case OPCODE_RETURN: {
text += "return ";
text += DADDR(1);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index d2ca795977..3f1265679b 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -304,6 +304,7 @@ public:
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,
OPCODE_JUMP_TO_DEF_ARGUMENT,
+ OPCODE_JUMP_IF_SHARED,
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 3f25c2fa43..16a8e728e4 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -313,6 +313,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_JUMP_IF, \
&&OPCODE_JUMP_IF_NOT, \
&&OPCODE_JUMP_TO_DEF_ARGUMENT, \
+ &&OPCODE_JUMP_IF_SHARED, \
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
@@ -2363,6 +2364,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_JUMP_IF_SHARED) {
+ CHECK_SPACE(3);
+
+ GET_INSTRUCTION_ARG(val, 0);
+
+ if (val->is_shared()) {
+ int to = _code_ptr[ip + 2];
+ GD_ERR_BREAK(to < 0 || to > _code_size);
+ ip = to;
+ } else {
+ ip += 3;
+ }
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_RETURN) {
CHECK_SPACE(2);
GET_INSTRUCTION_ARG(r, 0);
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 60858b00b1..4c00250162 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -85,12 +85,20 @@ void Node3D::_notify_dirty() {
}
void Node3D::_update_local_transform() const {
- if (this->get_rotation_edit_mode() != ROTATION_EDIT_MODE_BASIS) {
- data.local_transform = data.local_transform.orthogonalized();
- }
- data.local_transform.basis.set_euler_scale(data.rotation, data.scale);
+ // This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale.
+
+ data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order);
- data.dirty &= ~DIRTY_LOCAL;
+ data.dirty &= ~DIRTY_LOCAL_TRANSFORM;
+}
+
+void Node3D::_update_rotation_and_scale() const {
+ // This function is called when the Euler rotation (data.euler_rotation) is dirty and the right value is contained in the local transform
+
+ data.scale = data.local_transform.basis.get_scale();
+ data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
+
+ data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE;
}
void Node3D::_propagate_transform_changed(Node3D *p_origin) {
@@ -113,7 +121,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
#endif
get_tree()->xform_change_list.add(&xform_change);
}
- data.dirty |= DIRTY_GLOBAL;
+ data.dirty |= DIRTY_GLOBAL_TRANSFORM;
data.children_lock--;
}
@@ -137,12 +145,12 @@ void Node3D::_notification(int p_what) {
if (data.top_level && !Engine::get_singleton()->is_editor_hint()) {
if (data.parent) {
data.local_transform = data.parent->get_global_transform() * get_transform();
- data.dirty = DIRTY_VECTORS; //global is always dirty upon entering a scene
+ data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // As local transform was updated, rot/scale should be dirty.
}
data.top_level_active = true;
}
- data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene
+ data.dirty |= DIRTY_GLOBAL_TRANSFORM; // Global is always dirty upon entering a scene.
_notify_dirty();
notification(NOTIFICATION_ENTER_WORLD);
@@ -212,12 +220,27 @@ void Node3D::set_basis(const Basis &p_basis) {
set_transform(Transform3D(p_basis, data.local_transform.origin));
}
void Node3D::set_quaternion(const Quaternion &p_quaternion) {
- set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin));
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ // We need the scale part, so if these are dirty, update it
+ data.scale = data.local_transform.basis.get_scale();
+ data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE;
+ }
+ data.local_transform.basis = Basis(p_quaternion, data.scale);
+ // Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now.
+ data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
+
+ data.dirty = DIRTY_NONE;
+
+ _propagate_transform_changed(this);
+ if (data.notify_local_transform) {
+ notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
+ }
}
void Node3D::set_transform(const Transform3D &p_transform) {
data.local_transform = p_transform;
- data.dirty |= DIRTY_VECTORS;
+ data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // Make rot/scale dirty.
+
_propagate_transform_changed(this);
if (data.notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
@@ -227,8 +250,13 @@ void Node3D::set_transform(const Transform3D &p_transform) {
Basis Node3D::get_basis() const {
return get_transform().basis;
}
+
Quaternion Node3D::get_quaternion() const {
- return Quaternion(get_transform().basis);
+ if (data.dirty & DIRTY_LOCAL_TRANSFORM) {
+ _update_local_transform();
+ }
+
+ return data.local_transform.basis.get_rotation_quaternion();
}
void Node3D::set_global_transform(const Transform3D &p_transform) {
@@ -240,7 +268,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) {
}
Transform3D Node3D::get_transform() const {
- if (data.dirty & DIRTY_LOCAL) {
+ if (data.dirty & DIRTY_LOCAL_TRANSFORM) {
_update_local_transform();
}
@@ -249,8 +277,8 @@ Transform3D Node3D::get_transform() const {
Transform3D Node3D::get_global_transform() const {
ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
- if (data.dirty & DIRTY_GLOBAL) {
- if (data.dirty & DIRTY_LOCAL) {
+ if (data.dirty & DIRTY_GLOBAL_TRANSFORM) {
+ if (data.dirty & DIRTY_LOCAL_TRANSFORM) {
_update_local_transform();
}
@@ -264,7 +292,7 @@ Transform3D Node3D::get_global_transform() const {
data.global_transform.basis.orthonormalize();
}
- data.dirty &= ~DIRTY_GLOBAL;
+ data.dirty &= ~DIRTY_GLOBAL_TRANSFORM;
}
return data.global_transform;
@@ -314,13 +342,27 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
if (data.rotation_edit_mode == p_mode) {
return;
}
+
+ bool transform_changed = false;
+ if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty & DIRTY_LOCAL_TRANSFORM)) {
+ data.local_transform.orthogonalize();
+ transform_changed = true;
+ }
+
data.rotation_edit_mode = p_mode;
- // Shearing is not allowed except in ROTATION_EDIT_MODE_BASIS.
- data.dirty |= DIRTY_LOCAL;
- _propagate_transform_changed(this);
- if (data.notify_local_transform) {
- notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
+ if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE)) {
+ // If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong.
+ // Otherwise keep what is there, so switching back and forth between modes does not break the vectors.
+
+ _update_rotation_and_scale();
+ }
+
+ if (transform_changed) {
+ _propagate_transform_changed(this);
+ if (data.notify_local_transform) {
+ notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
+ }
}
notify_property_list_changed();
@@ -333,38 +375,47 @@ Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const {
void Node3D::set_rotation_order(RotationOrder p_order) {
Basis::EulerOrder order = Basis::EulerOrder(p_order);
- if (data.rotation_order == order) {
+ if (data.euler_rotation_order == order) {
return;
}
ERR_FAIL_INDEX(int32_t(order), 6);
+ bool transform_changed = false;
- if (data.dirty & DIRTY_VECTORS) {
- data.rotation = data.local_transform.basis.get_euler_normalized(order);
- data.scale = data.local_transform.basis.get_scale();
- data.dirty &= ~DIRTY_VECTORS;
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ _update_rotation_and_scale();
+ } else if (data.dirty & DIRTY_LOCAL_TRANSFORM) {
+ data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(order);
+ transform_changed = true;
} else {
- data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order);
+ data.dirty |= DIRTY_LOCAL_TRANSFORM;
+ transform_changed = true;
}
- data.rotation_order = order;
- //changing rotation order should not affect transform
+ data.euler_rotation_order = order;
- notify_property_list_changed(); //will change rotation
+ if (transform_changed) {
+ _propagate_transform_changed(this);
+ if (data.notify_local_transform) {
+ notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
+ }
+ }
+ notify_property_list_changed(); // Will change the rotation property.
}
Node3D::RotationOrder Node3D::get_rotation_order() const {
- return RotationOrder(data.rotation_order);
+ return RotationOrder(data.euler_rotation_order);
}
void Node3D::set_rotation(const Vector3 &p_euler_rad) {
- if (data.dirty & DIRTY_VECTORS) {
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ // Update scale only if rotation and scale are dirty, as rotation will be overridden.
data.scale = data.local_transform.basis.get_scale();
- data.dirty &= ~DIRTY_VECTORS;
+ data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE;
}
- data.rotation = p_euler_rad;
- data.dirty |= DIRTY_LOCAL;
+ data.euler_rotation = p_euler_rad;
+ data.dirty = DIRTY_LOCAL_TRANSFORM;
_propagate_transform_changed(this);
if (data.notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
@@ -372,13 +423,14 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) {
}
void Node3D::set_scale(const Vector3 &p_scale) {
- if (data.dirty & DIRTY_VECTORS) {
- data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
- data.dirty &= ~DIRTY_VECTORS;
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ // Update rotation only if rotation and scale are dirty, as scale will be overridden.
+ data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
+ data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE;
}
data.scale = p_scale;
- data.dirty |= DIRTY_LOCAL;
+ data.dirty = DIRTY_LOCAL_TRANSFORM;
_propagate_transform_changed(this);
if (data.notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
@@ -390,22 +442,16 @@ Vector3 Node3D::get_position() const {
}
Vector3 Node3D::get_rotation() const {
- if (data.dirty & DIRTY_VECTORS) {
- data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
-
- data.dirty &= ~DIRTY_VECTORS;
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ _update_rotation_and_scale();
}
- return data.rotation;
+ return data.euler_rotation;
}
Vector3 Node3D::get_scale() const {
- if (data.dirty & DIRTY_VECTORS) {
- data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
-
- data.dirty &= ~DIRTY_VECTORS;
+ if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) {
+ _update_rotation_and_scale();
}
return data.scale;
@@ -865,14 +911,14 @@ Variant Node3D::property_get_revert(const String &p_name) {
} else if (p_name == "quaternion") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Quaternion(Transform3D(variant).get_basis());
+ r_ret = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
} else {
return Quaternion();
}
} else if (p_name == "rotation") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.rotation_order);
+ r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
} else {
return Vector3();
}
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index 6d857a83ea..cfd88585e4 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -52,6 +52,9 @@ class Node3D : public Node {
GDCLASS(Node3D, Node);
public:
+ // Edit mode for the rotation.
+ // THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED
+ // IT DOES _NOT_ AFFECT THE TRANSFORM LOGIC (see comment in TransformDirty).
enum RotationEditMode {
ROTATION_EDIT_MODE_EULER,
ROTATION_EDIT_MODE_QUATERNION,
@@ -68,11 +71,27 @@ public:
};
private:
+ // For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaterinon/Scale and Euler Rotation/Scale.
+ // Transform and Quaterinon are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand).
+ // Euler needs to be kept separate because converting to Basis and back may result in a different vector (which is troublesome for users
+ // editing in the inspector, not only because of the numerical precision loss but because they expect these rotations to be consistent, or support
+ // "redundant" rotations for animation interpolation, like going from 0 to 720 degrees).
+ //
+ // As such, the system works in a way where if the local transform is set (via transform/basis/quaternion), the EULER rotation and scale becomes dirty.
+ // It will remain dirty until reading back is attempted (for performance reasons). Likewise, if the Euler rotation scale are set, the local transform
+ // will become dirty (and again, will not become valid again until read).
+ //
+ // All this is transparent from outside the Node3D API, which allows everything to works by calling these functions in exchange.
+ //
+ // Additionally, setting either transform, quaternion, Euler rotation or scale makes the global transform dirty, which will be updated when read again.
+ //
+ // NOTE: Again, RotationEditMode is _independent_ of this mechanism, it is only meant to expose the right set of properties for editing (editor) and saving
+ // (to scene, in order to keep the same values and avoid data loss on conversions). It has zero influence in the logic described above.
enum TransformDirty {
DIRTY_NONE = 0,
- DIRTY_VECTORS = 1,
- DIRTY_LOCAL = 2,
- DIRTY_GLOBAL = 4
+ DIRTY_EULER_ROTATION_AND_SCALE = 1,
+ DIRTY_LOCAL_TRANSFORM = 2,
+ DIRTY_GLOBAL_TRANSFORM = 4
};
mutable SelfList<Node> xform_change;
@@ -80,8 +99,8 @@ private:
struct Data {
mutable Transform3D global_transform;
mutable Transform3D local_transform;
- mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ;
- mutable Vector3 rotation;
+ mutable Basis::EulerOrder euler_rotation_order = Basis::EULER_ORDER_YXZ;
+ mutable Vector3 euler_rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
@@ -131,6 +150,7 @@ protected:
_FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; }
_FORCE_INLINE_ void _update_local_transform() const;
+ _FORCE_INLINE_ void _update_rotation_and_scale() const;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 8ae4872d14..7183accc66 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -3379,17 +3379,6 @@ Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) con
return bt->values[p_index].value.out_handle;
}
-static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
- /* Formula from Wikipedia article on Bezier curves. */
- real_t omt = (1.0 - t);
- real_t omt2 = omt * omt;
- real_t omt3 = omt2 * omt;
- real_t t2 = t * t;
- real_t t3 = t2 * t;
-
- return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
-}
-
real_t Animation::bezier_track_interpolate(int p_track, double p_time) const {
//this uses a different interpolation scheme
ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
@@ -3438,7 +3427,7 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const {
for (int i = 0; i < iterations; i++) {
real_t middle = (low + high) / 2;
- Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end);
+ Vector2 interp = start.bezier_interpolate(start_out, end_in, end, middle);
if (interp.x < t) {
low = middle;
@@ -3448,8 +3437,8 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const {
}
//interpolate the result:
- Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end);
- Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end);
+ Vector2 low_pos = start.bezier_interpolate(start_out, end_in, end, low);
+ Vector2 high_pos = start.bezier_interpolate(start_out, end_in, end, high);
real_t c = (t - low_pos.x) / (high_pos.x - low_pos.x);
return low_pos.lerp(high_pos, c).y;
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 96cf7bb708..da26a0261f 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -32,18 +32,6 @@
#include "core/core_string_names.h"
-template <class T>
-static _FORCE_INLINE_ T _bezier_interp(real_t p_t, T p_start, T p_control_1, T p_control_2, T p_end) {
- /* Formula from Wikipedia article on Bezier curves. */
- real_t omt = (1.0 - p_t);
- real_t omt2 = omt * omt;
- real_t omt3 = omt2 * omt;
- real_t t2 = p_t * p_t;
- real_t t3 = t2 * p_t;
-
- return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3;
-}
-
const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed";
Curve::Curve() {
@@ -376,7 +364,7 @@ real_t Curve::interpolate_local_nocheck(int p_index, real_t p_local_offset) cons
real_t yac = a.position.y + d * a.right_tangent;
real_t ybc = b.position.y - d * b.left_tangent;
- real_t y = _bezier_interp(p_local_offset, a.position.y, yac, ybc, b.position.y);
+ real_t y = Math::bezier_interpolate(a.position.y, yac, ybc, b.position.y, p_local_offset);
return y;
}
@@ -747,7 +735,7 @@ Vector2 Curve2D::interpolate(int p_index, const real_t p_offset) const {
Vector2 p3 = points[p_index + 1].position;
Vector2 p2 = p3 + points[p_index + 1].in;
- return _bezier_interp(p_offset, p0, p1, p2, p3);
+ return p0.bezier_interpolate(p1, p2, p3, p_offset);
}
Vector2 Curve2D::interpolatef(real_t p_findex) const {
@@ -767,9 +755,9 @@ void Curve2D::mark_dirty() {
void Curve2D::_bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const {
real_t mp = p_begin + (p_end - p_begin) * 0.5;
- Vector2 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b);
- Vector2 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b);
- Vector2 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b);
+ Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
+ Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
+ Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
Vector2 na = (mid - beg).normalized();
Vector2 nb = (end - mid).normalized();
@@ -828,7 +816,7 @@ void Curve2D::_bake() const {
np = 1.0;
}
- Vector2 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position);
+ Vector2 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np);
real_t d = position.distance_to(npp);
if (d > bake_interval) {
@@ -841,7 +829,7 @@ void Curve2D::_bake() const {
real_t mid = low + (hi - low) * 0.5;
for (int j = 0; j < iterations; j++) {
- npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position);
+ npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid);
d = position.distance_to(npp);
if (bake_interval < d) {
@@ -1336,7 +1324,7 @@ Vector3 Curve3D::interpolate(int p_index, real_t p_offset) const {
Vector3 p3 = points[p_index + 1].position;
Vector3 p2 = p3 + points[p_index + 1].in;
- return _bezier_interp(p_offset, p0, p1, p2, p3);
+ return p0.bezier_interpolate(p1, p2, p3, p_offset);
}
Vector3 Curve3D::interpolatef(real_t p_findex) const {
@@ -1356,9 +1344,9 @@ void Curve3D::mark_dirty() {
void Curve3D::_bake_segment3d(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const {
real_t mp = p_begin + (p_end - p_begin) * 0.5;
- Vector3 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b);
- Vector3 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b);
- Vector3 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b);
+ Vector3 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin);
+ Vector3 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp);
+ Vector3 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end);
Vector3 na = (mid - beg).normalized();
Vector3 nb = (end - mid).normalized();
@@ -1426,7 +1414,7 @@ void Curve3D::_bake() const {
np = 1.0;
}
- Vector3 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position);
+ Vector3 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np);
real_t d = position.distance_to(npp);
if (d > bake_interval) {
@@ -1439,7 +1427,7 @@ void Curve3D::_bake() const {
real_t mid = low + (hi - low) * 0.5;
for (int j = 0; j < iterations; j++) {
- npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position);
+ npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid);
d = position.distance_to(npp);
if (bake_interval < d) {
diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h
index a3d3449099..2b04ead0af 100644
--- a/scene/resources/gradient.h
+++ b/scene/resources/gradient.h
@@ -92,10 +92,6 @@ public:
void set_interpolation_mode(InterpolationMode p_interp_mode);
InterpolationMode get_interpolation_mode();
- _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) {
- return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
- }
-
_FORCE_INLINE_ Color get_color_at_offset(float p_offset) {
if (points.is_empty()) {
return Color(0, 0, 0, 1);
@@ -161,10 +157,10 @@ public:
const Point &pointP3 = points[p3];
float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset);
- float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x);
- float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x);
- float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x);
- float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x);
+ float r = Math::cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x);
+ float g = Math::cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x);
+ float b = Math::cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x);
+ float a = Math::cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x);
return Color(r, g, b, a);
} break;