diff options
49 files changed, 1334 insertions, 415 deletions
diff --git a/.gitignore b/.gitignore index ca27e42016..d9537edbf2 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,11 @@ gmon.out *.cflags *.cxxflags +# Code::Blocks files +*.cbp +*.layout +*.depend + # Eclipse CDT files .cproject .settings/ diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index e81351a3a6..267391c4d6 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -498,18 +498,10 @@ Dictionary _OS::get_time_zone_info() const { return infod; } -uint64_t _OS::get_unix_time() const { +double _OS::get_unix_time() const { return OS::get_singleton()->get_unix_time(); } -uint64_t _OS::get_system_time_secs() const { - return OS::get_singleton()->get_system_time_secs(); -} - -uint64_t _OS::get_system_time_msecs() const { - return OS::get_singleton()->get_system_time_msecs(); -} - void _OS::delay_usec(uint32_t p_usec) const { OS::get_singleton()->delay_usec(p_usec); } @@ -729,8 +721,6 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_unix_time"), &_OS::get_unix_time); ClassDB::bind_method(D_METHOD("get_datetime_from_unix_time", "unix_time_val"), &_OS::get_datetime_from_unix_time); ClassDB::bind_method(D_METHOD("get_unix_time_from_datetime", "datetime"), &_OS::get_unix_time_from_datetime); - ClassDB::bind_method(D_METHOD("get_system_time_secs"), &_OS::get_system_time_secs); - ClassDB::bind_method(D_METHOD("get_system_time_msecs"), &_OS::get_system_time_msecs); ClassDB::bind_method(D_METHOD("get_exit_code"), &_OS::get_exit_code); ClassDB::bind_method(D_METHOD("set_exit_code", "code"), &_OS::set_exit_code); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 26d0f7b8af..f9f5a4e7d7 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -199,9 +199,7 @@ public: Dictionary get_datetime_from_unix_time(int64_t unix_time_val) const; int64_t get_unix_time_from_datetime(Dictionary datetime) const; Dictionary get_time_zone_info() const; - uint64_t get_unix_time() const; - uint64_t get_system_time_secs() const; - uint64_t get_system_time_msecs() const; + double get_unix_time() const; uint64_t get_static_memory_usage() const; uint64_t get_static_memory_peak_usage() const; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 3cb4b43a26..ac032b7d10 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -48,7 +48,7 @@ void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("action_has_event", "action", "event"), &InputMap::action_has_event); ClassDB::bind_method(D_METHOD("action_erase_event", "action", "event"), &InputMap::action_erase_event); ClassDB::bind_method(D_METHOD("action_erase_events", "action"), &InputMap::action_erase_events); - ClassDB::bind_method(D_METHOD("get_action_list", "action"), &InputMap::_get_action_list); + ClassDB::bind_method(D_METHOD("action_get_events", "action"), &InputMap::_action_get_events); ClassDB::bind_method(D_METHOD("event_is_action", "event", "action"), &InputMap::event_is_action); ClassDB::bind_method(D_METHOD("load_from_globals"), &InputMap::load_from_globals); } @@ -152,9 +152,9 @@ void InputMap::action_erase_events(const StringName &p_action) { input_map[p_action].inputs.clear(); } -Array InputMap::_get_action_list(const StringName &p_action) { +Array InputMap::_action_get_events(const StringName &p_action) { Array ret; - const List<Ref<InputEvent>> *al = get_action_list(p_action); + const List<Ref<InputEvent>> *al = action_get_events(p_action); if (al) { for (const List<Ref<InputEvent>>::Element *E = al->front(); E; E = E->next()) { ret.push_back(E->get()); @@ -164,7 +164,7 @@ Array InputMap::_get_action_list(const StringName &p_action) { return ret; } -const List<Ref<InputEvent>> *InputMap::get_action_list(const StringName &p_action) { +const List<Ref<InputEvent>> *InputMap::action_get_events(const StringName &p_action) { const Map<StringName, Action>::Element *E = input_map.find(p_action); if (!E) { return nullptr; diff --git a/core/input/input_map.h b/core/input/input_map.h index 3abc224ccf..548553ed31 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -56,7 +56,7 @@ private: List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = nullptr, float *p_strength = nullptr) const; - Array _get_action_list(const StringName &p_action); + Array _action_get_events(const StringName &p_action); Array _get_actions(); protected: @@ -76,7 +76,7 @@ public: void action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event); void action_erase_events(const StringName &p_action); - const List<Ref<InputEvent>> *get_action_list(const StringName &p_action); + const List<Ref<InputEvent>> *action_get_events(const StringName &p_action); bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action) const; bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = nullptr, float *p_strength = nullptr) const; diff --git a/core/math/basis.cpp b/core/math/basis.cpp index cbfd09810c..df5199b0f9 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -428,12 +428,9 @@ Vector3 Basis::get_euler_xyz() const { // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy Vector3 euler; -#ifdef MATH_CHECKS - ERR_FAIL_COND_V(!is_rotation(), euler); -#endif real_t sy = elements[0][2]; - if (sy < 1.0) { - if (sy > -1.0) { + if (sy < (1.0 - CMP_EPSILON)) { + if (sy > -(1.0 - CMP_EPSILON)) { // is this a pure Y rotation? if (elements[1][0] == 0.0 && elements[0][1] == 0.0 && elements[1][2] == 0 && elements[2][1] == 0 && elements[1][1] == 1) { // return the simplest form (human friendlier in editor and scripts) @@ -446,12 +443,12 @@ Vector3 Basis::get_euler_xyz() const { euler.z = Math::atan2(-elements[0][1], elements[0][0]); } } else { - euler.x = -Math::atan2(elements[0][1], elements[1][1]); + euler.x = Math::atan2(elements[2][1], elements[1][1]); euler.y = -Math_PI / 2.0; euler.z = 0.0; } } else { - euler.x = Math::atan2(elements[0][1], elements[1][1]); + euler.x = Math::atan2(elements[2][1], elements[1][1]); euler.y = Math_PI / 2.0; euler.z = 0.0; } @@ -481,15 +478,106 @@ void Basis::set_euler_xyz(const Vector3 &p_euler) { *this = xmat * (ymat * zmat); } +Vector3 Basis::get_euler_xzy() const { + // Euler angles in XZY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy -sz cz*sy + // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx + // cy*sx*sz cz*sx cx*cy+sx*sz*sy + + Vector3 euler; + real_t sz = elements[0][1]; + if (sz < (1.0 - CMP_EPSILON)) { + if (sz > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(elements[2][1], elements[1][1]); + euler.y = Math::atan2(elements[0][2], elements[0][0]); + euler.z = Math::asin(-sz); + } else { + // It's -1 + euler.x = -Math::atan2(elements[1][2], elements[2][2]); + euler.y = 0.0; + euler.z = Math_PI / 2.0; + } + } else { + // It's 1 + euler.x = -Math::atan2(elements[1][2], elements[2][2]); + euler.y = 0.0; + euler.z = -Math_PI / 2.0; + } + return euler; +} + +void Basis::set_euler_xzy(const Vector3 &p_euler) { + real_t c, s; + + c = Math::cos(p_euler.x); + s = Math::sin(p_euler.x); + Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); + + c = Math::cos(p_euler.y); + s = Math::sin(p_euler.y); + Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); + + c = Math::cos(p_euler.z); + s = Math::sin(p_euler.z); + Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); + + *this = xmat * zmat * ymat; +} + +Vector3 Basis::get_euler_yzx() const { + // Euler angles in YZX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx + // sz cz*cx -cz*sx + // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx + + Vector3 euler; + real_t sz = elements[1][0]; + if (sz < (1.0 - CMP_EPSILON)) { + if (sz > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(-elements[1][2], elements[1][1]); + euler.y = Math::atan2(-elements[2][0], elements[0][0]); + euler.z = Math::asin(sz); + } else { + // It's -1 + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = 0.0; + euler.z = -Math_PI / 2.0; + } + } else { + // It's 1 + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = 0.0; + euler.z = Math_PI / 2.0; + } + return euler; +} + +void Basis::set_euler_yzx(const Vector3 &p_euler) { + real_t c, s; + + c = Math::cos(p_euler.x); + s = Math::sin(p_euler.x); + Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); + + c = Math::cos(p_euler.y); + s = Math::sin(p_euler.y); + Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); + + c = Math::cos(p_euler.z); + s = Math::sin(p_euler.z); + Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); + + *this = ymat * zmat * xmat; +} + // get_euler_yxz returns a vector containing the Euler angles in the YXZ convention, // as in first-Z, then-X, last-Y. The angles for X, Y, and Z rotations are returned // as the x, y, and z components of a Vector3 respectively. Vector3 Basis::get_euler_yxz() const { - /* checking this is a bad idea, because obtaining from scaled transform is a valid use case -#ifdef MATH_CHECKS - ERR_FAIL_COND(!is_rotation()); -#endif -*/ // Euler angles in YXZ convention. // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix // @@ -501,8 +589,8 @@ Vector3 Basis::get_euler_yxz() const { real_t m12 = elements[1][2]; - if (m12 < 1) { - if (m12 > -1) { + if (m12 < (1 - CMP_EPSILON)) { + if (m12 > -(1 - CMP_EPSILON)) { // is this a pure X rotation? if (elements[1][0] == 0 && elements[0][1] == 0 && elements[0][2] == 0 && elements[2][0] == 0 && elements[0][0] == 1) { // return the simplest form (human friendlier in editor and scripts) @@ -516,12 +604,12 @@ Vector3 Basis::get_euler_yxz() const { } } else { // m12 == -1 euler.x = Math_PI * 0.5; - euler.y = -atan2(-elements[0][1], elements[0][0]); + euler.y = atan2(elements[0][1], elements[0][0]); euler.z = 0; } } else { // m12 == 1 euler.x = -Math_PI * 0.5; - euler.y = -atan2(-elements[0][1], elements[0][0]); + euler.y = -atan2(elements[0][1], elements[0][0]); euler.z = 0; } @@ -551,6 +639,100 @@ void Basis::set_euler_yxz(const Vector3 &p_euler) { *this = ymat * xmat * zmat; } +Vector3 Basis::get_euler_zxy() const { + // Euler angles in ZXY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx + // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx + // -cx*sy sx cx*cy + Vector3 euler; + real_t sx = elements[2][1]; + if (sx < (1.0 - CMP_EPSILON)) { + if (sx > -(1.0 - CMP_EPSILON)) { + euler.x = Math::asin(sx); + euler.y = Math::atan2(-elements[2][0], elements[2][2]); + euler.z = Math::atan2(-elements[0][1], elements[1][1]); + } else { + // It's -1 + euler.x = -Math_PI / 2.0; + euler.y = Math::atan2(elements[0][2], elements[0][0]); + euler.z = 0; + } + } else { + // It's 1 + euler.x = Math_PI / 2.0; + euler.y = Math::atan2(elements[0][2], elements[0][0]); + euler.z = 0; + } + return euler; +} + +void Basis::set_euler_zxy(const Vector3 &p_euler) { + real_t c, s; + + c = Math::cos(p_euler.x); + s = Math::sin(p_euler.x); + Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); + + c = Math::cos(p_euler.y); + s = Math::sin(p_euler.y); + Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); + + c = Math::cos(p_euler.z); + s = Math::sin(p_euler.z); + Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); + + *this = zmat * xmat * ymat; +} + +Vector3 Basis::get_euler_zyx() const { + // Euler angles in ZYX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy + // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx + // -sy cy*sx cy*cx + Vector3 euler; + real_t sy = elements[2][0]; + if (sy < (1.0 - CMP_EPSILON)) { + if (sy > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = Math::asin(-sy); + euler.z = Math::atan2(elements[1][0], elements[0][0]); + } else { + // It's -1 + euler.x = 0; + euler.y = Math_PI / 2.0; + euler.z = -Math::atan2(elements[0][1], elements[1][1]); + } + } else { + // It's 1 + euler.x = 0; + euler.y = -Math_PI / 2.0; + euler.z = -Math::atan2(elements[0][1], elements[1][1]); + } + return euler; +} + +void Basis::set_euler_zyx(const Vector3 &p_euler) { + real_t c, s; + + c = Math::cos(p_euler.x); + s = Math::sin(p_euler.x); + Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); + + c = Math::cos(p_euler.y); + s = Math::sin(p_euler.y); + Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); + + c = Math::cos(p_euler.z); + s = Math::sin(p_euler.z); + Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); + + *this = zmat * ymat * xmat; +} + bool Basis::is_equal_approx(const Basis &p_basis) const { return elements[0].is_equal_approx(p_basis.elements[0]) && elements[1].is_equal_approx(p_basis.elements[1]) && elements[2].is_equal_approx(p_basis.elements[2]); } diff --git a/core/math/basis.h b/core/math/basis.h index d870a6b099..985fb0e44f 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -88,9 +88,22 @@ public: Vector3 get_euler_xyz() const; void set_euler_xyz(const Vector3 &p_euler); + + Vector3 get_euler_xzy() const; + void set_euler_xzy(const Vector3 &p_euler); + + Vector3 get_euler_yzx() const; + void set_euler_yzx(const Vector3 &p_euler); + Vector3 get_euler_yxz() const; void set_euler_yxz(const Vector3 &p_euler); + Vector3 get_euler_zxy() const; + void set_euler_zxy(const Vector3 &p_euler); + + Vector3 get_euler_zyx() const; + void set_euler_zyx(const Vector3 &p_euler); + Quat get_quat() const; void set_quat(const Quat &p_quat); diff --git a/core/os/os.cpp b/core/os/os.cpp index 56755bcf51..c842be333c 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -83,15 +83,7 @@ uint64_t OS::get_splash_tick_msec() const { return _msec_splash; } -uint64_t OS::get_unix_time() const { - return 0; -} - -uint64_t OS::get_system_time_secs() const { - return 0; -} - -uint64_t OS::get_system_time_msecs() const { +double OS::get_unix_time() const { return 0; } diff --git a/core/os/os.h b/core/os/os.h index 9ca034a01c..04e10518dc 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -209,9 +209,7 @@ public: virtual Time get_time(bool local = false) const = 0; virtual TimeZoneInfo get_time_zone_info() const = 0; virtual String get_iso_date_time(bool local = false) const; - virtual uint64_t get_unix_time() const; - virtual uint64_t get_system_time_secs() const; - virtual uint64_t get_system_time_msecs() const; + virtual double get_unix_time() const; virtual void delay_usec(uint32_t p_usec) const = 0; virtual uint64_t get_ticks_usec() const = 0; diff --git a/core/variant_call.cpp b/core/variant_call.cpp index f1b2a1547d..a8beac1e44 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -923,6 +923,18 @@ struct _VariantCall { VCALL_PTR1R(Basis, scaled); VCALL_PTR0R(Basis, get_scale); VCALL_PTR0R(Basis, get_euler); + VCALL_PTR0R(Basis, get_euler_xyz); + VCALL_PTR1(Basis, set_euler_xyz); + VCALL_PTR0R(Basis, get_euler_xzy); + VCALL_PTR1(Basis, set_euler_xzy); + VCALL_PTR0R(Basis, get_euler_yzx); + VCALL_PTR1(Basis, set_euler_yzx); + VCALL_PTR0R(Basis, get_euler_yxz); + VCALL_PTR1(Basis, set_euler_yxz); + VCALL_PTR0R(Basis, get_euler_zxy); + VCALL_PTR1(Basis, set_euler_zxy); + VCALL_PTR0R(Basis, get_euler_zyx); + VCALL_PTR1(Basis, set_euler_zyx); VCALL_PTR1R(Basis, tdotx); VCALL_PTR1R(Basis, tdoty); VCALL_PTR1R(Basis, tdotz); diff --git a/doc/classes/InputMap.xml b/doc/classes/InputMap.xml index da93d7fb53..842c69de27 100644 --- a/doc/classes/InputMap.xml +++ b/doc/classes/InputMap.xml @@ -95,7 +95,7 @@ Returns [code]true[/code] if the given event is part of an existing action. This method ignores keyboard modifiers if the given [InputEvent] is not pressed (for proper release detection). See [method action_has_event] if you don't want this behavior. </description> </method> - <method name="get_action_list"> + <method name="action_get_events"> <return type="Array"> </return> <argument index="0" name="action" type="StringName"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 03203e2ebb..b131d2728c 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -325,7 +325,7 @@ </description> </method> <method name="get_unix_time" qualifiers="const"> - <return type="int"> + <return type="float"> </return> <description> Returns the current UNIX epoch timestamp. diff --git a/doc/classes/PhysicsMaterial.xml b/doc/classes/PhysicsMaterial.xml index 6410626496..0889c238dc 100644 --- a/doc/classes/PhysicsMaterial.xml +++ b/doc/classes/PhysicsMaterial.xml @@ -12,6 +12,7 @@ </methods> <members> <member name="absorbent" type="bool" setter="set_absorbent" getter="is_absorbent" default="false"> + If [code]true[/code], subtracts the bounciness from the colliding object's bounciness instead of adding it. </member> <member name="bounce" type="float" setter="set_bounce" getter="get_bounce" default="0.0"> The body's bounciness. Values range from [code]0[/code] (no bounce) to [code]1[/code] (full bounciness). @@ -20,6 +21,7 @@ The body's friction. Values range from [code]0[/code] (frictionless) to [code]1[/code] (maximum friction). </member> <member name="rough" type="bool" setter="set_rough" getter="is_rough" default="false"> + If [code]true[/code], the physics engine will use the friction of the object marked as "rough" when two objects collide. If [code]false[/code], the physics engine will use the lowest friction of all colliding objects instead. If [code]true[/code] for both colliding objects, the physics engine will use the highest friction. </member> </members> <constants> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 640cbe84f5..183fd5396f 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -31,6 +31,16 @@ [i]Deprecated soon.[/i] </description> </method> + <method name="bone_transform_to_world_transform"> + <return type="Transform"> + </return> + <argument index="0" name="bone_transform" type="Transform"> + </argument> + <description> + Takes the given bone pose/transform and converts it to a world transform, relative to the [Skeleton3D] node. + This is useful for using the bone transform in calculations with transforms from [Node3D]-based nodes. + </description> + </method> <method name="clear_bones"> <return type="void"> </return> @@ -42,6 +52,7 @@ <return type="void"> </return> <description> + Removes the global pose override on all bones in the skeleton. </description> </method> <method name="find_bone" qualifiers="const"> @@ -136,12 +147,14 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> + Returns whether the bone rest for the bone at [code]bone_idx[/code] is disabled. </description> </method> <method name="localize_rests"> <return type="void"> </return> <description> + Returns all bones in the skeleton to their rest poses. </description> </method> <method name="physical_bones_add_collision_exception"> @@ -150,6 +163,8 @@ <argument index="0" name="exception" type="RID"> </argument> <description> + Adds a collision exception to the physical bone. + Works just like the [RigidBody3D] node. </description> </method> <method name="physical_bones_remove_collision_exception"> @@ -158,6 +173,8 @@ <argument index="0" name="exception" type="RID"> </argument> <description> + Removes a collision exception to the physical bone. + Works just like the [RigidBody3D] node. </description> </method> <method name="physical_bones_start_simulation"> @@ -166,12 +183,15 @@ <argument index="0" name="bones" type="StringName[]" default="[ ]"> </argument> <description> + Tells the [PhysicalBone3D] nodes in the Skeleton to start simulating and reacting to the physics world. + Optionally, a list of bone names can be passed-in, allowing only the passed-in bones to be simulated. </description> </method> <method name="physical_bones_stop_simulation"> <return type="void"> </return> <description> + Tells the [PhysicalBone3D] nodes in the Skeleton to stop simulating. </description> </method> <method name="register_skin"> @@ -180,6 +200,7 @@ <argument index="0" name="skin" type="Skin"> </argument> <description> + Binds the given Skin to the Skeleton. </description> </method> <method name="set_bone_custom_pose"> @@ -190,6 +211,8 @@ <argument index="1" name="custom_pose" type="Transform"> </argument> <description> + Sets the custom pose transform, [code]custom_pose[/code], for the bone at [code]bone_idx[/code]. This pose is an addition to the bone rest pose. + [b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space. </description> </method> <method name="set_bone_disable_rest"> @@ -200,6 +223,7 @@ <argument index="1" name="disable" type="bool"> </argument> <description> + Disables the rest pose for the bone at [code]bone_idx[/code] if [code]true[/code], enables the bone rest if [code]false[/code]. </description> </method> <method name="set_bone_global_pose_override"> @@ -214,6 +238,9 @@ <argument index="3" name="persistent" type="bool" default="false"> </argument> <description> + Sets the global pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code]. + [code]amount[/code] is the interpolation strengh that will be used when applying the pose, and [code]persistent[/code] determines if the applied pose will remain. + [b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space. </description> </method> <method name="set_bone_parent"> @@ -237,6 +264,7 @@ </argument> <description> Returns the pose transform for bone [code]bone_idx[/code]. + [b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space. </description> </method> <method name="set_bone_rest"> @@ -267,6 +295,17 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> + Unparents the bone at [code]bone_idx[/code] and sets its rest position to that of it's parent prior to being reset. + </description> + </method> + <method name="world_transform_to_bone_transform"> + <return type="Transform"> + </return> + <argument index="0" name="world_transform" type="Transform"> + </argument> + <description> + Takes the given world transform, relative to the [Skeleton3D], and converts it to a bone pose/transform. + This is useful for using setting bone poses using transforms from [Node3D]-based nodes. </description> </method> </methods> diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 083cd64116..9a5fc6d1a4 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -163,21 +163,11 @@ String OS_Unix::get_name() const { return "Unix"; } -uint64_t OS_Unix::get_unix_time() const { - return time(nullptr); -}; - -uint64_t OS_Unix::get_system_time_secs() const { - struct timeval tv_now; - gettimeofday(&tv_now, nullptr); - return uint64_t(tv_now.tv_sec); -} - -uint64_t OS_Unix::get_system_time_msecs() const { +double OS_Unix::get_unix_time() const { struct timeval tv_now; gettimeofday(&tv_now, nullptr); - return uint64_t(tv_now.tv_sec) * 1000 + uint64_t(tv_now.tv_usec) / 1000; -} + return (double)tv_now.tv_sec + double(tv_now.tv_usec) / 1000000; +}; OS::Date OS_Unix::get_date(bool utc) const { time_t t = time(nullptr); diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index 7d235803dc..2982e0c55c 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -77,9 +77,7 @@ public: virtual Time get_time(bool utc) const; virtual TimeZoneInfo get_time_zone_info() const; - virtual uint64_t get_unix_time() const; - virtual uint64_t get_system_time_secs() const; - virtual uint64_t get_system_time_msecs() const; + virtual double get_unix_time() const; virtual void delay_usec(uint32_t p_usec) const; virtual uint64_t get_ticks_usec() const; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index bad2203423..f50ac6d580 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -243,6 +243,9 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview if (p_overview) { class_desc->push_cell(); class_desc->push_align(RichTextLabel::ALIGN_RIGHT); + } else { + static const CharType prefix[3] = { 0x25CF /* filled circle */, ' ', 0 }; + class_desc->add_text(String(prefix)); } _add_type(p_method.return_type, p_method.return_enum); @@ -378,7 +381,6 @@ void EditorHelp::_update_doc() { class_desc->push_color(title_color); class_desc->push_font(doc_font); class_desc->add_text(TTR("Inherits:") + " "); - class_desc->pop(); String inherits = cd.inherits; @@ -393,6 +395,7 @@ void EditorHelp::_update_doc() { } class_desc->pop(); + class_desc->pop(); class_desc->add_newline(); } @@ -401,13 +404,12 @@ void EditorHelp::_update_doc() { bool found = false; bool prev = false; + class_desc->push_font(doc_font); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { if (E->get().inherits == cd.name) { if (!found) { class_desc->push_color(title_color); - class_desc->push_font(doc_font); class_desc->add_text(TTR("Inherited by:") + " "); - class_desc->pop(); found = true; } @@ -419,6 +421,7 @@ void EditorHelp::_update_doc() { prev = true; } } + class_desc->pop(); if (found) { class_desc->pop(); @@ -758,6 +761,8 @@ void EditorHelp::_update_doc() { signal_line[cd.signals[i].name] = class_desc->get_line_count() - 2; //gets overridden if description class_desc->push_font(doc_code_font); // monofont class_desc->push_color(headline_color); + static const CharType prefix[3] = { 0x25CF /* filled circle */, ' ', 0 }; + class_desc->add_text(String(prefix)); _add_text(cd.signals[i].name); class_desc->pop(); class_desc->push_color(symbol_color); @@ -835,10 +840,10 @@ void EditorHelp::_update_doc() { for (Map<String, Vector<DocData::ConstantDoc>>::Element *E = enums.front(); E; E = E->next()) { enum_line[E->key()] = class_desc->get_line_count() - 2; + class_desc->push_font(doc_code_font); class_desc->push_color(title_color); class_desc->add_text("enum "); class_desc->pop(); - class_desc->push_font(doc_code_font); String e = E->key(); if ((e.get_slice_count(".") > 1) && (e.get_slice(".", 0) == edited_class)) { e = e.get_slice(".", 1); @@ -851,6 +856,8 @@ void EditorHelp::_update_doc() { class_desc->push_color(symbol_color); class_desc->add_text(":"); class_desc->pop(); + + class_desc->add_newline(); class_desc->add_newline(); class_desc->push_indent(1); @@ -869,6 +876,8 @@ void EditorHelp::_update_doc() { class_desc->push_font(doc_code_font); class_desc->push_color(headline_color); + static const CharType prefix[3] = { 0x25CF /* filled circle */, ' ', 0 }; + class_desc->add_text(String(prefix)); _add_text(enum_list[i].name); class_desc->pop(); class_desc->push_color(symbol_color); @@ -880,14 +889,15 @@ void EditorHelp::_update_doc() { class_desc->pop(); if (enum_list[i].description != "") { class_desc->push_font(doc_font); - //class_desc->add_text(" "); - class_desc->push_indent(1); class_desc->push_color(comment_color); + static const CharType dash[6] = { ' ', ' ', 0x2013 /* en dash */, ' ', ' ', 0 }; + class_desc->add_text(String(dash)); _add_text(DTR(enum_list[i].description)); class_desc->pop(); class_desc->pop(); - class_desc->pop(); // indent - class_desc->add_newline(); + if (DTR(enum_list[i].description).find("\n") > 0) { + class_desc->add_newline(); + } } class_desc->add_newline(); @@ -931,6 +941,9 @@ void EditorHelp::_update_doc() { class_desc->add_text(String(prefix)); class_desc->pop(); } + } else { + static const CharType prefix[3] = { 0x25CF /* filled circle */, ' ', 0 }; + class_desc->add_text(String(prefix)); } class_desc->push_color(headline_color); @@ -946,13 +959,15 @@ void EditorHelp::_update_doc() { class_desc->pop(); if (constants[i].description != "") { class_desc->push_font(doc_font); - class_desc->push_indent(1); class_desc->push_color(comment_color); + static const CharType dash[6] = { ' ', ' ', 0x2013 /* en dash */, ' ', ' ', 0 }; + class_desc->add_text(String(dash)); _add_text(DTR(constants[i].description)); class_desc->pop(); class_desc->pop(); - class_desc->pop(); // indent - class_desc->add_newline(); + if (DTR(constants[i].description).find("\n") > 0) { + class_desc->add_newline(); + } } class_desc->add_newline(); @@ -987,6 +1002,9 @@ void EditorHelp::_update_doc() { class_desc->push_cell(); class_desc->push_font(doc_code_font); + static const CharType prefix[3] = { 0x25CF /* filled circle */, ' ', 0 }; + class_desc->add_text(String(prefix)); + _add_type(cd.properties[i].type, cd.properties[i].enumeration); class_desc->add_text(" "); class_desc->pop(); // font @@ -1015,6 +1033,11 @@ void EditorHelp::_update_doc() { class_desc->pop(); // font class_desc->pop(); // cell + Map<String, DocData::MethodDoc> method_map; + for (int j = 0; j < methods.size(); j++) { + method_map[methods[j].name] = methods[j]; + } + if (cd.properties[i].setter != "") { class_desc->push_cell(); class_desc->pop(); // cell @@ -1022,7 +1045,14 @@ void EditorHelp::_update_doc() { class_desc->push_cell(); class_desc->push_font(doc_code_font); class_desc->push_color(text_color); - class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + if (method_map[cd.properties[i].setter].arguments.size() > 1) { + // Setters with additional arguments are exposed in the method list, so we link them here for quick access. + class_desc->push_meta("@method " + cd.properties[i].setter); + class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + class_desc->pop(); + } else { + class_desc->add_text(cd.properties[i].setter + TTR("(value)")); + } class_desc->pop(); // color class_desc->push_color(comment_color); class_desc->add_text(" setter"); @@ -1039,7 +1069,14 @@ void EditorHelp::_update_doc() { class_desc->push_cell(); class_desc->push_font(doc_code_font); class_desc->push_color(text_color); - class_desc->add_text(cd.properties[i].getter + "()"); + if (method_map[cd.properties[i].getter].arguments.size() > 0) { + // Getters with additional arguments are exposed in the method list, so we link them here for quick access. + class_desc->push_meta("@method " + cd.properties[i].getter); + class_desc->add_text(cd.properties[i].getter + "()"); + class_desc->pop(); + } else { + class_desc->add_text(cd.properties[i].getter + "()"); + } class_desc->pop(); //color class_desc->push_color(comment_color); class_desc->add_text(" getter"); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index cfa0c7a063..74267452e6 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1283,14 +1283,25 @@ void EditorPropertyVector3::_value_changed(double val, const String &p_name) { } void EditorPropertyVector3::update_property() { - Vector3 val = get_edited_object()->get(get_edited_property()); + update_using_vector(get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyVector3::update_using_vector(Vector3 p_vector) { setting = true; - spin[0]->set_value(val.x); - spin[1]->set_value(val.y); - spin[2]->set_value(val.z); + spin[0]->set_value(p_vector.x); + spin[1]->set_value(p_vector.y); + spin[2]->set_value(p_vector.z); setting = false; } +Vector3 EditorPropertyVector3::get_vector() { + Vector3 v3; + v3.x = spin[0]->get_value(); + v3.y = spin[1]->get_value(); + v3.z = spin[2]->get_value(); + return v3; +} + void EditorPropertyVector3::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { Color base = get_theme_color("accent_color", "Editor"); @@ -1434,7 +1445,7 @@ EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) { setting = false; } -///////////////////// RECT2 ///////////////////////// +///////////////////// RECT2i ///////////////////////// void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { if (setting) { @@ -1520,7 +1531,7 @@ EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) { setting = false; } -///////////////////// VECTOR3 ///////////////////////// +///////////////////// VECTOR3i ///////////////////////// void EditorPropertyVector3i::_value_changed(double val, const String &p_name) { if (setting) { @@ -2029,21 +2040,23 @@ void EditorPropertyTransform::_value_changed(double val, const String &p_name) { } void EditorPropertyTransform::update_property() { - Transform val = get_edited_object()->get(get_edited_property()); - setting = true; - spin[0]->set_value(val.basis[0][0]); - spin[1]->set_value(val.basis[1][0]); - spin[2]->set_value(val.basis[2][0]); - spin[3]->set_value(val.basis[0][1]); - spin[4]->set_value(val.basis[1][1]); - spin[5]->set_value(val.basis[2][1]); - spin[6]->set_value(val.basis[0][2]); - spin[7]->set_value(val.basis[1][2]); - spin[8]->set_value(val.basis[2][2]); - spin[9]->set_value(val.origin[0]); - spin[10]->set_value(val.origin[1]); - spin[11]->set_value(val.origin[2]); + update_using_transform(get_edited_object()->get(get_edited_property())); +} +void EditorPropertyTransform::update_using_transform(Transform p_transform) { + setting = true; + spin[0]->set_value(p_transform.basis[0][0]); + spin[1]->set_value(p_transform.basis[1][0]); + spin[2]->set_value(p_transform.basis[2][0]); + spin[3]->set_value(p_transform.basis[0][1]); + spin[4]->set_value(p_transform.basis[1][1]); + spin[5]->set_value(p_transform.basis[2][1]); + spin[6]->set_value(p_transform.basis[0][2]); + spin[7]->set_value(p_transform.basis[1][2]); + spin[8]->set_value(p_transform.basis[2][2]); + spin[9]->set_value(p_transform.origin[0]); + spin[10]->set_value(p_transform.origin[1]); + spin[11]->set_value(p_transform.origin[2]); setting = false; } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 61c11f4534..35e6c10d90 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -392,6 +392,8 @@ protected: public: virtual void update_property(); + virtual void update_using_vector(Vector3 p_vector); + virtual Vector3 get_vector(); void setup(double p_min, double p_max, double p_step, bool p_no_slider); EditorPropertyVector3(bool p_force_wide = false); }; @@ -536,6 +538,7 @@ protected: public: virtual void update_property(); + virtual void update_using_transform(Transform p_transform); void setup(double p_min, double p_max, double p_step, bool p_no_slider); EditorPropertyTransform(); }; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 2586f17fe1..52da8dea19 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -62,125 +62,56 @@ void BoneTransformEditor::create_editors() { enabled_checkbox->set_visible(toggle_enabled); section->get_vbox()->add_child(enabled_checkbox); - Label *l1 = memnew(Label(TTR("Translation"))); - section->get_vbox()->add_child(l1); - - translation_grid = memnew(GridContainer()); - translation_grid->set_columns(TRANSLATION_COMPONENTS); - section->get_vbox()->add_child(translation_grid); - - Label *l2 = memnew(Label(TTR("Rotation Degrees"))); - section->get_vbox()->add_child(l2); - - rotation_grid = memnew(GridContainer()); - rotation_grid->set_columns(ROTATION_DEGREES_COMPONENTS); - section->get_vbox()->add_child(rotation_grid); - - Label *l3 = memnew(Label(TTR("Scale"))); - section->get_vbox()->add_child(l3); - - scale_grid = memnew(GridContainer()); - scale_grid->set_columns(SCALE_COMPONENTS); - section->get_vbox()->add_child(scale_grid); - - Label *l4 = memnew(Label(TTR("Transform"))); - section->get_vbox()->add_child(l4); - - transform_grid = memnew(GridContainer()); - transform_grid->set_columns(TRANSFORM_CONTROL_COMPONENTS); - section->get_vbox()->add_child(transform_grid); - - static const char *desc[TRANSFORM_COMPONENTS] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" }; - - for (int i = 0; i < TRANSFORM_CONTROL_COMPONENTS; ++i) { - translation_slider[i] = memnew(EditorSpinSlider()); - translation_slider[i]->set_label(desc[i]); - setup_spinner(translation_slider[i], false); - translation_grid->add_child(translation_slider[i]); - - rotation_slider[i] = memnew(EditorSpinSlider()); - rotation_slider[i]->set_label(desc[i]); - setup_spinner(rotation_slider[i], false); - rotation_grid->add_child(rotation_slider[i]); - - scale_slider[i] = memnew(EditorSpinSlider()); - scale_slider[i]->set_label(desc[i]); - setup_spinner(scale_slider[i], false); - scale_grid->add_child(scale_slider[i]); - } - - for (int i = 0; i < TRANSFORM_COMPONENTS; ++i) { - transform_slider[i] = memnew(EditorSpinSlider()); - transform_slider[i]->set_label(desc[i]); - setup_spinner(transform_slider[i], true); - transform_grid->add_child(transform_slider[i]); - } -} - -void BoneTransformEditor::setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner) { - spinner->set_flat(true); - spinner->set_min(-10000); - spinner->set_max(10000); - spinner->set_step(0.001f); - spinner->set_hide_slider(true); - spinner->set_allow_greater(true); - spinner->set_allow_lesser(true); - spinner->set_h_size_flags(SIZE_EXPAND_FILL); - - spinner->connect_compat("value_changed", this, "_value_changed", varray(is_transform_spinner)); + // Translation property + translation_property = memnew(EditorPropertyVector3()); + translation_property->setup(-10000, 10000, 0.001f, true); + translation_property->set_label("Translation"); + translation_property->set_use_folding(true); + translation_property->set_read_only(false); + translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + section->get_vbox()->add_child(translation_property); + + // Rotation property + rotation_property = memnew(EditorPropertyVector3()); + rotation_property->setup(-10000, 10000, 0.001f, true); + rotation_property->set_label("Rotation Degrees"); + rotation_property->set_use_folding(true); + rotation_property->set_read_only(false); + rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + section->get_vbox()->add_child(rotation_property); + + // Scale property + scale_property = memnew(EditorPropertyVector3()); + scale_property->setup(-10000, 10000, 0.001f, true); + scale_property->set_label("Scale"); + scale_property->set_use_folding(true); + scale_property->set_read_only(false); + scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + section->get_vbox()->add_child(scale_property); + + // Transform/Matrix section + transform_section = memnew(EditorInspectorSection); + transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); + section->get_vbox()->add_child(transform_section); + + // Transform/Matrix property + transform_property = memnew(EditorPropertyTransform()); + transform_property->setup(-10000, 10000, 0.001f, true); + transform_property->set_label("Transform"); + transform_property->set_use_folding(true); + transform_property->set_read_only(false); + transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); + transform_section->get_vbox()->add_child(transform_property); } void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - key_button->connect_compat("pressed", this, "_key_button_pressed"); - enabled_checkbox->connect_compat("toggled", this, "_checkbox_toggled"); + key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); + enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled)); [[fallthrough]]; } - case NOTIFICATION_THEME_CHANGED: { - const Color base = get_theme_color("accent_color", "Editor"); - const Color bg_color = get_theme_color("property_color", "Editor"); - const Color bg_lbl_color(bg_color.r, bg_color.g, bg_color.b, 0.5); - - for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { - Color c = base; - c.set_hsv(float(i % TRANSLATION_COMPONENTS) / TRANSLATION_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); - if (!translation_slider[i]) { - continue; - } - translation_slider[i]->set_custom_label_color(true, c); - } - - for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { - Color c = base; - c.set_hsv(float(i % ROTATION_DEGREES_COMPONENTS) / ROTATION_DEGREES_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); - if (!rotation_slider[i]) { - continue; - } - rotation_slider[i]->set_custom_label_color(true, c); - } - - for (int i = 0; i < SCALE_COMPONENTS; i++) { - Color c = base; - c.set_hsv(float(i % SCALE_COMPONENTS) / SCALE_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); - if (!scale_slider[i]) { - continue; - } - scale_slider[i]->set_custom_label_color(true, c); - } - - for (int i = 0; i < TRANSFORM_COMPONENTS; i++) { - Color c = base; - c.set_hsv(float(i % TRANSFORM_COMPONENTS) / TRANSFORM_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); - if (!transform_slider[i]) { - continue; - } - transform_slider[i]->set_custom_label_color(true, c); - } - - break; - } case NOTIFICATION_SORT_CHILDREN: { const Ref<Font> font = get_theme_font("font", "Tree"); @@ -189,8 +120,8 @@ void BoneTransformEditor::_notification(int p_what) { buffer.y += font->get_height(); buffer.y += get_theme_constant("vseparation", "Tree"); - const float vector_height = translation_grid->get_size().y; - const float transform_height = transform_grid->get_size().y; + const float vector_height = translation_property->get_size().y; + const float transform_height = transform_property->get_size().y; const float button_height = key_button->get_size().y; const float width = get_size().x - get_theme_constant("inspector_margin", "Editor"); @@ -202,10 +133,10 @@ void BoneTransformEditor::_notification(int p_what) { } if (section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(translation_grid->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(rotation_grid->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(scale_grid->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(transform_grid->get_position() + buffer, Size2(width, transform_height))); + input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height))); } else { const int32_t start = input_rects.size(); const int32_t empty_input_rect_elements = 4; @@ -234,49 +165,53 @@ void BoneTransformEditor::_notification(int p_what) { } } -void BoneTransformEditor::_value_changed(const double p_value, const bool p_from_transform) { +void BoneTransformEditor::_value_changed(const double p_value) { if (updating) return; - if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { - const Transform tform = compute_transform(p_from_transform); + Transform tform = compute_transform_from_vector3s(); + _change_transform(tform); +} +void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { + if (updating) + return; + Transform tform = compute_transform_from_vector3s(); + _change_transform(tform); +} + +Transform BoneTransformEditor::compute_transform_from_vector3s() const { + // Convert rotation from degrees to radians. + Vector3 prop_rotation = rotation_property->get_vector(); + prop_rotation.x = Math::deg2rad(prop_rotation.x); + prop_rotation.y = Math::deg2rad(prop_rotation.y); + prop_rotation.z = Math::deg2rad(prop_rotation.z); + + return Transform( + Basis(prop_rotation, scale_property->get_vector()), + translation_property->get_vector()); +} + +void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean) { + if (updating) + return; + _change_transform(p_transform); +} + +void BoneTransformEditor::_change_transform(Transform p_new_transform) { + if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); - undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), tform); + undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform); undo_redo->commit_action(); } else if (property.get_slicec('/', 0) == "bones") { - const Transform tform = compute_transform(p_from_transform); - undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); - undo_redo->add_do_property(skeleton, property, tform); + undo_redo->add_do_property(skeleton, property, p_new_transform); undo_redo->commit_action(); } } -Transform BoneTransformEditor::compute_transform(const bool p_from_transform) const { - // Last modified was a raw transform column... - if (p_from_transform) { - Transform tform; - - for (int i = 0; i < BASIS_COMPONENTS; ++i) { - tform.basis[i / BASIS_SPLIT_COMPONENTS][i % BASIS_SPLIT_COMPONENTS] = transform_slider[i]->get_value(); - } - - for (int i = 0; i < TRANSLATION_COMPONENTS; ++i) { - tform.origin[i] = transform_slider[i + BASIS_COMPONENTS]->get_value(); - } - - return tform; - } - - return Transform( - Basis(Vector3(Math::deg2rad(rotation_slider[0]->get_value()), Math::deg2rad(rotation_slider[1]->get_value()), Math::deg2rad(rotation_slider[2]->get_value())), - Vector3(scale_slider[0]->get_value(), scale_slider[1]->get_value(), scale_slider[2]->get_value())), - Vector3(translation_slider[0]->get_value(), translation_slider[1]->get_value(), translation_slider[2]->get_value())); -} - void BoneTransformEditor::update_enabled_checkbox() { if (enabled_checkbox) { const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; @@ -285,12 +220,6 @@ void BoneTransformEditor::update_enabled_checkbox() { } } -void BoneTransformEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_value_changed", "value"), &BoneTransformEditor::_value_changed); - ClassDB::bind_method(D_METHOD("_key_button_pressed"), &BoneTransformEditor::_key_button_pressed); - ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &BoneTransformEditor::_checkbox_toggled); -} - void BoneTransformEditor::_update_properties() { if (updating) return; @@ -318,47 +247,22 @@ void BoneTransformEditor::_update_custom_pose_properties() { } void BoneTransformEditor::_update_transform_properties(Transform tform) { - Quat rot = tform.get_basis(); - Vector3 rot_rad = rot.get_euler(); - Vector3 rot_degrees = Vector3(Math::rad2deg(rot_rad.x), Math::rad2deg(rot_rad.y), Math::rad2deg(rot_rad.z)); - Vector3 tr = tform.get_origin(); + Basis rotation_basis = tform.get_basis(); + Vector3 rotation_radians = rotation_basis.get_rotation_euler(); + Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z)); + Vector3 translation = tform.get_origin(); Vector3 scale = tform.basis.get_scale(); - for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { - translation_slider[i]->set_value(tr[i]); - } - - for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { - rotation_slider[i]->set_value(rot_degrees[i]); - } - - for (int i = 0; i < SCALE_COMPONENTS; i++) { - scale_slider[i]->set_value(scale[i]); - } - - transform_slider[0]->set_value(tform.get_basis()[Vector3::AXIS_X].x); - transform_slider[1]->set_value(tform.get_basis()[Vector3::AXIS_X].y); - transform_slider[2]->set_value(tform.get_basis()[Vector3::AXIS_X].z); - transform_slider[3]->set_value(tform.get_basis()[Vector3::AXIS_Y].x); - transform_slider[4]->set_value(tform.get_basis()[Vector3::AXIS_Y].y); - transform_slider[5]->set_value(tform.get_basis()[Vector3::AXIS_Y].z); - transform_slider[6]->set_value(tform.get_basis()[Vector3::AXIS_Z].x); - transform_slider[7]->set_value(tform.get_basis()[Vector3::AXIS_Z].y); - transform_slider[8]->set_value(tform.get_basis()[Vector3::AXIS_Z].z); - - for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { - transform_slider[BASIS_COMPONENTS + i]->set_value(tform.get_origin()[i]); - } + translation_property->update_using_vector(translation); + rotation_property->update_using_vector(rotation_degrees); + scale_property->update_using_vector(scale); + transform_property->update_using_transform(tform); update_enabled_checkbox(); updating = false; } BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : - translation_slider(), - rotation_slider(), - scale_slider(), - transform_slider(), skeleton(p_skeleton), key_button(nullptr), enabled_checkbox(nullptr), @@ -397,7 +301,7 @@ void BoneTransformEditor::_key_button_pressed() { return; // Need to normalize the basis before you key it - Transform tform = compute_transform(true); + Transform tform = compute_transform_from_vector3s(); tform.orthonormalize(); AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); } @@ -627,8 +531,7 @@ void Skeleton3DEditor::update_joint_tree() { items.insert(-1, root); const Vector<int> &joint_porder = skeleton->get_bone_process_orders(); - - Ref<Texture> bone_icon = get_theme_icon("Skeleton3D", "EditorIcons"); + Ref<Texture> bone_icon = get_theme_icon("BoneAttachment3D", "EditorIcons"); for (int i = 0; i < joint_porder.size(); ++i) { const int b_idx = joint_porder[i]; @@ -714,11 +617,11 @@ void Skeleton3DEditor::_notification(int p_what) { update_joint_tree(); update_editors(); - get_tree()->connect_compat("node_removed", this, "_node_removed", Vector<Variant>(), Object::CONNECT_ONESHOT); - joint_tree->connect_compat("item_selected", this, "_joint_tree_selection_changed"); - joint_tree->connect_compat("item_rmb_selected", this, "_joint_tree_rmb_select"); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); + joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); + joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); #ifdef TOOLS_ENABLED - skeleton->connect_compat("pose_updated", this, "_update_properties"); + skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); #endif // TOOLS_ENABLED break; diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 8b0639ed92..a5b8ce80a9 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -41,30 +41,19 @@ class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; class CheckBox; +class EditorPropertyTransform; +class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, VBoxContainer); - static const int32_t TRANSLATION_COMPONENTS = 3; - static const int32_t ROTATION_DEGREES_COMPONENTS = 3; - static const int32_t SCALE_COMPONENTS = 3; - static const int32_t BASIS_COMPONENTS = 9; - static const int32_t BASIS_SPLIT_COMPONENTS = 3; - static const int32_t TRANSFORM_COMPONENTS = 12; - static const int32_t TRANSFORM_SPLIT_COMPONENTS = 3; - static const int32_t TRANSFORM_CONTROL_COMPONENTS = 3; - EditorInspectorSection *section; - GridContainer *translation_grid; - GridContainer *rotation_grid; - GridContainer *scale_grid; - GridContainer *transform_grid; - - EditorSpinSlider *translation_slider[TRANSLATION_COMPONENTS]; - EditorSpinSlider *rotation_slider[ROTATION_DEGREES_COMPONENTS]; - EditorSpinSlider *scale_slider[SCALE_COMPONENTS]; - EditorSpinSlider *transform_slider[TRANSFORM_COMPONENTS]; + EditorPropertyVector3 *translation_property; + EditorPropertyVector3 *rotation_property; + EditorPropertyVector3 *scale_property; + EditorInspectorSection *transform_section; + EditorPropertyTransform *transform_property; Rect2 background_rects[5]; @@ -83,17 +72,22 @@ class BoneTransformEditor : public VBoxContainer { String label; void create_editors(); - void setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner); - void _value_changed(const double p_value, const bool p_from_transform); - - Transform compute_transform(const bool p_from_transform) const; + // Called when one of the EditorSpinSliders are changed. + void _value_changed(const double p_value); + // Called when the one of the EditorPropertyVector3 are updated. + void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean); + // Called when the transform_property is updated. + void _value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean); + // Changes the transform to the given transform and updates the UI accordingly. + void _change_transform(Transform p_new_transform); + // Creates a Transform using the EditorPropertyVector3 properties. + Transform compute_transform_from_vector3s() const; void update_enabled_checkbox(); protected: void _notification(int p_what); - static void _bind_methods(); public: BoneTransformEditor(Skeleton3D *p_skeleton); diff --git a/main/main.cpp b/main/main.cpp index 94dd895a26..5320274add 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -930,6 +930,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph #endif } + // Initialize user data dir. + OS::get_singleton()->ensure_user_data_dir(); + GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60); ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc", PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1")); // No negative and limit to 500 due to crashes GLOBAL_DEF("network/limits/debugger/max_chars_per_second", 32768); @@ -948,7 +951,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph packed_data->set_disabled(true); globals->set_disable_feature_overrides(true); } - #endif GLOBAL_DEF("logging/file_logging/enable_file_logging", false); @@ -1266,10 +1268,6 @@ Error Main::setup2(Thread::ID p_main_tid_override) { Thread::_main_thread_id = p_main_tid_override; } - /* Initialize user data dir */ - - OS::get_singleton()->ensure_user_data_dir(); - /* Initialize Input */ input = memnew(Input); diff --git a/main/tests/test_basis.cpp b/main/tests/test_basis.cpp new file mode 100644 index 0000000000..ac25151fd8 --- /dev/null +++ b/main/tests/test_basis.cpp @@ -0,0 +1,325 @@ +/*************************************************************************/ +/* test_fbx.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "test_basis.h" + +#include "core/math/random_number_generator.h" +#include "core/os/os.h" +#include "core/ustring.h" + +namespace TestBasis { + +enum RotOrder { + EulerXYZ, + EulerXZY, + EulerYZX, + EulerYXZ, + EulerZXY, + EulerZYX +}; + +Vector3 deg2rad(const Vector3 &p_rotation) { + return p_rotation / 180.0 * Math_PI; +} + +Vector3 rad2deg(const Vector3 &p_rotation) { + return p_rotation / Math_PI * 180.0; +} + +Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) { + Basis ret; + switch (mode) { + case EulerXYZ: + ret.set_euler_xyz(p_rotation); + break; + + case EulerXZY: + ret.set_euler_xzy(p_rotation); + break; + + case EulerYZX: + ret.set_euler_yzx(p_rotation); + break; + + case EulerYXZ: + ret.set_euler_yxz(p_rotation); + break; + + case EulerZXY: + ret.set_euler_zxy(p_rotation); + break; + + case EulerZYX: + ret.set_euler_zyx(p_rotation); + break; + + default: + // If you land here, Please integrate all rotation orders. + CRASH_NOW_MSG("This is not unreachable."); + } + + return ret; +} + +Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) { + switch (mode) { + case EulerXYZ: + return p_rotation.get_euler_xyz(); + + case EulerXZY: + return p_rotation.get_euler_xzy(); + + case EulerYZX: + return p_rotation.get_euler_yzx(); + + case EulerYXZ: + return p_rotation.get_euler_yxz(); + + case EulerZXY: + return p_rotation.get_euler_zxy(); + + case EulerZYX: + return p_rotation.get_euler_zyx(); + + default: + // If you land here, Please integrate all rotation orders. + CRASH_NOW_MSG("This is not unreachable."); + return Vector3(); + } +} + +String get_rot_order_name(RotOrder ro) { + switch (ro) { + case EulerXYZ: + return "XYZ"; + case EulerXZY: + return "XZY"; + case EulerYZX: + return "YZX"; + case EulerYXZ: + return "YXZ"; + case EulerZXY: + return "ZXY"; + case EulerZYX: + return "ZYX"; + default: + return "[Not supported]"; + } +} + +bool test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { + // This test: + // 1. Converts the rotation vector from deg to rad. + // 2. Converts euler to basis. + // 3. Converts the above basis back into euler. + // 4. Converts the above euler into basis again. + // 5. Compares the basis obtained in step 2 with the basis of step 4 + // + // The conversion "basis to euler", done in the step 3, may be different from + // the original euler, even if the final rotation are the same. + // This happens because there are more ways to represents the same rotation, + // both valid, using eulers. + // For this reason is necessary to convert that euler back to basis and finally + // compares it. + // + // In this way we can assert that both functions: basis to euler / euler to basis + // are correct. + + bool pass = true; + + // Euler to rotation + const Vector3 original_euler = deg2rad(deg_original_euler); + const Basis to_rotation = EulerToBasis(rot_order, original_euler); + + // Euler from rotation + const Vector3 euler_from_rotation = BasisToEuler(rot_order, to_rotation); + const Basis rotation_from_computed_euler = EulerToBasis(rot_order, euler_from_rotation); + + Basis res = to_rotation.inverse() * rotation_from_computed_euler; + + if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) { + OS::get_singleton()->print("Fail due to X %ls\n", String(res.get_axis(0)).c_str()); + pass = false; + } + if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) { + OS::get_singleton()->print("Fail due to Y %ls\n", String(res.get_axis(1)).c_str()); + pass = false; + } + if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) { + OS::get_singleton()->print("Fail due to Z %ls\n", String(res.get_axis(2)).c_str()); + pass = false; + } + + if (pass) { + // Double check `to_rotation` decomposing with XYZ rotation order. + const Vector3 euler_xyz_from_rotation = to_rotation.get_euler_xyz(); + Basis rotation_from_xyz_computed_euler; + rotation_from_xyz_computed_euler.set_euler_xyz(euler_xyz_from_rotation); + + res = to_rotation.inverse() * rotation_from_xyz_computed_euler; + + if ((res.get_axis(0) - Vector3(1.0, 0.0, 0.0)).length() > 0.1) { + OS::get_singleton()->print("Double check with XYZ rot order failed, due to X %ls\n", String(res.get_axis(0)).c_str()); + pass = false; + } + if ((res.get_axis(1) - Vector3(0.0, 1.0, 0.0)).length() > 0.1) { + OS::get_singleton()->print("Double check with XYZ rot order failed, due to Y %ls\n", String(res.get_axis(1)).c_str()); + pass = false; + } + if ((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() > 0.1) { + OS::get_singleton()->print("Double check with XYZ rot order failed, due to Z %ls\n", String(res.get_axis(2)).c_str()); + pass = false; + } + } + + if (pass == false) { + // Print phase only if not pass. + OS *os = OS::get_singleton(); + os->print("Rotation order: %ls\n.", get_rot_order_name(rot_order).c_str()); + os->print("Original Rotation: %ls\n", String(deg_original_euler).c_str()); + os->print("Quaternion to rotation order: %ls\n", String(rad2deg(euler_from_rotation)).c_str()); + } + + return pass; +} + +void test_euler_conversion() { + Vector<RotOrder> rotorder_to_test; + rotorder_to_test.push_back(EulerXYZ); + rotorder_to_test.push_back(EulerXZY); + rotorder_to_test.push_back(EulerYZX); + rotorder_to_test.push_back(EulerYXZ); + rotorder_to_test.push_back(EulerZXY); + rotorder_to_test.push_back(EulerZYX); + + Vector<Vector3> vectors_to_test; + + // Test the special cases. + vectors_to_test.push_back(Vector3(0.0, 0.0, 0.0)); + vectors_to_test.push_back(Vector3(0.5, 0.5, 0.5)); + vectors_to_test.push_back(Vector3(-0.5, -0.5, -0.5)); + vectors_to_test.push_back(Vector3(40.0, 40.0, 40.0)); + vectors_to_test.push_back(Vector3(-40.0, -40.0, -40.0)); + vectors_to_test.push_back(Vector3(0.0, 0.0, -90.0)); + vectors_to_test.push_back(Vector3(0.0, -90.0, 0.0)); + vectors_to_test.push_back(Vector3(-90.0, 0.0, 0.0)); + vectors_to_test.push_back(Vector3(0.0, 0.0, 90.0)); + vectors_to_test.push_back(Vector3(0.0, 90.0, 0.0)); + vectors_to_test.push_back(Vector3(90.0, 0.0, 0.0)); + vectors_to_test.push_back(Vector3(0.0, 0.0, -30.0)); + vectors_to_test.push_back(Vector3(0.0, -30.0, 0.0)); + vectors_to_test.push_back(Vector3(-30.0, 0.0, 0.0)); + vectors_to_test.push_back(Vector3(0.0, 0.0, 30.0)); + vectors_to_test.push_back(Vector3(0.0, 30.0, 0.0)); + vectors_to_test.push_back(Vector3(30.0, 0.0, 0.0)); + vectors_to_test.push_back(Vector3(0.5, 50.0, 20.0)); + vectors_to_test.push_back(Vector3(-0.5, -50.0, -20.0)); + vectors_to_test.push_back(Vector3(0.5, 0.0, 90.0)); + vectors_to_test.push_back(Vector3(0.5, 0.0, -90.0)); + vectors_to_test.push_back(Vector3(360.0, 360.0, 360.0)); + vectors_to_test.push_back(Vector3(-360.0, -360.0, -360.0)); + vectors_to_test.push_back(Vector3(-90.0, 60.0, -90.0)); + vectors_to_test.push_back(Vector3(90.0, 60.0, -90.0)); + vectors_to_test.push_back(Vector3(90.0, -60.0, -90.0)); + vectors_to_test.push_back(Vector3(-90.0, -60.0, -90.0)); + vectors_to_test.push_back(Vector3(-90.0, 60.0, 90.0)); + vectors_to_test.push_back(Vector3(90.0, 60.0, 90.0)); + vectors_to_test.push_back(Vector3(90.0, -60.0, 90.0)); + vectors_to_test.push_back(Vector3(-90.0, -60.0, 90.0)); + vectors_to_test.push_back(Vector3(60.0, 90.0, -40.0)); + vectors_to_test.push_back(Vector3(60.0, -90.0, -40.0)); + vectors_to_test.push_back(Vector3(-60.0, -90.0, -40.0)); + vectors_to_test.push_back(Vector3(-60.0, 90.0, 40.0)); + vectors_to_test.push_back(Vector3(60.0, 90.0, 40.0)); + vectors_to_test.push_back(Vector3(60.0, -90.0, 40.0)); + vectors_to_test.push_back(Vector3(-60.0, -90.0, 40.0)); + vectors_to_test.push_back(Vector3(-90.0, 90.0, -90.0)); + vectors_to_test.push_back(Vector3(90.0, 90.0, -90.0)); + vectors_to_test.push_back(Vector3(90.0, -90.0, -90.0)); + vectors_to_test.push_back(Vector3(-90.0, -90.0, -90.0)); + vectors_to_test.push_back(Vector3(-90.0, 90.0, 90.0)); + vectors_to_test.push_back(Vector3(90.0, 90.0, 90.0)); + vectors_to_test.push_back(Vector3(90.0, -90.0, 90.0)); + vectors_to_test.push_back(Vector3(20.0, 150.0, 30.0)); + vectors_to_test.push_back(Vector3(20.0, -150.0, 30.0)); + vectors_to_test.push_back(Vector3(-120.0, -150.0, 30.0)); + vectors_to_test.push_back(Vector3(-120.0, -150.0, -130.0)); + vectors_to_test.push_back(Vector3(120.0, -150.0, -130.0)); + vectors_to_test.push_back(Vector3(120.0, 150.0, -130.0)); + vectors_to_test.push_back(Vector3(120.0, 150.0, 130.0)); + + // Add 1000 random vectors with weirds numbers. + RandomNumberGenerator rng; + for (int _ = 0; _ < 1000; _ += 1) { + vectors_to_test.push_back(Vector3( + rng.randf_range(-1800, 1800), + rng.randf_range(-1800, 1800), + rng.randf_range(-1800, 1800))); + } + + bool success = true; + for (int h = 0; h < rotorder_to_test.size(); h += 1) { + int passed = 0; + int failed = 0; + for (int i = 0; i < vectors_to_test.size(); i += 1) { + if (test_rotation(vectors_to_test[i], rotorder_to_test[h])) { + //OS::get_singleton()->print("Success. \n\n"); + passed += 1; + } else { + OS::get_singleton()->print("FAILED FAILED FAILED. \n\n"); + OS::get_singleton()->print("------------>\n"); + OS::get_singleton()->print("------------>\n"); + failed += 1; + success = false; + } + } + + if (failed == 0) { + OS::get_singleton()->print("%i passed tests for rotation order: %ls.\n", passed, get_rot_order_name(rotorder_to_test[h]).c_str()); + } else { + OS::get_singleton()->print("%i FAILED tests for rotation order: %ls.\n", failed, get_rot_order_name(rotorder_to_test[h]).c_str()); + } + } + + if (success) { + OS::get_singleton()->print("Euler conversion checks passed.\n"); + } else { + OS::get_singleton()->print("Euler conversion checks FAILED.\n"); + } +} + +MainLoop *test() { + OS::get_singleton()->print("Start euler conversion checks.\n"); + test_euler_conversion(); + + return NULL; +} + +} // namespace TestBasis diff --git a/main/tests/test_basis.h b/main/tests/test_basis.h new file mode 100644 index 0000000000..67c9db8877 --- /dev/null +++ b/main/tests/test_basis.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* test_fbx.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_BASIS_H +#define TEST_BASIS_H + +#include "core/os/main_loop.h" + +namespace TestBasis { +MainLoop *test(); +} + +#endif diff --git a/main/tests/test_main.cpp b/main/tests/test_main.cpp index 0bb8367240..5ebdaf1741 100644 --- a/main/tests/test_main.cpp +++ b/main/tests/test_main.cpp @@ -35,6 +35,7 @@ #ifdef DEBUG_ENABLED #include "test_astar.h" +#include "test_basis.h" #include "test_class_db.h" #include "test_gdscript.h" #include "test_gui.h" @@ -51,6 +52,7 @@ const char **tests_get_names() { static const char *test_names[] = { "string", "math", + "basis", "physics_2d", "physics_3d", "render", @@ -79,6 +81,10 @@ MainLoop *test_main(String p_test, const List<String> &p_args) { return TestMath::test(); } + if (p_test == "basis") { + return TestBasis::test(); + } + if (p_test == "physics_2d") { return TestPhysics2D::test(); } diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index ba425371a8..2f4f7d7a4c 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -29,14 +29,15 @@ /*************************************************************************/ #include "texture_loader_dds.h" + #include "core/os/file_access.h" #define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0]))) +// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header + enum { DDS_MAGIC = 0x20534444, - DDSD_CAPS = 0x00000001, - DDSD_PIXELFORMAT = 0x00001000, DDSD_PITCH = 0x00000008, DDSD_LINEARSIZE = 0x00080000, DDSD_MIPMAPCOUNT = 0x00020000, @@ -47,7 +48,6 @@ enum { }; enum DDSFormat { - DDS_DXT1, DDS_DXT3, DDS_DXT5, @@ -128,7 +128,9 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, //validate - if (magic != DDS_MAGIC || hsize != 124 || !(flags & DDSD_PIXELFORMAT) || !(flags & DDSD_CAPS)) { + // We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing, + // but non-mandatory when reading (as some writers don't set them)... + if (magic != DDS_MAGIC || hsize != 124) { ERR_FAIL_V_MSG(RES(), "Invalid or unsupported DDS texture file '" + p_path + "'."); } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 50d8289fd1..3a5db3687b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -3093,6 +3093,14 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } } + + for (int i = 0; i < base_type.class_type->subclasses.size(); i++) { + if (base_type.class_type->subclasses[i]->name == p_symbol) { + r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location = base_type.class_type->subclasses[i]->line; + return OK; + } + } } base_type = base_type.class_type->base_type; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a06ecb1ea8..d2867cb77a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2821,6 +2821,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { #ifdef DEBUG_ENABLED + pending_newline = -1; // reset for the new block + NewLineNode *nl = alloc_node<NewLineNode>(); nl->line = tokenizer->get_token_line(); diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index 7391e8790d..3bbbf29d3b 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -15,7 +15,9 @@ def build_godot_tools(source, target, env): from .solution_builder import build_solution - build_solution(env, solution_path, build_config) + extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] + + build_solution(env, solution_path, build_config, extra_msbuild_args) # No need to copy targets. The GodotTools csproj takes care of copying them. diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 23f01b3cca..80e3b59325 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -191,17 +191,16 @@ def configure(env, env_mono): env.Append(LIBS=["psapi"]) env.Append(LIBS=["version"]) else: - mono_lib_name = find_name_in_dir_files( - mono_lib_path, mono_lib_names, prefixes=["", "lib"], extensions=lib_suffixes - ) + mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes) - if not mono_lib_name: + if not mono_lib_file: raise RuntimeError("Could not find mono library in: " + mono_lib_path) if env.msvc: - env.Append(LINKFLAGS=mono_lib_name + ".lib") + env.Append(LINKFLAGS=mono_lib_file) else: - env.Append(LIBS=[mono_lib_name]) + mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file) + env.Append(LINKFLAGS=mono_lib_file_path) mono_bin_path = os.path.join(mono_root, "bin") diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs index d069651dd3..572c541412 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs @@ -19,9 +19,12 @@ namespace GodotTools.IdeMessaging private readonly string identity; private string MetaFilePath { get; } + private DateTime? metaFileModifiedTime; private GodotIdeMetadata godotIdeMetadata; private readonly FileSystemWatcher fsWatcher; + public string GodotEditorExecutablePath => godotIdeMetadata.EditorExecutablePath; + private readonly IMessageHandler messageHandler; private Peer peer; @@ -123,7 +126,7 @@ namespace GodotTools.IdeMessaging MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); // FileSystemWatcher requires an existing directory - if (!File.Exists(projectMetadataDir)) + if (!Directory.Exists(projectMetadataDir)) Directory.CreateDirectory(projectMetadataDir); fsWatcher = new FileSystemWatcher(projectMetadataDir, GodotIdeMetadata.DefaultFileName); @@ -142,6 +145,13 @@ namespace GodotTools.IdeMessaging if (!File.Exists(MetaFilePath)) return; + var lastWriteTime = File.GetLastWriteTime(MetaFilePath); + + if (lastWriteTime == metaFileModifiedTime) + return; + + metaFileModifiedTime = lastWriteTime; + var metadata = ReadMetadataFile(); if (metadata != null && metadata != godotIdeMetadata) @@ -173,6 +183,13 @@ namespace GodotTools.IdeMessaging if (IsConnected || !File.Exists(MetaFilePath)) return; + var lastWriteTime = File.GetLastWriteTime(MetaFilePath); + + if (lastWriteTime == metaFileModifiedTime) + return; + + metaFileModifiedTime = lastWriteTime; + var metadata = ReadMetadataFile(); if (metadata != null) @@ -185,7 +202,8 @@ namespace GodotTools.IdeMessaging private GodotIdeMetadata? ReadMetadataFile() { - using (var reader = File.OpenText(MetaFilePath)) + using (var fileStream = new FileStream(MetaFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var reader = new StreamReader(fileStream)) { string portStr = reader.ReadLine(); @@ -272,6 +290,7 @@ namespace GodotTools.IdeMessaging // ReSharper disable once UnusedMember.Global public async void Start() { + fsWatcher.Created += OnMetaFileChanged; fsWatcher.Changed += OnMetaFileChanged; fsWatcher.Deleted += OnMetaFileDeleted; fsWatcher.EnableRaisingEvents = true; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj index 67815959a6..dad6b9ae7a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -4,7 +4,7 @@ <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>7.2</LangVersion> <PackageId>GodotTools.IdeMessaging</PackageId> - <Version>1.1.0</Version> + <Version>1.1.1</Version> <AssemblyVersion>$(Version)</AssemblyVersion> <Authors>Godot Engine contributors</Authors> <Company /> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs index a4e86d6177..10d7e1898e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs @@ -105,49 +105,45 @@ namespace GodotTools.IdeMessaging try { - try + if (msg.Kind == MessageKind.Request) { - if (msg.Kind == MessageKind.Request) - { - var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); - await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); - } - else if (msg.Kind == MessageKind.Response) - { - ResponseAwaiter responseAwaiter; + var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); + await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); + } + else if (msg.Kind == MessageKind.Response) + { + ResponseAwaiter responseAwaiter; - using (await requestsSem.UseAsync()) + using (await requestsSem.UseAsync()) + { + if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0) { - if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0) - { - Logger.LogError($"Received unexpected response: {msg.Id}"); - return; - } - - responseAwaiter = queue.Dequeue(); + Logger.LogError($"Received unexpected response: {msg.Id}"); + return; } - responseAwaiter.SetResult(msg.Content); - } - else - { - throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}"); + responseAwaiter = queue.Dequeue(); } + + responseAwaiter.SetResult(msg.Content); } - catch (Exception e) + else { - Logger.LogError($"Message handler for '{msg}' failed with exception", e); + throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}"); } } catch (Exception e) { - Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + Logger.LogError($"Message handler for '{msg}' failed with exception", e); } } } catch (Exception e) { - Logger.LogError("Unhandled exception in the peer loop", e); + if (!IsDisposed || !(e is SocketException || e.InnerException is SocketException)) + { + Logger.LogError("Unhandled exception in the peer loop", e); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs index 1dd4f852e5..e93db9377b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs @@ -67,6 +67,19 @@ namespace GodotTools.IdeMessaging.Requests { } + public sealed class StopPlayRequest : Request + { + public new const string Id = "StopPlay"; + + public StopPlayRequest() : base(Id) + { + } + } + + public sealed class StopPlayResponse : Response + { + } + public sealed class DebugPlayRequest : Request { public string DebuggerHost { get; set; } diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj new file mode 100644 index 0000000000..5b3ed0b1b7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid> + <OutputType>Exe</OutputType> + <TargetFramework>net472</TargetFramework> + <LangVersion>7.2</LangVersion> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> + <PackageReference Include="EnvDTE" Version="8.0.2" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs new file mode 100644 index 0000000000..affb2a47e7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -0,0 +1,270 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text.RegularExpressions; +using EnvDTE; + +namespace GodotTools.OpenVisualStudio +{ + internal static class Program + { + [DllImport("ole32.dll")] + private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot); + + [DllImport("ole32.dll")] + private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc); + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + private static void ShowHelp() + { + Console.WriteLine("Opens the file(s) in a Visual Studio instance that is editing the specified solution."); + Console.WriteLine("If an existing instance for the solution is not found, a new one is created."); + Console.WriteLine(); + Console.WriteLine("Usage:"); + Console.WriteLine(@" GodotTools.OpenVisualStudio.exe solution [file[;line[;col]]...]"); + Console.WriteLine(); + Console.WriteLine("Lines and columns begin at one. Zero or lower will result in an error."); + Console.WriteLine("If a line is specified but a column is not, the line is selected in the text editor."); + } + + // STAThread needed, otherwise CoRegisterMessageFilter may return CO_E_NOT_SUPPORTED. + [STAThread] + private static int Main(string[] args) + { + if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") + { + ShowHelp(); + return 0; + } + + string solutionFile = NormalizePath(args[0]); + + var dte = FindInstanceEditingSolution(solutionFile); + + if (dte == null) + { + // Open a new instance + + var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); + dte = (DTE)Activator.CreateInstance(visualStudioDteType); + + dte.UserControl = true; + + try + { + dte.Solution.Open(solutionFile); + } + catch (ArgumentException) + { + Console.Error.WriteLine("Solution.Open: Invalid path or file not found"); + return 1; + } + + dte.MainWindow.Visible = true; + } + + MessageFilter.Register(); + + try + { + // Open files + + for (int i = 1; i < args.Length; i++) + { + // Both the line number and the column begin at one + + string[] fileArgumentParts = args[i].Split(';'); + + string filePath = NormalizePath(fileArgumentParts[0]); + + try + { + dte.ItemOperations.OpenFile(filePath); + } + catch (ArgumentException) + { + Console.Error.WriteLine("ItemOperations.OpenFile: Invalid path or file not found"); + return 1; + } + + if (fileArgumentParts.Length > 1) + { + if (int.TryParse(fileArgumentParts[1], out int line)) + { + var textSelection = (TextSelection)dte.ActiveDocument.Selection; + + if (fileArgumentParts.Length > 2) + { + if (int.TryParse(fileArgumentParts[2], out int column)) + { + textSelection.MoveToLineAndOffset(line, column); + } + else + { + Console.Error.WriteLine("The column part of the argument must be a valid integer"); + return 1; + } + } + else + { + textSelection.GotoLine(line, Select: true); + } + } + else + { + Console.Error.WriteLine("The line part of the argument must be a valid integer"); + return 1; + } + } + } + } + finally + { + var mainWindow = dte.MainWindow; + mainWindow.Activate(); + SetForegroundWindow(new IntPtr(mainWindow.HWnd)); + + MessageFilter.Revoke(); + } + + return 0; + } + + private static DTE FindInstanceEditingSolution(string solutionPath) + { + if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) + return null; + + try + { + pprot.EnumRunning(out IEnumMoniker ppenumMoniker); + ppenumMoniker.Reset(); + + var moniker = new IMoniker[1]; + + while (ppenumMoniker.Next(1, moniker, IntPtr.Zero) == 0) + { + string ppszDisplayName; + + CreateBindCtx(0, out IBindCtx ppbc); + + try + { + moniker[0].GetDisplayName(ppbc, null, out ppszDisplayName); + } + finally + { + Marshal.ReleaseComObject(ppbc); + } + + if (ppszDisplayName == null) + continue; + + // The digits after the colon are the process ID + if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]")) + continue; + + if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) + { + if (ppunkObject is DTE dte && dte.Solution.FullName.Length > 0) + { + if (NormalizePath(dte.Solution.FullName) == solutionPath) + return dte; + } + } + } + } + finally + { + Marshal.ReleaseComObject(pprot); + } + + return null; + } + + static string NormalizePath(string path) + { + return new Uri(Path.GetFullPath(path)).LocalPath + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .ToUpperInvariant(); + } + + #region MessageFilter. See: http: //msdn.microsoft.com/en-us/library/ms228772.aspx + + private class MessageFilter : IOleMessageFilter + { + // Class containing the IOleMessageFilter + // thread error-handling functions + + private static IOleMessageFilter _oldFilter; + + // Start the filter + public static void Register() + { + IOleMessageFilter newFilter = new MessageFilter(); + int ret = CoRegisterMessageFilter(newFilter, out _oldFilter); + if (ret != 0) + Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); + } + + // Done with the filter, close it + public static void Revoke() + { + int ret = CoRegisterMessageFilter(_oldFilter, out _); + if (ret != 0) + Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); + } + + // + // IOleMessageFilter functions + // Handle incoming thread requests + int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) + { + // Return the flag SERVERCALL_ISHANDLED + return 0; + } + + // Thread call was rejected, so try again. + int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) + { + if (dwRejectType == 2) + // flag = SERVERCALL_RETRYLATER + { + // Retry the thread call immediately if return >= 0 & < 100 + return 99; + } + + // Too busy; cancel call + return -1; + } + + int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) + { + // Return the flag PENDINGMSG_WAITDEFPROCESS + return 2; + } + + // Implement the IOleMessageFilter interface + [DllImport("ole32.dll")] + private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); + } + + [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IOleMessageFilter + { + [PreserveSig] + int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); + + [PreserveSig] + int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); + + [PreserveSig] + int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); + } + + #endregion + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index fb2beb6995..679d5bb444 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -12,6 +12,11 @@ namespace GodotTools.ProjectEditor private const string CoreApiProjectName = "GodotSharp"; private const string EditorApiProjectName = "GodotSharpEditor"; + public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; + public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}"; + + public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}"; + public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems) { string path = Path.Combine(dir, name + ".csproj"); @@ -19,6 +24,7 @@ namespace GodotTools.ProjectEditor ProjectPropertyGroupElement mainGroup; var root = CreateLibraryProject(name, "Debug", out mainGroup); + mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids); mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 069a1edaa3..8774b4ee31 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -165,6 +165,21 @@ namespace GodotTools.ProjectEditor return result.ToArray(); } + public static void EnsureHasProjectTypeGuids(MSBuildProject project) + { + var root = project.Root; + + bool found = root.PropertyGroups.Any(pg => + string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids")); + + if (found) + return; + + root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids); + + project.HasUnsavedChanges = true; + } + /// Simple function to make sure the Api assembly references are configured correctly public static void FixApiHintPath(MSBuildProject project) { diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index f6147eb5bb..ba5379e562 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "G EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", "GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio", "GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj", "{EAFFF236-FA96-4A4D-BD23-0E51EF988277}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,5 +39,9 @@ Global {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index c874025be0..403e25781d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using GodotTools.Ides; using GodotTools.Ides.Rider; using GodotTools.Internals; @@ -238,7 +239,31 @@ namespace GodotTools // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor. return Error.Unavailable; case ExternalEditorId.VisualStudio: - throw new NotSupportedException(); + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + var args = new List<string> + { + GodotSharpDirs.ProjectSlnPath, + line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath + }; + + string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe"); + + try + { + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); + + OS.RunProcess(command, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); + } + + break; + } case ExternalEditorId.VisualStudioForMac: goto case ExternalEditorId.MonoDevelop; case ExternalEditorId.Rider: @@ -458,6 +483,9 @@ namespace GodotTools // Apply the other fixes only after configurations have been migrated + // Make sure the existing project has the ProjectTypeGuids property (for VisualStudio) + ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject); + // Make sure the existing project has Api assembly references configured correctly ProjectUtils.FixApiHintPath(msbuildProject); @@ -501,7 +529,8 @@ namespace GodotTools if (OS.IsWindows) { - settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + + settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" + + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index ba527ca3b5..3f14629b11 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -33,5 +33,7 @@ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" /> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> + <!-- Include it if this is an SCons build targeting Windows, or if it's not an SCons build but we're on Windows --> + <ProjectReference Include="..\GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) " /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 32f264d100..98e8d13be0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -307,6 +307,11 @@ namespace GodotTools.Ides var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); return await HandleDebugPlay(request); }, + [StopPlayRequest.Id] = async (peer, content) => + { + var request = JsonConvert.DeserializeObject<StopPlayRequest>(content.Body); + return await HandleStopPlay(request); + }, [ReloadScriptsRequest.Id] = async (peer, content) => { _ = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body); @@ -343,6 +348,12 @@ namespace GodotTools.Ides return Task.FromResult<Response>(new DebugPlayResponse()); } + private static Task<Response> HandleStopPlay(StopPlayRequest request) + { + DispatchToMainThread(Internal.EditorRunStop); + return Task.FromResult<Response>(new StopPlayResponse()); + } + private static Task<Response> HandleReloadScripts() { DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index 1b4fe68582..e0cf916a01 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -75,7 +75,6 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) if (res == ERROR_MORE_DATA) { // dwBufferSize now contains the actual size - Vector<WCHAR> buffer; buffer.resize(dwBufferSize); res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 29eabfdde8..9c1b8f2949 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -343,55 +343,21 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { return ret; } -uint64_t OS_Windows::get_unix_time() const { - FILETIME ft; - SYSTEMTIME st; - GetSystemTime(&st); - SystemTimeToFileTime(&st, &ft); - - SYSTEMTIME ep; - ep.wYear = 1970; - ep.wMonth = 1; - ep.wDayOfWeek = 4; - ep.wDay = 1; - ep.wHour = 0; - ep.wMinute = 0; - ep.wSecond = 0; - ep.wMilliseconds = 0; - FILETIME fep; - SystemTimeToFileTime(&ep, &fep); - - // Type punning through unions (rather than pointer cast) as per: - // https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime#remarks - ULARGE_INTEGER ft_punning; - ft_punning.LowPart = ft.dwLowDateTime; - ft_punning.HighPart = ft.dwHighDateTime; - - ULARGE_INTEGER fep_punning; - fep_punning.LowPart = fep.dwLowDateTime; - fep_punning.HighPart = fep.dwHighDateTime; - - return (ft_punning.QuadPart - fep_punning.QuadPart) / 10000000; -}; - -uint64_t OS_Windows::get_system_time_secs() const { - return get_system_time_msecs() / 1000; -} - -uint64_t OS_Windows::get_system_time_msecs() const { - const uint64_t WINDOWS_TICK = 10000; - const uint64_t MSEC_TO_UNIX_EPOCH = 11644473600000LL; +double OS_Windows::get_unix_time() const { + // 1 Windows tick is 100ns + const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000; + const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL; SYSTEMTIME st; GetSystemTime(&st); FILETIME ft; SystemTimeToFileTime(&st, &ft); - uint64_t ret; - ret = ft.dwHighDateTime; - ret <<= 32; - ret |= ft.dwLowDateTime; + uint64_t ticks_time; + ticks_time = ft.dwHighDateTime; + ticks_time <<= 32; + ticks_time |= ft.dwLowDateTime; - return (uint64_t)(ret / WINDOWS_TICK - MSEC_TO_UNIX_EPOCH); + return (double)(ticks_time - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND; } void OS_Windows::delay_usec(uint32_t p_usec) const { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 11e3533bfd..910a83539a 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -129,9 +129,7 @@ public: virtual Date get_date(bool utc) const; virtual Time get_time(bool utc) const; virtual TimeZoneInfo get_time_zone_info() const; - virtual uint64_t get_unix_time() const; - virtual uint64_t get_system_time_secs() const; - virtual uint64_t get_system_time_msecs() const; + virtual double get_unix_time() const; virtual Error set_cwd(const String &p_cwd); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 8f69676da4..68e99445d8 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -343,7 +343,9 @@ void Camera2D::_notification(int p_what) { void Camera2D::set_offset(const Vector2 &p_offset) { offset = p_offset; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } Vector2 Camera2D::get_offset() const { @@ -361,7 +363,9 @@ Camera2D::AnchorMode Camera2D::get_anchor_mode() const { void Camera2D::set_rotating(bool p_rotating) { rotating = p_rotating; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } bool Camera2D::is_rotating() const { @@ -522,7 +526,9 @@ bool Camera2D::is_v_drag_enabled() const { void Camera2D::set_v_offset(float p_offset) { v_ofs = p_offset; v_offset_changed = true; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } float Camera2D::get_v_offset() const { @@ -532,7 +538,9 @@ float Camera2D::get_v_offset() const { void Camera2D::set_h_offset(float p_offset) { h_ofs = p_offset; h_offset_changed = true; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } float Camera2D::get_h_offset() const { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index a09424fa17..6723ca04b9 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -67,6 +67,8 @@ SkinReference::~SkinReference() { RS::get_singleton()->free(skeleton); } +/////////////////////////////////////// + bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; @@ -853,6 +855,15 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { return skin_ref; } +// helper functions +Transform Skeleton3D::bone_transform_to_world_transform(Transform p_bone_transform) { + return get_global_transform() * p_bone_transform; +} + +Transform Skeleton3D::world_transform_to_bone_transform(Transform p_world_transform) { + return get_global_transform().affine_inverse() * p_world_transform; +} + void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_process_orders"), &Skeleton3D::get_bone_process_orders); ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton3D::add_bone); @@ -892,6 +903,9 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_custom_pose", "bone_idx"), &Skeleton3D::get_bone_custom_pose); ClassDB::bind_method(D_METHOD("set_bone_custom_pose", "bone_idx", "custom_pose"), &Skeleton3D::set_bone_custom_pose); + ClassDB::bind_method(D_METHOD("bone_transform_to_world_transform", "bone_transform"), &Skeleton3D::bone_transform_to_world_transform); + ClassDB::bind_method(D_METHOD("world_transform_to_bone_transform", "world_transform"), &Skeleton3D::world_transform_to_bone_transform); + #ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("set_animate_physical_bones"), &Skeleton3D::set_animate_physical_bones); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 66706a9450..a21891a32e 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -71,10 +71,6 @@ class Skeleton3D : public Node3D { private: friend class SkinReference; - Set<SkinReference *> skin_bindings; - - void _skin_changed(); - struct Bone { String name; @@ -116,6 +112,10 @@ private: } }; + Set<SkinReference *> skin_bindings; + + void _skin_changed(); + bool animate_physical_bones; Vector<Bone> bones; Vector<int> process_order; @@ -200,6 +200,10 @@ public: Ref<SkinReference> register_skin(const Ref<Skin> &p_skin); + // Helper functions + Transform bone_transform_to_world_transform(Transform p_transform); + Transform world_transform_to_bone_transform(Transform p_transform); + #ifndef _3D_DISABLED // Physical bone API @@ -213,7 +217,7 @@ public: PhysicalBone3D *get_physical_bone_parent(int p_bone); private: - /// This is a slow API os it's cached + /// This is a slow API, so it's cached PhysicalBone3D *_get_physical_bone_parent(int p_bone); void _rebuild_physical_bones_cache(); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 524ff346d1..721f573f39 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -1222,8 +1222,8 @@ void CanvasItem::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask"); ADD_GROUP("Texture", "texture_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "ParentNode,Nearest,Linear,MipmapNearest,MipmapLinear,MipmapNearestAniso,MipmapLinearAniso"), "set_texture_filter", "get_texture_filter"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_repeat", PROPERTY_HINT_ENUM, "ParentNode,Disabled,Enabled,Mirror"), "set_texture_repeat", "get_texture_repeat"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Inherit,Nearest,Linear,MipmapNearest,MipmapLinear,MipmapNearestAniso,MipmapLinearAniso"), "set_texture_filter", "get_texture_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_repeat", PROPERTY_HINT_ENUM, "Inherit,Disabled,Enabled,Mirror"), "set_texture_repeat", "get_texture_repeat"); ADD_GROUP("Material", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial"), "set_material", "get_material"); |