diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml index c64ec21614ed..c2e55dece264 100644 --- a/.github/workflows/javascript_builds.yml +++ b/.github/workflows/javascript_builds.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Emscripten latest - uses: mymindstorm/setup-emsdk@v11 + uses: mymindstorm/setup-emsdk@v12 with: version: ${{env.EM_VERSION}} actions-cache-folder: ${{env.EM_CACHE_FOLDER}} diff --git a/core/math/basis.cpp b/core/math/basis.cpp index a33e030b841e..48fd33ed4333 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -925,6 +925,7 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const { // as we have reached here there are no singularities so we can handle normally real_t s = Math::sqrt((elements[1][2] - elements[2][1]) * (elements[1][2] - elements[2][1]) + (elements[2][0] - elements[0][2]) * (elements[2][0] - elements[0][2]) + (elements[0][1] - elements[1][0]) * (elements[0][1] - elements[1][0])); // s=|axis||sin(angle)|, used to normalise + // acos does clamping. angle = Math::acos((elements[0][0] + elements[1][1] + elements[2][2] - 1) / 2); if (angle < 0) { s = -s; diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 482986f27fc4..557ed0564a98 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -74,11 +74,13 @@ class Math { static _ALWAYS_INLINE_ double tanh(double p_x) { return ::tanh(p_x); } static _ALWAYS_INLINE_ float tanh(float p_x) { return ::tanhf(p_x); } - static _ALWAYS_INLINE_ double asin(double p_x) { return ::asin(p_x); } - static _ALWAYS_INLINE_ float asin(float p_x) { return ::asinf(p_x); } + // Always does clamping so always safe to use. + static _ALWAYS_INLINE_ double asin(double p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asin(p_x)); } + static _ALWAYS_INLINE_ float asin(float p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asinf(p_x)); } - static _ALWAYS_INLINE_ double acos(double p_x) { return ::acos(p_x); } - static _ALWAYS_INLINE_ float acos(float p_x) { return ::acosf(p_x); } + // Always does clamping so always safe to use. + static _ALWAYS_INLINE_ double acos(double p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acos(p_x)); } + static _ALWAYS_INLINE_ float acos(float p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acosf(p_x)); } static _ALWAYS_INLINE_ double atan(double p_x) { return ::atan(p_x); } static _ALWAYS_INLINE_ float atan(float p_x) { return ::atanf(p_x); } diff --git a/core/math/quat.cpp b/core/math/quat.cpp index 37e5369faac2..600c60e1f1c6 100644 --- a/core/math/quat.cpp +++ b/core/math/quat.cpp @@ -35,7 +35,9 @@ real_t Quat::angle_to(const Quat &p_to) const { real_t d = dot(p_to); - return Math::acos(CLAMP(d * d * 2 - 1, -1, 1)); + + // acos does clamping. + return Math::acos(d * d * 2 - 1); } // set_euler_xyz expects a vector containing the Euler angles in the format diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index 4f70a478779c..806c35e88632 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -44,7 +44,7 @@ Transform2D Transform2D::inverse() const { } void Transform2D::affine_invert() { - real_t det = basis_determinant(); + real_t det = determinant(); #ifdef MATH_CHECKS ERR_FAIL_COND(det == 0); #endif @@ -93,7 +93,7 @@ Transform2D::Transform2D(real_t p_rot, const Vector2 &p_pos) { } Size2 Transform2D::get_scale() const { - real_t det_sign = SGN(basis_determinant()); + real_t det_sign = SGN(determinant()); return Size2(elements[0].length(), det_sign * elements[1].length()); } @@ -217,7 +217,7 @@ Transform2D Transform2D::rotated(real_t p_angle) const { return copy; } -real_t Transform2D::basis_determinant() const { +real_t Transform2D::determinant() const { return elements[0].x * elements[1].y - elements[0].y * elements[1].x; } diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index 6227a5d5d2a5..e9b36eafca7a 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -79,7 +79,7 @@ struct _NO_DISCARD_CLASS_ Transform2D { void translate(real_t p_tx, real_t p_ty); void translate(const Vector2 &p_translation); - real_t basis_determinant() const; + real_t determinant() const; Size2 get_scale() const; void set_scale(const Size2 &p_scale); diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 8cd97b032733..b615d9272e69 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -39,7 +39,7 @@ void TransformInterpolator::interpolate_transform2D(const Transform2D &p_prev, c // Special case for physics interpolation, if flipping, don't interpolate basis. // If the determinant polarity changes, the handedness of the coordinate system changes. - if (_sign(p_prev.basis_determinant()) != _sign(p_curr.basis_determinant())) { + if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) { r_result.elements[0] = p_curr.elements[0]; r_result.elements[1] = p_curr.elements[1]; r_result.set_origin(Vector2::linear_interpolate(p1, p2, p_fraction)); diff --git a/core/message_queue.cpp b/core/message_queue.cpp index 6e1bf25b8aea..ab0c102f59c0 100644 --- a/core/message_queue.cpp +++ b/core/message_queue.cpp @@ -251,8 +251,7 @@ void MessageQueue::statistics() { } int MessageQueue::get_max_buffer_usage() const { - // Note this may be better read_buffer, or a combination, depending when this is read. - return buffers[write_buffer].data.size(); + return _buffer_size_monitor.max_size_overall; } void MessageQueue::_call_function(Object *p_target, const StringName &p_func, const Variant *p_args, int p_argcount, bool p_show_error) { @@ -392,6 +391,7 @@ void MessageQueue::flush() { _THREAD_SAFE_LOCK_ // keep track of the maximum used size, so we can downsize buffers when appropriate _buffer_size_monitor.max_size = MAX(buffer_data_size, _buffer_size_monitor.max_size); + _buffer_size_monitor.max_size_overall = MAX(buffer_data_size, _buffer_size_monitor.max_size_overall); // flip buffers, this is the only part that requires a lock SWAP(read_buffer, write_buffer); diff --git a/core/message_queue.h b/core/message_queue.h index 4d6307534d30..c261238f689e 100644 --- a/core/message_queue.h +++ b/core/message_queue.h @@ -70,6 +70,9 @@ class MessageQueue { struct BufferSizeMonitor { uint32_t max_size = 0; uint32_t flush_count = 0; + + // Only used for performance statistics. + uint32_t max_size_overall = 0; } _buffer_size_monitor; void _call_function(Object *p_target, const StringName &p_func, const Variant *p_args, int p_argcount, bool p_show_error); @@ -97,6 +100,7 @@ class MessageQueue { bool is_flushing() const; int get_max_buffer_usage() const; + int get_current_buffer_usage() const; MessageQueue(); ~MessageQueue(); diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 59dd56aeb979..918553af6591 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -886,6 +886,7 @@ struct _VariantCall { VCALL_PTR1R(Transform2D, translated); VCALL_PTR2R(Transform2D, interpolate_with); VCALL_PTR1R(Transform2D, is_equal_approx); + VCALL_PTR0R(Transform2D, determinant); static void _call_Transform2D_xform(Variant &r_ret, Variant &p_self, const Variant **p_args) { switch (p_args[0]->type) { @@ -2151,6 +2152,7 @@ void register_variant_methods() { ADDFUNC1R(TRANSFORM2D, TRANSFORM2D, Transform2D, translated, VECTOR2, "offset", varray()); ADDFUNC1R(TRANSFORM2D, NIL, Transform2D, xform, NIL, "v", varray()); ADDFUNC1R(TRANSFORM2D, NIL, Transform2D, xform_inv, NIL, "v", varray()); + ADDFUNC0R(TRANSFORM2D, REAL, Transform2D, determinant, varray()); ADDFUNC1R(TRANSFORM2D, VECTOR2, Transform2D, basis_xform, VECTOR2, "v", varray()); ADDFUNC1R(TRANSFORM2D, VECTOR2, Transform2D, basis_xform_inv, VECTOR2, "v", varray()); ADDFUNC2R(TRANSFORM2D, TRANSFORM2D, Transform2D, interpolate_with, TRANSFORM2D, "transform", REAL, "weight", varray()); diff --git a/doc/classes/CPUParticles.xml b/doc/classes/CPUParticles.xml index 0a6b41b1a9c3..376cc5102d58 100644 --- a/doc/classes/CPUParticles.xml +++ b/doc/classes/CPUParticles.xml @@ -127,13 +127,16 @@ Animation speed randomness ratio. - Each particle's initial color. To have particle display color in a [SpatialMaterial] make sure to set [member SpatialMaterial.vertex_color_use_as_albedo] to [code]true[/code]. + Each particle's initial color. + [b]Note:[/b] [member color] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color] will have no visible effect. Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]). + [b]Note:[/b] [member color_initial_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_initial_ramp] will have no visible effect. Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]). + [b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect. The rate at which particles lose velocity. @@ -155,6 +158,7 @@ Sets the [Color]s to modulate particles by when using [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS]. + [b]Note:[/b] [member emission_colors] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member emission_colors] will have no visible effect. Sets the direction the particles will be emitted in when using [constant EMISSION_SHAPE_DIRECTED_POINTS]. diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index ebe96ddb7b62..12a975b75c01 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -510,10 +510,6 @@ The custom theme resource to use for the editor. Must be a Godot theme resource in [code].tres[/code] or [code].res[/code] format. - - If [code]true[/code], increases the touch area for the UI elements to improve usability on touchscreen devices. - [b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices. - If [code]true[/code], makes the background of selected tabs more contrasted in the editor theme (brighter on dark themes, darker on light themes). @@ -532,6 +528,22 @@ If [code]true[/code], use colored header backgrounds for individual [GraphNode]s in the visual script and visual shader editors. This can improve usability when frequently using these editors at low zoom levels. + + If [code]true[/code], long press on touchscreen is treated as right click. + [b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices. + + + If [code]true[/code], enable two finger pan and scale gestures on touchscreen devices. + [b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices. + + + If [code]true[/code], increases the scrollbar touch area to improve usability on touchscreen devices. + [b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices. + + + Specify the multiplier to apply to the scale for the editor gizmo handles to improve usability on touchscreen devices. + [b]Note:[/b] Defaults to [code]1[/code] on non-touchscreen devices. + The address to listen to when starting the remote debugger. This can be set to [code]0.0.0.0[/code] to allow external clients to connect to the remote debugger (instead of restricting the remote debugger to connections from [code]localhost[/code]). diff --git a/doc/classes/ParticlesMaterial.xml b/doc/classes/ParticlesMaterial.xml index 7c9f4807bbfe..c9f1ec8941b1 100644 --- a/doc/classes/ParticlesMaterial.xml +++ b/doc/classes/ParticlesMaterial.xml @@ -112,13 +112,16 @@ Animation speed randomness ratio. - Each particle's initial color. If the [Particles2D]'s [code]texture[/code] is defined, it will be multiplied by this color. To have particle display color in a [SpatialMaterial] make sure to set [member SpatialMaterial.vertex_color_use_as_albedo] to [code]true[/code]. + Each particle's initial color. If the [Particles2D]'s or [Particles]'s [code]texture[/code] is defined, it will be multiplied by this color. + [b]Note:[/b] [member color] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color] will have no visible effect. Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]). + [b]Note:[/b] [member color_initial_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_initial_ramp] will have no visible effect. Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]). + [b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect. The rate at which particles lose velocity. @@ -137,6 +140,7 @@ Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture]. + [b]Note:[/b] [member emission_color_texture] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member emission_color_texture] will have no visible effect. Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index e2aaba5bd7e5..3912ae8bd0c0 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1584,9 +1584,10 @@ If set to [code]Asynchronous[/code] and available on the target device, asynchronous compilation of shaders is enabled (in contrast to [code]Asynchronous[/code]). That means that when a shader is first used under some new rendering situation, the game won't stall while such shader is being compiled. Instead, a fallback will be used and the real shader will be compiled in the background. Once the actual shader is compiled, it will be used the next times it's used to draw a frame. - Depending on the async mode configured for a given material/shader, the fallback will be an "ubershader" (the default) or just skip rendering any item it is applied to. + Depending on the [member SpatialMaterial.async_mode] mode configured for a given material, the fallback will be an "ubershader" (the default) or just skip rendering any item it is applied to. In custom [ShaderMaterial]s, the async mode is set using [code]render_mode async_visible;[/code] (default) or [code]render_mode async_hidden;[/code] at the top of the shader. An ubershader is a very complex shader, slow but suited to any rendering situation, that the engine generates internally so it can be used from the beginning while the traditional conditioned, optimized version of it is being compiled. To reduce loading times after the project has been launched at least once, you can use [code]Asynchronous + Cache[/code]. This also causes the ubershaders to be cached into storage so they can be ready faster next time they are used (provided the platform provides support for it). + [b]Note:[/b] Asynchronous compilation requires driver support for the [code]GL_ARB_get_program_binary[/code] OpenGL extension. This extension is supported by all hardware that supports OpenGL 4.1 or higher as well as most hardware that supports OpenGL 3.3 or higher. [b]Note:[/b] Asynchronous compilation is currently only supported for spatial (3D) and particle materials/shaders. CanvasItem (2D) shaders will not use asynchronous compilation even if this setting is set to [code]Asynchronous[/code] or [code]Asynchronous + Cache[/code]. diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index f7f1cb394a25..043f6d1b2f54 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -60,6 +60,13 @@ This method does not account for translation (the origin vector). + + + + Returns the determinant of the basis matrix. If the basis is uniformly scaled, then its determinant equals the square of the scale factor. + A negative determinant means the basis was flipped, so one part of the scale is negative. A zero determinant means the basis isn't invertible, and is usually considered invalid. + + diff --git a/drivers/gles2/rasterizer_canvas_base_gles2.cpp b/drivers/gles2/rasterizer_canvas_base_gles2.cpp index bd592809dbce..0040a05d71bb 100644 --- a/drivers/gles2/rasterizer_canvas_base_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_base_gles2.cpp @@ -837,7 +837,7 @@ void RasterizerCanvasBaseGLES2::canvas_light_shadow_buffer_update(RID p_buffer, VS::CanvasOccluderPolygonCullMode transformed_cull_cache = instance->cull_cache; if (transformed_cull_cache != VS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED && - (p_light_xform.basis_determinant() * instance->xform_cache.basis_determinant()) < 0) { + (p_light_xform.determinant() * instance->xform_cache.determinant()) < 0) { transformed_cull_cache = (transformed_cull_cache == VS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE) ? VS::CANVAS_OCCLUDER_POLYGON_CULL_COUNTER_CLOCKWISE : VS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE; diff --git a/drivers/gles3/rasterizer_canvas_base_gles3.cpp b/drivers/gles3/rasterizer_canvas_base_gles3.cpp index d50c655ba85e..832e6739eaa4 100644 --- a/drivers/gles3/rasterizer_canvas_base_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_base_gles3.cpp @@ -895,7 +895,7 @@ void RasterizerCanvasBaseGLES3::canvas_light_shadow_buffer_update(RID p_buffer, VS::CanvasOccluderPolygonCullMode transformed_cull_cache = instance->cull_cache; if (transformed_cull_cache != VS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED && - (p_light_xform.basis_determinant() * instance->xform_cache.basis_determinant()) < 0) { + (p_light_xform.determinant() * instance->xform_cache.determinant()) < 0) { transformed_cull_cache = (transformed_cull_cache == VS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE) ? VS::CANVAS_OCCLUDER_POLYGON_CULL_COUNTER_CLOCKWISE : VS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 3b302baf4b2a..059359991b86 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -348,7 +348,6 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // Theme _initial_set("interface/theme/preset", "Default"); hints["interface/theme/preset"] = PropertyInfo(Variant::STRING, "interface/theme/preset", PROPERTY_HINT_ENUM, "Default,Alien,Arc,Godot 2,Grey,Light,Solarized (Dark),Solarized (Light),Custom", PROPERTY_USAGE_DEFAULT); - _initial_set("interface/theme/enable_touchscreen_touch_area", OS::get_singleton()->has_touchscreen_ui_hint()); _initial_set("interface/theme/icon_and_font_color", 0); hints["interface/theme/icon_and_font_color"] = PropertyInfo(Variant::INT, "interface/theme/icon_and_font_color", PROPERTY_HINT_ENUM, "Auto,Dark,Light", PROPERTY_USAGE_DEFAULT); _initial_set("interface/theme/base_color", Color(0.2, 0.23, 0.31)); @@ -368,6 +367,16 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("interface/theme/custom_theme", ""); hints["interface/theme/custom_theme"] = PropertyInfo(Variant::STRING, "interface/theme/custom_theme", PROPERTY_HINT_GLOBAL_FILE, "*.res,*.tres,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + // Touchscreen + bool has_touchscreen_ui = OS::get_singleton()->has_touchscreen_ui_hint(); + _initial_set("interface/touchscreen/increase_scrollbar_touch_area", has_touchscreen_ui); + _initial_set("interface/touchscreen/enable_long_press_as_right_click", has_touchscreen_ui); + set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true); + _initial_set("interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui); + set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true); + _initial_set("interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1); + hints["interface/touchscreen/scale_gizmo_handles"] = PropertyInfo(Variant::REAL, "interface/touchscreen/scale_gizmo_handles", PROPERTY_HINT_RANGE, "1,5,1"); + // Scene tabs _initial_set("interface/scene_tabs/show_thumbnail_on_hover", true); _initial_set("interface/scene_tabs/resize_if_many_tabs", true); @@ -613,7 +622,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("editors/2d/pan_speed", 20); // Polygon editor - _initial_set("editors/poly_editor/point_grab_radius", 8); + _initial_set("editors/poly_editor/point_grab_radius", has_touchscreen_ui ? 32 : 8); _initial_set("editors/poly_editor/show_previous_outline", true); // Animation diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index b1206f96cef3..7f6cfcfa9e30 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -134,6 +134,27 @@ static Ref editor_generate_icon(int p_index, bool p_convert_color, #define ADD_CONVERT_COLOR(dictionary, old_color, new_color) dictionary[Color::html(old_color)] = Color::html(new_color) #endif +float get_gizmo_handle_scale(const String &gizmo_handle_name = "") { + const float scale_gizmo_handles_for_touch = EDITOR_GET("interface/touchscreen/scale_gizmo_handles"); + if (scale_gizmo_handles_for_touch > 1.0f) { + // The names of the icons that require custom scaling. + static Set gizmo_to_scale; + if (gizmo_to_scale.empty()) { + gizmo_to_scale.insert("EditorHandle"); + gizmo_to_scale.insert("EditorHandleAdd"); + gizmo_to_scale.insert("EditorCurveHandle"); + gizmo_to_scale.insert("EditorPathSharpHandle"); + gizmo_to_scale.insert("EditorPathSmoothHandle"); + } + + if (gizmo_to_scale.has(gizmo_handle_name)) { + return EDSCALE * scale_gizmo_handles_for_touch; + } + } + + return EDSCALE; +} + void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme = true, int p_thumb_size = 32, bool p_only_thumbs = false) { OS::get_singleton()->benchmark_begin_measure("editor_register_and_generate_icons_" + String((p_only_thumbs ? "with_only_thumbs" : "all"))); #ifdef MODULE_SVG_ENABLED @@ -258,10 +279,11 @@ void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme = // Generate icons. if (!p_only_thumbs) { for (int i = 0; i < editor_icons_count; i++) { - const int is_exception = exceptions.has(editor_icons_names[i]); - const Ref icon = editor_generate_icon(i, !is_exception); + const String &editor_icon_name = editor_icons_names[i]; + const int is_exception = exceptions.has(editor_icon_name); + const Ref icon = editor_generate_icon(i, !is_exception, get_gizmo_handle_scale(editor_icon_name)); - p_theme->set_icon(editor_icons_names[i], "EditorIcons", icon); + p_theme->set_icon(editor_icon_name, "EditorIcons", icon); } } @@ -309,7 +331,8 @@ Ref create_editor_theme(const Ref p_theme) { String preset = EDITOR_GET("interface/theme/preset"); - bool enable_touchscreen_touch_area = EDITOR_GET("interface/theme/enable_touchscreen_touch_area"); + bool increase_scrollbar_touch_area = EDITOR_GET("interface/touchscreen/increase_scrollbar_touch_area"); + const float gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles"); bool highlight_tabs = EDITOR_GET("interface/theme/highlight_tabs"); int border_size = EDITOR_GET("interface/theme/border_size"); @@ -450,11 +473,18 @@ Ref create_editor_theme(const Ref p_theme) { theme->set_constant("scale", "Editor", EDSCALE); theme->set_constant("thumb_size", "Editor", thumb_size); theme->set_constant("dark_theme", "Editor", dark_theme); + theme->set_constant("gizmo_handle_scale", "Editor", gizmo_handle_scale); //Register icons + font + bool keep_old_icons = false; + if (p_theme != nullptr) { + keep_old_icons = fabs(p_theme->get_constant("scale", "Editor") - EDSCALE) < 0.00001 && + fabs(p_theme->get_constant("gizmo_handle_scale", "Editor") - gizmo_handle_scale) < 0.00001 && + (bool)p_theme->get_constant("dark_theme", "Editor") == dark_theme; + } // the resolution and the icon color (dark_theme bool) has not changed, so we do not regenerate the icons - if (p_theme != nullptr && fabs(p_theme->get_constant("scale", "Editor") - EDSCALE) < 0.00001 && (bool)p_theme->get_constant("dark_theme", "Editor") == dark_theme) { + if (keep_old_icons) { // register already generated icons for (int i = 0; i < editor_icons_count; i++) { theme->set_icon(editor_icons_names[i], "EditorIcons", p_theme->get_icon(editor_icons_names[i], "EditorIcons")); @@ -1054,7 +1084,7 @@ Ref create_editor_theme(const Ref p_theme) { // HScrollBar Ref empty_icon = memnew(ImageTexture); - if (enable_touchscreen_touch_area) { + if (increase_scrollbar_touch_area) { theme->set_stylebox("scroll", "HScrollBar", make_line_stylebox(separator_color, 50)); } else { theme->set_stylebox("scroll", "HScrollBar", make_stylebox(theme->get_icon("GuiScrollBg", "EditorIcons"), 5, 5, 5, 5, 0, 0, 0, 0)); @@ -1072,7 +1102,7 @@ Ref create_editor_theme(const Ref p_theme) { theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar - if (enable_touchscreen_touch_area) { + if (increase_scrollbar_touch_area) { theme->set_stylebox("scroll", "VScrollBar", make_line_stylebox(separator_color, 50, 1, 1, true)); } else { theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon("GuiScrollBg", "EditorIcons"), 5, 5, 5, 5, 0, 0, 0, 0)); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 85ab7821a4de..4d2760c694e0 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1623,7 +1623,7 @@ void CanvasItemEditor::_solve_IK(Node2D *leaf_node, Point2 target_position) { Point2 current = (joints_list[node_id - 1]->get_global_position() - joints_list[node_id]->get_global_position()).normalized(); Point2 target = (joints_pos[node_id - 1] - joints_list[node_id]->get_global_position()).normalized(); float rot = current.angle_to(target); - if (joints_list[node_id]->get_global_transform().basis_determinant() < 0) { + if (joints_list[node_id]->get_global_transform().determinant() < 0) { rot = -rot; } joints_list[node_id]->rotate(rot); diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 95a3b1c782d5..824cd6ef7841 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -343,7 +343,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref &p_e if (mb->get_button_index() == BUTTON_LEFT) { if (mb->is_pressed()) { for (int i = 0; i < handles.size(); i++) { - if (xform.xform(handles[i]).distance_to(gpoint) < 8) { + if (xform.xform(handles[i]).distance_to(gpoint) < grab_threshold) { edit_handle = i; break; @@ -557,6 +557,10 @@ void CollisionShape2DEditor::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { get_tree()->disconnect("node_removed", this, "_node_removed"); } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + } break; } } @@ -594,6 +598,7 @@ CollisionShape2DEditor::CollisionShape2DEditor(EditorNode *p_editor) { edit_handle = -1; pressed = false; + grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); } void CollisionShape2DEditorPlugin::edit(Object *p_obj) { diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index d952620d2a4c..978216981b5c 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -75,6 +75,7 @@ class CollisionShape2DEditor : public Control { int shape_type; int edit_handle; bool pressed; + real_t grab_threshold = 8; Variant original; Transform2D original_transform; Point2 last_point; diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index cfd90f88f673..7ac1a64efa50 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -44,6 +44,7 @@ CurveEditor::CurveEditor() { _tangents_length = 40; _dragging = false; _has_undo_data = false; + _gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles"); set_focus_mode(FOCUS_ALL); set_clip_contents(true); @@ -96,8 +97,13 @@ Size2 CurveEditor::get_minimum_size() const { } void CurveEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - _draw(); + switch (p_what) { + case NOTIFICATION_DRAW: { + _draw(); + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _gizmo_handle_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles"); + } break; } } @@ -391,7 +397,7 @@ int CurveEditor::get_point_at(Vector2 pos) const { } const Curve &curve = **_curve_ref; - const float true_hover_radius = Math::round(_hover_radius * EDSCALE); + const float true_hover_radius = Math::round(_hover_radius * _gizmo_handle_scale * EDSCALE); const float r = true_hover_radius * true_hover_radius; for (int i = 0; i < curve.get_point_count(); ++i) { @@ -411,14 +417,14 @@ CurveEditor::TangentIndex CurveEditor::get_tangent_at(Vector2 pos) const { if (_selected_point != 0) { Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_LEFT); - if (control_pos.distance_to(pos) < _hover_radius) { + if (control_pos.distance_to(pos) < _hover_radius * _gizmo_handle_scale) { return TANGENT_LEFT; } } if (_selected_point != _curve_ref->get_point_count() - 1) { Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_RIGHT); - if (control_pos.distance_to(pos) < _hover_radius) { + if (control_pos.distance_to(pos) < _hover_radius * _gizmo_handle_scale) { return TANGENT_RIGHT; } } @@ -555,7 +561,7 @@ Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const { Vector2 point_pos = get_view_pos(_curve_ref->get_point_position(i)); Vector2 control_pos = get_view_pos(_curve_ref->get_point_position(i) + dir); - return point_pos + Math::round(_tangents_length * EDSCALE) * (control_pos - point_pos).normalized(); + return point_pos + Math::round(_tangents_length * _gizmo_handle_scale * EDSCALE) * (control_pos - point_pos).normalized(); } Vector2 CurveEditor::get_view_pos(Vector2 world_pos) const { @@ -699,13 +705,13 @@ void CurveEditor::_draw() { if (i != 0) { Vector2 control_pos = get_tangent_view_pos(i, TANGENT_LEFT); draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE), true); - draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * _gizmo_handle_scale * EDSCALE)), tangent_color); } if (i != curve.get_point_count() - 1) { Vector2 control_pos = get_tangent_view_pos(i, TANGENT_RIGHT); draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE), true); - draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * _gizmo_handle_scale * EDSCALE)), tangent_color); } } @@ -728,7 +734,7 @@ void CurveEditor::_draw() { for (int i = 0; i < curve.get_point_count(); ++i) { Vector2 pos = curve.get_point_position(i); - draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * EDSCALE)), i == _selected_point ? selected_point_color : point_color); + draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * _gizmo_handle_scale * EDSCALE)), i == _selected_point ? selected_point_color : point_color); // TODO Circles are prettier. Needs a fix! Or a texture //draw_circle(pos, 2, point_color); } @@ -738,7 +744,7 @@ void CurveEditor::_draw() { if (_hover_point != -1) { const Color hover_color = line_color; Vector2 pos = curve.get_point_position(_hover_point); - draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * EDSCALE)), hover_color, false, Math::round(EDSCALE)); + draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * _gizmo_handle_scale * EDSCALE)), hover_color, false, Math::round(EDSCALE)); } // Help text diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index 819e4d95df42..8ffe9b478bff 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -117,6 +117,7 @@ class CurveEditor : public Control { // Constant float _hover_radius; float _tangents_length; + float _gizmo_handle_scale = 1.0; }; class EditorInspectorPluginCurve : public EditorInspectorPlugin { diff --git a/main/input_default.cpp b/main/input_default.cpp index 126b5ef0a1d4..3a3ee3a09efa 100644 --- a/main/input_default.cpp +++ b/main/input_default.cpp @@ -36,6 +36,10 @@ #include "scene/resources/texture.h" #include "servers/visual_server.h" +#ifdef DEV_ENABLED +#include "core/os/thread.h" +#endif + void InputDefault::SpeedTrack::update(const Vector2 &p_delta_p) { uint64_t tick = OS::get_singleton()->get_ticks_usec(); uint32_t tdiff = tick - last_tick; @@ -311,6 +315,10 @@ Vector3 InputDefault::get_gyroscope() const { } void InputDefault::_parse_input_event_impl(const Ref &p_event, bool p_is_emulated) { + // This function does the final delivery of the input event to user land. + // Regardless where the event came from originally, this has to happen on the main thread. + DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id()); + // Notes on mouse-touch emulation: // - Emulated mouse events are parsed, that is, re-routed to this method, so they make the same effects // as true mouse events. The only difference is the situation is flagged as emulated so they are not @@ -354,7 +362,9 @@ void InputDefault::_parse_input_event_impl(const Ref &p_event, bool touch_event->set_pressed(mb->is_pressed()); touch_event->set_position(mb->get_position()); touch_event->set_double_tap(mb->is_doubleclick()); + _THREAD_SAFE_UNLOCK_ main_loop->input_event(touch_event); + _THREAD_SAFE_LOCK_ } } @@ -376,7 +386,9 @@ void InputDefault::_parse_input_event_impl(const Ref &p_event, bool drag_event->set_relative(relative); drag_event->set_speed(get_last_mouse_speed()); + _THREAD_SAFE_UNLOCK_ main_loop->input_event(drag_event); + _THREAD_SAFE_LOCK_ } } @@ -472,7 +484,9 @@ void InputDefault::_parse_input_event_impl(const Ref &p_event, bool if (ge.is_valid()) { if (main_loop) { + _THREAD_SAFE_UNLOCK_ main_loop->input_event(ge); + _THREAD_SAFE_LOCK_ } } @@ -495,7 +509,9 @@ void InputDefault::_parse_input_event_impl(const Ref &p_event, bool } if (main_loop) { + _THREAD_SAFE_UNLOCK_ main_loop->input_event(p_event); + _THREAD_SAFE_LOCK_ } } @@ -645,6 +661,7 @@ bool InputDefault::is_emulating_touch_from_mouse() const { // Calling this whenever the game window is focused helps unstucking the "touch mouse" // if the OS or its abstraction class hasn't properly reported that touch pointers raised void InputDefault::ensure_touch_mouse_raised() { + _THREAD_SAFE_METHOD_ if (mouse_from_touch_index != -1) { mouse_from_touch_index = -1; @@ -747,8 +764,15 @@ void InputDefault::flush_buffered_events() { _THREAD_SAFE_METHOD_ while (buffered_events.front()) { - _parse_input_event_impl(buffered_events.front()->get(), false); + // The final delivery of the input event involves releasing the lock. + // While the lock is released, another thread may lock it and add new events to the back. + // Therefore, we get each event and pop it while we still have the lock, + // to ensure the list is in a consistent state. + List>::Element *E = buffered_events.front(); + Ref e = E->get(); buffered_events.pop_front(); + + _parse_input_event_impl(e, false); } } diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp index d59b71173d38..6b4bfd3c2368 100644 --- a/modules/gdnative/gdnative/transform2d.cpp +++ b/modules/gdnative/gdnative/transform2d.cpp @@ -93,6 +93,11 @@ godot_vector2 GDAPI godot_transform2d_get_scale(const godot_transform2d *p_self) return dest; } +godot_real GDAPI godot_transform2d_determinant(const godot_transform2d *p_self) { + const Transform2D *self = (const Transform2D *)p_self; + return self->determinant(); +} + godot_transform2d GDAPI godot_transform2d_orthonormalized(const godot_transform2d *p_self) { godot_transform2d dest; const Transform2D *self = (const Transform2D *)p_self; diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 2e7e315940ae..c8827a3b6ef1 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -4431,6 +4431,13 @@ ["const godot_transform2d *", "p_self"] ] }, + { + "name": "godot_transform2d_determinant", + "return_type": "godot_real", + "arguments": [ + ["const godot_transform2d *", "p_self"] + ] + }, { "name": "godot_transform2d_orthonormalized", "return_type": "godot_transform2d", diff --git a/modules/gdnative/include/gdnative/transform2d.h b/modules/gdnative/include/gdnative/transform2d.h index cb02158e70a7..4b997fbad395 100644 --- a/modules/gdnative/include/gdnative/transform2d.h +++ b/modules/gdnative/include/gdnative/transform2d.h @@ -74,6 +74,8 @@ godot_vector2 GDAPI godot_transform2d_get_origin(const godot_transform2d *p_self godot_vector2 GDAPI godot_transform2d_get_scale(const godot_transform2d *p_self); +godot_real GDAPI godot_transform2d_determinant(const godot_transform2d *p_self); + godot_transform2d GDAPI godot_transform2d_orthonormalized(const godot_transform2d *p_self); godot_transform2d GDAPI godot_transform2d_rotated(const godot_transform2d *p_self, const godot_real p_phi); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index fd382f44258e..2d7aed1f8fd4 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -52,7 +52,7 @@ - Returns the arc cosine of [code]s[/code] in radians. Use to get the angle of cosine [code]s[/code]. [code]s[/code] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method acos] will return [constant NAN]. + Returns the arc cosine of [code]s[/code] in radians. Use to get the angle of cosine [code]s[/code]. [code]s[/code] will be clamped between [code]-1.0[/code] and [code]1.0[/code] (inclusive), in order to prevent [method acos] from returning [constant NAN]. [codeblock] # c is 0.523599 or 30 degrees if converted with rad2deg(s) c = acos(0.866025) @@ -63,7 +63,7 @@ - Returns the arc sine of [code]s[/code] in radians. Use to get the angle of sine [code]s[/code]. [code]s[/code] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method asin] will return [constant NAN]. + Returns the arc sine of [code]s[/code] in radians. Use to get the angle of sine [code]s[/code]. [code]s[/code] will be clamped between [code]-1.0[/code] and [code]1.0[/code] (inclusive), in order to prevent [method asin] from returning [constant NAN]. [codeblock] # s is 0.523599 or 30 degrees if converted with rad2deg(s) s = asin(0.5) diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 89c8d58b3cb2..6224bb090281 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2194,7 +2194,7 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p static const char *_keywords[] = { "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", - "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", + "breakpoint", "class", "class_name", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif", "else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "puppet", "slave", "remotesync", "mastersync", "puppetsync", diff --git a/modules/gltf/config.py b/modules/gltf/config.py index c98e12be4beb..1386bee1bfd9 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -13,11 +13,13 @@ def get_doc_classes(): "GLTFAnimation", "GLTFBufferView", "GLTFCamera", + "GLTFCollider", "GLTFDocument", "GLTFDocumentExtension", "GLTFLight", "GLTFMesh", "GLTFNode", + "GLTFPhysicsBody", "GLTFSkeleton", "GLTFSkin", "GLTFSpecGloss", diff --git a/modules/gltf/doc_classes/GLTFCollider.xml b/modules/gltf/doc_classes/GLTFCollider.xml new file mode 100644 index 000000000000..7cc47aeebd73 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFCollider.xml @@ -0,0 +1,53 @@ + + + + Represents a GLTF collider. + + + Represents a collider as defined by the [code]OMI_collider[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + + + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider + + + + + + Serializes this GLTFCollider instance into a [Dictionary]. + + + + + + + Converts this GLTFCollider instance into a Godot [CollisionShape] node. + + + + + + The [ArrayMesh] resource of the collider. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh). + + + The height of the collider, in meters. This is only used when the collider type is "capsule" or "cylinder". This value should not be negative, and for "capsule" it should be at least twice the radius. + + + If [code]true[/code], indicates that this collider is a trigger. For Godot, this means that the collider should be a child of an Area3D node. + This is the only variable not used in the [method to_node] method, it's intended to be used alongside when deciding where to add the generated node as a child. + + + The index of the collider's mesh in the GLTF file. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh). + + + The radius of the collider, in meters. This is only used when the collider type is "capsule", "cylinder", or "sphere". This value should not be negative. + + + The type of shape this collider represents. Valid values are "box", "capsule", "cylinder", "sphere", "hull", and "trimesh". + + + The size of the collider, in meters. This is only used when the collider type is "box", and it represents the "diameter" of the box. This value should not be negative. + + + + + diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 80bfba47aa7b..c23b90038e67 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -31,30 +31,43 @@ + If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. + The indices of the children nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. + How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated. + This property is unused and does nothing. + If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. + If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. + The index of the parent node in the [GLTFState]. If -1, this node is a root node. + The rotation of the GLTF node relative to its parent. + The scale of the GLTF node relative to its parent. + If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. + If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. + The position of the GLTF node relative to its parent. + The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml new file mode 100644 index 000000000000..1f4bbe375b20 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -0,0 +1,42 @@ + + + + Represents a GLTF physics body. + + + Represents a physics body as defined by the [code]OMI_physics_body[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + + + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body + + + + + + Serializes this GLTFPhysicsBody instance into a [Dictionary]. + + + + + + Converts this GLTFPhysicsBody instance into a Godot [CollisionObject] node. + + + + + + The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle". + + + The type of the body. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger". + + + The linear velocity of the physics body, in meters per second. This is only used when the body type is "rigid" or "vehicle". + + + The mass of the physics body, in kilograms. This is only used when the body type is "rigid" or "vehicle". + + + + + diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml index e09c15a06672..3ce5a3aca59c 100644 --- a/modules/gltf/doc_classes/GLTFSpecGloss.xml +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -6,19 +6,25 @@ [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFSpecGloss] within a script will cause an error in an exported project. + https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + The reflected diffuse factor of the material. + The diffuse texture. + The glossiness or smoothness of the material. + The specular-glossiness texture. + The specular RGB color of the material. The alpha channel is unused. diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 74f005e3023d..d55b122c1c79 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -1,6 +1,7 @@ + Represents all data of a GLTF file. [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFState] within a script will cause an error in an exported project. @@ -44,6 +45,7 @@ + Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. @@ -54,6 +56,7 @@ + Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to. @@ -64,6 +67,7 @@ + Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to. @@ -74,11 +78,13 @@ + Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. + Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. @@ -95,11 +101,13 @@ + Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. + Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to. @@ -116,11 +124,13 @@ + Returns an array of unique animation names. This is only used during the import process. + Returns an array of unique node names. This is used in both the import process and export process. @@ -244,8 +254,10 @@ + The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. + The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file. diff --git a/modules/gltf/extensions/SCsub b/modules/gltf/extensions/SCsub index ad214bb79c87..105a1736dedb 100644 --- a/modules/gltf/extensions/SCsub +++ b/modules/gltf/extensions/SCsub @@ -7,3 +7,4 @@ env_gltf = env_modules.Clone() # Godot source files env_gltf.add_source_files(env.modules_sources, "*.cpp") +env_gltf.add_source_files(env.modules_sources, "physics/*.cpp") diff --git a/modules/gltf/extensions/physics/gltf_collider.cpp b/modules/gltf/extensions/physics/gltf_collider.cpp new file mode 100644 index 000000000000..fab20aeb4355 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_collider.cpp @@ -0,0 +1,304 @@ +/**************************************************************************/ +/* gltf_collider.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "gltf_collider.h" + +#include "../../gltf_state.h" +#include "core/math/convex_hull.h" +#include "scene/3d/area.h" +#include "scene/resources/box_shape.h" +#include "scene/resources/capsule_shape.h" +#include "scene/resources/concave_polygon_shape.h" +#include "scene/resources/convex_polygon_shape.h" +#include "scene/resources/cylinder_shape.h" +#include "scene/resources/sphere_shape.h" + +void GLTFCollider::_bind_methods() { + ClassDB::bind_method(D_METHOD("to_node", "cache_shapes"), &GLTFCollider::to_node, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFCollider::to_dictionary); + + ClassDB::bind_method(D_METHOD("get_shape_type"), &GLTFCollider::get_shape_type); + ClassDB::bind_method(D_METHOD("set_shape_type", "shape_type"), &GLTFCollider::set_shape_type); + ClassDB::bind_method(D_METHOD("get_size"), &GLTFCollider::get_size); + ClassDB::bind_method(D_METHOD("set_size", "size"), &GLTFCollider::set_size); + ClassDB::bind_method(D_METHOD("get_radius"), &GLTFCollider::get_radius); + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GLTFCollider::set_radius); + ClassDB::bind_method(D_METHOD("get_height"), &GLTFCollider::get_height); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFCollider::set_height); + ClassDB::bind_method(D_METHOD("get_is_trigger"), &GLTFCollider::get_is_trigger); + ClassDB::bind_method(D_METHOD("set_is_trigger", "is_trigger"), &GLTFCollider::set_is_trigger); + ClassDB::bind_method(D_METHOD("get_mesh_index"), &GLTFCollider::get_mesh_index); + ClassDB::bind_method(D_METHOD("set_mesh_index", "mesh_index"), &GLTFCollider::set_mesh_index); + ClassDB::bind_method(D_METHOD("get_array_mesh"), &GLTFCollider::get_array_mesh); + ClassDB::bind_method(D_METHOD("set_array_mesh", "array_mesh"), &GLTFCollider::set_array_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "shape_type"), "set_shape_type", "get_shape_type"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_trigger"), "set_is_trigger", "get_is_trigger"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_index"), "set_mesh_index", "get_mesh_index"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "array_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ArrayMesh"), "set_array_mesh", "get_array_mesh"); +} + +String GLTFCollider::get_shape_type() const { + return shape_type; +} + +void GLTFCollider::set_shape_type(String p_shape_type) { + shape_type = p_shape_type; +} + +Vector3 GLTFCollider::get_size() const { + return size; +} + +void GLTFCollider::set_size(Vector3 p_size) { + size = p_size; +} + +real_t GLTFCollider::get_radius() const { + return radius; +} + +void GLTFCollider::set_radius(real_t p_radius) { + radius = p_radius; +} + +real_t GLTFCollider::get_height() const { + return height; +} + +void GLTFCollider::set_height(real_t p_height) { + height = p_height; +} + +bool GLTFCollider::get_is_trigger() const { + return is_trigger; +} + +void GLTFCollider::set_is_trigger(bool p_is_trigger) { + is_trigger = p_is_trigger; +} + +GLTFMeshIndex GLTFCollider::get_mesh_index() const { + return mesh_index; +} + +void GLTFCollider::set_mesh_index(GLTFMeshIndex p_mesh_index) { + mesh_index = p_mesh_index; +} + +Ref GLTFCollider::get_array_mesh() const { + return array_mesh; +} + +void GLTFCollider::set_array_mesh(Ref p_array_mesh) { + array_mesh = p_array_mesh; +} + +Ref GLTFCollider::from_node(const CollisionShape *p_collider_node) { + Ref collider; + collider.instance(); + ERR_FAIL_NULL_V_MSG(p_collider_node, collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node was null."); + Node *parent = p_collider_node->get_parent(); + if (cast_to(parent)) { + collider->set_is_trigger(true); + } + // All the code for working with the shape is below this comment. + Ref shape = p_collider_node->get_shape(); + ERR_FAIL_COND_V_MSG(shape.is_null(), collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node had a null shape."); + collider->_shape_cache = shape; + if (cast_to(shape.ptr())) { + collider->shape_type = "box"; + Ref box = shape; + collider->set_size(box->get_extents() * 2.0f); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "capsule"; + Ref capsule = shape; + collider->set_radius(capsule->get_radius()); + collider->set_height(capsule->get_height()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "cylinder"; + Ref cylinder = shape; + collider->set_radius(cylinder->get_radius()); + collider->set_height(cylinder->get_height()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "sphere"; + Ref sphere = shape; + collider->set_radius(sphere->get_radius()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "hull"; + Ref convex = shape; + PoolVector hull_points = convex->get_points(); + ERR_FAIL_COND_V_MSG(hull_points.size() < 3, collider, "GLTFCollider: Convex hull has fewer points (" + itos(hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls."); + if (hull_points.size() > 255) { + WARN_PRINT("GLTFCollider: Convex hull has more points (" + itos(hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines."); + } + // Convert the convex hull points into an array of faces. + Geometry::MeshData md; + Error err = ConvexHullComputer::convex_hull(hull_points, md); + ERR_FAIL_COND_V_MSG(err != OK, collider, "GLTFCollider: Failed to compute convex hull."); + Vector face_vertices; + for (uint32_t i = 0; i < (uint32_t)md.faces.size(); i++) { + uint32_t index_count = md.faces[i].indices.size(); + for (uint32_t j = 1; j < index_count - 1; j++) { + face_vertices.push_back(hull_points[md.faces[i].indices[0]]); + face_vertices.push_back(hull_points[md.faces[i].indices[j]]); + face_vertices.push_back(hull_points[md.faces[i].indices[j + 1]]); + } + } + // Create an ArrayMesh from the faces. + Ref array_mesh; + array_mesh.instance(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = face_vertices; + array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_array_mesh(array_mesh); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "trimesh"; + Ref concave = shape; + Ref array_mesh; + array_mesh.instance(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = concave->get_faces(); + array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_array_mesh(array_mesh); + } else { + ERR_PRINT("Tried to create a GLTFCollider from a CollisionShape node, but the given node's shape '" + String(Variant(shape)) + + "' had an unsupported shape type. Only BoxShape, CapsuleShape, CylinderShape, SphereShape, ConcavePolygonShape, and ConvexPolygonShape are supported."); + } + return collider; +} + +CollisionShape *GLTFCollider::to_node(bool p_cache_shapes) { + CollisionShape *collider = memnew(CollisionShape); + if (!p_cache_shapes || _shape_cache == nullptr) { + if (shape_type == "box") { + Ref box; + box.instance(); + box->set_extents(size * 0.5f); + _shape_cache = box; + } else if (shape_type == "capsule") { + Ref capsule; + capsule.instance(); + capsule->set_radius(radius); + capsule->set_height(height); + _shape_cache = capsule; + } else if (shape_type == "cylinder") { + Ref cylinder; + cylinder.instance(); + cylinder->set_radius(radius); + cylinder->set_height(height); + _shape_cache = cylinder; + } else if (shape_type == "sphere") { + Ref sphere; + sphere.instance(); + sphere->set_radius(radius); + _shape_cache = sphere; + } else if (shape_type == "hull") { + ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting convex hull collider to a node: The mesh resource is null."); + Ref convex = array_mesh->create_convex_shape(); + _shape_cache = convex; + } else if (shape_type == "trimesh") { + ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting concave mesh collider to a node: The mesh resource is null."); + Ref concave = array_mesh->create_trimesh_shape(); + _shape_cache = concave; + } else { + ERR_PRINT("GLTFCollider: Error converting to a node: Shape type '" + shape_type + "' is unknown."); + } + } + collider->set_shape(_shape_cache); + return collider; +} + +Ref GLTFCollider::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref(), "Failed to parse GLTF collider, missing required field 'type'."); + Ref collider; + collider.instance(); + const String &shape_type = p_dictionary["type"]; + collider->shape_type = shape_type; + if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") { + ERR_PRINT("Error parsing GLTF collider: Shape type '" + shape_type + "' is unknown. Only box, capsule, cylinder, sphere, hull, and trimesh are supported."); + } + if (p_dictionary.has("radius")) { + collider->set_radius(p_dictionary["radius"]); + } + if (p_dictionary.has("height")) { + collider->set_height(p_dictionary["height"]); + } + if (p_dictionary.has("size")) { + const Array &arr = p_dictionary["size"]; + if (arr.size() == 3) { + collider->set_size(Vector3(arr[0], arr[1], arr[2])); + } else { + ERR_PRINT("Error parsing GLTF collider: The size must have exactly 3 numbers."); + } + } + if (p_dictionary.has("isTrigger")) { + collider->set_is_trigger(p_dictionary["isTrigger"]); + } + if (p_dictionary.has("mesh")) { + collider->set_mesh_index(p_dictionary["mesh"]); + } + if (unlikely(collider->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) { + ERR_PRINT("Error parsing GLTF collider: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index."); + } + return collider; +} + +Dictionary GLTFCollider::to_dictionary() const { + Dictionary d; + d["type"] = shape_type; + if (shape_type == "box") { + Array size_array; + size_array.resize(3); + size_array[0] = size.x; + size_array[1] = size.y; + size_array[2] = size.z; + d["size"] = size_array; + } else if (shape_type == "capsule") { + d["radius"] = get_radius(); + d["height"] = get_height(); + } else if (shape_type == "cylinder") { + d["radius"] = get_radius(); + d["height"] = get_height(); + } else if (shape_type == "sphere") { + d["radius"] = get_radius(); + } else if (shape_type == "trimesh" || shape_type == "hull") { + d["mesh"] = get_mesh_index(); + } + if (is_trigger) { + d["isTrigger"] = is_trigger; + } + return d; +} diff --git a/modules/gltf/extensions/physics/gltf_collider.h b/modules/gltf/extensions/physics/gltf_collider.h new file mode 100644 index 000000000000..13086946821a --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_collider.h @@ -0,0 +1,88 @@ +/**************************************************************************/ +/* gltf_collider.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 GLTF_COLLIDER_H +#define GLTF_COLLIDER_H + +#include "../../gltf_defines.h" +#include "scene/3d/collision_shape.h" + +class GLTFState; + +// GLTFCollider is an intermediary between OMI_collider and Godot's collision shape nodes. +// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider + +class GLTFCollider : public Resource { + GDCLASS(GLTFCollider, Resource) + +protected: + static void _bind_methods(); + +private: + String shape_type; + Vector3 size = Vector3(1.0, 1.0, 1.0); + real_t radius = 0.5; + real_t height = 2.0; + bool is_trigger = false; + GLTFMeshIndex mesh_index = -1; + Ref array_mesh = nullptr; + // Internal only, for caching Godot shape resources. Used in `to_node`. + Ref _shape_cache = nullptr; + +public: + String get_shape_type() const; + void set_shape_type(String p_shape_type); + + Vector3 get_size() const; + void set_size(Vector3 p_size); + + real_t get_radius() const; + void set_radius(real_t p_radius); + + real_t get_height() const; + void set_height(real_t p_height); + + bool get_is_trigger() const; + void set_is_trigger(bool p_is_trigger); + + GLTFMeshIndex get_mesh_index() const; + void set_mesh_index(GLTFMeshIndex p_mesh_index); + + Ref get_array_mesh() const; + void set_array_mesh(Ref p_array_mesh); + + static Ref from_node(const CollisionShape *p_collider_node); + CollisionShape *to_node(bool p_cache_shapes = false); + + static Ref from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; +}; + +#endif // GLTF_COLLIDER_H diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp new file mode 100644 index 000000000000..94d2fef73192 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -0,0 +1,274 @@ +/**************************************************************************/ +/* gltf_document_extension_physics.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "gltf_document_extension_physics.h" + +#include "scene/3d/area.h" + +// Import process. +Error GLTFDocumentExtensionPhysics::import_preflight(Ref p_state, Vector p_extensions) { + if (p_extensions.find("OMI_collider") < 0 && p_extensions.find("OMI_physics_body") < 0) { + return ERR_SKIP; + } + Dictionary state_json = p_state->get_json(); + if (state_json.has("extensions")) { + Dictionary state_extensions = state_json["extensions"]; + if (state_extensions.has("OMI_collider")) { + Dictionary omi_collider_ext = state_extensions["OMI_collider"]; + if (omi_collider_ext.has("colliders")) { + Array state_collider_dicts = omi_collider_ext["colliders"]; + if (state_collider_dicts.size() > 0) { + Array state_colliders; + for (int i = 0; i < state_collider_dicts.size(); i++) { + state_colliders.push_back(GLTFCollider::from_dictionary(state_collider_dicts[i])); + } + p_state->set_additional_data("GLTFColliders", state_colliders); + } + } + } + } + return OK; +} + +Vector GLTFDocumentExtensionPhysics::get_supported_extensions() { + Vector ret; + ret.push_back("OMI_collider"); + ret.push_back("OMI_physics_body"); + return ret; +} + +Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions) { + if (p_extensions.has("OMI_collider")) { + Dictionary node_collider_ext = p_extensions["OMI_collider"]; + if (node_collider_ext.has("collider")) { + // "collider" is the index of the collider in the state colliders array. + int node_collider_index = node_collider_ext["collider"]; + Array state_colliders = p_state->get_additional_data("GLTFColliders"); + ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); + p_gltf_node->set_additional_data("GLTFCollider", state_colliders[node_collider_index]); + } else { + p_gltf_node->set_additional_data("GLTFCollider", GLTFCollider::from_dictionary(p_extensions["OMI_collider"])); + } + } + if (p_extensions.has("OMI_physics_body")) { + p_gltf_node->set_additional_data("GLTFPhysicsBody", GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"])); + } + return OK; +} + +void _setup_collider_mesh_resource_from_index_if_needed(Ref p_state, Ref p_collider) { + GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index(); + if (collider_mesh_index == -1) { + return; // No mesh for this collider. + } + Ref array_mesh = p_collider->get_array_mesh(); + if (array_mesh.is_valid()) { + return; // The mesh resource is already set up. + } + Array state_meshes = p_state->get_meshes(); + ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); + Ref gltf_mesh = state_meshes[collider_mesh_index]; + ERR_FAIL_COND(gltf_mesh.is_null()); + array_mesh = gltf_mesh->get_mesh(); + ERR_FAIL_COND(array_mesh.is_null()); + p_collider->set_array_mesh(array_mesh); +} + +CollisionObject *_generate_collision_with_body(Ref p_state, Ref p_gltf_node, Ref p_collider, Ref p_physics_body) { + print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name()); + bool is_trigger = p_collider->get_is_trigger(); + // This method is used for the case where we must generate a parent body. + // This is can happen for multiple reasons. One possibility is that this + // GLTF file is using OMI_collider but not OMI_physics_body, or at least + // this particular node is not using it. Another possibility is that the + // physics body information is set up on the same GLTF node, not a parent. + CollisionObject *body; + if (p_physics_body.is_valid()) { + // This code is run when the physics body is on the same GLTF node. + body = p_physics_body->to_node(); + if (is_trigger != (p_physics_body->get_body_type() == "trigger")) { + // Edge case: If the body's trigger and the collider's trigger + // are in disagreement, we need to create another new body. + CollisionObject *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr); + child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid"))); + body->add_child(child); + return body; + } + } else if (is_trigger) { + body = memnew(Area); + } else { + body = memnew(StaticBody); + } + CollisionShape *shape = p_collider->to_node(); + shape->set_name(p_gltf_node->get_name() + "Shape"); + body->add_child(shape); + return body; +} + +Spatial *GLTFDocumentExtensionPhysics::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) { + Ref physics_body = p_gltf_node->get_additional_data("GLTFPhysicsBody"); + Ref collider = p_gltf_node->get_additional_data("GLTFCollider"); + if (collider.is_valid()) { + _setup_collider_mesh_resource_from_index_if_needed(p_state, collider); + // If the collider has the correct type of parent, we just return one node. + if (collider->get_is_trigger()) { + if (Object::cast_to(p_scene_parent)) { + return collider->to_node(true); + } + } else { + if (Object::cast_to(p_scene_parent)) { + return collider->to_node(true); + } + } + return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body); + } + if (physics_body.is_valid()) { + return physics_body->to_node(); + } + return nullptr; +} + +// Export process. +bool _are_all_faces_equal(const PoolVector &p_a, const PoolVector &p_b) { + if (p_a.size() != p_b.size()) { + return false; + } + for (int i = 0; i < p_a.size(); i++) { + Face3 a_face = p_a[i]; + Face3 b_face = p_b[i]; + const Vector3 *a_vertices = a_face.vertex; + const Vector3 *b_vertices = b_face.vertex; + for (int j = 0; j < 3; j++) { + if (!a_vertices[j].is_equal_approx(b_vertices[j])) { + return false; + } + } + } + return true; +} + +GLTFMeshIndex _get_or_insert_mesh_in_state(Ref p_state, Ref p_mesh) { + ERR_FAIL_COND_V(p_mesh.is_null(), -1); + Array state_meshes = p_state->get_meshes(); + PoolVector mesh_faces = p_mesh->get_faces(); + // De-duplication: If the state already has the mesh we need, use that one. + for (GLTFMeshIndex i = 0; i < state_meshes.size(); i++) { + Ref state_gltf_mesh = state_meshes[i]; + ERR_CONTINUE(state_gltf_mesh.is_null()); + Ref state_array_mesh = state_gltf_mesh->get_mesh(); + ERR_CONTINUE(state_array_mesh.is_null()); + if (state_array_mesh == p_mesh) { + return i; + } + if (_are_all_faces_equal(state_array_mesh->get_faces(), mesh_faces)) { + return i; + } + } + // After the loop, we have checked that the mesh is not equal to any of the + // meshes in the state. So we insert a new mesh into the state mesh array. + Ref gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_mesh(p_mesh); + GLTFMeshIndex mesh_index = state_meshes.size(); + state_meshes.push_back(gltf_mesh); + p_state->set_meshes(state_meshes); + return mesh_index; +} + +void GLTFDocumentExtensionPhysics::convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node) { + if (cast_to(p_scene_node)) { + CollisionShape *shape = Object::cast_to(p_scene_node); + Ref collider = GLTFCollider::from_node(shape); + { + Ref array_mesh = collider->get_array_mesh(); + if (array_mesh.is_valid()) { + collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, array_mesh)); + } + } + p_gltf_node->set_additional_data("GLTFCollider", collider); + } else if (cast_to(p_scene_node)) { + CollisionObject *body = Object::cast_to(p_scene_node); + p_gltf_node->set_additional_data("GLTFPhysicsBody", GLTFPhysicsBody::from_node(body)); + } +} + +Array _get_or_create_state_colliders_in_state(Ref p_state) { + Dictionary state_json = p_state->get_json(); + Dictionary state_extensions; + if (state_json.has("extensions")) { + state_extensions = state_json["extensions"]; + } else { + state_json["extensions"] = state_extensions; + } + Dictionary omi_collider_ext; + if (state_extensions.has("OMI_collider")) { + omi_collider_ext = state_extensions["OMI_collider"]; + } else { + state_extensions["OMI_collider"] = omi_collider_ext; + p_state->add_used_extension("OMI_collider"); + } + Array state_colliders; + if (omi_collider_ext.has("colliders")) { + state_colliders = omi_collider_ext["colliders"]; + } else { + omi_collider_ext["colliders"] = state_colliders; + } + return state_colliders; +} + +Error GLTFDocumentExtensionPhysics::export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_node_json, Node *p_node) { + Dictionary node_extensions = r_node_json["extensions"]; + Ref physics_body = p_gltf_node->get_additional_data("GLTFPhysicsBody"); + if (physics_body.is_valid()) { + node_extensions["OMI_physics_body"] = physics_body->to_dictionary(); + p_state->add_used_extension("OMI_physics_body"); + } + Ref collider = p_gltf_node->get_additional_data("GLTFCollider"); + if (collider.is_valid()) { + Array state_colliders = _get_or_create_state_colliders_in_state(p_state); + int size = state_colliders.size(); + Dictionary omi_collider_ext; + node_extensions["OMI_collider"] = omi_collider_ext; + Dictionary collider_dict = collider->to_dictionary(); + for (int i = 0; i < size; i++) { + Dictionary other = state_colliders[i]; + if (other == collider_dict) { + // De-duplication: If we already have an identical collider, + // set the collider index to the existing one and return. + omi_collider_ext["collider"] = i; + return OK; + } + } + // If we don't have an identical collider, add it to the array. + state_colliders.push_back(collider_dict); + omi_collider_ext["collider"] = size; + } + return OK; +} diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.h b/modules/gltf/extensions/physics/gltf_document_extension_physics.h new file mode 100644 index 000000000000..9a4f50fe3aa8 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.h @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* gltf_document_extension_physics.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 GLTF_DOCUMENT_EXTENSION_PHYSICS_H +#define GLTF_DOCUMENT_EXTENSION_PHYSICS_H + +#include "../gltf_document_extension.h" + +#include "gltf_collider.h" +#include "gltf_physics_body.h" + +class GLTFDocumentExtensionPhysics : public GLTFDocumentExtension { + GDCLASS(GLTFDocumentExtensionPhysics, GLTFDocumentExtension); + +public: + // Import process. + Error import_preflight(Ref p_state, Vector p_extensions); + Vector get_supported_extensions(); + Error parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions); + Spatial *generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent); + // Export process. + void convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node); + Error export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_node_json, Node *p_scene_node); +}; + +#endif // GLTF_DOCUMENT_EXTENSION_PHYSICS_H diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp new file mode 100644 index 000000000000..92e8b9cec752 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -0,0 +1,196 @@ +/**************************************************************************/ +/* gltf_physics_body.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "gltf_physics_body.h" + +#include "scene/3d/area.h" +#include "scene/3d/vehicle_body.h" + +void GLTFPhysicsBody::_bind_methods() { + ClassDB::bind_method(D_METHOD("to_node"), &GLTFPhysicsBody::to_node); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFPhysicsBody::to_dictionary); + + ClassDB::bind_method(D_METHOD("get_body_type"), &GLTFPhysicsBody::get_body_type); + ClassDB::bind_method(D_METHOD("set_body_type", "body_type"), &GLTFPhysicsBody::set_body_type); + ClassDB::bind_method(D_METHOD("get_mass"), &GLTFPhysicsBody::get_mass); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &GLTFPhysicsBody::set_mass); + ClassDB::bind_method(D_METHOD("get_linear_velocity"), &GLTFPhysicsBody::get_linear_velocity); + ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &GLTFPhysicsBody::set_linear_velocity); + ClassDB::bind_method(D_METHOD("get_angular_velocity"), &GLTFPhysicsBody::get_angular_velocity); + ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); +} + +String GLTFPhysicsBody::get_body_type() const { + return body_type; +} + +void GLTFPhysicsBody::set_body_type(String p_body_type) { + body_type = p_body_type; +} + +real_t GLTFPhysicsBody::get_mass() const { + return mass; +} + +void GLTFPhysicsBody::set_mass(real_t p_mass) { + mass = p_mass; +} + +Vector3 GLTFPhysicsBody::get_linear_velocity() const { + return linear_velocity; +} + +void GLTFPhysicsBody::set_linear_velocity(Vector3 p_linear_velocity) { + linear_velocity = p_linear_velocity; +} + +Vector3 GLTFPhysicsBody::get_angular_velocity() const { + return angular_velocity; +} + +void GLTFPhysicsBody::set_angular_velocity(Vector3 p_angular_velocity) { + angular_velocity = p_angular_velocity; +} + +Ref GLTFPhysicsBody::from_node(const CollisionObject *p_body_node) { + Ref physics_body; + physics_body.instance(); + ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject node, but the given node was null."); + if (cast_to(p_body_node)) { + physics_body->body_type = "kinematic"; + } else if (cast_to(p_body_node)) { + const RigidBody *body = cast_to(p_body_node); + physics_body->mass = body->get_mass(); + physics_body->linear_velocity = body->get_linear_velocity(); + physics_body->angular_velocity = body->get_angular_velocity(); + if (cast_to(p_body_node)) { + physics_body->body_type = "vehicle"; + } else { + physics_body->body_type = "rigid"; + } + } else if (cast_to(p_body_node)) { + physics_body->body_type = "static"; + } else if (cast_to(p_body_node)) { + physics_body->body_type = "trigger"; + } + return physics_body; +} + +CollisionObject *GLTFPhysicsBody::to_node() const { + if (body_type == "character" || body_type == "kinematic") { + KinematicBody *body = memnew(KinematicBody); + return body; + } + if (body_type == "vehicle") { + VehicleBody *body = memnew(VehicleBody); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + return body; + } + if (body_type == "rigid") { + RigidBody *body = memnew(RigidBody); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + return body; + } + if (body_type == "static") { + StaticBody *body = memnew(StaticBody); + return body; + } + if (body_type == "trigger") { + Area *body = memnew(Area); + return body; + } + ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown."); +} + +Ref GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) { + Ref physics_body; + physics_body.instance(); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'."); + const String &body_type = p_dictionary["type"]; + physics_body->body_type = body_type; + + if (p_dictionary.has("mass")) { + physics_body->mass = p_dictionary["mass"]; + } + if (p_dictionary.has("linearVelocity")) { + const Array &arr = p_dictionary["linearVelocity"]; + if (arr.size() == 3) { + physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2])); + } else { + ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers."); + } + } + if (p_dictionary.has("angularVelocity")) { + const Array &arr = p_dictionary["angularVelocity"]; + if (arr.size() == 3) { + physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2])); + } else { + ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers."); + } + } + if (body_type != "character" && body_type != "kinematic" && body_type != "rigid" && body_type != "static" && body_type != "trigger" && body_type != "vehicle") { + ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown."); + } + return physics_body; +} + +Dictionary GLTFPhysicsBody::to_dictionary() const { + Dictionary d; + d["type"] = body_type; + if (mass != 1.0) { + d["mass"] = mass; + } + if (linear_velocity != Vector3()) { + Array velocity_array; + velocity_array.resize(3); + velocity_array[0] = linear_velocity.x; + velocity_array[1] = linear_velocity.y; + velocity_array[2] = linear_velocity.z; + d["linearVelocity"] = velocity_array; + } + if (angular_velocity != Vector3()) { + Array velocity_array; + velocity_array.resize(3); + velocity_array[0] = angular_velocity.x; + velocity_array[1] = angular_velocity.y; + velocity_array[2] = angular_velocity.z; + d["angularVelocity"] = velocity_array; + } + return d; +} diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h new file mode 100644 index 000000000000..b496e0307de5 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.h @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* gltf_physics_body.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 GLTF_PHYSICS_BODY_H +#define GLTF_PHYSICS_BODY_H + +#include "scene/3d/physics_body.h" + +// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes. +// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body + +class GLTFPhysicsBody : public Resource { + GDCLASS(GLTFPhysicsBody, Resource) + +protected: + static void _bind_methods(); + +private: + String body_type = "static"; + real_t mass = 1.0; + Vector3 linear_velocity = Vector3(); + Vector3 angular_velocity = Vector3(); + +public: + String get_body_type() const; + void set_body_type(String p_body_type); + + real_t get_mass() const; + void set_mass(real_t p_mass); + + Vector3 get_linear_velocity() const; + void set_linear_velocity(Vector3 p_linear_velocity); + + Vector3 get_angular_velocity() const; + void set_angular_velocity(Vector3 p_angular_velocity); + + static Ref from_node(const CollisionObject *p_body_node); + CollisionObject *to_node() const; + + static Ref from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; +}; + +#endif // GLTF_PHYSICS_BODY_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index d6be7f5e6efa..69985c317c2d 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -34,6 +34,7 @@ #include "extensions/gltf_document_extension.h" #include "extensions/gltf_spec_gloss.h" +#include "extensions/physics/gltf_document_extension_physics.h" #include "gltf_state.h" #ifdef TOOLS_ENABLED @@ -50,6 +51,11 @@ static void _editor_init() { } #endif +#define GLTF_REGISTER_DOCUMENT_EXTENSION(m_doc_ext_class) \ + Ref extension_##m_doc_ext_class; \ + extension_##m_doc_ext_class.instance(); \ + GLTFDocument::register_gltf_document_extension(extension_##m_doc_ext_class); + void register_gltf_types() { #ifdef TOOLS_ENABLED ClassDB::APIType prev_api = ClassDB::get_current_api(); @@ -66,19 +72,23 @@ void register_gltf_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics); } void unregister_gltf_types() { + GLTFDocument::unregister_all_gltf_document_extensions(); } #endif // _3D_DISABLED diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 239047f2c83c..8b6efd572f6e 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -40,6 +40,7 @@ import android.util.Log import android.widget.Toast import androidx.window.layout.WindowMetricsCalculator import org.godotengine.godot.FullScreenGodotApp +import org.godotengine.godot.GodotLib import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix import java.util.* @@ -90,11 +91,19 @@ open class GodotEditor : FullScreenGodotApp() { } super.onCreate(savedInstanceState) + } + + override fun onGodotSetupCompleted() { + super.onGodotSetupCompleted() + val longPressEnabled = enableLongPressGestures() + val panScaleEnabled = enablePanAndScaleGestures() - // Enable long press, panning and scaling gestures - godotFragment?.renderView?.inputHandler?.apply { - enableLongPress(enableLongPressGestures()) - enablePanningAndScalingGestures(enablePanAndScaleGestures()) + runOnUiThread { + // Enable long press, panning and scaling gestures + godotFragment?.renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + } } } @@ -213,12 +222,14 @@ open class GodotEditor : FullScreenGodotApp() { /** * Enable long press gestures for the Godot Android editor. */ - protected open fun enableLongPressGestures() = true + protected open fun enableLongPressGestures() = + java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_long_press_as_right_click")) /** * Enable pan and scale gestures for the Godot Android editor. */ - protected open fun enablePanAndScaleGestures() = true + protected open fun enablePanAndScaleGestures() = + java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index f03f918dd3f1..40eed7ec781f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -179,6 +179,13 @@ public class GodotLib { */ public static native String getGlobal(String p_key); + /** + * Used to access Godot's editor settings. + * @param settingKey Setting key + * @return String value of the setting + */ + public static native String getEditorSetting(String settingKey); + /** * Invoke method |p_method| on the Godot object specified by |p_id| * @param p_id Id of the Godot object to invoke diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 04c221e74c35..f8179170c1d3 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -53,6 +53,10 @@ #include #include +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + static JavaClassWrapper *java_class_wrapper = nullptr; static OS_Android *os_android = nullptr; static AndroidInputHandler *input_handler = nullptr; @@ -449,6 +453,18 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv * return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data()); } +JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key) { + String editor_setting = ""; +#ifdef TOOLS_ENABLED + String godot_setting_key = jstring_to_string(p_setting_key, env); + editor_setting = EDITOR_GET(godot_setting_key).operator String(); +#else + WARN_PRINT("Access to the Editor Settings in only available on Editor builds"); +#endif + + return env->NewStringUTF(editor_setting.utf8().get_data()); +} + JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { Object *obj = ObjectDB::get_instance(ID); ERR_FAIL_NULL(obj); diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index cc1f2450a9ea..c699efdef9df 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -61,6 +61,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz); JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path); +JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height); diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp index 2cf5cf04e890..b41656b16ef7 100644 --- a/platform/x11/joypad_linux.cpp +++ b/platform/x11/joypad_linux.cpp @@ -32,6 +32,8 @@ #include "joypad_linux.h" +#include "core/os/os.h" + #include #include #include @@ -77,13 +79,41 @@ void JoypadLinux::Joypad::reset() { events.clear(); } +// This function is derived from SDL: +// https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45 +static bool detect_sandbox() { + if (access("/.flatpak-info", F_OK) == 0) { + return true; + } + + // For Snap, we check multiple variables because they might be set for + // unrelated reasons. This is the same thing WebKitGTK does. + if (OS::get_singleton()->has_environment("SNAP") && OS::get_singleton()->has_environment("SNAP_NAME") && OS::get_singleton()->has_environment("SNAP_REVISION")) { + return true; + } + + if (access("/run/host/container-manager", F_OK) == 0) { + return true; + } + + return false; +} + JoypadLinux::JoypadLinux(InputDefault *in) { #ifdef UDEV_ENABLED - use_udev = initialize_libudev() == 0; - if (use_udev) { - print_verbose("JoypadLinux: udev enabled and loaded successfully."); + if (detect_sandbox()) { + // Linux binaries in sandboxes / containers need special handling because + // libudev doesn't work there. So we need to fallback to manual parsing + // of /dev/input in such case. + use_udev = false; + print_verbose("JoypadLinux: udev enabled, but detected incompatible sandboxed mode. Falling back to /dev/input to detect joypads."); } else { - print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); + use_udev = initialize_libudev() == 0; + if (use_udev) { + print_verbose("JoypadLinux: udev enabled and loaded successfully."); + } else { + print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); + } } #else print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads."); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 13a923de593a..fabcf5bbb6f4 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -564,18 +564,32 @@ void TileMap::update_dirty_quadrants() { } Ref normal_map = tile_set->tile_get_normal_map(c.id); - Color modulate = tile_set->tile_get_modulate(c.id); - Color self_modulate = get_self_modulate(); - modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, - modulate.b * self_modulate.b, modulate.a * self_modulate.a); + Color modulate = tile_set->tile_get_modulate(c.id) * get_self_modulate(); + if (r == Rect2()) { tex->draw_rect(canvas_item, rect, false, modulate, c.transpose, normal_map); } else { - if (!multirect_started) { - multirect_started = true; - VisualServerCanvasHelper::tilemap_begin(); + Texture::RefineRectResult res = tex->refine_rect_region(rect, r); + switch (res) { + case Texture::REFINE_RECT_RESULT_DRAW: { + if (!multirect_started) { + multirect_started = true; + VisualServerCanvasHelper::tilemap_begin(); + } + VisualServerCanvasHelper::tilemap_add_rect(canvas_item, rect, tex->get_rid(), r, modulate, c.transpose, normal_map.is_valid() ? normal_map->get_rid() : RID(), clip_uv); + } break; + case Texture::REFINE_RECT_RESULT_FALLBACK: { + if (multirect_started) { + // If we are currently writing a multirect, we must flush + // to ensure there are no issues due to overlap. + VisualServerCanvasHelper::tilemap_end(); + multirect_started = false; + } + tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, normal_map, clip_uv); + } break; + default: { + } break; } - VisualServerCanvasHelper::tilemap_add_rect(canvas_item, rect, tex->get_rid(), r, modulate, c.transpose, normal_map.is_valid() ? normal_map->get_rid() : RID(), clip_uv); } Vector shapes = tile_set->tile_get_shapes(c.id); diff --git a/scene/3d/path.cpp b/scene/3d/path.cpp index 2370104a3259..ddbdcb2c5f1b 100644 --- a/scene/3d/path.cpp +++ b/scene/3d/path.cpp @@ -175,7 +175,9 @@ void PathFollow::_update_transform(bool p_update_xyz_rot) { Vector3 axis = t_prev.cross(t_cur); float dot = t_prev.dot(t_cur); - float angle = Math::acos(CLAMP(dot, -1, 1)); + + // acos does clamping. + float angle = Math::acos(dot); if (likely(!Math::is_zero_approx(angle))) { if (rotation_mode == ROTATION_Y) { diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp index 954ec66a845e..22561d1d7be8 100644 --- a/scene/animation/skeleton_ik.cpp +++ b/scene/animation/skeleton_ik.cpp @@ -296,7 +296,8 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized()); if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) { - const real_t rot_angle(Math::acos(CLAMP(initial_ori.dot(ci->current_ori), -1, 1))); + // acos does clamping. + const real_t rot_angle(Math::acos(initial_ori.dot(ci->current_ori))); new_bone_pose.basis.rotate(rot_axis, rot_angle); } diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 669ed82e4f33..064230439a4d 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -40,8 +40,6 @@ void GridContainer::_notification(int p_what) { int hsep = get_constant("hseparation"); int vsep = get_constant("vseparation"); - int max_col = MIN(get_child_count(), columns); - int max_row = ceil((float)get_child_count() / (float)columns); // Compute the per-column/per-row data. int valid_controls_index = 0; @@ -78,6 +76,9 @@ void GridContainer::_notification(int p_what) { } } + int max_col = MIN(valid_controls_index, columns); + int max_row = ceil((float)valid_controls_index / (float)columns); + // Consider all empty columns expanded. for (int i = valid_controls_index; i < columns; i++) { col_expanded.insert(i); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 78e17f498c8a..83694541ae29 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1786,8 +1786,8 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } Transform2D matrix = p_xform * p_node->get_transform(); - // matrix.basis_determinant() == 0.0f implies that node does not exist on scene - if (matrix.basis_determinant() == 0.0f) { + // matrix.determinant() == 0.0f implies that node does not exist on scene + if (matrix.determinant() == 0.0f) { return nullptr; } diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 358d3324c751..400736f0d452 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -318,7 +318,7 @@ Vector BitMap::_march_square(const Rect2i &rect, const Point2i &start) prevx = stepx; prevy = stepy; - ERR_FAIL_COND_V((int)count > width * height, _points); + ERR_FAIL_COND_V((int)count > 2 * (width * height + 1), _points); } while (curx != startx || cury != starty); return _points; } diff --git a/scene/resources/sky.cpp b/scene/resources/sky.cpp index 52bd36a3f8b1..9862bc71860a 100644 --- a/scene/resources/sky.cpp +++ b/scene/resources/sky.cpp @@ -173,7 +173,8 @@ Ref ProceduralSky::_generate_sky() { normal.normalize(); - float v_angle = Math::acos(CLAMP(normal.y, -1.0, 1.0)); + // acos does clamping. + float v_angle = Math::acos(normal.y); Color color; @@ -192,7 +193,8 @@ Ref ProceduralSky::_generate_sky() { color.g *= sky_energy; color.b *= sky_energy; - float sun_angle = Math::rad2deg(Math::acos(CLAMP(sun.dot(normal), -1.0, 1.0))); + // acos does clamping. + float sun_angle = Math::rad2deg(Math::acos(sun.dot(normal))); if (sun_angle < sun_angle_min) { color = color.blend(sun_linear); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 67e77c6c3e2b..c55172501fa2 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1028,17 +1028,31 @@ void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile atlas->draw_rect_region(p_canvas_item, dr, rc, p_modulate, p_transpose, p_normal_map); } + +Texture::RefineRectResult AtlasTexture::refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { + if (!atlas.is_valid()) { + return REFINE_RECT_RESULT_NO_DRAW; + } + Rect2 temp_rect = r_dst_rect; + Rect2 temp_src_rect = r_src_rect; + + if (get_rect_region(temp_rect, temp_src_rect, r_dst_rect, r_src_rect)) { + return atlas->refine_rect_region(r_dst_rect, r_src_rect); + } + + return REFINE_RECT_RESULT_NO_DRAW; +} + void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, const Ref &p_normal_map, bool p_clip_uv) const { - //this might not necessarily work well if using a rect, needs to be fixed properly if (!atlas.is_valid()) { return; } - Rect2 dr; - Rect2 src_c; - get_rect_region(p_rect, p_src_rect, dr, src_c); - - atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, p_normal_map); + Rect2 dst; + Rect2 src; + if (get_rect_region(p_rect, p_src_rect, dst, src)) { + atlas->draw_rect_region(p_canvas_item, dst, src, p_modulate, p_transpose, p_normal_map); + } } bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { @@ -1197,11 +1211,6 @@ void MeshTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); VisualServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid(), normal_rid); } -bool MeshTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { - r_rect = p_rect; - r_src_rect = p_src_rect; - return true; -} bool MeshTexture::is_pixel_opaque(int p_x, int p_y) const { return true; diff --git a/scene/resources/texture.h b/scene/resources/texture.h index cde6ea927955..a9fad1a16a7b 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -61,6 +61,12 @@ class Texture : public Resource { FLAG_MIRRORED_REPEAT = VisualServer::TEXTURE_FLAG_MIRRORED_REPEAT }; + enum RefineRectResult { + REFINE_RECT_RESULT_DRAW, + REFINE_RECT_RESULT_FALLBACK, + REFINE_RECT_RESULT_NO_DRAW, + }; + virtual int get_width() const = 0; virtual int get_height() const = 0; virtual Size2 get_size() const; @@ -77,6 +83,7 @@ class Texture : public Resource { virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { return REFINE_RECT_RESULT_DRAW; } virtual Ref get_data() const { return Ref(); } @@ -144,6 +151,7 @@ class ImageTexture : public Texture { virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { return ((w | h) == 0) ? REFINE_RECT_RESULT_NO_DRAW : REFINE_RECT_RESULT_DRAW; } void set_storage(Storage p_storage); Storage get_storage() const; @@ -215,6 +223,7 @@ class StreamTexture : public Texture { virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { return ((w | h) == 0) ? REFINE_RECT_RESULT_NO_DRAW : REFINE_RECT_RESULT_DRAW; } virtual bool has_alpha() const; virtual void set_flags(uint32_t p_flags); @@ -276,6 +285,7 @@ class AtlasTexture : public Texture { virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const; bool is_pixel_opaque(int p_x, int p_y) const; @@ -317,7 +327,7 @@ class MeshTexture : public Texture { virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; - virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { return REFINE_RECT_RESULT_FALLBACK; } bool is_pixel_opaque(int p_x, int p_y) const; @@ -366,6 +376,7 @@ class LargeTexture : public Texture { virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref()) const; virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref &p_normal_map = Ref(), bool p_clip_uv = true) const; + virtual RefineRectResult refine_rect_region(Rect2 &r_dst_rect, Rect2 &r_src_rect) const { return REFINE_RECT_RESULT_FALLBACK; } bool is_pixel_opaque(int p_x, int p_y) const;