summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp32
-rw-r--r--scene/2d/navigation_obstacle_2d.h2
-rw-r--r--scene/2d/physics/rigid_body_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp3
-rw-r--r--scene/2d/tile_map_layer.cpp25
-rw-r--r--scene/2d/tile_map_layer.h2
-rw-r--r--scene/2d/touch_screen_button.cpp2
-rw-r--r--scene/3d/label_3d.cpp2
-rw-r--r--scene/3d/look_at_modifier_3d.cpp781
-rw-r--r--scene/3d/look_at_modifier_3d.h199
-rw-r--r--scene/3d/navigation_agent_3d.cpp4
-rw-r--r--scene/3d/navigation_link_3d.cpp4
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp342
-rw-r--r--scene/3d/navigation_obstacle_3d.h13
-rw-r--r--scene/3d/navigation_region_3d.cpp8
-rw-r--r--scene/3d/path_3d.cpp4
-rw-r--r--scene/3d/physics/ray_cast_3d.cpp2
-rw-r--r--scene/3d/physics/rigid_body_3d.cpp2
-rw-r--r--scene/3d/physics/shape_cast_3d.cpp2
-rw-r--r--scene/3d/skeleton_3d.cpp22
-rw-r--r--scene/3d/skeleton_3d.h3
-rw-r--r--scene/3d/skeleton_ik_3d.h2
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp16
-rw-r--r--scene/3d/skeleton_modifier_3d.h1
-rw-r--r--scene/3d/sprite_3d.cpp2
-rw-r--r--scene/debugger/scene_debugger.cpp47
-rw-r--r--scene/debugger/scene_debugger.h6
-rw-r--r--scene/gui/base_button.cpp1
-rw-r--r--scene/gui/button.cpp2
-rw-r--r--scene/gui/button.h1
-rw-r--r--scene/gui/code_edit.h6
-rw-r--r--scene/gui/file_dialog.cpp1
-rw-r--r--scene/gui/graph_edit.cpp4
-rw-r--r--scene/gui/label.cpp344
-rw-r--r--scene/gui/label.h5
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/spin_box.cpp38
-rw-r--r--scene/gui/spin_box.h2
-rw-r--r--scene/gui/text_edit.cpp6
-rw-r--r--scene/gui/text_edit.h2
-rw-r--r--scene/gui/tree.cpp59
-rw-r--r--scene/main/missing_node.cpp14
-rw-r--r--scene/main/scene_tree.cpp2
-rw-r--r--scene/main/viewport.cpp8
-rw-r--r--scene/main/window.cpp31
-rw-r--r--scene/register_scene_types.cpp5
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.h2
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_stack_2d.h2
-rw-r--r--scene/resources/2d/tile_set.cpp48
-rw-r--r--scene/resources/2d/tile_set.h5
-rw-r--r--scene/resources/3d/importer_mesh.cpp103
-rw-r--r--scene/resources/3d/importer_mesh.h2
-rw-r--r--scene/resources/3d/shape_3d.cpp2
-rw-r--r--scene/resources/audio_stream_wav.cpp2
-rw-r--r--scene/resources/curve.cpp21
-rw-r--r--scene/resources/environment.cpp5
-rw-r--r--scene/resources/font.cpp34
-rw-r--r--scene/resources/mesh.cpp2
-rw-r--r--scene/resources/navigation_mesh.cpp4
-rw-r--r--scene/resources/packed_scene.cpp6
-rw-r--r--scene/resources/portable_compressed_texture.cpp4
-rw-r--r--scene/resources/resource_format_text.cpp33
-rw-r--r--scene/resources/resource_format_text.h1
-rw-r--r--scene/resources/surface_tool.cpp2
-rw-r--r--scene/resources/surface_tool.h8
-rw-r--r--scene/resources/text_paragraph.cpp45
-rw-r--r--scene/resources/text_paragraph.h6
-rw-r--r--scene/resources/texture_rd.cpp2
-rw-r--r--scene/resources/visual_shader.cpp6
70 files changed, 1806 insertions, 604 deletions
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index 21648bbc49..0b54d1e604 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -418,7 +418,7 @@ Vector2 PointLight2D::get_texture_offset() const {
}
PackedStringArray PointLight2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node2D::get_configuration_warnings();
+ PackedStringArray warnings = Light2D::get_configuration_warnings();
if (!texture.is_valid()) {
warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property."));
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index f6502a77e9..c6d1447ad8 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -167,8 +167,7 @@ void NavigationObstacle2D::_notification(int p_what) {
if (is_debug_enabled) {
RS::get_singleton()->canvas_item_clear(debug_canvas_item);
- Transform2D debug_transform = Transform2D(0.0, get_global_position());
- RS::get_singleton()->canvas_item_set_transform(debug_canvas_item, debug_transform);
+ RS::get_singleton()->canvas_item_set_transform(debug_canvas_item, Transform2D());
_update_fake_agent_radius_debug();
_update_static_obstacle_debug();
}
@@ -311,6 +310,25 @@ bool NavigationObstacle2D::get_carve_navigation_mesh() const {
return carve_navigation_mesh;
}
+PackedStringArray NavigationObstacle2D::get_configuration_warnings() const {
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
+
+ const Vector2 global_scale = get_global_scale();
+ if (global_scale.x < 0.001 || global_scale.y < 0.001) {
+ warnings.push_back(RTR("NavigationObstacle2D does not support negative or zero scaling."));
+ }
+
+ if (radius > 0.0 && !get_global_transform().is_conformal()) {
+ warnings.push_back(RTR("The agent radius can only be scaled uniformly. The largest value along the two axes of the global scale will be used to scale the radius. This value may change in unexpected ways when the node is rotated."));
+ }
+
+ if (radius > 0.0 && get_global_skew() != 0.0) {
+ warnings.push_back(RTR("Skew has no effect on the agent radius."));
+ }
+
+ return warnings;
+}
+
void NavigationObstacle2D::_update_map(RID p_map) {
map_current = p_map;
NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map);
@@ -327,8 +345,11 @@ void NavigationObstacle2D::_update_position(const Vector2 p_position) {
void NavigationObstacle2D::_update_fake_agent_radius_debug() {
if (radius > 0.0 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) {
Color debug_radius_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color();
-
- RS::get_singleton()->canvas_item_add_circle(debug_canvas_item, Vector2(), radius, debug_radius_color);
+ // Prevent non-positive scaling.
+ const Vector2 safe_scale = get_global_scale().abs().maxf(0.001);
+ // Agent radius is a scalar value and does not support non-uniform scaling, choose the largest axis.
+ const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
+ RS::get_singleton()->canvas_item_add_circle(debug_canvas_item, get_global_position(), scaling_max_value * radius, debug_radius_color);
}
}
#endif // DEBUG_ENABLED
@@ -370,7 +391,8 @@ void NavigationObstacle2D::_update_static_obstacle_debug() {
debug_obstacle_line_colors.resize(debug_obstacle_line_vertices.size());
debug_obstacle_line_colors.fill(debug_static_obstacle_edge_color);
- RS::get_singleton()->canvas_item_add_polyline(debug_canvas_item, debug_obstacle_line_vertices, debug_obstacle_line_colors, 4.0);
+ // Transforming the vertices directly instead of the canvas item in order to not affect the circle shape by non-uniform scales.
+ RS::get_singleton()->canvas_item_add_polyline(debug_canvas_item, get_global_transform().xform(debug_obstacle_line_vertices), debug_obstacle_line_colors, 4.0);
}
}
#endif // DEBUG_ENABLED
diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h
index 28c5f902e6..34e585142d 100644
--- a/scene/2d/navigation_obstacle_2d.h
+++ b/scene/2d/navigation_obstacle_2d.h
@@ -106,6 +106,8 @@ public:
void set_carve_navigation_mesh(bool p_enabled);
bool get_carve_navigation_mesh() const;
+ PackedStringArray get_configuration_warnings() const override;
+
private:
void _update_map(RID p_map);
void _update_position(const Vector2 p_position);
diff --git a/scene/2d/physics/rigid_body_2d.cpp b/scene/2d/physics/rigid_body_2d.cpp
index 402e5c8b95..4b1cde6b7a 100644
--- a/scene/2d/physics/rigid_body_2d.cpp
+++ b/scene/2d/physics/rigid_body_2d.cpp
@@ -641,7 +641,7 @@ void RigidBody2D::_notification(int p_what) {
PackedStringArray RigidBody2D::get_configuration_warnings() const {
Transform2D t = get_transform();
- PackedStringArray warnings = CollisionObject2D::get_configuration_warnings();
+ PackedStringArray warnings = PhysicsBody2D::get_configuration_warnings();
if (ABS(t.columns[0].length() - 1.0) > 0.05 || ABS(t.columns[1].length() - 1.0) > 0.05) {
warnings.push_back(RTR("Size changes to RigidBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."));
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index ddb0635f6b..20cbbc091a 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -75,7 +75,8 @@ void TileMap::_set_tile_map_data_using_compatibility_format(int p_layer, TileMap
for (int i = 0; i < c; i += offset) {
const uint8_t *ptr = (const uint8_t *)&r[i];
uint8_t local[12];
- for (int j = 0; j < ((p_format >= TileMapDataFormat::TILE_MAP_DATA_FORMAT_2) ? 12 : 8); j++) {
+ const int buffer_size = (format == TILE_MAP_DATA_FORMAT_2) ? 12 : 8;
+ for (int j = 0; j < buffer_size; j++) {
local[j] = ptr[j];
}
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index f737e23126..c4a2f35d31 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -823,6 +823,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer);
uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer);
uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer);
+ real_t physics_priority = tile_set->get_physics_layer_collision_priority(tile_set_physics_layer);
RID body = r_cell_data.bodies[tile_set_physics_layer];
if (tile_data->get_collision_polygons_count(tile_set_physics_layer) == 0) {
@@ -849,6 +850,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
ps->body_attach_object_instance_id(body, tile_map_node ? tile_map_node->get_instance_id() : get_instance_id());
ps->body_set_collision_layer(body, physics_layer);
ps->body_set_collision_mask(body, physics_mask);
+ ps->body_set_collision_priority(body, physics_priority);
ps->body_set_pickable(body, false);
ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer));
ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer));
@@ -1458,6 +1460,24 @@ void TileMapLayer::_clear_runtime_update_tile_data_for_cell(CellData &r_cell_dat
}
}
+void TileMapLayer::_update_cells_callback(bool p_force_cleanup) {
+ if (!GDVIRTUAL_IS_OVERRIDDEN(_update_cells)) {
+ return;
+ }
+
+ // Check if we should cleanup everything.
+ bool forced_cleanup = p_force_cleanup || !enabled || tile_set.is_null() || !is_visible_in_tree();
+
+ // List all the dirty cell's positions to notify script of cell updates.
+ TypedArray<Vector2i> dirty_cell_positions;
+ for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) {
+ CellData &cell_data = *cell_data_list_element->self();
+ dirty_cell_positions.push_back(cell_data.coords);
+ }
+
+ GDVIRTUAL_CALL(_update_cells, dirty_cell_positions, forced_cleanup);
+}
+
TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) const {
if (tile_set.is_null()) {
return TileSet::TerrainsPattern();
@@ -1671,6 +1691,10 @@ void TileMapLayer::_internal_update(bool p_force_cleanup) {
// This may add cells to the dirty list if a runtime modification has been notified.
_build_runtime_update_tile_data(p_force_cleanup);
+ // Callback for implementing custom subsystems.
+ // This may add to the dirty list if some cells are changed inside _update_cells.
+ _update_cells_callback(p_force_cleanup);
+
// Update all subsystems.
_rendering_update(p_force_cleanup);
_physics_update(p_force_cleanup);
@@ -1859,6 +1883,7 @@ void TileMapLayer::_bind_methods() {
GDVIRTUAL_BIND(_use_tile_data_runtime_update, "coords");
GDVIRTUAL_BIND(_tile_data_runtime_update, "coords", "tile_data");
+ GDVIRTUAL_BIND(_update_cells, "coords", "forced_cleanup");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "tile_map_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_tile_map_data_from_array", "get_tile_map_data_as_array");
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index 6cb481849c..912fa2be15 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -321,6 +321,7 @@ private:
bool _runtime_update_needs_all_cells_cleaned_up = false;
void _clear_runtime_update_tile_data();
void _clear_runtime_update_tile_data_for_cell(CellData &r_cell_data);
+ void _update_cells_callback(bool p_force_cleanup);
// Per-system methods.
#ifdef DEBUG_ENABLED
@@ -462,6 +463,7 @@ public:
void notify_runtime_tile_data_update();
GDVIRTUAL1R(bool, _use_tile_data_runtime_update, Vector2i);
GDVIRTUAL2(_tile_data_runtime_update, Vector2i, TileData *);
+ GDVIRTUAL2(_update_cells, TypedArray<Vector2i>, bool);
// --- Shortcuts to methods defined in TileSet ---
Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp
index b968f39ccb..220608a250 100644
--- a/scene/2d/touch_screen_button.cpp
+++ b/scene/2d/touch_screen_button.cpp
@@ -430,6 +430,6 @@ void TouchScreenButton::_bind_methods() {
}
TouchScreenButton::TouchScreenButton() {
- unit_rect = Ref<RectangleShape2D>(memnew(RectangleShape2D));
+ unit_rect.instantiate();
unit_rect->set_size(Vector2(1, 1));
}
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index 6b3510a72a..01f15137be 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -318,7 +318,7 @@ Ref<TriangleMesh> Label3D::generate_triangle_mesh() const {
facesw[j] = vtx;
}
- triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
+ triangle_mesh.instantiate();
triangle_mesh->create(faces);
return triangle_mesh;
diff --git a/scene/3d/look_at_modifier_3d.cpp b/scene/3d/look_at_modifier_3d.cpp
new file mode 100644
index 0000000000..ad33cd420a
--- /dev/null
+++ b/scene/3d/look_at_modifier_3d.cpp
@@ -0,0 +1,781 @@
+/**************************************************************************/
+/* look_at_modifier_3d.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 "look_at_modifier_3d.h"
+
+void LookAtModifier3D::_validate_property(PropertyInfo &p_property) const {
+ SkeletonModifier3D::_validate_property(p_property);
+
+ if (p_property.name == "bone" || p_property.name == "origin_bone") {
+ Skeleton3D *skeleton = get_skeleton();
+ if (skeleton) {
+ p_property.hint = PROPERTY_HINT_ENUM;
+ p_property.hint_string = skeleton->get_concatenated_bone_names();
+ } else {
+ p_property.hint = PROPERTY_HINT_NONE;
+ p_property.hint_string = "";
+ }
+ }
+
+ if (origin_from == ORIGIN_FROM_SPECIFIC_BONE) {
+ if (p_property.name == "origin_external_node") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+ } else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) {
+ if (p_property.name == "origin_bone") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+ } else {
+ if (p_property.name == "origin_external_node" || p_property.name == "origin_bone") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+ }
+
+ if ((!use_angle_limitation &&
+ (p_property.name == "symmetry_limitation" || p_property.name.ends_with("limit_angle") || p_property.name.ends_with("damp_threshold"))) ||
+ (!use_secondary_rotation && p_property.name.begins_with("secondary_")) ||
+ (!symmetry_limitation && (p_property.name == "primary_limit_angle" || p_property.name == "primary_damp_threshold" || p_property.name == "secondary_limit_angle" || p_property.name == "secondary_damp_threshold")) ||
+ (symmetry_limitation && (p_property.name.begins_with("primary_positive") || p_property.name.begins_with("primary_negative") || p_property.name.begins_with("secondary_positive") || (p_property.name.begins_with("secondary_negative"))))) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+}
+
+PackedStringArray LookAtModifier3D::get_configuration_warnings() const {
+ PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
+ if (get_axis_from_bone_axis(forward_axis) == primary_rotation_axis) {
+ warnings.push_back(RTR("Forward axis and primary rotation axis must not be parallel."));
+ }
+ return warnings;
+}
+
+void LookAtModifier3D::set_bone(int p_bone) {
+ bone = p_bone;
+}
+
+int LookAtModifier3D::get_bone() const {
+ return bone;
+}
+
+void LookAtModifier3D::set_forward_axis(BoneAxis p_axis) {
+ forward_axis = p_axis;
+ update_configuration_warnings();
+}
+
+LookAtModifier3D::BoneAxis LookAtModifier3D::get_forward_axis() const {
+ return forward_axis;
+}
+
+void LookAtModifier3D::set_primary_rotation_axis(Vector3::Axis p_axis) {
+ primary_rotation_axis = p_axis;
+ update_configuration_warnings();
+}
+
+Vector3::Axis LookAtModifier3D::get_primary_rotation_axis() const {
+ return primary_rotation_axis;
+}
+
+void LookAtModifier3D::set_use_secondary_rotation(bool p_enabled) {
+ use_secondary_rotation = p_enabled;
+ notify_property_list_changed();
+}
+
+bool LookAtModifier3D::is_using_secondary_rotation() const {
+ return use_secondary_rotation;
+}
+
+void LookAtModifier3D::set_target_node(const NodePath &p_target_node) {
+ if (target_node != p_target_node) {
+ init_transition();
+ }
+ target_node = p_target_node;
+}
+
+NodePath LookAtModifier3D::get_target_node() const {
+ return target_node;
+}
+
+// For origin settings.
+
+void LookAtModifier3D::set_origin_from(OriginFrom p_origin_from) {
+ origin_from = p_origin_from;
+ notify_property_list_changed();
+}
+
+LookAtModifier3D::OriginFrom LookAtModifier3D::get_origin_from() const {
+ return origin_from;
+}
+
+void LookAtModifier3D::set_origin_bone(int p_bone) {
+ origin_bone = p_bone;
+}
+
+int LookAtModifier3D::get_origin_bone() const {
+ return origin_bone;
+}
+
+void LookAtModifier3D::set_origin_external_node(const NodePath &p_external_node) {
+ origin_external_node = p_external_node;
+}
+
+NodePath LookAtModifier3D::get_origin_external_node() const {
+ return origin_external_node;
+}
+
+void LookAtModifier3D::set_origin_offset(const Vector3 &p_offset) {
+ origin_offset = p_offset;
+}
+
+Vector3 LookAtModifier3D::get_origin_offset() const {
+ return origin_offset;
+}
+
+void LookAtModifier3D::set_origin_safe_margin(float p_margin) {
+ origin_safe_margin = p_margin;
+}
+
+float LookAtModifier3D::get_origin_safe_margin() const {
+ return origin_safe_margin;
+}
+
+// For time-based interpolation.
+
+void LookAtModifier3D::set_duration(float p_duration) {
+ duration = p_duration;
+ if (Math::is_zero_approx(p_duration)) {
+ time_step = 0;
+ remaining = 0;
+ } else {
+ time_step = 1.0 / p_duration; // Cache to avoid division.
+ }
+}
+
+float LookAtModifier3D::get_duration() const {
+ return duration;
+}
+
+void LookAtModifier3D::set_transition_type(Tween::TransitionType p_transition_type) {
+ transition_type = p_transition_type;
+}
+
+Tween::TransitionType LookAtModifier3D::get_transition_type() const {
+ return transition_type;
+}
+
+void LookAtModifier3D::set_ease_type(Tween::EaseType p_ease_type) {
+ ease_type = p_ease_type;
+}
+
+Tween::EaseType LookAtModifier3D::get_ease_type() const {
+ return ease_type;
+}
+
+// For angle limitation.
+
+void LookAtModifier3D::set_use_angle_limitation(bool p_enabled) {
+ use_angle_limitation = p_enabled;
+ notify_property_list_changed();
+}
+
+bool LookAtModifier3D::is_using_angle_limitation() const {
+ return use_angle_limitation;
+}
+
+void LookAtModifier3D::set_symmetry_limitation(bool p_enabled) {
+ symmetry_limitation = p_enabled;
+ notify_property_list_changed();
+}
+
+bool LookAtModifier3D::is_limitation_symmetry() const {
+ return symmetry_limitation;
+}
+
+void LookAtModifier3D::set_primary_limit_angle(float p_angle) {
+ primary_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_primary_limit_angle() const {
+ return primary_limit_angle;
+}
+
+void LookAtModifier3D::set_primary_damp_threshold(float p_power) {
+ primary_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_primary_damp_threshold() const {
+ return primary_damp_threshold;
+}
+
+void LookAtModifier3D::set_primary_positive_limit_angle(float p_angle) {
+ primary_positive_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_primary_positive_limit_angle() const {
+ return primary_positive_limit_angle;
+}
+
+void LookAtModifier3D::set_primary_positive_damp_threshold(float p_power) {
+ primary_positive_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_primary_positive_damp_threshold() const {
+ return primary_positive_damp_threshold;
+}
+
+void LookAtModifier3D::set_primary_negative_limit_angle(float p_angle) {
+ primary_negative_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_primary_negative_limit_angle() const {
+ return primary_negative_limit_angle;
+}
+
+void LookAtModifier3D::set_primary_negative_damp_threshold(float p_power) {
+ primary_negative_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_primary_negative_damp_threshold() const {
+ return primary_negative_damp_threshold;
+}
+
+void LookAtModifier3D::set_secondary_limit_angle(float p_angle) {
+ secondary_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_secondary_limit_angle() const {
+ return secondary_limit_angle;
+}
+
+void LookAtModifier3D::set_secondary_damp_threshold(float p_power) {
+ secondary_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_secondary_damp_threshold() const {
+ return secondary_damp_threshold;
+}
+
+void LookAtModifier3D::set_secondary_positive_limit_angle(float p_angle) {
+ secondary_positive_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_secondary_positive_limit_angle() const {
+ return secondary_positive_limit_angle;
+}
+
+void LookAtModifier3D::set_secondary_positive_damp_threshold(float p_power) {
+ secondary_positive_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_secondary_positive_damp_threshold() const {
+ return secondary_positive_damp_threshold;
+}
+
+void LookAtModifier3D::set_secondary_negative_limit_angle(float p_angle) {
+ secondary_negative_limit_angle = p_angle;
+}
+
+float LookAtModifier3D::get_secondary_negative_limit_angle() const {
+ return secondary_negative_limit_angle;
+}
+
+void LookAtModifier3D::set_secondary_negative_damp_threshold(float p_power) {
+ secondary_negative_damp_threshold = p_power;
+}
+
+float LookAtModifier3D::get_secondary_negative_damp_threshold() const {
+ return secondary_negative_damp_threshold;
+}
+
+bool LookAtModifier3D::is_target_within_limitation() const {
+ return is_within_limitations;
+}
+
+float LookAtModifier3D::get_interpolation_remaining() const {
+ return remaining * duration;
+}
+
+bool LookAtModifier3D::is_interpolating() const {
+ return Math::is_zero_approx(remaining);
+}
+
+// General API.
+
+void LookAtModifier3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_target_node", "target_node"), &LookAtModifier3D::set_target_node);
+ ClassDB::bind_method(D_METHOD("get_target_node"), &LookAtModifier3D::get_target_node);
+
+ ClassDB::bind_method(D_METHOD("set_bone", "bone"), &LookAtModifier3D::set_bone);
+ ClassDB::bind_method(D_METHOD("get_bone"), &LookAtModifier3D::get_bone);
+ ClassDB::bind_method(D_METHOD("set_forward_axis", "forward_axis"), &LookAtModifier3D::set_forward_axis);
+ ClassDB::bind_method(D_METHOD("get_forward_axis"), &LookAtModifier3D::get_forward_axis);
+ ClassDB::bind_method(D_METHOD("set_primary_rotation_axis", "axis"), &LookAtModifier3D::set_primary_rotation_axis);
+ ClassDB::bind_method(D_METHOD("get_primary_rotation_axis"), &LookAtModifier3D::get_primary_rotation_axis);
+ ClassDB::bind_method(D_METHOD("set_use_secondary_rotation", "enabled"), &LookAtModifier3D::set_use_secondary_rotation);
+ ClassDB::bind_method(D_METHOD("is_using_secondary_rotation"), &LookAtModifier3D::is_using_secondary_rotation);
+ ClassDB::bind_method(D_METHOD("set_origin_safe_margin", "margin"), &LookAtModifier3D::set_origin_safe_margin);
+ ClassDB::bind_method(D_METHOD("get_origin_safe_margin"), &LookAtModifier3D::get_origin_safe_margin);
+
+ ClassDB::bind_method(D_METHOD("set_origin_from", "origin_from"), &LookAtModifier3D::set_origin_from);
+ ClassDB::bind_method(D_METHOD("get_origin_from"), &LookAtModifier3D::get_origin_from);
+ ClassDB::bind_method(D_METHOD("set_origin_bone", "bone"), &LookAtModifier3D::set_origin_bone);
+ ClassDB::bind_method(D_METHOD("get_origin_bone"), &LookAtModifier3D::get_origin_bone);
+ ClassDB::bind_method(D_METHOD("set_origin_external_node", "external_node"), &LookAtModifier3D::set_origin_external_node);
+ ClassDB::bind_method(D_METHOD("get_origin_external_node"), &LookAtModifier3D::get_origin_external_node);
+
+ ClassDB::bind_method(D_METHOD("set_origin_offset", "offset"), &LookAtModifier3D::set_origin_offset);
+ ClassDB::bind_method(D_METHOD("get_origin_offset"), &LookAtModifier3D::get_origin_offset);
+
+ ClassDB::bind_method(D_METHOD("set_duration", "duration"), &LookAtModifier3D::set_duration);
+ ClassDB::bind_method(D_METHOD("get_duration"), &LookAtModifier3D::get_duration);
+ ClassDB::bind_method(D_METHOD("set_transition_type", "transition_type"), &LookAtModifier3D::set_transition_type);
+ ClassDB::bind_method(D_METHOD("get_transition_type"), &LookAtModifier3D::get_transition_type);
+ ClassDB::bind_method(D_METHOD("set_ease_type", "ease_type"), &LookAtModifier3D::set_ease_type);
+ ClassDB::bind_method(D_METHOD("get_ease_type"), &LookAtModifier3D::get_ease_type);
+
+ ClassDB::bind_method(D_METHOD("set_use_angle_limitation", "enabled"), &LookAtModifier3D::set_use_angle_limitation);
+ ClassDB::bind_method(D_METHOD("is_using_angle_limitation"), &LookAtModifier3D::is_using_angle_limitation);
+ ClassDB::bind_method(D_METHOD("set_symmetry_limitation", "enabled"), &LookAtModifier3D::set_symmetry_limitation);
+ ClassDB::bind_method(D_METHOD("is_limitation_symmetry"), &LookAtModifier3D::is_limitation_symmetry);
+
+ ClassDB::bind_method(D_METHOD("set_primary_limit_angle", "angle"), &LookAtModifier3D::set_primary_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_primary_limit_angle"), &LookAtModifier3D::get_primary_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_primary_damp_threshold", "power"), &LookAtModifier3D::set_primary_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_primary_damp_threshold"), &LookAtModifier3D::get_primary_damp_threshold);
+
+ ClassDB::bind_method(D_METHOD("set_primary_positive_limit_angle", "angle"), &LookAtModifier3D::set_primary_positive_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_primary_positive_limit_angle"), &LookAtModifier3D::get_primary_positive_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_primary_positive_damp_threshold", "power"), &LookAtModifier3D::set_primary_positive_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_primary_positive_damp_threshold"), &LookAtModifier3D::get_primary_positive_damp_threshold);
+ ClassDB::bind_method(D_METHOD("set_primary_negative_limit_angle", "angle"), &LookAtModifier3D::set_primary_negative_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_primary_negative_limit_angle"), &LookAtModifier3D::get_primary_negative_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_primary_negative_damp_threshold", "power"), &LookAtModifier3D::set_primary_negative_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_primary_negative_damp_threshold"), &LookAtModifier3D::get_primary_negative_damp_threshold);
+
+ ClassDB::bind_method(D_METHOD("set_secondary_limit_angle", "angle"), &LookAtModifier3D::set_secondary_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_secondary_limit_angle"), &LookAtModifier3D::get_secondary_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_secondary_damp_threshold", "power"), &LookAtModifier3D::set_secondary_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_secondary_damp_threshold"), &LookAtModifier3D::get_secondary_damp_threshold);
+
+ ClassDB::bind_method(D_METHOD("set_secondary_positive_limit_angle", "angle"), &LookAtModifier3D::set_secondary_positive_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_secondary_positive_limit_angle"), &LookAtModifier3D::get_secondary_positive_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_secondary_positive_damp_threshold", "power"), &LookAtModifier3D::set_secondary_positive_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_secondary_positive_damp_threshold"), &LookAtModifier3D::get_secondary_positive_damp_threshold);
+ ClassDB::bind_method(D_METHOD("set_secondary_negative_limit_angle", "angle"), &LookAtModifier3D::set_secondary_negative_limit_angle);
+ ClassDB::bind_method(D_METHOD("get_secondary_negative_limit_angle"), &LookAtModifier3D::get_secondary_negative_limit_angle);
+ ClassDB::bind_method(D_METHOD("set_secondary_negative_damp_threshold", "power"), &LookAtModifier3D::set_secondary_negative_damp_threshold);
+ ClassDB::bind_method(D_METHOD("get_secondary_negative_damp_threshold"), &LookAtModifier3D::get_secondary_negative_damp_threshold);
+
+ ClassDB::bind_method(D_METHOD("get_interpolation_remaining"), &LookAtModifier3D::get_interpolation_remaining);
+ ClassDB::bind_method(D_METHOD("is_interpolating"), &LookAtModifier3D::is_interpolating);
+ ClassDB::bind_method(D_METHOD("is_target_within_limitation"), &LookAtModifier3D::is_target_within_limitation);
+
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_target_node", "get_target_node");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_ENUM, ""), "set_bone", "get_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "forward_axis", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z"), "set_forward_axis", "get_forward_axis");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "primary_rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z"), "set_primary_rotation_axis", "get_primary_rotation_axis");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_secondary_rotation"), "set_use_secondary_rotation", "is_using_secondary_rotation");
+
+ ADD_GROUP("Origin Settings", "origin_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_from", PROPERTY_HINT_ENUM, "Self,SpecificBone,ExternalNode"), "set_origin_from", "get_origin_from");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "origin_bone", PROPERTY_HINT_ENUM, ""), "set_origin_bone", "get_origin_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "origin_external_node", PROPERTY_HINT_NODE_TYPE, "Node3D"), "set_origin_external_node", "get_origin_external_node");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "origin_safe_margin", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_origin_safe_margin", "get_origin_safe_margin");
+
+ ADD_GROUP("Time Based Interpolation", "");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "duration", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:s"), "set_duration", "get_duration");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Quad,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_transition_type", "get_transition_type");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_ease_type", "get_ease_type");
+
+ ADD_GROUP("Angle Limitation", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_angle_limitation"), "set_use_angle_limitation", "is_using_angle_limitation");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symmetry_limitation"), "set_symmetry_limitation", "is_limitation_symmetry");
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_limit_angle", PROPERTY_HINT_RANGE, "0,360,0.01,radians_as_degrees"), "set_primary_limit_angle", "get_primary_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_damp_threshold", "get_primary_damp_threshold");
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_positive_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_primary_positive_limit_angle", "get_primary_positive_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_positive_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_positive_damp_threshold", "get_primary_positive_damp_threshold");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_negative_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_primary_negative_limit_angle", "get_primary_negative_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "primary_negative_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_primary_negative_damp_threshold", "get_primary_negative_damp_threshold");
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_limit_angle", PROPERTY_HINT_RANGE, "0,360,0.01,radians_as_degrees"), "set_secondary_limit_angle", "get_secondary_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_damp_threshold", "get_secondary_damp_threshold");
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_positive_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_secondary_positive_limit_angle", "get_secondary_positive_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_positive_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_positive_damp_threshold", "get_secondary_positive_damp_threshold");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_negative_limit_angle", PROPERTY_HINT_RANGE, "0,180,0.01,radians_as_degrees"), "set_secondary_negative_limit_angle", "get_secondary_negative_limit_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "secondary_negative_damp_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_secondary_negative_damp_threshold", "get_secondary_negative_damp_threshold");
+
+ BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_X);
+ BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_X);
+ BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_Y);
+ BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_Y);
+ BIND_ENUM_CONSTANT(BONE_AXIS_PLUS_Z);
+ BIND_ENUM_CONSTANT(BONE_AXIS_MINUS_Z);
+
+ BIND_ENUM_CONSTANT(ORIGIN_FROM_SELF);
+ BIND_ENUM_CONSTANT(ORIGIN_FROM_SPECIFIC_BONE);
+ BIND_ENUM_CONSTANT(ORIGIN_FROM_EXTERNAL_NODE);
+}
+
+void LookAtModifier3D::_process_modification() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ Skeleton3D *skeleton = get_skeleton();
+ if (!skeleton || bone < 0 || bone >= skeleton->get_bone_count()) {
+ return;
+ }
+
+ // Calculate bone rest space in the world.
+ Transform3D bone_rest_space;
+ int parent_bone = skeleton->get_bone_parent(bone);
+ if (parent_bone < 0) {
+ bone_rest_space = skeleton->get_global_transform() * skeleton->get_bone_rest(bone);
+ } else {
+ bone_rest_space = skeleton->get_global_transform() * skeleton->get_bone_global_pose(parent_bone) * skeleton->get_bone_rest(bone);
+ }
+
+ // Calculate forward_vector and destination.
+ is_within_limitations = true;
+ Vector3 prev_forward_vector = forward_vector;
+ Quaternion destination;
+ Node3D *target = Object::cast_to<Node3D>(get_node_or_null(target_node));
+ if (!target) {
+ destination = skeleton->get_bone_pose_rotation(bone);
+ } else {
+ Transform3D origin_tr;
+ if (origin_from == ORIGIN_FROM_SPECIFIC_BONE && origin_bone < skeleton->get_bone_count()) {
+ origin_tr = skeleton->get_global_transform() * skeleton->get_bone_global_pose(origin_bone);
+ } else if (origin_from == ORIGIN_FROM_EXTERNAL_NODE) {
+ Node3D *origin_src = Object::cast_to<Node3D>(get_node_or_null(origin_external_node));
+ if (origin_src) {
+ origin_tr = origin_src->get_global_transform();
+ } else {
+ origin_tr = bone_rest_space;
+ }
+ } else {
+ origin_tr = bone_rest_space;
+ }
+ forward_vector = bone_rest_space.basis.xform_inv((target->get_global_position() - origin_tr.translated_local(origin_offset).origin));
+ forward_vector_nrm = forward_vector.normalized();
+ if (forward_vector_nrm.abs().is_equal_approx(get_vector_from_axis(primary_rotation_axis))) {
+ destination = skeleton->get_bone_pose_rotation(bone);
+ forward_vector = Vector3(0, 0, 0); // The zero-vector to be used for checking in the line immediately below to avoid animation glitch.
+ } else {
+ destination = look_at_with_axes(skeleton->get_bone_rest(bone)).basis.get_rotation_quaternion();
+ }
+ }
+
+ // Detect flipping.
+ Vector3::Axis current_forward_axis = get_axis_from_bone_axis(forward_axis);
+ if (is_intersecting_axis(prev_forward_vector, forward_vector, current_forward_axis, secondary_rotation_axis) ||
+ is_intersecting_axis(prev_forward_vector, forward_vector, primary_rotation_axis, primary_rotation_axis, true) ||
+ is_intersecting_axis(prev_forward_vector, forward_vector, secondary_rotation_axis, current_forward_axis) ||
+ (prev_forward_vector != Vector3(0, 0, 0) && forward_vector == Vector3(0, 0, 0)) ||
+ (prev_forward_vector == Vector3(0, 0, 0) && forward_vector != Vector3(0, 0, 0))) {
+ init_transition();
+ } else if (use_angle_limitation && signbit(prev_forward_vector[secondary_rotation_axis]) != signbit(forward_vector[secondary_rotation_axis])) {
+ // Flipping by angle_limitation can be detected by sign of secondary rotation axes during forward_vector is rotated more than 90 degree from forward_axis (means dot production is negative).
+ Vector3 prev_forward_vector_nrm = forward_vector.normalized();
+ Vector3 rest_forward_vector = get_vector_from_bone_axis(forward_axis);
+ if (symmetry_limitation) {
+ if (!Math::is_equal_approx(primary_limit_angle, (float)Math_TAU) && prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && forward_vector_nrm.dot(rest_forward_vector) < 0) {
+ init_transition();
+ }
+ } else {
+ if (!Math::is_equal_approx(primary_positive_limit_angle + primary_negative_limit_angle, (float)Math_TAU) && prev_forward_vector_nrm.dot(rest_forward_vector) < 0 && forward_vector_nrm.dot(rest_forward_vector) < 0) {
+ init_transition();
+ }
+ }
+ }
+
+ // Do time-based interpolation.
+ if (remaining > 0) {
+ double delta = 0.0;
+ if (skeleton->get_modifier_callback_mode_process() == Skeleton3D::MODIFIER_CALLBACK_MODE_PROCESS_IDLE) {
+ delta = get_process_delta_time();
+ } else {
+ delta = get_physics_process_delta_time();
+ }
+ remaining = MAX(0, remaining - time_step * delta);
+ if (use_angle_limitation) {
+ // Interpolate through the rest same as AnimationTree blending for preventing to penetrate the bone into the body.
+ Quaternion rest = skeleton->get_bone_rest(bone).basis.get_rotation_quaternion();
+ float weight = Tween::run_equation(transition_type, ease_type, 1 - remaining, 0.0, 1.0, 1.0);
+ destination = rest * Quaternion().slerp(rest.inverse() * from_q, 1 - weight) * Quaternion().slerp(rest.inverse() * destination, weight);
+ } else {
+ destination = from_q.slerp(destination, Tween::run_equation(transition_type, ease_type, 1 - remaining, 0.0, 1.0, 1.0));
+ }
+ }
+
+ skeleton->set_bone_pose_rotation(bone, destination);
+ prev_q = destination;
+}
+
+bool LookAtModifier3D::is_intersecting_axis(const Vector3 &p_prev, const Vector3 &p_current, Vector3::Axis p_flipping_axis, Vector3::Axis p_check_axis, bool p_check_plane) const {
+ // Prevent that the angular velocity does not become too large.
+ // Check that is p_flipping_axis flipped nearby p_check_axis (close than origin_safe_margin) or not. If p_check_plane is true, check two axes of crossed plane.
+ if (p_check_plane) {
+ if (get_projection_vector(p_prev, p_check_axis).length() > origin_safe_margin && get_projection_vector(p_current, p_check_axis).length() > origin_safe_margin) {
+ return false;
+ }
+ } else if (Math::abs(p_prev[p_check_axis]) > origin_safe_margin && Math::abs(p_current[p_check_axis]) > origin_safe_margin) {
+ return false;
+ }
+
+ return signbit(p_prev[p_flipping_axis]) != signbit(p_current[p_flipping_axis]);
+}
+
+Vector3 LookAtModifier3D::get_basis_vector_from_bone_axis(const Basis &p_basis, LookAtModifier3D::BoneAxis p_axis) const {
+ Vector3 ret;
+ switch (p_axis) {
+ case BONE_AXIS_PLUS_X: {
+ ret = p_basis.get_column(0);
+ } break;
+ case BONE_AXIS_MINUS_X: {
+ ret = -p_basis.get_column(0);
+ } break;
+ case BONE_AXIS_PLUS_Y: {
+ ret = p_basis.get_column(1);
+ } break;
+ case BONE_AXIS_MINUS_Y: {
+ ret = -p_basis.get_column(1);
+ } break;
+ case BONE_AXIS_PLUS_Z: {
+ ret = p_basis.get_column(2);
+ } break;
+ case BONE_AXIS_MINUS_Z: {
+ ret = -p_basis.get_column(2);
+ } break;
+ }
+ return ret;
+}
+
+Vector3 LookAtModifier3D::get_vector_from_bone_axis(const LookAtModifier3D::BoneAxis &p_axis) const {
+ Vector3 ret;
+ switch (p_axis) {
+ case BONE_AXIS_PLUS_X: {
+ ret = Vector3(1, 0, 0);
+ } break;
+ case BONE_AXIS_MINUS_X: {
+ ret = Vector3(-1, 0, 0);
+ } break;
+ case BONE_AXIS_PLUS_Y: {
+ ret = Vector3(0, 1, 0);
+ } break;
+ case BONE_AXIS_MINUS_Y: {
+ ret = Vector3(0, -1, 0);
+ } break;
+ case BONE_AXIS_PLUS_Z: {
+ ret = Vector3(0, 0, 1);
+ } break;
+ case BONE_AXIS_MINUS_Z: {
+ ret = Vector3(0, 0, -1);
+ } break;
+ }
+ return ret;
+}
+
+Vector3 LookAtModifier3D::get_vector_from_axis(const Vector3::Axis &p_axis) const {
+ Vector3 ret;
+ switch (p_axis) {
+ case Vector3::AXIS_X: {
+ ret = Vector3(1, 0, 0);
+ } break;
+ case Vector3::AXIS_Y: {
+ ret = Vector3(0, 1, 0);
+ } break;
+ case Vector3::AXIS_Z: {
+ ret = Vector3(0, 0, 1);
+ } break;
+ }
+ return ret;
+}
+
+Vector3::Axis LookAtModifier3D::get_axis_from_bone_axis(BoneAxis p_axis) const {
+ Vector3::Axis ret = Vector3::AXIS_X;
+ switch (p_axis) {
+ case BONE_AXIS_PLUS_X:
+ case BONE_AXIS_MINUS_X: {
+ ret = Vector3::AXIS_X;
+ } break;
+ case BONE_AXIS_PLUS_Y:
+ case BONE_AXIS_MINUS_Y: {
+ ret = Vector3::AXIS_Y;
+ } break;
+ case BONE_AXIS_PLUS_Z:
+ case BONE_AXIS_MINUS_Z: {
+ ret = Vector3::AXIS_Z;
+ } break;
+ }
+ return ret;
+}
+
+Vector2 LookAtModifier3D::get_projection_vector(const Vector3 &p_vector, Vector3::Axis p_axis) const {
+ // NOTE: axis is swapped between 2D and 3D.
+ Vector2 ret;
+ switch (p_axis) {
+ case Vector3::AXIS_X: {
+ ret = Vector2(p_vector.z, p_vector.y);
+ } break;
+ case Vector3::AXIS_Y: {
+ ret = Vector2(p_vector.x, p_vector.z);
+ } break;
+ case Vector3::AXIS_Z: {
+ ret = Vector2(p_vector.y, p_vector.x);
+ } break;
+ }
+ return ret;
+}
+
+float LookAtModifier3D::remap_damped(float p_from, float p_to, float p_damp_threshold, float p_value) const {
+ float sign = signbit(p_value) ? -1.0f : 1.0f;
+ float abs_value = Math::abs(p_value);
+
+ if (Math::is_equal_approx(p_damp_threshold, 1.0f) || Math::is_zero_approx(p_to)) {
+ return sign * CLAMP(abs_value, p_from, p_to); // Avoid division by zero.
+ }
+
+ float value = Math::inverse_lerp(p_from, p_to, abs_value);
+
+ if (value <= p_damp_threshold) {
+ return sign * CLAMP(abs_value, p_from, p_to);
+ }
+
+ double limit = Math_PI;
+ double inv_to = 1.0 / p_to;
+ double end_x = limit * inv_to;
+ double position = abs_value * inv_to;
+ Vector2 start = Vector2(p_damp_threshold, p_damp_threshold);
+ Vector2 mid = Vector2(1.0, 1.0);
+ Vector2 end = Vector2(end_x, 1.0);
+ value = get_bspline_y(start, mid, end, position);
+
+ return sign * Math::lerp(p_from, p_to, value);
+}
+
+double LookAtModifier3D::get_bspline_y(const Vector2 &p_from, const Vector2 &p_control, const Vector2 &p_to, double p_x) const {
+ double a = p_from.x - 2.0 * p_control.x + p_to.x;
+ double b = -2.0 * p_from.x + 2.0 * p_control.x;
+ double c = p_from.x - p_x;
+ double t = 0.0;
+ if (Math::is_zero_approx(a)) {
+ t = -c / b; // Almost linear.
+ } else {
+ double discriminant = b * b - 4.0 * a * c;
+ double sqrt_discriminant = Math::sqrt(discriminant);
+ double e = 1.0 / (2.0 * a);
+ double t1 = (-b + sqrt_discriminant) * e;
+ t = (0.0 <= t1 && t1 <= 1.0) ? t1 : (-b - sqrt_discriminant) * e;
+ }
+ double u = 1.0 - t;
+ double y = u * u * p_from.y + 2.0 * u * t * p_control.y + t * t * p_to.y;
+ return y;
+}
+
+Transform3D LookAtModifier3D::look_at_with_axes(const Transform3D &p_rest) {
+ // Primary rotation by projection to 2D plane by xform_inv and picking elements.
+ Vector3 current_vector = get_basis_vector_from_bone_axis(p_rest.basis, forward_axis).normalized();
+ Vector2 src_vec2 = get_projection_vector(p_rest.basis.xform_inv(forward_vector_nrm), primary_rotation_axis).normalized();
+ Vector2 dst_vec2 = get_projection_vector(p_rest.basis.xform_inv(current_vector), primary_rotation_axis).normalized();
+ real_t calculated_angle = src_vec2.angle_to(dst_vec2);
+ Transform3D primary_result = p_rest.rotated_local(get_vector_from_axis(primary_rotation_axis), calculated_angle);
+ Transform3D current_result = primary_result; // primary_result will be used by calculation of secondary rotation, current_result is rotated by that.
+ float limit_angle = 0.0;
+ float damp_threshold = 0.0;
+
+ if (use_angle_limitation) {
+ if (symmetry_limitation) {
+ limit_angle = primary_limit_angle * 0.5f;
+ damp_threshold = primary_damp_threshold;
+ } else {
+ if (signbit(calculated_angle)) {
+ limit_angle = primary_negative_limit_angle;
+ damp_threshold = primary_negative_damp_threshold;
+ } else {
+ limit_angle = primary_positive_limit_angle;
+ damp_threshold = primary_positive_damp_threshold;
+ }
+ }
+ if (Math::abs(calculated_angle) > limit_angle) {
+ is_within_limitations = false;
+ }
+ calculated_angle = remap_damped(0, limit_angle, damp_threshold, calculated_angle);
+ current_result = p_rest.rotated_local(get_vector_from_axis(primary_rotation_axis), calculated_angle);
+ }
+
+ // Needs for detecting flipping even if use_secondary_rotation is false.
+ Vector3 secondary_plane = get_vector_from_bone_axis(forward_axis) + get_vector_from_axis(primary_rotation_axis);
+ secondary_rotation_axis = Math::is_zero_approx(secondary_plane.x) ? Vector3::AXIS_X : (Math::is_zero_approx(secondary_plane.y) ? Vector3::AXIS_Y : Vector3::AXIS_Z);
+
+ if (!use_secondary_rotation) {
+ return current_result;
+ }
+
+ // Secondary rotation by projection to 2D plane by xform_inv and picking elements.
+ current_vector = get_basis_vector_from_bone_axis(primary_result.basis, forward_axis).normalized();
+ src_vec2 = get_projection_vector(primary_result.basis.xform_inv(forward_vector_nrm), secondary_rotation_axis).normalized();
+ dst_vec2 = get_projection_vector(primary_result.basis.xform_inv(current_vector), secondary_rotation_axis).normalized();
+ calculated_angle = src_vec2.angle_to(dst_vec2);
+
+ if (use_angle_limitation) {
+ if (symmetry_limitation) {
+ limit_angle = secondary_limit_angle * 0.5f;
+ damp_threshold = secondary_damp_threshold;
+ } else {
+ if (signbit(calculated_angle)) {
+ limit_angle = secondary_negative_limit_angle;
+ damp_threshold = secondary_negative_damp_threshold;
+ } else {
+ limit_angle = secondary_positive_limit_angle;
+ damp_threshold = secondary_positive_damp_threshold;
+ }
+ }
+ if (Math::abs(calculated_angle) > limit_angle) {
+ is_within_limitations = false;
+ }
+ calculated_angle = remap_damped(0, limit_angle, damp_threshold, calculated_angle);
+ }
+
+ current_result = current_result.rotated_local(get_vector_from_axis(secondary_rotation_axis), calculated_angle);
+
+ return current_result;
+}
+
+void LookAtModifier3D::init_transition() {
+ if (Math::is_zero_approx(duration)) {
+ return;
+ }
+ from_q = prev_q;
+ remaining = 1.0;
+}
diff --git a/scene/3d/look_at_modifier_3d.h b/scene/3d/look_at_modifier_3d.h
new file mode 100644
index 0000000000..5f3c4e8b1c
--- /dev/null
+++ b/scene/3d/look_at_modifier_3d.h
@@ -0,0 +1,199 @@
+/**************************************************************************/
+/* look_at_modifier_3d.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 LOOK_AT_MODIFIER_3D_H
+#define LOOK_AT_MODIFIER_3D_H
+
+#include "scene/3d/skeleton_modifier_3d.h"
+#include "scene/animation/tween.h"
+
+class LookAtModifier3D : public SkeletonModifier3D {
+ GDCLASS(LookAtModifier3D, SkeletonModifier3D);
+
+public:
+ enum BoneAxis {
+ BONE_AXIS_PLUS_X,
+ BONE_AXIS_MINUS_X,
+ BONE_AXIS_PLUS_Y,
+ BONE_AXIS_MINUS_Y,
+ BONE_AXIS_PLUS_Z,
+ BONE_AXIS_MINUS_Z,
+ };
+
+ enum OriginFrom {
+ ORIGIN_FROM_SELF,
+ ORIGIN_FROM_SPECIFIC_BONE,
+ ORIGIN_FROM_EXTERNAL_NODE,
+ };
+
+private:
+ int bone = 0;
+
+ Vector3 forward_vector;
+ Vector3 forward_vector_nrm;
+ BoneAxis forward_axis = BONE_AXIS_PLUS_Z;
+ Vector3::Axis primary_rotation_axis = Vector3::AXIS_Y;
+ Vector3::Axis secondary_rotation_axis = Vector3::AXIS_X;
+ bool use_secondary_rotation = true;
+
+ OriginFrom origin_from = ORIGIN_FROM_SELF;
+ int origin_bone = -1;
+ NodePath origin_external_node;
+
+ Vector3 origin_offset;
+ float origin_safe_margin = 0.1;
+
+ NodePath target_node;
+
+ float duration = 0;
+ Tween::TransitionType transition_type = Tween::TRANS_LINEAR;
+ Tween::EaseType ease_type = Tween::EASE_IN;
+
+ bool use_angle_limitation = false;
+ bool symmetry_limitation = true;
+
+ float primary_limit_angle = Math_TAU;
+ float primary_damp_threshold = 1.0f;
+ float primary_positive_limit_angle = Math_PI;
+ float primary_positive_damp_threshold = 1.0f;
+ float primary_negative_limit_angle = Math_PI;
+ float primary_negative_damp_threshold = 1.0f;
+
+ float secondary_limit_angle = Math_TAU;
+ float secondary_damp_threshold = 1.0f;
+ float secondary_positive_limit_angle = Math_PI;
+ float secondary_positive_damp_threshold = 1.0f;
+ float secondary_negative_limit_angle = Math_PI;
+ float secondary_negative_damp_threshold = 1.0f;
+
+ bool is_within_limitations = false;
+
+ // For time-based interpolation.
+ Quaternion from_q;
+ Quaternion prev_q;
+
+ float remaining = 0;
+ float time_step = 1.0;
+
+ Vector3 get_basis_vector_from_bone_axis(const Basis &p_basis, BoneAxis p_axis) const;
+ Vector3 get_vector_from_bone_axis(const BoneAxis &p_axis) const;
+ Vector3 get_vector_from_axis(const Vector3::Axis &p_axis) const;
+ Vector3::Axis get_axis_from_bone_axis(BoneAxis p_axis) const;
+ Vector2 get_projection_vector(const Vector3 &p_vector, Vector3::Axis p_axis) const;
+ float remap_damped(float p_from, float p_to, float p_damp_threshold, float p_value) const;
+ double get_bspline_y(const Vector2 &p_from, const Vector2 &p_control, const Vector2 &p_to, double p_x) const;
+ bool is_intersecting_axis(const Vector3 &p_prev, const Vector3 &p_current, Vector3::Axis p_flipping_axis, Vector3::Axis p_check_axis, bool p_check_plane = false) const;
+
+ Transform3D look_at_with_axes(const Transform3D &p_rest);
+ void init_transition();
+
+protected:
+ virtual PackedStringArray get_configuration_warnings() const override;
+ void _validate_property(PropertyInfo &p_property) const;
+
+ static void _bind_methods();
+
+ virtual void _process_modification() override;
+
+public:
+ void set_bone(int p_bone);
+ int get_bone() const;
+
+ void set_forward_axis(BoneAxis p_axis);
+ BoneAxis get_forward_axis() const;
+ void set_primary_rotation_axis(Vector3::Axis p_axis);
+ Vector3::Axis get_primary_rotation_axis() const;
+ void set_use_secondary_rotation(bool p_enabled);
+ bool is_using_secondary_rotation() const;
+
+ void set_origin_from(OriginFrom p_origin_from);
+ OriginFrom get_origin_from() const;
+ void set_origin_bone(int p_bone);
+ int get_origin_bone() const;
+ void set_origin_external_node(const NodePath &p_external_node);
+ NodePath get_origin_external_node() const;
+
+ void set_origin_offset(const Vector3 &p_offset);
+ Vector3 get_origin_offset() const;
+ void set_origin_safe_margin(float p_margin);
+ float get_origin_safe_margin() const;
+
+ void set_target_node(const NodePath &p_target_node);
+ NodePath get_target_node() const;
+
+ void set_duration(float p_duration);
+ float get_duration() const;
+ void set_transition_type(Tween::TransitionType p_transition_type);
+ Tween::TransitionType get_transition_type() const;
+ void set_ease_type(Tween::EaseType p_ease_type);
+ Tween::EaseType get_ease_type() const;
+
+ void set_use_angle_limitation(bool p_enabled);
+ bool is_using_angle_limitation() const;
+ void set_symmetry_limitation(bool p_enabled);
+ bool is_limitation_symmetry() const;
+
+ void set_primary_limit_angle(float p_angle);
+ float get_primary_limit_angle() const;
+ void set_primary_damp_threshold(float p_power);
+ float get_primary_damp_threshold() const;
+
+ void set_primary_positive_limit_angle(float p_angle);
+ float get_primary_positive_limit_angle() const;
+ void set_primary_positive_damp_threshold(float p_power);
+ float get_primary_positive_damp_threshold() const;
+ void set_primary_negative_limit_angle(float p_angle);
+ float get_primary_negative_limit_angle() const;
+ void set_primary_negative_damp_threshold(float p_power);
+ float get_primary_negative_damp_threshold() const;
+
+ void set_secondary_limit_angle(float p_angle);
+ float get_secondary_limit_angle() const;
+ void set_secondary_damp_threshold(float p_power);
+ float get_secondary_damp_threshold() const;
+
+ void set_secondary_positive_limit_angle(float p_angle);
+ float get_secondary_positive_limit_angle() const;
+ void set_secondary_positive_damp_threshold(float p_power);
+ float get_secondary_positive_damp_threshold() const;
+ void set_secondary_negative_limit_angle(float p_angle);
+ float get_secondary_negative_limit_angle() const;
+ void set_secondary_negative_damp_threshold(float p_power);
+ float get_secondary_negative_damp_threshold() const;
+
+ float get_interpolation_remaining() const;
+ bool is_interpolating() const;
+ bool is_target_within_limitation() const;
+};
+
+VARIANT_ENUM_CAST(LookAtModifier3D::BoneAxis);
+VARIANT_ENUM_CAST(LookAtModifier3D::OriginFrom);
+
+#endif // LOOK_AT_MODIFIER_3D_H
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index 9242c2a2ea..faf138896a 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -1081,8 +1081,8 @@ void NavigationAgent3D::_update_debug_path() {
debug_path_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!debug_path_mesh.is_valid()) {
- debug_path_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_path_mesh.is_null()) {
+ debug_path_mesh.instantiate();
}
debug_path_mesh->clear_surfaces();
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index 0cce21b9d0..9e29384fc9 100644
--- a/scene/3d/navigation_link_3d.cpp
+++ b/scene/3d/navigation_link_3d.cpp
@@ -56,8 +56,8 @@ void NavigationLink3D::_update_debug_mesh() {
debug_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!debug_mesh.is_valid()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_mesh.is_null()) {
+ debug_mesh.instantiate();
}
RID nav_map = get_world_3d()->get_navigation_map();
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 2eb04a0054..68a6505897 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -92,30 +92,26 @@ void NavigationObstacle3D::_notification(int p_what) {
} else {
_update_map(RID());
}
- previous_transform = get_global_transform();
// need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents
NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
- _update_position(get_global_transform().origin);
+ _update_position(get_global_position());
set_physics_process_internal(true);
#ifdef DEBUG_ENABLED
- if ((NavigationServer3D::get_singleton()->get_debug_avoidance_enabled()) &&
- (NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius())) {
- _update_fake_agent_radius_debug();
- _update_static_obstacle_debug();
- }
+ _update_debug();
#endif // DEBUG_ENABLED
} break;
+#ifdef TOOLS_ENABLED
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+ update_gizmos();
+ } break;
+#endif // TOOLS_ENABLED
+
case NOTIFICATION_EXIT_TREE: {
set_physics_process_internal(false);
_update_map(RID());
#ifdef DEBUG_ENABLED
- if (fake_agent_radius_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false);
- }
- if (static_obstacle_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
- }
+ _update_debug();
#endif // DEBUG_ENABLED
} break;
@@ -151,20 +147,13 @@ void NavigationObstacle3D::_notification(int p_what) {
#ifdef DEBUG_ENABLED
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (is_inside_tree()) {
- if (fake_agent_radius_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, is_visible_in_tree());
- }
- if (static_obstacle_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, is_visible_in_tree());
- }
- }
+ _update_debug();
} break;
#endif // DEBUG_ENABLED
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_inside_tree()) {
- _update_position(get_global_transform().origin);
+ _update_position(get_global_position());
if (velocity_submitted) {
velocity_submitted = false;
@@ -175,15 +164,23 @@ void NavigationObstacle3D::_notification(int p_what) {
previous_velocity = velocity;
}
#ifdef DEBUG_ENABLED
- if (fake_agent_radius_debug_instance.is_valid() && radius > 0.0) {
- Transform3D debug_transform;
- debug_transform.origin = get_global_position();
- RS::get_singleton()->instance_set_transform(fake_agent_radius_debug_instance, debug_transform);
+ if (fake_agent_radius_debug_instance_rid.is_valid() && radius > 0.0) {
+ // Prevent non-positive scaling.
+ const Vector3 safe_scale = get_global_basis().get_scale().abs().maxf(0.001);
+ // Agent radius is a scalar value and does not support non-uniform scaling, choose the largest axis.
+ const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
+ const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value);
+ const Transform3D debug_transform = Transform3D(Basis().scaled(uniform_max_scale), get_global_position());
+
+ RS::get_singleton()->instance_set_transform(fake_agent_radius_debug_instance_rid, debug_transform);
}
- if (static_obstacle_debug_instance.is_valid() && get_vertices().size() > 0) {
- Transform3D debug_transform;
- debug_transform.origin = get_global_position();
- RS::get_singleton()->instance_set_transform(static_obstacle_debug_instance, debug_transform);
+ if (static_obstacle_debug_instance_rid.is_valid() && get_vertices().size() > 0) {
+ // Prevent non-positive scaling.
+ const Vector3 safe_scale = get_global_basis().get_scale().abs().maxf(0.001);
+ // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
+ const Transform3D debug_transform = Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), get_global_rotation().y), get_global_position());
+
+ RS::get_singleton()->instance_set_transform(static_obstacle_debug_instance_rid, debug_transform);
}
#endif // DEBUG_ENABLED
}
@@ -192,53 +189,79 @@ void NavigationObstacle3D::_notification(int p_what) {
}
NavigationObstacle3D::NavigationObstacle3D() {
- obstacle = NavigationServer3D::get_singleton()->obstacle_create();
+ NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
- NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, height);
- NavigationServer3D::get_singleton()->obstacle_set_radius(obstacle, radius);
- NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
- NavigationServer3D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
- NavigationServer3D::get_singleton()->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
- NavigationServer3D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
+ obstacle = ns3d->obstacle_create();
+
+ ns3d->obstacle_set_height(obstacle, height);
+ ns3d->obstacle_set_radius(obstacle, radius);
+ ns3d->obstacle_set_vertices(obstacle, vertices);
+ ns3d->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
+ ns3d->obstacle_set_use_3d_avoidance(obstacle, use_3d_avoidance);
+ ns3d->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
#ifdef DEBUG_ENABLED
- NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
- NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
+ RenderingServer *rs = RenderingServer::get_singleton();
+
+ fake_agent_radius_debug_mesh_rid = rs->mesh_create();
+ static_obstacle_debug_mesh_rid = rs->mesh_create();
+
+ fake_agent_radius_debug_instance_rid = rs->instance_create();
+ static_obstacle_debug_instance_rid = rs->instance_create();
+
+ rs->instance_set_base(fake_agent_radius_debug_instance_rid, fake_agent_radius_debug_mesh_rid);
+ rs->instance_set_base(static_obstacle_debug_instance_rid, static_obstacle_debug_mesh_rid);
+
+ ns3d->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
+ ns3d->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
_update_fake_agent_radius_debug();
_update_static_obstacle_debug();
#endif // DEBUG_ENABLED
+
+#ifdef TOOLS_ENABLED
+ set_notify_transform(true);
+#endif // TOOLS_ENABLED
}
NavigationObstacle3D::~NavigationObstacle3D() {
- ERR_FAIL_NULL(NavigationServer3D::get_singleton());
+ NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
+ ERR_FAIL_NULL(ns3d);
- NavigationServer3D::get_singleton()->free(obstacle);
+ ns3d->free(obstacle);
obstacle = RID();
#ifdef DEBUG_ENABLED
- NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
- NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
- if (fake_agent_radius_debug_instance.is_valid()) {
- RenderingServer::get_singleton()->free(fake_agent_radius_debug_instance);
+ ns3d->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug));
+ ns3d->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug));
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rs);
+ if (fake_agent_radius_debug_instance_rid.is_valid()) {
+ rs->free(fake_agent_radius_debug_instance_rid);
+ fake_agent_radius_debug_instance_rid = RID();
}
- if (fake_agent_radius_debug_mesh.is_valid()) {
- RenderingServer::get_singleton()->free(fake_agent_radius_debug_mesh->get_rid());
+ if (fake_agent_radius_debug_mesh_rid.is_valid()) {
+ rs->free(fake_agent_radius_debug_mesh_rid);
+ fake_agent_radius_debug_mesh_rid = RID();
}
-
- if (static_obstacle_debug_instance.is_valid()) {
- RenderingServer::get_singleton()->free(static_obstacle_debug_instance);
+ if (static_obstacle_debug_instance_rid.is_valid()) {
+ rs->free(static_obstacle_debug_instance_rid);
+ static_obstacle_debug_instance_rid = RID();
}
- if (static_obstacle_debug_mesh.is_valid()) {
- RenderingServer::get_singleton()->free(static_obstacle_debug_mesh->get_rid());
+ if (static_obstacle_debug_mesh_rid.is_valid()) {
+ rs->free(static_obstacle_debug_mesh_rid);
+ static_obstacle_debug_mesh_rid = RID();
}
#endif // DEBUG_ENABLED
}
void NavigationObstacle3D::set_vertices(const Vector<Vector3> &p_vertices) {
vertices = p_vertices;
+
NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
#ifdef DEBUG_ENABLED
_update_static_obstacle_debug();
+ update_gizmos();
#endif // DEBUG_ENABLED
}
@@ -270,6 +293,7 @@ void NavigationObstacle3D::set_radius(real_t p_radius) {
#ifdef DEBUG_ENABLED
_update_fake_agent_radius_debug();
+ update_gizmos();
#endif // DEBUG_ENABLED
}
@@ -284,6 +308,7 @@ void NavigationObstacle3D::set_height(real_t p_height) {
#ifdef DEBUG_ENABLED
_update_static_obstacle_debug();
+ update_gizmos();
#endif // DEBUG_ENABLED
}
@@ -354,6 +379,25 @@ bool NavigationObstacle3D::get_carve_navigation_mesh() const {
return carve_navigation_mesh;
}
+PackedStringArray NavigationObstacle3D::get_configuration_warnings() const {
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
+
+ if (get_global_rotation().x != 0.0 || get_global_rotation().z != 0.0) {
+ warnings.push_back(RTR("NavigationObstacle3D only takes global rotation around the y-axis into account. Rotations around the x-axis or z-axis might lead to unexpected results."));
+ }
+
+ const Vector3 global_scale = get_global_basis().get_scale();
+ if (global_scale.x < 0.001 || global_scale.y < 0.001 || global_scale.z < 0.001) {
+ warnings.push_back(RTR("NavigationObstacle3D does not support negative or zero scaling."));
+ }
+
+ if (radius > 0.0 && !get_global_basis().is_conformal()) {
+ warnings.push_back(RTR("The agent radius can only be scaled uniformly. The largest scale value along the three axes will be used."));
+ }
+
+ return warnings;
+}
+
void NavigationObstacle3D::_update_map(RID p_map) {
NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map);
map_current = p_map;
@@ -369,30 +413,43 @@ void NavigationObstacle3D::_update_use_3d_avoidance(bool p_use_3d_avoidance) {
}
#ifdef DEBUG_ENABLED
+void NavigationObstacle3D::_update_debug() {
+ RenderingServer *rs = RenderingServer::get_singleton();
+ if (is_inside_tree()) {
+ rs->instance_set_visible(fake_agent_radius_debug_instance_rid, is_visible_in_tree());
+ rs->instance_set_visible(static_obstacle_debug_instance_rid, is_visible_in_tree());
+ rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, get_world_3d()->get_scenario());
+ rs->instance_set_scenario(static_obstacle_debug_instance_rid, get_world_3d()->get_scenario());
+ rs->instance_set_transform(fake_agent_radius_debug_instance_rid, Transform3D(Basis(), get_global_position()));
+ rs->instance_set_transform(static_obstacle_debug_instance_rid, Transform3D(Basis(), get_global_position()));
+ _update_fake_agent_radius_debug();
+ _update_static_obstacle_debug();
+ } else {
+ rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
+ rs->mesh_clear(static_obstacle_debug_mesh_rid);
+ rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, RID());
+ rs->instance_set_scenario(static_obstacle_debug_instance_rid, RID());
+ }
+}
+
void NavigationObstacle3D::_update_fake_agent_radius_debug() {
+ NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
+ RenderingServer *rs = RenderingServer::get_singleton();
+
bool is_debug_enabled = false;
if (Engine::get_singleton()->is_editor_hint()) {
is_debug_enabled = true;
- } else if (NavigationServer3D::get_singleton()->get_debug_enabled() &&
- NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() &&
- NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) {
+ } else if (ns3d->get_debug_enabled() &&
+ ns3d->get_debug_avoidance_enabled() &&
+ ns3d->get_debug_navigation_avoidance_enable_obstacles_radius()) {
is_debug_enabled = true;
}
- if (is_debug_enabled == false) {
- if (fake_agent_radius_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false);
- }
- return;
- }
+ rs->mesh_clear(fake_agent_radius_debug_mesh_rid);
- if (!fake_agent_radius_debug_instance.is_valid()) {
- fake_agent_radius_debug_instance = RenderingServer::get_singleton()->instance_create();
- }
- if (!fake_agent_radius_debug_mesh.is_valid()) {
- fake_agent_radius_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (!is_debug_enabled) {
+ return;
}
- fake_agent_radius_debug_mesh->clear_surfaces();
Vector<Vector3> face_vertex_array;
Vector<int> face_indices_array;
@@ -447,147 +504,106 @@ void NavigationObstacle3D::_update_fake_agent_radius_debug() {
face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array;
- fake_agent_radius_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array);
- Ref<StandardMaterial3D> face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_material();
- fake_agent_radius_debug_mesh->surface_set_material(0, face_material);
+ rs->mesh_add_surface_from_arrays(fake_agent_radius_debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array);
+
+ Ref<StandardMaterial3D> face_material = ns3d->get_debug_navigation_avoidance_obstacles_radius_material();
+ rs->instance_set_surface_override_material(fake_agent_radius_debug_instance_rid, 0, face_material->get_rid());
- RS::get_singleton()->instance_set_base(fake_agent_radius_debug_instance, fake_agent_radius_debug_mesh->get_rid());
if (is_inside_tree()) {
- RS::get_singleton()->instance_set_scenario(fake_agent_radius_debug_instance, get_world_3d()->get_scenario());
- RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, is_visible_in_tree());
+ rs->instance_set_scenario(fake_agent_radius_debug_instance_rid, get_world_3d()->get_scenario());
+ rs->instance_set_visible(fake_agent_radius_debug_instance_rid, is_visible_in_tree());
}
}
#endif // DEBUG_ENABLED
#ifdef DEBUG_ENABLED
void NavigationObstacle3D::_update_static_obstacle_debug() {
- bool is_debug_enabled = false;
if (Engine::get_singleton()->is_editor_hint()) {
- is_debug_enabled = true;
- } else if (NavigationServer3D::get_singleton()->get_debug_enabled() &&
- NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() &&
- NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) {
- is_debug_enabled = true;
- }
-
- if (is_debug_enabled == false) {
- if (static_obstacle_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
- }
+ // Don't update inside Editor as Node3D gizmo takes care of this.
return;
}
- if (vertices.size() < 3) {
- if (static_obstacle_debug_instance.is_valid()) {
- RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false);
- }
- return;
- }
-
- if (!static_obstacle_debug_instance.is_valid()) {
- static_obstacle_debug_instance = RenderingServer::get_singleton()->instance_create();
- }
- if (!static_obstacle_debug_mesh.is_valid()) {
- static_obstacle_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
- }
- static_obstacle_debug_mesh->clear_surfaces();
-
- Vector<Vector2> polygon_2d_vertices;
- polygon_2d_vertices.resize(vertices.size());
- Vector2 *polygon_2d_vertices_ptr = polygon_2d_vertices.ptrw();
+ NavigationServer3D *ns3d = NavigationServer3D::get_singleton();
+ RenderingServer *rs = RenderingServer::get_singleton();
- for (int i = 0; i < vertices.size(); ++i) {
- Vector3 obstacle_vertex = vertices[i];
- Vector2 obstacle_vertex_2d = Vector2(obstacle_vertex.x, obstacle_vertex.z);
- polygon_2d_vertices_ptr[i] = obstacle_vertex_2d;
+ bool is_debug_enabled = false;
+ if (ns3d->get_debug_enabled() &&
+ ns3d->get_debug_avoidance_enabled() &&
+ ns3d->get_debug_navigation_avoidance_enable_obstacles_static()) {
+ is_debug_enabled = true;
}
- Vector<int> triangulated_polygon_2d_indices = Geometry2D::triangulate_polygon(polygon_2d_vertices);
+ rs->mesh_clear(static_obstacle_debug_mesh_rid);
- if (triangulated_polygon_2d_indices.is_empty()) {
- // failed triangulation
+ if (!is_debug_enabled) {
return;
}
- bool obstacle_pushes_inward = Geometry2D::is_polygon_clockwise(polygon_2d_vertices);
+ const int vertex_count = vertices.size();
- Vector<Vector3> face_vertex_array;
- Vector<int> face_indices_array;
-
- face_vertex_array.resize(polygon_2d_vertices.size());
- face_indices_array.resize(triangulated_polygon_2d_indices.size());
-
- Vector3 *face_vertex_array_ptr = face_vertex_array.ptrw();
- int *face_indices_array_ptr = face_indices_array.ptrw();
-
- for (int i = 0; i < triangulated_polygon_2d_indices.size(); ++i) {
- int vertex_index = triangulated_polygon_2d_indices[i];
- const Vector2 &vertex_2d = polygon_2d_vertices[vertex_index];
- Vector3 vertex_3d = Vector3(vertex_2d.x, 0.0, vertex_2d.y);
- face_vertex_array_ptr[vertex_index] = vertex_3d;
- face_indices_array_ptr[i] = vertex_index;
+ if (vertex_count < 3) {
+ if (static_obstacle_debug_instance_rid.is_valid()) {
+ rs->instance_set_visible(static_obstacle_debug_instance_rid, false);
+ }
+ return;
}
- Array face_mesh_array;
- face_mesh_array.resize(Mesh::ARRAY_MAX);
- face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
- face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array;
-
- static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array);
-
Vector<Vector3> edge_vertex_array;
+ edge_vertex_array.resize(vertex_count * 8);
- for (int i = 0; i < polygon_2d_vertices.size(); ++i) {
- int from_index = i - 1;
- int to_index = i;
+ Vector3 *edge_vertex_array_ptrw = edge_vertex_array.ptrw();
- if (i == 0) {
- from_index = polygon_2d_vertices.size() - 1;
- }
+ int vertex_index = 0;
- const Vector2 &vertex_2d_from = polygon_2d_vertices[from_index];
- const Vector2 &vertex_2d_to = polygon_2d_vertices[to_index];
+ for (int i = 0; i < vertex_count; i++) {
+ Vector3 point = vertices[i];
+ Vector3 next_point = vertices[(i + 1) % vertex_count];
- Vector3 vertex_3d_ground_from = Vector3(vertex_2d_from.x, 0.0, vertex_2d_from.y);
- Vector3 vertex_3d_ground_to = Vector3(vertex_2d_to.x, 0.0, vertex_2d_to.y);
+ Vector3 direction = next_point.direction_to(point);
+ Vector3 arrow_dir = direction.cross(Vector3(0.0, 1.0, 0.0));
+ Vector3 edge_middle = point + ((next_point - point) * 0.5);
- edge_vertex_array.push_back(vertex_3d_ground_from);
- edge_vertex_array.push_back(vertex_3d_ground_to);
+ edge_vertex_array_ptrw[vertex_index++] = edge_middle;
+ edge_vertex_array_ptrw[vertex_index++] = edge_middle + (arrow_dir * 0.5);
- Vector3 vertex_3d_height_from = Vector3(vertex_2d_from.x, height, vertex_2d_from.y);
- Vector3 vertex_3d_height_to = Vector3(vertex_2d_to.x, height, vertex_2d_to.y);
+ edge_vertex_array_ptrw[vertex_index++] = point;
+ edge_vertex_array_ptrw[vertex_index++] = next_point;
- edge_vertex_array.push_back(vertex_3d_height_from);
- edge_vertex_array.push_back(vertex_3d_height_to);
+ edge_vertex_array_ptrw[vertex_index++] = Vector3(point.x, height, point.z);
+ edge_vertex_array_ptrw[vertex_index++] = Vector3(next_point.x, height, next_point.z);
- edge_vertex_array.push_back(vertex_3d_ground_from);
- edge_vertex_array.push_back(vertex_3d_height_from);
+ edge_vertex_array_ptrw[vertex_index++] = point;
+ edge_vertex_array_ptrw[vertex_index++] = Vector3(point.x, height, point.z);
}
Array edge_mesh_array;
edge_mesh_array.resize(Mesh::ARRAY_MAX);
edge_mesh_array[Mesh::ARRAY_VERTEX] = edge_vertex_array;
- static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, edge_mesh_array);
+ rs->mesh_add_surface_from_arrays(static_obstacle_debug_mesh_rid, RS::PRIMITIVE_LINES, edge_mesh_array);
+
+ Vector<Vector2> polygon_2d_vertices;
+ polygon_2d_vertices.resize(vertex_count);
+ for (int i = 0; i < vertex_count; i++) {
+ const Vector3 &vert = vertices[i];
+ polygon_2d_vertices.write[i] = Vector2(vert.x, vert.z);
+ }
+
+ Vector<int> triangulated_polygon_2d_indices = Geometry2D::triangulate_polygon(polygon_2d_vertices);
- Ref<StandardMaterial3D> face_material;
Ref<StandardMaterial3D> edge_material;
- if (obstacle_pushes_inward) {
- face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_material();
- edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material();
+ if (triangulated_polygon_2d_indices.is_empty()) {
+ edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material();
} else {
- face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_material();
- edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material();
+ edge_material = ns3d->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material();
}
- static_obstacle_debug_mesh->surface_set_material(0, face_material);
- static_obstacle_debug_mesh->surface_set_material(1, edge_material);
+ rs->instance_set_surface_override_material(static_obstacle_debug_instance_rid, 0, edge_material->get_rid());
- RS::get_singleton()->instance_set_base(static_obstacle_debug_instance, static_obstacle_debug_mesh->get_rid());
if (is_inside_tree()) {
- RS::get_singleton()->instance_set_scenario(static_obstacle_debug_instance, get_world_3d()->get_scenario());
- RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, is_visible_in_tree());
+ rs->instance_set_scenario(static_obstacle_debug_instance_rid, get_world_3d()->get_scenario());
+ rs->instance_set_visible(static_obstacle_debug_instance_rid, is_visible_in_tree());
}
}
#endif // DEBUG_ENABLED
diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h
index 99288fc59e..99a5770f02 100644
--- a/scene/3d/navigation_obstacle_3d.h
+++ b/scene/3d/navigation_obstacle_3d.h
@@ -51,8 +51,6 @@ class NavigationObstacle3D : public Node3D {
bool use_3d_avoidance = false;
- Transform3D previous_transform;
-
Vector3 velocity;
Vector3 previous_velocity;
bool velocity_submitted = false;
@@ -61,13 +59,14 @@ class NavigationObstacle3D : public Node3D {
bool carve_navigation_mesh = false;
#ifdef DEBUG_ENABLED
- RID fake_agent_radius_debug_instance;
- Ref<ArrayMesh> fake_agent_radius_debug_mesh;
+ RID fake_agent_radius_debug_instance_rid;
+ RID fake_agent_radius_debug_mesh_rid;
- RID static_obstacle_debug_instance;
- Ref<ArrayMesh> static_obstacle_debug_mesh;
+ RID static_obstacle_debug_instance_rid;
+ RID static_obstacle_debug_mesh_rid;
private:
+ void _update_debug();
void _update_fake_agent_radius_debug();
void _update_static_obstacle_debug();
#endif // DEBUG_ENABLED
@@ -117,6 +116,8 @@ public:
void set_carve_navigation_mesh(bool p_enabled);
bool get_carve_navigation_mesh() const;
+ PackedStringArray get_configuration_warnings() const override;
+
private:
void _update_map(RID p_map);
void _update_position(const Vector3 p_position);
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index c0c254e7ed..2d67e4334e 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -492,8 +492,8 @@ void NavigationRegion3D::_update_debug_mesh() {
debug_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!debug_mesh.is_valid()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_mesh.is_null()) {
+ debug_mesh.instantiate();
}
debug_mesh->clear_surfaces();
@@ -669,8 +669,8 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() {
debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!debug_edge_connections_mesh.is_valid()) {
- debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_edge_connections_mesh.is_null()) {
+ debug_edge_connections_mesh.instantiate();
}
debug_edge_connections_mesh->clear_surfaces();
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 64259a24b0..00c83101b9 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -88,8 +88,8 @@ void Path3D::_update_debug_mesh() {
return;
}
- if (!debug_mesh.is_valid()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_mesh.is_null()) {
+ debug_mesh.instantiate();
}
if (!(curve.is_valid())) {
diff --git a/scene/3d/physics/ray_cast_3d.cpp b/scene/3d/physics/ray_cast_3d.cpp
index a9272388c1..b9159f072b 100644
--- a/scene/3d/physics/ray_cast_3d.cpp
+++ b/scene/3d/physics/ray_cast_3d.cpp
@@ -464,7 +464,7 @@ void RayCast3D::_create_debug_shape() {
}
if (debug_mesh.is_null()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ debug_mesh.instantiate();
}
}
diff --git a/scene/3d/physics/rigid_body_3d.cpp b/scene/3d/physics/rigid_body_3d.cpp
index 275e8cd7a9..6c8cea1ac3 100644
--- a/scene/3d/physics/rigid_body_3d.cpp
+++ b/scene/3d/physics/rigid_body_3d.cpp
@@ -659,7 +659,7 @@ void RigidBody3D::_reload_physics_characteristics() {
}
PackedStringArray RigidBody3D::get_configuration_warnings() const {
- PackedStringArray warnings = CollisionObject3D::get_configuration_warnings();
+ PackedStringArray warnings = PhysicsBody3D::get_configuration_warnings();
Vector3 scale = get_transform().get_basis().get_scale();
if (ABS(scale.x - 1.0) > 0.05 || ABS(scale.y - 1.0) > 0.05 || ABS(scale.z - 1.0) > 0.05) {
diff --git a/scene/3d/physics/shape_cast_3d.cpp b/scene/3d/physics/shape_cast_3d.cpp
index 8ad651fdf5..19c74bc925 100644
--- a/scene/3d/physics/shape_cast_3d.cpp
+++ b/scene/3d/physics/shape_cast_3d.cpp
@@ -546,7 +546,7 @@ void ShapeCast3D::_create_debug_shape() {
}
if (debug_mesh.is_null()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ debug_mesh.instantiate();
}
}
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index c1c992588b..b51d8a3438 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -183,17 +183,17 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
for (uint32_t i = 0; i < bones.size(); i++) {
- const String prep = vformat("%s/%d/", PNAME("bones"), i);
- p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + PNAME("rest"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::BOOL, prep + PNAME("enabled"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ const String prep = vformat("%s/%d/", "bones", i);
+ p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
for (const KeyValue<StringName, Variant> &K : bones[i].metadata) {
- PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + PNAME("bone_meta/") + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
+ PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + "bone_meta/" + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
p_list->push_back(pi);
}
}
@@ -1032,6 +1032,10 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
return skin_ref;
}
+void Skeleton3D::force_update_deferred() {
+ _make_dirty();
+}
+
void Skeleton3D::force_update_all_dirty_bones() {
if (!dirty) {
return;
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index ecfe095f1d..6c51c172af 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -162,7 +162,7 @@ private:
Vector<int> parentless_bones;
AHashMap<String, int> name_to_bone_index;
- mutable StringName concatenated_bone_names = StringName();
+ mutable StringName concatenated_bone_names;
void _update_bone_names() const;
void _make_dirty();
@@ -284,6 +284,7 @@ public:
void force_update_all_dirty_bones();
void force_update_all_bone_transforms();
void force_update_bone_children_transforms(int bone_idx);
+ void force_update_deferred();
void set_modifier_callback_mode_process(ModifierCallbackModeProcess p_mode);
ModifierCallbackModeProcess get_modifier_callback_mode_process() const;
diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h
index 5d6020194e..94145a6915 100644
--- a/scene/3d/skeleton_ik_3d.h
+++ b/scene/3d/skeleton_ik_3d.h
@@ -131,7 +131,7 @@ class SkeletonIK3D : public SkeletonModifier3D {
real_t min_distance = 0.01;
int max_iterations = 10;
- Variant target_node_override_ref = Variant();
+ Variant target_node_override_ref;
FabrikInverseKinematic::Task *task = nullptr;
#ifndef DISABLE_DEPRECATED
diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp
index d5c603112e..e8a8e517a2 100644
--- a/scene/3d/skeleton_modifier_3d.cpp
+++ b/scene/3d/skeleton_modifier_3d.cpp
@@ -75,6 +75,17 @@ void SkeletonModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new)
//
}
+void SkeletonModifier3D::_force_update_skeleton_skin() {
+ if (!is_inside_tree()) {
+ return;
+ }
+ Skeleton3D *skeleton = get_skeleton();
+ if (!skeleton) {
+ return;
+ }
+ skeleton->force_update_deferred();
+}
+
/* Process */
void SkeletonModifier3D::set_active(bool p_active) {
@@ -83,6 +94,7 @@ void SkeletonModifier3D::set_active(bool p_active) {
}
active = p_active;
_set_active(active);
+ _force_update_skeleton_skin();
}
bool SkeletonModifier3D::is_active() const {
@@ -119,6 +131,10 @@ void SkeletonModifier3D::_notification(int p_what) {
case NOTIFICATION_PARENTED: {
_update_skeleton();
} break;
+ case NOTIFICATION_EXIT_TREE:
+ case NOTIFICATION_UNPARENTED: {
+ _force_update_skeleton_skin();
+ } break;
}
}
diff --git a/scene/3d/skeleton_modifier_3d.h b/scene/3d/skeleton_modifier_3d.h
index d00a1e94a9..728b000ff5 100644
--- a/scene/3d/skeleton_modifier_3d.h
+++ b/scene/3d/skeleton_modifier_3d.h
@@ -50,6 +50,7 @@ protected:
void _update_skeleton();
void _update_skeleton_path();
+ void _force_update_skeleton_skin();
virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 42460eec4c..6e3ada83ad 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -470,7 +470,7 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const {
facesw[j] = vtx;
}
- triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
+ triangle_mesh.instantiate();
triangle_mesh->create(faces);
return triangle_mesh;
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index b87285ed74..377bc0eefe 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -30,6 +30,7 @@
#include "scene_debugger.h"
+#include "core/debugger/debugger_marshalls.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "core/object/script_language.h"
@@ -93,6 +94,13 @@ void SceneDebugger::deinitialize() {
}
#ifdef DEBUG_ENABLED
+void SceneDebugger::_handle_input(const Ref<InputEvent> &p_event, const Ref<Shortcut> &p_shortcut) {
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed() && !k->is_echo() && p_shortcut->matches_event(k)) {
+ EngineDebugger::get_singleton()->send_message("request_quit", Array());
+ }
+}
+
Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
SceneTree *scene_tree = SceneTree::get_singleton();
if (!scene_tree) {
@@ -109,7 +117,10 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
}
r_captured = true;
- if (p_msg == "request_scene_tree") { // Scene tree
+ if (p_msg == "setup_scene") {
+ SceneTree::get_singleton()->get_root()->connect(SceneStringName(window_input), callable_mp_static(SceneDebugger::_handle_input).bind(DebuggerMarshalls::deserialize_key_shortcut(p_args)));
+
+ } else if (p_msg == "request_scene_tree") { // Scene tree
live_editor->_send_tree();
} else if (p_msg == "save_node") { // Save node.
@@ -147,7 +158,6 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
Transform2D transform = p_args[0];
scene_tree->get_root()->set_canvas_transform_override(transform);
-
runtime_node_select->_queue_selection_update();
#ifndef _3D_DISABLED
@@ -164,16 +174,19 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
scene_tree->get_root()->set_camera_3d_override_orthogonal(size_or_fov, depth_near, depth_far);
}
scene_tree->get_root()->set_camera_3d_override_transform(transform);
-
runtime_node_select->_queue_selection_update();
#endif // _3D_DISABLED
} else if (p_msg == "set_object_property") {
ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
_set_object_property(p_args[0], p_args[1], p_args[2]);
-
runtime_node_select->_queue_selection_update();
+ } else if (p_msg == "reload_cached_files") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ PackedStringArray files = p_args[0];
+ reload_cached_files(files);
+
} else if (p_msg.begins_with("live_")) { /// Live Edit
if (p_msg == "live_set_root") {
ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
@@ -269,7 +282,8 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
} else if (p_msg.begins_with("runtime_node_select_")) { /// Runtime Node Selection
if (p_msg == "runtime_node_select_setup") {
- runtime_node_select->_setup();
+ ERR_FAIL_COND_V(p_args.is_empty() || p_args[0].get_type() != Variant::DICTIONARY, ERR_INVALID_DATA);
+ runtime_node_select->_setup(p_args[0]);
} else if (p_msg == "runtime_node_select_set_type") {
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
@@ -289,8 +303,10 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
} else if (p_msg == "runtime_node_select_reset_camera_2d") {
runtime_node_select->_reset_camera_2d();
+#ifndef _3D_DISABLED
} else if (p_msg == "runtime_node_select_reset_camera_3d") {
runtime_node_select->_reset_camera_3d();
+#endif // _3D_DISABLED
} else {
return ERR_SKIP;
@@ -413,6 +429,15 @@ void SceneDebugger::remove_from_cache(const String &p_filename, Node *p_node) {
}
}
+void SceneDebugger::reload_cached_files(const PackedStringArray &p_files) {
+ for (const String &file : p_files) {
+ Ref<Resource> res = ResourceCache::get_ref(file);
+ if (res.is_valid()) {
+ res->reload_from_file();
+ }
+ }
+}
+
/// SceneDebuggerObject
SceneDebuggerObject::SceneDebuggerObject(ObjectID p_id) {
id = ObjectID();
@@ -1210,7 +1235,7 @@ RuntimeNodeSelect::~RuntimeNodeSelect() {
#endif // _3D_DISABLED
}
-void RuntimeNodeSelect::_setup() {
+void RuntimeNodeSelect::_setup(const Dictionary &p_settings) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)));
@@ -1227,6 +1252,14 @@ void RuntimeNodeSelect::_setup() {
panner.instantiate();
panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));
+ ViewPanner::ControlScheme panning_scheme = (ViewPanner::ControlScheme)p_settings.get("editors/panning/2d_editor_panning_scheme", 0).operator int();
+ bool simple_panning = p_settings.get("editors/panning/simple_panning", false);
+ int pan_speed = p_settings.get("editors/panning/2d_editor_pan_speed", 20);
+ Array keys = p_settings.get("canvas_item_editor/pan_view", Array()).operator Array();
+ panner->setup(panning_scheme, DebuggerMarshalls::deserialize_key_shortcut(keys), simple_panning);
+ panner->set_scroll_speed(pan_speed);
+ warped_panning = p_settings.get("editors/panning/warped_mouse_panning", false);
+
/// 2D Selection Box Generation
sbox_2d_canvas = RS::get_singleton()->canvas_create();
@@ -1336,7 +1369,7 @@ void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) {
if (camera_override) {
if (node_select_type == NODE_TYPE_2D) {
- if (panner->gui_input(p_event, Rect2(Vector2(), root->get_size()))) {
+ if (panner->gui_input(p_event, warped_panning ? Rect2(Vector2(), root->get_size()) : Rect2())) {
return;
}
} else if (node_select_type == NODE_TYPE_3D) {
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index f9dd6161aa..90c8000eb4 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -57,6 +57,8 @@ public:
#ifdef DEBUG_ENABLED
private:
+ static void _handle_input(const Ref<InputEvent> &p_event, const Ref<Shortcut> &p_shortcut);
+
static void _save_node(ObjectID id, const String &p_path);
static void _set_node_owner_recursive(Node *p_node, Node *p_owner);
static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value);
@@ -67,6 +69,7 @@ public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
static void add_to_cache(const String &p_filename, Node *p_node);
static void remove_from_cache(const String &p_filename, Node *p_node);
+ static void reload_cached_files(const PackedStringArray &p_files);
#endif
};
@@ -200,6 +203,7 @@ private:
PopupMenu *selection_list = nullptr;
bool selection_visible = true;
bool selection_update_queued = false;
+ bool warped_panning = false;
bool camera_override = false;
@@ -270,7 +274,7 @@ private:
NodeType node_select_type = NODE_TYPE_2D;
SelectMode node_select_mode = SELECT_MODE_SINGLE;
- void _setup();
+ void _setup(const Dictionary &p_settings);
void _node_set_type(NodeType p_type);
void _select_set_mode(SelectMode p_mode);
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 34f5095493..bd4770bcc3 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -205,6 +205,7 @@ void BaseButton::set_disabled(bool p_disabled) {
status.pressing_inside = false;
}
queue_redraw();
+ update_minimum_size();
}
bool BaseButton::is_disabled() const {
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 4bd85cbde9..3e593a8372 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -561,6 +561,7 @@ void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) {
}
autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
p_paragraph->set_break_flags(autowrap_flags);
+ p_paragraph->set_line_spacing(theme_cache.line_spacing);
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
p_paragraph->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
@@ -833,6 +834,7 @@ void Button::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, icon_max_width);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, align_to_largest_stylebox);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, line_spacing);
}
Button::Button(const String &p_text) {
diff --git a/scene/gui/button.h b/scene/gui/button.h
index b86d6a6c1f..686d4806db 100644
--- a/scene/gui/button.h
+++ b/scene/gui/button.h
@@ -100,6 +100,7 @@ private:
int h_separation = 0;
int icon_max_width = 0;
+ int line_spacing = 0;
} theme_cache;
void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index ab443e95e1..2cd34ec99f 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -251,13 +251,13 @@ private:
Ref<Texture2D> completion_color_bg;
Color breakpoint_color = Color(1, 1, 1);
- Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
+ Ref<Texture2D> breakpoint_icon;
Color bookmark_color = Color(1, 1, 1);
- Ref<Texture2D> bookmark_icon = Ref<Texture2D>();
+ Ref<Texture2D> bookmark_icon;
Color executing_line_color = Color(1, 1, 1);
- Ref<Texture2D> executing_line_icon = Ref<Texture2D>();
+ Ref<Texture2D> executing_line_icon;
Color line_number_color = Color(1, 1, 1);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 18864b1289..9eda1a256f 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -1586,6 +1586,7 @@ FileDialog::FileDialog() {
vbox->add_child(hbc);
tree = memnew(Tree);
+ tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tree->set_hide_root(true);
vbox->add_margin_child(ETR("Directories & Files:"), tree, true);
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index cb495f9d47..7e07f4a202 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -317,7 +317,7 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con
void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) {
ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
- for (const List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) {
+ for (List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) {
if (E->get()->from_node == p_from && E->get()->from_port == p_from_port && E->get()->to_node == p_to && E->get()->to_port == p_to_port) {
connection_map[p_from].erase(E->get());
connection_map[p_to].erase(E->get());
@@ -3001,5 +3001,5 @@ GraphEdit::GraphEdit() {
set_clip_contents(true);
- arranger = Ref<GraphEditArranger>(memnew(GraphEditArranger(this)));
+ arranger.instantiate(this);
}
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 42b4e56b48..7a0e5b8867 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -335,6 +335,121 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
}
}
+void Label::_ensure_shaped() const {
+ if (dirty || font_dirty || lines_dirty) {
+ const_cast<Label *>(this)->_shape();
+ }
+}
+
+RID Label::get_line_rid(int p_line) const {
+ return lines_rid[p_line];
+}
+
+Rect2 Label::get_line_rect(int p_line) const {
+ // Returns a rect providing the line's horizontal offset and total size. To determine the vertical
+ // offset, use r_offset and r_line_spacing from get_layout_data.
+ bool rtl = TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL;
+ bool rtl_layout = is_layout_rtl();
+ Ref<StyleBox> style = theme_cache.normal_style;
+ Size2 size = get_size();
+ Size2 line_size = TS->shaped_text_get_size(lines_rid[p_line]);
+ Vector2 offset;
+
+ switch (horizontal_alignment) {
+ case HORIZONTAL_ALIGNMENT_FILL:
+ if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
+ offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ } else {
+ offset.x = style->get_offset().x;
+ }
+ break;
+ case HORIZONTAL_ALIGNMENT_LEFT: {
+ if (rtl_layout) {
+ offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ } else {
+ offset.x = style->get_offset().x;
+ }
+ } break;
+ case HORIZONTAL_ALIGNMENT_CENTER: {
+ offset.x = int(size.width - line_size.width) / 2;
+ } break;
+ case HORIZONTAL_ALIGNMENT_RIGHT: {
+ if (rtl_layout) {
+ offset.x = style->get_offset().x;
+ } else {
+ offset.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+ }
+ } break;
+ }
+
+ return Rect2(offset, line_size);
+}
+
+void Label::get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const {
+ // Computes several common parameters involved in laying out and rendering text set to this label.
+ // Only vertical margin is considered in r_offset: use get_line_rect to get the horizontal offset
+ // for a given line of text.
+ Size2 size = get_size();
+ Ref<StyleBox> style = theme_cache.normal_style;
+ int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing;
+
+ float total_h = 0.0;
+ int lines_visible = 0;
+
+ // Get number of lines to fit to the height.
+ for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
+ total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
+ if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
+ break;
+ }
+ lines_visible++;
+ }
+
+ if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
+ lines_visible = max_lines_visible;
+ }
+
+ r_line_limit = MIN(lines_rid.size(), lines_visible + lines_skipped);
+
+ // Get real total height.
+ total_h = 0;
+ for (int64_t i = lines_skipped; i < r_line_limit; i++) {
+ total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
+ }
+ total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
+
+ int vbegin = 0, vsep = 0;
+ if (lines_visible > 0) {
+ switch (vertical_alignment) {
+ case VERTICAL_ALIGNMENT_TOP: {
+ // Nothing.
+ } break;
+ case VERTICAL_ALIGNMENT_CENTER: {
+ vbegin = (size.y - (total_h - line_spacing)) / 2;
+ vsep = 0;
+
+ } break;
+ case VERTICAL_ALIGNMENT_BOTTOM: {
+ vbegin = size.y - (total_h - line_spacing);
+ vsep = 0;
+
+ } break;
+ case VERTICAL_ALIGNMENT_FILL: {
+ vbegin = 0;
+ if (lines_visible > 1) {
+ vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
+ } else {
+ vsep = 0;
+ }
+
+ } break;
+ }
+ }
+
+ r_offset = { 0, style->get_offset().y + vbegin };
+ r_line_spacing = line_spacing + vsep;
+}
+
PackedStringArray Label::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
@@ -361,10 +476,7 @@ PackedStringArray Label::get_configuration_warnings() const {
}
if (font.is_valid()) {
- if (dirty || font_dirty || lines_dirty) {
- const_cast<Label *>(this)->_shape();
- }
-
+ _ensure_shaped();
const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid);
int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid);
for (int64_t i = 0; i < glyph_count; i++) {
@@ -416,22 +528,17 @@ void Label::_notification(int p_what) {
}
}
- if (dirty || font_dirty || lines_dirty) {
- _shape();
- }
+ _ensure_shaped();
RID ci = get_canvas_item();
bool has_settings = settings.is_valid();
Size2 string_size;
- Size2 size = get_size();
Ref<StyleBox> style = theme_cache.normal_style;
- Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
Color font_color = has_settings ? settings->get_font_color() : theme_cache.font_color;
Color font_shadow_color = has_settings ? settings->get_shadow_color() : theme_cache.font_shadow_color;
Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : theme_cache.font_shadow_offset;
- int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing;
Color font_outline_color = has_settings ? settings->get_outline_color() : theme_cache.font_outline_color;
int outline_size = has_settings ? settings->get_outline_size() : theme_cache.font_outline_size;
int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
@@ -440,98 +547,28 @@ void Label::_notification(int p_what) {
style->draw(ci, Rect2(Point2(0, 0), get_size()));
- float total_h = 0.0;
- int lines_visible = 0;
-
- // Get number of lines to fit to the height.
- for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
- total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
- if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
- break;
- }
- lines_visible++;
- }
-
- if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
- lines_visible = max_lines_visible;
- }
-
- int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING);
bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !rtl_layout));
bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && rtl_layout));
- // Get real total height.
+ Vector2 ofs;
+ int line_limit;
+ int line_spacing;
+ get_layout_data(ofs, line_limit, line_spacing);
+
+ int processed_glyphs = 0;
int total_glyphs = 0;
- total_h = 0;
- for (int64_t i = lines_skipped; i < last_line; i++) {
- total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
+
+ for (int64_t i = lines_skipped; i < line_limit; i++) {
total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
}
- int visible_glyphs = total_glyphs * visible_ratio;
- int processed_glyphs = 0;
- total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
-
- int vbegin = 0, vsep = 0;
- if (lines_visible > 0) {
- switch (vertical_alignment) {
- case VERTICAL_ALIGNMENT_TOP: {
- // Nothing.
- } break;
- case VERTICAL_ALIGNMENT_CENTER: {
- vbegin = (size.y - (total_h - line_spacing)) / 2;
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_BOTTOM: {
- vbegin = size.y - (total_h - line_spacing);
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_FILL: {
- vbegin = 0;
- if (lines_visible > 1) {
- vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
- } else {
- vsep = 0;
- }
- } break;
- }
- }
+ int visible_glyphs = total_glyphs * visible_ratio;
- Vector2 ofs;
- ofs.y = style->get_offset().y + vbegin;
- for (int i = lines_skipped; i < last_line; i++) {
- Size2 line_size = TS->shaped_text_get_size(lines_rid[i]);
- ofs.x = 0;
+ for (int i = lines_skipped; i < line_limit; i++) {
+ Vector2 line_offset = get_line_rect(i).position;
+ ofs.x = line_offset.x;
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
- switch (horizontal_alignment) {
- case HORIZONTAL_ALIGNMENT_FILL:
- if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- } else {
- ofs.x = style->get_offset().x;
- }
- break;
- case HORIZONTAL_ALIGNMENT_LEFT: {
- if (rtl_layout) {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- } else {
- ofs.x = style->get_offset().x;
- }
- } break;
- case HORIZONTAL_ALIGNMENT_CENTER: {
- ofs.x = int(size.width - line_size.width) / 2;
- } break;
- case HORIZONTAL_ALIGNMENT_RIGHT: {
- if (rtl_layout) {
- ofs.x = style->get_offset().x;
- } else {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- }
- } break;
- }
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
@@ -621,7 +658,7 @@ void Label::_notification(int p_what) {
}
}
}
- ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing;
+ ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
}
} break;
@@ -637,102 +674,16 @@ void Label::_notification(int p_what) {
}
Rect2 Label::get_character_bounds(int p_pos) const {
- if (dirty || font_dirty || lines_dirty) {
- const_cast<Label *>(this)->_shape();
- }
-
- bool has_settings = settings.is_valid();
- Size2 size = get_size();
- Ref<StyleBox> style = theme_cache.normal_style;
- int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing;
- bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL);
- bool rtl_layout = is_layout_rtl();
-
- float total_h = 0.0;
- int lines_visible = 0;
-
- // Get number of lines to fit to the height.
- for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
- total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
- if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
- break;
- }
- lines_visible++;
- }
-
- if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
- lines_visible = max_lines_visible;
- }
-
- int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
-
- // Get real total height.
- total_h = 0;
- for (int64_t i = lines_skipped; i < last_line; i++) {
- total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
- }
-
- total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
-
- int vbegin = 0, vsep = 0;
- if (lines_visible > 0) {
- switch (vertical_alignment) {
- case VERTICAL_ALIGNMENT_TOP: {
- // Nothing.
- } break;
- case VERTICAL_ALIGNMENT_CENTER: {
- vbegin = (size.y - (total_h - line_spacing)) / 2;
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_BOTTOM: {
- vbegin = size.y - (total_h - line_spacing);
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_FILL: {
- vbegin = 0;
- if (lines_visible > 1) {
- vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
- } else {
- vsep = 0;
- }
-
- } break;
- }
- }
+ _ensure_shaped();
Vector2 ofs;
- ofs.y = style->get_offset().y + vbegin;
- for (int i = lines_skipped; i < last_line; i++) {
- Size2 line_size = TS->shaped_text_get_size(lines_rid[i]);
- ofs.x = 0;
- switch (horizontal_alignment) {
- case HORIZONTAL_ALIGNMENT_FILL:
- if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- } else {
- ofs.x = style->get_offset().x;
- }
- break;
- case HORIZONTAL_ALIGNMENT_LEFT: {
- if (rtl_layout) {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- } else {
- ofs.x = style->get_offset().x;
- }
- } break;
- case HORIZONTAL_ALIGNMENT_CENTER: {
- ofs.x = int(size.width - line_size.width) / 2;
- } break;
- case HORIZONTAL_ALIGNMENT_RIGHT: {
- if (rtl_layout) {
- ofs.x = style->get_offset().x;
- } else {
- ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
- }
- } break;
- }
+ int line_limit;
+ int line_spacing;
+ get_layout_data(ofs, line_limit, line_spacing);
+
+ for (int i = lines_skipped; i < line_limit; i++) {
+ Rect2 line_rect = get_line_rect(i);
+ ofs.x = line_rect.position.x;
int v_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
@@ -746,22 +697,19 @@ Rect2 Label::get_character_bounds(int p_pos) const {
}
Rect2 rect;
rect.position = ofs + Vector2(gl_off, 0);
- rect.size = Vector2(advance, TS->shaped_text_get_size(lines_rid[i]).y);
+ rect.size = Vector2(advance, line_rect.size.y);
return rect;
}
}
gl_off += glyphs[j].advance * glyphs[j].repeat;
}
- ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing;
+ ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
}
return Rect2();
}
Size2 Label::get_minimum_size() const {
- // don't want to mutable everything
- if (dirty || font_dirty || lines_dirty) {
- const_cast<Label *>(this)->_shape();
- }
+ _ensure_shaped();
Size2 min_size = minsize;
@@ -798,10 +746,7 @@ int Label::get_line_count() const {
if (!is_inside_tree()) {
return 1;
}
- if (dirty || font_dirty || lines_dirty) {
- const_cast<Label *>(this)->_shape();
- }
-
+ _ensure_shaped();
return lines_rid.size();
}
@@ -1104,10 +1049,7 @@ int Label::get_max_lines_visible() const {
}
int Label::get_total_character_count() const {
- if (dirty || font_dirty || lines_dirty) {
- const_cast<Label *>(this)->_shape();
- }
-
+ _ensure_shaped();
return xl_text.length();
}
diff --git a/scene/gui/label.h b/scene/gui/label.h
index e0ebca944a..2576d21c33 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -91,11 +91,16 @@ private:
int font_shadow_outline_size;
} theme_cache;
+ void _ensure_shaped() const;
void _update_visible();
void _shape();
void _invalidate();
protected:
+ RID get_line_rid(int p_line) const;
+ Rect2 get_line_rect(int p_line) const;
+ void get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const;
+
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 9967805134..3f979f7c20 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -128,6 +128,7 @@ bool LineEdit::has_ime_text() const {
void LineEdit::cancel_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
ime_text = String();
@@ -140,6 +141,7 @@ void LineEdit::cancel_ime() {
void LineEdit::apply_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 01c2b9bffe..e56a9e71ef 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -41,7 +41,11 @@ Size2 SpinBox::get_minimum_size() const {
}
void SpinBox::_update_text(bool p_keep_line_edit) {
- String value = String::num(get_value(), Math::range_step_decimals(get_step()));
+ double step = get_step();
+ if (use_custom_arrow_step && custom_arrow_step != 0.0) {
+ step = custom_arrow_step;
+ }
+ String value = String::num(get_value(), Math::range_step_decimals(step));
if (is_localizing_numeral_system()) {
value = TS->format_number(value);
}
@@ -75,6 +79,9 @@ void SpinBox::_text_submitted(const String &p_string) {
text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix);
Error err = expr->parse(text);
+
+ use_custom_arrow_step = false;
+
if (err != OK) {
// If the expression failed try without converting commas to dots - they might have been for parameter separation.
text = p_string;
@@ -114,8 +121,13 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) {
void SpinBox::_range_click_timeout() {
if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
bool up = get_local_mouse_position().y < (get_size().height / 2);
- double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
- set_value(get_value() + (up ? step : -step));
+ double step = get_step();
+ // Arrow button is being pressed, so we also need to set the step to the same value as custom_arrow_step if its not 0.
+ double temp_step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
+ _set_step_no_signal(temp_step);
+ set_value(get_value() + (up ? temp_step : -temp_step));
+ _set_step_no_signal(step);
+ use_custom_arrow_step = true;
if (range_click_timer->is_one_shot()) {
range_click_timer->set_wait_time(0.075);
@@ -156,8 +168,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
Ref<InputEventMouseMotion> mm = p_event;
- double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
-
+ double step = get_step();
Vector2 mpos;
bool mouse_on_up_button = false;
bool mouse_on_down_button = false;
@@ -177,7 +188,12 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
line_edit->grab_focus();
if (mouse_on_up_button || mouse_on_down_button) {
- set_value(get_value() + (mouse_on_up_button ? step : -step));
+ // Arrow button is being pressed, so step is being changed temporarily.
+ double temp_step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
+ _set_step_no_signal(temp_step);
+ set_value(get_value() + (mouse_on_up_button ? temp_step : -temp_step));
+ _set_step_no_signal(step);
+ use_custom_arrow_step = true;
}
state_cache.up_button_pressed = mouse_on_up_button;
state_cache.down_button_pressed = mouse_on_down_button;
@@ -193,17 +209,20 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
case MouseButton::RIGHT: {
line_edit->grab_focus();
if (mouse_on_up_button || mouse_on_down_button) {
+ use_custom_arrow_step = false;
set_value(mouse_on_up_button ? get_max() : get_min());
}
} break;
case MouseButton::WHEEL_UP: {
if (line_edit->is_editing()) {
+ use_custom_arrow_step = false;
set_value(get_value() + step * mb->get_factor());
accept_event();
}
} break;
case MouseButton::WHEEL_DOWN: {
if (line_edit->is_editing()) {
+ use_custom_arrow_step = false;
set_value(get_value() - step * mb->get_factor());
accept_event();
}
@@ -243,6 +262,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
if (drag.enabled) {
drag.diff_y += mm->get_relative().y;
double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8) * SIGN(drag.diff_y);
+ use_custom_arrow_step = false;
set_value(CLAMP(drag.base_val + step * diff_y, get_min(), get_max()));
} else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
@@ -519,6 +539,12 @@ void SpinBox::_update_buttons_state_for_current_value() {
}
}
+void SpinBox::_set_step_no_signal(double p_step) {
+ set_block_signals(true);
+ set_step(p_step);
+ set_block_signals(false);
+}
+
void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment);
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 294dc3e5d5..06a0f622b9 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -66,6 +66,7 @@ class SpinBox : public Range {
String suffix;
String last_updated_text;
double custom_arrow_step = 0.0;
+ bool use_custom_arrow_step = false;
void _line_edit_input(const Ref<InputEvent> &p_event);
@@ -133,6 +134,7 @@ class SpinBox : public Range {
void _mouse_exited();
void _update_buttons_state_for_current_value();
+ void _set_step_no_signal(double p_step);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 6b5ff23436..b1918ff23f 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -3169,6 +3169,7 @@ bool TextEdit::has_ime_text() const {
void TextEdit::cancel_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
ime_text = String();
@@ -3181,6 +3182,7 @@ void TextEdit::cancel_ime() {
void TextEdit::apply_ime() {
if (!has_ime_text()) {
+ _close_ime_window();
return;
}
@@ -5069,7 +5071,7 @@ bool TextEdit::multicaret_edit_ignore_caret(int p_caret) const {
}
bool TextEdit::is_caret_visible(int p_caret) const {
- ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), false);
return carets[p_caret].visible;
}
@@ -5736,7 +5738,7 @@ TextServer::AutowrapMode TextEdit::get_autowrap_mode() const {
}
bool TextEdit::is_line_wrapped(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+ ERR_FAIL_INDEX_V(p_line, text.size(), false);
if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
return false;
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index e0d552848d..6b137581f2 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -139,7 +139,7 @@ private:
Variant metadata;
bool clickable = false;
- Ref<Texture2D> icon = Ref<Texture2D>();
+ Ref<Texture2D> icon;
String text = "";
Color color = Color(1, 1, 1);
};
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index aab6f672f0..f6e05f5796 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1574,7 +1574,7 @@ Size2 TreeItem::get_minimum_size(int p_column) {
const TreeItem::Cell &cell = cells[p_column];
- if (cell.cached_minimum_size_dirty) {
+ if (cell.cached_minimum_size_dirty || cell.text_buf->is_dirty() || cell.dirty) {
Size2 size = Size2(
parent_tree->theme_cache.inner_item_margin_left + parent_tree->theme_cache.inner_item_margin_right,
parent_tree->theme_cache.inner_item_margin_top + parent_tree->theme_cache.inner_item_margin_bottom);
@@ -1593,7 +1593,9 @@ Size2 TreeItem::get_minimum_size(int p_column) {
// Icon.
if (cell.mode == CELL_MODE_CHECK) {
- size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.h_separation;
+ Size2i check_size = parent_tree->theme_cache.checked->get_size();
+ size.width += check_size.width + parent_tree->theme_cache.h_separation;
+ size.height = MAX(size.height, check_size.height);
}
if (cell.icon.is_valid()) {
Size2i icon_size = parent_tree->_get_cell_icon_size(cell);
@@ -1605,7 +1607,8 @@ Size2 TreeItem::get_minimum_size(int p_column) {
for (int i = 0; i < cell.buttons.size(); i++) {
Ref<Texture2D> texture = cell.buttons[i].texture;
if (texture.is_valid()) {
- Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size();
+ Size2 button_size = texture->get_size();
+ button_size.width += parent_tree->theme_cache.button_pressed->get_minimum_size().width;
size.width += button_size.width + parent_tree->theme_cache.button_margin;
size.height = MAX(size.height, button_size.height);
}
@@ -1889,44 +1892,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
int height = 0;
for (int i = 0; i < columns.size(); i++) {
- if (p_item->cells[i].dirty) {
- const_cast<Tree *>(this)->update_item_cell(p_item, i);
- }
- height = MAX(height, p_item->cells[i].text_buf->get_size().y);
- for (int j = 0; j < p_item->cells[i].buttons.size(); j++) {
- Size2i s; // = cache.button_pressed->get_minimum_size();
- s += p_item->cells[i].buttons[j].texture->get_size();
- if (s.height > height) {
- height = s.height;
- }
- }
-
- switch (p_item->cells[i].mode) {
- case TreeItem::CELL_MODE_CHECK: {
- int check_icon_h = theme_cache.checked->get_height();
- if (height < check_icon_h) {
- height = check_icon_h;
- }
- [[fallthrough]];
- }
- case TreeItem::CELL_MODE_STRING:
- case TreeItem::CELL_MODE_CUSTOM:
- case TreeItem::CELL_MODE_ICON: {
- Ref<Texture2D> icon = p_item->cells[i].icon;
- if (!icon.is_null()) {
- Size2i s = _get_cell_icon_size(p_item->cells[i]);
- if (s.height > height) {
- height = s.height;
- }
- }
- if (p_item->cells[i].mode == TreeItem::CELL_MODE_CUSTOM && p_item->cells[i].custom_button) {
- height += theme_cache.custom_button->get_minimum_size().height;
- }
-
- } break;
- default: {
- }
- }
+ height = MAX(height, p_item->get_minimum_size(i).y);
}
int item_min_height = MAX(theme_cache.font->get_height(theme_cache.font_size), p_item->get_custom_minimum_height());
if (height < item_min_height) {
@@ -4702,6 +4668,7 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous
edited_col = p_column;
if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
edited_item->cells.write[p_column].dirty = true;
+ edited_item->cells.write[p_column].cached_minimum_size_dirty = true;
}
emit_signal(SNAME("item_edited"));
if (p_custom_mouse_index != MouseButton::NONE) {
@@ -5362,14 +5329,16 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
int y_offset = get_item_offset(p_item);
if (y_offset != -1) {
- const int tbh = _get_title_button_height();
- y_offset -= tbh;
+ const int title_button_height = _get_title_button_height();
+ y_offset -= title_button_height;
const int cell_h = compute_item_height(p_item) + theme_cache.v_separation;
- int screen_h = area_size.height - tbh;
+ int screen_h = area_size.height - title_button_height;
if (p_center_on_item) {
- v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f);
+ // This makes sure that centering the offset doesn't overflow.
+ const double v_scroll_value = y_offset - MAX((screen_h - cell_h) / 2.0, 0.0);
+ v_scroll->set_value(v_scroll_value);
} else {
if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet.
v_scroll->set_value(y_offset);
diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp
index 83672ae5e0..d5a183eab1 100644
--- a/scene/main/missing_node.cpp
+++ b/scene/main/missing_node.cpp
@@ -84,17 +84,17 @@ bool MissingNode::is_recording_properties() const {
PackedStringArray MissingNode::get_configuration_warnings() const {
// The mere existence of this node is warning.
- PackedStringArray ret;
+ PackedStringArray warnings = Node::get_configuration_warnings();
if (!original_scene.is_empty()) {
- ret.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene));
- ret.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing).")));
+ warnings.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene));
+ warnings.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing).")));
} else if (!original_class.is_empty()) {
- ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class));
- ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss."));
+ warnings.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class));
+ warnings.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss."));
} else {
- ret.push_back(RTR("Unrecognized missing node. Check scene dependency errors for details."));
+ warnings.push_back(RTR("Unrecognized missing node. Check scene dependency errors for details."));
}
- return ret;
+ return warnings;
}
void MissingNode::_bind_methods() {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 60cecfcfe7..16b41c8f9b 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -899,7 +899,7 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() {
return debug_contact_mesh;
}
- debug_contact_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ debug_contact_mesh.instantiate();
Ref<StandardMaterial3D> mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 5a90eb8f3e..1ee99099ec 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1213,7 +1213,7 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
}
} else {
WARN_PRINT("Invalid world_2d");
- world_2d = Ref<World2D>(memnew(World2D));
+ world_2d.instantiate();
}
world_2d->register_viewport(this);
@@ -4386,7 +4386,7 @@ void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) {
own_world_3d = world_3d->duplicate();
world_3d->connect_changed(callable_mp(this, &Viewport::_own_world_3d_changed));
} else {
- own_world_3d = Ref<World3D>(memnew(World3D));
+ own_world_3d.instantiate();
}
}
@@ -4437,7 +4437,7 @@ void Viewport::set_use_own_world_3d(bool p_use_own_world_3d) {
own_world_3d = world_3d->duplicate();
world_3d->connect_changed(callable_mp(this, &Viewport::_own_world_3d_changed));
} else {
- own_world_3d = Ref<World3D>(memnew(World3D));
+ own_world_3d.instantiate();
}
} else {
own_world_3d = Ref<World3D>();
@@ -4963,7 +4963,7 @@ void Viewport::_validate_property(PropertyInfo &p_property) const {
}
Viewport::Viewport() {
- world_2d = Ref<World2D>(memnew(World2D));
+ world_2d.instantiate();
world_2d->register_viewport(this);
viewport = RenderingServer::get_singleton()->viewport_create();
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 045c3ae02d..fc2fe4320b 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -31,10 +31,8 @@
#include "window.h"
#include "core/config/project_settings.h"
-#include "core/debugger/engine_debugger.h"
#include "core/input/shortcut.h"
#include "core/string/translation_server.h"
-#include "core/variant/variant_parser.h"
#include "scene/gui/control.h"
#include "scene/theme/theme_db.h"
#include "scene/theme/theme_owner.h"
@@ -1631,35 +1629,6 @@ bool Window::_can_consume_input_events() const {
void Window::_window_input(const Ref<InputEvent> &p_ev) {
ERR_MAIN_THREAD_GUARD;
- if (EngineDebugger::is_active()) {
- // Quit from game window using the stop shortcut (F8 by default).
- // The custom shortcut is provided via environment variable when running from the editor.
- if (debugger_stop_shortcut.is_null()) {
- String shortcut_str = OS::get_singleton()->get_environment("__GODOT_EDITOR_STOP_SHORTCUT__");
- if (!shortcut_str.is_empty()) {
- Variant shortcut_var;
-
- VariantParser::StreamString ss;
- ss.s = shortcut_str;
-
- String errs;
- int line;
- VariantParser::parse(&ss, shortcut_var, errs, line);
- debugger_stop_shortcut = shortcut_var;
- }
-
- if (debugger_stop_shortcut.is_null()) {
- // Define a default shortcut if it wasn't provided or is invalid.
- debugger_stop_shortcut.instantiate();
- debugger_stop_shortcut->set_events({ (Variant)InputEventKey::create_reference(Key::F8) });
- }
- }
-
- Ref<InputEventKey> k = p_ev;
- if (k.is_valid() && k->is_pressed() && !k->is_echo() && debugger_stop_shortcut->matches_event(k)) {
- EngineDebugger::get_singleton()->send_message("request_quit", Array());
- }
- }
if (exclusive_child != nullptr) {
if (!is_embedding_subwindows()) { // Not embedding, no need for event.
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 6b1ce2b4ca..8a048e9cc3 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -243,6 +243,7 @@
#include "scene/3d/light_3d.h"
#include "scene/3d/lightmap_gi.h"
#include "scene/3d/lightmap_probe.h"
+#include "scene/3d/look_at_modifier_3d.h"
#include "scene/3d/marker_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/multimesh_instance_3d.h"
@@ -512,6 +513,9 @@ void register_scene_types() {
GDREGISTER_CLASS(AnimationNodeStateMachine);
GDREGISTER_CLASS(AnimationNodeStateMachinePlayback);
+ GDREGISTER_INTERNAL_CLASS(AnimationNodeStartState);
+ GDREGISTER_INTERNAL_CLASS(AnimationNodeEndState);
+
GDREGISTER_CLASS(AnimationNodeSync);
GDREGISTER_CLASS(AnimationNodeStateMachineTransition);
GDREGISTER_CLASS(AnimationNodeOutput);
@@ -608,6 +612,7 @@ void register_scene_types() {
GDREGISTER_CLASS(SkeletonIK3D);
GDREGISTER_CLASS(BoneAttachment3D);
+ GDREGISTER_CLASS(LookAtModifier3D);
GDREGISTER_CLASS(VehicleBody3D);
GDREGISTER_CLASS(VehicleWheel3D);
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
index b29c106fb5..2812925770 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
@@ -36,6 +36,8 @@
#include "scene/resources/2d/navigation_polygon.h"
class NavigationMeshSourceGeometryData2D : public Resource {
+ friend class NavMeshGenerator2D;
+
GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
RWLock geometry_rwlock;
diff --git a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
index 0732153997..d1e50cb702 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
+++ b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h
@@ -64,7 +64,7 @@ public:
execution_mode_physics_process
};
- Vector<Ref<SkeletonModification2D>> modifications = Vector<Ref<SkeletonModification2D>>();
+ Vector<Ref<SkeletonModification2D>> modifications;
void setup();
void execute(float p_delta, int p_execution_mode);
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index ca80486363..5ecfc32622 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -699,6 +699,17 @@ uint32_t TileSet::get_physics_layer_collision_mask(int p_layer_index) const {
return physics_layers[p_layer_index].collision_mask;
}
+void TileSet::set_physics_layer_collision_priority(int p_layer_index, real_t p_priority) {
+ ERR_FAIL_INDEX(p_layer_index, physics_layers.size());
+ physics_layers.write[p_layer_index].collision_priority = p_priority;
+ emit_changed();
+}
+
+real_t TileSet::get_physics_layer_collision_priority(int p_layer_index) const {
+ ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0);
+ return physics_layers[p_layer_index].collision_priority;
+}
+
void TileSet::set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material) {
ERR_FAIL_INDEX(p_layer_index, physics_layers.size());
physics_layers.write[p_layer_index].physics_material = p_physics_material;
@@ -3900,6 +3911,13 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
}
set_physics_layer_collision_mask(index, p_value);
return true;
+ } else if (components[1] == "collision_priority") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::FLOAT, false);
+ while (index >= physics_layers.size()) {
+ add_physics_layer();
+ }
+ set_physics_layer_collision_priority(index, p_value);
+ return true;
} else if (components[1] == "physics_material") {
Ref<PhysicsMaterial> physics_material = p_value;
while (index >= physics_layers.size()) {
@@ -4051,6 +4069,9 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
} else if (components[1] == "collision_mask") {
r_ret = get_physics_layer_collision_mask(index);
return true;
+ } else if (components[1] == "collision_priority") {
+ r_ret = get_physics_layer_collision_priority(index);
+ return true;
} else if (components[1] == "physics_material") {
r_ret = get_physics_layer_physics_material(index);
return true;
@@ -4176,6 +4197,13 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
}
p_list->push_back(property_info);
+ // physics_layer_%d/collision_priority
+ property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/collision_priority", i));
+ if (physics_layers[i].collision_priority == 1.0) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
+
// physics_layer_%d/physics_material
property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/physics_material", i), PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial");
if (!physics_layers[i].physics_material.is_valid()) {
@@ -4220,10 +4248,10 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
// Tile Proxies.
// Note: proxies need to be set after sources are set.
- p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Tile Proxies", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/source_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/coords_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/alternative_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::NIL, "Tile Proxies", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
// Patterns.
for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) {
@@ -4287,6 +4315,8 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer);
ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask);
ClassDB::bind_method(D_METHOD("get_physics_layer_collision_mask", "layer_index"), &TileSet::get_physics_layer_collision_mask);
+ ClassDB::bind_method(D_METHOD("set_physics_layer_collision_priority", "layer_index", "priority"), &TileSet::set_physics_layer_collision_priority);
+ ClassDB::bind_method(D_METHOD("get_physics_layer_collision_priority", "layer_index"), &TileSet::get_physics_layer_collision_priority);
ClassDB::bind_method(D_METHOD("set_physics_layer_physics_material", "layer_index", "physics_material"), &TileSet::set_physics_layer_physics_material);
ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material);
@@ -4931,10 +4961,13 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
}
for (const KeyValue<int, TileData *> &E_alternative : E_tile.value.alternatives) {
+ const String formatted_key = itos(E_alternative.key);
+
// Add a dummy property to show the alternative exists.
- tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ tile_property_list.push_back(PropertyInfo(Variant::INT, formatted_key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
// Get the alternative tile's properties and append them to the list of properties.
+ const String alternative_property_info_prefix = formatted_key + '/';
List<PropertyInfo> alternative_property_list;
E_alternative.value->get_property_list(&alternative_property_list);
for (PropertyInfo &alternative_property_info : alternative_property_list) {
@@ -4943,14 +4976,15 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
- alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative.key), alternative_property_info.name);
+ alternative_property_info.name = alternative_property_info_prefix + alternative_property_info.name;
tile_property_list.push_back(alternative_property_info);
}
}
// Add all alternative.
+ const String property_info_prefix = vformat("%d:%d/", E_tile.key.x, E_tile.key.y);
for (PropertyInfo &tile_property_info : tile_property_list) {
- tile_property_info.name = vformat("%s/%s", vformat("%d:%d", E_tile.key.x, E_tile.key.y), tile_property_info.name);
+ tile_property_info.name = property_info_prefix + tile_property_info.name;
p_list->push_back(tile_property_info);
}
}
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index 7979e2ca39..6d3ccd1d2d 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -327,6 +327,7 @@ private:
struct PhysicsLayer {
uint32_t collision_layer = 1;
uint32_t collision_mask = 1;
+ real_t collision_priority = 1.0;
Ref<PhysicsMaterial> physics_material;
};
Vector<PhysicsLayer> physics_layers;
@@ -448,6 +449,8 @@ public:
uint32_t get_physics_layer_collision_layer(int p_layer_index) const;
void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask);
uint32_t get_physics_layer_collision_mask(int p_layer_index) const;
+ void set_physics_layer_collision_priority(int p_layer_index, real_t p_priority);
+ real_t get_physics_layer_collision_priority(int p_layer_index) const;
void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material);
Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const;
@@ -836,7 +839,7 @@ private:
bool flip_v = false;
bool transpose = false;
Vector2i texture_origin;
- Ref<Material> material = Ref<Material>();
+ Ref<Material> material;
Color modulate = Color(1.0, 1.0, 1.0, 1.0);
int z_index = 0;
int y_sort_origin = 0;
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index e255cb077f..f040f04cd8 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -168,10 +168,56 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma
mesh.unref();
}
-void ImporterMesh::optimize_indices_for_cache() {
+template <typename T>
+static Vector<T> _remap_array(Vector<T> p_array, const Vector<uint32_t> &p_remap, uint32_t p_vertex_count) {
+ ERR_FAIL_COND_V(p_array.size() % p_remap.size() != 0, p_array);
+ int num_elements = p_array.size() / p_remap.size();
+ T *data = p_array.ptrw();
+ SurfaceTool::remap_vertex_func(data, data, p_remap.size(), sizeof(T) * num_elements, p_remap.ptr());
+ p_array.resize(p_vertex_count * num_elements);
+ return p_array;
+}
+
+static void _remap_arrays(Array &r_arrays, const Vector<uint32_t> &p_remap, uint32_t p_vertex_count) {
+ for (int i = 0; i < r_arrays.size(); i++) {
+ if (i == RS::ARRAY_INDEX) {
+ continue;
+ }
+
+ switch (r_arrays[i].get_type()) {
+ case Variant::NIL:
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ r_arrays[i] = _remap_array<Vector3>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ r_arrays[i] = _remap_array<Vector2>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ r_arrays[i] = _remap_array<float>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ r_arrays[i] = _remap_array<int32_t>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ r_arrays[i] = _remap_array<uint8_t>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ r_arrays[i] = _remap_array<Color>(r_arrays[i], p_remap, p_vertex_count);
+ break;
+ default:
+ ERR_FAIL_MSG("Unhandled array type.");
+ }
+ }
+}
+
+void ImporterMesh::optimize_indices() {
if (!SurfaceTool::optimize_vertex_cache_func) {
return;
}
+ if (!SurfaceTool::optimize_vertex_fetch_remap_func || !SurfaceTool::remap_vertex_func || !SurfaceTool::remap_index_func) {
+ return;
+ }
for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) {
@@ -188,10 +234,48 @@ void ImporterMesh::optimize_indices_for_cache() {
continue;
}
+ // Optimize indices for vertex cache to establish final triangle order.
int *indices_ptr = indices.ptrw();
SurfaceTool::optimize_vertex_cache_func((unsigned int *)indices_ptr, (const unsigned int *)indices_ptr, index_count, vertex_count);
+ surfaces.write[i].arrays[RS::ARRAY_INDEX] = indices;
+
+ for (int j = 0; j < surfaces[i].lods.size(); ++j) {
+ Surface::LOD &lod = surfaces.write[i].lods.write[j];
+ int *lod_indices_ptr = lod.indices.ptrw();
+ SurfaceTool::optimize_vertex_cache_func((unsigned int *)lod_indices_ptr, (const unsigned int *)lod_indices_ptr, lod.indices.size(), vertex_count);
+ }
+ // Concatenate indices for all LODs in the order of coarse->fine; this establishes the effective order of vertices,
+ // and is important to optimize for vertex fetch (all GPUs) and shading (Mali GPUs)
+ PackedInt32Array merged_indices;
+ for (int j = surfaces[i].lods.size() - 1; j >= 0; --j) {
+ merged_indices.append_array(surfaces[i].lods[j].indices);
+ }
+ merged_indices.append_array(indices);
+
+ // Generate remap array that establishes optimal vertex order according to the order of indices above.
+ Vector<uint32_t> remap;
+ remap.resize(vertex_count);
+ unsigned int new_vertex_count = SurfaceTool::optimize_vertex_fetch_remap_func(remap.ptrw(), (const unsigned int *)merged_indices.ptr(), merged_indices.size(), vertex_count);
+
+ // We need to remap all vertex and index arrays in lockstep according to the remap.
+ SurfaceTool::remap_index_func((unsigned int *)indices_ptr, (const unsigned int *)indices_ptr, index_count, remap.ptr());
surfaces.write[i].arrays[RS::ARRAY_INDEX] = indices;
+
+ for (int j = 0; j < surfaces[i].lods.size(); ++j) {
+ Surface::LOD &lod = surfaces.write[i].lods.write[j];
+ int *lod_indices_ptr = lod.indices.ptrw();
+ SurfaceTool::remap_index_func((unsigned int *)lod_indices_ptr, (const unsigned int *)lod_indices_ptr, lod.indices.size(), remap.ptr());
+ }
+
+ _remap_arrays(surfaces.write[i].arrays, remap, new_vertex_count);
+ for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) {
+ _remap_arrays(surfaces.write[i].blend_shape_data.write[j].arrays, remap, new_vertex_count);
+ }
+ }
+
+ if (shadow_mesh.is_valid()) {
+ shadow_mesh->optimize_indices();
}
}
@@ -215,9 +299,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
if (!SurfaceTool::simplify_with_attrib_func) {
return;
}
- if (!SurfaceTool::optimize_vertex_cache_func) {
- return;
- }
LocalVector<Transform3D> bone_transform_vector;
for (int i = 0; i < p_bone_transform_array.size(); i++) {
@@ -431,12 +512,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transf
}
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
-
- for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
- Surface::LOD &lod = surfaces.write[i].lods.write[j];
- unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw();
- SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), vertex_count);
- }
}
}
@@ -574,10 +649,6 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[j] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
- SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
- }
-
new_surface[RS::ARRAY_INDEX] = new_indices;
// Make sure the same LODs as the full version are used.
@@ -596,10 +667,6 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[k] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
- SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
- }
-
lods[surfaces[i].lods[j].distance] = new_indices;
}
}
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index 7776e78f11..2bdf759da6 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -113,7 +113,7 @@ public:
void set_surface_material(int p_surface, const Ref<Material> &p_material);
- void optimize_indices_for_cache();
+ void optimize_indices();
void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array);
diff --git a/scene/resources/3d/shape_3d.cpp b/scene/resources/3d/shape_3d.cpp
index 5a79392ba5..259d82b7a0 100644
--- a/scene/resources/3d/shape_3d.cpp
+++ b/scene/resources/3d/shape_3d.cpp
@@ -73,7 +73,7 @@ Ref<ArrayMesh> Shape3D::get_debug_mesh() {
Vector<Vector3> lines = get_debug_mesh_lines();
- debug_mesh_cache = Ref<ArrayMesh>(memnew(ArrayMesh));
+ debug_mesh_cache.instantiate();
if (!lines.is_empty()) {
//make mesh
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index f9787dde2e..539001bf25 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -624,7 +624,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
}
String file_path = p_path;
- if (!(file_path.substr(file_path.length() - 4, 4) == ".wav")) {
+ if (file_path.substr(file_path.length() - 4, 4).to_lower() != ".wav") {
file_path += ".wav";
}
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 8926eb1d51..765373ce99 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -479,6 +479,9 @@ void Curve::set_bake_resolution(int p_resolution) {
}
real_t Curve::sample_baked(real_t p_offset) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), 0, "Offset is non-finite");
+
if (_baked_cache_dirty) {
// Last-second bake if not done already
const_cast<Curve *>(this)->bake();
@@ -981,6 +984,9 @@ Transform2D Curve2D::_sample_posture(Interval p_interval) const {
}
Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector2(), "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
@@ -1000,6 +1006,9 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const {
}
Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Transform2D(), "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
@@ -1881,6 +1890,9 @@ Basis Curve3D::get_point_baked_posture(int p_index, bool p_apply_tilt) const {
#endif
Vector3 Curve3D::sample_baked(real_t p_offset, bool p_cubic) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector3(), "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
@@ -1900,6 +1912,9 @@ Vector3 Curve3D::sample_baked(real_t p_offset, bool p_cubic) const {
}
Transform3D Curve3D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_apply_tilt) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Transform3D(), "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
@@ -1929,6 +1944,9 @@ Transform3D Curve3D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, b
}
real_t Curve3D::sample_baked_tilt(real_t p_offset) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), 0, "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
@@ -1948,6 +1966,9 @@ real_t Curve3D::sample_baked_tilt(real_t p_offset) const {
}
Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) const {
+ // Make sure that p_offset is finite.
+ ERR_FAIL_COND_V_MSG(!Math::is_finite(p_offset), Vector3(0, 1, 0), "Offset is non-finite");
+
if (baked_cache_dirty) {
_bake();
}
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index f8c70c3002..b5e23e9832 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -130,10 +130,7 @@ int Environment::get_canvas_max_layer() const {
void Environment::set_camera_feed_id(int p_id) {
bg_camera_feed_id = p_id;
-// FIXME: Disabled during Vulkan refactoring, should be ported.
-#if 0
- RS::get_singleton()->environment_set_camera_feed_id(environment, camera_feed_id);
-#endif
+ RS::get_singleton()->environment_set_camera_feed_id(environment, bg_camera_feed_id);
}
int Environment::get_camera_feed_id() const {
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 5e4136f449..a4677d917d 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -647,13 +647,13 @@ void FontFile::_convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz)
wa[ofs_dst + 1] = r[ofs_src + 3];
}
}
- Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ Ref<Image> img_r = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_r));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
- Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ Ref<Image> img_b = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_b));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
- Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ Ref<Image> img_a = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_a));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
}
@@ -738,22 +738,22 @@ void FontFile::_convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz)
}
}
}
- Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ Ref<Image> img_r = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_r));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
- Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ Ref<Image> img_b = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_b));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
- Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ Ref<Image> img_a = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_a));
set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
- Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro));
+ Ref<Image> img_ro = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_ro));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro);
- Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go));
+ Ref<Image> img_go = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_go));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go);
- Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo));
+ Ref<Image> img_bo = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_bo));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo);
- Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao));
+ Ref<Image> img_ao = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_ao));
set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao);
}
@@ -806,10 +806,10 @@ void FontFile::_convert_rgba_4bit(Ref<Image> &p_source, int p_page, int p_sz) {
}
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_RGBA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
- Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o));
+ Ref<Image> img_o = memnew(Image(w, h, false, Image::FORMAT_RGBA8, imgdata_o));
set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o);
}
@@ -838,7 +838,7 @@ void FontFile::_convert_mono_8bit(Ref<Image> &p_source, int p_page, int p_ch, in
wg[ofs_dst + 1] = r[ofs_src + p_ch];
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g);
}
@@ -878,10 +878,10 @@ void FontFile::_convert_mono_4bit(Ref<Image> &p_source, int p_page, int p_ch, in
}
}
}
- Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ Ref<Image> img_g = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_g));
set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
- Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o));
+ Ref<Image> img_o = memnew(Image(w, h, false, Image::FORMAT_LA8, imgdata_o));
set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o);
}
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 848ae2713d..b0353b4f2c 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -385,7 +385,7 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const {
}
}
- triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
+ triangle_mesh.instantiate();
triangle_mesh->create(faces);
return triangle_mesh;
diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp
index 67ed65df0d..034d4d6996 100644
--- a/scene/resources/navigation_mesh.cpp
+++ b/scene/resources/navigation_mesh.cpp
@@ -392,8 +392,8 @@ Ref<ArrayMesh> NavigationMesh::get_debug_mesh() {
return debug_mesh;
}
- if (!debug_mesh.is_valid()) {
- debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (debug_mesh.is_null()) {
+ debug_mesh.instantiate();
} else {
debug_mesh->clear_surfaces();
}
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index d6fe4385c4..809a77a487 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -786,7 +786,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
Dictionary missing_resource_properties = p_node->get_meta(META_MISSING_RESOURCES, Dictionary());
for (const PropertyInfo &E : plist) {
- if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
+ if (!(E.usage & PROPERTY_USAGE_STORAGE) && !missing_resource_properties.has(E.name)) {
continue;
}
@@ -2195,7 +2195,7 @@ void PackedScene::replace_state(Ref<SceneState> p_by) {
}
void PackedScene::recreate_state() {
- state = Ref<SceneState>(memnew(SceneState));
+ state.instantiate();
state->set_path(get_path());
#ifdef TOOLS_ENABLED
state->set_last_modified_time(get_last_modified_time());
@@ -2286,5 +2286,5 @@ void PackedScene::_bind_methods() {
}
PackedScene::PackedScene() {
- state = Ref<SceneState>(memnew(SceneState));
+ state.instantiate();
}
diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp
index 06b5ec6d5a..55bbed7c47 100644
--- a/scene/resources/portable_compressed_texture.cpp
+++ b/scene/resources/portable_compressed_texture.cpp
@@ -89,7 +89,7 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) {
data_size -= mipsize;
}
- image = Ref<Image>(memnew(Image(size.width, size.height, mipmaps, format, image_data)));
+ image.instantiate(size.width, size.height, mipmaps, format, image_data);
} break;
case COMPRESSION_MODE_BASIS_UNIVERSAL: {
@@ -100,7 +100,7 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) {
case COMPRESSION_MODE_S3TC:
case COMPRESSION_MODE_ETC2:
case COMPRESSION_MODE_BPTC: {
- image = Ref<Image>(memnew(Image(size.width, size.height, mipmaps, format, p_data.slice(20))));
+ image.instantiate(size.width, size.height, mipmaps, format, p_data.slice(20));
} break;
}
ERR_FAIL_COND(image.is_null());
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 4a318a10f0..6e03fe25c9 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -600,7 +600,7 @@ Error ResourceLoaderText::load() {
if (do_assign) {
bool set_valid = true;
- if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) {
+ if (value.get_type() == Variant::OBJECT && missing_resource == nullptr && ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
// If the property being set is a missing resource (and the parent is not),
// then setting it will most likely not work.
// Instead, save it as metadata.
@@ -723,24 +723,25 @@ Error ResourceLoaderText::load() {
if (error) {
if (error != ERR_FILE_EOF) {
_printerr();
- } else {
- error = OK;
- if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- if (!ResourceCache::has(res_path)) {
- resource->set_path(res_path);
- }
- resource->set_as_translation_remapped(translation_remapped);
- } else {
- resource->set_path_cache(res_path);
+ return error;
+ }
+ // EOF, Done parsing.
+ error = OK;
+ if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
+ if (!ResourceCache::has(res_path)) {
+ resource->set_path(res_path);
}
+ resource->set_as_translation_remapped(translation_remapped);
+ } else {
+ resource->set_path_cache(res_path);
}
- return error;
+ break;
}
if (!assign.is_empty()) {
bool set_valid = true;
- if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) {
+ if (value.get_type() == Variant::OBJECT && missing_resource == nullptr && ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
// If the property being set is a missing resource (and the parent is not),
// then setting it will most likely not work.
// Instead, save it as metadata.
@@ -1525,6 +1526,10 @@ ResourceUID::ID ResourceFormatLoaderText::get_resource_uid(const String &p_path)
return loader.get_uid(f);
}
+bool ResourceFormatLoaderText::has_custom_uid_support() const {
+ return true;
+}
+
void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
@@ -1900,7 +1905,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
#endif
}
- Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary());
+ Dictionary missing_resource_properties = res->get_meta(META_MISSING_RESOURCES, Dictionary());
List<PropertyInfo> property_list;
res->get_property_list(&property_list);
@@ -1912,7 +1917,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
continue;
}
- if (PE->get().usage & PROPERTY_USAGE_STORAGE) {
+ if (PE->get().usage & PROPERTY_USAGE_STORAGE || missing_resource_properties.has(PE->get().name)) {
String name = PE->get().name;
Variant value;
if (PE->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) {
diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h
index 8397bc985f..4c0bf3d917 100644
--- a/scene/resources/resource_format_text.h
+++ b/scene/resources/resource_format_text.h
@@ -155,6 +155,7 @@ public:
virtual String get_resource_type(const String &p_path) const override;
virtual String get_resource_script_class(const String &p_path) const override;
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override;
+ virtual bool has_custom_uid_support() const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override;
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 6921885ee0..c230cf1b70 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -33,10 +33,10 @@
#define EQ_VERTEX_DIST 0.00001
SurfaceTool::OptimizeVertexCacheFunc SurfaceTool::optimize_vertex_cache_func = nullptr;
+SurfaceTool::OptimizeVertexFetchRemapFunc SurfaceTool::optimize_vertex_fetch_remap_func = nullptr;
SurfaceTool::SimplifyFunc SurfaceTool::simplify_func = nullptr;
SurfaceTool::SimplifyWithAttribFunc SurfaceTool::simplify_with_attrib_func = nullptr;
SurfaceTool::SimplifyScaleFunc SurfaceTool::simplify_scale_func = nullptr;
-SurfaceTool::SimplifySloppyFunc SurfaceTool::simplify_sloppy_func = nullptr;
SurfaceTool::GenerateRemapFunc SurfaceTool::generate_remap_func = nullptr;
SurfaceTool::RemapVertexFunc SurfaceTool::remap_vertex_func = nullptr;
SurfaceTool::RemapIndexFunc SurfaceTool::remap_index_func = nullptr;
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index 3b18e082a1..68dc9e7198 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -90,14 +90,14 @@ public:
typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
static OptimizeVertexCacheFunc optimize_vertex_cache_func;
+ typedef size_t (*OptimizeVertexFetchRemapFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
+ static OptimizeVertexFetchRemapFunc optimize_vertex_fetch_remap_func;
typedef size_t (*SimplifyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float *r_error);
static SimplifyFunc simplify_func;
typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, const float *attributes, size_t attribute_stride, const float *attribute_weights, size_t attribute_count, const unsigned char *vertex_lock, size_t target_index_count, float target_error, unsigned int options, float *result_error);
static SimplifyWithAttribFunc simplify_with_attrib_func;
typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
static SimplifyScaleFunc simplify_scale_func;
- typedef size_t (*SimplifySloppyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float *out_result_error);
- static SimplifySloppyFunc simplify_sloppy_func;
typedef size_t (*GenerateRemapFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const void *vertices, size_t vertex_count, size_t vertex_size);
static GenerateRemapFunc generate_remap_func;
typedef void (*RemapVertexFunc)(void *destination, const void *vertices, size_t vertex_count, size_t vertex_size, const unsigned int *remap);
@@ -222,7 +222,9 @@ public:
void clear();
- LocalVector<Vertex> &get_vertex_array() { return vertex_array; }
+ LocalVector<Vertex> &get_vertex_array() {
+ return vertex_array;
+ }
void create_from_triangle_arrays(const Array &p_arrays);
void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES);
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index 29a8541cb0..65c6e40241 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -112,6 +112,11 @@ void TextParagraph::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible"), "set_max_lines_visible", "get_max_lines_visible");
+ ClassDB::bind_method(D_METHOD("set_line_spacing", "line_spacing"), &TextParagraph::set_line_spacing);
+ ClassDB::bind_method(D_METHOD("get_line_spacing"), &TextParagraph::get_line_spacing);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing"), "set_line_spacing", "get_line_spacing");
+
ClassDB::bind_method(D_METHOD("get_line_objects", "line"), &TextParagraph::get_line_objects);
ClassDB::bind_method(D_METHOD("get_line_object_rect", "line", "key"), &TextParagraph::get_line_object_rect);
ClassDB::bind_method(D_METHOD("get_line_size", "line"), &TextParagraph::get_line_size);
@@ -180,6 +185,7 @@ void TextParagraph::_shape_lines() {
for (int i = 0; i < line_breaks.size(); i = i + 2) {
RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
float h = (TS->shaped_text_get_orientation(line) == TextServer::ORIENTATION_HORIZONTAL) ? TS->shaped_text_get_size(line).y : TS->shaped_text_get_size(line).x;
+ h += line_spacing;
if (!tab_stops.is_empty()) {
TS->shaped_text_tab_align(line, tab_stops);
}
@@ -574,12 +580,18 @@ Size2 TextParagraph::get_size() const {
}
size.x = MAX(size.x, lsize.x);
size.y += lsize.y;
+ if (i != visible_lines - 1) {
+ size.y += line_spacing;
+ }
} else {
if (h_offset > 0 && i <= dropcap_lines) {
lsize.y += h_offset;
}
size.x += lsize.x;
size.y = MAX(size.y, lsize.y);
+ if (i != visible_lines - 1) {
+ size.x += line_spacing;
+ }
}
}
if (h_offset > 0) {
@@ -612,6 +624,19 @@ int TextParagraph::get_max_lines_visible() const {
return max_lines_visible;
}
+void TextParagraph::set_line_spacing(float p_spacing) {
+ _THREAD_SAFE_METHOD_
+
+ if (line_spacing != p_spacing) {
+ line_spacing = p_spacing;
+ lines_dirty = true;
+ }
+}
+
+float TextParagraph::get_line_spacing() const {
+ return line_spacing;
+}
+
Array TextParagraph::get_line_objects(int p_line) const {
_THREAD_SAFE_METHOD_
@@ -697,10 +722,10 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const {
if (i != p_line) {
if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
ofs.x = 0.f;
- ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
} else {
ofs.y = 0.f;
- ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
}
}
}
@@ -872,10 +897,10 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
TS->shaped_text_draw(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color);
if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
ofs.x = p_pos.x;
- ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
} else {
ofs.y = p_pos.y;
- ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
}
}
}
@@ -974,10 +999,10 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
TS->shaped_text_draw_outline(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color);
if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
ofs.x = p_pos.x;
- ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
} else {
ofs.y = p_pos.y;
- ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
+ ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
}
}
}
@@ -1001,17 +1026,21 @@ int TextParagraph::hit_test(const Point2 &p_coords) const {
if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(line_rid).y)) {
return TS->shaped_text_hit_test_position(line_rid, p_coords.x);
}
- ofs.y += TS->shaped_text_get_size(line_rid).y;
+ ofs.y += TS->shaped_text_get_size(line_rid).y + line_spacing;
} else {
if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(line_rid).x)) {
return TS->shaped_text_hit_test_position(line_rid, p_coords.y);
}
- ofs.y += TS->shaped_text_get_size(line_rid).x;
+ ofs.y += TS->shaped_text_get_size(line_rid).x + line_spacing;
}
}
return TS->shaped_text_get_range(rid).y;
}
+bool TextParagraph::is_dirty() {
+ return lines_dirty;
+}
+
void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color &p_color) const {
_THREAD_SAFE_METHOD_
diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h
index 8b7f21fa9a..966ce556d5 100644
--- a/scene/resources/text_paragraph.h
+++ b/scene/resources/text_paragraph.h
@@ -51,6 +51,7 @@ private:
bool lines_dirty = true;
+ float line_spacing = 0.0;
float width = -1.0;
int max_lines_visible = -1;
@@ -122,6 +123,9 @@ public:
void set_max_lines_visible(int p_lines);
int get_max_lines_visible() const;
+ void set_line_spacing(float p_spacing);
+ float get_line_spacing() const;
+
Size2 get_non_wrapped_size() const;
Size2 get_size() const;
@@ -152,6 +156,8 @@ public:
int hit_test(const Point2 &p_coords) const;
+ bool is_dirty();
+
Mutex &get_mutex() const { return _thread_safe_; }
TextParagraph(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", float p_width = -1.f, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL);
diff --git a/scene/resources/texture_rd.cpp b/scene/resources/texture_rd.cpp
index 531dbcbe7e..8e7eeb0ff4 100644
--- a/scene/resources/texture_rd.cpp
+++ b/scene/resources/texture_rd.cpp
@@ -197,7 +197,7 @@ void TextureLayeredRD::_set_texture_rd_rid(RID p_texture_rd_rid) {
RS::TextureLayeredType rs_layer_type;
RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid);
- ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D_ARRAY);
+ ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D_ARRAY && tf.texture_type != RD::TEXTURE_TYPE_CUBE && tf.texture_type != RD::TEXTURE_TYPE_CUBE_ARRAY);
ERR_FAIL_COND(tf.depth > 1);
switch (layer_type) {
case LAYERED_TYPE_2D_ARRAY: {
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 933e990920..847867fa4d 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1368,7 +1368,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por
ERR_FAIL_INDEX(p_type, TYPE_MAX);
Graph *g = &graph[p_type];
- for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) {
+ for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) {
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
g->connections.erase(E);
g->nodes[p_from_node].next_connected_nodes.erase(p_to_node);
@@ -1921,13 +1921,13 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
}
for (const KeyValue<String, Varying> &E : varyings) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", "varyings", E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
for (const KeyValue<String, Variant> &E : preview_params) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("preview_params"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", "preview_params", E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
#endif // TOOLS_ENABLED