summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/collision_object_2d.cpp4
-rw-r--r--scene/2d/physics_body_2d.cpp15
-rw-r--r--scene/2d/physics_body_2d.h2
-rw-r--r--scene/2d/tile_map.cpp749
-rw-r--r--scene/2d/tile_map.h67
-rw-r--r--scene/3d/area_3d.cpp2
-rw-r--r--scene/3d/audio_stream_player_3d.cpp2
-rw-r--r--scene/3d/bone_attachment_3d.cpp2
-rw-r--r--scene/3d/camera_3d.cpp2
-rw-r--r--scene/3d/collision_object_3d.cpp4
-rw-r--r--scene/3d/cpu_particles_3d.cpp2
-rw-r--r--scene/3d/decal.cpp1
-rw-r--r--scene/3d/gpu_particles_3d.cpp2
-rw-r--r--scene/3d/light_3d.cpp36
-rw-r--r--scene/3d/light_3d.h1
-rw-r--r--scene/3d/lightmap_gi.cpp1
-rw-r--r--scene/3d/node_3d.cpp103
-rw-r--r--scene/3d/node_3d.h35
-rw-r--r--scene/3d/path_3d.cpp1
-rw-r--r--scene/3d/physics_body_3d.cpp4
-rw-r--r--scene/3d/reflection_probe.cpp1
-rw-r--r--scene/3d/remote_transform_3d.cpp4
-rw-r--r--scene/3d/skeleton_ik_3d.cpp2
-rw-r--r--scene/3d/skeleton_ik_3d.h3
-rw-r--r--scene/3d/sprite_3d.cpp4
-rw-r--r--scene/3d/xr_nodes.cpp2
-rw-r--r--scene/animation/animation_player.cpp2
-rw-r--r--scene/audio/audio_stream_player.cpp2
-rw-r--r--scene/gui/button.cpp15
-rw-r--r--scene/gui/file_dialog.cpp6
-rw-r--r--scene/gui/line_edit.cpp17
-rw-r--r--scene/gui/line_edit.h4
-rw-r--r--scene/gui/link_button.cpp7
-rw-r--r--scene/gui/option_button.cpp6
-rw-r--r--scene/gui/rich_text_label.cpp25
-rw-r--r--scene/gui/rich_text_label.h3
-rw-r--r--scene/gui/tab_bar.cpp2
-rw-r--r--scene/gui/text_edit.cpp20
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/resources/animation.cpp6
-rw-r--r--scene/resources/default_theme/default_theme.cpp9
-rw-r--r--scene/resources/material.cpp2
-rw-r--r--scene/resources/skeleton_modification_stack_3d.cpp1
-rw-r--r--scene/resources/tile_set.cpp181
-rw-r--r--scene/resources/tile_set.h15
45 files changed, 1276 insertions, 102 deletions
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp
index 28facd09ce..4b348f12e6 100644
--- a/scene/2d/collision_object_2d.cpp
+++ b/scene/2d/collision_object_2d.cpp
@@ -50,7 +50,9 @@ void CollisionObject2D::_notification(int p_what) {
}
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
- RID space = get_world_2d()->get_space();
+ Ref<World2D> world_ref = get_world_2d();
+ ERR_FAIL_COND(!world_ref.is_valid());
+ RID space = world_ref->get_space();
if (area) {
PhysicsServer2D::get_singleton()->area_set_space(rid, space);
} else {
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 41288d646f..4d4d21bad7 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -1101,7 +1101,7 @@ bool CharacterBody2D::move_and_slide() {
}
if (motion_mode == MOTION_MODE_GROUNDED) {
- _move_and_slide_grounded(delta, was_on_floor, current_platform_velocity);
+ _move_and_slide_grounded(delta, was_on_floor);
} else {
_move_and_slide_free(delta);
}
@@ -1122,14 +1122,11 @@ bool CharacterBody2D::move_and_slide() {
return motion_results.size() > 0;
}
-void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) {
+void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor) {
Vector2 motion = motion_velocity * p_delta;
Vector2 motion_slide_up = motion.slide(up_direction);
Vector2 prev_floor_normal = floor_normal;
- RID prev_platform_rid = platform_rid;
- ObjectID prev_platform_object_id = platform_object_id;
- int prev_platform_layer = platform_layer;
platform_rid = RID();
platform_object_id = ObjectID();
@@ -1202,12 +1199,8 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
gt.elements[2] -= result.travel;
set_global_transform(gt);
}
- on_floor = true;
- platform_rid = prev_platform_rid;
- platform_object_id = prev_platform_object_id;
- platform_layer = prev_platform_layer;
- platform_velocity = p_prev_platform_velocity;
- floor_normal = prev_floor_normal;
+ // Determines if you are on the ground.
+ _snap_on_floor(true, false);
motion_velocity = Vector2();
last_motion = Vector2();
motion = Vector2();
diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h
index d1f52b33f2..15e8469bb4 100644
--- a/scene/2d/physics_body_2d.h
+++ b/scene/2d/physics_body_2d.h
@@ -416,7 +416,7 @@ private:
MovingPlatformApplyVelocityOnLeave get_moving_platform_apply_velocity_on_leave() const;
void _move_and_slide_free(double p_delta);
- void _move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity);
+ void _move_and_slide_grounded(double p_delta, bool p_was_on_floor);
Ref<KinematicCollision2D> _get_slide_collision(int p_bounce);
Ref<KinematicCollision2D> _get_last_slide_collision();
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index b546eaefa6..e54d1cd597 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -34,6 +34,324 @@
#include "servers/navigation_server_2d.h"
+Map<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const {
+ Map<Vector2i, TileSet::CellNeighbor> output;
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ // Half offset shapes.
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ }
+ }
+ return output;
+}
+
+TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
+ // The way we build the constraint make it easy to detect conflicting constraints.
+ tile_map = p_tile_map;
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ // Half-offset shapes
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ }
+ }
+ terrain = p_terrain;
+}
+
Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
// Transform to stacked layout.
Vector2i output = p_coords;
@@ -168,7 +486,6 @@ int TileMap::get_selected_layer() const {
void TileMap::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- pending_update = true;
_clear_internals();
_recreate_internals();
} break;
@@ -305,8 +622,8 @@ String TileMap::get_layer_name(int p_layer) const {
void TileMap::set_layer_enabled(int p_layer, bool p_enabled) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
layers[p_layer].enabled = p_enabled;
- _clear_internals();
- _recreate_internals();
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
emit_signal(SNAME("changed"));
update_configuration_warnings();
@@ -320,8 +637,8 @@ bool TileMap::is_layer_enabled(int p_layer) const {
void TileMap::set_layer_modulate(int p_layer, Color p_modulate) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
layers[p_layer].modulate = p_modulate;
- _clear_internals();
- _recreate_internals();
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
emit_signal(SNAME("changed"));
}
@@ -333,8 +650,8 @@ Color TileMap::get_layer_modulate(int p_layer) const {
void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
layers[p_layer].y_sort_enabled = p_y_sort_enabled;
- _clear_internals();
- _recreate_internals();
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
emit_signal(SNAME("changed"));
update_configuration_warnings();
@@ -348,8 +665,8 @@ bool TileMap::is_layer_y_sort_enabled(int p_layer) const {
void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
layers[p_layer].y_sort_origin = p_y_sort_origin;
- _clear_internals();
- _recreate_internals();
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
emit_signal(SNAME("changed"));
}
@@ -361,8 +678,8 @@ int TileMap::get_layer_y_sort_origin(int p_layer) const {
void TileMap::set_layer_z_index(int p_layer, int p_z_index) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
layers[p_layer].z_index = p_z_index;
- _clear_internals();
- _recreate_internals();
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
emit_signal(SNAME("changed"));
update_configuration_warnings();
@@ -486,8 +803,10 @@ void TileMap::_update_dirty_quadrants() {
}
for (unsigned int layer = 0; layer < layers.size(); layer++) {
+ SelfList<TileMapQuadrant>::List &dirty_quadrant_list = layers[layer].dirty_quadrant_list;
+
// Update the coords cache.
- for (SelfList<TileMapQuadrant> *q = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) {
+ for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) {
q->self()->map_to_world.clear();
q->self()->world_to_map.clear();
for (Set<Vector2i>::Element *E = q->self()->cells.front(); E; E = E->next()) {
@@ -498,15 +817,18 @@ void TileMap::_update_dirty_quadrants() {
}
}
+ // Find TileData that need a runtime modification.
+ _build_runtime_update_tile_data(dirty_quadrant_list);
+
// Call the update_dirty_quadrant method on plugins.
- _rendering_update_dirty_quadrants(layers[layer].dirty_quadrant_list);
- _physics_update_dirty_quadrants(layers[layer].dirty_quadrant_list);
- _navigation_update_dirty_quadrants(layers[layer].dirty_quadrant_list);
- _scenes_update_dirty_quadrants(layers[layer].dirty_quadrant_list);
+ _rendering_update_dirty_quadrants(dirty_quadrant_list);
+ _physics_update_dirty_quadrants(dirty_quadrant_list);
+ _navigation_update_dirty_quadrants(dirty_quadrant_list);
+ _scenes_update_dirty_quadrants(dirty_quadrant_list);
// Redraw the debug canvas_items.
RenderingServer *rs = RenderingServer::get_singleton();
- for (SelfList<TileMapQuadrant> *q = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) {
+ for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) {
rs->canvas_item_clear(q->self()->debug_canvas_item);
Transform2D xform;
xform.set_origin(map_to_world(q->self()->coords * get_effective_quadrant_size(layer)));
@@ -519,8 +841,13 @@ void TileMap::_update_dirty_quadrants() {
}
// Clear the list
- while (layers[layer].dirty_quadrant_list.first()) {
- layers[layer].dirty_quadrant_list.remove(layers[layer].dirty_quadrant_list.first());
+ while (dirty_quadrant_list.first()) {
+ // Clear the runtime tile data.
+ for (const KeyValue<Vector2i, TileData *> &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) {
+ memdelete(kv.value);
+ }
+
+ dirty_quadrant_list.remove(dirty_quadrant_list.first());
}
}
@@ -529,37 +856,43 @@ void TileMap::_update_dirty_quadrants() {
_recompute_rect_cache();
}
-void TileMap::_recreate_internals() {
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- // Make sure that _clear_internals() was called prior.
- ERR_FAIL_COND_MSG(layers[layer].quadrant_map.size() > 0, "TileMap layer " + itos(layer) + " had a non-empty quadrant map.");
+void TileMap::_recreate_layer_internals(int p_layer) {
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
- if (!layers[layer].enabled) {
- continue;
- }
+ // Make sure that _clear_internals() was called prior.
+ ERR_FAIL_COND_MSG(layers[p_layer].quadrant_map.size() > 0, "TileMap layer " + itos(p_layer) + " had a non-empty quadrant map.");
- // Upadate the layer internals.
- _rendering_update_layer(layer);
-
- // Recreate the quadrants.
- const Map<Vector2i, TileMapCell> &tile_map = layers[layer].tile_map;
- for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) {
- Vector2i qk = _coords_to_quadrant_coords(layer, Vector2i(E.key.x, E.key.y));
+ if (!layers[p_layer].enabled) {
+ return;
+ }
- Map<Vector2i, TileMapQuadrant>::Element *Q = layers[layer].quadrant_map.find(qk);
- if (!Q) {
- Q = _create_quadrant(layer, qk);
- layers[layer].dirty_quadrant_list.add(&Q->get().dirty_list_element);
- }
+ // Upadate the layer internals.
+ _rendering_update_layer(p_layer);
- Vector2i pk = E.key;
- Q->get().cells.insert(pk);
+ // Recreate the quadrants.
+ const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map;
+ for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) {
+ Vector2i qk = _coords_to_quadrant_coords(p_layer, Vector2i(E.key.x, E.key.y));
- _make_quadrant_dirty(Q);
+ Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk);
+ if (!Q) {
+ Q = _create_quadrant(p_layer, qk);
+ layers[p_layer].dirty_quadrant_list.add(&Q->get().dirty_list_element);
}
+
+ Vector2i pk = E.key;
+ Q->get().cells.insert(pk);
+
+ _make_quadrant_dirty(Q);
}
- _update_dirty_quadrants();
+ _queue_update_dirty_quadrants();
+}
+
+void TileMap::_recreate_internals() {
+ for (unsigned int layer = 0; layer < layers.size(); layer++) {
+ _recreate_layer_internals(layer);
+ }
}
void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) {
@@ -782,7 +1115,13 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
// Get the tile data.
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell.value)) {
+ tile_data = q.runtime_tile_data_cache[E_cell.value];
+ } else {
+ tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ }
+
Ref<ShaderMaterial> mat = tile_data->get_material();
int z_index = tile_data->get_z_index();
@@ -829,7 +1168,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
}
// Drawing the tile in the canvas item.
- draw_tile(canvas_item, E_cell.key - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate);
+ draw_tile(canvas_item, E_cell.key - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate, tile_data);
// --- Occluders ---
for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
@@ -946,7 +1285,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
}
}
-void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation) {
+void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override) {
ERR_FAIL_COND(!p_tile_set.is_valid());
ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
@@ -972,7 +1311,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
}
// Get tile data.
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
+ const TileData *tile_data = p_tile_data_override ? p_tile_data_override : Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
// Get the tile modulation.
Color modulate = tile_data->get_modulate() * p_modulation;
@@ -1131,8 +1470,12 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
-
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell->get())) {
+ tile_data = q.runtime_tile_data_cache[E_cell->get()];
+ } else {
+ tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ }
for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) {
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);
@@ -1324,7 +1667,12 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell->get())) {
+ tile_data = q.runtime_tile_data_cache[E_cell->get()];
+ } else {
+ tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ }
q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count());
for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) {
@@ -1408,7 +1756,12 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
- TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ const TileData *tile_data;
+ if (p_quadrant->runtime_tile_data_cache.has(E_cell->get())) {
+ tile_data = p_quadrant->runtime_tile_data_cache[E_cell->get()];
+ } else {
+ tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+ }
Transform2D xform;
xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos);
@@ -1784,6 +2137,243 @@ void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPat
}
}
+Set<TileSet::TerrainsPattern> TileMap::_get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints) {
+ if (!tile_set.is_valid()) {
+ return Set<TileSet::TerrainsPattern>();
+ }
+
+ // Returns all tiles compatible with the given constraints.
+ Set<TileSet::TerrainsPattern> compatible_terrain_tile_patterns;
+ for (TileSet::TerrainsPattern &terrain_pattern : tile_set->get_terrains_pattern_set(p_terrain_set)) {
+ int valid = true;
+ int in_pattern_count = 0;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ // Check if the bit is compatible with the constraints.
+ TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern[in_pattern_count]);
+ Set<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
+ if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
+ valid = false;
+ break;
+ }
+ in_pattern_count++;
+ }
+ }
+
+ if (valid) {
+ compatible_terrain_tile_patterns.insert(terrain_pattern);
+ }
+ }
+
+ return compatible_terrain_tile_patterns;
+}
+
+Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains) const {
+ if (!tile_set.is_valid()) {
+ return Set<TerrainConstraint>();
+ }
+
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<TerrainConstraint>());
+ ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Set<TerrainConstraint>());
+
+ // Build a set of dummy constraints get the constrained points.
+ Set<TerrainConstraint> dummy_constraints;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides.
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ dummy_constraints.insert(TerrainConstraint(this, E->get(), bit, -1));
+ }
+ }
+ }
+
+ // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
+ Set<TerrainConstraint> constraints;
+ for (Set<TerrainConstraint>::Element *E = dummy_constraints.front(); E; E = E->next()) {
+ TerrainConstraint c = E->get();
+
+ Map<int, int> terrain_count;
+
+ // Count the number of occurrences per terrain.
+ Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
+ for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) {
+ if (!p_to_replace.has(E_overlapping.key)) {
+ TileData *neighbor_tile_data = nullptr;
+ TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key);
+ if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) {
+ Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id);
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile));
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ neighbor_tile_data = tile_data;
+ }
+ }
+ }
+
+ int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping.value)) : -1;
+ if (!p_ignore_empty_terrains || terrain >= 0) {
+ if (!terrain_count.has(terrain)) {
+ terrain_count[terrain] = 0;
+ }
+ terrain_count[terrain] += 1;
+ }
+ }
+ }
+
+ // Get the terrain with the max number of occurrences.
+ int max = 0;
+ int max_terrain = -1;
+ for (const KeyValue<int, int> &E_terrain_count : terrain_count) {
+ if (E_terrain_count.value > max) {
+ max = E_terrain_count.value;
+ max_terrain = E_terrain_count.key;
+ }
+ }
+
+ // Set the adequate terrain.
+ if (max > 0) {
+ c.set_terrain(max_terrain);
+ constraints.insert(c);
+ }
+ }
+
+ return constraints;
+}
+
+Set<TileMap::TerrainConstraint> TileMap::get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const {
+ if (!tile_set.is_valid()) {
+ return Set<TerrainConstraint>();
+ }
+
+ // Compute the constraints needed from the surrounding tiles.
+ Set<TerrainConstraint> output;
+ int in_pattern_count = 0;
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
+ TerrainConstraint c = TerrainConstraint(this, p_position, side, p_terrains_pattern[in_pattern_count]);
+ output.insert(c);
+ in_pattern_count++;
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints) {
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileSet::TerrainsPattern>();
+ }
+
+ // Copy the constraints set.
+ Set<TerrainConstraint> constraints = p_constraints;
+
+ // Compute all acceptable patterns for each cell.
+ Map<Vector2i, Set<TileSet::TerrainsPattern>> per_cell_acceptable_tiles;
+ for (Vector2i cell : p_to_replace) {
+ per_cell_acceptable_tiles[cell] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, cell, constraints);
+ }
+
+ // Output map.
+ Map<Vector2i, TileSet::TerrainsPattern> output;
+
+ // Add all positions to a set.
+ Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace);
+ while (!to_replace.is_empty()) {
+ // Compute the minimum number of tile possibilities for each cell.
+ int min_nb_possibilities = 100000000;
+ for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) {
+ min_nb_possibilities = MIN(min_nb_possibilities, E.value.size());
+ }
+
+ // Get the set of possible cells to fill, out of the most constrained ones.
+ LocalVector<Vector2i> to_choose_from;
+ for (const KeyValue<Vector2i, Set<TileSet::TerrainsPattern>> &E : per_cell_acceptable_tiles) {
+ if (E.value.size() == min_nb_possibilities) {
+ to_choose_from.push_back(E.key);
+ }
+ }
+
+ // Randomly a cell to fill out of the most constrained.
+ Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)];
+
+ // Get the list of acceptable pattens for the given cell.
+ Set<TileSet::TerrainsPattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace];
+ if (valid_tiles.is_empty()) {
+ break; // No possibilities :/
+ }
+
+ // Out of the possible patterns, prioritize the one which have the least amount of different terrains.
+ LocalVector<TileSet::TerrainsPattern> valid_tiles_with_least_amount_of_terrains;
+ int min_terrain_count = 10000;
+ LocalVector<int> terrains_counts;
+ int pattern_index = 0;
+ for (const TileSet::TerrainsPattern &pattern : valid_tiles) {
+ Set<int> terrains;
+ for (int i = 0; i < pattern.size(); i++) {
+ terrains.insert(pattern[i]);
+ }
+ min_terrain_count = MIN(min_terrain_count, terrains.size());
+ terrains_counts.push_back(terrains.size());
+ pattern_index++;
+ }
+ pattern_index = 0;
+ for (const TileSet::TerrainsPattern &pattern : valid_tiles) {
+ if (terrains_counts[pattern_index] == min_terrain_count) {
+ valid_tiles_with_least_amount_of_terrains.push_back(pattern);
+ }
+ pattern_index++;
+ }
+
+ // Randomly select a pattern out of the remaining ones.
+ TileSet::TerrainsPattern selected_terrain_tile_pattern = valid_tiles_with_least_amount_of_terrains[Math::random(0, valid_tiles_with_least_amount_of_terrains.size() - 1)];
+
+ // Set the selected cell into the output.
+ output[selected_cell_to_replace] = selected_terrain_tile_pattern;
+ to_replace.erase(selected_cell_to_replace);
+ per_cell_acceptable_tiles.erase(selected_cell_to_replace);
+
+ // Add the new constraints from the added tiles.
+ Set<TerrainConstraint> new_constraints = get_terrain_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern);
+ for (Set<TerrainConstraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) {
+ constraints.insert(E_constraint->get());
+ }
+
+ // Compute valid tiles again for neighbors.
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (is_existing_neighbor(side)) {
+ Vector2i neighbor = get_neighbor_cell(selected_cell_to_replace, side);
+ if (to_replace.has(neighbor)) {
+ per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_patterns_for_constraints(p_terrain_set, neighbor, constraints);
+ }
+ }
+ }
+ }
+ return output;
+}
+
+void TileMap::set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+ ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
+
+ Set<Vector2i> coords_set;
+ for (int i = 0; i < p_coords_array.size(); i++) {
+ coords_set.insert(p_coords_array[i]);
+ }
+
+ Set<TileMap::TerrainConstraint> constraints = get_terrain_constraints_from_removed_cells_list(p_layer, coords_set, p_terrain_set, p_ignore_empty_terrains);
+
+ Map<Vector2i, TileSet::TerrainsPattern> wfc_output = terrain_wave_function_collapse(coords_set, p_terrain_set, constraints);
+ for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : wfc_output) {
+ TileMapCell cell = tile_set->get_random_tile_from_pattern(p_terrain_set, kv.value);
+ set_cell(p_layer, kv.key, cell.source_id, cell.get_atlas_coords(), cell.alternative_tile);
+ }
+}
+
TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const {
ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileMapCell());
const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map;
@@ -1849,6 +2439,17 @@ void TileMap::clear() {
used_rect_cache_dirty = true;
}
+void TileMap::force_update(int p_layer) {
+ if (p_layer >= 0) {
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+ _clear_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
+ } else {
+ _clear_internals();
+ _recreate_internals();
+ }
+}
+
void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
ERR_FAIL_COND(format > FORMAT_3);
@@ -1958,6 +2559,44 @@ Vector<int> TileMap::_get_tile_data(int p_layer) const {
return data;
}
+void TileMap::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
+ if (GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) {
+ SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+ // Iterate over the cells of the quadrant.
+ for (const KeyValue<Vector2i, Vector2i> &E_cell : q.world_to_map) {
+ TileMapCell c = get_cell(q.layer, E_cell.value, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ bool ret = false;
+ if (GDVIRTUAL_CALL(_use_tile_data_runtime_update, q.layer, E_cell.value, ret) && ret) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));
+
+ // Create the runtime TileData.
+ TileData *tile_data_runtime_use = tile_data->duplicate();
+ tile_data->set_allow_transform(true);
+ q.runtime_tile_data_cache[E_cell.value] = tile_data_runtime_use;
+
+ GDVIRTUAL_CALL(_tile_data_runtime_update, q.layer, E_cell.value, tile_data_runtime_use);
+ }
+ }
+ }
+ }
+ q_list_element = q_list_element->next();
+ }
+ }
+}
+
#ifdef TOOLS_ENABLED
Rect2 TileMap::_edit_get_rect() const {
// Return the visible rect of the tilemap
@@ -2989,10 +3628,14 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMap::map_pattern);
ClassDB::bind_method(D_METHOD("set_pattern", "layer", "position", "pattern"), &TileMap::set_pattern);
+ ClassDB::bind_method(D_METHOD("set_cells_from_surrounding_terrains", "layer", "cells", "terrain_set", "ignore_empty_terrains"), &TileMap::set_cells_from_surrounding_terrains, DEFVAL(true));
+
ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles);
ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer);
ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear);
+ ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1));
+
ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles);
ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells);
@@ -3010,6 +3653,9 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileMap::_tile_set_changed_deferred_update);
+ GDVIRTUAL_BIND(_use_tile_data_runtime_update, "layer", "coords");
+ GDVIRTUAL_BIND(_tile_data_runtime_update, "layer", "coords", "tile_data");
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable");
@@ -3030,7 +3676,7 @@ void TileMap::_bind_methods() {
void TileMap::_tile_set_changed() {
emit_signal(SNAME("changed"));
_tile_set_changed_deferred_update_needed = true;
- call_deferred("_tile_set_changed_deferred_update");
+ call_deferred(SNAME("_tile_set_changed_deferred_update"));
}
void TileMap::_tile_set_changed_deferred_update() {
@@ -3052,5 +3698,6 @@ TileMap::~TileMap() {
if (tile_set.is_valid()) {
tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed));
}
+
_clear_internals();
}
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index e1f38a314c..f260422290 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -79,6 +79,9 @@ struct TileMapQuadrant {
// Scenes.
Map<Vector2i, String> scenes;
+ // Runtime TileData cache.
+ Map<Vector2i, TileData *> runtime_tile_data_cache;
+
void operator=(const TileMapQuadrant &q) {
layer = q.layer;
coords = q.coords;
@@ -109,6 +112,43 @@ class TileMap : public Node2D {
GDCLASS(TileMap, Node2D);
public:
+ class TerrainConstraint {
+ private:
+ const TileMap *tile_map;
+ Vector2i base_cell_coords = Vector2i();
+ int bit = -1;
+ int terrain = -1;
+
+ public:
+ bool operator<(const TerrainConstraint &p_other) const {
+ if (base_cell_coords == p_other.base_cell_coords) {
+ return bit < p_other.bit;
+ }
+ return base_cell_coords < p_other.base_cell_coords;
+ }
+
+ String to_string() const {
+ return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
+ }
+
+ Vector2i get_base_cell_coords() const {
+ return base_cell_coords;
+ }
+
+ Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
+
+ void set_terrain(int p_terrain) {
+ terrain = p_terrain;
+ }
+
+ int get_terrain() const {
+ return terrain;
+ }
+
+ TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
+ TerrainConstraint() {}
+ };
+
enum VisibilityMode {
VISIBILITY_MODE_DEFAULT,
VISIBILITY_MODE_FORCE_SHOW,
@@ -174,6 +214,7 @@ private:
void _update_dirty_quadrants();
+ void _recreate_layer_internals(int p_layer);
void _recreate_internals();
void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q);
@@ -209,10 +250,15 @@ private:
void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant);
void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant);
+ // Terrains.
+ Set<TileSet::TerrainsPattern> _get_valid_terrains_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TerrainConstraint> p_constraints);
+
// Set and get tiles from data arrays.
void _set_tile_data(int p_layer, const Vector<int> &p_data);
Vector<int> _get_tile_data(int p_layer) const;
+ void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list);
+
void _tile_set_changed();
bool _tile_set_changed_deferred_update_needed = false;
void _tile_set_changed_deferred_update();
@@ -242,7 +288,7 @@ public:
void set_quadrant_size(int p_size);
int get_quadrant_size() const;
- static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));
+ static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr);
// Layers management.
int get_layers_count() const;
@@ -267,21 +313,30 @@ public:
void set_collision_animatable(bool p_enabled);
bool is_collision_animatable() const;
+ // Debug visibility modes.
void set_collision_visibility_mode(VisibilityMode p_show_collision);
VisibilityMode get_collision_visibility_mode();
void set_navigation_visibility_mode(VisibilityMode p_show_navigation);
VisibilityMode get_navigation_visibility_mode();
+ // Cells accessors.
void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE);
int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
+ // Patterns.
Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern);
+ // Terrains.
+ Set<TerrainConstraint> get_terrain_constraints_from_removed_cells_list(int p_layer, const Set<Vector2i> &p_to_replace, int p_terrain_set, bool p_ignore_empty_terrains = true) const; // Not exposed.
+ Set<TerrainConstraint> get_terrain_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; // Not exposed.
+ Map<Vector2i, TileSet::TerrainsPattern> terrain_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TerrainConstraint> p_constraints); // Not exposed.
+ void set_cells_from_surrounding_terrains(int p_layer, TypedArray<Vector2i> p_coords_array, int p_terrain_set, bool p_ignore_empty_terrains = true);
+
// Not exposed to users
TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const;
Map<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer);
@@ -312,13 +367,21 @@ public:
// Fixing a nclearing methods.
void fix_invalid_tiles();
+ // Clears tiles from a given layer
void clear_layer(int p_layer);
void clear();
- // Helpers
+ // Force a TileMap update
+ void force_update(int p_layer = -1);
+
+ // Helpers?
TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords);
void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D());
+ // Virtual function to modify the TileData at runtime
+ GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i);
+ GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *);
+
// Configuration warnings.
TypedArray<String> get_configuration_warnings() const override;
diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp
index d411525707..9179983220 100644
--- a/scene/3d/area_3d.cpp
+++ b/scene/3d/area_3d.cpp
@@ -580,6 +580,8 @@ void Area3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ CollisionObject3D::_validate_property(property);
}
void Area3D::_bind_methods() {
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 44a685f506..261acbeeb9 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -638,6 +638,8 @@ void AudioStreamPlayer3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ Node3D::_validate_property(property);
}
void AudioStreamPlayer3D::_bus_layout_changed() {
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 8e89f4fc54..5dc7382197 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -58,6 +58,8 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
+
+ Node3D::_validate_property(property);
}
bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) {
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 588d2b5018..af3b3ae5bc 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -71,6 +71,8 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NOEDITOR;
}
}
+
+ Node3D::_validate_property(p_property);
}
void Camera3D::_update_camera() {
diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp
index fd891a5e13..a166a05c71 100644
--- a/scene/3d/collision_object_3d.cpp
+++ b/scene/3d/collision_object_3d.cpp
@@ -64,7 +64,9 @@ void CollisionObject3D::_notification(int p_what) {
}
if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
- RID space = get_world_3d()->get_space();
+ Ref<World3D> world_ref = get_world_3d();
+ ERR_FAIL_COND(!world_ref.is_valid());
+ RID space = world_ref->get_space();
if (area) {
PhysicsServer3D::get_singleton()->area_set_space(rid, space);
} else {
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 55feb02fce..f1e800988d 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -532,6 +532,8 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const {
if (property.name.begins_with("scale_curve_") && !split_scale) {
property.usage = PROPERTY_USAGE_NONE;
}
+
+ Node3D::_validate_property(property);
}
static uint32_t idhash(uint32_t x) {
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index c94a99a203..e3c63d62f9 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -160,6 +160,7 @@ void Decal::_validate_property(PropertyInfo &property) const {
if (!distance_fade_enabled && (property.name == "distance_fade_begin" || property.name == "distance_fade_length")) {
property.usage = PROPERTY_USAGE_NOEDITOR;
}
+ VisualInstance3D::_validate_property(property);
}
TypedArray<String> Decal::get_configuration_warnings() const {
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index ea6242b669..13cb8b7dfb 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -388,6 +388,8 @@ void GPUParticles3D::_validate_property(PropertyInfo &property) const {
return;
}
}
+
+ GeometryInstance3D::_validate_property(property);
}
void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {
diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp
index c787ba5087..fbdbd79c0c 100644
--- a/scene/3d/light_3d.cpp
+++ b/scene/3d/light_3d.cpp
@@ -200,21 +200,11 @@ void Light3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR;
}
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_size") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_specular") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
- if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_projector") {
- property.usage = PROPERTY_USAGE_NONE;
- }
-
if (get_light_type() != RS::LIGHT_DIRECTIONAL && property.name == "light_angular_distance") {
+ // Angular distance is only used in DirectionalLight3D.
property.usage = PROPERTY_USAGE_NONE;
}
+ VisualInstance3D::_validate_property(property);
}
void Light3D::_bind_methods() {
@@ -361,6 +351,7 @@ Light3D::~Light3D() {
void DirectionalLight3D::set_shadow_mode(ShadowMode p_mode) {
shadow_mode = p_mode;
RS::get_singleton()->light_directional_set_shadow_mode(light, RS::LightDirectionalShadowMode(p_mode));
+ notify_property_list_changed();
}
DirectionalLight3D::ShadowMode DirectionalLight3D::get_shadow_mode() const {
@@ -385,6 +376,25 @@ bool DirectionalLight3D::is_sky_only() const {
return sky_only;
}
+void DirectionalLight3D::_validate_property(PropertyInfo &property) const {
+ if (shadow_mode == SHADOW_ORTHOGONAL && (property.name == "directional_shadow_split_1" || property.name == "directional_shadow_blend_splits")) {
+ // Split 2 and split blending are only used with the PSSM 2 Splits and PSSM 4 Splits shadow modes.
+ property.usage = PROPERTY_USAGE_NOEDITOR;
+ }
+
+ if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS) && (property.name == "directional_shadow_split_2" || property.name == "directional_shadow_split_3")) {
+ // Splits 3 and 4 are only used with the PSSM 4 Splits shadow mode.
+ property.usage = PROPERTY_USAGE_NOEDITOR;
+ }
+
+ if (property.name == "light_size" || property.name == "light_projector" || property.name == "light_specular") {
+ // Not implemented in DirectionalLight3D (`light_size` is replaced by `light_angular_distance`).
+ property.usage = PROPERTY_USAGE_NONE;
+ }
+
+ Light3D::_validate_property(property);
+}
+
void DirectionalLight3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_mode", "mode"), &DirectionalLight3D::set_shadow_mode);
ClassDB::bind_method(D_METHOD("get_shadow_mode"), &DirectionalLight3D::get_shadow_mode);
@@ -400,8 +410,8 @@ void DirectionalLight3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_1", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_1_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_2", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_2_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_split_3", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_3_OFFSET);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,8192,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_shadow_pancake_size", PROPERTY_HINT_RANGE, "0,1024,0.1,or_greater,exp"), "set_param", "get_param", PARAM_SHADOW_PANCAKE_SIZE);
diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h
index f788c323f7..a9f5ce27b4 100644
--- a/scene/3d/light_3d.h
+++ b/scene/3d/light_3d.h
@@ -152,6 +152,7 @@ private:
protected:
static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
public:
void set_shadow_mode(ShadowMode p_mode);
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index b56f0fd145..3bcb6add76 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1370,6 +1370,7 @@ void LightmapGI::_validate_property(PropertyInfo &property) const {
if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
property.usage = PROPERTY_USAGE_NONE;
}
+ VisualInstance3D::_validate_property(property);
}
void LightmapGI::_bind_methods() {
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 5293dd3f0a..8a39d4d30f 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -220,6 +220,13 @@ void Node3D::_notification(int p_what) {
}
}
+void Node3D::set_basis(const Basis &p_basis) {
+ set_transform(Transform3D(p_basis, data.local_transform.origin));
+}
+void Node3D::set_quaternion(const Quaternion &p_quaternion) {
+ set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin));
+}
+
void Node3D::set_transform(const Transform3D &p_transform) {
data.local_transform = p_transform;
data.dirty |= DIRTY_VECTORS;
@@ -229,6 +236,13 @@ void Node3D::set_transform(const Transform3D &p_transform) {
}
}
+Basis Node3D::get_basis() const {
+ return get_transform().basis;
+}
+Quaternion Node3D::get_quaternion() const {
+ return Quaternion(get_transform().basis);
+}
+
void Node3D::set_global_transform(const Transform3D &p_transform) {
Transform3D xform =
(data.parent && !data.top_level_active) ?
@@ -308,6 +322,45 @@ void Node3D::set_position(const Vector3 &p_position) {
}
}
+void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
+ if (data.rotation_edit_mode == p_mode) {
+ return;
+ }
+ data.rotation_edit_mode = p_mode;
+ notify_property_list_changed();
+}
+
+Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const {
+ return data.rotation_edit_mode;
+}
+
+void Node3D::set_rotation_order(RotationOrder p_order) {
+ Basis::EulerOrder order = Basis::EulerOrder(p_order);
+
+ if (data.rotation_order == order) {
+ return;
+ }
+
+ ERR_FAIL_INDEX(int32_t(order), 6);
+
+ if (data.dirty & DIRTY_VECTORS) {
+ data.rotation = data.local_transform.basis.get_euler_normalized(order);
+ data.scale = data.local_transform.basis.get_scale();
+ data.dirty &= ~DIRTY_VECTORS;
+ } else {
+ data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order);
+ }
+
+ data.rotation_order = order;
+ //changing rotation order should not affect transform
+
+ notify_property_list_changed(); //will change rotation
+}
+
+Node3D::RotationOrder Node3D::get_rotation_order() const {
+ return RotationOrder(data.rotation_order);
+}
+
void Node3D::set_rotation(const Vector3 &p_euler_rad) {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
@@ -324,7 +377,7 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) {
void Node3D::set_scale(const Vector3 &p_scale) {
if (data.dirty & DIRTY_VECTORS) {
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -343,7 +396,7 @@ Vector3 Node3D::get_position() const {
Vector3 Node3D::get_rotation() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -354,7 +407,7 @@ Vector3 Node3D::get_rotation() const {
Vector3 Node3D::get_scale() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
- data.rotation = data.local_transform.basis.get_rotation();
+ data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@@ -780,6 +833,24 @@ NodePath Node3D::get_visibility_parent() const {
return visibility_parent_path;
}
+void Node3D::_validate_property(PropertyInfo &property) const {
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_BASIS && property.name == "basis") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && property.name == "scale") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_QUATERNION && property.name == "quaternion") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation") {
+ property.usage = 0;
+ }
+ if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation_order") {
+ property.usage = 0;
+ }
+}
+
void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transform", "local"), &Node3D::set_transform);
ClassDB::bind_method(D_METHOD("get_transform"), &Node3D::get_transform);
@@ -787,8 +858,16 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &Node3D::get_position);
ClassDB::bind_method(D_METHOD("set_rotation", "euler"), &Node3D::set_rotation);
ClassDB::bind_method(D_METHOD("get_rotation"), &Node3D::get_rotation);
+ ClassDB::bind_method(D_METHOD("set_rotation_order", "order"), &Node3D::set_rotation_order);
+ ClassDB::bind_method(D_METHOD("get_rotation_order"), &Node3D::get_rotation_order);
+ ClassDB::bind_method(D_METHOD("set_rotation_edit_mode", "edit_mode"), &Node3D::set_rotation_edit_mode);
+ ClassDB::bind_method(D_METHOD("get_rotation_edit_mode"), &Node3D::get_rotation_edit_mode);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node3D::set_scale);
ClassDB::bind_method(D_METHOD("get_scale"), &Node3D::get_scale);
+ ClassDB::bind_method(D_METHOD("set_quaternion", "quaternion"), &Node3D::set_quaternion);
+ ClassDB::bind_method(D_METHOD("get_quaternion"), &Node3D::get_quaternion);
+ ClassDB::bind_method(D_METHOD("set_basis", "basis"), &Node3D::set_basis);
+ ClassDB::bind_method(D_METHOD("get_basis"), &Node3D::get_basis);
ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
ClassDB::bind_method(D_METHOD("get_parent_node_3d"), &Node3D::get_parent_node_3d);
@@ -848,15 +927,29 @@ void Node3D::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_EXIT_WORLD);
BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_EULER);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_QUATERNION);
+ BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_BASIS);
+
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_XYZ);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_XZY);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_YXZ);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_YZX);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_ZXY);
+ BIND_ENUM_CONSTANT(ROTATION_ORDER_ZYX);
+
//ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM3D,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ;
ADD_GROUP("Transform", "");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation");
+ ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion");
+ ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_edit_mode", PROPERTY_HINT_ENUM, "Euler,Quaternion,Basis"), "set_rotation_edit_mode", "get_rotation_edit_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_order", PROPERTY_HINT_ENUM, "XYZ,XZY,YXZ,YZX,ZXY,ZYX"), "set_rotation_order", "get_rotation_order");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level");
- ADD_GROUP("Matrix", "");
- ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, ""), "set_transform", "get_transform");
+ ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_transform", "get_transform");
ADD_GROUP("Visibility", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent");
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index d6dcdd96fe..3e21eb12be 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -51,6 +51,23 @@ class Node3D : public Node {
GDCLASS(Node3D, Node);
OBJ_CATEGORY("3D");
+public:
+ enum RotationEditMode {
+ ROTATION_EDIT_MODE_EULER,
+ ROTATION_EDIT_MODE_QUATERNION,
+ ROTATION_EDIT_MODE_BASIS,
+ };
+
+ enum RotationOrder {
+ ROTATION_ORDER_XYZ,
+ ROTATION_ORDER_XZY,
+ ROTATION_ORDER_YXZ,
+ ROTATION_ORDER_YZX,
+ ROTATION_ORDER_ZXY,
+ ROTATION_ORDER_ZYX
+ };
+
+private:
enum TransformDirty {
DIRTY_NONE = 0,
DIRTY_VECTORS = 1,
@@ -63,8 +80,10 @@ class Node3D : public Node {
struct Data {
mutable Transform3D global_transform;
mutable Transform3D local_transform;
+ mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ;
mutable Vector3 rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
+ mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
mutable int dirty = DIRTY_NONE;
@@ -116,6 +135,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
+
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED,
@@ -130,17 +151,28 @@ public:
Ref<World3D> get_world_3d() const;
void set_position(const Vector3 &p_position);
+
+ void set_rotation_edit_mode(RotationEditMode p_mode);
+ RotationEditMode get_rotation_edit_mode() const;
+
+ void set_rotation_order(RotationOrder p_order);
void set_rotation(const Vector3 &p_euler_rad);
void set_scale(const Vector3 &p_scale);
Vector3 get_position() const;
+
+ RotationOrder get_rotation_order() const;
Vector3 get_rotation() const;
Vector3 get_scale() const;
void set_transform(const Transform3D &p_transform);
+ void set_basis(const Basis &p_basis);
+ void set_quaternion(const Quaternion &p_quaternion);
void set_global_transform(const Transform3D &p_transform);
Transform3D get_transform() const;
+ Basis get_basis() const;
+ Quaternion get_quaternion() const;
Transform3D get_global_transform() const;
#ifdef TOOLS_ENABLED
@@ -214,4 +246,7 @@ public:
Node3D();
};
+VARIANT_ENUM_CAST(Node3D::RotationEditMode)
+VARIANT_ENUM_CAST(Node3D::RotationOrder)
+
#endif // NODE_3D_H
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 9ea37e4bfa..a0eac76e39 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -248,6 +248,7 @@ void PathFollow3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater";
}
+ Node3D::_validate_property(property);
}
TypedArray<String> PathFollow3D::get_configuration_warnings() const {
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 8cb348d6e3..4f1003839e 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -1059,6 +1059,7 @@ void RigidDynamicBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
RigidDynamicBody3D::RigidDynamicBody3D() :
@@ -1933,6 +1934,7 @@ void CharacterBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
CharacterBody3D::CharacterBody3D() :
@@ -3060,7 +3062,7 @@ void PhysicalBone3D::set_joint_rotation(const Vector3 &p_euler_rad) {
}
Vector3 PhysicalBone3D::get_joint_rotation() const {
- return joint_offset.basis.get_rotation();
+ return joint_offset.basis.get_euler_normalized();
}
const Transform3D &PhysicalBone3D::get_body_offset() const {
diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp
index 719dbedd94..29c382cd05 100644
--- a/scene/3d/reflection_probe.cpp
+++ b/scene/3d/reflection_probe.cpp
@@ -188,6 +188,7 @@ void ReflectionProbe::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ VisualInstance3D::_validate_property(property);
}
void ReflectionProbe::_bind_methods() {
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index d5fb1fa6ab..d890609e23 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -68,7 +68,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_global_transform();
if (update_remote_rotation) {
- n->set_rotation(our_trans.basis.get_rotation());
+ n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {
@@ -90,7 +90,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_transform();
if (update_remote_rotation) {
- n->set_rotation(our_trans.basis.get_rotation());
+ n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 2e788051f4..1498955ec0 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -351,6 +351,8 @@ void SkeletonIK3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
+
+ Node::_validate_property(property);
}
void SkeletonIK3D::_bind_methods() {
diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h
index 4cf08e7c99..ccb25bcd4c 100644
--- a/scene/3d/skeleton_ik_3d.h
+++ b/scene/3d/skeleton_ik_3d.h
@@ -137,8 +137,7 @@ class SkeletonIK3D : public Node {
FabrikInverseKinematic::Task *task = nullptr;
protected:
- virtual void
- _validate_property(PropertyInfo &property) const override;
+ virtual void _validate_property(PropertyInfo &property) const override;
static void _bind_methods();
virtual void _notification(int p_what);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 349a534680..90af70e7c2 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -743,6 +743,8 @@ void Sprite3D::_validate_property(PropertyInfo &property) const {
if (property.name == "frame_coords") {
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ SpriteBase3D::_validate_property(property);
}
void Sprite3D::_bind_methods() {
@@ -1015,6 +1017,8 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const {
}
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ SpriteBase3D::_validate_property(property);
}
void AnimatedSprite3D::_notification(int p_what) {
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 9dbee58f0e..a16820cbdc 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -261,6 +261,8 @@ void XRNode3D::_validate_property(PropertyInfo &property) const {
}
property.hint_string = hint_string;
}
+
+ Node3D::_validate_property(property);
}
void XRNode3D::set_tracker(const StringName p_tracker_name) {
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 1f000d5f39..26caf826a7 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -166,6 +166,8 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = hint;
}
+
+ Node::_validate_property(property);
}
void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index d2fd65d3d9..5065f21604 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -292,6 +292,8 @@ void AudioStreamPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ Node::_validate_property(property);
}
void AudioStreamPlayer::_bus_layout_changed() {
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index e253a27e66..9818c8f0cc 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -111,9 +111,18 @@ void Button::_notification(int p_what) {
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
- color = get_theme_color(SNAME("font_color"));
- if (has_theme_color(SNAME("icon_normal_color"))) {
- color_icon = get_theme_color(SNAME("icon_normal_color"));
+
+ // Focus colors only take precedence over normal state.
+ if (has_focus()) {
+ color = get_theme_color(SNAME("font_focus_color"));
+ if (has_theme_color(SNAME("icon_focus_color"))) {
+ color_icon = get_theme_color(SNAME("icon_focus_color"));
+ }
+ } else {
+ color = get_theme_color(SNAME("font_color"));
+ if (has_theme_color(SNAME("icon_normal_color"))) {
+ color_icon = get_theme_color(SNAME("icon_normal_color"));
+ }
}
} break;
case DRAW_HOVER_PRESSED: {
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 973b72973d..bb77da9548 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -51,26 +51,32 @@ VBoxContainer *FileDialog::get_vbox() {
void FileDialog::_theme_changed() {
Color font_color = vbox->get_theme_color(SNAME("font_color"), SNAME("Button"));
Color font_hover_color = vbox->get_theme_color(SNAME("font_hover_color"), SNAME("Button"));
+ Color font_focus_color = vbox->get_theme_color(SNAME("font_focus_color"), SNAME("Button"));
Color font_pressed_color = vbox->get_theme_color(SNAME("font_pressed_color"), SNAME("Button"));
dir_up->add_theme_color_override("icon_normal_color", font_color);
dir_up->add_theme_color_override("icon_hover_color", font_hover_color);
+ dir_up->add_theme_color_override("icon_focus_color", font_focus_color);
dir_up->add_theme_color_override("icon_pressed_color", font_pressed_color);
dir_prev->add_theme_color_override("icon_color_normal", font_color);
dir_prev->add_theme_color_override("icon_color_hover", font_hover_color);
+ dir_prev->add_theme_color_override("icon_focus_color", font_focus_color);
dir_prev->add_theme_color_override("icon_color_pressed", font_pressed_color);
dir_next->add_theme_color_override("icon_color_normal", font_color);
dir_next->add_theme_color_override("icon_color_hover", font_hover_color);
+ dir_next->add_theme_color_override("icon_focus_color", font_focus_color);
dir_next->add_theme_color_override("icon_color_pressed", font_pressed_color);
refresh->add_theme_color_override("icon_normal_color", font_color);
refresh->add_theme_color_override("icon_hover_color", font_hover_color);
+ refresh->add_theme_color_override("icon_focus_color", font_focus_color);
refresh->add_theme_color_override("icon_pressed_color", font_pressed_color);
show_hidden->add_theme_color_override("icon_normal_color", font_color);
show_hidden->add_theme_color_override("icon_hover_color", font_hover_color);
+ show_hidden->add_theme_color_override("icon_focus_color", font_focus_color);
show_hidden->add_theme_color_override("icon_pressed_color", font_pressed_color);
}
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index dda1151273..8c33d306a1 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -920,6 +920,9 @@ void LineEdit::_notification(int p_what) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
+ if (deselect_on_focus_loss_enabled) {
+ deselect();
+ }
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
@@ -1938,6 +1941,17 @@ bool LineEdit::is_selecting_enabled() const {
return selecting_enabled;
}
+void LineEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
+ deselect_on_focus_loss_enabled = p_enabled;
+ if (p_enabled && selection.enabled && !has_focus()) {
+ deselect();
+ }
+}
+
+bool LineEdit::is_deselect_on_focus_loss_enabled() const {
+ return deselect_on_focus_loss_enabled;
+}
+
void LineEdit::set_right_icon(const Ref<Texture2D> &p_icon) {
if (right_icon == p_icon) {
return;
@@ -2196,6 +2210,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &LineEdit::is_middle_mouse_paste_enabled);
ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &LineEdit::set_selecting_enabled);
ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &LineEdit::is_selecting_enabled);
+ ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &LineEdit::set_deselect_on_focus_loss_enabled);
+ ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &LineEdit::is_deselect_on_focus_loss_enabled);
ClassDB::bind_method(D_METHOD("set_right_icon", "icon"), &LineEdit::set_right_icon);
ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon);
@@ -2251,6 +2267,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 94179ce05b..3364e02e01 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -97,6 +97,7 @@ private:
float full_width = 0.0;
bool selecting_enabled = true;
+ bool deselect_on_focus_loss_enabled = true;
bool context_menu_enabled = true;
PopupMenu *menu = nullptr;
@@ -325,6 +326,9 @@ public:
void set_selecting_enabled(bool p_enabled);
bool is_selecting_enabled() const;
+ void set_deselect_on_focus_loss_enabled(const bool p_enabled);
+ bool is_deselect_on_focus_loss_enabled() const;
+
void set_right_icon(const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_right_icon();
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 925e6f5b97..c3201186ea 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -168,7 +168,12 @@ void LinkButton::_notification(int p_what) {
switch (get_draw_mode()) {
case DRAW_NORMAL: {
- color = get_theme_color(SNAME("font_color"));
+ if (has_focus()) {
+ color = get_theme_color(SNAME("font_focus_color"));
+ } else {
+ color = get_theme_color(SNAME("font_color"));
+ }
+
do_underline = underline_mode == UNDERLINE_MODE_ALWAYS;
} break;
case DRAW_HOVER_PRESSED:
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 2adeb2d947..c00c040048 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -71,7 +71,11 @@ void OptionButton::_notification(int p_what) {
clr = get_theme_color(SNAME("font_disabled_color"));
break;
default:
- clr = get_theme_color(SNAME("font_color"));
+ if (has_focus()) {
+ clr = get_theme_color(SNAME("font_focus_color"));
+ } else {
+ clr = get_theme_color(SNAME("font_color"));
+ }
}
}
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 31767dd263..f1efbbda98 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1491,7 +1491,13 @@ void RichTextLabel::_notification(int p_what) {
_update_fx(main, dt);
update();
}
- }
+ } break;
+ case NOTIFICATION_FOCUS_EXIT: {
+ if (deselect_on_focus_loss_enabled) {
+ selection.active = false;
+ update();
+ }
+ } break;
}
}
@@ -3609,6 +3615,14 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
}
}
+void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
+ deselect_on_focus_loss_enabled = p_enabled;
+ if (p_enabled && selection.active && !has_focus()) {
+ selection.active = false;
+ update();
+ }
+}
+
bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) {
List<Item *>::Element *E = p_from;
while (E != nullptr) {
@@ -3858,6 +3872,10 @@ bool RichTextLabel::is_selection_enabled() const {
return selection.enabled;
}
+bool RichTextLabel::is_deselect_on_focus_loss_enabled() const {
+ return deselect_on_focus_loss_enabled;
+}
+
int RichTextLabel::get_selection_from() const {
if (!selection.active || !selection.enabled) {
return -1;
@@ -4111,6 +4129,9 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selection_enabled", "enabled"), &RichTextLabel::set_selection_enabled);
ClassDB::bind_method(D_METHOD("is_selection_enabled"), &RichTextLabel::is_selection_enabled);
+ ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &RichTextLabel::set_deselect_on_focus_loss_enabled);
+ ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &RichTextLabel::is_deselect_on_focus_loss_enabled);
+
ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from);
ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to);
@@ -4162,6 +4183,8 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 48186ca8b8..6d772876ad 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -399,6 +399,7 @@ private:
};
Selection selection;
+ bool deselect_on_focus_loss_enabled = true;
int visible_characters = -1;
float percent_visible = 1.0;
@@ -551,6 +552,8 @@ public:
int get_selection_to() const;
String get_selected_text() const;
void selection_copy();
+ void set_deselect_on_focus_loss_enabled(const bool p_enabled);
+ bool is_deselect_on_focus_loss_enabled() const;
void parse_bbcode(const String &p_bbcode);
void append_text(const String &p_bbcode);
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 78b58c773a..780614302c 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -222,7 +222,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (max_drawn_tab <= 0) {
+ if (tabs.is_empty()) {
// Return early if there are no actual tabs to handle input for.
return;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 8cb3b23020..cb7a6c0978 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1364,6 +1364,10 @@ void TextEdit::_notification(int p_what) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
+
+ if (deselect_on_focus_loss_enabled) {
+ deselect();
+ }
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
@@ -3669,6 +3673,17 @@ bool TextEdit::is_selecting_enabled() const {
return selecting_enabled;
}
+void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
+ deselect_on_focus_loss_enabled = p_enabled;
+ if (p_enabled && selection.active && !has_focus()) {
+ deselect();
+ }
+}
+
+bool TextEdit::is_deselect_on_focus_loss_enabled() const {
+ return deselect_on_focus_loss_enabled;
+}
+
void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) {
override_selected_font_color = p_override_selected_font_color;
}
@@ -4716,6 +4731,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled);
ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled);
+ ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &TextEdit::set_deselect_on_focus_loss_enabled);
+ ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &TextEdit::is_deselect_on_focus_loss_enabled);
+
ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color);
ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color);
@@ -4875,9 +4893,9 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled");
-
ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 8823e44c0d..23f80efce0 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -395,6 +395,7 @@ private:
} selection;
bool selecting_enabled = true;
+ bool deselect_on_focus_loss_enabled = true;
Color font_selected_color = Color(1, 1, 1);
Color selection_color = Color(1, 1, 1);
@@ -753,6 +754,9 @@ public:
void set_selecting_enabled(const bool p_enabled);
bool is_selecting_enabled() const;
+ void set_deselect_on_focus_loss_enabled(const bool p_enabled);
+ bool is_deselect_on_focus_loss_enabled() const;
+
void set_override_selected_font_color(bool p_override_selected_font_color);
bool is_overriding_selected_font_color() const;
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index af2f4cbdb6..f8e4845ae6 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -4688,7 +4688,7 @@ bool Animation::_blend_shape_interpolate_compressed(uint32_t p_compressed_track,
template <uint32_t COMPONENTS>
bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Vector3i &r_current_value, double &r_current_time, Vector3i &r_next_value, double &r_next_time, uint32_t *key_index) const {
ERR_FAIL_COND_V(!compression.enabled, false);
- ERR_FAIL_INDEX_V(p_compressed_track, compression.bounds.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), false);
p_time = CLAMP(p_time, 0, length);
if (key_index) {
*key_index = 0;
@@ -4836,7 +4836,7 @@ bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Ve
template <uint32_t COMPONENTS>
void Animation::_get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const {
ERR_FAIL_COND(!compression.enabled);
- ERR_FAIL_INDEX(p_compressed_track, compression.bounds.size());
+ ERR_FAIL_UNSIGNED_INDEX(p_compressed_track, compression.bounds.size());
double frame_to_sec = 1.0 / double(compression.fps);
uint32_t key_index = 0;
@@ -4957,7 +4957,7 @@ float Animation::_uncompress_blend_shape(const Vector3i &p_value) const {
template <uint32_t COMPONENTS>
bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const {
ERR_FAIL_COND_V(!compression.enabled, false);
- ERR_FAIL_INDEX_V(p_compressed_track, compression.bounds.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), false);
for (uint32_t i = 0; i < compression.pages.size(); i++) {
const uint8_t *page_data = compression.pages[i].data.ptr();
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index ed093d7ea3..f21a070133 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -147,6 +147,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
Color control_font_lower_color = Color(0.63, 0.63, 0.63);
Color control_font_low_color = Color(0.69, 0.69, 0.69);
Color control_font_hover_color = Color(0.94, 0.94, 0.94);
+ Color control_font_focus_color = Color(0.94, 0.94, 0.94);
Color control_font_disabled_color = Color(0.9, 0.9, 0.9, 0.2);
Color control_font_pressed_color = Color(1, 1, 1);
@@ -185,6 +186,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "Button", control_font_color);
theme->set_color("font_pressed_color", "Button", control_font_pressed_color);
theme->set_color("font_hover_color", "Button", control_font_hover_color);
+ theme->set_color("font_focus_color", "Button", control_font_focus_color);
theme->set_color("font_hover_pressed_color", "Button", control_font_pressed_color);
theme->set_color("font_disabled_color", "Button", control_font_disabled_color);
theme->set_color("font_outline_color", "Button", Color(1, 1, 1));
@@ -193,6 +195,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("icon_pressed_color", "Button", Color(1, 1, 1, 1));
theme->set_color("icon_hover_color", "Button", Color(1, 1, 1, 1));
theme->set_color("icon_hover_pressed_color", "Button", Color(1, 1, 1, 1));
+ theme->set_color("icon_focus_color", "Button", Color(1, 1, 1, 1));
theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 1));
theme->set_constant("hseparation", "Button", 2 * scale);
@@ -207,6 +210,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "LinkButton", control_font_color);
theme->set_color("font_pressed_color", "LinkButton", control_font_pressed_color);
theme->set_color("font_hover_color", "LinkButton", control_font_hover_color);
+ theme->set_color("font_focus_color", "LinkButton", control_font_focus_color);
theme->set_color("font_outline_color", "LinkButton", Color(1, 1, 1));
theme->set_constant("outline_size", "LinkButton", 0);
@@ -245,6 +249,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "OptionButton", control_font_color);
theme->set_color("font_pressed_color", "OptionButton", control_font_pressed_color);
theme->set_color("font_hover_color", "OptionButton", control_font_hover_color);
+ theme->set_color("font_focus_color", "OptionButton", control_font_focus_color);
theme->set_color("font_disabled_color", "OptionButton", control_font_disabled_color);
theme->set_color("font_outline_color", "OptionButton", Color(1, 1, 1));
@@ -266,6 +271,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "MenuButton", control_font_color);
theme->set_color("font_pressed_color", "MenuButton", control_font_pressed_color);
theme->set_color("font_hover_color", "MenuButton", control_font_hover_color);
+ theme->set_color("font_focus_color", "MenuButton", control_font_focus_color);
theme->set_color("font_disabled_color", "MenuButton", Color(1, 1, 1, 0.3));
theme->set_color("font_outline_color", "MenuButton", Color(1, 1, 1));
@@ -308,6 +314,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_pressed_color", "CheckBox", control_font_pressed_color);
theme->set_color("font_hover_color", "CheckBox", control_font_hover_color);
theme->set_color("font_hover_pressed_color", "CheckBox", control_font_pressed_color);
+ theme->set_color("font_focus_color", "CheckBox", control_font_focus_color);
theme->set_color("font_disabled_color", "CheckBox", control_font_disabled_color);
theme->set_color("font_outline_color", "CheckBox", Color(1, 1, 1));
@@ -347,6 +354,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_pressed_color", "CheckButton", control_font_pressed_color);
theme->set_color("font_hover_color", "CheckButton", control_font_hover_color);
theme->set_color("font_hover_pressed_color", "CheckButton", control_font_pressed_color);
+ theme->set_color("font_focus_color", "CheckButton", control_font_focus_color);
theme->set_color("font_disabled_color", "CheckButton", control_font_disabled_color);
theme->set_color("font_outline_color", "CheckButton", Color(1, 1, 1));
@@ -867,6 +875,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1));
theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1));
theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1));
+ theme->set_color("font_focus_color", "ColorPickerButton", Color(1, 1, 1, 1));
theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3));
theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1));
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index abb3381c4e..267180973f 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -1100,7 +1100,7 @@ void BaseMaterial3D::_update_shader() {
if (proximity_fade_enabled) {
code += " float depth_tex = textureLod(DEPTH_TEXTURE,SCREEN_UV,0.0).r;\n";
- code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex*2.0-1.0,1.0);\n";
+ code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex,1.0);\n";
code += " world_pos.xyz/=world_pos.w;\n";
code += " ALPHA*=clamp(1.0-smoothstep(world_pos.z+proximity_fade_distance,world_pos.z,VERTEX.z),0.0,1.0);\n";
}
diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp
index a724b732b9..c03210cf48 100644
--- a/scene/resources/skeleton_modification_stack_3d.cpp
+++ b/scene/resources/skeleton_modification_stack_3d.cpp
@@ -125,6 +125,7 @@ Ref<SkeletonModification3D> SkeletonModificationStack3D::get_modification(int p_
}
void SkeletonModificationStack3D::add_modification(Ref<SkeletonModification3D> p_mod) {
+ ERR_FAIL_NULL(p_mod);
p_mod->_setup_modification(this);
modifications.push_back(p_mod);
}
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index a45b6b8eb6..141e9e1b0e 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -301,6 +301,66 @@ int TileSet::get_next_source_id() const {
return next_source_id;
}
+void TileSet::_update_terrains_cache() {
+ if (terrains_cache_dirty) {
+ // Organizes tiles into structures.
+ per_terrain_pattern_tiles.resize(terrain_sets.size());
+ for (int i = 0; i < (int)per_terrain_pattern_tiles.size(); i++) {
+ per_terrain_pattern_tiles[i].clear();
+ }
+
+ for (const KeyValue<int, Ref<TileSetSource>> &kv : sources) {
+ Ref<TileSetSource> source = kv.value;
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) {
+ Vector2i tile_id = source->get_tile_id(tile_index);
+ for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) {
+ int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index);
+
+ // Executed for each tile_data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
+ int terrain_set = tile_data->get_terrain_set();
+ if (terrain_set >= 0) {
+ TileMapCell cell;
+ cell.source_id = kv.key;
+ cell.set_atlas_coords(tile_id);
+ cell.alternative_tile = alternative_id;
+
+ TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern();
+
+ // Terrain bits.
+ for (int i = 0; i < terrains_pattern.size(); i++) {
+ int terrain = terrains_pattern[i];
+ if (terrain >= 0) {
+ per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add the empty cell in the possible patterns and cells.
+ for (int i = 0; i < terrain_sets.size(); i++) {
+ TileSet::TerrainsPattern empty_pattern;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ if (is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) {
+ empty_pattern.push_back(-1);
+ }
+ }
+
+ TileMapCell empty_cell;
+ empty_cell.source_id = TileSet::INVALID_SOURCE;
+ empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ per_terrain_pattern_tiles[i][empty_pattern].insert(empty_cell);
+ }
+ terrains_cache_dirty = false;
+ }
+}
+
void TileSet::_compute_next_source_id() {
while (sources.has(next_source_id)) {
next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30
@@ -321,6 +381,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source
sources[new_source_id]->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed));
+ terrains_cache_dirty = true;
emit_changed();
return new_source_id;
@@ -336,6 +397,7 @@ void TileSet::remove_source(int p_source_id) {
source_ids.erase(p_source_id);
source_ids.sort();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -357,6 +419,7 @@ void TileSet::set_source_id(int p_source_id, int p_new_source_id) {
_compute_next_source_id();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -545,6 +608,7 @@ void TileSet::add_terrain_set(int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -557,6 +621,7 @@ void TileSet::move_terrain_set(int p_from_index, int p_to_pos) {
source.value->move_terrain_set(p_from_index, p_to_pos);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -567,6 +632,7 @@ void TileSet::remove_terrain_set(int p_index) {
source.value->remove_terrain_set(p_index);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -578,6 +644,7 @@ void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -612,6 +679,7 @@ void TileSet::add_terrain(int p_terrain_set, int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -627,6 +695,7 @@ void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {
source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -640,6 +709,7 @@ void TileSet::remove_terrain(int p_terrain_set, int p_index) {
source.value->remove_terrain(p_terrain_set, p_index);
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -1196,6 +1266,73 @@ int TileSet::get_patterns_count() {
return patterns.size();
}
+Set<TileSet::TerrainsPattern> TileSet::get_terrains_pattern_set(int p_terrain_set) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileSet::TerrainsPattern>());
+ _update_terrains_cache();
+
+ Set<TileSet::TerrainsPattern> output;
+ for (KeyValue<TileSet::TerrainsPattern, Set<TileMapCell>> kv : per_terrain_pattern_tiles[p_terrain_set]) {
+ output.insert(kv.key);
+ }
+ return output;
+}
+
+Set<TileMapCell> TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Set<TileMapCell>());
+ _update_terrains_cache();
+ return per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern];
+}
+
+TileMapCell TileSet::get_random_tile_from_pattern(int p_terrain_set, TileSet::TerrainsPattern p_terrain_tile_pattern) {
+ ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileMapCell());
+ _update_terrains_cache();
+
+ // Count the sum of probabilities.
+ double sum = 0.0;
+ Set<TileMapCell> set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern];
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = sources[E->get().source_id];
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ sum += tile_data->get_probability();
+ } else {
+ sum += 1.0;
+ }
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ // Generate a random number.
+ double count = 0.0;
+ double picked = Math::random(0.0, sum);
+
+ // Pick the tile.
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = sources[E->get().source_id];
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ count += tile_data->get_probability();
+ } else {
+ count += 1.0;
+ }
+ } else {
+ count += 1.0;
+ }
+
+ if (count >= picked) {
+ return E->get();
+ }
+ }
+
+ ERR_FAIL_V(TileMapCell());
+}
+
Vector<Vector2> TileSet::get_tile_shape_polygon() {
Vector<Vector2> points;
if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
@@ -1510,6 +1647,7 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) {
}
void TileSet::_source_changed() {
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -4500,6 +4638,37 @@ bool TileData::is_allowing_transform() const {
return allow_transform;
}
+TileData *TileData::duplicate() {
+ TileData *output = memnew(TileData);
+ output->tile_set = tile_set;
+
+ output->allow_transform = allow_transform;
+
+ // Rendering
+ output->flip_h = flip_h;
+ output->flip_v = flip_v;
+ output->transpose = transpose;
+ output->tex_offset = tex_offset;
+ output->material = material;
+ output->modulate = modulate;
+ output->z_index = z_index;
+ output->y_sort_origin = y_sort_origin;
+ output->occluders = occluders;
+ // Physics
+ output->physics = physics;
+ // Terrain
+ output->terrain_set = -1;
+ memcpy(output->terrain_peering_bits, terrain_peering_bits, 16 * sizeof(int));
+ // Navigation
+ output->navigation = navigation;
+ // Misc
+ output->probability = probability;
+ // Custom data
+ output->custom_data = custom_data;
+
+ return output;
+}
+
// Rendering
void TileData::set_flip_h(bool p_flip_h) {
ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
@@ -4743,6 +4912,18 @@ bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit)
return tile_set->is_valid_peering_bit_terrain(terrain_set, p_peering_bit);
}
+TileSet::TerrainsPattern TileData::get_terrains_pattern() const {
+ ERR_FAIL_COND_V(!tile_set, TileSet::TerrainsPattern());
+
+ TileSet::TerrainsPattern output;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, TileSet::CellNeighbor(i))) {
+ output.push_back(get_peering_bit_terrain(TileSet::CellNeighbor(i)));
+ }
+ }
+ return output;
+}
+
// Navigation
void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) {
ERR_FAIL_INDEX(p_layer_id, navigation.size());
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 530c90920f..077315e58d 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -253,6 +253,7 @@ public:
Ref<PackedScene> scene;
Vector2 offset;
};
+ typedef Array TerrainsPattern;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -303,6 +304,10 @@ private:
Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes;
bool terrain_bits_meshes_dirty = true;
+ LocalVector<Map<TileSet::TerrainsPattern, Set<TileMapCell>>> per_terrain_pattern_tiles; // Cached data.
+ bool terrains_cache_dirty = true;
+ void _update_terrains_cache();
+
// Navigation
struct NavigationLayer {
uint32_t layers = 1;
@@ -470,6 +475,11 @@ public:
void remove_pattern(int p_index);
int get_patterns_count();
+ // Terrains.
+ Set<TerrainsPattern> get_terrains_pattern_set(int p_terrain_set);
+ Set<TileMapCell> get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
+ TileMapCell get_random_tile_from_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
+
// Helpers
Vector<Vector2> get_tile_shape_polygon();
void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());
@@ -784,6 +794,9 @@ public:
void set_allow_transform(bool p_allow_transform);
bool is_allowing_transform() const;
+ // To duplicate a TileData object, needed for runtiume update.
+ TileData *duplicate();
+
// Rendering
void set_flip_h(bool p_flip_h);
bool get_flip_h() const;
@@ -831,6 +844,8 @@ public:
int get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
bool is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const;
+ TileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed.
+
// Navigation
void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon);
Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const;