summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/area_2d.cpp131
-rw-r--r--scene/2d/area_2d.h28
-rw-r--r--scene/2d/audio_listener_2d.h2
-rw-r--r--scene/2d/audio_stream_player_2d.cpp14
-rw-r--r--scene/2d/camera_2d.cpp3
-rw-r--r--scene/2d/collision_object_2d.cpp8
-rw-r--r--scene/2d/collision_polygon_2d.cpp1
-rw-r--r--scene/2d/collision_shape_2d.cpp2
-rw-r--r--scene/2d/cpu_particles_2d.cpp2
-rw-r--r--scene/2d/cpu_particles_2d.h7
-rw-r--r--scene/2d/gpu_particles_2d.cpp7
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/line_builder.cpp6
-rw-r--r--scene/2d/mesh_instance_2d.cpp4
-rw-r--r--scene/2d/mesh_instance_2d.h1
-rw-r--r--scene/2d/navigation_agent_2d.cpp4
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp65
-rw-r--r--scene/2d/navigation_obstacle_2d.h17
-rw-r--r--scene/2d/navigation_region_2d.cpp8
-rw-r--r--scene/2d/parallax_layer.cpp4
-rw-r--r--scene/2d/physics_body_2d.cpp74
-rw-r--r--scene/2d/physics_body_2d.h22
-rw-r--r--scene/2d/polygon_2d.cpp11
-rw-r--r--scene/2d/ray_cast_2d.cpp33
-rw-r--r--scene/2d/ray_cast_2d.h5
-rw-r--r--scene/2d/shape_cast_2d.cpp459
-rw-r--r--scene/2d/shape_cast_2d.h120
-rw-r--r--scene/2d/sprite_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp978
-rw-r--r--scene/2d/tile_map.h141
-rw-r--r--scene/3d/area_3d.cpp134
-rw-r--r--scene/3d/area_3d.h29
-rw-r--r--scene/3d/audio_listener_3d.h3
-rw-r--r--scene/3d/audio_stream_player_3d.cpp20
-rw-r--r--scene/3d/bone_attachment_3d.cpp8
-rw-r--r--scene/3d/bone_attachment_3d.h1
-rw-r--r--scene/3d/camera_3d.cpp9
-rw-r--r--scene/3d/collision_object_3d.cpp8
-rw-r--r--scene/3d/cpu_particles_3d.cpp8
-rw-r--r--scene/3d/cpu_particles_3d.h7
-rw-r--r--scene/3d/decal.cpp3
-rw-r--r--scene/3d/fog_volume.cpp122
-rw-r--r--scene/3d/fog_volume.h72
-rw-r--r--scene/3d/gpu_particles_3d.cpp5
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp21
-rw-r--r--scene/3d/light_3d.cpp38
-rw-r--r--scene/3d/light_3d.h1
-rw-r--r--scene/3d/lightmap_gi.cpp7
-rw-r--r--scene/3d/mesh_instance_3d.cpp92
-rw-r--r--scene/3d/mesh_instance_3d.h15
-rw-r--r--scene/3d/navigation_agent_3d.cpp4
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp65
-rw-r--r--scene/3d/navigation_obstacle_3d.h17
-rw-r--r--scene/3d/navigation_region_3d.cpp2
-rw-r--r--scene/3d/node_3d.cpp127
-rw-r--r--scene/3d/node_3d.h35
-rw-r--r--scene/3d/occluder_instance_3d.cpp4
-rw-r--r--scene/3d/path_3d.cpp1
-rw-r--r--scene/3d/physics_body_3d.cpp108
-rw-r--r--scene/3d/physics_body_3d.h40
-rw-r--r--scene/3d/ray_cast_3d.cpp32
-rw-r--r--scene/3d/ray_cast_3d.h9
-rw-r--r--scene/3d/reflection_probe.cpp7
-rw-r--r--scene/3d/remote_transform_3d.cpp4
-rw-r--r--scene/3d/skeleton_3d.cpp303
-rw-r--r--scene/3d/skeleton_3d.h36
-rw-r--r--scene/3d/skeleton_ik_3d.cpp2
-rw-r--r--scene/3d/skeleton_ik_3d.h3
-rw-r--r--scene/3d/soft_dynamic_body_3d.cpp88
-rw-r--r--scene/3d/soft_dynamic_body_3d.h15
-rw-r--r--scene/3d/spring_arm_3d.cpp27
-rw-r--r--scene/3d/sprite_3d.cpp4
-rw-r--r--scene/3d/sprite_3d.h6
-rw-r--r--scene/3d/vehicle_body_3d.cpp18
-rw-r--r--scene/3d/visual_instance_3d.cpp39
-rw-r--r--scene/3d/visual_instance_3d.h17
-rw-r--r--scene/3d/voxel_gi.cpp6
-rw-r--r--scene/3d/xr_nodes.cpp631
-rw-r--r--scene/3d/xr_nodes.h110
-rw-r--r--scene/animation/animation_blend_space_1d.cpp12
-rw-r--r--scene/animation/animation_blend_space_2d.cpp27
-rw-r--r--scene/animation/animation_blend_tree.cpp87
-rw-r--r--scene/animation/animation_blend_tree.h26
-rw-r--r--scene/animation/animation_cache.cpp314
-rw-r--r--scene/animation/animation_node_state_machine.cpp12
-rw-r--r--scene/animation/animation_player.cpp365
-rw-r--r--scene/animation/animation_player.h21
-rw-r--r--scene/animation/animation_tree.cpp504
-rw-r--r--scene/animation/animation_tree.h22
-rw-r--r--scene/animation/tween.cpp4
-rw-r--r--scene/audio/audio_stream_player.cpp8
-rw-r--r--scene/gui/base_button.cpp6
-rw-r--r--scene/gui/base_button.h6
-rw-r--r--scene/gui/box_container.cpp2
-rw-r--r--scene/gui/button.cpp15
-rw-r--r--scene/gui/code_edit.cpp26
-rw-r--r--scene/gui/color_picker.cpp24
-rw-r--r--scene/gui/control.cpp34
-rw-r--r--scene/gui/control.h6
-rw-r--r--scene/gui/dialogs.cpp2
-rw-r--r--scene/gui/file_dialog.cpp14
-rw-r--r--scene/gui/gradient_edit.cpp112
-rw-r--r--scene/gui/gradient_edit.h7
-rw-r--r--scene/gui/graph_edit.cpp73
-rw-r--r--scene/gui/graph_edit.h5
-rw-r--r--scene/gui/graph_node.cpp4
-rw-r--r--scene/gui/item_list.cpp150
-rw-r--r--scene/gui/item_list.h17
-rw-r--r--scene/gui/label.cpp10
-rw-r--r--scene/gui/line_edit.cpp167
-rw-r--r--scene/gui/line_edit.h16
-rw-r--r--scene/gui/link_button.cpp7
-rw-r--r--scene/gui/menu_button.cpp74
-rw-r--r--scene/gui/menu_button.h9
-rw-r--r--scene/gui/option_button.cpp8
-rw-r--r--scene/gui/popup.cpp4
-rw-r--r--scene/gui/popup.h1
-rw-r--r--scene/gui/popup_menu.cpp263
-rw-r--r--scene/gui/popup_menu.h30
-rw-r--r--scene/gui/rich_text_label.cpp136
-rw-r--r--scene/gui/rich_text_label.h13
-rw-r--r--scene/gui/scroll_bar.cpp8
-rw-r--r--scene/gui/scroll_container.cpp14
-rw-r--r--scene/gui/slider.cpp6
-rw-r--r--scene/gui/spin_box.cpp16
-rw-r--r--scene/gui/split_container.cpp2
-rw-r--r--scene/gui/tab_bar.cpp (renamed from scene/gui/tabs.cpp)231
-rw-r--r--scene/gui/tab_bar.h (renamed from scene/gui/tabs.h)19
-rw-r--r--scene/gui/tab_container.cpp2
-rw-r--r--scene/gui/text_edit.cpp179
-rw-r--r--scene/gui/text_edit.h13
-rw-r--r--scene/gui/texture_progress_bar.cpp12
-rw-r--r--scene/gui/tree.cpp70
-rw-r--r--scene/gui/tree.h2
-rw-r--r--scene/gui/video_player.cpp4
-rw-r--r--scene/main/canvas_item.cpp7
-rw-r--r--scene/main/canvas_item.h8
-rw-r--r--scene/main/canvas_layer.cpp10
-rw-r--r--scene/main/node.cpp66
-rw-r--r--scene/main/node.h8
-rw-r--r--scene/main/resource_preloader.cpp2
-rw-r--r--scene/main/scene_tree.h4
-rw-r--r--scene/main/viewport.cpp126
-rw-r--r--scene/main/viewport.h4
-rw-r--r--scene/main/window.cpp2
-rw-r--r--scene/property_utils.cpp188
-rw-r--r--scene/property_utils.h51
-rw-r--r--scene/register_scene_types.cpp61
-rw-r--r--scene/resources/animation.cpp3520
-rw-r--r--scene/resources/animation.h218
-rw-r--r--scene/resources/audio_stream_sample.cpp8
-rw-r--r--scene/resources/audio_stream_sample.h2
-rw-r--r--scene/resources/bit_map.cpp2
-rw-r--r--scene/resources/camera_effects.cpp2
-rw-r--r--scene/resources/concave_polygon_shape_3d.cpp2
-rw-r--r--scene/resources/curve.cpp6
-rw-r--r--scene/resources/default_theme/SCsub2
-rw-r--r--scene/resources/default_theme/default_theme.cpp50
-rw-r--r--scene/resources/default_theme/overbright_indicator.pngbin593 -> 210 bytes
-rw-r--r--scene/resources/default_theme/theme_data.h10
-rw-r--r--scene/resources/environment.cpp93
-rw-r--r--scene/resources/environment.h31
-rw-r--r--scene/resources/fog_material.cpp181
-rw-r--r--scene/resources/fog_material.h (renamed from scene/animation/animation_cache.h)79
-rw-r--r--scene/resources/font.cpp158
-rw-r--r--scene/resources/font.h37
-rw-r--r--scene/resources/gradient.cpp30
-rw-r--r--scene/resources/gradient.h57
-rw-r--r--scene/resources/importer_mesh.cpp53
-rw-r--r--scene/resources/importer_mesh.h1
-rw-r--r--scene/resources/material.cpp16
-rw-r--r--scene/resources/mesh.cpp6
-rw-r--r--scene/resources/mesh.h1
-rw-r--r--scene/resources/mesh_data_tool.cpp2
-rw-r--r--scene/resources/navigation_mesh.cpp4
-rw-r--r--scene/resources/navigation_mesh.h2
-rw-r--r--scene/resources/packed_scene.cpp218
-rw-r--r--scene/resources/packed_scene.h16
-rw-r--r--scene/resources/particles_material.cpp2
-rw-r--r--scene/resources/polygon_path_finder.cpp2
-rw-r--r--scene/resources/resource_format_text.cpp6
-rw-r--r--scene/resources/shader.cpp39
-rw-r--r--scene/resources/shader.h7
-rw-r--r--scene/resources/shape_2d.h1
-rw-r--r--scene/resources/skeleton_modification_2d_ccdik.cpp4
-rw-r--r--scene/resources/skeleton_modification_2d_jiggle.cpp8
-rw-r--r--scene/resources/skeleton_modification_2d_lookat.cpp2
-rw-r--r--scene/resources/skeleton_modification_3d_fabrik.cpp78
-rw-r--r--scene/resources/skeleton_modification_3d_fabrik.h2
-rw-r--r--scene/resources/skeleton_modification_3d_jiggle.cpp15
-rw-r--r--scene/resources/skeleton_modification_3d_lookat.cpp4
-rw-r--r--scene/resources/skeleton_modification_3d_twoboneik.cpp28
-rw-r--r--scene/resources/skeleton_modification_stack_2d.cpp1
-rw-r--r--scene/resources/skeleton_modification_stack_3d.cpp2
-rw-r--r--scene/resources/skin.cpp2
-rw-r--r--scene/resources/sprite_frames.cpp2
-rw-r--r--scene/resources/style_box.cpp2
-rw-r--r--scene/resources/surface_tool.cpp2
-rw-r--r--scene/resources/text_paragraph.cpp18
-rw-r--r--scene/resources/text_paragraph.h5
-rw-r--r--scene/resources/texture.cpp305
-rw-r--r--scene/resources/texture.h82
-rw-r--r--scene/resources/theme.cpp4
-rw-r--r--scene/resources/tile_set.cpp774
-rw-r--r--scene/resources/tile_set.h149
-rw-r--r--scene/resources/visual_shader.cpp149
-rw-r--r--scene/resources/visual_shader.h12
-rw-r--r--scene/resources/visual_shader_nodes.cpp19
-rw-r--r--scene/resources/visual_shader_particle_nodes.cpp381
-rw-r--r--scene/resources/visual_shader_particle_nodes.h61
-rw-r--r--scene/resources/world_3d.cpp4
211 files changed, 11565 insertions, 3696 deletions
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp
index 33b1c7bcce..8db1491953 100644
--- a/scene/2d/area_2d.cpp
+++ b/scene/2d/area_2d.cpp
@@ -33,13 +33,13 @@
#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
-void Area2D::set_space_override_mode(SpaceOverride p_mode) {
- space_override = p_mode;
- PhysicsServer2D::get_singleton()->area_set_space_override_mode(get_rid(), PhysicsServer2D::AreaSpaceOverrideMode(p_mode));
+void Area2D::set_gravity_space_override_mode(SpaceOverride p_mode) {
+ gravity_space_override = p_mode;
+ PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode);
}
-Area2D::SpaceOverride Area2D::get_space_override_mode() const {
- return space_override;
+Area2D::SpaceOverride Area2D::get_gravity_space_override_mode() const {
+ return gravity_space_override;
}
void Area2D::set_gravity_is_point(bool p_enabled) {
@@ -51,21 +51,30 @@ bool Area2D::is_gravity_a_point() const {
return gravity_is_point;
}
-void Area2D::set_gravity_distance_scale(real_t p_scale) {
+void Area2D::set_gravity_point_distance_scale(real_t p_scale) {
gravity_distance_scale = p_scale;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_DISTANCE_SCALE, p_scale);
}
-real_t Area2D::get_gravity_distance_scale() const {
+real_t Area2D::get_gravity_point_distance_scale() const {
return gravity_distance_scale;
}
-void Area2D::set_gravity_vector(const Vector2 &p_vec) {
- gravity_vec = p_vec;
- PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_vec);
+void Area2D::set_gravity_point_center(const Vector2 &p_center) {
+ gravity_vec = p_center;
+ PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_center);
}
-Vector2 Area2D::get_gravity_vector() const {
+const Vector2 &Area2D::get_gravity_point_center() const {
+ return gravity_vec;
+}
+
+void Area2D::set_gravity_direction(const Vector2 &p_direction) {
+ gravity_vec = p_direction;
+ PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_direction);
+}
+
+const Vector2 &Area2D::get_gravity_direction() const {
return gravity_vec;
}
@@ -78,6 +87,24 @@ real_t Area2D::get_gravity() const {
return gravity;
}
+void Area2D::set_linear_damp_space_override_mode(SpaceOverride p_mode) {
+ linear_damp_space_override = p_mode;
+ PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode);
+}
+
+Area2D::SpaceOverride Area2D::get_linear_damp_space_override_mode() const {
+ return linear_damp_space_override;
+}
+
+void Area2D::set_angular_damp_space_override_mode(SpaceOverride p_mode) {
+ angular_damp_space_override = p_mode;
+ PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode);
+}
+
+Area2D::SpaceOverride Area2D::get_angular_damp_space_override_mode() const {
+ return angular_damp_space_override;
+}
+
void Area2D::set_linear_damp(real_t p_linear_damp) {
linear_damp = p_linear_damp;
PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, p_linear_damp);
@@ -369,12 +396,11 @@ void Area2D::set_monitoring(bool p_enable) {
monitoring = p_enable;
if (monitoring) {
- PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_body_inout);
- PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_area_inout);
-
+ PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), callable_mp(this, &Area2D::_body_inout));
+ PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), callable_mp(this, &Area2D::_area_inout));
} else {
- PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), nullptr, StringName());
- PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), nullptr, StringName());
+ PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), Callable());
+ PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), Callable());
_clear_monitoring();
}
}
@@ -484,25 +510,56 @@ void Area2D::_validate_property(PropertyInfo &property) const {
}
property.hint_string = options;
+ } else if (property.name.begins_with("gravity") && property.name != "gravity_space_override") {
+ if (gravity_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ } else {
+ if (gravity_is_point) {
+ if (property.name == "gravity_direction") {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ } else {
+ if (property.name.begins_with("gravity_point_")) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ }
+ }
+ } else if (property.name.begins_with("linear_damp") && property.name != "linear_damp_space_override") {
+ if (linear_damp_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ } else if (property.name.begins_with("angular_damp") && property.name != "angular_damp_space_override") {
+ if (angular_damp_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
}
}
void Area2D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_space_override_mode", "space_override_mode"), &Area2D::set_space_override_mode);
- ClassDB::bind_method(D_METHOD("get_space_override_mode"), &Area2D::get_space_override_mode);
+ ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area2D::set_gravity_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area2D::get_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area2D::set_gravity_is_point);
ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area2D::is_gravity_a_point);
- ClassDB::bind_method(D_METHOD("set_gravity_distance_scale", "distance_scale"), &Area2D::set_gravity_distance_scale);
- ClassDB::bind_method(D_METHOD("get_gravity_distance_scale"), &Area2D::get_gravity_distance_scale);
+ ClassDB::bind_method(D_METHOD("set_gravity_point_distance_scale", "distance_scale"), &Area2D::set_gravity_point_distance_scale);
+ ClassDB::bind_method(D_METHOD("get_gravity_point_distance_scale"), &Area2D::get_gravity_point_distance_scale);
- ClassDB::bind_method(D_METHOD("set_gravity_vector", "vector"), &Area2D::set_gravity_vector);
- ClassDB::bind_method(D_METHOD("get_gravity_vector"), &Area2D::get_gravity_vector);
+ ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area2D::set_gravity_point_center);
+ ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area2D::get_gravity_point_center);
+
+ ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area2D::set_gravity_direction);
+ ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area2D::get_gravity_direction);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area2D::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &Area2D::get_gravity);
+ ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area2D::set_linear_damp_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area2D::get_linear_damp_space_override_mode);
+
+ ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area2D::set_angular_damp_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area2D::get_angular_damp_space_override_mode);
+
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &Area2D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &Area2D::get_linear_damp);
@@ -530,16 +587,13 @@ void Area2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_audio_bus_override", "enable"), &Area2D::set_audio_bus_override);
ClassDB::bind_method(D_METHOD("is_overriding_audio_bus"), &Area2D::is_overriding_audio_bus);
- ClassDB::bind_method(D_METHOD("_body_inout"), &Area2D::_body_inout);
- ClassDB::bind_method(D_METHOD("_area_inout"), &Area2D::_area_inout);
-
- ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D")));
- ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("area_entered", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D")));
ADD_SIGNAL(MethodInfo("area_exited", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D")));
@@ -547,13 +601,20 @@ void Area2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority");
- ADD_GROUP("Physics Overrides", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_distance_scale", "get_gravity_distance_scale");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_vec"), "set_gravity_vector", "get_gravity_vector");
+ ADD_GROUP("Gravity", "gravity_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "gravity_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_space_override_mode", "get_gravity_space_override_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_is_point", "is_gravity_a_point");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_point_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_point_distance_scale", "get_gravity_point_distance_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_point_center"), "set_gravity_point_center", "get_gravity_point_center");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_direction"), "set_gravity_direction", "get_gravity_direction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-4096,4096,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity");
+
+ ADD_GROUP("Linear Damp", "linear_damp_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_linear_damp_space_override_mode", "get_linear_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
+
+ ADD_GROUP("Angular Damp", "angular_damp_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_angular_damp_space_override_mode", "get_angular_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
ADD_GROUP("Audio Bus", "audio_bus_");
@@ -570,7 +631,7 @@ void Area2D::_bind_methods() {
Area2D::Area2D() :
CollisionObject2D(PhysicsServer2D::get_singleton()->area_create(), true) {
set_gravity(980);
- set_gravity_vector(Vector2(0, 1));
+ set_gravity_direction(Vector2(0, 1));
set_monitoring(true);
set_monitorable(true);
}
diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h
index 2c29e4660d..98ba270a61 100644
--- a/scene/2d/area_2d.h
+++ b/scene/2d/area_2d.h
@@ -47,14 +47,19 @@ public:
};
private:
- SpaceOverride space_override = SPACE_OVERRIDE_DISABLED;
+ SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED;
Vector2 gravity_vec;
real_t gravity;
bool gravity_is_point = false;
real_t gravity_distance_scale = 0.0;
+
+ SpaceOverride linear_damp_space_override = SPACE_OVERRIDE_DISABLED;
+ SpaceOverride angular_damp_space_override = SPACE_OVERRIDE_DISABLED;
real_t linear_damp = 0.1;
real_t angular_damp = 1.0;
+
int priority = 0;
+
bool monitoring = false;
bool monitorable = false;
bool locked = false;
@@ -133,21 +138,30 @@ protected:
void _validate_property(PropertyInfo &property) const override;
public:
- void set_space_override_mode(SpaceOverride p_mode);
- SpaceOverride get_space_override_mode() const;
+ void set_gravity_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_gravity_space_override_mode() const;
void set_gravity_is_point(bool p_enabled);
bool is_gravity_a_point() const;
- void set_gravity_distance_scale(real_t p_scale);
- real_t get_gravity_distance_scale() const;
+ void set_gravity_point_distance_scale(real_t p_scale);
+ real_t get_gravity_point_distance_scale() const;
- void set_gravity_vector(const Vector2 &p_vec);
- Vector2 get_gravity_vector() const;
+ void set_gravity_point_center(const Vector2 &p_center);
+ const Vector2 &get_gravity_point_center() const;
+
+ void set_gravity_direction(const Vector2 &p_direction);
+ const Vector2 &get_gravity_direction() const;
void set_gravity(real_t p_gravity);
real_t get_gravity() const;
+ void set_linear_damp_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_linear_damp_space_override_mode() const;
+
+ void set_angular_damp_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_angular_damp_space_override_mode() const;
+
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
diff --git a/scene/2d/audio_listener_2d.h b/scene/2d/audio_listener_2d.h
index 875887acc6..454053bc4a 100644
--- a/scene/2d/audio_listener_2d.h
+++ b/scene/2d/audio_listener_2d.h
@@ -43,8 +43,6 @@ private:
friend class Viewport;
protected:
- void _update_listener();
-
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index bddc342c1a..24da2ce9ce 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -68,7 +68,7 @@ void AudioStreamPlayer2D::_notification(int p_what) {
active.set();
Ref<AudioStreamPlayback> new_playback = stream->instance_playback();
ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
- AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get());
+ AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
stream_playbacks.push_back(new_playback);
setplay.set(-1);
}
@@ -78,7 +78,6 @@ void AudioStreamPlayer2D::_notification(int p_what) {
Vector<Ref<AudioStreamPlayback>> playbacks_to_remove;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) {
- emit_signal(SNAME("finished"));
playbacks_to_remove.push_back(playback);
}
}
@@ -91,6 +90,9 @@ void AudioStreamPlayer2D::_notification(int p_what) {
active.clear();
set_physics_process_internal(false);
}
+ if (!playbacks_to_remove.is_empty()) {
+ emit_signal(SNAME("finished"));
+ }
}
while (stream_playbacks.size() > max_polyphony) {
@@ -110,7 +112,13 @@ StringName AudioStreamPlayer2D::_get_actual_bus() {
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
- int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
+ PhysicsDirectSpaceState2D::PointParameters point_params;
+ point_params.position = global_pos;
+ point_params.collision_mask = area_mask;
+ point_params.collide_with_bodies = false;
+ point_params.collide_with_areas = true;
+
+ int areas = space_state->intersect_point(point_params, sr, MAX_INTERSECT_AREAS);
for (int i = 0; i < areas; i++) {
Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider);
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 8195d98f55..bf5671be19 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -232,6 +232,7 @@ void Camera2D::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_TREE: {
+ ERR_FAIL_COND(!is_inside_tree());
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
viewport = custom_viewport;
} else {
@@ -660,7 +661,7 @@ bool Camera2D::is_margin_drawing_enabled() const {
void Camera2D::_validate_property(PropertyInfo &property) const {
if (!smoothing_enabled && property.name == "smoothing_speed") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp
index 6916f832d0..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 {
@@ -454,7 +456,7 @@ void CollisionObject2D::shape_owner_clear_shapes(uint32_t p_owner) {
}
uint32_t CollisionObject2D::shape_find_owner(int p_shape_index) const {
- ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, 0);
+ ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, UINT32_MAX);
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
for (int i = 0; i < E.value.shapes.size(); i++) {
@@ -465,7 +467,7 @@ uint32_t CollisionObject2D::shape_find_owner(int p_shape_index) const {
}
//in theory it should be unreachable
- return 0;
+ ERR_FAIL_V_MSG(UINT32_MAX, "Can't find owner for shape index " + itos(p_shape_index) + ".");
}
void CollisionObject2D::set_pickable(bool p_enabled) {
diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp
index 271a4da705..00bfa62449 100644
--- a/scene/2d/collision_polygon_2d.cpp
+++ b/scene/2d/collision_polygon_2d.cpp
@@ -131,6 +131,7 @@ void CollisionPolygon2D::_notification(int p_what) {
} break;
case NOTIFICATION_DRAW: {
+ ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp
index 54cb851216..c7742c7ba5 100644
--- a/scene/2d/collision_shape_2d.cpp
+++ b/scene/2d/collision_shape_2d.cpp
@@ -88,6 +88,8 @@ void CollisionShape2D::_notification(int p_what) {
} break;
case NOTIFICATION_DRAW: {
+ ERR_FAIL_COND(!is_inside_tree());
+
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp
index bf26ec1f20..80c17b6e88 100644
--- a/scene/2d/cpu_particles_2d.cpp
+++ b/scene/2d/cpu_particles_2d.cpp
@@ -1168,7 +1168,7 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) {
set_color(material->get_color());
- Ref<GradientTexture> gt = material->get_color_ramp();
+ Ref<GradientTexture1D> gt = material->get_color_ramp();
if (gt.is_valid()) {
set_color_ramp(gt->get_gradient());
}
diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h
index 4990d443e3..391f51224e 100644
--- a/scene/2d/cpu_particles_2d.h
+++ b/scene/2d/cpu_particles_2d.h
@@ -201,7 +201,6 @@ public:
void set_explosiveness_ratio(real_t p_ratio);
void set_randomness_ratio(real_t p_ratio);
void set_lifetime_randomness(double p_random);
- void set_visibility_aabb(const Rect2 &p_aabb);
void set_use_local_coordinates(bool p_enable);
void set_speed_scale(double p_scale);
@@ -213,7 +212,6 @@ public:
real_t get_explosiveness_ratio() const;
real_t get_randomness_ratio() const;
double get_lifetime_randomness() const;
- Rect2 get_visibility_aabb() const;
bool get_use_local_coordinates() const;
double get_speed_scale() const;
@@ -226,9 +224,6 @@ public:
void set_draw_order(DrawOrder p_order);
DrawOrder get_draw_order() const;
- void set_draw_passes(int p_count);
- int get_draw_passes() const;
-
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
@@ -264,7 +259,6 @@ public:
void set_emission_points(const Vector<Vector2> &p_points);
void set_emission_normals(const Vector<Vector2> &p_normals);
void set_emission_colors(const Vector<Color> &p_colors);
- void set_emission_point_count(int p_count);
void set_scale_curve_x(Ref<Curve> p_scale_curve);
void set_scale_curve_y(Ref<Curve> p_scale_curve);
void set_split_scale(bool p_split_scale);
@@ -275,7 +269,6 @@ public:
Vector<Vector2> get_emission_points() const;
Vector<Vector2> get_emission_normals() const;
Vector<Color> get_emission_colors() const;
- int get_emission_point_count() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
bool get_split_scale();
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 5bce705dd5..f1f4d1b769 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -163,8 +163,8 @@ void GPUParticles2D::set_trail_sections(int p_sections) {
}
void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) {
- ERR_FAIL_COND(trail_section_subdivisions < 1);
- ERR_FAIL_COND(trail_section_subdivisions > 1024);
+ ERR_FAIL_COND(p_subdivisions < 1);
+ ERR_FAIL_COND(p_subdivisions > 1024);
trail_section_subdivisions = p_subdivisions;
update();
@@ -284,7 +284,7 @@ TypedArray<String> GPUParticles2D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (RenderingServer::get_singleton()->is_low_end()) {
- warnings.push_back(TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose."));
+ warnings.push_back(TTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose."));
}
if (process_material.is_null()) {
@@ -532,6 +532,7 @@ void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
+ ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
ADD_GROUP("Time", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime");
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index 1853b3428c..66c0f979ae 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -221,7 +221,7 @@ real_t Light2D::get_shadow_smooth() const {
void Light2D::_validate_property(PropertyInfo &property) const {
if (!shadow && (property.name == "shadow_color" || property.name == "shadow_filter" || property.name == "shadow_filter_smooth" || property.name == "shadow_item_cull_mask")) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp
index a8a2639ccf..05d77f8224 100644
--- a/scene/2d/line_builder.cpp
+++ b/scene/2d/line_builder.cpp
@@ -128,9 +128,9 @@ void LineBuilder::build() {
_interpolate_color = gradient != nullptr;
bool retrieve_curve = curve != nullptr;
bool distance_required = _interpolate_color ||
- retrieve_curve ||
- texture_mode == Line2D::LINE_TEXTURE_TILE ||
- texture_mode == Line2D::LINE_TEXTURE_STRETCH;
+ retrieve_curve ||
+ texture_mode == Line2D::LINE_TEXTURE_TILE ||
+ texture_mode == Line2D::LINE_TEXTURE_STRETCH;
if (distance_required) {
total_distance = calculate_total_distance(points);
//Adjust totalDistance.
diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp
index 15008390b7..58bff97da9 100644
--- a/scene/2d/mesh_instance_2d.cpp
+++ b/scene/2d/mesh_instance_2d.cpp
@@ -96,6 +96,10 @@ Rect2 MeshInstance2D::_edit_get_rect() const {
return Node2D::_edit_get_rect();
}
+
+bool MeshInstance2D::_edit_use_rect() const {
+ return mesh.is_valid();
+}
#endif
MeshInstance2D::MeshInstance2D() {
diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h
index adfda4cf7f..f94d53da7d 100644
--- a/scene/2d/mesh_instance_2d.h
+++ b/scene/2d/mesh_instance_2d.h
@@ -48,6 +48,7 @@ protected:
public:
#ifdef TOOLS_ENABLED
virtual Rect2 _edit_get_rect() const override;
+ virtual bool _edit_use_rect() const override;
#endif
void set_mesh(const Ref<Mesh> &p_mesh);
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 2f00978123..7faa964407 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -184,7 +184,7 @@ Vector2 NavigationAgent2D::get_target_location() const {
Vector2 NavigationAgent2D::get_next_location() {
update_navigation();
if (navigation_path.size() == 0) {
- ERR_FAIL_COND_V(agent_parent == nullptr, Vector2());
+ ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector2(), "The agent has no parent.");
return agent_parent->get_global_position();
} else {
return navigation_path[nav_path_index];
@@ -192,7 +192,7 @@ Vector2 NavigationAgent2D::get_next_location() {
}
real_t NavigationAgent2D::distance_to_target() const {
- ERR_FAIL_COND_V(agent_parent == nullptr, 0.0);
+ ERR_FAIL_COND_V_MSG(agent_parent == nullptr, 0.0, "The agent has no parent.");
return agent_parent->get_global_position().distance_to(target_location);
}
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index 0a105826c0..8802a1098a 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -34,19 +34,41 @@
#include "servers/navigation_server_2d.h"
void NavigationObstacle2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle2D::set_estimate_radius);
+ ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle2D::is_radius_estimated);
+ ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle2D::set_radius);
+ ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle2D::get_radius);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "estimate_radius"), "set_estimate_radius", "is_radius_estimated");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,500,0.01"), "set_radius", "get_radius");
+}
+
+void NavigationObstacle2D::_validate_property(PropertyInfo &p_property) const {
+ if (p_property.name == "radius") {
+ if (estimate_radius) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ }
}
void NavigationObstacle2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
+ initialize_agent();
+ parent_node2d = Object::cast_to<Node2D>(get_parent());
+ if (parent_node2d != nullptr) {
+ // place agent on navigation map first or else the RVO agent callback creation fails silently later
+ NavigationServer2D::get_singleton()->agent_set_map(get_rid(), parent_node2d->get_world_2d()->get_navigation_map());
+ }
set_physics_process_internal(true);
} break;
case NOTIFICATION_EXIT_TREE: {
+ parent_node2d = nullptr;
set_physics_process_internal(false);
} break;
case NOTIFICATION_PARENTED: {
parent_node2d = Object::cast_to<Node2D>(get_parent());
- update_agent_shape();
+ reevaluate_agent_radius();
} break;
case NOTIFICATION_UNPARENTED: {
parent_node2d = nullptr;
@@ -78,7 +100,22 @@ TypedArray<String> NavigationObstacle2D::get_configuration_warnings() const {
return warnings;
}
-void NavigationObstacle2D::update_agent_shape() {
+void NavigationObstacle2D::initialize_agent() {
+ NavigationServer2D::get_singleton()->agent_set_neighbor_dist(agent, 0.0);
+ NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, 0);
+ NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, 0.0);
+ NavigationServer2D::get_singleton()->agent_set_max_speed(agent, 0.0);
+}
+
+void NavigationObstacle2D::reevaluate_agent_radius() {
+ if (!estimate_radius) {
+ NavigationServer2D::get_singleton()->agent_set_radius(agent, radius);
+ } else if (parent_node2d) {
+ NavigationServer2D::get_singleton()->agent_set_radius(agent, estimate_agent_radius());
+ }
+}
+
+real_t NavigationObstacle2D::estimate_agent_radius() const {
if (parent_node2d) {
// Estimate the radius of this physics body
real_t radius = 0.0;
@@ -101,15 +138,21 @@ void NavigationObstacle2D::update_agent_shape() {
Vector2 s = parent_node2d->get_global_scale();
radius *= MAX(s.x, s.y);
- if (radius == 0.0) {
- radius = 1.0; // Never a 0 radius
+ if (radius > 0.0) {
+ return radius;
}
-
- // Initialize the Agent as an object
- NavigationServer2D::get_singleton()->agent_set_neighbor_dist(agent, 0.0);
- NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, 0);
- NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, 0.0);
- NavigationServer2D::get_singleton()->agent_set_radius(agent, radius);
- NavigationServer2D::get_singleton()->agent_set_max_speed(agent, 0.0);
}
+ return 1.0; // Never a 0 radius
+}
+
+void NavigationObstacle2D::set_estimate_radius(bool p_estimate_radius) {
+ estimate_radius = p_estimate_radius;
+ notify_property_list_changed();
+ reevaluate_agent_radius();
+}
+
+void NavigationObstacle2D::set_radius(real_t p_radius) {
+ ERR_FAIL_COND_MSG(p_radius <= 0.0, "Radius must be greater than 0.");
+ radius = p_radius;
+ reevaluate_agent_radius();
}
diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h
index 9cffc2c0c3..a5603f059f 100644
--- a/scene/2d/navigation_obstacle_2d.h
+++ b/scene/2d/navigation_obstacle_2d.h
@@ -40,8 +40,12 @@ class NavigationObstacle2D : public Node {
Node2D *parent_node2d = nullptr;
RID agent;
+ bool estimate_radius = true;
+ real_t radius = 1.0;
+
protected:
static void _bind_methods();
+ void _validate_property(PropertyInfo &p_property) const override;
void _notification(int p_what);
public:
@@ -52,10 +56,21 @@ public:
return agent;
}
+ void set_estimate_radius(bool p_estimate_radius);
+ bool is_radius_estimated() const {
+ return estimate_radius;
+ }
+ void set_radius(real_t p_radius);
+ real_t get_radius() const {
+ return radius;
+ }
+
TypedArray<String> get_configuration_warnings() const override;
private:
- void update_agent_shape();
+ void initialize_agent();
+ void reevaluate_agent_radius();
+ real_t estimate_agent_radius() const;
};
#endif
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index cbf0d50c4e..204ed72878 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -346,9 +346,9 @@ void NavigationPolygon::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_outlines", "outlines"), &NavigationPolygon::_set_outlines);
ClassDB::bind_method(D_METHOD("_get_outlines"), &NavigationPolygon::_get_outlines);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
}
void NavigationRegion2D::set_enabled(bool p_enabled) {
@@ -464,7 +464,7 @@ void NavigationRegion2D::_notification(int p_what) {
draw_line(a, b, doors_color);
// Draw a circle to illustrate the margins.
- real_t angle = b.angle_to_point(a);
+ real_t angle = a.angle_to_point(b);
draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, doors_color);
draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, doors_color);
}
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index 67e35cc7a3..797e2e59cb 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -123,12 +123,12 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s
Point2 new_ofs = (screen_offset + (p_offset - screen_offset) * motion_scale) + motion_offset * p_scale + orig_offset * p_scale;
if (mirroring.x) {
- double den = mirroring.x * p_scale;
+ real_t den = mirroring.x * p_scale;
new_ofs.x -= den * ceil(new_ofs.x / den);
}
if (mirroring.y) {
- double den = mirroring.y * p_scale;
+ real_t den = mirroring.y * p_scale;
new_ofs.y -= den * ceil(new_ofs.y / den);
}
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index f493d97ceb..5ec2a81108 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -133,9 +133,12 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear
ERR_FAIL_COND_V(!is_inside_tree(), false);
PhysicsServer2D::MotionResult *r = nullptr;
+ PhysicsServer2D::MotionResult temp_result;
if (r_collision.is_valid()) {
// Needs const_cast because method bindings don't support non-const Ref.
r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result);
+ } else {
+ r = &temp_result;
}
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky.
@@ -143,7 +146,14 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_linear
PhysicsServer2D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin);
- return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r);
+ bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r);
+
+ if (colliding) {
+ // Don't report collision when the whole motion is done.
+ return (r->collision_safe_fraction < 1.0);
+ } else {
+ return false;
+ }
}
TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() {
@@ -684,6 +694,24 @@ real_t RigidDynamicBody2D::get_gravity_scale() const {
return gravity_scale;
}
+void RigidDynamicBody2D::set_linear_damp_mode(DampMode p_mode) {
+ linear_damp_mode = p_mode;
+ PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_LINEAR_DAMP_MODE, linear_damp_mode);
+}
+
+RigidDynamicBody2D::DampMode RigidDynamicBody2D::get_linear_damp_mode() const {
+ return linear_damp_mode;
+}
+
+void RigidDynamicBody2D::set_angular_damp_mode(DampMode p_mode) {
+ angular_damp_mode = p_mode;
+ PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_ANGULAR_DAMP_MODE, angular_damp_mode);
+}
+
+RigidDynamicBody2D::DampMode RigidDynamicBody2D::get_angular_damp_mode() const {
+ return angular_damp_mode;
+}
+
void RigidDynamicBody2D::set_linear_damp(real_t p_linear_damp) {
ERR_FAIL_COND(p_linear_damp < -1);
linear_damp = p_linear_damp;
@@ -916,6 +944,12 @@ void RigidDynamicBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &RigidDynamicBody2D::set_gravity_scale);
ClassDB::bind_method(D_METHOD("get_gravity_scale"), &RigidDynamicBody2D::get_gravity_scale);
+ ClassDB::bind_method(D_METHOD("set_linear_damp_mode", "linear_damp_mode"), &RigidDynamicBody2D::set_linear_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_linear_damp_mode"), &RigidDynamicBody2D::get_linear_damp_mode);
+
+ ClassDB::bind_method(D_METHOD("set_angular_damp_mode", "angular_damp_mode"), &RigidDynamicBody2D::set_angular_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_angular_damp_mode"), &RigidDynamicBody2D::get_angular_damp_mode);
+
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &RigidDynamicBody2D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &RigidDynamicBody2D::get_linear_damp);
@@ -992,16 +1026,18 @@ void RigidDynamicBody2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode");
ADD_GROUP("Linear", "linear_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
ADD_GROUP("Angular", "angular_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
ADD_GROUP("Applied Forces", "applied_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "applied_force"), "set_applied_force", "get_applied_force");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "applied_torque"), "set_applied_torque", "get_applied_torque");
- ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("sleeping_state_changed"));
@@ -1012,6 +1048,9 @@ void RigidDynamicBody2D::_bind_methods() {
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO);
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM);
+ BIND_ENUM_CONSTANT(DAMP_MODE_COMBINE);
+ BIND_ENUM_CONSTANT(DAMP_MODE_REPLACE);
+
BIND_ENUM_CONSTANT(CCD_MODE_DISABLED);
BIND_ENUM_CONSTANT(CCD_MODE_CAST_RAY);
BIND_ENUM_CONSTANT(CCD_MODE_CAST_SHAPE);
@@ -1020,7 +1059,7 @@ void RigidDynamicBody2D::_bind_methods() {
void RigidDynamicBody2D::_validate_property(PropertyInfo &property) const {
if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) {
if (property.name == "center_of_mass") {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
}
@@ -1072,6 +1111,10 @@ bool CharacterBody2D::move_and_slide() {
if (bs) {
Vector2 local_position = gt.elements[2] - bs->get_transform().elements[2];
current_platform_velocity = bs->get_velocity_at_local_position(local_position);
+ } else {
+ // Body is removed or destroyed, invalidate floor.
+ current_platform_velocity = Vector2();
+ platform_rid = RID();
}
} else {
current_platform_velocity = Vector2();
@@ -1101,7 +1144,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 +1165,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 +1242,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();
@@ -1693,9 +1729,9 @@ void CharacterBody2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_motion_velocity", "get_motion_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_motion_velocity", "get_motion_velocity");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides");
ADD_GROUP("Free Mode", "free_mode_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "free_mode_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_free_mode_min_slide_angle", "get_free_mode_min_slide_angle");
@@ -1722,11 +1758,11 @@ void CharacterBody2D::_bind_methods() {
void CharacterBody2D::_validate_property(PropertyInfo &property) const {
if (motion_mode == MOTION_MODE_FREE) {
if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
} else {
if (property.name == "free_mode_min_slide_angle") {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
}
diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h
index d1f52b33f2..2abce4b0a5 100644
--- a/scene/2d/physics_body_2d.h
+++ b/scene/2d/physics_body_2d.h
@@ -127,6 +127,11 @@ public:
CENTER_OF_MASS_MODE_CUSTOM,
};
+ enum DampMode {
+ DAMP_MODE_COMBINE,
+ DAMP_MODE_REPLACE,
+ };
+
enum CCDMode {
CCD_MODE_DISABLED,
CCD_MODE_CAST_RAY,
@@ -146,8 +151,12 @@ private:
Ref<PhysicsMaterial> physics_material_override;
real_t gravity_scale = 1.0;
- real_t linear_damp = -1.0;
- real_t angular_damp = -1.0;
+
+ DampMode linear_damp_mode = DAMP_MODE_COMBINE;
+ DampMode angular_damp_mode = DAMP_MODE_COMBINE;
+
+ real_t linear_damp = 0.0;
+ real_t angular_damp = 0.0;
Vector2 linear_velocity;
real_t angular_velocity = 0.0;
@@ -241,6 +250,12 @@ public:
void set_gravity_scale(real_t p_gravity_scale);
real_t get_gravity_scale() const;
+ void set_linear_damp_mode(DampMode p_mode);
+ DampMode get_linear_damp_mode() const;
+
+ void set_angular_damp_mode(DampMode p_mode);
+ DampMode get_angular_damp_mode() const;
+
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
@@ -300,6 +315,7 @@ private:
VARIANT_ENUM_CAST(RigidDynamicBody2D::FreezeMode);
VARIANT_ENUM_CAST(RigidDynamicBody2D::CenterOfMassMode);
+VARIANT_ENUM_CAST(RigidDynamicBody2D::DampMode);
VARIANT_ENUM_CAST(RigidDynamicBody2D::CCDMode);
class CharacterBody2D : public PhysicsBody2D {
@@ -416,7 +432,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/polygon_2d.cpp b/scene/2d/polygon_2d.cpp
index 7366be5a7d..f00959bbb6 100644
--- a/scene/2d/polygon_2d.cpp
+++ b/scene/2d/polygon_2d.cpp
@@ -92,7 +92,7 @@ bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toler
void Polygon2D::_validate_property(PropertyInfo &property) const {
if (!invert && property.name == "invert_border") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
@@ -554,7 +554,9 @@ void Polygon2D::set_bone_path(int p_index, const NodePath &p_path) {
Array Polygon2D::_get_bones() const {
Array bones;
for (int i = 0; i < get_bone_count(); i++) {
- bones.push_back(get_bone_path(i));
+ // Convert path property to String to avoid errors due to invalid node path in editor,
+ // because it's relative to the Skeleton2D node and not Polygon2D.
+ bones.push_back(String(get_bone_path(i)));
bones.push_back(get_bone_weights(i));
}
return bones;
@@ -564,7 +566,8 @@ void Polygon2D::_set_bones(const Array &p_bones) {
ERR_FAIL_COND(p_bones.size() & 1);
clear_bones();
for (int i = 0; i < p_bones.size(); i += 2) {
- add_bone(p_bones[i], p_bones[i + 1]);
+ // Convert back from String to NodePath.
+ add_bone(NodePath(p_bones[i]), p_bones[i + 1]);
}
}
@@ -659,7 +662,7 @@ void Polygon2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons"), "set_polygons", "get_polygons");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_bones", "_get_bones");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_bones", "_get_bones");
ADD_PROPERTY(PropertyInfo(Variant::INT, "internal_vertex_count", PROPERTY_HINT_RANGE, "0,1000"), "set_internal_vertex_count", "get_internal_vertex_count");
}
diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp
index 3ac2128c2e..f9830a8743 100644
--- a/scene/2d/ray_cast_2d.cpp
+++ b/scene/2d/ray_cast_2d.cpp
@@ -157,6 +157,7 @@ void RayCast2D::_notification(int p_what) {
} break;
case NOTIFICATION_DRAW: {
+ ERR_FAIL_COND(!is_inside_tree());
if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
break;
}
@@ -191,7 +192,17 @@ void RayCast2D::_update_raycast_state() {
PhysicsDirectSpaceState2D::RayResult rr;
bool prev_collision_state = collided;
- if (dss->intersect_ray(gt.get_origin(), gt.xform(to), rr, exclude, collision_mask, collide_with_bodies, collide_with_areas)) {
+
+ PhysicsDirectSpaceState2D::RayParameters ray_params;
+ ray_params.from = gt.get_origin();
+ ray_params.to = gt.xform(to);
+ ray_params.exclude = exclude;
+ ray_params.collision_mask = collision_mask;
+ ray_params.collide_with_bodies = collide_with_bodies;
+ ray_params.collide_with_areas = collide_with_areas;
+ ray_params.hit_from_inside = hit_from_inside;
+
+ if (dss->intersect_ray(ray_params, rr)) {
collided = true;
against = rr.collider_id;
collision_point = rr.position;
@@ -280,22 +291,30 @@ void RayCast2D::clear_exceptions() {
exclude.clear();
}
-void RayCast2D::set_collide_with_areas(bool p_clip) {
- collide_with_areas = p_clip;
+void RayCast2D::set_collide_with_areas(bool p_enabled) {
+ collide_with_areas = p_enabled;
}
bool RayCast2D::is_collide_with_areas_enabled() const {
return collide_with_areas;
}
-void RayCast2D::set_collide_with_bodies(bool p_clip) {
- collide_with_bodies = p_clip;
+void RayCast2D::set_collide_with_bodies(bool p_enabled) {
+ collide_with_bodies = p_enabled;
}
bool RayCast2D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
+void RayCast2D::set_hit_from_inside(bool p_enabled) {
+ hit_from_inside = p_enabled;
+}
+
+bool RayCast2D::is_hit_from_inside_enabled() const {
+ return hit_from_inside;
+}
+
void RayCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast2D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast2D::is_enabled);
@@ -334,10 +353,14 @@ void RayCast2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast2D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast2D::is_collide_with_bodies_enabled);
+ ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast2D::set_hit_from_inside);
+ ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast2D::is_hit_from_inside_enabled);
+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position"), "set_target_position", "get_target_position");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled");
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h
index 65b6e7899b..3ee09fad32 100644
--- a/scene/2d/ray_cast_2d.h
+++ b/scene/2d/ray_cast_2d.h
@@ -51,6 +51,8 @@ class RayCast2D : public Node2D {
bool collide_with_areas = false;
bool collide_with_bodies = true;
+ bool hit_from_inside = false;
+
void _draw_debug_shape();
protected:
@@ -65,6 +67,9 @@ public:
void set_collide_with_bodies(bool p_clip);
bool is_collide_with_bodies_enabled() const;
+ void set_hit_from_inside(bool p_enable);
+ bool is_hit_from_inside_enabled() const;
+
void set_enabled(bool p_enabled);
bool is_enabled() const;
diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp
new file mode 100644
index 0000000000..50b44eb4ef
--- /dev/null
+++ b/scene/2d/shape_cast_2d.cpp
@@ -0,0 +1,459 @@
+/*************************************************************************/
+/* shape_cast_2d.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "shape_cast_2d.h"
+
+#include "core/config/engine.h"
+#include "core/core_string_names.h"
+#include "scene/2d/collision_object_2d.h"
+#include "scene/2d/physics_body_2d.h"
+#include "scene/resources/circle_shape_2d.h"
+#include "servers/physics_2d/godot_physics_server_2d.h"
+
+void ShapeCast2D::set_target_position(const Vector2 &p_point) {
+ target_position = p_point;
+ if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) {
+ update();
+ }
+}
+
+Vector2 ShapeCast2D::get_target_position() const {
+ return target_position;
+}
+
+void ShapeCast2D::set_margin(real_t p_margin) {
+ margin = p_margin;
+}
+
+real_t ShapeCast2D::get_margin() const {
+ return margin;
+}
+
+void ShapeCast2D::set_max_results(int p_max_results) {
+ max_results = p_max_results;
+}
+
+int ShapeCast2D::get_max_results() const {
+ return max_results;
+}
+
+void ShapeCast2D::set_collision_mask(uint32_t p_mask) {
+ collision_mask = p_mask;
+}
+
+uint32_t ShapeCast2D::get_collision_mask() const {
+ return collision_mask;
+}
+
+void ShapeCast2D::set_collision_mask_value(int p_layer_number, bool p_value) {
+ ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive.");
+ ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive.");
+ uint32_t mask = get_collision_mask();
+ if (p_value) {
+ mask |= 1 << (p_layer_number - 1);
+ } else {
+ mask &= ~(1 << (p_layer_number - 1));
+ }
+ set_collision_mask(mask);
+}
+
+bool ShapeCast2D::get_collision_mask_value(int p_layer_number) const {
+ ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive.");
+ ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive.");
+ return get_collision_mask() & (1 << (p_layer_number - 1));
+}
+
+int ShapeCast2D::get_collision_count() const {
+ return result.size();
+}
+
+bool ShapeCast2D::is_colliding() const {
+ return collided;
+}
+
+Object *ShapeCast2D::get_collider(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), nullptr, "No collider found.");
+
+ if (result[p_idx].collider_id.is_null()) {
+ return nullptr;
+ }
+ return ObjectDB::get_instance(result[p_idx].collider_id);
+}
+
+int ShapeCast2D::get_collider_shape(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found.");
+ return result[p_idx].shape;
+}
+
+Vector2 ShapeCast2D::get_collision_point(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision point found.");
+ return result[p_idx].point;
+}
+
+Vector2 ShapeCast2D::get_collision_normal(int p_idx) const {
+ ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), Vector2(), "No collision normal found.");
+ return result[p_idx].normal;
+}
+
+real_t ShapeCast2D::get_closest_collision_safe_fraction() const {
+ return collision_safe_fraction;
+}
+
+real_t ShapeCast2D::get_closest_collision_unsafe_fraction() const {
+ return collision_unsafe_fraction;
+}
+
+void ShapeCast2D::set_enabled(bool p_enabled) {
+ enabled = p_enabled;
+ update();
+ if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
+ set_physics_process_internal(p_enabled);
+ }
+ if (!p_enabled) {
+ collided = false;
+ }
+}
+
+bool ShapeCast2D::is_enabled() const {
+ return enabled;
+}
+
+void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) {
+ shape = p_shape;
+ if (p_shape.is_valid()) {
+ shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_redraw_shape));
+ shape_rid = shape->get_rid();
+ }
+ update_configuration_warnings();
+ update();
+}
+
+Ref<Shape2D> ShapeCast2D::get_shape() const {
+ return shape;
+}
+
+void ShapeCast2D::set_exclude_parent_body(bool p_exclude_parent_body) {
+ if (exclude_parent_body == p_exclude_parent_body) {
+ return;
+ }
+ exclude_parent_body = p_exclude_parent_body;
+
+ if (!is_inside_tree()) {
+ return;
+ }
+ if (Object::cast_to<CollisionObject2D>(get_parent())) {
+ if (exclude_parent_body) {
+ exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
+ } else {
+ exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
+ }
+ }
+}
+
+bool ShapeCast2D::get_exclude_parent_body() const {
+ return exclude_parent_body;
+}
+
+void ShapeCast2D::_redraw_shape() {
+ update();
+}
+
+void ShapeCast2D::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (enabled && !Engine::get_singleton()->is_editor_hint()) {
+ set_physics_process_internal(true);
+ } else {
+ set_physics_process_internal(false);
+ }
+ if (Object::cast_to<CollisionObject2D>(get_parent())) {
+ if (exclude_parent_body) {
+ exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
+ } else {
+ exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid());
+ }
+ }
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ if (enabled) {
+ set_physics_process_internal(false);
+ }
+ } break;
+
+ case NOTIFICATION_DRAW: {
+#ifdef TOOLS_ENABLED
+ ERR_FAIL_COND(!is_inside_tree());
+ if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) {
+ break;
+ }
+ if (shape.is_null()) {
+ break;
+ }
+ Color draw_col = get_tree()->get_debug_collisions_color();
+ if (!enabled) {
+ float g = draw_col.get_v();
+ draw_col.r = g;
+ draw_col.g = g;
+ draw_col.b = g;
+ }
+ // Draw continuos chain of shapes along the cast.
+ const int steps = MAX(2, target_position.length() / shape->get_rect().get_size().length() * 4);
+ for (int i = 0; i <= steps; ++i) {
+ Vector2 t = (real_t(i) / steps) * target_position;
+ draw_set_transform(t, 0.0, Size2(1, 1));
+ shape->draw(get_canvas_item(), draw_col);
+ }
+ draw_set_transform(Vector2(), 0.0, Size2(1, 1));
+
+ // Draw an arrow indicating where the ShapeCast is pointing to.
+ if (target_position != Vector2()) {
+ Transform2D xf;
+ xf.rotate(target_position.angle());
+ xf.translate(Vector2(target_position.length(), 0));
+
+ draw_line(Vector2(), target_position, draw_col, 2);
+ Vector<Vector2> pts;
+ float tsize = 8;
+ pts.push_back(xf.xform(Vector2(tsize, 0)));
+ pts.push_back(xf.xform(Vector2(0, Math_SQRT12 * tsize)));
+ pts.push_back(xf.xform(Vector2(0, -Math_SQRT12 * tsize)));
+ Vector<Color> cols;
+ for (int i = 0; i < 3; i++)
+ cols.push_back(draw_col);
+
+ draw_primitive(pts, cols, Vector<Vector2>());
+ }
+#endif
+ } break;
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ if (!enabled) {
+ break;
+ }
+ _update_shapecast_state();
+ } break;
+ }
+}
+
+void ShapeCast2D::_update_shapecast_state() {
+ result.clear();
+
+ ERR_FAIL_COND_MSG(shape.is_null(), "Invalid shape.");
+
+ Ref<World2D> w2d = get_world_2d();
+ ERR_FAIL_COND(w2d.is_null());
+
+ PhysicsDirectSpaceState2D *dss = PhysicsServer2D::get_singleton()->space_get_direct_state(w2d->get_space());
+ ERR_FAIL_COND(!dss);
+
+ Transform2D gt = get_global_transform();
+
+ PhysicsDirectSpaceState2D::ShapeParameters params;
+ params.shape_rid = shape_rid;
+ params.transform = gt;
+ params.motion = gt.basis_xform(target_position);
+ params.margin = margin;
+ params.exclude = exclude;
+ params.collision_mask = collision_mask;
+ params.collide_with_bodies = collide_with_bodies;
+ params.collide_with_areas = collide_with_areas;
+
+ collision_safe_fraction = 0.0;
+ collision_unsafe_fraction = 0.0;
+
+ if (target_position != Vector2()) {
+ dss->cast_motion(params, collision_safe_fraction, collision_unsafe_fraction);
+ if (collision_unsafe_fraction < 1.0) {
+ // Move shape transform to the point of impact,
+ // so we can collect contact info at that point.
+ gt.set_origin(gt.get_origin() + params.motion * (collision_unsafe_fraction + CMP_EPSILON));
+ params.transform = gt;
+ }
+ }
+ // Regardless of whether the shape is stuck or it's moved along
+ // the motion vector, we'll only consider static collisions from now on.
+ params.motion = Vector2();
+
+ bool intersected = true;
+ while (intersected && result.size() < max_results) {
+ PhysicsDirectSpaceState2D::ShapeRestInfo info;
+ intersected = dss->rest_info(params, &info);
+ if (intersected) {
+ result.push_back(info);
+ params.exclude.insert(info.rid);
+ }
+ }
+ collided = !result.is_empty();
+}
+
+void ShapeCast2D::force_shapecast_update() {
+ _update_shapecast_state();
+}
+
+void ShapeCast2D::add_exception_rid(const RID &p_rid) {
+ exclude.insert(p_rid);
+}
+
+void ShapeCast2D::add_exception(const Object *p_object) {
+ ERR_FAIL_NULL(p_object);
+ const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object);
+ if (!co) {
+ return;
+ }
+ add_exception_rid(co->get_rid());
+}
+
+void ShapeCast2D::remove_exception_rid(const RID &p_rid) {
+ exclude.erase(p_rid);
+}
+
+void ShapeCast2D::remove_exception(const Object *p_object) {
+ ERR_FAIL_NULL(p_object);
+ const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object);
+ if (!co) {
+ return;
+ }
+ remove_exception_rid(co->get_rid());
+}
+
+void ShapeCast2D::clear_exceptions() {
+ exclude.clear();
+}
+
+void ShapeCast2D::set_collide_with_areas(bool p_clip) {
+ collide_with_areas = p_clip;
+}
+
+bool ShapeCast2D::is_collide_with_areas_enabled() const {
+ return collide_with_areas;
+}
+
+void ShapeCast2D::set_collide_with_bodies(bool p_clip) {
+ collide_with_bodies = p_clip;
+}
+
+bool ShapeCast2D::is_collide_with_bodies_enabled() const {
+ return collide_with_bodies;
+}
+
+Array ShapeCast2D::_get_collision_result() const {
+ Array ret;
+
+ for (int i = 0; i < result.size(); ++i) {
+ const PhysicsDirectSpaceState2D::ShapeRestInfo &sri = result[i];
+
+ Dictionary col;
+ col["point"] = sri.point;
+ col["normal"] = sri.normal;
+ col["rid"] = sri.rid;
+ col["collider"] = ObjectDB::get_instance(sri.collider_id);
+ col["collider_id"] = sri.collider_id;
+ col["shape"] = sri.shape;
+ col["linear_velocity"] = sri.linear_velocity;
+
+ ret.push_back(col);
+ }
+ return ret;
+}
+
+TypedArray<String> ShapeCast2D::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node2D::get_configuration_warnings();
+
+ if (shape.is_null()) {
+ warnings.push_back(TTR("This node cannot interact with other objects unless a Shape2D is assigned."));
+ }
+ return warnings;
+}
+
+void ShapeCast2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &ShapeCast2D::set_enabled);
+ ClassDB::bind_method(D_METHOD("is_enabled"), &ShapeCast2D::is_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_shape", "shape"), &ShapeCast2D::set_shape);
+ ClassDB::bind_method(D_METHOD("get_shape"), &ShapeCast2D::get_shape);
+
+ ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &ShapeCast2D::set_target_position);
+ ClassDB::bind_method(D_METHOD("get_target_position"), &ShapeCast2D::get_target_position);
+
+ ClassDB::bind_method(D_METHOD("set_margin", "margin"), &ShapeCast2D::set_margin);
+ ClassDB::bind_method(D_METHOD("get_margin"), &ShapeCast2D::get_margin);
+
+ ClassDB::bind_method(D_METHOD("set_max_results", "max_results"), &ShapeCast2D::set_max_results);
+ ClassDB::bind_method(D_METHOD("get_max_results"), &ShapeCast2D::get_max_results);
+
+ ClassDB::bind_method(D_METHOD("is_colliding"), &ShapeCast2D::is_colliding);
+ ClassDB::bind_method(D_METHOD("get_collision_count"), &ShapeCast2D::get_collision_count);
+
+ ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update);
+
+ ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider);
+ ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape);
+ ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point);
+ ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal);
+
+ ClassDB::bind_method(D_METHOD("get_closest_collision_safe_fraction"), &ShapeCast2D::get_closest_collision_safe_fraction);
+ ClassDB::bind_method(D_METHOD("get_closest_collision_unsafe_fraction"), &ShapeCast2D::get_closest_collision_unsafe_fraction);
+
+ ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ShapeCast2D::add_exception_rid);
+ ClassDB::bind_method(D_METHOD("add_exception", "node"), &ShapeCast2D::add_exception);
+
+ ClassDB::bind_method(D_METHOD("remove_exception_rid", "rid"), &ShapeCast2D::remove_exception_rid);
+ ClassDB::bind_method(D_METHOD("remove_exception", "node"), &ShapeCast2D::remove_exception);
+
+ ClassDB::bind_method(D_METHOD("clear_exceptions"), &ShapeCast2D::clear_exceptions);
+
+ ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ShapeCast2D::set_collision_mask);
+ ClassDB::bind_method(D_METHOD("get_collision_mask"), &ShapeCast2D::get_collision_mask);
+
+ ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ShapeCast2D::set_collision_mask_value);
+ ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ShapeCast2D::get_collision_mask_value);
+
+ ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &ShapeCast2D::set_exclude_parent_body);
+ ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &ShapeCast2D::get_exclude_parent_body);
+
+ ClassDB::bind_method(D_METHOD("set_collide_with_areas", "enable"), &ShapeCast2D::set_collide_with_areas);
+ ClassDB::bind_method(D_METHOD("is_collide_with_areas_enabled"), &ShapeCast2D::is_collide_with_areas_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies);
+ ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled);
+
+ ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position"), "set_target_position", "get_target_position");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_margin", "get_margin");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result");
+ ADD_GROUP("Collide With", "collide_with");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
+}
diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h
new file mode 100644
index 0000000000..fca6b46155
--- /dev/null
+++ b/scene/2d/shape_cast_2d.h
@@ -0,0 +1,120 @@
+/*************************************************************************/
+/* shape_cast_2d.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SHAPE_CAST_2D
+#define SHAPE_CAST_2D
+
+#include "scene/2d/node_2d.h"
+#include "scene/resources/shape_2d.h"
+
+class ShapeCast2D : public Node2D {
+ GDCLASS(ShapeCast2D, Node2D);
+
+ bool enabled = true;
+
+ Ref<Shape2D> shape;
+ RID shape_rid;
+ Vector2 target_position = Vector2(0, 50);
+
+ Set<RID> exclude;
+ real_t margin = 0.0;
+ uint32_t collision_mask = 1;
+ bool exclude_parent_body = true;
+ bool collide_with_areas = false;
+ bool collide_with_bodies = true;
+
+ // Result
+ int max_results = 32;
+ Vector<PhysicsDirectSpaceState2D::ShapeRestInfo> result;
+ bool collided = false;
+ real_t collision_safe_fraction = 1.0;
+ real_t collision_unsafe_fraction = 1.0;
+
+ Array _get_collision_result() const;
+ void _redraw_shape();
+
+protected:
+ void _notification(int p_what);
+ void _update_shapecast_state();
+ static void _bind_methods();
+
+public:
+ void set_collide_with_areas(bool p_clip);
+ bool is_collide_with_areas_enabled() const;
+
+ void set_collide_with_bodies(bool p_clip);
+ bool is_collide_with_bodies_enabled() const;
+
+ void set_enabled(bool p_enabled);
+ bool is_enabled() const;
+
+ void set_shape(const Ref<Shape2D> &p_shape);
+ Ref<Shape2D> get_shape() const;
+
+ void set_target_position(const Vector2 &p_point);
+ Vector2 get_target_position() const;
+
+ void set_margin(real_t p_margin);
+ real_t get_margin() const;
+
+ void set_max_results(int p_max_results);
+ int get_max_results() const;
+
+ void set_collision_mask(uint32_t p_mask);
+ uint32_t get_collision_mask() const;
+
+ void set_collision_mask_value(int p_layer_number, bool p_value);
+ bool get_collision_mask_value(int p_layer_number) const;
+
+ void set_exclude_parent_body(bool p_exclude_parent_body);
+ bool get_exclude_parent_body() const;
+
+ void force_shapecast_update();
+ bool is_colliding() const;
+
+ int get_collision_count() const;
+ Object *get_collider(int p_idx) const;
+ int get_collider_shape(int p_idx) const;
+ Vector2 get_collision_point(int p_idx) const;
+ Vector2 get_collision_normal(int p_idx) const;
+
+ real_t get_closest_collision_safe_fraction() const;
+ real_t get_closest_collision_unsafe_fraction() const;
+
+ void add_exception_rid(const RID &p_rid);
+ void add_exception(const Object *p_object);
+ void remove_exception_rid(const RID &p_rid);
+ void remove_exception(const Object *p_object);
+ void clear_exceptions();
+
+ TypedArray<String> get_configuration_warnings() const override;
+};
+
+#endif
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 5761f19a53..b2302d09db 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -385,7 +385,7 @@ void Sprite2D::_validate_property(PropertyInfo &property) const {
}
if (!region_enabled && (property.name == "region_rect" || property.name == "region_filter_clip")) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 222ec986b0..96c4164721 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -34,96 +34,322 @@
#include "servers/navigation_server_2d.h"
-void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
- ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
+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);
- size = size.max(p_coords + Vector2i(1, 1));
- pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
-}
-
-bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
- return pattern.has(p_coords);
-}
-
-void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
- ERR_FAIL_COND(!pattern.has(p_coords));
-
- pattern.erase(p_coords);
- if (p_update_size) {
- size = Vector2i();
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- size = size.max(E.key + Vector2i(1, 1));
+ 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;
}
-int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
-
- return pattern[p_coords].source_id;
-}
-
-Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
-
- return pattern[p_coords].get_atlas_coords();
-}
-
-int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
- ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
-
- return pattern[p_coords].alternative_tile;
-}
-
-TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
- // Returns the cells used in the tilemap.
- TypedArray<Vector2i> a;
- a.resize(pattern.size());
- int i = 0;
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- Vector2i p(E.key.x, E.key.y);
- a[i++] = p;
- }
-
- return a;
-}
+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;
-Vector2i TileMapPattern::get_size() const {
- return size;
-}
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
-void TileMapPattern::set_size(const Vector2i &p_size) {
- for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
- Vector2i coords = E.key;
- if (p_size.x <= coords.x || p_size.y <= coords.y) {
- ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
- };
+ 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;
+ }
+ }
}
-
- size = p_size;
-}
-
-bool TileMapPattern::is_empty() const {
- return pattern.is_empty();
-};
-
-void TileMapPattern::clear() {
- size = Vector2i();
- pattern.clear();
-};
-
-void TileMapPattern::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
- ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
- ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
- ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
- ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
- ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
-
- ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
- ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
- ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
- ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
+ 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) {
@@ -260,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;
@@ -397,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();
@@ -409,11 +634,24 @@ bool TileMap::is_layer_enabled(int p_layer) const {
return layers[p_layer].enabled;
}
+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_layer_internals(p_layer);
+ _recreate_layer_internals(p_layer);
+ emit_signal(SNAME("changed"));
+}
+
+Color TileMap::get_layer_modulate(int p_layer) const {
+ ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Color());
+ return layers[p_layer].modulate;
+}
+
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();
@@ -427,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"));
}
@@ -440,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();
@@ -565,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()) {
@@ -577,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)));
@@ -598,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());
}
}
@@ -608,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) {
@@ -798,8 +1052,9 @@ void TileMap::_rendering_cleanup_layer(int p_layer) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
RenderingServer *rs = RenderingServer::get_singleton();
- if (!layers[p_layer].canvas_item.is_valid()) {
+ if (layers[p_layer].canvas_item.is_valid()) {
rs->free(layers[p_layer].canvas_item);
+ layers[p_layer].canvas_item = RID();
}
}
@@ -832,6 +1087,19 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
int prev_z_index = 0;
RID prev_canvas_item;
+ Color modulate = get_self_modulate();
+ modulate *= get_layer_modulate(q.layer);
+ if (selected_layer >= 0) {
+ int z1 = get_layer_z_index(q.layer);
+ int z2 = get_layer_z_index(selected_layer);
+ if (z1 < z2 || (z1 == z2 && q.layer < selected_layer)) {
+ modulate = modulate.darkened(0.5);
+ } else if (z1 > z2 || (z1 == z2 && q.layer > selected_layer)) {
+ modulate = modulate.darkened(0.5);
+ modulate.a *= 0.3;
+ }
+ }
+
// 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);
@@ -847,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();
@@ -894,16 +1168,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List
}
// Drawing the tile in the canvas item.
- Color modulate = get_self_modulate();
- if (selected_layer >= 0) {
- if (q.layer < selected_layer) {
- modulate = modulate.darkened(0.5);
- } else if (q.layer > selected_layer) {
- modulate = modulate.darkened(0.5);
- modulate.a *= 0.3;
- }
- }
- 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++) {
@@ -994,7 +1259,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
Vector2i grid_size = atlas_source->get_atlas_grid_size();
- if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) {
+ if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) {
// Generate a random color from the hashed values of the tiles.
Array to_hash;
to_hash.push_back(c.source_id);
@@ -1020,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));
@@ -1034,7 +1299,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
}
// Get the texture.
- Ref<Texture2D> tex = atlas_source->get_texture();
+ Ref<Texture2D> tex = atlas_source->get_runtime_texture();
if (!tex.is_valid()) {
return;
}
@@ -1046,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;
@@ -1056,7 +1321,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
// Get destination rect.
Rect2 dest_rect;
- dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size;
+ dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size;
dest_rect.size.x += FP_ADJUST;
dest_rect.size.y += FP_ADJUST;
@@ -1077,10 +1342,10 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
// Draw the tile.
if (p_frame >= 0) {
- Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame);
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame);
tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
} else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) {
- Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, 0);
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0);
tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
} else {
real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords);
@@ -1090,7 +1355,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe
real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed;
RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, 0.0);
- Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, frame);
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame);
tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
time += frame_duration;
@@ -1205,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);
@@ -1398,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++) {
@@ -1482,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);
@@ -1770,11 +2049,12 @@ int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bo
return E->get().alternative_tile;
}
-TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
+Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) {
ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr);
ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
- TileMapPattern *output = memnew(TileMapPattern);
+ Ref<TileMapPattern> output;
+ output.instantiate();
if (p_coords_array.is_empty()) {
return output;
}
@@ -1823,7 +2103,7 @@ TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_
return output;
}
-Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern) {
+Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, Ref<TileMapPattern> p_pattern) {
ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i());
Vector2i output = p_position_in_tilemap + p_coords_in_pattern;
@@ -1846,7 +2126,7 @@ Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_
return output;
}
-void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern) {
+void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern) {
ERR_FAIL_INDEX(p_layer, (int)layers.size());
ERR_FAIL_COND(!tile_set.is_valid());
@@ -1857,6 +2137,242 @@ void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern
}
}
+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;
+ 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.get_terrain(bit));
+ 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;
+ }
+ }
+ }
+
+ 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;
+ 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.get_terrain(side));
+ output.insert(c);
+ }
+ }
+
+ 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 < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
+ terrains.insert(pattern.get_terrain(side));
+ }
+ }
+ 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_terrains_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;
@@ -1922,6 +2438,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);
@@ -2031,6 +2558,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
@@ -2083,6 +2648,9 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
} else if (components[1] == "enabled") {
set_layer_enabled(index, p_value);
return true;
+ } else if (components[1] == "modulate") {
+ set_layer_modulate(index, p_value);
+ return true;
} else if (components[1] == "y_sort_enabled") {
set_layer_y_sort_enabled(index, p_value);
return true;
@@ -2119,6 +2687,9 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
} else if (components[1] == "enabled") {
r_ret = is_layer_enabled(index);
return true;
+ } else if (components[1] == "modulate") {
+ r_ret = get_layer_modulate(index);
+ return true;
} else if (components[1] == "y_sort_enabled") {
r_ret = is_layer_y_sort_enabled(index);
return true;
@@ -2139,15 +2710,16 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
}
void TileMap::_get_property_list(List<PropertyInfo> *p_list) const {
- p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
for (unsigned int i = 0; i < layers.size(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, vformat("layer_%d/name", i), PROPERTY_HINT_NONE));
p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE));
+ p_list->push_back(PropertyInfo(Variant::COLOR, vformat("layer_%d/modulate", i), PROPERTY_HINT_NONE));
p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/y_sort_enabled", i), PROPERTY_HINT_NONE));
p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE));
p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE));
- p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
@@ -2401,38 +2973,38 @@ bool TileMap::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const
TileSet::TileShape shape = tile_set->get_tile_shape();
if (shape == TileSet::TILE_SHAPE_SQUARE) {
return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
} else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
} else {
if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
} else {
return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
- p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE ||
+ p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
}
}
}
@@ -2477,7 +3049,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(is_offset ? 0 : -1, 1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-1, 0);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(is_offset ? 0 : -1, -1);
@@ -2501,7 +3073,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(1, is_offset ? 0 : -1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(0, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, is_offset ? 0 : -1);
@@ -2528,7 +3100,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(is_offset ? -1 : 0, 1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-1, 0);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(is_offset ? -1 : 0, -1);
@@ -2552,7 +3124,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(1, is_offset ? -1 : 0);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(0, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, is_offset ? -1 : 0);
@@ -2579,7 +3151,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(-1, 1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-1, 0);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(0, -1);
@@ -2602,7 +3174,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(1, -1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(0, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, 0);
@@ -2627,7 +3199,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(-1, 1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-2, 1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, 0);
@@ -2650,7 +3222,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(1, -1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(1, -2);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(0, -1);
@@ -2679,7 +3251,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(-1, 0);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-1, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(0, -1);
@@ -2702,7 +3274,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(0, -1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(-1, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, 0);
@@ -2727,7 +3299,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) {
return p_coords + Vector2i(0, 1);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) {
return p_coords + Vector2i(-1, 1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(-1, 0);
@@ -2750,7 +3322,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) {
return p_coords + Vector2i(1, 0);
} else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) ||
- (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
+ (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) {
return p_coords + Vector2i(1, -1);
} else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) {
return p_coords + Vector2i(0, -1);
@@ -3027,6 +3599,8 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_layer_name", "layer"), &TileMap::get_layer_name);
ClassDB::bind_method(D_METHOD("set_layer_enabled", "layer", "enabled"), &TileMap::set_layer_enabled);
ClassDB::bind_method(D_METHOD("is_layer_enabled", "layer"), &TileMap::is_layer_enabled);
+ ClassDB::bind_method(D_METHOD("set_layer_modulate", "layer", "enabled"), &TileMap::set_layer_modulate);
+ ClassDB::bind_method(D_METHOD("get_layer_modulate", "layer"), &TileMap::get_layer_modulate);
ClassDB::bind_method(D_METHOD("set_layer_y_sort_enabled", "layer", "y_sort_enabled"), &TileMap::set_layer_y_sort_enabled);
ClassDB::bind_method(D_METHOD("is_layer_y_sort_enabled", "layer"), &TileMap::is_layer_y_sort_enabled);
ClassDB::bind_method(D_METHOD("set_layer_y_sort_origin", "layer", "y_sort_origin"), &TileMap::set_layer_y_sort_origin);
@@ -3049,10 +3623,18 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid);
+ ClassDB::bind_method(D_METHOD("get_pattern", "layer", "coords_array"), &TileMap::get_pattern);
+ 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);
@@ -3065,9 +3647,14 @@ void TileMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants);
- ClassDB::bind_method(D_METHOD("_set_tile_data", "layer"), &TileMap::_set_tile_data);
+ ClassDB::bind_method(D_METHOD("_set_tile_data", "layer", "data"), &TileMap::_set_tile_data);
ClassDB::bind_method(D_METHOD("_get_tile_data", "layer"), &TileMap::_get_tile_data);
+ 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");
@@ -3087,8 +3674,16 @@ void TileMap::_bind_methods() {
void TileMap::_tile_set_changed() {
emit_signal(SNAME("changed"));
- _clear_internals();
- _recreate_internals();
+ _tile_set_changed_deferred_update_needed = true;
+ call_deferred(SNAME("_tile_set_changed_deferred_update"));
+}
+
+void TileMap::_tile_set_changed_deferred_update() {
+ if (_tile_set_changed_deferred_update_needed) {
+ _clear_internals();
+ _recreate_internals();
+ _tile_set_changed_deferred_update_needed = false;
+ }
}
TileMap::TileMap() {
@@ -3102,5 +3697,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 2faede2445..f260422290 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -37,51 +37,6 @@
class TileSetAtlasSource;
-union TileMapCell {
- struct {
- int32_t source_id : 16;
- int16_t coord_x : 16;
- int16_t coord_y : 16;
- int32_t alternative_tile : 16;
- };
-
- uint64_t _u64t;
- TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) {
- source_id = p_source_id;
- set_atlas_coords(p_atlas_coords);
- alternative_tile = p_alternative_tile;
- }
-
- Vector2i get_atlas_coords() const {
- return Vector2i(coord_x, coord_y);
- }
-
- void set_atlas_coords(const Vector2i &r_coords) {
- coord_x = r_coords.x;
- coord_y = r_coords.y;
- }
-
- bool operator<(const TileMapCell &p_other) const {
- if (source_id == p_other.source_id) {
- if (coord_x == p_other.coord_x) {
- if (coord_y == p_other.coord_y) {
- return alternative_tile < p_other.alternative_tile;
- } else {
- return coord_y < p_other.coord_y;
- }
- } else {
- return coord_x < p_other.coord_x;
- }
- } else {
- return source_id < p_other.source_id;
- }
- }
-
- bool operator!=(const TileMapCell &p_other) const {
- return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
- }
-};
-
struct TileMapQuadrant {
struct CoordsWorldComparator {
_ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const {
@@ -124,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;
@@ -150,36 +108,47 @@ struct TileMapQuadrant {
}
};
-class TileMapPattern : public Object {
- GDCLASS(TileMapPattern, Object);
+class TileMap : public Node2D {
+ GDCLASS(TileMap, Node2D);
- Vector2i size;
- Map<Vector2i, TileMapCell> pattern;
+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;
+ }
-protected:
- static void _bind_methods();
+ String to_string() const {
+ return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
+ }
-public:
- void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
- bool has_cell(const Vector2i &p_coords) const;
- void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
- int get_cell_source_id(const Vector2i &p_coords) const;
- Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
- int get_cell_alternative_tile(const Vector2i &p_coords) const;
+ Vector2i get_base_cell_coords() const {
+ return base_cell_coords;
+ }
- TypedArray<Vector2i> get_used_cells() const;
+ Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
- Vector2i get_size() const;
- void set_size(const Vector2i &p_size);
- bool is_empty() const;
+ void set_terrain(int p_terrain) {
+ terrain = p_terrain;
+ }
- void clear();
-};
+ int get_terrain() const {
+ return terrain;
+ }
-class TileMap : public Node2D {
- GDCLASS(TileMap, Node2D);
+ TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
+ TerrainConstraint() {}
+ };
-public:
enum VisibilityMode {
VISIBILITY_MODE_DEFAULT,
VISIBILITY_MODE_FORCE_SHOW,
@@ -219,6 +188,7 @@ private:
struct TileMapLayer {
String name;
bool enabled = true;
+ Color modulate = Color(1, 1, 1, 1);
bool y_sort_enabled = false;
int y_sort_origin = 0;
int z_index = 0;
@@ -244,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);
@@ -279,11 +250,18 @@ 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();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -310,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;
@@ -321,6 +299,8 @@ public:
String get_layer_name(int p_layer) const;
void set_layer_enabled(int p_layer, bool p_visible);
bool is_layer_enabled(int p_layer) const;
+ void set_layer_modulate(int p_layer, Color p_modulate);
+ Color get_layer_modulate(int p_layer) const;
void set_layer_y_sort_enabled(int p_layer, bool p_enabled);
bool is_layer_y_sort_enabled(int p_layer) const;
void set_layer_y_sort_origin(int p_layer, int p_y_sort_origin);
@@ -333,20 +313,29 @@ 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;
- TileMapPattern *get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array);
- Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern);
- void set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern);
+ // 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;
@@ -378,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 7e4c40ca0e..073543638f 100644
--- a/scene/3d/area_3d.cpp
+++ b/scene/3d/area_3d.cpp
@@ -33,13 +33,13 @@
#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
-void Area3D::set_space_override_mode(SpaceOverride p_mode) {
- space_override = p_mode;
- PhysicsServer3D::get_singleton()->area_set_space_override_mode(get_rid(), PhysicsServer3D::AreaSpaceOverrideMode(p_mode));
+void Area3D::set_gravity_space_override_mode(SpaceOverride p_mode) {
+ gravity_space_override = p_mode;
+ PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_OVERRIDE_MODE, p_mode);
}
-Area3D::SpaceOverride Area3D::get_space_override_mode() const {
- return space_override;
+Area3D::SpaceOverride Area3D::get_gravity_space_override_mode() const {
+ return gravity_space_override;
}
void Area3D::set_gravity_is_point(bool p_enabled) {
@@ -51,21 +51,30 @@ bool Area3D::is_gravity_a_point() const {
return gravity_is_point;
}
-void Area3D::set_gravity_distance_scale(real_t p_scale) {
+void Area3D::set_gravity_point_distance_scale(real_t p_scale) {
gravity_distance_scale = p_scale;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_DISTANCE_SCALE, p_scale);
}
-real_t Area3D::get_gravity_distance_scale() const {
+real_t Area3D::get_gravity_point_distance_scale() const {
return gravity_distance_scale;
}
-void Area3D::set_gravity_vector(const Vector3 &p_vec) {
- gravity_vec = p_vec;
- PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_vec);
+void Area3D::set_gravity_point_center(const Vector3 &p_center) {
+ gravity_vec = p_center;
+ PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_center);
}
-Vector3 Area3D::get_gravity_vector() const {
+const Vector3 &Area3D::get_gravity_point_center() const {
+ return gravity_vec;
+}
+
+void Area3D::set_gravity_direction(const Vector3 &p_direction) {
+ gravity_vec = p_direction;
+ PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, p_direction);
+}
+
+const Vector3 &Area3D::get_gravity_direction() const {
return gravity_vec;
}
@@ -78,6 +87,24 @@ real_t Area3D::get_gravity() const {
return gravity;
}
+void Area3D::set_linear_damp_space_override_mode(SpaceOverride p_mode) {
+ linear_damp_space_override = p_mode;
+ PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE, p_mode);
+}
+
+Area3D::SpaceOverride Area3D::get_linear_damp_space_override_mode() const {
+ return linear_damp_space_override;
+}
+
+void Area3D::set_angular_damp_space_override_mode(SpaceOverride p_mode) {
+ angular_damp_space_override = p_mode;
+ PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE, p_mode);
+}
+
+Area3D::SpaceOverride Area3D::get_angular_damp_space_override_mode() const {
+ return angular_damp_space_override;
+}
+
void Area3D::set_linear_damp(real_t p_linear_damp) {
linear_damp = p_linear_damp;
PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, p_linear_damp);
@@ -334,11 +361,11 @@ void Area3D::set_monitoring(bool p_enable) {
monitoring = p_enable;
if (monitoring) {
- PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_body_inout);
- PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_area_inout);
+ PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), callable_mp(this, &Area3D::_body_inout));
+ PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), callable_mp(this, &Area3D::_area_inout));
} else {
- PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), nullptr, StringName());
- PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), nullptr, StringName());
+ PhysicsServer3D::get_singleton()->area_set_monitor_callback(get_rid(), Callable());
+ PhysicsServer3D::get_singleton()->area_set_area_monitor_callback(get_rid(), Callable());
_clear_monitoring();
}
}
@@ -579,25 +606,58 @@ void Area3D::_validate_property(PropertyInfo &property) const {
}
property.hint_string = options;
+ } else if (property.name.begins_with("gravity") && property.name != "gravity_space_override") {
+ if (gravity_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ } else {
+ if (gravity_is_point) {
+ if (property.name == "gravity_direction") {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ } else {
+ if (property.name.begins_with("gravity_point_")) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ }
+ }
+ } else if (property.name.begins_with("linear_damp") && property.name != "linear_damp_space_override") {
+ if (linear_damp_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
+ } else if (property.name.begins_with("angular_damp") && property.name != "angular_damp_space_override") {
+ if (angular_damp_space_override == SPACE_OVERRIDE_DISABLED) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
+ }
}
+
+ CollisionObject3D::_validate_property(property);
}
void Area3D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_space_override_mode", "enable"), &Area3D::set_space_override_mode);
- ClassDB::bind_method(D_METHOD("get_space_override_mode"), &Area3D::get_space_override_mode);
+ ClassDB::bind_method(D_METHOD("set_gravity_space_override_mode", "space_override_mode"), &Area3D::set_gravity_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_gravity_space_override_mode"), &Area3D::get_gravity_space_override_mode);
ClassDB::bind_method(D_METHOD("set_gravity_is_point", "enable"), &Area3D::set_gravity_is_point);
ClassDB::bind_method(D_METHOD("is_gravity_a_point"), &Area3D::is_gravity_a_point);
- ClassDB::bind_method(D_METHOD("set_gravity_distance_scale", "distance_scale"), &Area3D::set_gravity_distance_scale);
- ClassDB::bind_method(D_METHOD("get_gravity_distance_scale"), &Area3D::get_gravity_distance_scale);
+ ClassDB::bind_method(D_METHOD("set_gravity_point_distance_scale", "distance_scale"), &Area3D::set_gravity_point_distance_scale);
+ ClassDB::bind_method(D_METHOD("get_gravity_point_distance_scale"), &Area3D::get_gravity_point_distance_scale);
- ClassDB::bind_method(D_METHOD("set_gravity_vector", "vector"), &Area3D::set_gravity_vector);
- ClassDB::bind_method(D_METHOD("get_gravity_vector"), &Area3D::get_gravity_vector);
+ ClassDB::bind_method(D_METHOD("set_gravity_point_center", "center"), &Area3D::set_gravity_point_center);
+ ClassDB::bind_method(D_METHOD("get_gravity_point_center"), &Area3D::get_gravity_point_center);
+
+ ClassDB::bind_method(D_METHOD("set_gravity_direction", "direction"), &Area3D::set_gravity_direction);
+ ClassDB::bind_method(D_METHOD("get_gravity_direction"), &Area3D::get_gravity_direction);
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &Area3D::set_gravity);
ClassDB::bind_method(D_METHOD("get_gravity"), &Area3D::get_gravity);
+ ClassDB::bind_method(D_METHOD("set_linear_damp_space_override_mode", "space_override_mode"), &Area3D::set_linear_damp_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_linear_damp_space_override_mode"), &Area3D::get_linear_damp_space_override_mode);
+
+ ClassDB::bind_method(D_METHOD("set_angular_damp_space_override_mode", "space_override_mode"), &Area3D::set_angular_damp_space_override_mode);
+ ClassDB::bind_method(D_METHOD("get_angular_damp_space_override_mode"), &Area3D::get_angular_damp_space_override_mode);
+
ClassDB::bind_method(D_METHOD("set_angular_damp", "angular_damp"), &Area3D::set_angular_damp);
ClassDB::bind_method(D_METHOD("get_angular_damp"), &Area3D::get_angular_damp);
@@ -628,9 +688,6 @@ void Area3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("overlaps_body", "body"), &Area3D::overlaps_body);
ClassDB::bind_method(D_METHOD("overlaps_area", "area"), &Area3D::overlaps_area);
- ClassDB::bind_method(D_METHOD("_body_inout"), &Area3D::_body_inout);
- ClassDB::bind_method(D_METHOD("_area_inout"), &Area3D::_area_inout);
-
ClassDB::bind_method(D_METHOD("set_audio_bus_override", "enable"), &Area3D::set_audio_bus_override);
ClassDB::bind_method(D_METHOD("is_overriding_audio_bus"), &Area3D::is_overriding_audio_bus);
@@ -649,13 +706,13 @@ void Area3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_reverb_uniformity", "amount"), &Area3D::set_reverb_uniformity);
ClassDB::bind_method(D_METHOD("get_reverb_uniformity"), &Area3D::get_reverb_uniformity);
- ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
- ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D"), PropertyInfo(Variant::INT, "area_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("area_entered", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D")));
ADD_SIGNAL(MethodInfo("area_exited", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area3D")));
@@ -663,14 +720,23 @@ void Area3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority");
- ADD_GROUP("Physics Overrides", "");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_distance_scale", "get_gravity_distance_scale");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_vec"), "set_gravity_vector", "get_gravity_vector");
+ ADD_GROUP("Gravity", "gravity_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "gravity_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_space_override_mode", "get_gravity_space_override_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_gravity_is_point", "is_gravity_a_point");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_point_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_point_distance_scale", "get_gravity_point_distance_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_point_center"), "set_gravity_point_center", "get_gravity_point_center");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_direction"), "set_gravity_direction", "get_gravity_direction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-32,32,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity");
+
+ ADD_GROUP("Linear Damp", "linear_damp_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_linear_damp_space_override_mode", "get_linear_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
+
+ ADD_GROUP("Angular Damp", "angular_damp_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_angular_damp_space_override_mode", "get_angular_damp_space_override_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
+
+ ADD_GROUP("Wind", "wind_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_force_magnitude", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_wind_force_magnitude", "get_wind_force_magnitude");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_attenuation_factor", PROPERTY_HINT_RANGE, "0.0,3.0,0.001,or_greater"), "set_wind_attenuation_factor", "get_wind_attenuation_factor");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "wind_source_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_wind_source_path", "get_wind_source_path");
@@ -695,7 +761,7 @@ void Area3D::_bind_methods() {
Area3D::Area3D() :
CollisionObject3D(PhysicsServer3D::get_singleton()->area_create(), true) {
set_gravity(9.8);
- set_gravity_vector(Vector3(0, -1, 0));
+ set_gravity_direction(Vector3(0, -1, 0));
set_monitoring(true);
set_monitorable(true);
}
diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h
index 847d1c5966..7f31be2e17 100644
--- a/scene/3d/area_3d.h
+++ b/scene/3d/area_3d.h
@@ -47,17 +47,23 @@ public:
};
private:
- SpaceOverride space_override = SPACE_OVERRIDE_DISABLED;
+ SpaceOverride gravity_space_override = SPACE_OVERRIDE_DISABLED;
Vector3 gravity_vec;
real_t gravity;
bool gravity_is_point = false;
real_t gravity_distance_scale = 0.0;
+
+ SpaceOverride linear_damp_space_override = SPACE_OVERRIDE_DISABLED;
+ SpaceOverride angular_damp_space_override = SPACE_OVERRIDE_DISABLED;
real_t angular_damp = 0.1;
real_t linear_damp = 0.1;
+
int priority = 0;
+
real_t wind_force_magnitude = 0.0;
real_t wind_attenuation_factor = 0.0;
NodePath wind_source_path;
+
bool monitoring = false;
bool monitorable = false;
bool locked = false;
@@ -144,21 +150,30 @@ protected:
static void _bind_methods();
public:
- void set_space_override_mode(SpaceOverride p_mode);
- SpaceOverride get_space_override_mode() const;
+ void set_gravity_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_gravity_space_override_mode() const;
void set_gravity_is_point(bool p_enabled);
bool is_gravity_a_point() const;
- void set_gravity_distance_scale(real_t p_scale);
- real_t get_gravity_distance_scale() const;
+ void set_gravity_point_distance_scale(real_t p_scale);
+ real_t get_gravity_point_distance_scale() const;
- void set_gravity_vector(const Vector3 &p_vec);
- Vector3 get_gravity_vector() const;
+ void set_gravity_point_center(const Vector3 &p_center);
+ const Vector3 &get_gravity_point_center() const;
+
+ void set_gravity_direction(const Vector3 &p_direction);
+ const Vector3 &get_gravity_direction() const;
void set_gravity(real_t p_gravity);
real_t get_gravity() const;
+ void set_linear_damp_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_linear_damp_space_override_mode() const;
+
+ void set_angular_damp_space_override_mode(SpaceOverride p_mode);
+ SpaceOverride get_angular_damp_space_override_mode() const;
+
void set_angular_damp(real_t p_angular_damp);
real_t get_angular_damp() const;
diff --git a/scene/3d/audio_listener_3d.h b/scene/3d/audio_listener_3d.h
index 492cacb0e9..31de3b4fb1 100644
--- a/scene/3d/audio_listener_3d.h
+++ b/scene/3d/audio_listener_3d.h
@@ -63,9 +63,6 @@ public:
virtual Transform3D get_listener_transform() const;
- void set_visible_layers(uint32_t p_layers);
- uint32_t get_visible_layers() const;
-
AudioListener3D();
~AudioListener3D();
};
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index a54b10ba70..34f748b197 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -282,7 +282,7 @@ void AudioStreamPlayer3D::_notification(int p_what) {
ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
Map<StringName, Vector<AudioFrame>> bus_map;
bus_map[_get_actual_bus()] = volume_vector;
- AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), linear_attenuation, attenuation_filter_cutoff_hz, actual_pitch_scale);
+ AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
stream_playbacks.push_back(new_playback);
setplay.set(-1);
}
@@ -292,7 +292,6 @@ void AudioStreamPlayer3D::_notification(int p_what) {
Vector<Ref<AudioStreamPlayback>> playbacks_to_remove;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) {
- emit_signal(SNAME("finished"));
playbacks_to_remove.push_back(playback);
}
}
@@ -305,6 +304,9 @@ void AudioStreamPlayer3D::_notification(int p_what) {
active.clear();
set_physics_process_internal(false);
}
+ if (!playbacks_to_remove.is_empty()) {
+ emit_signal(SNAME("finished"));
+ }
}
while (stream_playbacks.size() > max_polyphony) {
@@ -325,7 +327,13 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() {
PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS];
- int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
+ PhysicsDirectSpaceState3D::PointParameters point_params;
+ point_params.position = global_pos;
+ point_params.collision_mask = area_mask;
+ point_params.collide_with_bodies = false;
+ point_params.collide_with_areas = true;
+
+ int areas = space_state->intersect_point(point_params, sr, MAX_INTERSECT_AREAS);
for (int i = 0; i < areas; i++) {
if (!sr[i].collider) {
@@ -638,6 +646,8 @@ void AudioStreamPlayer3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
+
+ Node3D::_validate_property(property);
}
void AudioStreamPlayer3D::_bus_layout_changed() {
@@ -835,7 +845,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Log,Disabled"), "set_attenuation_model", "get_attenuation_model");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Logarithmic,Disabled"), "set_attenuation_model", "get_attenuation_model");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_db", PROPERTY_HINT_RANGE, "-80,80"), "set_unit_db", "get_unit_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6"), "set_max_db", "get_max_db");
@@ -843,7 +853,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater"), "set_max_distance", "get_max_distance");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask");
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index afd11482e3..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) {
@@ -215,8 +217,6 @@ void BoneAttachment3D::_transform_changed() {
sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true);
} else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) {
sk->set_bone_local_pose_override(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans), 1.0, true);
- } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) {
- sk->set_bone_custom_pose(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans));
}
}
}
@@ -273,8 +273,6 @@ void BoneAttachment3D::set_override_pose(bool p_override) {
sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false);
} else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) {
sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false);
- } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) {
- sk->set_bone_custom_pose(bone_idx, Transform3D());
}
}
_transform_changed();
@@ -294,8 +292,6 @@ void BoneAttachment3D::set_override_mode(int p_mode) {
sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false);
} else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) {
sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false);
- } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) {
- sk->set_bone_custom_pose(bone_idx, Transform3D());
}
}
override_mode = p_mode;
diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h
index cf681cace8..57b9854e0e 100644
--- a/scene/3d/bone_attachment_3d.h
+++ b/scene/3d/bone_attachment_3d.h
@@ -47,7 +47,6 @@ class BoneAttachment3D : public Node3D {
enum OVERRIDE_MODES {
MODE_GLOBAL_POSE,
MODE_LOCAL_POSE,
- MODE_CUSTOM_POSE
};
bool use_external_skeleton = false;
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 18b7bcc789..cc5b7078e3 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -60,17 +60,19 @@ void Camera3D::_update_camera_mode() {
void Camera3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "fov") {
if (mode != PROJECTION_PERSPECTIVE) {
- p_property.usage = PROPERTY_USAGE_NOEDITOR;
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "size") {
if (mode != PROJECTION_ORTHOGONAL && mode != PROJECTION_FRUSTUM) {
- p_property.usage = PROPERTY_USAGE_NOEDITOR;
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "frustum_offset") {
if (mode != PROJECTION_FRUSTUM) {
- p_property.usage = PROPERTY_USAGE_NOEDITOR;
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
+
+ Node3D::_validate_property(p_property);
}
void Camera3D::_update_camera() {
@@ -656,6 +658,7 @@ Vector3 Camera3D::get_doppler_tracked_velocity() const {
}
RID Camera3D::get_pyramid_shape_rid() {
+ ERR_FAIL_COND_V_MSG(!is_inside_tree(), RID(), "Camera is not inside scene.");
if (pyramid_shape == RID()) {
pyramid_shape_points = get_near_plane_points();
pyramid_shape = PhysicsServer3D::get_singleton()->convex_polygon_shape_create();
diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp
index 814ed5c2a7..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 {
@@ -648,7 +650,7 @@ void CollisionObject3D::shape_owner_clear_shapes(uint32_t p_owner) {
}
uint32_t CollisionObject3D::shape_find_owner(int p_shape_index) const {
- ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, 0);
+ ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, UINT32_MAX);
for (const KeyValue<uint32_t, ShapeData> &E : shapes) {
for (int i = 0; i < E.value.shapes.size(); i++) {
@@ -659,7 +661,7 @@ uint32_t CollisionObject3D::shape_find_owner(int p_shape_index) const {
}
//in theory it should be unreachable
- return 0;
+ ERR_FAIL_V_MSG(UINT32_MAX, "Can't find owner for shape index " + itos(p_shape_index) + ".");
}
CollisionObject3D::CollisionObject3D(RID p_rid, bool p_area) {
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 48ef41e015..d347d24c2c 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -73,6 +73,7 @@ void CPUParticles3D::set_amount(int p_amount) {
}
particle_data.resize((12 + 4 + 4) * p_amount);
+ RS::get_singleton()->multimesh_set_visible_instances(multimesh, -1);
RS::get_singleton()->multimesh_allocate_data(multimesh, p_amount, RS::MULTIMESH_TRANSFORM_3D, true, true);
particle_order.resize(p_amount);
@@ -212,8 +213,7 @@ TypedArray<String> CPUParticles3D::get_configuration_warnings() const {
warnings.push_back(TTR("Nothing is visible because no mesh has been assigned."));
}
- if (!anim_material_found && (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 ||
- get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) {
+ if (!anim_material_found && (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) {
warnings.push_back(TTR("CPUParticles3D animation requires the usage of a StandardMaterial3D whose Billboard Mode is set to \"Particle Billboard\"."));
}
@@ -531,6 +531,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) {
@@ -1326,7 +1328,7 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) {
set_color(material->get_color());
- Ref<GradientTexture> gt = material->get_color_ramp();
+ Ref<GradientTexture1D> gt = material->get_color_ramp();
if (gt.is_valid()) {
set_color_ramp(gt->get_gradient());
}
diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h
index 160814ead4..aca7328a27 100644
--- a/scene/3d/cpu_particles_3d.h
+++ b/scene/3d/cpu_particles_3d.h
@@ -209,7 +209,6 @@ public:
void set_explosiveness_ratio(real_t p_ratio);
void set_randomness_ratio(real_t p_ratio);
void set_lifetime_randomness(double p_random);
- void set_visibility_aabb(const AABB &p_aabb);
void set_use_local_coordinates(bool p_enable);
void set_speed_scale(double p_scale);
@@ -221,7 +220,6 @@ public:
real_t get_explosiveness_ratio() const;
real_t get_randomness_ratio() const;
double get_lifetime_randomness() const;
- AABB get_visibility_aabb() const;
bool get_use_local_coordinates() const;
double get_speed_scale() const;
@@ -234,9 +232,6 @@ public:
void set_draw_order(DrawOrder p_order);
DrawOrder get_draw_order() const;
- void set_draw_passes(int p_count);
- int get_draw_passes() const;
-
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
@@ -275,7 +270,6 @@ public:
void set_emission_points(const Vector<Vector3> &p_points);
void set_emission_normals(const Vector<Vector3> &p_normals);
void set_emission_colors(const Vector<Color> &p_colors);
- void set_emission_point_count(int p_count);
void set_emission_ring_axis(Vector3 p_axis);
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
@@ -291,7 +285,6 @@ public:
Vector<Vector3> get_emission_points() const;
Vector<Vector3> get_emission_normals() const;
Vector<Color> get_emission_colors() const;
- int get_emission_point_count() const;
Vector3 get_emission_ring_axis() const;
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index c94a99a203..c9f634a5e5 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -158,8 +158,9 @@ Vector<Face3> Decal::get_faces(uint32_t p_usage_flags) const {
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;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
+ VisualInstance3D::_validate_property(property);
}
TypedArray<String> Decal::get_configuration_warnings() const {
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
new file mode 100644
index 0000000000..694defd7dc
--- /dev/null
+++ b/scene/3d/fog_volume.cpp
@@ -0,0 +1,122 @@
+/*************************************************************************/
+/* fog_volume.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "fog_volume.h"
+
+///////////////////////////
+
+void FogVolume::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_extents", "extents"), &FogVolume::set_extents);
+ ClassDB::bind_method(D_METHOD("get_extents"), &FogVolume::get_extents);
+ ClassDB::bind_method(D_METHOD("set_shape", "shape"), &FogVolume::set_shape);
+ ClassDB::bind_method(D_METHOD("get_shape"), &FogVolume::get_shape);
+ ClassDB::bind_method(D_METHOD("set_material", "material"), &FogVolume::set_material);
+ ClassDB::bind_method(D_METHOD("get_material"), &FogVolume::get_material);
+
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_extents", "get_extents");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Ellipsoid,Box,World"), "set_shape", "get_shape");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "FogMaterial,ShaderMaterial"), "set_material", "get_material");
+}
+
+void FogVolume::_validate_property(PropertyInfo &property) const {
+ if (property.name == "extents" && shape == RS::FOG_VOLUME_SHAPE_WORLD) {
+ property.usage = PROPERTY_USAGE_NONE;
+ return;
+ }
+ VisualInstance3D::_validate_property(property);
+}
+
+void FogVolume::set_extents(const Vector3 &p_extents) {
+ extents = p_extents;
+ extents.x = MAX(0.0, extents.x);
+ extents.y = MAX(0.0, extents.y);
+ extents.z = MAX(0.0, extents.z);
+ RS::get_singleton()->fog_volume_set_extents(_get_volume(), extents);
+ update_gizmos();
+}
+
+Vector3 FogVolume::get_extents() const {
+ return extents;
+}
+
+void FogVolume::set_shape(RS::FogVolumeShape p_type) {
+ shape = p_type;
+ RS::get_singleton()->fog_volume_set_shape(_get_volume(), shape);
+ RS::get_singleton()->instance_set_ignore_culling(get_instance(), shape == RS::FOG_VOLUME_SHAPE_WORLD);
+ update_gizmos();
+ notify_property_list_changed();
+}
+
+RS::FogVolumeShape FogVolume::get_shape() const {
+ return shape;
+}
+
+void FogVolume::set_material(const Ref<Material> &p_material) {
+ material = p_material;
+ RID material_rid;
+ if (material.is_valid()) {
+ material_rid = material->get_rid();
+ }
+ RS::get_singleton()->fog_volume_set_material(_get_volume(), material_rid);
+ update_gizmos();
+}
+
+Ref<Material> FogVolume::get_material() const {
+ return material;
+}
+
+AABB FogVolume::get_aabb() const {
+ if (shape != RS::FOG_VOLUME_SHAPE_WORLD) {
+ return AABB(-extents, extents * 2);
+ }
+ return AABB();
+}
+
+TypedArray<String> FogVolume::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node::get_configuration_warnings();
+
+ Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment();
+
+ if (environment.is_valid() && !environment->is_volumetric_fog_enabled()) {
+ warnings.push_back(("Fog Volumes need volumetric fog to be enabled in the scene's Environment in order to be visible."));
+ }
+
+ return warnings;
+}
+
+FogVolume::FogVolume() {
+ volume = RS::get_singleton()->fog_volume_create();
+ RS::get_singleton()->fog_volume_set_shape(volume, RS::FOG_VOLUME_SHAPE_BOX);
+ set_base(volume);
+}
+
+FogVolume::~FogVolume() {
+ RS::get_singleton()->free(volume);
+}
diff --git a/scene/3d/fog_volume.h b/scene/3d/fog_volume.h
new file mode 100644
index 0000000000..0807fb22e6
--- /dev/null
+++ b/scene/3d/fog_volume.h
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* fog_volume.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef FOG_VOLUME_H
+#define FOG_VOLUME_H
+
+#include "core/templates/rid.h"
+#include "scene/3d/visual_instance_3d.h"
+#include "scene/main/node.h"
+#include "scene/main/viewport.h"
+#include "scene/resources/material.h"
+
+class FogVolume : public VisualInstance3D {
+ GDCLASS(FogVolume, VisualInstance3D);
+
+ Vector3 extents = Vector3(1, 1, 1);
+ Ref<Material> material;
+ RS::FogVolumeShape shape = RS::FOG_VOLUME_SHAPE_BOX;
+
+ RID volume;
+
+protected:
+ _FORCE_INLINE_ RID _get_volume() { return volume; }
+ static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
+
+public:
+ void set_extents(const Vector3 &p_extents);
+ Vector3 get_extents() const;
+
+ void set_shape(RS::FogVolumeShape p_type);
+ RS::FogVolumeShape get_shape() const;
+
+ void set_material(const Ref<Material> &p_material);
+ Ref<Material> get_material() const;
+
+ virtual AABB get_aabb() const override;
+ virtual Vector<Face3> get_faces(uint32_t p_usage_flags) const override { return Vector<Face3>(); }
+ TypedArray<String> get_configuration_warnings() const override;
+
+ FogVolume();
+ ~FogVolume();
+};
+
+#endif // FOG_VOLUME_H
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 32a62d8c7e..b35a45576f 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -277,7 +277,7 @@ TypedArray<String> GPUParticles3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (RenderingServer::get_singleton()->is_low_end()) {
- warnings.push_back(TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose."));
+ warnings.push_back(TTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose."));
}
bool meshes_found = false;
@@ -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) {
@@ -562,6 +564,7 @@ void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
+ ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D"), "set_sub_emitter", "get_sub_emitter");
ADD_GROUP("Time", "");
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index 4fa34615bf..2235de1599 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -256,10 +256,10 @@ void GPUParticlesCollisionSDF::_find_closest_distance(const Vector3 &p_pos, cons
Vector2 pq1 = v1 - e1 * CLAMP(v1.dot(e1) / e1.dot(e1), 0.0, 1.0);
Vector2 pq2 = v2 - e2 * CLAMP(v2.dot(e2) / e2.dot(e2), 0.0, 1.0);
- float s = SGN(e0.x * e2.y - e0.y * e2.x);
+ float s = SIGN(e0.x * e2.y - e0.y * e2.x);
Vector2 d2 = Vector2(pq0.dot(pq0), s * (v0.x * e0.y - v0.y * e0.x)).min(Vector2(pq1.dot(pq1), s * (v1.x * e1.y - v1.y * e1.x))).min(Vector2(pq2.dot(pq2), s * (v2.x * e2.y - v2.y * e2.x)));
- inside_d = -Math::sqrt(d2.x) * SGN(d2.y);
+ inside_d = -Math::sqrt(d2.x) * SIGN(d2.y);
}
//make sure distance to planes is not shorter if inside
@@ -288,15 +288,12 @@ void GPUParticlesCollisionSDF::_find_closest_distance(const Vector3 &p_pos, cons
Vector3 nor = ba.cross(ac);
inside_d = Math::sqrt(
- (SGN(ba.cross(nor).dot(pa)) +
- SGN(cb.cross(nor).dot(pb)) +
- SGN(ac.cross(nor).dot(pc)) <
- 2.0) ?
- MIN(MIN(
- Vector3_dot2(ba * CLAMP(ba.dot(pa) / Vector3_dot2(ba), 0.0, 1.0) - pa),
- Vector3_dot2(cb * CLAMP(cb.dot(pb) / Vector3_dot2(cb), 0.0, 1.0) - pb)),
- Vector3_dot2(ac * CLAMP(ac.dot(pc) / Vector3_dot2(ac), 0.0, 1.0) - pc)) :
- nor.dot(pa) * nor.dot(pa) / Vector3_dot2(nor));
+ (SIGN(ba.cross(nor).dot(pa)) + SIGN(cb.cross(nor).dot(pb)) + SIGN(ac.cross(nor).dot(pc)) < 2.0)
+ ? MIN(MIN(
+ Vector3_dot2(ba * CLAMP(ba.dot(pa) / Vector3_dot2(ba), 0.0, 1.0) - pa),
+ Vector3_dot2(cb * CLAMP(cb.dot(pb) / Vector3_dot2(cb), 0.0, 1.0) - pb)),
+ Vector3_dot2(ac * CLAMP(ac.dot(pc) / Vector3_dot2(ac), 0.0, 1.0) - pc))
+ : nor.dot(pa) * nor.dot(pa) / Vector3_dot2(nor));
closest_distance = MIN(closest_distance, inside_d);
}
@@ -475,7 +472,7 @@ Ref<Image> GPUParticlesCollisionSDF::bake() {
_create_bvh(bvh, face_pos.ptr(), face_pos.size(), faces.ptr(), th);
Vector<uint8_t> data;
- data.resize(sdf_size.z * sdf_size.y * sdf_size.x * sizeof(float));
+ data.resize(sdf_size.z * sdf_size.y * sdf_size.x * (int)sizeof(float));
if (bake_step_function) {
bake_step_function(0, "Baking SDF");
diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp
index c787ba5087..5d9ae019c2 100644
--- a/scene/3d/light_3d.cpp
+++ b/scene/3d/light_3d.cpp
@@ -197,24 +197,14 @@ bool Light3D::is_editor_only() const {
void Light3D::_validate_property(PropertyInfo &property) const {
if (!shadow && (property.name == "shadow_color" || property.name == "shadow_bias" || property.name == "shadow_normal_bias" || property.name == "shadow_reverse_cull_face" || property.name == "shadow_transmittance_bias" || property.name == "shadow_fog_fade" || property.name == "shadow_blur")) {
- 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;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
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_NO_EDITOR;
+ }
+
+ 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_NO_EDITOR;
+ }
+
+ 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..1b5d4ad243 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -200,9 +200,9 @@ void LightmapGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered"), "set_light_texture", "get_light_texture");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
}
LightmapGIData::LightmapGIData() {
@@ -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/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 67f4a88228..ae686143e4 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -42,10 +42,9 @@ bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
- Map<StringName, BlendShapeTrack>::Element *E = blend_shape_tracks.find(p_name);
+ Map<StringName, int>::Element *E = blend_shape_properties.find(p_name);
if (E) {
- E->get().value = p_value;
- RenderingServer::get_singleton()->instance_set_blend_shape_weight(get_instance(), E->get().idx, E->get().value);
+ set_blend_shape_value(E->get(), p_value);
return true;
}
@@ -67,9 +66,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
return false;
}
- const Map<StringName, BlendShapeTrack>::Element *E = blend_shape_tracks.find(p_name);
+ const Map<StringName, int>::Element *E = blend_shape_properties.find(p_name);
if (E) {
- r_ret = E->get().value;
+ r_ret = get_blend_shape_value(E->get());
return true;
}
@@ -86,7 +85,7 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const {
List<String> ls;
- for (const KeyValue<StringName, BlendShapeTrack> &E : blend_shape_tracks) {
+ for (const KeyValue<StringName, int> &E : blend_shape_properties) {
ls.push_back(E.key);
}
@@ -114,25 +113,17 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) {
mesh = p_mesh;
- blend_shape_tracks.clear();
if (mesh.is_valid()) {
- for (int i = 0; i < mesh->get_blend_shape_count(); i++) {
- BlendShapeTrack mt;
- mt.idx = i;
- mt.value = 0;
- blend_shape_tracks["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = mt;
- }
-
mesh->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &MeshInstance3D::_mesh_changed));
- surface_override_materials.resize(mesh->get_surface_count());
-
+ _mesh_changed();
set_base(mesh->get_rid());
} else {
+ blend_shape_tracks.clear();
+ blend_shape_properties.clear();
set_base(RID());
+ update_gizmos();
}
- update_gizmos();
-
notify_property_list_changed();
}
@@ -140,17 +131,48 @@ Ref<Mesh> MeshInstance3D::get_mesh() const {
return mesh;
}
+int MeshInstance3D::get_blend_shape_count() const {
+ if (mesh.is_null()) {
+ return 0;
+ }
+ return mesh->get_blend_shape_count();
+}
+int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) {
+ if (mesh.is_null()) {
+ return -1;
+ }
+ for (int i = 0; i < mesh->get_blend_shape_count(); i++) {
+ if (mesh->get_blend_shape_name(i) == p_name) {
+ return i;
+ }
+ }
+ return -1;
+}
+float MeshInstance3D::get_blend_shape_value(int p_blend_shape) const {
+ ERR_FAIL_COND_V(mesh.is_null(), 0.0);
+ ERR_FAIL_INDEX_V(p_blend_shape, (int)blend_shape_tracks.size(), 0);
+ return blend_shape_tracks[p_blend_shape];
+}
+void MeshInstance3D::set_blend_shape_value(int p_blend_shape, float p_value) {
+ ERR_FAIL_COND(mesh.is_null());
+ ERR_FAIL_INDEX(p_blend_shape, (int)blend_shape_tracks.size());
+ blend_shape_tracks[p_blend_shape] = p_value;
+ RenderingServer::get_singleton()->instance_set_blend_shape_weight(get_instance(), p_blend_shape, p_value);
+}
+
void MeshInstance3D::_resolve_skeleton_path() {
Ref<SkinReference> new_skin_reference;
if (!skeleton_path.is_empty()) {
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(get_node(skeleton_path));
if (skeleton) {
- new_skin_reference = skeleton->register_skin(skin_internal);
if (skin_internal.is_null()) {
+ new_skin_reference = skeleton->register_skin(skeleton->create_skin_from_rest_transforms());
//a skin was created for us
skin_internal = new_skin_reference->get_skin();
notify_property_list_changed();
+ } else {
+ new_skin_reference = skeleton->register_skin(skin_internal);
}
}
}
@@ -222,7 +244,7 @@ Node *MeshInstance3D::create_trimesh_collision_node() {
StaticBody3D *static_body = memnew(StaticBody3D);
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
- static_body->add_child(cshape);
+ static_body->add_child(cshape, true);
return static_body;
}
@@ -231,7 +253,7 @@ void MeshInstance3D::create_trimesh_collision() {
ERR_FAIL_COND(!static_body);
static_body->set_name(String(get_name()) + "_col");
- add_child(static_body);
+ add_child(static_body, true);
if (get_owner()) {
CollisionShape3D *cshape = Object::cast_to<CollisionShape3D>(static_body->get_child(0));
static_body->set_owner(get_owner());
@@ -252,7 +274,7 @@ Node *MeshInstance3D::create_convex_collision_node(bool p_clean, bool p_simplify
StaticBody3D *static_body = memnew(StaticBody3D);
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
- static_body->add_child(cshape);
+ static_body->add_child(cshape, true);
return static_body;
}
@@ -261,7 +283,7 @@ void MeshInstance3D::create_convex_collision(bool p_clean, bool p_simplify) {
ERR_FAIL_COND(!static_body);
static_body->set_name(String(get_name()) + "_col");
- add_child(static_body);
+ add_child(static_body, true);
if (get_owner()) {
CollisionShape3D *cshape = Object::cast_to<CollisionShape3D>(static_body->get_child(0));
static_body->set_owner(get_owner());
@@ -284,7 +306,7 @@ Node *MeshInstance3D::create_multiple_convex_collisions_node() {
for (int i = 0; i < shapes.size(); i++) {
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shapes[i]);
- static_body->add_child(cshape);
+ static_body->add_child(cshape, true);
}
return static_body;
}
@@ -294,7 +316,7 @@ void MeshInstance3D::create_multiple_convex_collisions() {
ERR_FAIL_COND(!static_body);
static_body->set_name(String(get_name()) + "_col");
- add_child(static_body);
+ add_child(static_body, true);
if (get_owner()) {
static_body->set_owner(get_owner());
int count = static_body->get_child_count();
@@ -355,6 +377,19 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const {
void MeshInstance3D::_mesh_changed() {
ERR_FAIL_COND(mesh.is_null());
surface_override_materials.resize(mesh->get_surface_count());
+
+ uint32_t initialize_bs_from = blend_shape_tracks.size();
+ blend_shape_tracks.resize(mesh->get_blend_shape_count());
+
+ for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) {
+ blend_shape_properties["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = i;
+ if (i < initialize_bs_from) {
+ set_blend_shape_value(i, blend_shape_tracks[i]);
+ } else {
+ set_blend_shape_value(i, 0);
+ }
+ }
+
update_gizmos();
}
@@ -425,7 +460,7 @@ void MeshInstance3D::create_debug_tangents() {
MeshInstance3D *mi = memnew(MeshInstance3D);
mi->set_mesh(am);
mi->set_name("DebugTangents");
- add_child(mi);
+ add_child(mi, true);
#ifdef TOOLS_ENABLED
if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) {
@@ -457,6 +492,11 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions"), &MeshInstance3D::create_multiple_convex_collisions);
ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT);
+ ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &MeshInstance3D::get_blend_shape_count);
+ ClassDB::bind_method(D_METHOD("find_blend_shape_by_name", "name"), &MeshInstance3D::find_blend_shape_by_name);
+ ClassDB::bind_method(D_METHOD("get_blend_shape_value", "blend_shape_idx"), &MeshInstance3D::get_blend_shape_value);
+ ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
+
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
ClassDB::set_method_flags("MeshInstance3D", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index beb7f6cf95..8f21726601 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -31,8 +31,8 @@
#ifndef MESH_INSTANCE_H
#define MESH_INSTANCE_H
+#include "core/templates/local_vector.h"
#include "scene/3d/visual_instance_3d.h"
-
class Skin;
class SkinReference;
@@ -46,12 +46,8 @@ protected:
Ref<SkinReference> skin_ref;
NodePath skeleton_path = NodePath("..");
- struct BlendShapeTrack {
- int idx = 0;
- float value = 0.0;
- };
-
- Map<StringName, BlendShapeTrack> blend_shape_tracks;
+ LocalVector<float> blend_shape_tracks;
+ Map<StringName, int> blend_shape_properties;
Vector<Ref<Material>> surface_override_materials;
void _mesh_changed();
@@ -75,6 +71,11 @@ public:
void set_skeleton_path(const NodePath &p_skeleton);
NodePath get_skeleton_path();
+ int get_blend_shape_count() const;
+ int find_blend_shape_by_name(const StringName &p_name);
+ float get_blend_shape_value(int p_blend_shape) const;
+ void set_blend_shape_value(int p_blend_shape, float p_value);
+
int get_surface_override_material_count() const;
void set_surface_override_material(int p_surface, const Ref<Material> &p_material);
Ref<Material> get_surface_override_material(int p_surface) const;
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index c2d5c757db..1bc7d20c19 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -192,7 +192,7 @@ Vector3 NavigationAgent3D::get_target_location() const {
Vector3 NavigationAgent3D::get_next_location() {
update_navigation();
if (navigation_path.size() == 0) {
- ERR_FAIL_COND_V(agent_parent == nullptr, Vector3());
+ ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector3(), "The agent has no parent.");
return agent_parent->get_global_transform().origin;
} else {
return navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0);
@@ -200,7 +200,7 @@ Vector3 NavigationAgent3D::get_next_location() {
}
real_t NavigationAgent3D::distance_to_target() const {
- ERR_FAIL_COND_V(agent_parent == nullptr, 0.0);
+ ERR_FAIL_COND_V_MSG(agent_parent == nullptr, 0.0, "The agent has no parent.");
return agent_parent->get_global_transform().origin.distance_to(target_location);
}
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 20ffc3b00e..f9fff802e0 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -35,19 +35,41 @@
#include "servers/navigation_server_3d.h"
void NavigationObstacle3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle3D::set_estimate_radius);
+ ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle3D::is_radius_estimated);
+ ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle3D::set_radius);
+ ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle3D::get_radius);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "estimate_radius"), "set_estimate_radius", "is_radius_estimated");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_radius", "get_radius");
+}
+
+void NavigationObstacle3D::_validate_property(PropertyInfo &p_property) const {
+ if (p_property.name == "radius") {
+ if (estimate_radius) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ }
}
void NavigationObstacle3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
+ initialize_agent();
+ parent_node3d = Object::cast_to<Node3D>(get_parent());
+ if (parent_node3d != nullptr) {
+ // place agent on navigation map first or else the RVO agent callback creation fails silently later
+ NavigationServer3D::get_singleton()->agent_set_map(get_rid(), parent_node3d->get_world_3d()->get_navigation_map());
+ }
set_physics_process_internal(true);
} break;
case NOTIFICATION_EXIT_TREE: {
+ parent_node3d = nullptr;
set_physics_process_internal(false);
} break;
case NOTIFICATION_PARENTED: {
parent_node3d = Object::cast_to<Node3D>(get_parent());
- update_agent_shape();
+ reevaluate_agent_radius();
} break;
case NOTIFICATION_UNPARENTED: {
parent_node3d = nullptr;
@@ -86,7 +108,22 @@ TypedArray<String> NavigationObstacle3D::get_configuration_warnings() const {
return warnings;
}
-void NavigationObstacle3D::update_agent_shape() {
+void NavigationObstacle3D::initialize_agent() {
+ NavigationServer3D::get_singleton()->agent_set_neighbor_dist(agent, 0.0);
+ NavigationServer3D::get_singleton()->agent_set_max_neighbors(agent, 0);
+ NavigationServer3D::get_singleton()->agent_set_time_horizon(agent, 0.0);
+ NavigationServer3D::get_singleton()->agent_set_max_speed(agent, 0.0);
+}
+
+void NavigationObstacle3D::reevaluate_agent_radius() {
+ if (!estimate_radius) {
+ NavigationServer3D::get_singleton()->agent_set_radius(agent, radius);
+ } else if (parent_node3d) {
+ NavigationServer3D::get_singleton()->agent_set_radius(agent, estimate_agent_radius());
+ }
+}
+
+real_t NavigationObstacle3D::estimate_agent_radius() const {
if (parent_node3d) {
// Estimate the radius of this physics body
real_t radius = 0.0;
@@ -110,15 +147,21 @@ void NavigationObstacle3D::update_agent_shape() {
Vector3 s = parent_node3d->get_global_transform().basis.get_scale();
radius *= MAX(s.x, MAX(s.y, s.z));
- if (radius == 0.0) {
- radius = 1.0; // Never a 0 radius
+ if (radius > 0.0) {
+ return radius;
}
-
- // Initialize the Agent as an object
- NavigationServer3D::get_singleton()->agent_set_neighbor_dist(agent, 0.0);
- NavigationServer3D::get_singleton()->agent_set_max_neighbors(agent, 0);
- NavigationServer3D::get_singleton()->agent_set_time_horizon(agent, 0.0);
- NavigationServer3D::get_singleton()->agent_set_radius(agent, radius);
- NavigationServer3D::get_singleton()->agent_set_max_speed(agent, 0.0);
}
+ return 1.0; // Never a 0 radius
+}
+
+void NavigationObstacle3D::set_estimate_radius(bool p_estimate_radius) {
+ estimate_radius = p_estimate_radius;
+ notify_property_list_changed();
+ reevaluate_agent_radius();
+}
+
+void NavigationObstacle3D::set_radius(real_t p_radius) {
+ ERR_FAIL_COND_MSG(p_radius <= 0.0, "Radius must be greater than 0.");
+ radius = p_radius;
+ reevaluate_agent_radius();
}
diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h
index ab0b158303..12c813ab08 100644
--- a/scene/3d/navigation_obstacle_3d.h
+++ b/scene/3d/navigation_obstacle_3d.h
@@ -39,8 +39,12 @@ class NavigationObstacle3D : public Node {
Node3D *parent_node3d = nullptr;
RID agent;
+ bool estimate_radius = true;
+ real_t radius = 1.0;
+
protected:
static void _bind_methods();
+ void _validate_property(PropertyInfo &p_property) const override;
void _notification(int p_what);
public:
@@ -51,10 +55,21 @@ public:
return agent;
}
+ void set_estimate_radius(bool p_estimate_radius);
+ bool is_radius_estimated() const {
+ return estimate_radius;
+ }
+ void set_radius(real_t p_radius);
+ real_t get_radius() const {
+ return radius;
+ }
+
TypedArray<String> get_configuration_warnings() const override;
private:
- void update_agent_shape();
+ void initialize_agent();
+ void reevaluate_agent_radius();
+ real_t estimate_agent_radius() const;
};
#endif
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index 8a51a259f7..473368cf69 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -162,7 +162,7 @@ void _bake_navigation_mesh(void *p_user_data) {
}
void NavigationRegion3D::bake_navigation_mesh() {
- ERR_FAIL_COND(bake_thread.is_started());
+ ERR_FAIL_COND_MSG(bake_thread.is_started(), "Unable to start another bake request. The navigation mesh bake thread is already baking a navigation mesh.");
BakeThreadsArgs *args = memnew(BakeThreadsArgs);
args->nav_region = this;
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 272a06bce4..b7b88c7135 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -44,7 +44,7 @@
definition of invalidation: global is invalid
1) If a node sets a LOCAL, it produces an invalidation of everything above
- a) If above is invalid, don't keep invalidating upwards
+ . a) If above is invalid, don't keep invalidating upwards
2) If a node sets a GLOBAL, it is converted to LOCAL (and forces validation of everything pending below)
drawback: setting/reading globals is useful and used very often, and using affine inverses is slow
@@ -56,7 +56,7 @@
definition of invalidation: NONE dirty, LOCAL dirty, GLOBAL dirty
1) If a node sets a LOCAL, it must climb the tree and set it as GLOBAL dirty
- a) marking GLOBALs as dirty up all the tree must be done always
+ . a) marking GLOBALs as dirty up all the tree must be done always
2) If a node sets a GLOBAL, it marks local as dirty, and that's all?
//is clearing the dirty state correct in this case?
@@ -94,11 +94,6 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
return;
}
- /*
- if (data.dirty&DIRTY_GLOBAL)
- return; //already dirty
- */
-
data.children_lock++;
for (Node3D *&E : data.children) {
@@ -220,6 +215,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,11 +231,17 @@ 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) ?
- data.parent->get_global_transform().affine_inverse() * p_transform :
- p_transform;
+ Transform3D xform = (data.parent && !data.top_level_active)
+ ? data.parent->get_global_transform().affine_inverse() * p_transform
+ : p_transform;
set_transform(xform);
}
@@ -308,6 +316,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 +371,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 +390,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 +401,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;
}
@@ -371,6 +418,9 @@ void Node3D::update_gizmos() {
if (data.gizmos.is_empty()) {
return;
}
+ if (data.gizmos_dirty) {
+ return;
+ }
data.gizmos_dirty = true;
MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_update_gizmos));
#endif
@@ -467,6 +517,7 @@ Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const {
void Node3D::_update_gizmos() {
#ifdef TOOLS_ENABLED
if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) {
+ data.gizmos_dirty = false;
return;
}
data.gizmos_dirty = false;
@@ -480,14 +531,14 @@ void Node3D::_update_gizmos() {
#endif
}
-#ifdef TOOLS_ENABLED
void Node3D::set_disable_gizmos(bool p_enabled) {
+#ifdef TOOLS_ENABLED
data.gizmos_disabled = p_enabled;
if (!p_enabled) {
clear_gizmos();
}
-}
#endif
+}
void Node3D::set_disable_scale(bool p_enabled) {
data.disable_scale = p_enabled;
@@ -776,6 +827,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);
@@ -783,8 +852,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);
@@ -844,15 +921,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_NO_EDITOR), "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/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index f3e174c01b..6c548edc74 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -130,8 +130,8 @@ void Occluder3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &Occluder3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &Occluder3D::get_indices);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_vertices", "get_vertices");
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_indices", "get_indices");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_vertices", "get_vertices");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_indices", "get_indices");
}
Occluder3D::Occluder3D() {
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 976bff4fbc..5cb7f431e3 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -174,9 +174,12 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear
ERR_FAIL_COND_V(!is_inside_tree(), false);
PhysicsServer3D::MotionResult *r = nullptr;
+ PhysicsServer3D::MotionResult temp_result;
if (r_collision.is_valid()) {
// Needs const_cast because method bindings don't support non-const Ref.
r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result);
+ } else {
+ r = &temp_result;
}
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
@@ -184,7 +187,14 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_linear
PhysicsServer3D::MotionParameters parameters(p_from, p_linear_velocity * delta, p_margin);
- return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r);
+ bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r);
+
+ if (colliding) {
+ // Don't report collision when the whole motion is done.
+ return (r->collision_safe_fraction < 1.0);
+ } else {
+ return false;
+ }
}
void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) {
@@ -758,8 +768,26 @@ real_t RigidDynamicBody3D::get_gravity_scale() const {
return gravity_scale;
}
+void RigidDynamicBody3D::set_linear_damp_mode(DampMode p_mode) {
+ linear_damp_mode = p_mode;
+ PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_LINEAR_DAMP_MODE, linear_damp_mode);
+}
+
+RigidDynamicBody3D::DampMode RigidDynamicBody3D::get_linear_damp_mode() const {
+ return linear_damp_mode;
+}
+
+void RigidDynamicBody3D::set_angular_damp_mode(DampMode p_mode) {
+ angular_damp_mode = p_mode;
+ PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP_MODE, angular_damp_mode);
+}
+
+RigidDynamicBody3D::DampMode RigidDynamicBody3D::get_angular_damp_mode() const {
+ return angular_damp_mode;
+}
+
void RigidDynamicBody3D::set_linear_damp(real_t p_linear_damp) {
- ERR_FAIL_COND(p_linear_damp < -1);
+ ERR_FAIL_COND(p_linear_damp < 0.0);
linear_damp = p_linear_damp;
PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_LINEAR_DAMP, linear_damp);
}
@@ -769,7 +797,7 @@ real_t RigidDynamicBody3D::get_linear_damp() const {
}
void RigidDynamicBody3D::set_angular_damp(real_t p_angular_damp) {
- ERR_FAIL_COND(p_angular_damp < -1);
+ ERR_FAIL_COND(p_angular_damp < 0.0);
angular_damp = p_angular_damp;
PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP, angular_damp);
}
@@ -970,6 +998,12 @@ void RigidDynamicBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &RigidDynamicBody3D::set_gravity_scale);
ClassDB::bind_method(D_METHOD("get_gravity_scale"), &RigidDynamicBody3D::get_gravity_scale);
+ ClassDB::bind_method(D_METHOD("set_linear_damp_mode", "linear_damp_mode"), &RigidDynamicBody3D::set_linear_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_linear_damp_mode"), &RigidDynamicBody3D::get_linear_damp_mode);
+
+ ClassDB::bind_method(D_METHOD("set_angular_damp_mode", "angular_damp_mode"), &RigidDynamicBody3D::set_angular_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_angular_damp_mode"), &RigidDynamicBody3D::get_angular_damp_mode);
+
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &RigidDynamicBody3D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &RigidDynamicBody3D::get_linear_damp);
@@ -1035,13 +1069,15 @@ void RigidDynamicBody3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "freeze_mode", PROPERTY_HINT_ENUM, "Static,Kinematic"), "set_freeze_mode", "get_freeze_mode");
ADD_GROUP("Linear", "linear_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
ADD_GROUP("Angular", "angular_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
- ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
- ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape")));
+ ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
+ ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape_index"), PropertyInfo(Variant::INT, "local_shape_index")));
ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("sleeping_state_changed"));
@@ -1051,14 +1087,18 @@ void RigidDynamicBody3D::_bind_methods() {
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO);
BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM);
+
+ BIND_ENUM_CONSTANT(DAMP_MODE_COMBINE);
+ BIND_ENUM_CONSTANT(DAMP_MODE_REPLACE);
}
void RigidDynamicBody3D::_validate_property(PropertyInfo &property) const {
if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) {
if (property.name == "center_of_mass") {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
RigidDynamicBody3D::RigidDynamicBody3D() :
@@ -1115,6 +1155,10 @@ bool CharacterBody3D::move_and_slide() {
if (bs) {
Vector3 local_position = gt.origin - bs->get_transform().origin;
current_platform_velocity = bs->get_velocity_at_local_position(local_position);
+ } else {
+ // Body is removed or destroyed, invalidate floor.
+ current_platform_velocity = Vector3();
+ platform_rid = RID();
}
} else {
current_platform_velocity = Vector3();
@@ -1903,8 +1947,8 @@ void CharacterBody3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_ceiling"), "set_slide_on_ceiling_enabled", "is_slide_on_ceiling_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_motion_velocity", "get_motion_velocity");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "motion_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_motion_velocity", "get_motion_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_slides", "get_max_slides");
ADD_GROUP("Free Mode", "free_mode_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wall_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_wall_min_slide_angle", "get_wall_min_slide_angle");
ADD_GROUP("Floor", "floor_");
@@ -1930,9 +1974,10 @@ void CharacterBody3D::_bind_methods() {
void CharacterBody3D::_validate_property(PropertyInfo &property) const {
if (motion_mode == MOTION_MODE_FREE) {
if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
+ PhysicsBody3D::_validate_property(property);
}
CharacterBody3D::CharacterBody3D() :
@@ -2823,6 +2868,12 @@ void PhysicalBone3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &PhysicalBone3D::set_gravity_scale);
ClassDB::bind_method(D_METHOD("get_gravity_scale"), &PhysicalBone3D::get_gravity_scale);
+ ClassDB::bind_method(D_METHOD("set_linear_damp_mode", "linear_damp_mode"), &PhysicalBone3D::set_linear_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_linear_damp_mode"), &PhysicalBone3D::get_linear_damp_mode);
+
+ ClassDB::bind_method(D_METHOD("set_angular_damp_mode", "angular_damp_mode"), &PhysicalBone3D::set_angular_damp_mode);
+ ClassDB::bind_method(D_METHOD("get_angular_damp_mode"), &PhysicalBone3D::get_angular_damp_mode);
+
ClassDB::bind_method(D_METHOD("set_linear_damp", "linear_damp"), &PhysicalBone3D::set_linear_damp);
ClassDB::bind_method(D_METHOD("get_linear_damp"), &PhysicalBone3D::get_linear_damp);
@@ -2843,10 +2894,15 @@ void PhysicalBone3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-10,10,0.01"), "set_gravity_scale", "get_gravity_scale");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "linear_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_linear_damp_mode", "get_linear_damp_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "angular_damp_mode", PROPERTY_HINT_ENUM, "Combine,Replace"), "set_angular_damp_mode", "get_angular_damp_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep");
+ BIND_ENUM_CONSTANT(DAMP_MODE_COMBINE);
+ BIND_ENUM_CONSTANT(DAMP_MODE_REPLACE);
+
BIND_ENUM_CONSTANT(JOINT_TYPE_NONE);
BIND_ENUM_CONSTANT(JOINT_TYPE_PIN);
BIND_ENUM_CONSTANT(JOINT_TYPE_CONE);
@@ -3060,7 +3116,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 {
@@ -3144,8 +3200,27 @@ real_t PhysicalBone3D::get_gravity_scale() const {
return gravity_scale;
}
+void PhysicalBone3D::set_linear_damp_mode(DampMode p_mode) {
+ linear_damp_mode = p_mode;
+ PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_LINEAR_DAMP_MODE, linear_damp_mode);
+}
+
+PhysicalBone3D::DampMode PhysicalBone3D::get_linear_damp_mode() const {
+ return linear_damp_mode;
+}
+
+void PhysicalBone3D::set_angular_damp_mode(DampMode p_mode) {
+ angular_damp_mode = p_mode;
+ PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP_MODE, angular_damp_mode);
+}
+
+PhysicalBone3D::DampMode PhysicalBone3D::get_angular_damp_mode() const {
+ return angular_damp_mode;
+}
+
void PhysicalBone3D::set_linear_damp(real_t p_linear_damp) {
- ERR_FAIL_COND(p_linear_damp < -1);
+ ERR_FAIL_COND(p_linear_damp < 0);
+
linear_damp = p_linear_damp;
PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_LINEAR_DAMP, linear_damp);
}
@@ -3155,7 +3230,8 @@ real_t PhysicalBone3D::get_linear_damp() const {
}
void PhysicalBone3D::set_angular_damp(real_t p_angular_damp) {
- ERR_FAIL_COND(p_angular_damp < -1);
+ ERR_FAIL_COND(p_angular_damp < 0);
+
angular_damp = p_angular_damp;
PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_ANGULAR_DAMP, angular_damp);
}
diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h
index 5677df730c..2ea796d335 100644
--- a/scene/3d/physics_body_3d.h
+++ b/scene/3d/physics_body_3d.h
@@ -143,6 +143,11 @@ public:
CENTER_OF_MASS_MODE_CUSTOM,
};
+ enum DampMode {
+ DAMP_MODE_COMBINE,
+ DAMP_MODE_REPLACE,
+ };
+
private:
bool can_sleep = true;
bool lock_rotation = false;
@@ -160,8 +165,12 @@ private:
Vector3 angular_velocity;
Basis inverse_inertia_tensor;
real_t gravity_scale = 1.0;
- real_t linear_damp = -1.0;
- real_t angular_damp = -1.0;
+
+ DampMode linear_damp_mode = DAMP_MODE_COMBINE;
+ DampMode angular_damp_mode = DAMP_MODE_COMBINE;
+
+ real_t linear_damp = 0.0;
+ real_t angular_damp = 0.0;
bool sleeping = false;
bool ccd = false;
@@ -265,6 +274,12 @@ public:
void set_gravity_scale(real_t p_gravity_scale);
real_t get_gravity_scale() const;
+ void set_linear_damp_mode(DampMode p_mode);
+ DampMode get_linear_damp_mode() const;
+
+ void set_angular_damp_mode(DampMode p_mode);
+ DampMode get_angular_damp_mode() const;
+
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
@@ -310,6 +325,7 @@ private:
VARIANT_ENUM_CAST(RigidDynamicBody3D::FreezeMode);
VARIANT_ENUM_CAST(RigidDynamicBody3D::CenterOfMassMode);
+VARIANT_ENUM_CAST(RigidDynamicBody3D::DampMode);
class KinematicCollision3D;
@@ -494,6 +510,11 @@ class PhysicalBone3D : public PhysicsBody3D {
GDCLASS(PhysicalBone3D, PhysicsBody3D);
public:
+ enum DampMode {
+ DAMP_MODE_COMBINE,
+ DAMP_MODE_REPLACE,
+ };
+
enum JointType {
JOINT_TYPE_NONE,
JOINT_TYPE_PIN,
@@ -632,10 +653,14 @@ private:
real_t mass = 1.0;
real_t friction = 1.0;
real_t gravity_scale = 1.0;
- real_t linear_damp = -1.0;
- real_t angular_damp = -1.0;
bool can_sleep = true;
+ DampMode linear_damp_mode = DAMP_MODE_COMBINE;
+ DampMode angular_damp_mode = DAMP_MODE_COMBINE;
+
+ real_t linear_damp = 0.0;
+ real_t angular_damp = 0.0;
+
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -698,6 +723,12 @@ public:
void set_gravity_scale(real_t p_gravity_scale);
real_t get_gravity_scale() const;
+ void set_linear_damp_mode(DampMode p_mode);
+ DampMode get_linear_damp_mode() const;
+
+ void set_angular_damp_mode(DampMode p_mode);
+ DampMode get_angular_damp_mode() const;
+
void set_linear_damp(real_t p_linear_damp);
real_t get_linear_damp() const;
@@ -725,5 +756,6 @@ private:
};
VARIANT_ENUM_CAST(PhysicalBone3D::JointType);
+VARIANT_ENUM_CAST(PhysicalBone3D::DampMode);
#endif // PHYSICS_BODY__H
diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp
index fd4c6e7416..bfa397a1f5 100644
--- a/scene/3d/ray_cast_3d.cpp
+++ b/scene/3d/ray_cast_3d.cpp
@@ -212,9 +212,17 @@ void RayCast3D::_update_raycast_state() {
to = Vector3(0, 0.01, 0);
}
- PhysicsDirectSpaceState3D::RayResult rr;
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = gt.get_origin();
+ ray_params.to = gt.xform(to);
+ ray_params.exclude = exclude;
+ ray_params.collision_mask = collision_mask;
+ ray_params.collide_with_bodies = collide_with_bodies;
+ ray_params.collide_with_areas = collide_with_areas;
+ ray_params.hit_from_inside = hit_from_inside;
- if (dss->intersect_ray(gt.get_origin(), gt.xform(to), rr, exclude, collision_mask, collide_with_bodies, collide_with_areas)) {
+ PhysicsDirectSpaceState3D::RayResult rr;
+ if (dss->intersect_ray(ray_params, rr)) {
collided = true;
against = rr.collider_id;
collision_point = rr.position;
@@ -261,22 +269,30 @@ void RayCast3D::clear_exceptions() {
exclude.clear();
}
-void RayCast3D::set_collide_with_areas(bool p_clip) {
- collide_with_areas = p_clip;
+void RayCast3D::set_collide_with_areas(bool p_enabled) {
+ collide_with_areas = p_enabled;
}
bool RayCast3D::is_collide_with_areas_enabled() const {
return collide_with_areas;
}
-void RayCast3D::set_collide_with_bodies(bool p_clip) {
- collide_with_bodies = p_clip;
+void RayCast3D::set_collide_with_bodies(bool p_enabled) {
+ collide_with_bodies = p_enabled;
}
bool RayCast3D::is_collide_with_bodies_enabled() const {
return collide_with_bodies;
}
+void RayCast3D::set_hit_from_inside(bool p_enabled) {
+ hit_from_inside = p_enabled;
+}
+
+bool RayCast3D::is_hit_from_inside_enabled() const {
+ return hit_from_inside;
+}
+
void RayCast3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast3D::set_enabled);
ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast3D::is_enabled);
@@ -315,6 +331,9 @@ void RayCast3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &RayCast3D::set_collide_with_bodies);
ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &RayCast3D::is_collide_with_bodies_enabled);
+ ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast3D::set_hit_from_inside);
+ ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast3D::is_hit_from_inside_enabled);
+
ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &RayCast3D::set_debug_shape_custom_color);
ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &RayCast3D::get_debug_shape_custom_color);
@@ -325,6 +344,7 @@ void RayCast3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position"), "set_target_position", "get_target_position");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled");
ADD_GROUP("Collide With", "collide_with");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h
index 3828bfb4c4..5c2a61c35b 100644
--- a/scene/3d/ray_cast_3d.h
+++ b/scene/3d/ray_cast_3d.h
@@ -65,18 +65,23 @@ class RayCast3D : public Node3D {
bool collide_with_areas = false;
bool collide_with_bodies = true;
+ bool hit_from_inside = false;
+
protected:
void _notification(int p_what);
void _update_raycast_state();
static void _bind_methods();
public:
- void set_collide_with_areas(bool p_clip);
+ void set_collide_with_areas(bool p_enabled);
bool is_collide_with_areas_enabled() const;
- void set_collide_with_bodies(bool p_clip);
+ void set_collide_with_bodies(bool p_enabled);
bool is_collide_with_bodies_enabled() const;
+ void set_hit_from_inside(bool p_enabled);
+ bool is_hit_from_inside_enabled() const;
+
void set_enabled(bool p_enabled);
bool is_enabled() const;
diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp
index 719dbedd94..f7f19596a7 100644
--- a/scene/3d/reflection_probe.cpp
+++ b/scene/3d/reflection_probe.cpp
@@ -94,7 +94,7 @@ void ReflectionProbe::set_extents(const Vector3 &p_extents) {
}
if (extents[i] - 0.01 < ABS(origin_offset[i])) {
- origin_offset[i] = SGN(origin_offset[i]) * (extents[i] - 0.01);
+ origin_offset[i] = SIGN(origin_offset[i]) * (extents[i] - 0.01);
}
}
@@ -113,7 +113,7 @@ void ReflectionProbe::set_origin_offset(const Vector3 &p_extents) {
for (int i = 0; i < 3; i++) {
if (extents[i] - 0.01 < ABS(origin_offset[i])) {
- origin_offset[i] = SGN(origin_offset[i]) * (extents[i] - 0.01);
+ origin_offset[i] = SIGN(origin_offset[i]) * (extents[i] - 0.01);
}
}
RS::get_singleton()->reflection_probe_set_extents(probe, extents);
@@ -185,9 +185,10 @@ Vector<Face3> ReflectionProbe::get_faces(uint32_t p_usage_flags) const {
void ReflectionProbe::_validate_property(PropertyInfo &property) const {
if (property.name == "interior/ambient_color" || property.name == "interior/ambient_color_energy") {
if (ambient_mode != AMBIENT_COLOR) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | 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_3d.cpp b/scene/3d/skeleton_3d.cpp
index 2b52f034b2..d4d3c0ebe1 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -99,8 +99,12 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
set_bone_rest(which, p_value);
} else if (what == "enabled") {
set_bone_enabled(which, p_value);
- } else if (what == "pose") {
- set_bone_pose(which, p_value);
+ } else if (what == "position") {
+ set_bone_pose_position(which, p_value);
+ } else if (what == "rotation") {
+ set_bone_pose_rotation(which, p_value);
+ } else if (what == "scale") {
+ set_bone_pose_scale(which, p_value);
} else {
return false;
}
@@ -135,8 +139,12 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
r_ret = get_bone_rest(which);
} else if (what == "enabled") {
r_ret = is_bone_enabled(which);
- } else if (what == "pose") {
- r_ret = get_bone_pose(which);
+ } else if (what == "position") {
+ r_ret = get_bone_pose_position(which);
+ } else if (what == "rotation") {
+ r_ret = get_bone_pose_rotation(which);
+ } else if (what == "scale") {
+ r_ret = get_bone_pose_scale(which);
} else {
return false;
}
@@ -147,11 +155,13 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < bones.size(); i++) {
String prep = "bones/" + itos(i) + "/";
- p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
#ifndef _3D_DISABLED
@@ -161,6 +171,45 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
"SkeletonModificationStack3D",
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
#endif //_3D_DISABLED
+
+ for (PropertyInfo &E : *p_list) {
+ _validate_property(E);
+ }
+}
+
+void Skeleton3D::_validate_property(PropertyInfo &property) const {
+ PackedStringArray split = property.name.split("/");
+ if (split.size() == 3 && split[0] == "bones") {
+ if (split[2] == "rest") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (is_show_rest_only()) {
+ if (split[2] == "enabled") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (split[2] == "position") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (split[2] == "rotation") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (split[2] == "scale") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ } else if (!is_bone_enabled(split[1].to_int())) {
+ if (split[2] == "position") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (split[2] == "rotation") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ if (split[2] == "scale") {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+ }
+ }
+
+ Node3D::_validate_property(property);
}
void Skeleton3D::_update_process_order() {
@@ -547,18 +596,6 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) {
_make_dirty();
}
-void Skeleton3D::set_bone_disable_rest(int p_bone, bool p_disable) {
- const int bone_size = bones.size();
- ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].disable_rest = p_disable;
-}
-
-bool Skeleton3D::is_bone_rest_disabled(int p_bone) const {
- const int bone_size = bones.size();
- ERR_FAIL_INDEX_V(p_bone, bone_size, false);
- return bones[p_bone].disable_rest;
-}
-
int Skeleton3D::get_bone_parent(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, -1);
@@ -657,36 +694,60 @@ void Skeleton3D::clear_bones() {
// Posing api
-void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
+void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose = p_pose;
+ bones.write[p_bone].pose_position = p_position;
+ bones.write[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
}
}
-Transform3D Skeleton3D::get_bone_pose(int p_bone) const {
+void Skeleton3D::set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation) {
const int bone_size = bones.size();
- ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- return bones[p_bone].pose;
-}
+ ERR_FAIL_INDEX(p_bone, bone_size);
-void Skeleton3D::set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose) {
+ bones.write[p_bone].pose_rotation = p_rotation;
+ bones.write[p_bone].pose_cache_dirty = true;
+ if (is_inside_tree()) {
+ _make_dirty();
+ }
+}
+void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- //ERR_FAIL_COND( !is_inside_scene() );
- bones.write[p_bone].custom_pose_enable = (p_custom_pose != Transform3D());
- bones.write[p_bone].custom_pose = p_custom_pose;
+ bones.write[p_bone].pose_scale = p_scale;
+ bones.write[p_bone].pose_cache_dirty = true;
+ if (is_inside_tree()) {
+ _make_dirty();
+ }
+}
- _make_dirty();
+Vector3 Skeleton3D::get_bone_pose_position(int p_bone) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3());
+ return bones[p_bone].pose_position;
}
-Transform3D Skeleton3D::get_bone_custom_pose(int p_bone) const {
+Quaternion Skeleton3D::get_bone_pose_rotation(int p_bone) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, Quaternion());
+ return bones[p_bone].pose_rotation;
+}
+
+Vector3 Skeleton3D::get_bone_pose_scale(int p_bone) const {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3());
+ return bones[p_bone].pose_scale;
+}
+
+Transform3D Skeleton3D::get_bone_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- return bones[p_bone].custom_pose;
+ ((Skeleton3D *)this)->bones.write[p_bone].update_pose_cache();
+ return bones[p_bone].pose_cache;
}
void Skeleton3D::_make_dirty() {
@@ -887,59 +948,57 @@ void Skeleton3D::_skin_changed() {
_make_dirty();
}
-Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
- for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
- if (E->get()->skin == p_skin) {
- return Ref<SkinReference>(E->get());
+Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
+ Ref<Skin> skin;
+
+ skin.instantiate();
+ skin->set_bind_count(bones.size());
+ _update_process_order(); // Just in case.
+
+ // Pose changed, rebuild cache of inverses.
+ const Bone *bonesptr = bones.ptr();
+ int len = bones.size();
+
+ // Calculate global rests and invert them.
+ LocalVector<int> bones_to_process;
+ bones_to_process = get_parentless_bones();
+ while (bones_to_process.size() > 0) {
+ int current_bone_idx = bones_to_process[0];
+ const Bone &b = bonesptr[current_bone_idx];
+ bones_to_process.erase(current_bone_idx);
+ LocalVector<int> child_bones_vector;
+ child_bones_vector = get_bone_children(current_bone_idx);
+ int child_bones_size = child_bones_vector.size();
+ if (b.parent < 0) {
+ skin->set_bind_pose(current_bone_idx, b.rest);
+ }
+ for (int i = 0; i < child_bones_size; i++) {
+ int child_bone_idx = child_bones_vector[i];
+ const Bone &cb = bonesptr[child_bone_idx];
+ skin->set_bind_pose(child_bone_idx, skin->get_bind_pose(current_bone_idx) * cb.rest);
+ // Add the bone's children to the list of bones to be processed.
+ bones_to_process.push_back(child_bones_vector[i]);
}
}
- Ref<Skin> skin = p_skin;
-
- if (skin.is_null()) {
- // Need to create one from existing code, this is for compatibility only
- // when skeletons did not support skins. It is also used by gizmo
- // to display the skeleton.
-
- skin.instantiate();
- skin->set_bind_count(bones.size());
- _update_process_order(); // Just in case.
-
- // Pose changed, rebuild cache of inverses.
- const Bone *bonesptr = bones.ptr();
- int len = bones.size();
-
- // Calculate global rests and invert them.
- LocalVector<int> bones_to_process;
- bones_to_process = get_parentless_bones();
- while (bones_to_process.size() > 0) {
- int current_bone_idx = bones_to_process[0];
- const Bone &b = bonesptr[current_bone_idx];
- bones_to_process.erase(current_bone_idx);
- LocalVector<int> child_bones_vector;
- child_bones_vector = get_bone_children(current_bone_idx);
- int child_bones_size = child_bones_vector.size();
- if (b.parent < 0) {
- skin->set_bind_pose(current_bone_idx, b.rest);
- }
- for (int i = 0; i < child_bones_size; i++) {
- int child_bone_idx = child_bones_vector[i];
- const Bone &cb = bonesptr[child_bone_idx];
- skin->set_bind_pose(child_bone_idx, skin->get_bind_pose(current_bone_idx) * cb.rest);
- // Add the bone's children to the list of bones to be processed.
- bones_to_process.push_back(child_bones_vector[i]);
- }
- }
+ for (int i = 0; i < len; i++) {
+ // The inverse is what is actually required.
+ skin->set_bind_bone(i, i);
+ skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
+ }
- for (int i = 0; i < len; i++) {
- // The inverse is what is actually required.
- skin->set_bind_bone(i, i);
- skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
+ return skin;
+}
+
+Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
+ ERR_FAIL_COND_V(p_skin.is_null(), Ref<SkinReference>());
+
+ for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
+ if (E->get()->skin == p_skin) {
+ return Ref<SkinReference>(E->get());
}
}
- ERR_FAIL_COND_V(skin.is_null(), Ref<SkinReference>());
-
Ref<SkinReference> skin_ref;
skin_ref.instantiate();
@@ -947,11 +1006,11 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
skin_ref->bind_count = 0;
skin_ref->skeleton = RenderingServer::get_singleton()->skeleton_create();
skin_ref->skeleton_node = this;
- skin_ref->skin = skin;
+ skin_ref->skin = p_skin;
skin_bindings.insert(skin_ref.operator->());
- skin->connect("changed", Callable(skin_ref.operator->(), "_skin_changed"));
+ skin_ref->skin->connect("changed", Callable(skin_ref.operator->(), "_skin_changed"));
_make_dirty(); // Skin needs to be updated, so update skeleton.
@@ -987,59 +1046,33 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
Bone &b = bonesptr[current_bone_idx];
bool bone_enabled = b.enabled && !show_rest_only;
- if (b.disable_rest) {
- if (bone_enabled) {
- Transform3D pose = b.pose;
- if (b.custom_pose_enable) {
- pose = b.custom_pose * pose;
- }
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * pose;
- b.pose_global_no_override = b.pose_global;
- } else {
- b.pose_global = pose;
- b.pose_global_no_override = b.pose_global;
- }
+ if (bone_enabled) {
+ b.update_pose_cache();
+ Transform3D pose = b.pose_cache;
+
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global * pose;
+ b.pose_global_no_override = b.pose_global;
} else {
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global;
- b.pose_global_no_override = b.pose_global;
- } else {
- b.pose_global = Transform3D();
- b.pose_global_no_override = b.pose_global;
- }
+ b.pose_global = pose;
+ b.pose_global_no_override = b.pose_global;
}
-
} else {
- if (bone_enabled) {
- Transform3D pose = b.pose;
- if (b.custom_pose_enable) {
- pose = b.custom_pose * pose;
- }
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose);
- b.pose_global_no_override = b.pose_global;
- } else {
- b.pose_global = b.rest * pose;
- b.pose_global_no_override = b.pose_global;
- }
+ if (b.parent >= 0) {
+ b.pose_global = bonesptr[b.parent].pose_global * b.rest;
+ b.pose_global_no_override = b.pose_global;
} else {
- if (b.parent >= 0) {
- b.pose_global = bonesptr[b.parent].pose_global * b.rest;
- b.pose_global_no_override = b.pose_global;
- } else {
- b.pose_global = b.rest;
- b.pose_global_no_override = b.pose_global;
- }
+ b.pose_global = b.rest;
+ b.pose_global_no_override = b.pose_global;
}
}
if (b.local_pose_override_amount >= CMP_EPSILON) {
Transform3D override_local_pose;
if (b.parent >= 0) {
- override_local_pose = bonesptr[b.parent].pose_global * (b.rest * b.local_pose_override);
+ override_local_pose = bonesptr[b.parent].pose_global * b.local_pose_override;
} else {
- override_local_pose = (b.rest * b.local_pose_override);
+ override_local_pose = b.local_pose_override;
}
b.pose_global = b.pose_global.interpolate_with(override_local_pose, b.local_pose_override_amount);
}
@@ -1080,8 +1113,8 @@ Transform3D Skeleton3D::global_pose_to_local_pose(int p_bone_idx, Transform3D p_
ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D());
if (bones[p_bone_idx].parent >= 0) {
int parent_bone_idx = bones[p_bone_idx].parent;
- Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest);
- return conversion_transform.affine_inverse() * p_global_pose;
+ Transform3D conversion_transform = get_bone_global_pose(parent_bone_idx).affine_inverse();
+ return conversion_transform * p_global_pose;
} else {
return p_global_pose;
}
@@ -1092,8 +1125,7 @@ Transform3D Skeleton3D::local_pose_to_global_pose(int p_bone_idx, Transform3D p_
ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D());
if (bones[p_bone_idx].parent >= 0) {
int parent_bone_idx = bones[p_bone_idx].parent;
- Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest);
- return conversion_transform * p_local_pose;
+ return bones[parent_bone_idx].pose_global * p_local_pose;
} else {
return p_local_pose;
}
@@ -1183,17 +1215,21 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_bone_rest", "bone_idx"), &Skeleton3D::get_bone_rest);
ClassDB::bind_method(D_METHOD("set_bone_rest", "bone_idx", "rest"), &Skeleton3D::set_bone_rest);
+ ClassDB::bind_method(D_METHOD("create_skin_from_rest_transforms"), &Skeleton3D::create_skin_from_rest_transforms);
ClassDB::bind_method(D_METHOD("register_skin", "skin"), &Skeleton3D::register_skin);
ClassDB::bind_method(D_METHOD("localize_rests"), &Skeleton3D::localize_rests);
- ClassDB::bind_method(D_METHOD("set_bone_disable_rest", "bone_idx", "disable"), &Skeleton3D::set_bone_disable_rest);
- ClassDB::bind_method(D_METHOD("is_bone_rest_disabled", "bone_idx"), &Skeleton3D::is_bone_rest_disabled);
-
ClassDB::bind_method(D_METHOD("clear_bones"), &Skeleton3D::clear_bones);
ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton3D::get_bone_pose);
- ClassDB::bind_method(D_METHOD("set_bone_pose", "bone_idx", "pose"), &Skeleton3D::set_bone_pose);
+ ClassDB::bind_method(D_METHOD("set_bone_pose_position", "bone_idx", "position"), &Skeleton3D::set_bone_pose_position);
+ ClassDB::bind_method(D_METHOD("set_bone_pose_rotation", "bone_idx", "rotation"), &Skeleton3D::set_bone_pose_rotation);
+ ClassDB::bind_method(D_METHOD("set_bone_pose_scale", "bone_idx", "scale"), &Skeleton3D::set_bone_pose_scale);
+
+ ClassDB::bind_method(D_METHOD("get_bone_pose_position", "bone_idx"), &Skeleton3D::get_bone_pose_position);
+ ClassDB::bind_method(D_METHOD("get_bone_pose_rotation", "bone_idx"), &Skeleton3D::get_bone_pose_rotation);
+ ClassDB::bind_method(D_METHOD("get_bone_pose_scale", "bone_idx"), &Skeleton3D::get_bone_pose_scale);
ClassDB::bind_method(D_METHOD("is_bone_enabled", "bone_idx"), &Skeleton3D::is_bone_enabled);
ClassDB::bind_method(D_METHOD("set_bone_enabled", "bone_idx", "enabled"), &Skeleton3D::set_bone_enabled, DEFVAL(true));
@@ -1208,9 +1244,6 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_local_pose_override, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton3D::get_bone_local_pose_override);
- ClassDB::bind_method(D_METHOD("get_bone_custom_pose", "bone_idx"), &Skeleton3D::get_bone_custom_pose);
- ClassDB::bind_method(D_METHOD("set_bone_custom_pose", "bone_idx", "custom_pose"), &Skeleton3D::set_bone_custom_pose);
-
ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms);
ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms);
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 9aa4fc823f..f7bc3df94e 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -76,16 +76,24 @@ private:
bool enabled;
int parent;
- bool disable_rest = false;
Transform3D rest;
- Transform3D pose;
+ _FORCE_INLINE_ void update_pose_cache() {
+ if (pose_cache_dirty) {
+ pose_cache.basis.set_quaternion_scale(pose_rotation, pose_scale);
+ pose_cache.origin = pose_position;
+ pose_cache_dirty = false;
+ }
+ }
+ bool pose_cache_dirty = true;
+ Transform3D pose_cache;
+ Vector3 pose_position;
+ Quaternion pose_rotation;
+ Vector3 pose_scale = Vector3(1, 1, 1);
+
Transform3D pose_global;
Transform3D pose_global_no_override;
- bool custom_pose_enable = false;
- Transform3D custom_pose;
-
real_t global_pose_override_amount = 0.0;
bool global_pose_override_reset = false;
Transform3D global_pose_override;
@@ -107,8 +115,6 @@ private:
Bone() {
parent = -1;
enabled = true;
- disable_rest = false;
- custom_pose_enable = false;
global_pose_override_amount = 0;
global_pose_override_reset = false;
#ifndef _3D_DISABLED
@@ -147,6 +153,7 @@ protected:
bool _get(const StringName &p_path, Variant &r_ret) const;
bool _set(const StringName &p_path, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_list) const;
+ virtual void _validate_property(PropertyInfo &property) const override;
void _notification(int p_what);
static void _bind_methods();
@@ -187,9 +194,6 @@ public:
void remove_bone_child(int p_bone, int p_child);
Vector<int> get_parentless_bones();
- void set_bone_disable_rest(int p_bone, bool p_disable);
- bool is_bone_rest_disabled(int p_bone) const;
-
int get_bone_count() const;
void set_bone_rest(int p_bone, const Transform3D &p_rest);
@@ -206,11 +210,15 @@ public:
// posing api
- void set_bone_pose(int p_bone, const Transform3D &p_pose);
+ void set_bone_pose_position(int p_bone, const Vector3 &p_position);
+ void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation);
+ void set_bone_pose_scale(int p_bone, const Vector3 &p_scale);
+
Transform3D get_bone_pose(int p_bone) const;
- void set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose);
- Transform3D get_bone_custom_pose(int p_bone) const;
+ Vector3 get_bone_pose_position(int p_bone) const;
+ Quaternion get_bone_pose_rotation(int p_bone) const;
+ Vector3 get_bone_pose_scale(int p_bone) const;
void clear_bones_global_pose_override();
Transform3D get_bone_global_pose_override(int p_bone) const;
@@ -222,6 +230,8 @@ public:
void localize_rests(); // used for loaders and tools
+ Ref<Skin> create_skin_from_rest_transforms();
+
Ref<SkinReference> register_skin(const Ref<Skin> &p_skin);
void force_update_all_dirty_bones();
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/soft_dynamic_body_3d.cpp b/scene/3d/soft_dynamic_body_3d.cpp
index 9fceb21790..903eedb58b 100644
--- a/scene/3d/soft_dynamic_body_3d.cpp
+++ b/scene/3d/soft_dynamic_body_3d.cpp
@@ -250,7 +250,7 @@ void SoftDynamicBody3D::_notification(int p_what) {
RID space = get_world_3d()->get_space();
PhysicsServer3D::get_singleton()->soft_body_set_space(physics_rid, space);
- prepare_physics_server();
+ _prepare_physics_server();
} break;
case NOTIFICATION_READY: {
@@ -284,13 +284,13 @@ void SoftDynamicBody3D::_notification(int p_what) {
case NOTIFICATION_DISABLED: {
if (is_inside_tree() && (disable_mode == DISABLE_MODE_REMOVE)) {
- prepare_physics_server();
+ _prepare_physics_server();
}
} break;
case NOTIFICATION_ENABLED: {
if (is_inside_tree() && (disable_mode == DISABLE_MODE_REMOVE)) {
- prepare_physics_server();
+ _prepare_physics_server();
}
} break;
@@ -378,7 +378,7 @@ void SoftDynamicBody3D::_bind_methods() {
TypedArray<String> SoftDynamicBody3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
- if (get_mesh().is_null()) {
+ if (mesh.is_null()) {
warnings.push_back(TTR("This body will be ignored until you set a mesh."));
}
@@ -407,11 +407,17 @@ void SoftDynamicBody3D::_update_physics_server() {
}
void SoftDynamicBody3D::_draw_soft_mesh() {
- if (get_mesh().is_null()) {
+ if (mesh.is_null()) {
return;
}
- const RID mesh_rid = get_mesh()->get_rid();
+ RID mesh_rid = mesh->get_rid();
+ if (owned_mesh != mesh_rid) {
+ _become_mesh_owner();
+ mesh_rid = mesh->get_rid();
+ PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, mesh_rid);
+ }
+
if (!rendering_server_handler.is_ready(mesh_rid)) {
rendering_server_handler.prepare(mesh_rid, 0);
@@ -430,11 +436,11 @@ void SoftDynamicBody3D::_draw_soft_mesh() {
rendering_server_handler.commit_changes();
}
-void SoftDynamicBody3D::prepare_physics_server() {
+void SoftDynamicBody3D::_prepare_physics_server() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- if (get_mesh().is_valid()) {
- PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, get_mesh()->get_rid());
+ if (mesh.is_valid()) {
+ PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, mesh->get_rid());
} else {
PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, RID());
}
@@ -443,9 +449,13 @@ void SoftDynamicBody3D::prepare_physics_server() {
}
#endif
- if (get_mesh().is_valid() && (is_enabled() || (disable_mode != DISABLE_MODE_REMOVE))) {
- become_mesh_owner();
- PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, get_mesh()->get_rid());
+ if (mesh.is_valid() && (is_enabled() || (disable_mode != DISABLE_MODE_REMOVE))) {
+ RID mesh_rid = mesh->get_rid();
+ if (owned_mesh != mesh_rid) {
+ _become_mesh_owner();
+ mesh_rid = mesh->get_rid();
+ }
+ PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, mesh_rid);
RS::get_singleton()->connect("frame_pre_draw", callable_mp(this, &SoftDynamicBody3D::_draw_soft_mesh));
} else {
PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, RID());
@@ -455,38 +465,32 @@ void SoftDynamicBody3D::prepare_physics_server() {
}
}
-void SoftDynamicBody3D::become_mesh_owner() {
- if (mesh.is_null()) {
- return;
- }
+void SoftDynamicBody3D::_become_mesh_owner() {
+ Vector<Ref<Material>> copy_materials;
+ copy_materials.append_array(surface_override_materials);
- if (!mesh_owner) {
- mesh_owner = true;
+ ERR_FAIL_COND(!mesh->get_surface_count());
- Vector<Ref<Material>> copy_materials;
- copy_materials.append_array(surface_override_materials);
+ // Get current mesh array and create new mesh array with necessary flag for SoftDynamicBody
+ Array surface_arrays = mesh->surface_get_arrays(0);
+ Array surface_blend_arrays = mesh->surface_get_blend_shape_arrays(0);
+ Dictionary surface_lods = mesh->surface_get_lods(0);
+ uint32_t surface_format = mesh->surface_get_format(0);
- ERR_FAIL_COND(!mesh->get_surface_count());
+ surface_format |= Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE;
- // Get current mesh array and create new mesh array with necessary flag for SoftDynamicBody
- Array surface_arrays = mesh->surface_get_arrays(0);
- Array surface_blend_arrays = mesh->surface_get_blend_shape_arrays(0);
- Dictionary surface_lods = mesh->surface_get_lods(0);
- uint32_t surface_format = mesh->surface_get_format(0);
+ Ref<ArrayMesh> soft_mesh;
+ soft_mesh.instantiate();
+ soft_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_arrays, surface_blend_arrays, surface_lods, surface_format);
+ soft_mesh->surface_set_material(0, mesh->surface_get_material(0));
- surface_format |= Mesh::ARRAY_FLAG_USE_DYNAMIC_UPDATE;
+ set_mesh(soft_mesh);
- Ref<ArrayMesh> soft_mesh;
- soft_mesh.instantiate();
- soft_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_arrays, surface_blend_arrays, surface_lods, surface_format);
- soft_mesh->surface_set_material(0, mesh->surface_get_material(0));
-
- set_mesh(soft_mesh);
-
- for (int i = copy_materials.size() - 1; 0 <= i; --i) {
- set_surface_override_material(i, copy_materials[i]);
- }
+ for (int i = copy_materials.size() - 1; 0 <= i; --i) {
+ set_surface_override_material(i, copy_materials[i]);
}
+
+ owned_mesh = soft_mesh->get_rid();
}
void SoftDynamicBody3D::set_collision_mask(uint32_t p_mask) {
@@ -548,16 +552,10 @@ void SoftDynamicBody3D::set_disable_mode(DisableMode p_mode) {
return;
}
- bool inside_tree = is_inside_tree();
-
- if (inside_tree && (disable_mode == DISABLE_MODE_REMOVE)) {
- prepare_physics_server();
- }
-
disable_mode = p_mode;
- if (inside_tree && (disable_mode == DISABLE_MODE_REMOVE)) {
- prepare_physics_server();
+ if (mesh.is_valid() && is_inside_tree() && !is_enabled()) {
+ _prepare_physics_server();
}
}
diff --git a/scene/3d/soft_dynamic_body_3d.h b/scene/3d/soft_dynamic_body_3d.h
index 5e7fbfe29e..57e116aa05 100644
--- a/scene/3d/soft_dynamic_body_3d.h
+++ b/scene/3d/soft_dynamic_body_3d.h
@@ -90,7 +90,7 @@ private:
DisableMode disable_mode = DISABLE_MODE_REMOVE;
- bool mesh_owner = false;
+ RID owned_mesh;
uint32_t collision_mask = 1;
uint32_t collision_layer = 1;
NodePath parent_collision_ignore;
@@ -106,6 +106,12 @@ private:
void _update_pickable();
+ void _update_physics_server();
+ void _draw_soft_mesh();
+
+ void _prepare_physics_server();
+ void _become_mesh_owner();
+
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -120,14 +126,7 @@ protected:
TypedArray<String> get_configuration_warnings() const override;
-protected:
- void _update_physics_server();
- void _draw_soft_mesh();
-
public:
- void prepare_physics_server();
- void become_mesh_owner();
-
RID get_physics_rid() const { return physics_rid; }
void set_collision_mask(uint32_t p_mask);
diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp
index 116cab19b1..0b5af8823b 100644
--- a/scene/3d/spring_arm_3d.cpp
+++ b/scene/3d/spring_arm_3d.cpp
@@ -149,10 +149,24 @@ void SpringArm3D::process_spring() {
//use camera rotation, but spring arm position
Transform3D base_transform = camera->get_global_transform();
base_transform.origin = get_global_transform().origin;
- get_world_3d()->get_direct_space_state()->cast_motion(camera->get_pyramid_shape_rid(), base_transform, motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask);
+
+ PhysicsDirectSpaceState3D::ShapeParameters shape_params;
+ shape_params.shape_rid = camera->get_pyramid_shape_rid();
+ shape_params.transform = base_transform;
+ shape_params.motion = motion;
+ shape_params.exclude = excluded_objects;
+ shape_params.collision_mask = mask;
+
+ get_world_3d()->get_direct_space_state()->cast_motion(shape_params, motion_delta, motion_delta_unsafe);
} else {
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = get_global_transform().origin;
+ ray_params.to = get_global_transform().origin + motion;
+ ray_params.exclude = excluded_objects;
+ ray_params.collision_mask = mask;
+
PhysicsDirectSpaceState3D::RayResult r;
- bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(get_global_transform().origin, get_global_transform().origin + motion, r, excluded_objects, mask);
+ bool intersected = get_world_3d()->get_direct_space_state()->intersect_ray(ray_params, r);
if (intersected) {
real_t dist = get_global_transform().origin.distance_to(r.position);
dist -= margin;
@@ -160,7 +174,14 @@ void SpringArm3D::process_spring() {
}
}
} else {
- get_world_3d()->get_direct_space_state()->cast_motion(shape->get_rid(), get_global_transform(), motion, 0, motion_delta, motion_delta_unsafe, excluded_objects, mask);
+ PhysicsDirectSpaceState3D::ShapeParameters shape_params;
+ shape_params.shape_rid = shape->get_rid();
+ shape_params.transform = get_global_transform();
+ shape_params.motion = motion;
+ shape_params.exclude = excluded_objects;
+ shape_params.collision_mask = mask;
+
+ get_world_3d()->get_direct_space_state()->cast_motion(shape_params, motion_delta, motion_delta_unsafe);
}
current_spring_length = spring_length * motion_delta;
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/sprite_3d.h b/scene/3d/sprite_3d.h
index 90c2a309e1..61448c0e32 100644
--- a/scene/3d/sprite_3d.h
+++ b/scene/3d/sprite_3d.h
@@ -118,12 +118,6 @@ public:
void set_flip_v(bool p_flip);
bool is_flipped_v() const;
- void set_region_enabled(bool p_region);
- bool is_region_enabled() const;
-
- void set_region_rect(const Rect2 &p_region_rect);
- Rect2 get_region_rect() const;
-
void set_modulate(const Color &p_color);
Color get_modulate() const;
diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp
index 9a2aaa8be2..90db093137 100644
--- a/scene/3d/vehicle_body_3d.cpp
+++ b/scene/3d/vehicle_body_3d.cpp
@@ -124,7 +124,7 @@ void VehicleWheel3D::_update(PhysicsDirectBodyState3D *s) {
Vector3 relpos = m_raycastInfo.m_contactPointWS - s->get_transform().origin;
chassis_velocity_at_contactPoint = s->get_linear_velocity() +
- (s->get_angular_velocity()).cross(relpos); // * mPos);
+ (s->get_angular_velocity()).cross(relpos); // * mPos);
real_t projVel = m_raycastInfo.m_contactNormalWS.dot(chassis_velocity_at_contactPoint);
if (project >= real_t(-0.1)) {
@@ -275,7 +275,7 @@ void VehicleWheel3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_steering"), &VehicleWheel3D::get_steering);
ADD_GROUP("Per-Wheel Motion", "");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "0.00,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "-1024,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_brake", "get_brake");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering");
ADD_GROUP("VehicleBody3D Motion", "");
@@ -407,7 +407,13 @@ real_t VehicleBody3D::_ray_cast(int p_idx, PhysicsDirectBodyState3D *s) {
PhysicsDirectSpaceState3D *ss = s->get_space_state();
- bool col = ss->intersect_ray(source, target, rr, exclude, get_collision_mask());
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = source;
+ ray_params.to = target;
+ ray_params.exclude = exclude;
+ ray_params.collision_mask = get_collision_mask();
+
+ bool col = ss->intersect_ray(ray_params, rr);
wheel.m_raycastInfo.m_groundObject = nullptr;
@@ -444,7 +450,7 @@ real_t VehicleBody3D::_ray_cast(int p_idx, PhysicsDirectBodyState3D *s) {
//chassis_velocity_at_contactPoint = getRigidBody()->getVelocityInLocalPoint(relpos);
chassis_velocity_at_contactPoint = s->get_linear_velocity() +
- (s->get_angular_velocity()).cross(wheel.m_raycastInfo.m_contactPointWS - s->get_transform().origin); // * mPos);
+ (s->get_angular_velocity()).cross(wheel.m_raycastInfo.m_contactPointWS - s->get_transform().origin); // * mPos);
real_t projVel = wheel.m_raycastInfo.m_contactNormalWS.dot(chassis_velocity_at_contactPoint);
@@ -771,7 +777,7 @@ void VehicleBody3D::_update_friction(PhysicsDirectBodyState3D *s) {
VehicleWheel3D &wheelInfo = *wheels[wheel];
Vector3 rel_pos = wheelInfo.m_raycastInfo.m_contactPointWS -
- s->get_transform().origin;
+ s->get_transform().origin;
if (m_forwardImpulse[wheel] != real_t(0.)) {
s->apply_impulse(m_forwardWS[wheel] * (m_forwardImpulse[wheel]), rel_pos);
@@ -914,7 +920,7 @@ void VehicleBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_steering"), &VehicleBody3D::get_steering);
ADD_GROUP("Motion", "");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "0.00,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "engine_force", PROPERTY_HINT_RANGE, "-1024,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "brake", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_brake", "get_brake");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering");
}
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 73c2887983..d407592376 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -152,9 +152,18 @@ Ref<Material> GeometryInstance3D::get_material_override() const {
return material_override;
}
+void GeometryInstance3D::set_transparecy(float p_transparency) {
+ transparency = CLAMP(p_transparency, 0.0f, 1.0f);
+ RS::get_singleton()->instance_geometry_set_transparency(get_instance(), transparency);
+}
+
+float GeometryInstance3D::get_transparency() const {
+ return transparency;
+}
+
void GeometryInstance3D::set_visibility_range_begin(float p_dist) {
visibility_range_begin = p_dist;
- RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin);
+ RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin, (RS::VisibilityRangeFadeMode)visibility_range_fade_mode);
update_configuration_warnings();
}
@@ -164,7 +173,7 @@ float GeometryInstance3D::get_visibility_range_begin() const {
void GeometryInstance3D::set_visibility_range_end(float p_dist) {
visibility_range_end = p_dist;
- RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin);
+ RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin, (RS::VisibilityRangeFadeMode)visibility_range_fade_mode);
update_configuration_warnings();
}
@@ -174,7 +183,7 @@ float GeometryInstance3D::get_visibility_range_end() const {
void GeometryInstance3D::set_visibility_range_begin_margin(float p_dist) {
visibility_range_begin_margin = p_dist;
- RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin);
+ RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin, (RS::VisibilityRangeFadeMode)visibility_range_fade_mode);
}
float GeometryInstance3D::get_visibility_range_begin_margin() const {
@@ -183,13 +192,22 @@ float GeometryInstance3D::get_visibility_range_begin_margin() const {
void GeometryInstance3D::set_visibility_range_end_margin(float p_dist) {
visibility_range_end_margin = p_dist;
- RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin);
+ RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin, (RS::VisibilityRangeFadeMode)visibility_range_fade_mode);
}
float GeometryInstance3D::get_visibility_range_end_margin() const {
return visibility_range_end_margin;
}
+void GeometryInstance3D::set_visibility_range_fade_mode(VisibilityRangeFadeMode p_mode) {
+ visibility_range_fade_mode = p_mode;
+ RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin, (RS::VisibilityRangeFadeMode)visibility_range_fade_mode);
+}
+
+GeometryInstance3D::VisibilityRangeFadeMode GeometryInstance3D::get_visibility_range_fade_mode() const {
+ return visibility_range_fade_mode;
+}
+
void GeometryInstance3D::_notification(int p_what) {
}
@@ -375,6 +393,9 @@ void GeometryInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_lod_bias", "bias"), &GeometryInstance3D::set_lod_bias);
ClassDB::bind_method(D_METHOD("get_lod_bias"), &GeometryInstance3D::get_lod_bias);
+ ClassDB::bind_method(D_METHOD("set_transparency", "transparency"), &GeometryInstance3D::set_transparecy);
+ ClassDB::bind_method(D_METHOD("get_transparency"), &GeometryInstance3D::get_transparency);
+
ClassDB::bind_method(D_METHOD("set_visibility_range_end_margin", "distance"), &GeometryInstance3D::set_visibility_range_end_margin);
ClassDB::bind_method(D_METHOD("get_visibility_range_end_margin"), &GeometryInstance3D::get_visibility_range_end_margin);
@@ -387,6 +408,9 @@ void GeometryInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_visibility_range_begin", "distance"), &GeometryInstance3D::set_visibility_range_begin);
ClassDB::bind_method(D_METHOD("get_visibility_range_begin"), &GeometryInstance3D::get_visibility_range_begin);
+ ClassDB::bind_method(D_METHOD("set_visibility_range_fade_mode", "mode"), &GeometryInstance3D::set_visibility_range_fade_mode);
+ ClassDB::bind_method(D_METHOD("get_visibility_range_fade_mode"), &GeometryInstance3D::get_visibility_range_fade_mode);
+
ClassDB::bind_method(D_METHOD("set_shader_instance_uniform", "uniform", "value"), &GeometryInstance3D::set_shader_instance_uniform);
ClassDB::bind_method(D_METHOD("get_shader_instance_uniform", "uniform"), &GeometryInstance3D::get_shader_instance_uniform);
@@ -408,6 +432,7 @@ void GeometryInstance3D::_bind_methods() {
ADD_GROUP("Geometry", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_override", "get_material_override");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "transparency", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_transparency", "get_transparency");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01"), "set_extra_cull_margin", "get_extra_cull_margin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_bias", PROPERTY_HINT_RANGE, "0.001,128,0.001"), "set_lod_bias", "get_lod_bias");
@@ -421,7 +446,7 @@ void GeometryInstance3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_begin_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_begin_margin", "get_visibility_range_begin_margin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_end", "get_visibility_range_end");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visibility_range_end_margin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01"), "set_visibility_range_end_margin", "get_visibility_range_end_margin");
-
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_range_fade_mode", PROPERTY_HINT_ENUM, "Disabled,Self,Dependencies"), "set_visibility_range_fade_mode", "get_visibility_range_fade_mode");
//ADD_SIGNAL( MethodInfo("visibility_changed"));
BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_OFF);
@@ -438,6 +463,10 @@ void GeometryInstance3D::_bind_methods() {
BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_4X);
BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_8X);
BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_MAX);
+
+ BIND_ENUM_CONSTANT(VISIBILITY_RANGE_FADE_DISABLED);
+ BIND_ENUM_CONSTANT(VISIBILITY_RANGE_FADE_SELF);
+ BIND_ENUM_CONSTANT(VISIBILITY_RANGE_FADE_DEPENDENCIES);
}
GeometryInstance3D::GeometryInstance3D() {
diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h
index 8d24e13d47..acdbc4666a 100644
--- a/scene/3d/visual_instance_3d.h
+++ b/scene/3d/visual_instance_3d.h
@@ -101,6 +101,12 @@ public:
LIGHTMAP_SCALE_MAX,
};
+ enum VisibilityRangeFadeMode {
+ VISIBILITY_RANGE_FADE_DISABLED = RS::VISIBILITY_RANGE_FADE_DISABLED,
+ VISIBILITY_RANGE_FADE_SELF = RS::VISIBILITY_RANGE_FADE_SELF,
+ VISIBILITY_RANGE_FADE_DEPENDENCIES = RS::VISIBILITY_RANGE_FADE_DEPENDENCIES,
+ };
+
private:
ShadowCastingSetting shadow_casting_setting = SHADOW_CASTING_SETTING_ON;
Ref<Material> material_override;
@@ -109,8 +115,9 @@ private:
float visibility_range_end = 0.0;
float visibility_range_begin_margin = 0.0;
float visibility_range_end_margin = 0.0;
+ VisibilityRangeFadeMode visibility_range_fade_mode = VISIBILITY_RANGE_FADE_DISABLED;
- Vector<NodePath> visibility_range_children;
+ float transparency = 0.0f;
float lod_bias = 1.0;
@@ -136,6 +143,9 @@ public:
void set_cast_shadows_setting(ShadowCastingSetting p_shadow_casting_setting);
ShadowCastingSetting get_cast_shadows_setting() const;
+ void set_transparecy(float p_transparency);
+ float get_transparency() const;
+
void set_visibility_range_begin(float p_dist);
float get_visibility_range_begin() const;
@@ -148,8 +158,8 @@ public:
void set_visibility_range_end_margin(float p_dist);
float get_visibility_range_end_margin() const;
- void set_visibility_range_parent(const Node *p_parent);
- void clear_visibility_range_parent();
+ void set_visibility_range_fade_mode(VisibilityRangeFadeMode p_mode);
+ VisibilityRangeFadeMode get_visibility_range_fade_mode() const;
void set_material_override(const Ref<Material> &p_material);
Ref<Material> get_material_override() const;
@@ -181,5 +191,6 @@ public:
VARIANT_ENUM_CAST(GeometryInstance3D::ShadowCastingSetting);
VARIANT_ENUM_CAST(GeometryInstance3D::LightmapScale);
VARIANT_ENUM_CAST(GeometryInstance3D::GIMode);
+VARIANT_ENUM_CAST(GeometryInstance3D::VisibilityRangeFadeMode);
#endif
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index 377abd5b38..c7108cbae0 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -225,7 +225,7 @@ void VoxelGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_data", "data"), &VoxelGIData::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &VoxelGIData::_get_data);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "dynamic_range", PROPERTY_HINT_RANGE, "0,8,0.01"), "set_dynamic_range", "get_dynamic_range");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_energy", "get_energy");
@@ -403,7 +403,7 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) {
if (p_create_visual_debug) {
MultiMeshInstance3D *mmi = memnew(MultiMeshInstance3D);
mmi->set_multimesh(baker.create_debug_multimesh());
- add_child(mmi);
+ add_child(mmi, true);
#ifdef TOOLS_ENABLED
if (is_inside_tree() && get_tree()->get_edited_scene_root() == this) {
mmi->set_owner(this);
@@ -458,7 +458,7 @@ TypedArray<String> VoxelGI::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (RenderingServer::get_singleton()->is_low_end()) {
- warnings.push_back(TTR("VoxelGIs are not supported by the GLES2 video driver.\nUse a LightmapGI instead."));
+ warnings.push_back(TTR("VoxelGIs are not supported by the OpenGL video driver.\nUse a LightmapGI instead."));
} else if (probe_data.is_null()) {
warnings.push_back(TTR("No VoxelGI data set, so this node is disabled. Bake static objects to enable GI."));
}
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index ebfb58e9fe..a16820cbdc 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -47,13 +47,45 @@ void XRCamera3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
// need to find our XROrigin3D parent and let it know we're no longer its camera!
XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin != nullptr) {
- origin->clear_tracked_camera_if(this);
+ if (origin != nullptr && origin->get_tracked_camera() == this) {
+ origin->set_tracked_camera(nullptr);
}
}; break;
};
};
+void XRCamera3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) {
+ if (p_tracker_name == tracker_name) {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL(xr_server);
+
+ tracker = xr_server->get_tracker(p_tracker_name);
+ if (tracker.is_valid()) {
+ tracker->connect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
+
+ Ref<XRPose> pose = tracker->get_pose(pose_name);
+ if (pose.is_valid()) {
+ set_transform(pose->get_adjusted_transform());
+ }
+ }
+ }
+}
+
+void XRCamera3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) {
+ if (p_tracker_name == tracker_name) {
+ if (tracker.is_valid()) {
+ tracker->disconnect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
+ }
+ tracker.unref();
+ }
+}
+
+void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
+ if (p_pose->get_name() == pose_name) {
+ set_transform(p_pose->get_adjusted_transform());
+ }
+}
+
TypedArray<String> XRCamera3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
@@ -172,195 +204,217 @@ Vector<Plane> XRCamera3D::get_frustum() const {
return cm.get_projection_planes(get_camera_transform());
};
-////////////////////////////////////////////////////////////////////////////////////////////////////
+XRCamera3D::XRCamera3D() {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL(xr_server);
-void XRController3D::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- set_process_internal(true);
- }; break;
- case NOTIFICATION_EXIT_TREE: {
- set_process_internal(false);
- }; break;
- case NOTIFICATION_INTERNAL_PROCESS: {
- // get our XRServer
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL(xr_server);
-
- // find the tracker for our controller
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
- if (!tracker.is_valid()) {
- // this controller is currently turned off
- is_active = false;
- button_states = 0;
- } else {
- is_active = true;
- set_transform(tracker->get_transform(true));
-
- int joy_id = tracker->get_joy_id();
- if (joy_id >= 0) {
- int mask = 1;
- // check button states
- for (int i = 0; i < 16; i++) {
- bool was_pressed = (button_states & mask) == mask;
- bool is_pressed = Input::get_singleton()->is_joy_button_pressed(joy_id, (JoyButton)i);
-
- if (!was_pressed && is_pressed) {
- emit_signal(SNAME("button_pressed"), i);
- button_states += mask;
- } else if (was_pressed && !is_pressed) {
- emit_signal(SNAME("button_released"), i);
- button_states -= mask;
- };
-
- mask = mask << 1;
- };
-
- } else {
- button_states = 0;
- };
-
- // check for an updated mesh
- Ref<Mesh> trackerMesh = tracker->get_mesh();
- if (mesh != trackerMesh) {
- mesh = trackerMesh;
- emit_signal(SNAME("mesh_updated"), mesh);
- }
- };
- }; break;
- default:
- break;
- };
-};
+ xr_server->connect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
+}
-void XRController3D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_controller_id", "controller_id"), &XRController3D::set_controller_id);
- ClassDB::bind_method(D_METHOD("get_controller_id"), &XRController3D::get_controller_id);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "controller_id", PROPERTY_HINT_RANGE, "0,32,1"), "set_controller_id", "get_controller_id");
- ClassDB::bind_method(D_METHOD("get_controller_name"), &XRController3D::get_controller_name);
+XRCamera3D::~XRCamera3D() {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL(xr_server);
- // passthroughs to information about our related joystick
- ClassDB::bind_method(D_METHOD("get_joystick_id"), &XRController3D::get_joystick_id);
- ClassDB::bind_method(D_METHOD("is_button_pressed", "button"), &XRController3D::is_button_pressed);
- ClassDB::bind_method(D_METHOD("get_joystick_axis", "axis"), &XRController3D::get_joystick_axis);
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
+}
- ClassDB::bind_method(D_METHOD("get_is_active"), &XRController3D::get_is_active);
- ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand);
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// XRNode3D is a node that has it's transform updated by an XRPositionalTracker.
+// Note that trackers are only available in runtime and only after an XRInterface registers one.
+// So we bind by name and as long as a tracker isn't available, our node remains inactive.
- ClassDB::bind_method(D_METHOD("get_rumble"), &XRController3D::get_rumble);
- ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRController3D::set_rumble);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble");
- ADD_PROPERTY_DEFAULT("rumble", 0.0);
+void XRNode3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_tracker", "tracker_name"), &XRNode3D::set_tracker);
+ ClassDB::bind_method(D_METHOD("get_tracker"), &XRNode3D::get_tracker);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "tracker", PROPERTY_HINT_ENUM_SUGGESTION), "set_tracker", "get_tracker");
- ClassDB::bind_method(D_METHOD("get_mesh"), &XRController3D::get_mesh);
+ ClassDB::bind_method(D_METHOD("set_pose_name", "pose"), &XRNode3D::set_pose_name);
+ ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name");
- ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::INT, "button")));
- ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::INT, "button")));
- ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")));
+ ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active);
+ ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data);
+ ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose);
+ ClassDB::bind_method(D_METHOD("trigger_haptic_pulse", "action_name", "frequency", "amplitude", "duration_sec", "delay_sec"), &XRNode3D::trigger_haptic_pulse);
};
-void XRController3D::set_controller_id(int p_controller_id) {
- // We don't check any bounds here, this controller may not yet be active and just be a place holder until it is.
- // Note that setting this to 0 means this node is not bound to a controller yet.
- controller_id = p_controller_id;
- update_configuration_warnings();
-};
+void XRNode3D::_validate_property(PropertyInfo &property) const {
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL(xr_server);
-int XRController3D::get_controller_id() const {
- return controller_id;
-};
+ if (property.name == "tracker") {
+ PackedStringArray names = xr_server->get_suggested_tracker_names();
+ String hint_string;
+ for (const String &name : names) {
+ hint_string += name + ",";
+ }
+ property.hint_string = hint_string;
+ } else if (property.name == "pose") {
+ PackedStringArray names = xr_server->get_suggested_pose_names(tracker_name);
+ String hint_string;
+ for (const String &name : names) {
+ hint_string += name + ",";
+ }
+ property.hint_string = hint_string;
+ }
-String XRController3D::get_controller_name() const {
- // get our XRServer
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, String());
+ Node3D::_validate_property(property);
+}
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
- if (!tracker.is_valid()) {
- return String("Not connected");
- };
+void XRNode3D::set_tracker(const StringName p_tracker_name) {
+ if (tracker.is_valid() && tracker->get_tracker_name() == p_tracker_name) {
+ // didn't change
+ return;
+ }
- return tracker->get_tracker_name();
-};
+ // just in case
+ _unbind_tracker();
-int XRController3D::get_joystick_id() const {
- // get our XRServer
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, 0);
+ // copy the name
+ tracker_name = p_tracker_name;
+ pose_name = "default";
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
- if (!tracker.is_valid()) {
- // No tracker? no joystick id... (0 is our first joystick)
- return -1;
- };
+ // see if it's already available
+ _bind_tracker();
- return tracker->get_joy_id();
-};
+ update_configuration_warnings();
+ notify_property_list_changed();
+}
-bool XRController3D::is_button_pressed(int p_button) const {
- int joy_id = get_joystick_id();
- if (joy_id == -1) {
- return false;
- };
+StringName XRNode3D::get_tracker() const {
+ return tracker_name;
+}
- return Input::get_singleton()->is_joy_button_pressed(joy_id, (JoyButton)p_button);
-};
+void XRNode3D::set_pose_name(const StringName p_pose_name) {
+ pose_name = p_pose_name;
-float XRController3D::get_joystick_axis(int p_axis) const {
- int joy_id = get_joystick_id();
- if (joy_id == -1) {
- return 0.0;
- };
+ // Update pose if we are bound to a tracker with a valid pose
+ Ref<XRPose> pose = get_pose();
+ if (pose.is_valid()) {
+ set_transform(pose->get_adjusted_transform());
+ }
+}
- return Input::get_singleton()->get_joy_axis(joy_id, (JoyAxis)p_axis);
-};
+StringName XRNode3D::get_pose_name() const {
+ return pose_name;
+}
-real_t XRController3D::get_rumble() const {
- // get our XRServer
+bool XRNode3D::get_is_active() const {
+ if (tracker.is_null()) {
+ return false;
+ } else if (!tracker->has_pose(pose_name)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool XRNode3D::get_has_tracking_data() const {
+ if (tracker.is_null()) {
+ return false;
+ } else if (!tracker->has_pose(pose_name)) {
+ return false;
+ } else {
+ return tracker->get_pose(pose_name)->get_has_tracking_data();
+ }
+}
+
+void XRNode3D::trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) {
+ // TODO need to link trackers to the interface that registered them so we can call this on the correct interface.
+ // For now this works fine as in 99% of the cases we only have our primary interface active
XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, 0.0);
+ if (xr_server != nullptr) {
+ Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ xr_interface->trigger_haptic_pulse(p_action_name, tracker_name, p_frequency, p_amplitude, p_duration_sec, p_delay_sec);
+ }
+ }
+}
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
- if (!tracker.is_valid()) {
- return 0.0;
- };
+Ref<XRPose> XRNode3D::get_pose() {
+ if (tracker.is_valid()) {
+ return tracker->get_pose(pose_name);
+ } else {
+ return Ref<XRPose>();
+ }
+}
- return tracker->get_rumble();
-};
+void XRNode3D::_bind_tracker() {
+ ERR_FAIL_COND_MSG(tracker.is_valid(), "Unbind the current tracker first");
-void XRController3D::set_rumble(real_t p_rumble) {
- // get our XRServer
XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL(xr_server);
+ if (xr_server != nullptr) {
+ tracker = xr_server->get_tracker(tracker_name);
+ if (tracker.is_null()) {
+ // It is possible and valid if the tracker isn't available (yet), in this case we just exit
+ return;
+ }
+
+ tracker->connect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
+
+ Ref<XRPose> pose = get_pose();
+ if (pose.is_valid()) {
+ set_transform(pose->get_adjusted_transform());
+ }
+ }
+}
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
+void XRNode3D::_unbind_tracker() {
if (tracker.is_valid()) {
- tracker->set_rumble(p_rumble);
- };
-};
+ tracker->disconnect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
-Ref<Mesh> XRController3D::get_mesh() const {
- return mesh;
+ tracker.unref();
+ }
}
-bool XRController3D::get_is_active() const {
- return is_active;
-};
+void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) {
+ if (p_tracker_name == p_tracker_name) {
+ // just in case unref our current tracker
+ _unbind_tracker();
-XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const {
- // get our XRServer
+ // get our new tracker
+ _bind_tracker();
+ }
+}
+
+void XRNode3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) {
+ if (p_tracker_name == p_tracker_name) {
+ // unref our tracker, it's no longer available
+ _unbind_tracker();
+ }
+}
+
+void XRNode3D::_pose_changed(const Ref<XRPose> &p_pose) {
+ if (p_pose.is_valid() && p_pose->get_name() == pose_name) {
+ set_transform(p_pose->get_adjusted_transform());
+ }
+}
+
+XRNode3D::XRNode3D() {
XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, XRPositionalTracker::TRACKER_HAND_UNKNOWN);
+ ERR_FAIL_NULL(xr_server);
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
- if (!tracker.is_valid()) {
- return XRPositionalTracker::TRACKER_HAND_UNKNOWN;
- };
+ xr_server->connect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
+}
- return tracker->get_tracker_hand();
-};
+XRNode3D::~XRNode3D() {
+ _unbind_tracker();
+
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL(xr_server);
+
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
+}
-TypedArray<String> XRController3D::get_configuration_warnings() const {
+TypedArray<String> XRNode3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
@@ -370,142 +424,179 @@ TypedArray<String> XRController3D::get_configuration_warnings() const {
warnings.push_back(TTR("XRController3D must have an XROrigin3D node as its parent."));
}
- if (controller_id == 0) {
- warnings.push_back(TTR("The controller ID must not be 0 or this controller won't be bound to an actual controller."));
+ if (tracker_name == "") {
+ warnings.push_back(TTR("No tracker name is set."));
+ }
+
+ if (pose_name == "") {
+ warnings.push_back(TTR("No pose is set."));
}
}
return warnings;
-};
+}
////////////////////////////////////////////////////////////////////////////////////////////////////
-void XRAnchor3D::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- set_process_internal(true);
- }; break;
- case NOTIFICATION_EXIT_TREE: {
- set_process_internal(false);
- }; break;
- case NOTIFICATION_INTERNAL_PROCESS: {
- // get our XRServer
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL(xr_server);
-
- // find the tracker for our anchor
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_ANCHOR, anchor_id);
- if (!tracker.is_valid()) {
- // this anchor is currently not available
- is_active = false;
- } else {
- is_active = true;
- Transform3D transform;
-
- // we'll need our world_scale
- real_t world_scale = xr_server->get_world_scale();
-
- // get our info from our tracker
- transform.basis = tracker->get_orientation();
- transform.origin = tracker->get_position(); // <-- already adjusted to world scale
-
- // our basis is scaled to the size of the plane the anchor is tracking
- // extract the size from our basis and reset the scale
- size = transform.basis.get_scale() * world_scale;
- transform.basis.orthonormalize();
-
- // apply our reference frame and set our transform
- set_transform(xr_server->get_reference_frame() * transform);
-
- // check for an updated mesh
- Ref<Mesh> trackerMesh = tracker->get_mesh();
- if (mesh != trackerMesh) {
- mesh = trackerMesh;
- emit_signal(SNAME("mesh_updated"), mesh);
- }
- };
- }; break;
- default:
- break;
- };
-};
-
-void XRAnchor3D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_anchor_id", "anchor_id"), &XRAnchor3D::set_anchor_id);
- ClassDB::bind_method(D_METHOD("get_anchor_id"), &XRAnchor3D::get_anchor_id);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_id", PROPERTY_HINT_RANGE, "0,32,1"), "set_anchor_id", "get_anchor_id");
- ClassDB::bind_method(D_METHOD("get_anchor_name"), &XRAnchor3D::get_anchor_name);
+void XRController3D::_bind_methods() {
+ // passthroughs to information about our related joystick
+ ClassDB::bind_method(D_METHOD("is_button_pressed", "name"), &XRController3D::is_button_pressed);
+ ClassDB::bind_method(D_METHOD("get_value", "name"), &XRController3D::get_value);
+ ClassDB::bind_method(D_METHOD("get_axis", "name"), &XRController3D::get_axis);
- ClassDB::bind_method(D_METHOD("get_is_active"), &XRAnchor3D::get_is_active);
- ClassDB::bind_method(D_METHOD("get_size"), &XRAnchor3D::get_size);
+ ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand);
- ClassDB::bind_method(D_METHOD("get_plane"), &XRAnchor3D::get_plane);
+ ClassDB::bind_method(D_METHOD("get_rumble"), &XRController3D::get_rumble);
+ ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRController3D::set_rumble);
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble");
+ ADD_PROPERTY_DEFAULT("rumble", 0.0);
- ClassDB::bind_method(D_METHOD("get_mesh"), &XRAnchor3D::get_mesh);
- ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")));
+ ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::STRING, "name")));
+ ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name")));
+ ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value")));
+ ADD_SIGNAL(MethodInfo("input_axis_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "value")));
};
-void XRAnchor3D::set_anchor_id(int p_anchor_id) {
- // We don't check any bounds here, this anchor may not yet be active and just be a place holder until it is.
- // Note that setting this to 0 means this node is not bound to an anchor yet.
- anchor_id = p_anchor_id;
- update_configuration_warnings();
-};
+void XRController3D::_bind_tracker() {
+ XRNode3D::_bind_tracker();
+ if (tracker.is_valid()) {
+ // bind to input signals
+ tracker->connect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
+ tracker->connect("button_released", callable_mp(this, &XRController3D::_button_released));
+ tracker->connect("input_value_changed", callable_mp(this, &XRController3D::_input_value_changed));
+ tracker->connect("input_axis_changed", callable_mp(this, &XRController3D::_input_axis_changed));
+ }
+}
-int XRAnchor3D::get_anchor_id() const {
- return anchor_id;
-};
+void XRController3D::_unbind_tracker() {
+ if (tracker.is_valid()) {
+ // unbind input signals
+ tracker->disconnect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
+ tracker->disconnect("button_released", callable_mp(this, &XRController3D::_button_released));
+ tracker->disconnect("input_value_changed", callable_mp(this, &XRController3D::_input_value_changed));
+ tracker->disconnect("input_axis_changed", callable_mp(this, &XRController3D::_input_axis_changed));
+ }
-Vector3 XRAnchor3D::get_size() const {
- return size;
-};
+ XRNode3D::_unbind_tracker();
+}
-String XRAnchor3D::get_anchor_name() const {
- // get our XRServer
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL_V(xr_server, String());
+void XRController3D::_button_pressed(const String &p_name) {
+ // just pass it on...
+ emit_signal("button_pressed", p_name);
+}
- Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_ANCHOR, anchor_id);
- if (!tracker.is_valid()) {
- return String("Not connected");
- };
+void XRController3D::_button_released(const String &p_name) {
+ // just pass it on...
+ emit_signal("button_released", p_name);
+}
- return tracker->get_tracker_name();
-};
+void XRController3D::_input_value_changed(const String &p_name, float p_value) {
+ // just pass it on...
+ emit_signal("input_value_changed", p_name, p_value);
+}
-bool XRAnchor3D::get_is_active() const {
- return is_active;
-};
+void XRController3D::_input_axis_changed(const String &p_name, Vector2 p_value) {
+ // just pass it on...
+ emit_signal("input_axis_changed", p_name, p_value);
+}
-TypedArray<String> XRAnchor3D::get_configuration_warnings() const {
- TypedArray<String> warnings = Node::get_configuration_warnings();
+bool XRController3D::is_button_pressed(const StringName &p_name) const {
+ if (tracker.is_valid()) {
+ // Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type
+ bool pressed = tracker->get_input(p_name);
+ return pressed;
+ } else {
+ return false;
+ }
+}
- if (is_visible() && is_inside_tree()) {
- // must be child node of XROrigin3D!
- XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin == nullptr) {
- warnings.push_back(TTR("XRAnchor3D must have an XROrigin3D node as its parent."));
- }
+float XRController3D::get_value(const StringName &p_name) const {
+ if (tracker.is_valid()) {
+ // Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
+ Variant input = tracker->get_input(p_name);
+ switch (input.get_type()) {
+ case Variant::BOOL: {
+ bool value = input;
+ return value ? 1.0 : 0.0;
+ } break;
+ case Variant::FLOAT: {
+ float value = input;
+ return value;
+ } break;
+ default:
+ return 0.0;
+ };
+ } else {
+ return 0.0;
+ }
+}
- if (anchor_id == 0) {
- warnings.push_back(TTR("The anchor ID must not be 0 or this anchor won't be bound to an actual anchor."));
+Vector2 XRController3D::get_axis(const StringName &p_name) const {
+ if (tracker.is_valid()) {
+ // Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
+ Variant input = tracker->get_input(p_name);
+ switch (input.get_type()) {
+ case Variant::BOOL: {
+ bool value = input;
+ return Vector2(value ? 1.0 : 0.0, 0.0);
+ } break;
+ case Variant::FLOAT: {
+ float value = input;
+ return Vector2(value, 0.0);
+ } break;
+ case Variant::VECTOR2: {
+ Vector2 axis = input;
+ return axis;
+ }
+ default:
+ return Vector2();
}
+ } else {
+ return Vector2();
}
+}
- return warnings;
-};
+real_t XRController3D::get_rumble() const {
+ if (!tracker.is_valid()) {
+ return 0.0;
+ }
+
+ return tracker->get_rumble();
+}
+
+void XRController3D::set_rumble(real_t p_rumble) {
+ if (tracker.is_valid()) {
+ tracker->set_rumble(p_rumble);
+ }
+}
+
+XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const {
+ // get our XRServer
+ if (!tracker.is_valid()) {
+ return XRPositionalTracker::TRACKER_HAND_UNKNOWN;
+ }
+
+ return tracker->get_tracker_hand();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void XRAnchor3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_size"), &XRAnchor3D::get_size);
+ ClassDB::bind_method(D_METHOD("get_plane"), &XRAnchor3D::get_plane);
+}
+
+Vector3 XRAnchor3D::get_size() const {
+ return size;
+}
Plane XRAnchor3D::get_plane() const {
Vector3 location = get_position();
Basis orientation = get_transform().basis;
- Plane plane(location, orientation.get_axis(1).normalized());
+ Plane plane(orientation.get_axis(1).normalized(), location);
return plane;
-};
-
-Ref<Mesh> XRAnchor3D::get_mesh() const {
- return mesh;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -525,23 +616,21 @@ TypedArray<String> XROrigin3D::get_configuration_warnings() const {
}
return warnings;
-};
+}
void XROrigin3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_world_scale", "world_scale"), &XROrigin3D::set_world_scale);
ClassDB::bind_method(D_METHOD("get_world_scale"), &XROrigin3D::get_world_scale);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale");
-};
+}
void XROrigin3D::set_tracked_camera(XRCamera3D *p_tracked_camera) {
tracked_camera = p_tracked_camera;
-};
+}
-void XROrigin3D::clear_tracked_camera_if(XRCamera3D *p_tracked_camera) {
- if (tracked_camera == p_tracked_camera) {
- tracked_camera = nullptr;
- };
-};
+XRCamera3D *XROrigin3D::get_tracked_camera() const {
+ return tracked_camera;
+}
real_t XROrigin3D::get_world_scale() const {
// get our XRServer
@@ -549,7 +638,7 @@ real_t XROrigin3D::get_world_scale() const {
ERR_FAIL_NULL_V(xr_server, 1.0);
return xr_server->get_world_scale();
-};
+}
void XROrigin3D::set_world_scale(real_t p_world_scale) {
// get our XRServer
@@ -557,7 +646,7 @@ void XROrigin3D::set_world_scale(real_t p_world_scale) {
ERR_FAIL_NULL(xr_server);
xr_server->set_world_scale(p_world_scale);
-};
+}
void XROrigin3D::_notification(int p_what) {
// get our XRServer
@@ -596,4 +685,4 @@ void XROrigin3D::_notification(int p_what) {
interface->notification(p_what);
}
}
-};
+}
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index 6e54ff83d7..5e7d06093d 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -45,8 +45,18 @@ class XRCamera3D : public Camera3D {
GDCLASS(XRCamera3D, Camera3D);
protected:
+ // The name and pose for our HMD tracker is currently the only hardcoded bit.
+ // If we ever are able to support multiple HMDs we may need to make this settable.
+ StringName tracker_name = "head";
+ StringName pose_name = "default";
+ Ref<XRPositionalTracker> tracker;
+
void _notification(int p_what);
+ void _changed_tracker(const StringName p_tracker_name, int p_tracker_type);
+ void _removed_tracker(const StringName p_tracker_name, int p_tracker_type);
+ void _pose_changed(const Ref<XRPose> &p_pose);
+
public:
TypedArray<String> get_configuration_warnings() const override;
@@ -55,48 +65,88 @@ public:
virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const override;
virtual Vector<Plane> get_frustum() const override;
- XRCamera3D() {}
- ~XRCamera3D() {}
+ XRCamera3D();
+ ~XRCamera3D();
};
/*
- XRController3D is a helper node that automatically updates its position based on tracker data.
+ XRNode3D is a helper node that implements binding to a tracker.
It must be a child node of our XROrigin node
*/
-class XRController3D : public Node3D {
- GDCLASS(XRController3D, Node3D);
+class XRNode3D : public Node3D {
+ GDCLASS(XRNode3D, Node3D);
private:
- int controller_id = 1;
+ StringName tracker_name;
+ StringName pose_name = "default";
bool is_active = true;
- int button_states = 0;
- Ref<Mesh> mesh;
protected:
- void _notification(int p_what);
+ Ref<XRPositionalTracker> tracker;
+
static void _bind_methods();
-public:
- void set_controller_id(int p_controller_id);
- int get_controller_id() const;
- String get_controller_name() const;
+ virtual void _bind_tracker();
+ virtual void _unbind_tracker();
+ void _changed_tracker(const StringName p_tracker_name, int p_tracker_type);
+ void _removed_tracker(const StringName p_tracker_name, int p_tracker_type);
- int get_joystick_id() const;
- bool is_button_pressed(int p_button) const;
- float get_joystick_axis(int p_axis) const;
+ void _pose_changed(const Ref<XRPose> &p_pose);
- real_t get_rumble() const;
- void set_rumble(real_t p_rumble);
+public:
+ virtual void _validate_property(PropertyInfo &property) const override;
+ void set_tracker(const StringName p_tracker_name);
+ StringName get_tracker() const;
+
+ void set_pose_name(const StringName p_pose);
+ StringName get_pose_name() const;
bool get_is_active() const;
- XRPositionalTracker::TrackerHand get_tracker_hand() const;
+ bool get_has_tracking_data() const;
- Ref<Mesh> get_mesh() const;
+ void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0);
+
+ Ref<XRPose> get_pose();
TypedArray<String> get_configuration_warnings() const override;
+ XRNode3D();
+ ~XRNode3D();
+};
+
+/*
+ XRController3D is a helper node that automatically updates its position based on tracker data.
+
+ It must be a child node of our XROrigin node
+*/
+
+class XRController3D : public XRNode3D {
+ GDCLASS(XRController3D, XRNode3D);
+
+private:
+protected:
+ static void _bind_methods();
+
+ virtual void _bind_tracker() override;
+ virtual void _unbind_tracker() override;
+
+ void _button_pressed(const String &p_name);
+ void _button_released(const String &p_name);
+ void _input_value_changed(const String &p_name, float p_value);
+ void _input_axis_changed(const String &p_name, Vector2 p_value);
+
+public:
+ bool is_button_pressed(const StringName &p_name) const;
+ float get_value(const StringName &p_name) const;
+ Vector2 get_axis(const StringName &p_name) const;
+
+ real_t get_rumble() const;
+ void set_rumble(real_t p_rumble);
+
+ XRPositionalTracker::TrackerHand get_tracker_hand() const;
+
XRController3D() {}
~XRController3D() {}
};
@@ -106,33 +156,19 @@ public:
It must be a child node of our XROrigin3D node
*/
-class XRAnchor3D : public Node3D {
- GDCLASS(XRAnchor3D, Node3D);
+class XRAnchor3D : public XRNode3D {
+ GDCLASS(XRAnchor3D, XRNode3D);
private:
- int anchor_id = 1;
- bool is_active = true;
Vector3 size;
- Ref<Mesh> mesh;
protected:
- void _notification(int p_what);
static void _bind_methods();
public:
- void set_anchor_id(int p_anchor_id);
- int get_anchor_id() const;
- String get_anchor_name() const;
-
- bool get_is_active() const;
Vector3 get_size() const;
-
Plane get_plane() const;
- Ref<Mesh> get_mesh() const;
-
- TypedArray<String> get_configuration_warnings() const override;
-
XRAnchor3D() {}
~XRAnchor3D() {}
};
@@ -159,7 +195,7 @@ public:
TypedArray<String> get_configuration_warnings() const override;
void set_tracked_camera(XRCamera3D *p_tracked_camera);
- void clear_tracked_camera_if(XRCamera3D *p_tracked_camera);
+ XRCamera3D *get_tracked_camera() const;
real_t get_world_scale() const;
void set_world_scale(real_t p_world_scale);
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
index 0167992baf..9a71e7bf55 100644
--- a/scene/animation/animation_blend_space_1d.cpp
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -81,14 +81,14 @@ void AnimationNodeBlendSpace1D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point);
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
}
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label");
}
void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) {
diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp
index 145e7c605b..e621f06ce9 100644
--- a/scene/animation/animation_blend_space_2d.cpp
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -30,6 +30,7 @@
#include "animation_blend_space_2d.h"
+#include "animation_blend_tree.h"
#include "core/math/geometry_2d.h"
void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
@@ -531,6 +532,12 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) {
if (new_closest != closest && new_closest != -1) {
float from = 0.0;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) {
+ //for ping-pong loop
+ Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[closest].node);
+ Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
+ if (!na_c.is_null() && !na_n.is_null()) {
+ na_n->set_backward(na_c->is_backward());
+ }
//see how much animation remains
from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, 0.0, FILTER_IGNORE, false);
}
@@ -639,21 +646,21 @@ void AnimationNodeBlendSpace2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_triangles"), &AnimationNodeBlendSpace2D::_update_triangles);
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_auto_triangles", "get_auto_triangles");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_triangles", "get_auto_triangles");
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
- ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
}
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NOEDITOR), "set_blend_mode", "get_blend_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_x_label", "get_x_label");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_y_label", "get_y_label");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
ADD_SIGNAL(MethodInfo("triangles_updated"));
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 10a66386eb..d6c5d0b51c 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -30,6 +30,7 @@
#include "animation_blend_tree.h"
+#include "scene/resources/animation.h"
#include "scene/scene_string_names.h"
void AnimationNodeAnimation::set_animation(const StringName &p_name) {
@@ -83,30 +84,55 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) {
}
Ref<Animation> anim = ap->get_animation(animation);
-
- double step;
+ double anim_size = (double)anim->get_length();
+ double step = 0.0;
+ double prev_time = time;
+ int pingponged = 0;
+ bool current_backward = signbit(p_time);
if (p_seek) {
+ step = p_time - time;
time = p_time;
- step = 0;
} else {
- time = MAX(0, time + p_time);
- step = p_time;
+ p_time *= backward ? -1.0 : 1.0;
+ if (!(time == anim_size && !current_backward) && !(time == 0 && current_backward)) {
+ time = time + p_time;
+ step = p_time;
+ }
}
- double anim_size = anim->get_length();
-
- if (anim->has_loop()) {
+ if (anim->get_loop_mode() == Animation::LoopMode::LOOP_PINGPONG) {
if (anim_size) {
- time = Math::fposmod(time, anim_size);
+ if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) {
+ if (prev_time > 0 && time <= 0) {
+ backward = !backward;
+ pingponged = -1;
+ }
+ if (prev_time < anim_size && time >= anim_size) {
+ backward = !backward;
+ pingponged = 1;
+ }
+ }
+ time = Math::pingpong(time, anim_size);
}
-
- } else if (time > anim_size) {
- time = anim_size;
+ } else {
+ if (anim->get_loop_mode() == Animation::LoopMode::LOOP_LINEAR) {
+ if (anim_size) {
+ time = Math::fposmod(time, anim_size);
+ }
+ } else if (time < 0) {
+ time = 0;
+ } else if (time > anim_size) {
+ time = anim_size;
+ }
+ backward = false;
}
- blend_animation(animation, time, step, p_seek, 1.0);
-
+ if (play_mode == PLAY_MODE_FORWARD) {
+ blend_animation(animation, time, step, p_seek, 1.0, pingponged);
+ } else {
+ blend_animation(animation, anim_size - time, -step, p_seek, 1.0, pingponged);
+ }
set_parameter(this->time, time);
return anim_size - time;
@@ -116,11 +142,34 @@ String AnimationNodeAnimation::get_caption() const {
return "Animation";
}
+void AnimationNodeAnimation::set_play_mode(PlayMode p_play_mode) {
+ play_mode = p_play_mode;
+}
+
+AnimationNodeAnimation::PlayMode AnimationNodeAnimation::get_play_mode() const {
+ return play_mode;
+}
+
+void AnimationNodeAnimation::set_backward(bool p_backward) {
+ backward = p_backward;
+}
+
+bool AnimationNodeAnimation::is_backward() const {
+ return backward;
+}
+
void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
+ ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode);
+ ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode");
+
+ BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD);
+ BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD);
}
AnimationNodeAnimation::AnimationNodeAnimation() {
@@ -533,7 +582,7 @@ AnimationNodeBlend3::AnimationNodeBlend3() {
/////////////////////////////////
void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const {
- r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"));
+ r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_lesser,or_greater"));
}
Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const {
@@ -1112,12 +1161,12 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons
for (const StringName &E : names) {
String name = E;
if (name != "output") {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
}
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
- p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
void AnimationNodeBlendTree::reset_state() {
@@ -1152,7 +1201,7 @@ void AnimationNodeBlendTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset);
ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset);
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset");
BIND_CONSTANT(CONNECTION_OK);
BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT);
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index 258443a999..e55dfb58ed 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -42,12 +42,12 @@ class AnimationNodeAnimation : public AnimationRootNode {
uint64_t last_version = 0;
bool skip = false;
-protected:
- void _validate_property(PropertyInfo &property) const override;
-
- static void _bind_methods();
-
public:
+ enum PlayMode {
+ PLAY_MODE_FORWARD,
+ PLAY_MODE_BACKWARD
+ };
+
void get_parameter_list(List<PropertyInfo> *r_list) const override;
static Vector<String> (*get_editable_animation_list)();
@@ -58,9 +58,25 @@ public:
void set_animation(const StringName &p_name);
StringName get_animation() const;
+ void set_play_mode(PlayMode p_play_mode);
+ PlayMode get_play_mode() const;
+
+ void set_backward(bool p_backward);
+ bool is_backward() const;
+
AnimationNodeAnimation();
+
+protected:
+ void _validate_property(PropertyInfo &property) const override;
+ static void _bind_methods();
+
+private:
+ PlayMode play_mode = PLAY_MODE_FORWARD;
+ bool backward = false;
};
+VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode)
+
class AnimationNodeOneShot : public AnimationNode {
GDCLASS(AnimationNodeOneShot, AnimationNode);
diff --git a/scene/animation/animation_cache.cpp b/scene/animation/animation_cache.cpp
deleted file mode 100644
index 56743007e4..0000000000
--- a/scene/animation/animation_cache.cpp
+++ /dev/null
@@ -1,314 +0,0 @@
-/*************************************************************************/
-/* animation_cache.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "animation_cache.h"
-
-void AnimationCache::_node_exit_tree(Node *p_node) {
- //it is one shot, so it disconnects upon arrival
-
- ERR_FAIL_COND(!connected_nodes.has(p_node));
-
- connected_nodes.erase(p_node);
-
- for (int i = 0; i < path_cache.size(); i++) {
- if (path_cache[i].node != p_node) {
- continue;
- }
-
- path_cache.write[i].valid = false; //invalidate path cache
- }
-}
-
-void AnimationCache::_animation_changed() {
- _clear_cache();
-}
-
-void AnimationCache::_clear_cache() {
- while (connected_nodes.size()) {
- connected_nodes.front()->get()->disconnect("tree_exiting", callable_mp(this, &AnimationCache::_node_exit_tree));
- connected_nodes.erase(connected_nodes.front());
- }
- path_cache.clear();
- cache_valid = false;
- cache_dirty = true;
-}
-
-void AnimationCache::_update_cache() {
- cache_valid = false;
-
- ERR_FAIL_COND(!root);
- ERR_FAIL_COND(!root->is_inside_tree());
- ERR_FAIL_COND(animation.is_null());
-
- for (int i = 0; i < animation->get_track_count(); i++) {
- NodePath np = animation->track_get_path(i);
-
- Node *node = root->get_node(np);
- if (!node) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(!node, "Invalid track path in animation '" + np + "'.");
- }
-
- Path path;
-
- Ref<Resource> res;
-
- if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM3D) {
-#ifndef _3D_DISABLED
- if (np.get_subname_count() > 1) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(animation->track_get_type(i) == Animation::TYPE_TRANSFORM3D, "Transform tracks can't have a subpath '" + np + "'.");
- }
-
- Node3D *sp = Object::cast_to<Node3D>(node);
-
- if (!sp) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(!sp, "Transform track not of type Node3D '" + np + "'.");
- }
-
- if (np.get_subname_count() == 1) {
- StringName property = np.get_subname(0);
- String ps = property;
-
- Skeleton3D *sk = Object::cast_to<Skeleton3D>(node);
- if (!sk) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(!sk, "Property defined in Transform track, but not a Skeleton! '" + np + "'.");
- }
-
- int idx = sk->find_bone(ps);
- if (idx == -1) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(idx == -1, "Property defined in Transform track, but not a Skeleton Bone! '" + np + "'.");
- }
-
- path.bone_idx = idx;
- path.skeleton = sk;
- }
-
- path.node_3d = sp;
-#endif // _3D_DISABLED
- } else {
- if (np.get_subname_count() > 0) {
- RES res2;
- Vector<StringName> leftover_subpath;
-
- // We don't want to cache the last resource unless it is a method call
- bool is_method = animation->track_get_type(i) == Animation::TYPE_METHOD;
- root->get_node_and_resource(np, res2, leftover_subpath, is_method);
-
- if (res2.is_valid()) {
- path.resource = res2;
- } else {
- path.node = node;
- }
- path.object = res2.is_valid() ? res2.ptr() : (Object *)node;
- path.subpath = leftover_subpath;
-
- } else {
- path.node = node;
- path.object = node;
- path.subpath = np.get_subnames();
- }
- }
-
- if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
- if (np.get_subname_count() == 0) {
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(np.get_subname_count() == 0, "Value Track lacks property: " + np + ".");
- }
-
- } else if (animation->track_get_type(i) == Animation::TYPE_METHOD) {
- if (path.subpath.size() != 0) { // Trying to call a method of a non-resource
-
- path_cache.push_back(Path());
- ERR_CONTINUE_MSG(path.subpath.size() != 0, "Method Track has property: " + np + ".");
- }
- }
-
- path.valid = true;
-
- path_cache.push_back(path);
-
- if (!connected_nodes.has(path.node)) {
- connected_nodes.insert(path.node);
- path.node->connect("tree_exiting", callable_mp(this, &AnimationCache::_node_exit_tree), Node::make_binds(path.node), CONNECT_ONESHOT);
- }
- }
-
- cache_dirty = false;
- cache_valid = true;
-}
-
-void AnimationCache::set_track_transform(int p_idx, const Transform3D &p_transform) {
- if (cache_dirty) {
- _update_cache();
- }
-
- ERR_FAIL_COND(!cache_valid);
- ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache.write[p_idx];
- if (!p.valid) {
- return;
- }
-
-#ifndef _3D_DISABLED
- ERR_FAIL_COND(!p.node);
- ERR_FAIL_COND(!p.node_3d);
-
- if (p.skeleton) {
- p.skeleton->set_bone_pose(p.bone_idx, p_transform);
- } else {
- p.node_3d->set_transform(p_transform);
- }
-#endif // _3D_DISABLED
-}
-
-void AnimationCache::set_track_value(int p_idx, const Variant &p_value) {
- if (cache_dirty) {
- _update_cache();
- }
-
- ERR_FAIL_COND(!cache_valid);
- ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache.write[p_idx];
- if (!p.valid) {
- return;
- }
-
- ERR_FAIL_COND(!p.object);
- p.object->set_indexed(p.subpath, p_value);
-}
-
-void AnimationCache::call_track(int p_idx, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- if (cache_dirty) {
- _update_cache();
- }
-
- ERR_FAIL_COND(!cache_valid);
- ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache.write[p_idx];
- if (!p.valid) {
- return;
- }
-
- ERR_FAIL_COND(!p.object);
- p.object->call(p_method, p_args, p_argcount, r_error);
-}
-
-void AnimationCache::set_all(float p_time, float p_delta) {
- if (cache_dirty) {
- _update_cache();
- }
-
- ERR_FAIL_COND(!cache_valid);
-
- int tc = animation->get_track_count();
- for (int i = 0; i < tc; i++) {
- switch (animation->track_get_type(i)) {
- case Animation::TYPE_TRANSFORM3D: {
- Vector3 loc, scale;
- Quaternion rot;
- animation->transform_track_interpolate(i, p_time, &loc, &rot, &scale);
- Transform3D tr(Basis(rot), loc);
- tr.basis.scale(scale);
-
- set_track_transform(i, tr);
-
- } break;
- case Animation::TYPE_VALUE: {
- if (animation->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (animation->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE && p_delta == 0)) {
- Variant v = animation->value_track_interpolate(i, p_time);
- set_track_value(i, v);
- } else {
- List<int> indices;
- animation->value_track_get_key_indices(i, p_time, p_delta, &indices);
-
- for (int &E : indices) {
- Variant v = animation->track_get_key_value(i, E);
- set_track_value(i, v);
- }
- }
-
- } break;
- case Animation::TYPE_METHOD: {
- List<int> indices;
- animation->method_track_get_key_indices(i, p_time, p_delta, &indices);
-
- for (int &E : indices) {
- Vector<Variant> args = animation->method_track_get_params(i, E);
- StringName name = animation->method_track_get_name(i, E);
- Callable::CallError err;
-
- if (!args.size()) {
- call_track(i, name, nullptr, 0, err);
- } else {
- Vector<const Variant *> argptrs;
- argptrs.resize(args.size());
- for (int j = 0; j < args.size(); j++) {
- argptrs.write[j] = &args.write[j];
- }
-
- call_track(i, name, (const Variant **)&argptrs[0], args.size(), err);
- }
- }
-
- } break;
- default: {
- }
- }
- }
-}
-
-void AnimationCache::set_animation(const Ref<Animation> &p_animation) {
- _clear_cache();
-
- if (animation.is_valid()) {
- animation->disconnect("changed", callable_mp(this, &AnimationCache::_animation_changed));
- }
-
- animation = p_animation;
-
- if (animation.is_valid()) {
- animation->connect("changed", callable_mp(this, &AnimationCache::_animation_changed));
- }
-}
-
-void AnimationCache::_bind_methods() {
-}
-
-void AnimationCache::set_root(Node *p_root) {
- _clear_cache();
- root = p_root;
-}
-
-AnimationCache::AnimationCache() {
-}
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index 81ecb50ea0..b5d7a0555e 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -909,14 +909,14 @@ void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) c
names.sort_custom<StringName::AlphCompare>();
for (const StringName &name : names) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
- p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::STRING_NAME, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::STRING_NAME, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
void AnimationNodeStateMachine::reset_state() {
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 2c8c4ee788..93339711bd 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -60,7 +60,12 @@ void AnimatedValuesBackup::restore() const {
if (entry->bone_idx == -1) {
entry->object->set_indexed(entry->subpath, entry->value);
} else {
- Object::cast_to<Skeleton3D>(entry->object)->set_bone_pose(entry->bone_idx, entry->value);
+ Array arr = entry->value;
+ if (arr.size() == 3) {
+ Object::cast_to<Skeleton3D>(entry->object)->set_bone_pose_position(entry->bone_idx, arr[0]);
+ Object::cast_to<Skeleton3D>(entry->object)->set_bone_pose_rotation(entry->bone_idx, arr[1]);
+ Object::cast_to<Skeleton3D>(entry->object)->set_bone_pose_scale(entry->bone_idx, arr[0]);
+ }
}
}
}
@@ -161,15 +166,17 @@ 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 {
List<PropertyInfo> anim_names;
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
- anim_names.push_back(PropertyInfo(Variant::OBJECT, "anims/" + String(E.key), PROPERTY_HINT_RESOURCE_TYPE, "Animation", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+ anim_names.push_back(PropertyInfo(Variant::OBJECT, "anims/" + String(E.key), PROPERTY_HINT_RESOURCE_TYPE, "Animation", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
if (E.value.next != StringName()) {
- anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
}
@@ -179,7 +186,7 @@ void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(E);
}
- p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
void AnimationPlayer::advance(float p_time) {
@@ -242,6 +249,8 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
p_anim->node_cache.resize(a->get_track_count());
+ setup_pass++;
+
for (int i = 0; i < a->get_track_count(); i++) {
p_anim->node_cache.write[i] = nullptr;
RES resource;
@@ -250,6 +259,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node
ObjectID id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id();
int bone_idx = -1;
+ int blend_shape_idx = -1;
#ifndef _3D_DISABLED
if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to<Skeleton3D>(child)) {
@@ -259,6 +269,22 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
continue;
}
}
+
+ if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) {
+ MeshInstance3D *mi_3d = Object::cast_to<MeshInstance3D>(child);
+ if (!mi_3d) {
+ continue;
+ }
+ if (a->track_get_path(i).get_subname_count() != 1) {
+ continue;
+ }
+
+ blend_shape_idx = mi_3d->find_blend_shape_by_name(a->track_get_path(i).get_subname(0));
+ if (blend_shape_idx == -1) {
+ continue;
+ }
+ }
+
#endif // _3D_DISABLED
{
@@ -270,51 +296,81 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
TrackNodeCacheKey key;
key.id = id;
key.bone_idx = bone_idx;
+ key.blend_shape_idx = blend_shape_idx;
if (!node_cache_map.has(key)) {
node_cache_map[key] = TrackNodeCache();
}
- p_anim->node_cache.write[i] = &node_cache_map[key];
- p_anim->node_cache[i]->path = a->track_get_path(i);
- p_anim->node_cache[i]->node = child;
- p_anim->node_cache[i]->resource = resource;
- p_anim->node_cache[i]->node_2d = Object::cast_to<Node2D>(child);
+ TrackNodeCache *node_cache = &node_cache_map[key];
+ p_anim->node_cache.write[i] = node_cache;
+
+ node_cache->path = a->track_get_path(i);
+ node_cache->node = child;
+ node_cache->resource = resource;
+ node_cache->node_2d = Object::cast_to<Node2D>(child);
#ifndef _3D_DISABLED
- if (a->track_get_type(i) == Animation::TYPE_TRANSFORM3D) {
+ if (a->track_get_type(i) == Animation::TYPE_POSITION_3D || a->track_get_type(i) == Animation::TYPE_ROTATION_3D || a->track_get_type(i) == Animation::TYPE_SCALE_3D) {
// special cases and caches for transform tracks
+ if (node_cache->last_setup_pass != setup_pass) {
+ node_cache->loc_used = false;
+ node_cache->rot_used = false;
+ node_cache->scale_used = false;
+ }
+
// cache node_3d
- p_anim->node_cache[i]->node_3d = Object::cast_to<Node3D>(child);
+ node_cache->node_3d = Object::cast_to<Node3D>(child);
// cache skeleton
- p_anim->node_cache[i]->skeleton = Object::cast_to<Skeleton3D>(child);
- if (p_anim->node_cache[i]->skeleton) {
+ node_cache->skeleton = Object::cast_to<Skeleton3D>(child);
+ if (node_cache->skeleton) {
if (a->track_get_path(i).get_subname_count() == 1) {
StringName bone_name = a->track_get_path(i).get_subname(0);
- p_anim->node_cache[i]->bone_idx = p_anim->node_cache[i]->skeleton->find_bone(bone_name);
- if (p_anim->node_cache[i]->bone_idx < 0) {
+ node_cache->bone_idx = node_cache->skeleton->find_bone(bone_name);
+ if (node_cache->bone_idx < 0) {
// broken track (nonexistent bone)
- p_anim->node_cache[i]->skeleton = nullptr;
- p_anim->node_cache[i]->node_3d = nullptr;
- ERR_CONTINUE(p_anim->node_cache[i]->bone_idx < 0);
+ node_cache->skeleton = nullptr;
+ node_cache->node_3d = nullptr;
+ ERR_CONTINUE(node_cache->bone_idx < 0);
}
} else {
// no property, just use spatialnode
- p_anim->node_cache[i]->skeleton = nullptr;
+ node_cache->skeleton = nullptr;
}
}
+
+ switch (a->track_get_type(i)) {
+ case Animation::TYPE_POSITION_3D: {
+ node_cache->loc_used = true;
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ node_cache->rot_used = true;
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ node_cache->scale_used = true;
+ } break;
+ default: {
+ }
+ }
+ }
+
+ if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) {
+ // special cases and caches for transform tracks
+ node_cache->node_blend_shape = Object::cast_to<MeshInstance3D>(child);
+ node_cache->blend_shape_idx = blend_shape_idx;
}
+
#endif // _3D_DISABLED
if (a->track_get_type(i) == Animation::TYPE_VALUE) {
- if (!p_anim->node_cache[i]->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
+ if (!node_cache->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
TrackNodeCache::PropertyAnim pa;
pa.subpath = leftover_path;
pa.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
pa.special = SP_NONE;
pa.owner = p_anim->node_cache[i];
- if (false && p_anim->node_cache[i]->node_2d) {
+ if (false && node_cache->node_2d) {
if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_pos) {
pa.special = SP_NODE2D_POS;
} else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_rot) {
@@ -323,29 +379,32 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
pa.special = SP_NODE2D_SCALE;
}
}
- p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
+ node_cache->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
}
}
if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) {
- if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
+ if (!node_cache->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
TrackNodeCache::BezierAnim ba;
ba.bezier_property = leftover_path;
ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
ba.owner = p_anim->node_cache[i];
- p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
+ node_cache->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
}
}
+
+ node_cache->last_setup_pass = setup_pass;
}
}
-void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, int p_pingponged) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
Animation *a = p_anim->animation.operator->();
bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint();
+ bool backward = signbit(p_delta);
for (int i = 0; i < a->get_track_count(); i++) {
// If an animation changes this animation (or it animates itself)
@@ -369,17 +428,15 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
}
switch (a->track_get_type(i)) {
- case Animation::TYPE_TRANSFORM3D: {
+ case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
if (!nc->node_3d) {
continue;
}
Vector3 loc;
- Quaternion rot;
- Vector3 scale;
- Error err = a->transform_track_interpolate(i, p_time, &loc, &rot, &scale);
+ Error err = a->position_track_interpolate(i, p_time, &loc);
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
if (err != OK) {
@@ -391,16 +448,92 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
cache_update[cache_update_size++] = nc;
nc->accum_pass = accum_pass;
nc->loc_accum = loc;
- nc->rot_accum = rot;
- nc->scale_accum = scale;
-
+ nc->rot_accum = Quaternion();
+ nc->scale_accum = Vector3();
} else {
nc->loc_accum = nc->loc_accum.lerp(loc, p_interp);
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+#ifndef _3D_DISABLED
+ if (!nc->node_3d) {
+ continue;
+ }
+
+ Quaternion rot;
+
+ Error err = a->rotation_track_interpolate(i, p_time, &rot);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (nc->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update[cache_update_size++] = nc;
+ nc->accum_pass = accum_pass;
+ nc->loc_accum = Vector3();
+ nc->rot_accum = rot;
+ nc->scale_accum = Vector3();
+ } else {
nc->rot_accum = nc->rot_accum.slerp(rot, p_interp);
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+#ifndef _3D_DISABLED
+ if (!nc->node_3d) {
+ continue;
+ }
+
+ Vector3 scale;
+
+ Error err = a->scale_track_interpolate(i, p_time, &scale);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (nc->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update[cache_update_size++] = nc;
+ nc->accum_pass = accum_pass;
+ nc->loc_accum = Vector3();
+ nc->rot_accum = Quaternion();
+ nc->scale_accum = scale;
+ } else {
nc->scale_accum = nc->scale_accum.lerp(scale, p_interp);
}
#endif // _3D_DISABLED
} break;
+ case Animation::TYPE_BLEND_SHAPE: {
+#ifndef _3D_DISABLED
+ if (!nc->node_blend_shape) {
+ continue;
+ }
+
+ float blend;
+
+ Error err = a->blend_shape_track_interpolate(i, p_time, &blend);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ if (err != OK) {
+ continue;
+ }
+
+ if (nc->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
+ nc->accum_pass = accum_pass;
+ cache_update[cache_update_size++] = nc;
+ nc->blend_shape_accum = blend;
+ } else {
+ nc->blend_shape_accum = Math::lerp(nc->blend_shape_accum, blend, p_interp);
+ }
+#endif // _3D_DISABLED
+ } break;
case Animation::TYPE_VALUE: {
if (!nc->node) {
continue;
@@ -425,8 +558,8 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
continue; //eeh not worth it
}
- float first_key_time = a->track_get_key_time(i, 0);
- float transition = 1.0;
+ double first_key_time = a->track_get_key_time(i, 0);
+ double transition = 1.0;
int first_key = 0;
if (first_key_time == 0.0) {
@@ -434,13 +567,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (key_count == 1) {
continue; //with one key we can't do anything
}
- transition = a->track_get_key_transition(i, 0);
+ transition = (double)a->track_get_key_transition(i, 0);
first_key_time = a->track_get_key_time(i, 1);
first_key = 1;
}
if (p_time < first_key_time) {
- float c = Math::ease(p_time / first_key_time, transition);
+ double c = Math::ease(p_time / first_key_time, transition);
Variant first_value = a->track_get_key_value(i, first_key);
Variant interp_value;
Variant::interpolate(pa->capture, first_value, c, interp_value);
@@ -482,7 +615,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else if (p_is_current && p_delta != 0) {
List<int> indices;
- a->value_track_get_key_indices(i, p_time, p_delta, &indices);
+ a->value_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged);
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
@@ -541,7 +674,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
List<int> indices;
- a->method_track_get_key_indices(i, p_time, p_delta, &indices);
+ a->method_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged);
for (int &E : indices) {
StringName method = a->method_track_get_name(i, E);
@@ -596,7 +729,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
TrackNodeCache::BezierAnim *ba = &E->get();
- float bezier = a->bezier_track_interpolate(i, p_time);
+ real_t bezier = a->bezier_track_interpolate(i, p_time);
if (ba->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
cache_update_bezier[cache_update_bezier_size++] = ba;
@@ -657,7 +790,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -685,12 +818,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
nc->audio_start = p_time;
}
} else if (nc->audio_playing) {
- bool loop = a->has_loop();
+ bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE;
bool stop = false;
- if (!loop && p_time < nc->audio_start) {
- stop = true;
+ if (!loop) {
+ if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
+ stop = true;
+ }
} else if (nc->audio_len > 0) {
float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
@@ -731,12 +866,23 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
Ref<Animation> anim = player->get_animation(anim_name);
- double at_anim_pos;
+ double at_anim_pos = 0.0;
- if (anim->has_loop()) {
- at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop
- } else {
- at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end
+ switch (anim->get_loop_mode()) {
+ case Animation::LoopMode::LOOP_NONE: {
+ at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end
+ } break;
+
+ case Animation::LoopMode::LOOP_LINEAR: {
+ at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop
+ } break;
+
+ case Animation::LoopMode::LOOP_PINGPONG: {
+ at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length());
+ } break;
+
+ default:
+ break;
}
if (player->is_playing() || p_seeked) {
@@ -751,7 +897,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -781,46 +927,73 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
double next_pos = cd.pos + delta;
real_t len = cd.from->animation->get_length();
- bool loop = cd.from->animation->has_loop();
+ int pingponged = 0;
+
+ switch (cd.from->animation->get_loop_mode()) {
+ case Animation::LoopMode::LOOP_NONE: {
+ if (next_pos < 0) {
+ next_pos = 0;
+ } else if (next_pos > len) {
+ next_pos = len;
+ }
- if (!loop) {
- if (next_pos < 0) {
- next_pos = 0;
- } else if (next_pos > len) {
- next_pos = len;
- }
+ bool backwards = signbit(delta); // Negative zero means playing backwards too
+ delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here)
- bool backwards = signbit(delta); // Negative zero means playing backwards too
- delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here)
+ if (&cd == &playback.current) {
+ if (!backwards && cd.pos <= len && next_pos == len) {
+ //playback finished
+ end_reached = true;
+ end_notify = cd.pos < len; // Notify only if not already at the end
+ }
- if (&cd == &playback.current) {
- if (!backwards && cd.pos <= len && next_pos == len) {
- //playback finished
- end_reached = true;
- end_notify = cd.pos < len; // Notify only if not already at the end
+ if (backwards && cd.pos >= 0 && next_pos == 0) {
+ //playback finished
+ end_reached = true;
+ end_notify = cd.pos > 0; // Notify only if not already at the beginning
+ }
}
+ } break;
- if (backwards && cd.pos >= 0 && next_pos == 0) {
- //playback finished
- end_reached = true;
- end_notify = cd.pos > 0; // Notify only if not already at the beginning
+ case Animation::LoopMode::LOOP_LINEAR: {
+ double looped_next_pos = Math::fposmod(next_pos, (double)len);
+ if (looped_next_pos == 0 && next_pos != 0) {
+ // Loop multiples of the length to it, rather than 0
+ // so state at time=length is previewable in the editor
+ next_pos = len;
+ } else {
+ next_pos = looped_next_pos;
}
- }
+ } break;
- } else {
- double looped_next_pos = Math::fposmod(next_pos, (double)len);
- if (looped_next_pos == 0 && next_pos != 0) {
- // Loop multiples of the length to it, rather than 0
- // so state at time=length is previewable in the editor
- next_pos = len;
- } else {
- next_pos = looped_next_pos;
- }
+ case Animation::LoopMode::LOOP_PINGPONG: {
+ if ((int)Math::floor(abs(next_pos - cd.pos) / len) % 2 == 0) {
+ if (next_pos < 0 && cd.pos >= 0) {
+ cd.speed_scale *= -1.0;
+ pingponged = -1;
+ }
+ if (next_pos > len && cd.pos <= len) {
+ cd.speed_scale *= -1.0;
+ pingponged = 1;
+ }
+ }
+ double looped_next_pos = Math::pingpong(next_pos, (double)len);
+ if (looped_next_pos == 0 && next_pos != 0) {
+ // Loop multiples of the length to it, rather than 0
+ // so state at time=length is previewable in the editor
+ next_pos = len;
+ } else {
+ next_pos = looped_next_pos;
+ }
+ } break;
+
+ default:
+ break;
}
cd.pos = next_pos;
- _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started);
+ _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, pingponged);
}
void AnimationPlayer::_animation_process2(double p_delta, bool p_started) {
@@ -855,15 +1028,32 @@ void AnimationPlayer::_animation_update_transforms() {
TrackNodeCache *nc = cache_update[i];
ERR_CONTINUE(nc->accum_pass != accum_pass);
-
- t.origin = nc->loc_accum;
- t.basis.set_quaternion_scale(nc->rot_accum, nc->scale_accum);
#ifndef _3D_DISABLED
if (nc->skeleton && nc->bone_idx >= 0) {
- nc->skeleton->set_bone_pose(nc->bone_idx, t);
+ if (nc->loc_used) {
+ nc->skeleton->set_bone_pose_position(nc->bone_idx, nc->loc_accum);
+ }
+ if (nc->rot_used) {
+ nc->skeleton->set_bone_pose_rotation(nc->bone_idx, nc->rot_accum);
+ }
+ if (nc->scale_used) {
+ nc->skeleton->set_bone_pose_scale(nc->bone_idx, nc->scale_accum);
+ }
+
+ } else if (nc->node_blend_shape) {
+ nc->node_blend_shape->set_blend_shape_value(nc->blend_shape_idx, nc->blend_shape_accum);
} else if (nc->node_3d) {
- nc->node_3d->set_transform(t);
+ if (nc->loc_used) {
+ nc->node_3d->set_position(nc->loc_accum);
+ }
+ if (nc->rot_used) {
+ nc->node_3d->set_rotation(nc->rot_accum.get_euler());
+ }
+ if (nc->scale_used) {
+ nc->node_3d->set_scale(nc->scale_accum);
+ }
}
+
#endif // _3D_DISABLED
}
}
@@ -1527,7 +1717,12 @@ Ref<AnimatedValuesBackup> AnimationPlayer::backup_animated_values(Node *p_root_o
AnimatedValuesBackup::Entry entry;
entry.object = nc->skeleton;
entry.bone_idx = nc->bone_idx;
- entry.value = nc->skeleton->get_bone_pose(nc->bone_idx);
+ Array arr;
+ arr.resize(3);
+ arr[0] = nc->skeleton->get_bone_pose_position(nc->bone_idx);
+ arr[1] = nc->skeleton->get_bone_pose_rotation(nc->bone_idx);
+ arr[2] = nc->skeleton->get_bone_pose_scale(nc->bone_idx);
+ entry.value = nc;
backup->entries.push_back(entry);
} else {
if (nc->node_3d) {
@@ -1659,7 +1854,7 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ANIMATE_AS_TRIGGER), "set_current_animation", "get_current_animation");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_assigned_animation", "get_assigned_animation");
- ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_autoplay", "get_autoplay");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position");
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index b693e29bdf..ea04918988 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -32,6 +32,7 @@
#define ANIMATION_PLAYER_H
#include "scene/2d/node_2d.h"
+#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
@@ -88,6 +89,8 @@ private:
SP_NODE2D_SCALE,
};
+ uint32_t setup_pass = 1;
+
struct TrackNodeCache {
NodePath path;
uint32_t id = 0;
@@ -97,13 +100,20 @@ private:
#ifndef _3D_DISABLED
Node3D *node_3d = nullptr;
Skeleton3D *skeleton = nullptr;
+ MeshInstance3D *node_blend_shape = nullptr;
+ int blend_shape_idx = -1;
#endif // _3D_DISABLED
int bone_idx = -1;
// accumulated transforms
+ bool loc_used = false;
+ bool rot_used = false;
+ bool scale_used = false;
+
Vector3 loc_accum;
Quaternion rot_accum;
Vector3 scale_accum;
+ float blend_shape_accum = 0;
uint64_t accum_pass = 0;
bool audio_playing = false;
@@ -134,16 +144,22 @@ private:
Map<StringName, BezierAnim> bezier_anim;
+ uint32_t last_setup_pass = 0;
TrackNodeCache() {}
};
struct TrackNodeCacheKey {
ObjectID id;
int bone_idx = -1;
+ int blend_shape_idx = -1;
inline bool operator<(const TrackNodeCacheKey &p_right) const {
if (id == p_right.id) {
- return bone_idx < p_right.bone_idx;
+ if (blend_shape_idx == p_right.blend_shape_idx) {
+ return bone_idx < p_right.bone_idx;
+ } else {
+ return blend_shape_idx < p_right.blend_shape_idx;
+ }
} else {
return id < p_right.id;
}
@@ -215,7 +231,7 @@ private:
NodePath root;
- void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false);
+ void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, int p_pingponged = 0);
void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr);
void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started);
@@ -285,7 +301,6 @@ public:
void set_current_animation(const String &p_anim);
String get_assigned_animation() const;
void set_assigned_animation(const String &p_anim);
- void stop_all();
void set_active(bool p_active);
bool is_active() const;
bool is_valid() const;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 9ca8d478b1..37e754148c 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -32,6 +32,7 @@
#include "animation_blend_tree.h"
#include "core/config/engine.h"
+#include "scene/resources/animation.h"
#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
@@ -87,7 +88,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
}
}
-void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend) {
+void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged) {
ERR_FAIL_COND(!state);
ERR_FAIL_COND(!state->player->has_animation(p_animation));
@@ -113,6 +114,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time
anim_state.time = p_time;
anim_state.animation = animation;
anim_state.seeked = p_seeked;
+ anim_state.pingponged = p_pingponged;
state->animation_states.push_back(anim_state);
}
@@ -418,15 +420,15 @@ void AnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters);
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
- ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation);
+ ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0));
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter);
ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter);
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_filter_enabled", "is_filter_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters");
GDVIRTUAL_BIND(_get_child_nodes);
GDVIRTUAL_BIND(_get_parameter_list);
@@ -544,13 +546,18 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
NodePath path = anim->track_get_path(i);
Animation::TrackType track_type = anim->track_get_type(i);
+ Animation::TrackType track_cache_type = track_type;
+ if (track_cache_type == Animation::TYPE_POSITION_3D || track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) {
+ track_cache_type = Animation::TYPE_POSITION_3D; //reference them as position3D tracks, even if they modify rotation or scale
+ }
+
TrackCache *track = nullptr;
if (track_cache.has(path)) {
track = track_cache.get(path);
}
//if not valid, delete track
- if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == nullptr)) {
+ if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) {
playing_caches.erase(track);
memdelete(track);
track_cache.erase(path);
@@ -587,7 +594,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
track = track_value;
} break;
- case Animation::TYPE_TRANSFORM3D: {
+ case Animation::TYPE_POSITION_3D:
+ case Animation::TYPE_ROTATION_3D:
+ case Animation::TYPE_SCALE_3D: {
#ifndef _3D_DISABLED
Node3D *node_3d = Object::cast_to<Node3D>(child);
@@ -597,6 +606,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
}
TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
+ track_xform->type = Animation::TYPE_POSITION_3D;
track_xform->node_3d = node_3d;
track_xform->skeleton = nullptr;
@@ -615,8 +625,54 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
track_xform->object_id = track_xform->object->get_instance_id();
track = track_xform;
+
+ switch (track_type) {
+ case Animation::TYPE_POSITION_3D: {
+ track_xform->loc_used = true;
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ track_xform->rot_used = true;
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ track_xform->scale_used = true;
+ } break;
+ default: {
+ }
+ }
+
#endif // _3D_DISABLED
} break;
+ case Animation::TYPE_BLEND_SHAPE: {
+#ifndef _3D_DISABLED
+
+ if (path.get_subname_count() != 1) {
+ ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'");
+ continue;
+ }
+ MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child);
+
+ if (!mesh_3d) {
+ ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'");
+ continue;
+ }
+
+ StringName blend_shape_name = path.get_subname(0);
+ int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name);
+ if (blend_shape_idx == -1) {
+ ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'");
+ continue;
+ }
+
+ TrackCacheBlendShape *track_bshape = memnew(TrackCacheBlendShape);
+
+ track_bshape->mesh_3d = mesh_3d;
+ track_bshape->shape_index = blend_shape_idx;
+
+ track_bshape->object = mesh_3d;
+ track_bshape->object_id = mesh_3d->get_instance_id();
+ track = track_bshape;
+#endif
+ } break;
case Animation::TYPE_METHOD: {
TrackCacheMethod *track_method = memnew(TrackCacheMethod);
@@ -670,6 +726,26 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
}
track_cache[path] = track;
+ } else if (track_cache_type == Animation::TYPE_POSITION_3D) {
+ TrackCacheTransform *track_xform = static_cast<TrackCacheTransform *>(track);
+ if (track->setup_pass != setup_pass) {
+ track_xform->loc_used = false;
+ track_xform->rot_used = false;
+ track_xform->scale_used = false;
+ }
+ switch (track_type) {
+ case Animation::TYPE_POSITION_3D: {
+ track_xform->loc_used = true;
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ track_xform->rot_used = true;
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ track_xform->scale_used = true;
+ } break;
+ default: {
+ }
+ }
}
track->setup_pass = setup_pass;
@@ -824,6 +900,10 @@ void AnimationTree::_process_graph(real_t p_delta) {
double delta = as.delta;
real_t weight = as.blend;
bool seeked = as.seeked;
+ int pingponged = as.pingponged;
+#ifndef _3D_DISABLED
+ bool backward = signbit(delta);
+#endif // _3D_DISABLED
for (int i = 0; i < a->get_track_count(); i++) {
NodePath path = a->track_get_path(i);
@@ -831,8 +911,11 @@ void AnimationTree::_process_graph(real_t p_delta) {
ERR_CONTINUE(!track_cache.has(path));
TrackCache *track = track_cache[path];
- if (track->type != a->track_get_type(i)) {
- continue; //may happen should not
+
+ Animation::TrackType ttype = a->track_get_type(i);
+ if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
+ //broken animation, but avoid error spamming
+ continue;
}
track->root_motion = root_motion_track == path;
@@ -848,84 +931,192 @@ void AnimationTree::_process_graph(real_t p_delta) {
continue; //nothing to blend
}
- switch (track->type) {
- case Animation::TYPE_TRANSFORM3D: {
+ switch (ttype) {
+ case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+ if (t->process_pass != process_pass) {
+ t->process_pass = process_pass;
+ t->loc = Vector3();
+ t->rot = Quaternion();
+ t->rot_blend_accum = 0;
+ t->scale = Vector3(1, 1, 1);
+ }
+
if (track->root_motion) {
- if (t->process_pass != process_pass) {
- t->process_pass = process_pass;
- t->loc = Vector3();
- t->rot = Quaternion();
- t->rot_blend_accum = 0;
- t->scale = Vector3(1, 1, 1);
+ double prev_time = time - delta;
+ if (!backward) {
+ if (prev_time < 0) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = 0;
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
+ } else {
+ if (prev_time > a->get_length()) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = (double)a->get_length();
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
}
- real_t prev_time = time - delta;
- if (prev_time < 0) {
- if (!a->has_loop()) {
+ Vector3 loc[2];
+
+ if (!backward) {
+ if (prev_time > time) {
+ Error err = a->position_track_interpolate(i, prev_time, &loc[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->position_track_interpolate(i, (double)a->get_length(), &loc[1]);
+ t->loc += (loc[1] - loc[0]) * blend;
+ prev_time = 0;
+ }
+ } else {
+ if (prev_time < time) {
+ Error err = a->position_track_interpolate(i, prev_time, &loc[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->position_track_interpolate(i, 0, &loc[1]);
+ t->loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
- } else {
- prev_time = a->get_length() + prev_time;
}
}
- Vector3 loc[2];
- Quaternion rot[2];
- Vector3 scale[2];
+ Error err = a->position_track_interpolate(i, prev_time, &loc[0]);
+ if (err != OK) {
+ continue;
+ }
- if (prev_time > time) {
- Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
- if (err != OK) {
- continue;
- }
+ a->position_track_interpolate(i, time, &loc[1]);
+ t->loc += (loc[1] - loc[0]) * blend;
+ prev_time = !backward ? 0 : (double)a->get_length();
- a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]);
+ } else {
+ Vector3 loc;
- t->loc += (loc[1] - loc[0]) * blend;
- t->scale += (scale[1] - scale[0]) * blend;
- Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
- t->rot = (t->rot * q).normalized();
+ Error err = a->position_track_interpolate(i, time, &loc);
+ if (err != OK) {
+ continue;
+ }
- prev_time = 0;
+ t->loc = t->loc.lerp(loc, blend);
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+#ifndef _3D_DISABLED
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ if (t->process_pass != process_pass) {
+ t->process_pass = process_pass;
+ t->loc = Vector3();
+ t->rot = Quaternion();
+ t->rot_blend_accum = 0;
+ t->scale = Vector3(1, 1, 1);
+ }
+
+ if (track->root_motion) {
+ double prev_time = time - delta;
+ if (!backward) {
+ if (prev_time < 0) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = 0;
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
+ } else {
+ if (prev_time > a->get_length()) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = (double)a->get_length();
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
}
- Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
+ Quaternion rot[2];
+
+ if (!backward) {
+ if (prev_time > time) {
+ Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
+ Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
+ t->rot = (t->rot * q).normalized();
+ prev_time = 0;
+ }
+ } else {
+ if (prev_time < time) {
+ Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->rotation_track_interpolate(i, 0, &rot[1]);
+ Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
+ t->rot = (t->rot * q).normalized();
+ prev_time = 0;
+ }
+ }
+
+ Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]);
if (err != OK) {
continue;
}
- a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]);
-
- t->loc += (loc[1] - loc[0]) * blend;
- t->scale += (scale[1] - scale[0]) * blend;
+ a->rotation_track_interpolate(i, time, &rot[1]);
Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
t->rot = (t->rot * q).normalized();
-
- prev_time = 0;
+ prev_time = !backward ? 0 : (double)a->get_length();
} else {
- Vector3 loc;
Quaternion rot;
- Vector3 scale;
-
- Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale);
- //ERR_CONTINUE(err!=OK); //used for testing, should be removed
-
- if (t->process_pass != process_pass) {
- t->process_pass = process_pass;
- t->loc = loc;
- t->rot = rot;
- t->rot_blend_accum = 0;
- t->scale = scale;
- }
+ Error err = a->rotation_track_interpolate(i, time, &rot);
if (err != OK) {
continue;
}
- t->loc = t->loc.lerp(loc, blend);
if (t->rot_blend_accum == 0) {
t->rot = rot;
t->rot_blend_accum = blend;
@@ -934,10 +1125,124 @@ void AnimationTree::_process_graph(real_t p_delta) {
t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized();
t->rot_blend_accum = rot_total;
}
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+#ifndef _3D_DISABLED
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ if (t->process_pass != process_pass) {
+ t->process_pass = process_pass;
+ t->loc = Vector3();
+ t->rot = Quaternion();
+ t->rot_blend_accum = 0;
+ t->scale = Vector3(1, 1, 1);
+ }
+
+ if (track->root_motion) {
+ double prev_time = time - delta;
+ if (!backward) {
+ if (prev_time < 0) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = 0;
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
+ } else {
+ if (prev_time > a->get_length()) {
+ switch (a->get_loop_mode()) {
+ case Animation::LOOP_NONE: {
+ prev_time = (double)a->get_length();
+ } break;
+ case Animation::LOOP_LINEAR: {
+ prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ } break;
+ case Animation::LOOP_PINGPONG: {
+ prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+
+ Vector3 scale[2];
+
+ if (!backward) {
+ if (prev_time > time) {
+ Error err = a->scale_track_interpolate(i, prev_time, &scale[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
+ t->scale += (scale[1] - scale[0]) * blend;
+ prev_time = 0;
+ }
+ } else {
+ if (prev_time < time) {
+ Error err = a->scale_track_interpolate(i, prev_time, &scale[0]);
+ if (err != OK) {
+ continue;
+ }
+ a->scale_track_interpolate(i, 0, &scale[1]);
+ t->scale += (scale[1] - scale[0]) * blend;
+ prev_time = 0;
+ }
+ }
+
+ Error err = a->scale_track_interpolate(i, prev_time, &scale[0]);
+ if (err != OK) {
+ continue;
+ }
+
+ a->scale_track_interpolate(i, time, &scale[1]);
+ t->scale += (scale[1] - scale[0]) * blend;
+ prev_time = !backward ? 0 : (double)a->get_length();
+
+ } else {
+ Vector3 scale;
+
+ Error err = a->scale_track_interpolate(i, time, &scale);
+ if (err != OK) {
+ continue;
+ }
+
t->scale = t->scale.lerp(scale, blend);
}
#endif // _3D_DISABLED
} break;
+ case Animation::TYPE_BLEND_SHAPE: {
+#ifndef _3D_DISABLED
+ TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
+
+ if (t->process_pass != process_pass) {
+ t->process_pass = process_pass;
+ t->value = 0;
+ }
+
+ float value;
+
+ Error err = a->blend_shape_track_interpolate(i, time, &value);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ if (err != OK) {
+ continue;
+ }
+
+ t->value = Math::lerp(t->value, value, blend);
+
+#endif // _3D_DISABLED
+ } break;
case Animation::TYPE_VALUE: {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
@@ -960,7 +1265,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
} else {
List<int> indices;
- a->value_track_get_key_indices(i, time, delta, &indices);
+ a->value_track_get_key_indices(i, time, delta, &indices, pingponged);
for (int &F : indices) {
Variant value = a->track_get_key_value(i, F);
@@ -977,7 +1282,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
List<int> indices;
- a->method_track_get_key_indices(i, time, delta, &indices);
+ a->method_track_get_key_indices(i, time, delta, &indices, pingponged);
for (int &F : indices) {
StringName method = a->method_track_get_name(i, F);
@@ -1060,7 +1365,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -1088,12 +1393,20 @@ void AnimationTree::_process_graph(real_t p_delta) {
t->start = time;
}
} else if (t->playing) {
- bool loop = a->has_loop();
+ bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE;
bool stop = false;
- if (!loop && time < t->start) {
- stop = true;
+ if (!loop) {
+ if (delta > 0) {
+ if (time < t->start) {
+ stop = true;
+ }
+ } else if (delta < 0) {
+ if (time > t->start) {
+ stop = true;
+ }
+ }
} else if (t->len > 0) {
real_t len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
@@ -1127,7 +1440,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
continue;
}
- if (delta == 0 || seeked) {
+ if (seeked) {
//seek
int idx = a->track_find_key(i, time);
if (idx < 0) {
@@ -1143,12 +1456,20 @@ void AnimationTree::_process_graph(real_t p_delta) {
Ref<Animation> anim = player2->get_animation(anim_name);
- real_t at_anim_pos;
-
- if (anim->has_loop()) {
- at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop
- } else {
- at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end
+ real_t at_anim_pos = 0.0;
+
+ switch (anim->get_loop_mode()) {
+ case Animation::LoopMode::LOOP_NONE: {
+ at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end
+ } break;
+ case Animation::LoopMode::LOOP_LINEAR: {
+ at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop
+ } break;
+ case Animation::LoopMode::LOOP_PINGPONG: {
+ at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
+ } break;
+ default:
+ break;
}
if (player2->is_playing() || seeked) {
@@ -1163,7 +1484,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
} else {
//find stuff to play
List<int> to_play;
- a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
if (to_play.size()) {
int idx = to_play.back()->get();
@@ -1198,26 +1519,47 @@ void AnimationTree::_process_graph(real_t p_delta) {
}
switch (track->type) {
- case Animation::TYPE_TRANSFORM3D: {
+ case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
- Transform3D xform;
- xform.origin = t->loc;
-
- xform.basis.set_quaternion_scale(t->rot, t->scale);
-
if (t->root_motion) {
+ Transform3D xform;
+ xform.origin = t->loc;
+ xform.basis.set_quaternion_scale(t->rot, t->scale);
+
root_motion_transform = xform;
- if (t->skeleton && t->bone_idx >= 0) {
- root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse();
- }
} else if (t->skeleton && t->bone_idx >= 0) {
- t->skeleton->set_bone_pose(t->bone_idx, xform);
+ if (t->loc_used) {
+ t->skeleton->set_bone_pose_position(t->bone_idx, t->loc);
+ }
+ if (t->rot_used) {
+ t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot);
+ }
+ if (t->scale_used) {
+ t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale);
+ }
} else if (!t->skeleton) {
- t->node_3d->set_transform(xform);
+ if (t->loc_used) {
+ t->node_3d->set_position(t->loc);
+ }
+ if (t->rot_used) {
+ t->node_3d->set_rotation(t->rot.get_euler());
+ }
+ if (t->scale_used) {
+ t->node_3d->set_scale(t->scale);
+ }
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_BLEND_SHAPE: {
+#ifndef _3D_DISABLED
+ TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
+
+ if (t->mesh_3d) {
+ t->mesh_3d->set_blend_shape_value(t->shape_index, t->value);
}
#endif // _3D_DISABLED
} break;
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index 1e0267682e..6fc051fa41 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -57,8 +57,6 @@ public:
Vector<Input> inputs;
- real_t process_input(int p_input, real_t p_time, bool p_seek, real_t p_blend);
-
friend class AnimationTree;
struct AnimationState {
@@ -68,6 +66,7 @@ public:
const Vector<real_t> *track_blends = nullptr;
real_t blend = 0.0;
bool seeked = false;
+ int pingponged = 0;
};
struct State {
@@ -85,7 +84,6 @@ public:
State *state = nullptr;
real_t _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, real_t p_time, bool p_seek, const Vector<StringName> &p_connections);
- void _pre_update_animations(HashMap<NodePath, int> *track_map);
//all this is temporary
StringName base_path;
@@ -101,17 +99,16 @@ public:
real_t _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr);
protected:
- void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend);
+ void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0);
real_t blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
real_t blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
+
void make_invalid(const String &p_reason);
static void _bind_methods();
void _validate_property(PropertyInfo &property) const override;
- void _set_parent(Object *p_parent);
-
GDVIRTUAL0RC(Dictionary, _get_child_nodes)
GDVIRTUAL0RC(Array, _get_parameter_list)
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)
@@ -197,16 +194,26 @@ private:
Skeleton3D *skeleton = nullptr;
#endif // _3D_DISABLED
int bone_idx = -1;
+ bool loc_used = false;
+ bool rot_used = false;
+ bool scale_used = false;
Vector3 loc;
Quaternion rot;
real_t rot_blend_accum = 0.0;
Vector3 scale;
TrackCacheTransform() {
- type = Animation::TYPE_TRANSFORM3D;
+ type = Animation::TYPE_POSITION_3D;
}
};
+ struct TrackCacheBlendShape : public TrackCache {
+ MeshInstance3D *mesh_3d = nullptr;
+ float value = 0;
+ int shape_index = -1;
+ TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
+ };
+
struct TrackCacheValue : public TrackCache {
Variant value;
Vector<StringName> subpath;
@@ -255,7 +262,6 @@ private:
AnimationNode::State state;
bool cache_valid = false;
void _node_removed(Node *p_node);
- void _caches_cleared();
void _clear_caches();
bool _update_caches(AnimationPlayer *player);
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index c43b83747b..da933ae02d 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -291,7 +291,7 @@ bool Tween::step(float p_delta) {
float temp_delta = rem_delta;
// Turns to true if any Tweener returns true (i.e. is still not finished).
step_active = tweener->step(temp_delta) || step_active;
- step_delta = MIN(temp_delta, rem_delta);
+ step_delta = MIN(temp_delta, step_delta);
}
rem_delta = step_delta;
@@ -626,7 +626,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("parallel"), &Tween::parallel);
ClassDB::bind_method(D_METHOD("chain"), &Tween::chain);
- ClassDB::bind_method(D_METHOD("interpolate_value", "trans_type", "ease_type", "elapsed_time", "initial_value", "delta_value", "duration"), &Tween::interpolate_variant);
+ ClassDB::bind_method(D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &Tween::interpolate_variant);
ADD_SIGNAL(MethodInfo("step_finished", PropertyInfo(Variant::INT, "idx")));
ADD_SIGNAL(MethodInfo("loop_finished", PropertyInfo(Variant::INT, "loop_count")));
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 0334ba667b..43c4ce4c82 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -45,7 +45,6 @@ void AudioStreamPlayer::_notification(int p_what) {
Vector<Ref<AudioStreamPlayback>> playbacks_to_remove;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) {
- emit_signal(SNAME("finished"));
playbacks_to_remove.push_back(playback);
}
}
@@ -58,6 +57,9 @@ void AudioStreamPlayer::_notification(int p_what) {
active.clear();
set_process_internal(false);
}
+ if (!playbacks_to_remove.is_empty()) {
+ emit_signal(SNAME("finished"));
+ }
}
if (p_what == NOTIFICATION_EXIT_TREE) {
@@ -135,7 +137,7 @@ void AudioStreamPlayer::play(float p_from_pos) {
Ref<AudioStreamPlayback> stream_playback = stream->instance_playback();
ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
- AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos);
+ AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos, pitch_scale);
stream_playbacks.push_back(stream_playback);
active.set();
set_process_internal(true);
@@ -292,6 +294,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/base_button.cpp b/scene/gui/base_button.cpp
index bef1011364..9f712ed478 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -62,7 +62,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mouse_button = p_event;
bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo();
- bool button_masked = mouse_button.is_valid() && ((1 << (mouse_button->get_button_index() - 1)) & button_mask) != 0;
+ bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE;
if (button_masked || ui_accept) {
on_action_event(p_event);
return;
@@ -313,11 +313,11 @@ BaseButton::ActionMode BaseButton::get_action_mode() const {
return action_mode;
}
-void BaseButton::set_button_mask(int p_mask) {
+void BaseButton::set_button_mask(MouseButton p_mask) {
button_mask = p_mask;
}
-int BaseButton::get_button_mask() const {
+MouseButton BaseButton::get_button_mask() const {
return button_mask;
}
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index cd6e78464f..3ea59c3ff9 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -45,7 +45,7 @@ public:
};
private:
- int button_mask = MOUSE_BUTTON_MASK_LEFT;
+ MouseButton button_mask = MouseButton::MASK_LEFT;
bool toggle_mode = false;
bool shortcut_in_tooltip = true;
bool keep_pressed_outside = false;
@@ -118,8 +118,8 @@ public:
void set_keep_pressed_outside(bool p_on);
bool is_keep_pressed_outside() const;
- void set_button_mask(int p_mask);
- int get_button_mask() const;
+ void set_button_mask(MouseButton p_mask);
+ MouseButton get_button_mask() const;
void set_shortcut(const Ref<Shortcut> &p_shortcut);
Ref<Shortcut> get_shortcut() const;
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index cf2df4e1a2..cb9f13e970 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -354,7 +354,7 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control
add_child(l, false, INTERNAL_MODE_FRONT);
MarginContainer *mc = memnew(MarginContainer);
mc->add_theme_constant_override("margin_left", 0);
- mc->add_child(p_control);
+ mc->add_child(p_control, true);
add_child(mc, false, INTERNAL_MODE_FRONT);
if (p_expand) {
mc->set_v_size_flags(SIZE_EXPAND_FILL);
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/code_edit.cpp b/scene/gui/code_edit.cpp
index da54903871..324b21c6d0 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -129,7 +129,7 @@ void CodeEdit::_notification(int p_what) {
}
const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0;
- const int code_completion_base_width = font->get_string_size(code_completion_base).width;
+ const int code_completion_base_width = font->get_string_size(code_completion_base, font_size).width;
if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) {
code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width;
} else {
@@ -263,19 +263,19 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
switch (mb->get_button_index()) {
- case MOUSE_BUTTON_WHEEL_UP: {
+ case MouseButton::WHEEL_UP: {
if (code_completion_current_selected > 0) {
code_completion_current_selected--;
update();
}
} break;
- case MOUSE_BUTTON_WHEEL_DOWN: {
+ case MouseButton::WHEEL_DOWN: {
if (code_completion_current_selected < code_completion_options.size() - 1) {
code_completion_current_selected++;
update();
}
} break;
- case MOUSE_BUTTON_LEFT: {
+ case MouseButton::LEFT: {
code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1);
if (mb->is_double_click()) {
confirm_code_completion();
@@ -300,7 +300,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
int line = pos.y;
int col = pos.x;
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
if (is_line_folded(line)) {
int wrap_index = get_line_wrap_index_at_column(line, col);
if (wrap_index == get_line_wrap_count(line)) {
@@ -314,7 +314,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
} else {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_command_pressed() && symbol_lookup_word != String()) {
Vector2i mpos = mb->get_position();
if (is_layout_rtl()) {
@@ -340,7 +340,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (symbol_lookup_on_click_enabled) {
- if (mm->is_command_pressed() && mm->get_button_mask() == 0 && !is_dragging_cursor()) {
+ if (mm->is_command_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) {
symbol_lookup_new_word = get_word_at_pos(mpos);
if (symbol_lookup_new_word != symbol_lookup_word) {
emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word);
@@ -360,9 +360,9 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
/* Ctrl + Hover symbols */
#ifdef OSX_ENABLED
- if (k->get_keycode() == KEY_META) {
+ if (k->get_keycode() == Key::META) {
#else
- if (k->get_keycode() == KEY_CTRL) {
+ if (k->get_keycode() == Key::CTRL) {
#endif
if (symbol_lookup_on_click_enabled) {
if (k->is_pressed() && !is_dragging_cursor()) {
@@ -378,7 +378,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
/* If a modifier has been pressed, and nothing else, return. */
- if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) {
+ if (!k->is_pressed() || k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) {
return;
}
@@ -1146,7 +1146,7 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
}
void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
- if (draw_breakpoints) {
+ if (draw_breakpoints && breakpoint_icon.is_valid()) {
bool hovering = p_region.has_point(get_local_mouse_pos());
bool breakpointed = is_line_breakpointed(p_line);
@@ -1162,7 +1162,7 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
}
}
- if (draw_bookmarks && is_line_bookmarked(p_line)) {
+ if (draw_bookmarks && is_line_bookmarked(p_line) && bookmark_icon.is_valid()) {
int horizontal_padding = p_region.size.x / 2;
int vertical_padding = p_region.size.y / 4;
@@ -1172,7 +1172,7 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
bookmark_icon->draw_rect(get_canvas_item(), bookmark_region, false, bookmark_color);
}
- if (draw_executing_lines && is_line_executing(p_line)) {
+ if (draw_executing_lines && is_line_executing(p_line) && executing_line_icon.is_valid()) {
int horizontal_padding = p_region.size.x / 10;
int vertical_padding = p_region.size.y / 4;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 894175d559..5a378554c9 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -582,7 +582,7 @@ void ColorPicker::_update_text_value() {
void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseButton> mb = p_event;
- if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
if (rect_old.has_point(mb->get_position())) {
// Revert to the old color when left-clicking the old color sample.
@@ -827,13 +827,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
Ref<InputEventMouseButton> bev = p_event;
if (bev.is_valid()) {
- if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
Vector2 center = c->get_size() / 2.0;
if (picker_type == SHAPE_VHS_CIRCLE) {
real_t dist = center.distance_to(bev->get_position());
if (dist <= center.x) {
- real_t rad = bev->get_position().angle_to_point(center);
+ real_t rad = center.angle_to_point(bev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
s = CLAMP(dist / center.x, 0, 1);
} else {
@@ -850,7 +850,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
real_t dist = center.distance_to(bev->get_position());
if (dist >= center.x * 0.84 && dist <= center.x) {
- real_t rad = bev->get_position().angle_to_point(center);
+ real_t rad = center.angle_to_point(bev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
spinning = true;
} else {
@@ -875,7 +875,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
if (!deferred_mode_enabled) {
emit_signal(SNAME("color_changed"), color);
}
- } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
emit_signal(SNAME("color_changed"), color);
changing_color = false;
spinning = false;
@@ -895,12 +895,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
Vector2 center = c->get_size() / 2.0;
if (picker_type == SHAPE_VHS_CIRCLE) {
real_t dist = center.distance_to(mev->get_position());
- real_t rad = mev->get_position().angle_to_point(center);
+ real_t rad = center.angle_to_point(mev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
s = CLAMP(dist / center.x, 0, 1);
} else {
if (spinning) {
- real_t rad = mev->get_position().angle_to_point(center);
+ real_t rad = center.angle_to_point(mev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
} else {
real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0;
@@ -929,7 +929,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> bev = p_event;
if (bev.is_valid()) {
- if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
changing_color = true;
float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height);
if (picker_type == SHAPE_VHS_CIRCLE) {
@@ -946,7 +946,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
_update_color();
if (!deferred_mode_enabled) {
emit_signal(SNAME("color_changed"), color);
- } else if (!bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
emit_signal(SNAME("color_changed"), color);
}
}
@@ -977,11 +977,11 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_c
Ref<InputEventMouseButton> bev = p_event;
if (bev.is_valid()) {
- if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
set_pick_color(p_color);
_update_color();
emit_signal(SNAME("color_changed"), p_color);
- } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) {
+ } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) {
erase_preset(p_color);
emit_signal(SNAME("preset_removed"), p_color);
}
@@ -994,7 +994,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseButton> bev = p_event;
- if (bev.is_valid() && bev->get_button_index() == MOUSE_BUTTON_LEFT && !bev->is_pressed()) {
+ if (bev.is_valid() && bev->get_button_index() == MouseButton::LEFT && !bev->is_pressed()) {
emit_signal(SNAME("color_changed"), color);
screen->hide();
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 38da40a402..582c8e5860 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -73,8 +73,8 @@ Dictionary Control::_edit_get_state() const {
void Control::_edit_set_state(const Dictionary &p_state) {
ERR_FAIL_COND((p_state.size() <= 0) ||
- !p_state.has("rotation") || !p_state.has("scale") ||
- !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets"));
+ !p_state.has("rotation") || !p_state.has("scale") ||
+ !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets"));
Dictionary state = p_state;
set_rotation(state["rotation"]);
@@ -400,7 +400,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_NONE, "", usage));
+ p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater", usage));
}
}
{
@@ -637,7 +637,9 @@ void Control::_notification(int p_notification) {
}
} else {
//is a regular root control or top_level
- data.RI = get_viewport()->_gui_add_root_control(this);
+ Viewport *viewport = get_viewport();
+ ERR_FAIL_COND(!viewport);
+ data.RI = viewport->_gui_add_root_control(this);
}
data.parent_canvas_item = get_parent_item();
@@ -646,7 +648,9 @@ void Control::_notification(int p_notification) {
data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed));
} else {
//connect viewport
- get_viewport()->connect("size_changed", callable_mp(this, &Control::_size_changed));
+ Viewport *viewport = get_viewport();
+ ERR_FAIL_COND(!viewport);
+ viewport->connect("size_changed", callable_mp(this, &Control::_size_changed));
}
} break;
case NOTIFICATION_EXIT_CANVAS: {
@@ -655,7 +659,9 @@ void Control::_notification(int p_notification) {
data.parent_canvas_item = nullptr;
} else if (!is_set_as_top_level()) {
//disconnect viewport
- get_viewport()->disconnect("size_changed", callable_mp(this, &Control::_size_changed));
+ Viewport *viewport = get_viewport();
+ ERR_FAIL_COND(!viewport);
+ viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed));
}
if (data.RI) {
@@ -740,7 +746,7 @@ bool Control::has_point(const Point2 &p_point) const {
return Rect2(Point2(), get_size()).has_point(p_point);
}
-void Control::set_drag_forwarding(Node *p_target) {
+void Control::set_drag_forwarding(Object *p_target) {
if (p_target) {
data.drag_owner = p_target->get_instance_id();
} else {
@@ -979,7 +985,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String
Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const Ref<Font> *font = data.font_override.getptr(p_name);
- if (font) {
+ if (font && (*font)->get_data_count() > 0) {
return *font;
}
}
@@ -992,7 +998,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const int *font_size = data.font_size_override.getptr(p_name);
- if (font_size) {
+ if (font_size && (*font_size) > 0) {
return *font_size;
}
}
@@ -2580,16 +2586,6 @@ void Control::warp_mouse(const Point2 &p_to_pos) {
}
bool Control::is_text_field() const {
- /*
- if (get_script_instance()) {
- Variant v=p_point;
- const Variant *p[2]={&v,&p_data};
- Callable::CallError ce;
- Variant ret = get_script_instance()->call("is_text_field",p,2,ce);
- if (ce.error==Callable::CallError::CALL_OK)
- return ret;
- }
- */
return false;
}
diff --git a/scene/gui/control.h b/scene/gui/control.h
index be692b6a0c..02ab336ef0 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -233,9 +233,6 @@ private:
static constexpr unsigned properties_managed_by_container_count = 11;
static String properties_managed_by_container[properties_managed_by_container_count];
- // used internally
- Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform);
-
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest);
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
@@ -250,7 +247,6 @@ private:
void _update_minimum_size();
void _clear_size_warning();
- void _update_scroll();
void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]);
void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]);
@@ -355,7 +351,7 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
virtual bool has_point(const Point2 &p_point) const;
- virtual void set_drag_forwarding(Node *p_target);
+ virtual void set_drag_forwarding(Object *p_target);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
virtual void drop_data(const Point2 &p_point, const Variant &p_data);
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index 71d2778cc3..f66d3f6835 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -44,7 +44,7 @@
void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> key = p_event;
- if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) {
+ if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
_cancel_pressed();
}
}
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 973b72973d..44853fc006 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);
}
@@ -104,7 +110,7 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) {
bool handled = true;
switch (k->get_keycode()) {
- case KEY_H: {
+ case Key::H: {
if (k->is_command_pressed()) {
set_show_hidden_files(!show_hidden_files);
} else {
@@ -112,10 +118,10 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) {
}
} break;
- case KEY_F5: {
+ case Key::F5: {
invalidate();
} break;
- case KEY_BACKSPACE: {
+ case Key::BACKSPACE: {
_dir_submitted("..");
} break;
default: {
@@ -342,7 +348,7 @@ bool FileDialog::_is_open_should_be_disabled() {
// Opening a file, but selected a folder? Forbidden.
return ((mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden.
- (mode == FILE_MODE_OPEN_DIR && !d["dir"]);
+ (mode == FILE_MODE_OPEN_DIR && !d["dir"]);
}
void FileDialog::_go_up() {
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 5d024d3be7..ae15b021a5 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -39,6 +39,10 @@ GradientEdit::GradientEdit() {
picker = memnew(ColorPicker);
popup->add_child(picker);
+ gradient_cache.instantiate();
+ preview_texture.instantiate();
+
+ preview_texture->set_width(1024);
add_child(popup, false, INTERNAL_MODE_FRONT);
}
@@ -47,7 +51,7 @@ int GradientEdit::_get_point_from_pos(int x) {
int total_w = get_size().width - get_size().height - draw_spacing;
float min_distance = 1e20;
for (int i = 0; i < points.size(); i++) {
- //Check if we clicked at point
+ // Check if we clicked at point.
float distance = ABS(x - points[i].offset * total_w);
float min = (draw_point_width / 2 * 1.7); //make it easier to grab
if (distance <= min && distance < min_distance) {
@@ -84,7 +88,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && grabbed != -1) {
+ if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) {
points.remove(grabbed);
grabbed = -1;
grabbing = false;
@@ -94,15 +98,15 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseButton> mb = p_event;
- //Show color picker on double click.
- if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_double_click() && mb->is_pressed()) {
+ // Show color picker on double click.
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) {
grabbed = _get_point_from_pos(mb->get_position().x);
_show_color_picker();
accept_event();
}
- //Delete point on right click
- if (mb.is_valid() && mb->get_button_index() == 2 && mb->is_pressed()) {
+ // Delete point on right click.
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
grabbed = _get_point_from_pos(mb->get_position().x);
if (grabbed != -1) {
points.remove(grabbed);
@@ -114,20 +118,20 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
- //Hold alt key to duplicate selected color
- if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->is_alt_pressed()) {
+ // Hold alt key to duplicate selected color.
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && mb->is_alt_pressed()) {
int x = mb->get_position().x;
grabbed = _get_point_from_pos(x);
if (grabbed != -1) {
int total_w = get_size().width - get_size().height - draw_spacing;
- Gradient::Point newPoint = points[grabbed];
- newPoint.offset = CLAMP(x / float(total_w), 0, 1);
+ Gradient::Point new_point = points[grabbed];
+ new_point.offset = CLAMP(x / float(total_w), 0, 1);
- points.push_back(newPoint);
+ points.push_back(new_point);
points.sort();
for (int i = 0; i < points.size(); ++i) {
- if (points[i].offset == newPoint.offset) {
+ if (points[i].offset == new_point.offset) {
grabbed = i;
break;
}
@@ -138,8 +142,8 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
- //select
- if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
+ // Select.
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
update();
int x = mb->get_position().x;
int total_w = get_size().width - get_size().height - draw_spacing;
@@ -158,16 +162,16 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- //insert
- Gradient::Point newPoint;
- newPoint.offset = CLAMP(x / float(total_w), 0, 1);
+ // Insert point.
+ Gradient::Point new_point;
+ new_point.offset = CLAMP(x / float(total_w), 0, 1);
Gradient::Point prev;
Gradient::Point next;
int pos = -1;
for (int i = 0; i < points.size(); i++) {
- if (points[i].offset < newPoint.offset) {
+ if (points[i].offset < new_point.offset) {
pos = i;
}
}
@@ -191,12 +195,12 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
prev = points[pos];
}
- newPoint.color = prev.color.lerp(next.color, (newPoint.offset - prev.offset) / (next.offset - prev.offset));
+ new_point.color = prev.color.lerp(next.color, (new_point.offset - prev.offset) / (next.offset - prev.offset));
- points.push_back(newPoint);
+ points.push_back(new_point);
points.sort();
for (int i = 0; i < points.size(); i++) {
- if (points[i].offset == newPoint.offset) {
+ if (points[i].offset == new_point.offset) {
grabbed = i;
break;
}
@@ -205,7 +209,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("ramp_changed"));
}
- if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
if (grabbing) {
grabbing = false;
emit_signal(SNAME("ramp_changed"));
@@ -223,7 +227,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) {
float newofs = CLAMP(x / float(total_w), 0, 1);
// Snap to "round" coordinates if holding Ctrl.
- // Be more precise if holding Shift as well
+ // Be more precise if holding Shift as well.
if (mm->is_ctrl_pressed()) {
newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1);
} else if (mm->is_shift_pressed()) {
@@ -299,57 +303,22 @@ void GradientEdit::_notification(int p_what) {
int h = get_size().y;
if (w == 0 || h == 0) {
- return; //Safety check. We have division by 'h'. And in any case there is nothing to draw with such size
+ return; // Safety check. We have division by 'h'. And in any case there is nothing to draw with such size.
}
int total_w = get_size().width - get_size().height - draw_spacing;
- //Draw checker pattern for ramp
+ // Draw checker pattern for ramp.
draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(0, 0, total_w, h), true);
- //Draw color ramp
- Gradient::Point prev;
- prev.offset = 0;
- if (points.size() == 0) {
- prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points
- } else {
- prev.color = points[0].color; //Extend color of first point to the beginning.
- }
+ // Draw color ramp.
- for (int i = -1; i < points.size(); i++) {
- Gradient::Point next;
- //If there is no next point
- if (i + 1 == points.size()) {
- if (points.size() == 0) {
- next.color = Color(0, 0, 0); //Draw black rectangle if we have no points
- } else {
- next.color = points[i].color; //Extend color of last point to the end.
- }
- next.offset = 1;
- } else {
- next = points[i + 1];
- }
+ gradient_cache->set_points(points);
+ gradient_cache->set_interpolation_mode(interpolation_mode);
+ preview_texture->set_gradient(gradient_cache);
+ draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h));
- if (prev.offset == next.offset) {
- prev = next;
- continue;
- }
-
- Vector<Vector2> points;
- Vector<Color> colors;
- points.push_back(Vector2(prev.offset * total_w, h));
- points.push_back(Vector2(prev.offset * total_w, 0));
- points.push_back(Vector2(next.offset * total_w, 0));
- points.push_back(Vector2(next.offset * total_w, h));
- colors.push_back(prev.color);
- colors.push_back(prev.color);
- colors.push_back(next.color);
- colors.push_back(next.color);
- draw_primitive(points, colors, Vector<Point2>());
- prev = next;
- }
-
- //Draw point markers
+ // Draw point markers.
for (int i = 0; i < points.size(); i++) {
Color col = points[i].color.inverted();
col.a = 0.9;
@@ -383,7 +352,7 @@ void GradientEdit::_notification(int p_what) {
draw_line(Vector2(total_w + draw_spacing, h), Vector2(total_w + draw_spacing + h, 0), Color(1, 1, 1, 0.6));
}
- //Draw borders around color ramp if in focus
+ // Draw borders around color ramp if in focus.
if (has_focus()) {
draw_line(Vector2(-1, -1), Vector2(total_w + 1, -1), Color(1, 1, 1, 0.6));
draw_line(Vector2(total_w + 1, -1), Vector2(total_w + 1, h + 1), Color(1, 1, 1, 0.6));
@@ -448,12 +417,21 @@ void GradientEdit::set_points(Vector<Gradient::Point> &p_points) {
}
points.clear();
points = p_points;
+ points.sort();
}
Vector<Gradient::Point> &GradientEdit::get_points() {
return points;
}
+void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+ interpolation_mode = p_interp_mode;
+}
+
+Gradient::InterpolationMode GradientEdit::get_interpolation_mode() {
+ return interpolation_mode;
+}
+
void GradientEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("ramp_changed"));
}
diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h
index f3a39daaf6..66b60d87c7 100644
--- a/scene/gui/gradient_edit.h
+++ b/scene/gui/gradient_edit.h
@@ -45,6 +45,10 @@ class GradientEdit : public Control {
bool grabbing = false;
int grabbed = -1;
Vector<Gradient::Point> points;
+ Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR;
+
+ Ref<Gradient> gradient_cache;
+ Ref<GradientTexture1D> preview_texture;
// Make sure to use the scaled value below.
const int BASE_SPACING = 3;
@@ -69,6 +73,9 @@ public:
Vector<Color> get_colors() const;
void set_points(Vector<Gradient::Point> &p_points);
Vector<Gradient::Point> &get_points();
+ void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode);
+ Gradient::InterpolationMode get_interpolation_mode();
+
virtual Size2 get_minimum_size() const override;
GradientEdit();
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 35e31be9af..e7d98a686f 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -150,7 +150,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
Ref<InputEventMouseMotion> mm = p_ev;
- if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
is_pressing = true;
@@ -501,6 +501,43 @@ void GraphEdit::_notification(int p_what) {
}
}
+void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) {
+ Rect2 comment_node_rect = p_node->get_rect();
+ Vector<GraphNode *> enclosed_nodes;
+
+ for (int i = 0; i < get_child_count(); i++) {
+ GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
+ if (!gn || gn->is_selected()) {
+ continue;
+ }
+
+ Rect2 node_rect = gn->get_rect();
+ bool included = comment_node_rect.encloses(node_rect);
+ if (included) {
+ enclosed_nodes.push_back(gn);
+ }
+ }
+
+ p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes);
+}
+
+void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) {
+ for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) {
+ p_comment_enclosed_nodes[p_node->get_name()][i]->set_drag(p_drag);
+ }
+}
+
+void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_drag_accum) {
+ for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) {
+ Vector2 pos = (p_comment_enclosed_nodes[p_node->get_name()][i]->get_drag_from() * zoom + drag_accum) / zoom;
+ if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ const int snap = get_snap();
+ pos = pos.snapped(Vector2(snap, snap));
+ }
+ p_comment_enclosed_nodes[p_node->get_name()][i]->set_position_offset(pos);
+ }
+}
+
bool GraphEdit::_filter_input(const Point2 &p_point) {
Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
Vector2i port_size = Vector2i(port->get_width(), port->get_height());
@@ -531,7 +568,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
- if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
Vector2i port_size = Vector2i(port->get_width(), port->get_height());
@@ -678,7 +715,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
}
}
- if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
if (connecting_valid) {
if (connecting && connecting_target) {
String from = connecting_from;
@@ -1031,7 +1068,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventMouseMotion> mm = p_ev;
- if (mm.is_valid() && (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) {
+ if (mm.is_valid() && ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && Input::get_singleton()->is_key_pressed(Key::SPACE)))) {
Vector2i relative = Input::get_singleton()->warp_mouse_motion(mm, get_global_rect());
h_scroll->set_value(h_scroll->get_value() - relative.x);
v_scroll->set_value(v_scroll->get_value() - relative.y);
@@ -1052,12 +1089,15 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
- if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
+ if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
const int snap = get_snap();
pos = pos.snapped(Vector2(snap, snap));
}
gn->set_position_offset(pos);
+ if (gn->is_comment()) {
+ _set_position_of_comment_enclosed_nodes(gn, comment_enclosed_nodes, drag_accum);
+ }
}
}
}
@@ -1101,7 +1141,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> b = p_ev;
if (b.is_valid()) {
- if (b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) {
+ if (b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
if (box_selecting) {
box_selecting = false;
for (int i = get_child_count() - 1; i >= 0; i--) {
@@ -1131,8 +1171,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
}
- if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && dragging) {
- if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
+ if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && dragging) {
+ if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) {
//deselect current node
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -1153,6 +1193,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (gn && gn->is_selected()) {
gn->set_drag(false);
+ if (gn->is_comment()) {
+ _set_drag_comment_enclosed_nodes(gn, comment_enclosed_nodes, false);
+ }
}
}
}
@@ -1170,7 +1213,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
connections_layer->update();
}
- if (b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) {
+ if (b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
GraphNode *gn = nullptr;
for (int i = get_child_count() - 1; i >= 0; i--) {
@@ -1196,7 +1239,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
dragging = true;
drag_accum = Vector2();
just_selected = !gn->is_selected();
- if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
+ if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
for (int i = 0; i < get_child_count(); i++) {
GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i));
if (o_gn) {
@@ -1220,6 +1263,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
if (o_gn->is_selected()) {
o_gn->set_drag(true);
+ if (o_gn->is_comment()) {
+ _update_comment_enclosed_nodes_list(o_gn, comment_enclosed_nodes);
+ _set_drag_comment_enclosed_nodes(o_gn, comment_enclosed_nodes, true);
+ }
}
}
@@ -1227,7 +1274,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
if (_filter_input(b->get_position())) {
return;
}
- if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) {
+ if (Input::get_singleton()->is_key_pressed(Key::SPACE)) {
return;
}
@@ -1272,7 +1319,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
}
- if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && box_selecting) {
+ if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && box_selecting) {
box_selecting = false;
box_selecting_rect = Rect2();
previous_selected.clear();
@@ -1280,7 +1327,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
minimap->update();
}
- int scroll_direction = (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) - (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP);
+ int scroll_direction = (b->get_button_index() == MouseButton::WHEEL_DOWN) - (b->get_button_index() == MouseButton::WHEEL_UP);
if (scroll_direction != 0) {
if (b->is_ctrl_pressed()) {
if (b->is_shift_pressed()) {
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 44e50aa3c2..6c11f9df6a 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -217,6 +217,11 @@ private:
Set<int> valid_left_disconnect_types;
Set<int> valid_right_disconnect_types;
+ HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes;
+ void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes);
+ void _set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag);
+ void _set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_pos);
+
HBoxContainer *zoom_hb;
friend class GraphEditFilter;
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 8462fd259e..e7094c89b1 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -894,7 +894,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) {
if (mb.is_valid()) {
ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node.");
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Vector2 mpos = mb->get_position();
if (close_rect.size != Size2() && close_rect.has_point(mpos)) {
//send focus to parent
@@ -917,7 +917,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) {
emit_signal(SNAME("raise_request"));
}
- if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
resizing = false;
}
}
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index d10ad90c1f..c0f2cdbef1 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "item_list.h"
+
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/string/translation.h"
@@ -55,16 +56,8 @@ void ItemList::_shape(int p_idx) {
int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) {
Item item;
item.icon = p_texture;
- item.icon_transposed = false;
- item.icon_region = Rect2i();
- item.icon_modulate = Color(1, 1, 1, 1);
item.text = p_item;
- item.text_buf.instantiate();
item.selectable = p_selectable;
- item.selected = false;
- item.disabled = false;
- item.tooltip_enabled = true;
- item.custom_bg = Color(0, 0, 0, 0);
items.push_back(item);
int item_id = items.size() - 1;
@@ -72,27 +65,20 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo
update();
shape_changed = true;
+ notify_property_list_changed();
return item_id;
}
int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) {
Item item;
item.icon = p_item;
- item.icon_transposed = false;
- item.icon_region = Rect2i();
- item.icon_modulate = Color(1, 1, 1, 1);
- //item.text=p_item;
- item.text_buf.instantiate();
item.selectable = p_selectable;
- item.selected = false;
- item.disabled = false;
- item.tooltip_enabled = true;
- item.custom_bg = Color(0, 0, 0, 0);
items.push_back(item);
int item_id = items.size() - 1;
update();
shape_changed = true;
+ notify_property_list_changed();
return item_id;
}
@@ -400,6 +386,15 @@ void ItemList::move_item(int p_from_idx, int p_to_idx) {
update();
shape_changed = true;
+ notify_property_list_changed();
+}
+
+void ItemList::set_item_count(int p_count) {
+ ERR_FAIL_COND(p_count < 0);
+ items.resize(p_count);
+ update();
+ shape_changed = true;
+ notify_property_list_changed();
}
int ItemList::get_item_count() const {
@@ -416,6 +411,7 @@ void ItemList::remove_item(int p_idx) {
update();
shape_changed = true;
defer_select_single = -1;
+ notify_property_list_changed();
}
void ItemList::clear() {
@@ -425,6 +421,7 @@ void ItemList::clear() {
update();
shape_changed = true;
defer_select_single = -1;
+ notify_property_list_changed();
}
void ItemList::set_fixed_column_width(int p_size) {
@@ -550,7 +547,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
- if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) {
+ if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
select(defer_select_single, true);
emit_signal(SNAME("multi_selected"), defer_select_single, true);
@@ -558,7 +555,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (mb.is_valid() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == MOUSE_BUTTON_RIGHT)) && mb->is_pressed()) {
+ if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) {
search_string = ""; //any mousepress cancels
Vector2 pos = mb->get_position();
Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"));
@@ -604,16 +601,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->get_button_index() == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position());
}
} else {
- if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) {
defer_select_single = i;
return;
}
- if (items[i].selected && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (items[i].selected && mb->get_button_index() == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position());
} else {
bool selected = items[i].selected;
@@ -628,7 +625,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->get_button_index() == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position());
} else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) {
emit_signal(SNAME("item_activated"), i);
@@ -638,7 +635,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->get_button_index() == MouseButton::RIGHT) {
emit_signal(SNAME("rmb_clicked"), mb->get_position());
return;
@@ -647,10 +644,10 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
// Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting.
emit_signal(SNAME("nothing_selected"));
}
- if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) {
scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8);
}
- if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) {
scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * mb->get_factor() / 8);
}
@@ -1446,32 +1443,6 @@ bool ItemList::is_anything_selected() {
return false;
}
-void ItemList::_set_items(const Array &p_items) {
- ERR_FAIL_COND(p_items.size() % 3);
- clear();
-
- for (int i = 0; i < p_items.size(); i += 3) {
- String text = p_items[i + 0];
- Ref<Texture2D> icon = p_items[i + 1];
- bool disabled = p_items[i + 2];
-
- int idx = get_item_count();
- add_item(text, icon);
- set_item_disabled(idx, disabled);
- }
-}
-
-Array ItemList::_get_items() const {
- Array items;
- for (int i = 0; i < get_item_count(); i++) {
- items.push_back(get_item_text(i));
- items.push_back(get_item_icon(i));
- items.push_back(is_item_disabled(i));
- }
-
- return items;
-}
-
Size2 ItemList::get_minimum_size() const {
if (auto_height) {
return Size2(0, auto_height_value);
@@ -1508,6 +1479,74 @@ TextParagraph::OverrunBehavior ItemList::get_text_overrun_behavior() const {
return text_overrun_behavior;
}
+bool ItemList::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
+ int item_index = components[0].trim_prefix("item_").to_int();
+ if (components[1] == "text") {
+ set_item_text(item_index, p_value);
+ return true;
+ } else if (components[1] == "icon") {
+ set_item_icon(item_index, p_value);
+ return true;
+ } else if (components[1] == "disabled") {
+ set_item_disabled(item_index, p_value);
+ return true;
+ }
+ }
+#ifndef DISABLE_DEPRECATED
+ // Compatibility.
+ if (p_name == "items") {
+ Array arr = p_value;
+ ERR_FAIL_COND_V(arr.size() % 3, false);
+ clear();
+
+ for (int i = 0; i < arr.size(); i += 3) {
+ String text = arr[i + 0];
+ Ref<Texture2D> icon = arr[i + 1];
+ bool disabled = arr[i + 2];
+
+ int idx = get_item_count();
+ add_item(text, icon);
+ set_item_disabled(idx, disabled);
+ }
+ }
+#endif
+ return false;
+}
+
+bool ItemList::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
+ int item_index = components[0].trim_prefix("item_").to_int();
+ if (components[1] == "text") {
+ r_ret = get_item_text(item_index);
+ return true;
+ } else if (components[1] == "icon") {
+ r_ret = get_item_icon(item_index);
+ return true;
+ } else if (components[1] == "disabled") {
+ r_ret = is_item_disabled(item_index);
+ return true;
+ }
+ }
+ return false;
+}
+
+void ItemList::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (int i = 0; i < items.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
+
+ PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
+ pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
+ pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+ }
+}
+
void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true));
@@ -1567,6 +1606,7 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_item", "from_idx", "to_idx"), &ItemList::move_item);
+ ClassDB::bind_method(D_METHOD("set_item_count", "count"), &ItemList::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count);
ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item);
@@ -1614,20 +1654,16 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_v_scroll"), &ItemList::get_v_scroll);
- ClassDB::bind_method(D_METHOD("_set_items"), &ItemList::_set_items);
- ClassDB::bind_method(D_METHOD("_get_items"), &ItemList::_get_items);
-
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
-
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ADD_ARRAY_COUNT("Items", "items_count", "set_item_count", "get_item_count", "item_");
ADD_GROUP("Columns", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width");
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 148fa7ba9f..e780179e7b 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -54,7 +54,7 @@ private:
Ref<Texture2D> icon;
bool icon_transposed = false;
Rect2i icon_region;
- Color icon_modulate;
+ Color icon_modulate = Color(1, 1, 1, 1);
Ref<Texture2D> tag_icon;
String text;
Ref<TextParagraph> text_buf;
@@ -65,11 +65,11 @@ private:
bool selectable = false;
bool selected = false;
bool disabled = false;
- bool tooltip_enabled = false;
+ bool tooltip_enabled = true;
Variant metadata;
String tooltip;
Color custom_fg;
- Color custom_bg;
+ Color custom_bg = Color(0.0, 0.0, 0.0, 0.0);
Rect2 rect_cache;
Rect2 min_rect_cache;
@@ -77,6 +77,10 @@ private:
Size2 get_icon_size() const;
bool operator<(const Item &p_another) const { return text < p_another.text; }
+
+ Item() {
+ text_buf.instantiate();
+ }
};
int current = -1;
@@ -119,14 +123,14 @@ private:
bool do_autoscroll_to_bottom = false;
- Array _get_items() const;
- void _set_items(const Array &p_items);
-
void _scroll_changed(double);
void _shape(int p_idx);
protected:
void _notification(int p_what);
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
@@ -199,6 +203,7 @@ public:
void move_item(int p_from_idx, int p_to_idx);
+ void set_item_count(int p_count);
int get_item_count() const;
void remove_item(int p_idx);
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index b8cb618171..50908f6a77 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -243,11 +243,9 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
if (p_gl.font_rid != RID()) {
if (p_font_shadow_color.a > 0) {
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
- if (p_shadow_outline_size > 0) {
- TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), p_gl.index, p_font_shadow_color);
- TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color);
- TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color);
- }
+ }
+ if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) {
+ TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color);
}
if (p_font_outline_color.a != 0.0 && p_outline_size > 0) {
TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color);
@@ -397,7 +395,7 @@ void Label::_notification(int p_what) {
int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
// Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps.
- if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) {
+ if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) {
Vector2 offset = ofs;
// Draw RTL ellipsis string when necessary.
if (rtl && ellipsis_pos >= 0) {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 653885aa08..124d5c7821 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -225,7 +225,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
// Ignore mouse clicks in IME input mode.
return;
}
- if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) {
+ if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
_ensure_menu();
menu->set_position(get_screen_transform().xform(get_local_mouse_position()));
menu->set_size(Vector2(1, 1));
@@ -235,13 +235,32 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (b->get_button_index() != MOUSE_BUTTON_LEFT) {
+ if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes();
+
+ deselect();
+ set_caret_at_pixel_pos(b->get_position().x);
+ if (!paste_buffer.is_empty()) {
+ insert_text_at_caret(paste_buffer);
+
+ if (!text_changed_dirty) {
+ if (is_inside_tree()) {
+ MessageQueue::get_singleton()->push_call(this, "_text_changed");
+ }
+ text_changed_dirty = true;
+ }
+ }
+ grab_focus();
+ return;
+ }
+
+ if (b->get_button_index() != MouseButton::LEFT) {
return;
}
_reset_caret_blink_timer();
if (b->is_pressed()) {
- accept_event(); //don't pass event further when clicked on text field
+ accept_event(); // don't pass event further when clicked on text field
if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) {
clear_button_status.press_attempt = true;
clear_button_status.pressing_inside = true;
@@ -271,6 +290,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
selection.double_click = true;
last_dblclk = 0;
caret_column = selection.begin;
+ if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(text);
+ }
} else if (b->is_double_click()) {
// Double-click select word.
last_dblclk = OS::get_singleton()->get_ticks_msec();
@@ -286,6 +308,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
+ if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin));
+ }
}
}
@@ -303,6 +328,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
update();
} else {
+ if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin));
+ }
if (!text.is_empty() && is_editable() && clear_button_enabled) {
bool press_attempt = clear_button_status.press_attempt;
clear_button_status.press_attempt = false;
@@ -335,7 +363,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (m->get_button_mask() & MOUSE_BUTTON_LEFT) {
+ if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) {
if (selection.creating) {
set_caret_at_pixel_pos(m->get_position().x);
selection_fill_at_caret();
@@ -549,13 +577,12 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
if (p_data.get_type() == Variant::STRING) {
set_caret_at_pixel_pos(p_point.x);
- int selected = selection.end - selection.begin;
- text.erase(selection.begin, selected);
+ text = text.left(selection.begin) + text.substr(selection.end);
_shape();
insert_text_at_caret(p_data);
- selection.begin = caret_column - selected;
+ selection.begin = caret_column - (selection.end - selection.begin);
selection.end = caret_column;
}
}
@@ -636,7 +663,9 @@ void LineEdit::_notification(int p_what) {
}
Ref<Font> font = get_theme_font(SNAME("font"));
- style->draw(ci, Rect2(Point2(), size));
+ if (!flat) {
+ style->draw(ci, Rect2(Point2(), size));
+ }
if (has_focus()) {
get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size));
@@ -892,6 +921,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()) {
@@ -1211,7 +1243,7 @@ void LineEdit::delete_char() {
return;
}
- text.erase(caret_column - 1, 1);
+ text = text.left(caret_column - 1) + text.substr(caret_column);
_shape();
set_caret_column(get_caret_column() - 1);
@@ -1223,7 +1255,7 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) {
ERR_FAIL_COND_MSG(p_from_column < 0 || p_from_column > p_to_column || p_to_column > text.length(),
vformat("Positional parameters (from: %d, to: %d) are inverted or outside the text length (%d).", p_from_column, p_to_column, text.length()));
- text.erase(p_from_column, p_to_column - p_from_column);
+ text = text.left(p_from_column) + text.substr(p_to_column);
_shape();
caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column);
@@ -1890,6 +1922,14 @@ bool LineEdit::is_virtual_keyboard_enabled() const {
return virtual_keyboard_enabled;
}
+void LineEdit::set_middle_mouse_paste_enabled(bool p_enabled) {
+ middle_mouse_paste_enabled = p_enabled;
+}
+
+bool LineEdit::is_middle_mouse_paste_enabled() const {
+ return middle_mouse_paste_enabled;
+}
+
void LineEdit::set_selecting_enabled(bool p_enabled) {
selecting_enabled = p_enabled;
@@ -1902,6 +1942,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;
@@ -1916,6 +1967,17 @@ Ref<Texture2D> LineEdit::get_right_icon() {
return right_icon;
}
+void LineEdit::set_flat(bool p_enabled) {
+ if (flat != p_enabled) {
+ flat = p_enabled;
+ update();
+ }
+}
+
+bool LineEdit::is_flat() const {
+ return flat;
+}
+
void LineEdit::_text_changed() {
_emit_text_change();
_clear_redo();
@@ -1931,7 +1993,7 @@ void LineEdit::_shape() {
TS->shaped_text_clear(text_rid);
String t;
- if (text.length() == 0) {
+ if (text.length() == 0 && ime_text.length() == 0) {
t = placeholder_translated;
} else if (pass) {
t = secret_character.repeat(text.length() + ime_text.length());
@@ -2008,25 +2070,25 @@ void LineEdit::_create_undo_state() {
undo_stack.push_back(op);
}
-int LineEdit::_get_menu_action_accelerator(const String &p_action) {
+Key LineEdit::_get_menu_action_accelerator(const String &p_action) {
const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
if (!events) {
- return 0;
+ return Key::NONE;
}
// Use first event in the list for the accelerator.
const List<Ref<InputEvent>>::Element *first_event = events->front();
if (!first_event) {
- return 0;
+ return Key::NONE;
}
const Ref<InputEventKey> event = first_event->get();
if (event.is_null()) {
- return 0;
+ return Key::NONE;
}
// Use physical keycode if non-zero
- if (event->get_physical_keycode() != 0) {
+ if (event->get_physical_keycode() != Key::NONE) {
return event->get_physical_keycode_with_modifiers();
} else {
return event->get_keycode_with_modifiers();
@@ -2085,7 +2147,7 @@ void LineEdit::_get_property_list(List<PropertyInfo> *p_list) const {
void LineEdit::_validate_property(PropertyInfo &property) const {
if (!caret_blink_enabled && property.name == "caret_blink_speed") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
@@ -2156,10 +2218,16 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_clear_button_enabled"), &LineEdit::is_clear_button_enabled);
ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &LineEdit::set_shortcut_keys_enabled);
ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &LineEdit::is_shortcut_keys_enabled);
+ ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enable"), &LineEdit::set_middle_mouse_paste_enabled);
+ 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);
+ ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat);
+ ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat);
ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text")));
ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring")));
@@ -2211,8 +2279,11 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled");
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::BOOL, "flat"), "set_flat", "is_flat");
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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
@@ -2237,33 +2308,33 @@ void LineEdit::_ensure_menu() {
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
- menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED);
- menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO);
- menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR);
- menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
- menu->add_child(menu_dir);
+ menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
+ menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
+ menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
+ menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
+ menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
menu_ctl = memnew(PopupMenu);
menu_ctl->set_name("CTLMenu");
- menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM);
- menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM);
- menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE);
- menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE);
- menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO);
- menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO);
- menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF);
+ menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
+ menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
+ menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
+ menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
+ menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
+ menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
+ menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
menu_ctl->add_separator();
- menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM);
- menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI);
- menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI);
- menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI);
- menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI);
+ menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
+ menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
+ menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
+ menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
+ menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
menu_ctl->add_separator();
- menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ);
- menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
- menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
- menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
- menu->add_child(menu_ctl);
+ menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
+ menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
+ menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
+ menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
+ menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
@@ -2273,29 +2344,29 @@ void LineEdit::_ensure_menu() {
// Reorganize context menu.
menu->clear();
if (editable) {
- menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0);
+ menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE);
}
- menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0);
+ menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
if (editable) {
- menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0);
+ menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE);
}
menu->add_separator();
if (is_selecting_enabled()) {
- menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0);
+ menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
}
if (editable) {
menu->add_item(RTR("Clear"), MENU_CLEAR);
menu->add_separator();
- menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0);
- menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0);
+ menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE);
+ menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE);
}
menu->add_separator();
- menu->add_submenu_item(RTR("Text writing direction"), "DirMenu");
+ menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu");
menu->add_separator();
- menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC);
+ menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
if (editable) {
- menu->add_submenu_item(RTR("Insert control character"), "CTLMenu");
+ menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu");
}
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 923024dd56..134d5f8f76 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;
@@ -126,7 +127,10 @@ private:
bool virtual_keyboard_enabled = true;
+ bool middle_mouse_paste_enabled = true;
+
Ref<Texture2D> right_icon;
+ bool flat = false;
struct Selection {
int begin = 0;
@@ -166,7 +170,7 @@ private:
void _clear_redo();
void _create_undo_state();
- int _get_menu_action_accelerator(const String &p_action);
+ Key _get_menu_action_accelerator(const String &p_action);
void _shape();
void _fit_to_width();
@@ -187,7 +191,6 @@ private:
void _toggle_draw_caret();
void clear_internal();
- void changed_internal();
void _editor_settings_changed();
@@ -318,12 +321,21 @@ public:
void set_virtual_keyboard_enabled(bool p_enable);
bool is_virtual_keyboard_enabled() const;
+ void set_middle_mouse_paste_enabled(bool p_enabled);
+ bool is_middle_mouse_paste_enabled() const;
+
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();
+ void set_flat(bool p_enabled);
+ bool is_flat() const;
+
virtual bool is_text_field() const override;
void show_virtual_keyboard();
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/menu_button.cpp b/scene/gui/menu_button.cpp
index ceb2092e3a..39c7b04955 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -110,14 +110,6 @@ PopupMenu *MenuButton::get_popup() const {
return popup;
}
-void MenuButton::_set_items(const Array &p_items) {
- popup->set("items", p_items);
-}
-
-Array MenuButton::_get_items() const {
- return popup->get("items");
-}
-
void MenuButton::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
@@ -126,6 +118,16 @@ bool MenuButton::is_switch_on_hover() {
return switch_on_hover;
}
+void MenuButton::set_item_count(int p_count) {
+ ERR_FAIL_COND(p_count < 0);
+ popup->set_item_count(p_count);
+ notify_property_list_changed();
+}
+
+int MenuButton::get_item_count() const {
+ return popup->get_item_count();
+}
+
void MenuButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -146,16 +148,66 @@ void MenuButton::_notification(int p_what) {
}
}
+bool MenuButton::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0] == "popup") {
+ bool valid;
+ popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid);
+ return valid;
+ }
+ return false;
+}
+
+bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0] == "popup") {
+ bool valid;
+ r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid);
+ return valid;
+ }
+ return false;
+}
+
+void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (int i = 0; i < popup->get_item_count(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i)));
+
+ PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
+ pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
+ pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i));
+ pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "1,10,1,or_greater");
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
+ pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i));
+ pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+ }
+}
+
void MenuButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
- ClassDB::bind_method(D_METHOD("_set_items"), &MenuButton::_set_items);
- ClassDB::bind_method(D_METHOD("_get_items"), &MenuButton::_get_items);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
+ ClassDB::bind_method(D_METHOD("set_item_count", "count"), &MenuButton::set_item_count);
+ ClassDB::bind_method(D_METHOD("get_item_count"), &MenuButton::get_item_count);
+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
+ ADD_ARRAY_COUNT("Items", "items_count", "set_item_count", "get_item_count", "popup/item_");
ADD_SIGNAL(MethodInfo("about_to_popup"));
}
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index 730495b65d..455b7dc870 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -44,15 +44,15 @@ class MenuButton : public Button {
Vector2i mouse_pos_adjusted;
- Array _get_items() const;
- void _set_items(const Array &p_items);
-
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _popup_visibility_changed(bool p_visible);
protected:
void _notification(int p_what);
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
@@ -64,6 +64,9 @@ public:
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
+ void set_item_count(int p_count);
+ int get_item_count() const;
+
MenuButton();
~MenuButton();
};
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 2adeb2d947..dcf3cfeb09 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"));
+ }
}
}
@@ -328,7 +332,7 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_items"), &OptionButton::_set_items);
ClassDB::bind_method(D_METHOD("_get_items"), &OptionButton::_get_items);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
// "selected" property must come after "items", otherwise GH-10213 occurs.
ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected");
ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index be05fd5a60..a48ad0f770 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -36,7 +36,7 @@
void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> key = p_event;
- if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) {
+ if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
_close_pressed();
}
}
@@ -105,8 +105,6 @@ void Popup::_close_pressed() {
_deinitialize_visible_parents();
call_deferred(SNAME("hide"));
-
- emit_signal(SNAME("cancelled"));
}
void Popup::set_as_minsize() {
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index c7090e7231..8458a75eef 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -80,7 +80,6 @@ protected:
virtual Size2 _get_contents_minimum_size() const override;
public:
- void set_child_rect(Control *p_child);
PopupPanel();
};
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index c0a559e624..7e9b545776 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -39,7 +39,7 @@
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
- } else if (p_item.accel) {
+ } else if (p_item.accel != Key::NONE) {
return keycode_get_string(p_item.accel);
}
return String();
@@ -74,7 +74,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
size.width += items[i].text_buf->get_size().x;
size.height += vseparation;
- if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
+ if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
int accel_w = hseparation * 2;
accel_w += items[i].accel_text_buf->get_size().x;
accel_max_w = MAX(accel_w, accel_max_w);
@@ -358,15 +358,15 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- int button_idx = b->get_button_index();
+ MouseButton button_idx = b->get_button_index();
if (!b->is_pressed()) {
// Activate the item on release of either the left mouse button or
// any mouse button held down when the popup was opened.
// This allows for opening the popup and triggering an action in a single mouse click.
- if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) {
+ if (button_idx == MouseButton::LEFT || (initial_button_mask & mouse_button_to_mask(button_idx)) != MouseButton::NONE) {
bool was_during_grabbed_click = during_grabbed_click;
during_grabbed_click = false;
- initial_button_mask = 0;
+ initial_button_mask = MouseButton::NONE;
// Disable clicks under a time threshold to avoid selection right when opening the popup.
uint64_t now = OS::get_singleton()->get_ticks_msec();
@@ -637,7 +637,7 @@ void PopupMenu::_draw_items() {
}
// Accelerator / Shortcut
- if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
+ if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
if (rtl) {
item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding;
} else {
@@ -813,16 +813,17 @@ void PopupMenu::_notification(int p_what) {
item.id = p_id == -1 ? items.size() : p_id; \
item.accel = p_accel;
-void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
+ notify_property_list_changed();
}
-void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.icon = p_icon;
@@ -830,9 +831,10 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
+ notify_property_list_changed();
}
-void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
@@ -842,7 +844,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel
child_controls_changed();
}
-void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.icon = p_icon;
@@ -853,7 +855,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
child_controls_changed();
}
-void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
@@ -863,7 +865,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p
child_controls_changed();
}
-void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
+void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.icon = p_icon;
@@ -874,7 +876,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
child_controls_changed();
}
-void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) {
+void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.max_states = p_max_states;
@@ -1043,7 +1045,7 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
child_controls_changed();
}
-void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
+void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
@@ -1119,8 +1121,8 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const {
return items[p_idx].icon;
}
-uint32_t PopupMenu::get_item_accelerator(int p_idx) const {
- ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
+Key PopupMenu::get_item_accelerator(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
return items[p_idx].accel;
}
@@ -1271,30 +1273,38 @@ int PopupMenu::get_current_index() const {
return mouse_over;
}
+void PopupMenu::set_item_count(int p_count) {
+ ERR_FAIL_COND(p_count < 0);
+ items.resize(p_count);
+ control->update();
+ child_controls_changed();
+ notify_property_list_changed();
+}
+
int PopupMenu::get_item_count() const {
return items.size();
}
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
- Key code = KEY_NONE;
+ Key code = Key::NONE;
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
code = k->get_keycode();
- if (code == KEY_NONE) {
+ if (code == Key::NONE) {
code = (Key)k->get_unicode();
}
if (k->is_ctrl_pressed()) {
- code |= KEY_MASK_CTRL;
+ code |= KeyModifierMask::CTRL;
}
if (k->is_alt_pressed()) {
- code |= KEY_MASK_ALT;
+ code |= KeyModifierMask::ALT;
}
if (k->is_meta_pressed()) {
- code |= KEY_MASK_META;
+ code |= KeyModifierMask::META;
}
if (k->is_shift_pressed()) {
- code |= KEY_MASK_SHIFT;
+ code |= KeyModifierMask::SHIFT;
}
}
@@ -1308,7 +1318,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
return true;
}
- if (code != 0 && items[i].accel == code) {
+ if (code != Key::NONE && items[i].accel == code) {
activate_item(i);
return true;
}
@@ -1420,27 +1430,7 @@ void PopupMenu::clear() {
mouse_over = -1;
control->update();
child_controls_changed();
-}
-
-Array PopupMenu::_get_items() const {
- Array items;
- for (int i = 0; i < get_item_count(); i++) {
- items.push_back(get_item_text(i));
- items.push_back(get_item_icon(i));
- // For compatibility, use false/true for no/checkbox and integers for other values
- int ct = this->items[i].checkable_type;
- items.push_back(Variant(ct <= Item::CHECKABLE_TYPE_CHECK_BOX ? is_item_checkable(i) : ct));
- items.push_back(is_item_checked(i));
- items.push_back(is_item_disabled(i));
-
- items.push_back(get_item_id(i));
- items.push_back(get_item_accelerator(i));
- items.push_back(get_item_metadata(i));
- items.push_back(get_item_submenu(i));
- items.push_back(is_item_separator(i));
- }
-
- return items;
+ notify_property_list_changed();
}
void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) {
@@ -1461,45 +1451,6 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) {
}
}
-void PopupMenu::_set_items(const Array &p_items) {
- ERR_FAIL_COND(p_items.size() % 10);
- clear();
-
- for (int i = 0; i < p_items.size(); i += 10) {
- String text = p_items[i + 0];
- Ref<Texture2D> icon = p_items[i + 1];
- // For compatibility, use false/true for no/checkbox and integers for other values
- bool checkable = p_items[i + 2];
- bool radio_checkable = (int)p_items[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
- bool checked = p_items[i + 3];
- bool disabled = p_items[i + 4];
-
- int id = p_items[i + 5];
- int accel = p_items[i + 6];
- Variant meta = p_items[i + 7];
- String subm = p_items[i + 8];
- bool sep = p_items[i + 9];
-
- int idx = get_item_count();
- add_item(text, id);
- set_item_icon(idx, icon);
- if (checkable) {
- if (radio_checkable) {
- set_item_as_radio_checkable(idx, true);
- } else {
- set_item_as_checkable(idx, true);
- }
- }
- set_item_checked(idx, checked);
- set_item_disabled(idx, disabled);
- set_item_id(idx, id);
- set_item_metadata(idx, meta);
- set_item_as_separator(idx, sep);
- set_item_accelerator(idx, accel);
- set_item_submenu(idx, subm);
- }
-}
-
// Hide on item selection determines whether or not the popup will close after item selection
void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
hide_on_item_selection = p_enabled;
@@ -1581,6 +1532,145 @@ void PopupMenu::take_mouse_focus() {
}
}
+bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
+ int item_index = components[0].trim_prefix("item_").to_int();
+ String property = components[1];
+ if (property == "text") {
+ set_item_text(item_index, p_value);
+ return true;
+ } else if (property == "icon") {
+ set_item_icon(item_index, p_value);
+ return true;
+ } else if (property == "checkable") {
+ bool radio_checkable = (int)p_value == Item::CHECKABLE_TYPE_RADIO_BUTTON;
+ if (radio_checkable) {
+ set_item_as_radio_checkable(item_index, true);
+ } else {
+ bool checkable = p_value;
+ set_item_as_checkable(item_index, checkable);
+ }
+ return true;
+ } else if (property == "checked") {
+ set_item_checked(item_index, p_value);
+ return true;
+ } else if (property == "id") {
+ set_item_id(item_index, p_value);
+ return true;
+ } else if (components[1] == "disabled") {
+ set_item_disabled(item_index, p_value);
+ return true;
+ } else if (property == "separator") {
+ set_item_as_separator(item_index, p_value);
+ return true;
+ }
+ }
+#ifndef DISABLE_DEPRECATED
+ // Compatibility.
+ if (p_name == "items") {
+ Array arr = p_value;
+ ERR_FAIL_COND_V(arr.size() % 10, false);
+ clear();
+
+ for (int i = 0; i < arr.size(); i += 10) {
+ String text = arr[i + 0];
+ Ref<Texture2D> icon = arr[i + 1];
+ // For compatibility, use false/true for no/checkbox and integers for other values
+ bool checkable = arr[i + 2];
+ bool radio_checkable = (int)arr[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
+ bool checked = arr[i + 3];
+ bool disabled = arr[i + 4];
+
+ int id = arr[i + 5];
+ int accel = arr[i + 6];
+ Variant meta = arr[i + 7];
+ String subm = arr[i + 8];
+ bool sep = arr[i + 9];
+
+ int idx = get_item_count();
+ add_item(text, id);
+ set_item_icon(idx, icon);
+ if (checkable) {
+ if (radio_checkable) {
+ set_item_as_radio_checkable(idx, true);
+ } else {
+ set_item_as_checkable(idx, true);
+ }
+ }
+ set_item_checked(idx, checked);
+ set_item_disabled(idx, disabled);
+ set_item_id(idx, id);
+ set_item_metadata(idx, meta);
+ set_item_as_separator(idx, sep);
+ set_item_accelerator(idx, (Key)accel);
+ set_item_submenu(idx, subm);
+ }
+ }
+#endif
+ return false;
+}
+
+bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
+ int item_index = components[0].trim_prefix("item_").to_int();
+ String property = components[1];
+ if (property == "text") {
+ r_ret = get_item_text(item_index);
+ return true;
+ } else if (property == "icon") {
+ r_ret = get_item_icon(item_index);
+ return true;
+ } else if (property == "checkable") {
+ r_ret = this->items[item_index].checkable_type;
+ return true;
+ } else if (property == "checked") {
+ r_ret = is_item_checked(item_index);
+ return true;
+ } else if (property == "id") {
+ r_ret = get_item_id(item_index);
+ return true;
+ } else if (components[1] == "disabled") {
+ r_ret = is_item_disabled(item_index);
+ return true;
+ } else if (property == "separator") {
+ r_ret = is_item_separator(item_index);
+ return true;
+ }
+ }
+ return false;
+}
+
+void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (int i = 0; i < items.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
+
+ PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
+ pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::INT, vformat("item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
+ pi.usage &= ~(!is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("item_%d/checked", i));
+ pi.usage &= ~(!is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::INT, vformat("item_%d/id", i), PROPERTY_HINT_RANGE, "1,10,1,or_greater");
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
+ pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+
+ pi = PropertyInfo(Variant::BOOL, vformat("item_%d/separator", i));
+ pi.usage &= ~(!is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
+ p_list->push_back(pi);
+ }
+}
+
void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
@@ -1643,6 +1733,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut);
ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
+ ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
ClassDB::bind_method(D_METHOD("remove_item", "idx"), &PopupMenu::remove_item);
@@ -1650,9 +1741,6 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("clear"), &PopupMenu::clear);
- ClassDB::bind_method(D_METHOD("_set_items"), &PopupMenu::_set_items);
- ClassDB::bind_method(D_METHOD("_get_items"), &PopupMenu::_get_items);
-
ClassDB::bind_method(D_METHOD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
ClassDB::bind_method(D_METHOD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
@@ -1668,13 +1756,14 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
+ ADD_ARRAY_COUNT("Items", "items_count", "set_item_count", "get_item_count", "item_");
+
ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 428076c6da..22912fb59c 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -66,7 +66,7 @@ class PopupMenu : public Popup {
Variant metadata;
String submenu;
String tooltip;
- uint32_t accel = 0;
+ Key accel = Key::NONE;
int _ofs_cache = 0;
int _height_cache = 0;
int h_ofs = 0;
@@ -92,7 +92,7 @@ class PopupMenu : public Popup {
Timer *submenu_timer;
List<Rect2> autohide_areas;
Vector<Item> items;
- int initial_button_mask = 0;
+ MouseButton initial_button_mask = MouseButton::NONE;
bool during_grabbed_click = false;
int mouse_over = -1;
int submenu_over = -1;
@@ -117,9 +117,6 @@ class PopupMenu : public Popup {
bool hide_on_multistate_item_selection = false;
Vector2 moved;
- Array _get_items() const;
- void _set_items(const Array &p_items);
-
Map<Ref<Shortcut>, int> shortcut_refcount;
void _ref_shortcut(Ref<Shortcut> p_sc);
@@ -141,6 +138,9 @@ class PopupMenu : public Popup {
protected:
void _notification(int p_what);
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
@@ -148,14 +148,14 @@ public:
// this value should be updated to reflect the new size.
static const int ITEM_PROPERTY_SIZE = 10;
- void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0);
- void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0);
- void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0);
- void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0);
- void add_radio_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0);
- void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0);
+ void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
+ void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
+ void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
+ void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
+ void add_radio_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
+ void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
- void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, uint32_t p_accel = 0);
+ void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, Key p_accel = Key::NONE);
void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
@@ -175,7 +175,7 @@ public:
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_checked(int p_idx, bool p_checked);
void set_item_id(int p_idx, int p_id);
- void set_item_accelerator(int p_idx, uint32_t p_accel);
+ void set_item_accelerator(int p_idx, Key p_accel);
void set_item_metadata(int p_idx, const Variant &p_meta);
void set_item_disabled(int p_idx, bool p_disabled);
void set_item_submenu(int p_idx, const String &p_submenu);
@@ -200,7 +200,7 @@ public:
bool is_item_checked(int p_idx) const;
int get_item_id(int p_idx) const;
int get_item_index(int p_id) const;
- uint32_t get_item_accelerator(int p_idx) const;
+ Key get_item_accelerator(int p_idx) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
String get_item_submenu(int p_idx) const;
@@ -213,6 +213,8 @@ public:
int get_item_state(int p_idx) const;
int get_current_index() const;
+
+ void set_item_count(int p_count);
int get_item_count() const;
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 4588966d88..f191dfecb4 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -36,7 +36,7 @@
#include "scene/scene_string_names.h"
#include "servers/display_server.h"
-#include "modules/modules_enabled.gen.h"
+#include "modules/modules_enabled.gen.h" // For regex.
#ifdef MODULE_REGEX_ENABLED
#include "modules/regex/regex.h"
#endif
@@ -486,7 +486,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
remaining_characters -= cell_ch;
table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
- table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wraped_size().x));
+ table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x));
}
idx++;
}
@@ -621,7 +621,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
}
-int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &p_shadow_ofs) {
+int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs) {
Vector2 off;
ERR_FAIL_COND_V(p_frame == nullptr, 0);
@@ -804,7 +804,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
for (int j = 0; j < frame->lines.size(); j++) {
- _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_as_outline, p_shadow_ofs);
+ _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs);
}
idx++;
}
@@ -824,7 +824,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
int size = _find_outline_size(it, p_outline_size);
Color font_color = _find_outline_color(it, p_outline_color);
- if (size <= 0) {
+ Color font_shadow_color = p_font_shadow_color;
+ if ((size <= 0 || font_color.a == 0) && (font_shadow_color.a == 0)) {
gloff.x += glyphs[i].advance;
continue;
}
@@ -871,9 +872,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
}
font_color.a = faded_visibility;
+ font_shadow_color.a = faded_visibility;
}
- bool visible = (font_color.a != 0);
+ bool visible = (font_color.a != 0) || (font_shadow_color.a != 0);
for (int j = 0; j < fx_stack.size(); j++) {
ItemFX *item_fx = fx_stack[j];
@@ -942,18 +944,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
- Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y")));
-
// Draw glyph outlines.
for (int j = 0; j < glyphs[i].repeat; j++) {
if (visible) {
if (frid != RID()) {
- if (p_shadow_as_outline) {
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, shadow_ofs.y), gl, p_font_shadow_color);
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color);
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color);
+ if (font_shadow_color.a > 0) {
+ TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color);
+ }
+ if (font_shadow_color.a > 0 && p_shadow_outline_size > 0) {
+ TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color);
+ }
+ if (font_color.a != 0.0 && size > 0) {
+ TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color);
}
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color);
}
}
gloff.x += glyphs[i].advance;
@@ -1470,7 +1473,7 @@ void RichTextLabel::_notification(int p_what) {
Color outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
Color font_shadow_color = get_theme_color(SNAME("font_shadow_color"));
- bool use_outline = get_theme_constant(SNAME("shadow_as_outline"));
+ int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size"));
Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y")));
visible_paragraph_count = 0;
@@ -1480,7 +1483,7 @@ void RichTextLabel::_notification(int p_what) {
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
while (ofs.y < size.height && from_line < main->lines.size()) {
visible_paragraph_count++;
- visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, use_outline, shadow_ofs);
+ visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs);
ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
from_line++;
}
@@ -1491,7 +1494,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;
}
}
@@ -1536,7 +1545,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (b->get_button_index() == MouseButton::LEFT) {
if (b->is_pressed() && !b->is_double_click()) {
scroll_updated = false;
ItemFrame *c_frame = nullptr;
@@ -1596,12 +1605,18 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
selection.to_char = words[i + 1];
selection.active = true;
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
+ }
update();
break;
}
}
}
} else if (!b->is_pressed()) {
+ if (selection.enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
+ }
selection.click_item = nullptr;
if (!b->is_double_click() && !scroll_updated) {
@@ -1621,12 +1636,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
+ if (b->get_button_index() == MouseButton::WHEEL_UP) {
if (scroll_active) {
vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
- if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
+ if (b->get_button_index() == MouseButton::WHEEL_DOWN) {
if (scroll_active) {
vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
@@ -1719,6 +1734,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
swap = true;
} else if (selection.from_char == selection.to_char) {
selection.active = false;
+ update();
return;
}
}
@@ -2810,12 +2826,12 @@ bool RichTextLabel::is_scroll_following() const {
return scroll_follow;
}
-Error RichTextLabel::parse_bbcode(const String &p_bbcode) {
+void RichTextLabel::parse_bbcode(const String &p_bbcode) {
clear();
- return append_text(p_bbcode);
+ append_text(p_bbcode);
}
-Error RichTextLabel::append_text(const String &p_bbcode) {
+void RichTextLabel::append_text(const String &p_bbcode) {
int pos = 0;
List<String> tag_stack;
@@ -3538,8 +3554,6 @@ Error RichTextLabel::append_text(const String &p_bbcode) {
break;
}
}
-
- return OK;
}
void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
@@ -3604,6 +3618,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) {
@@ -3779,45 +3801,32 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
}
}
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
+ if (it->type == ITEM_TABLE) {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ for (Item *E : table->subitems) {
+ ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(E);
+ for (int i = 0; i < frame->lines.size(); i++) {
+ text += _get_line_text(frame, i, p_selection);
+ }
+ }
+ }
if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) {
- break;
+ continue;
}
if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) {
- break;
+ continue;
}
- switch (it->type) {
- case ITEM_NEWLINE: {
- text += "\n";
- } break;
- case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
- text += t->text;
- } break;
- case ITEM_IMAGE: {
- text += " ";
- } break;
- case ITEM_TABLE: {
- ItemTable *table = static_cast<ItemTable *>(it);
- int idx = 0;
- int col_count = table->columns.size();
- for (Item *E : table->subitems) {
- ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
- ItemFrame *frame = static_cast<ItemFrame *>(E);
- int column = idx % col_count;
-
- for (int i = 0; i < frame->lines.size(); i++) {
- text += _get_line_text(frame, i, p_selection);
- }
- if (column == col_count - 1) {
- text += "\n";
- } else {
- text += " ";
- }
- idx++;
- }
- } break;
- default:
- break;
+ if (it->type == ITEM_DROPCAP) {
+ const ItemDropcap *dc = static_cast<ItemDropcap *>(it);
+ text += dc->text;
+ } else if (it->type == ITEM_TEXT) {
+ const ItemText *t = static_cast<ItemText *>(it);
+ text += t->text;
+ } else if (it->type == ITEM_NEWLINE) {
+ text += "\n";
+ } else if (it->type == ITEM_IMAGE) {
+ text += " ";
}
}
if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) {
@@ -3853,6 +3862,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;
@@ -4106,6 +4119,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);
@@ -4157,6 +4173,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 94f02a3989..5b58f14d96 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -278,12 +278,12 @@ private:
uint64_t offset_random(int index) {
return (_current_rng >> (index % 64)) |
- (_current_rng << (64 - (index % 64)));
+ (_current_rng << (64 - (index % 64)));
}
uint64_t offset_previous_random(int index) {
return (_previous_rng >> (index % 64)) |
- (_previous_rng << (64 - (index % 64)));
+ (_previous_rng << (64 - (index % 64)));
}
};
@@ -399,6 +399,7 @@ private:
};
Selection selection;
+ bool deselect_on_focus_loss_enabled = true;
int visible_characters = -1;
float percent_visible = 1.0;
@@ -411,7 +412,7 @@ private:
void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
- int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &shadow_ofs);
+ int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr);
String _roman(int p_num, bool p_capitalize) const;
@@ -551,9 +552,11 @@ 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;
- Error parse_bbcode(const String &p_bbcode);
- Error append_text(const String &p_bbcode);
+ void parse_bbcode(const String &p_bbcode);
+ void append_text(const String &p_bbcode);
void set_use_bbcode(bool p_enable);
bool is_using_bbcode() const;
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 4a3a6837d5..8c292e663e 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -54,17 +54,17 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
if (b.is_valid()) {
accept_event();
- if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && b->is_pressed()) {
+ if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) {
set_value(get_value() + get_page() / 4.0);
accept_event();
}
- if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && b->is_pressed()) {
+ if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) {
set_value(get_value() - get_page() / 4.0);
accept_event();
}
- if (b->get_button_index() != MOUSE_BUTTON_LEFT) {
+ if (b->get_button_index() != MouseButton::LEFT) {
return;
}
@@ -525,7 +525,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
Ref<InputEventMouseButton> mb = p_input;
if (mb.is_valid()) {
- if (mb->get_button_index() != 1) {
+ if (mb->get_button_index() != MouseButton::LEFT) {
return;
}
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 0c0ec39c7f..7b2ea46e17 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -92,7 +92,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) {
// only horizontal is enabled, scroll horizontally
if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor());
@@ -101,7 +101,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) {
// only horizontal is enabled, scroll horizontally
if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor());
@@ -110,13 +110,13 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT && mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) {
if (h_scroll->is_visible_in_tree()) {
h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8);
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT && mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) {
if (h_scroll->is_visible_in_tree()) {
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8);
}
@@ -130,7 +130,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- if (mb->get_button_index() != MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() != MouseButton::LEFT) {
return;
}
@@ -320,7 +320,9 @@ void ScrollContainer::_notification(int p_what) {
};
if (p_what == NOTIFICATION_READY) {
- get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed));
+ Viewport *viewport = get_viewport();
+ ERR_FAIL_COND(!viewport);
+ viewport->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed));
_update_dimensions();
}
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index 352f87954e..4cc425aad3 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -55,7 +55,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber");
grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x;
@@ -74,10 +74,10 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
grab.active = false;
}
} else if (scrollable) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
grab_focus();
set_value(get_value() + get_step());
- } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
+ } else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
grab_focus();
set_value(get_value() - get_step());
}
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 0446e1b402..f30206c943 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -85,7 +85,7 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) {
}
void SpinBox::_range_click_timeout() {
- if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) {
+ if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
bool up = get_local_mouse_position().y < (get_size().height / 2);
set_value(get_value() + (up ? get_step() : -get_step()));
@@ -121,7 +121,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
bool up = mb->get_position().y < (get_size().height / 2);
switch (mb->get_button_index()) {
- case MOUSE_BUTTON_LEFT: {
+ case MouseButton::LEFT: {
line_edit->grab_focus();
set_value(get_value() + (up ? get_step() : -get_step()));
@@ -133,17 +133,17 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
drag.allowed = true;
drag.capture_pos = mb->get_position();
} break;
- case MOUSE_BUTTON_RIGHT: {
+ case MouseButton::RIGHT: {
line_edit->grab_focus();
set_value((up ? get_max() : get_min()));
} break;
- case MOUSE_BUTTON_WHEEL_UP: {
+ case MouseButton::WHEEL_UP: {
if (line_edit->has_focus()) {
set_value(get_value() + get_step() * mb->get_factor());
accept_event();
}
} break;
- case MOUSE_BUTTON_WHEEL_DOWN: {
+ case MouseButton::WHEEL_DOWN: {
if (line_edit->has_focus()) {
set_value(get_value() - get_step() * mb->get_factor());
accept_event();
@@ -154,7 +154,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
//set_default_cursor_shape(CURSOR_ARROW);
range_click_timer->stop();
_release_mouse();
@@ -163,10 +163,10 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
- if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) {
if (drag.enabled) {
drag.diff_y += mm->get_relative().y;
- float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y);
+ float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SIGN(drag.diff_y);
set_value(CLAMP(drag.base_val + get_step() * diff_y, get_min(), get_max()));
} else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index 4736a1ad37..6b53c0220e 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -216,7 +216,7 @@ void SplitContainer::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
int sep = get_theme_constant(SNAME("separation"));
diff --git a/scene/gui/tabs.cpp b/scene/gui/tab_bar.cpp
index 0755a79eee..1dda29f668 100644
--- a/scene/gui/tabs.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* tabs.cpp */
+/* tab_bar.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "tabs.h"
+#include "tab_bar.h"
#include "core/object/message_queue.h"
#include "core/string/translation.h"
@@ -37,7 +37,7 @@
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
-Size2 Tabs::get_minimum_size() const {
+Size2 TabBar::get_minimum_size() const {
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected"));
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
@@ -90,7 +90,7 @@ Size2 Tabs::get_minimum_size() const {
return ms;
}
-void Tabs::gui_input(const Ref<InputEvent> &p_event) {
+void TabBar::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseMotion> mm = p_event;
@@ -143,7 +143,7 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (offset > 0) {
offset--;
@@ -152,7 +152,7 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (missing_right) {
offset++;
@@ -162,9 +162,9 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (rb_hover != -1) {
- //pressed
+ // pressed
emit_signal(SNAME("tab_rmb_clicked"), rb_hover);
}
@@ -172,17 +172,17 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
update();
}
- if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (cb_hover != -1) {
- //pressed
- emit_signal(SNAME("tab_closed"), cb_hover);
+ // pressed
+ emit_signal(SNAME("tab_close_pressed"), cb_hover);
}
cb_pressing = false;
update();
}
- if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) {
+ if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) {
// clicks
Point2 pos = mb->get_position();
@@ -222,6 +222,11 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ if (tabs.is_empty()) {
+ // Return early if there are no actual tabs to handle input for.
+ return;
+ }
+
int found = -1;
for (int i = offset; i <= max_drawn_tab; i++) {
if (tabs[i].rb_rect.has_point(pos)) {
@@ -252,7 +257,7 @@ void Tabs::gui_input(const Ref<InputEvent> &p_event) {
}
}
-void Tabs::_shape(int p_tab) {
+void TabBar::_shape(int p_tab) {
Ref<Font> font = get_theme_font(SNAME("font"));
int font_size = get_theme_font_size(SNAME("font_size"));
@@ -268,7 +273,7 @@ void Tabs::_shape(int p_tab) {
tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, (tabs[p_tab].language != "") ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale());
}
-void Tabs::_notification(int p_what) {
+void TabBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
_update_cache();
@@ -505,11 +510,11 @@ void Tabs::_notification(int p_what) {
}
}
-int Tabs::get_tab_count() const {
+int TabBar::get_tab_count() const {
return tabs.size();
}
-void Tabs::set_current_tab(int p_current) {
+void TabBar::set_current_tab(int p_current) {
if (current == p_current) {
return;
}
@@ -524,27 +529,27 @@ void Tabs::set_current_tab(int p_current) {
emit_signal(SNAME("tab_changed"), p_current);
}
-int Tabs::get_current_tab() const {
+int TabBar::get_current_tab() const {
return current;
}
-int Tabs::get_previous_tab() const {
+int TabBar::get_previous_tab() const {
return previous;
}
-int Tabs::get_hovered_tab() const {
+int TabBar::get_hovered_tab() const {
return hover;
}
-int Tabs::get_tab_offset() const {
+int TabBar::get_tab_offset() const {
return offset;
}
-bool Tabs::get_offset_buttons_visible() const {
+bool TabBar::get_offset_buttons_visible() const {
return buttons_visible;
}
-void Tabs::set_tab_title(int p_tab, const String &p_title) {
+void TabBar::set_tab_title(int p_tab, const String &p_title) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].text = p_title;
_shape(p_tab);
@@ -552,12 +557,12 @@ void Tabs::set_tab_title(int p_tab, const String &p_title) {
minimum_size_changed();
}
-String Tabs::get_tab_title(int p_tab) const {
+String TabBar::get_tab_title(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), "");
return tabs[p_tab].text;
}
-void Tabs::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) {
+void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) {
ERR_FAIL_INDEX(p_tab, tabs.size());
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (tabs[p_tab].text_direction != p_text_direction) {
@@ -567,19 +572,19 @@ void Tabs::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direc
}
}
-Control::TextDirection Tabs::get_tab_text_direction(int p_tab) const {
+Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Control::TEXT_DIRECTION_INHERITED);
return tabs[p_tab].text_direction;
}
-void Tabs::clear_tab_opentype_features(int p_tab) {
+void TabBar::clear_tab_opentype_features(int p_tab) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].opentype_features.clear();
_shape(p_tab);
update();
}
-void Tabs::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) {
+void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) {
ERR_FAIL_INDEX(p_tab, tabs.size());
int32_t tag = TS->name_to_tag(p_name);
if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) {
@@ -589,7 +594,7 @@ void Tabs::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value
}
}
-int Tabs::get_tab_opentype_feature(int p_tab, const String &p_name) const {
+int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1);
int32_t tag = TS->name_to_tag(p_name);
if (!tabs[p_tab].opentype_features.has(tag)) {
@@ -598,7 +603,7 @@ int Tabs::get_tab_opentype_feature(int p_tab, const String &p_name) const {
return tabs[p_tab].opentype_features[tag];
}
-void Tabs::set_tab_language(int p_tab, const String &p_language) {
+void TabBar::set_tab_language(int p_tab, const String &p_language) {
ERR_FAIL_INDEX(p_tab, tabs.size());
if (tabs[p_tab].language != p_language) {
tabs.write[p_tab].language = p_language;
@@ -607,35 +612,35 @@ void Tabs::set_tab_language(int p_tab, const String &p_language) {
}
}
-String Tabs::get_tab_language(int p_tab) const {
+String TabBar::get_tab_language(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), "");
return tabs[p_tab].language;
}
-void Tabs::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
+void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].icon = p_icon;
update();
minimum_size_changed();
}
-Ref<Texture2D> Tabs::get_tab_icon(int p_tab) const {
+Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>());
return tabs[p_tab].icon;
}
-void Tabs::set_tab_disabled(int p_tab, bool p_disabled) {
+void TabBar::set_tab_disabled(int p_tab, bool p_disabled) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].disabled = p_disabled;
update();
}
-bool Tabs::get_tab_disabled(int p_tab) const {
+bool TabBar::get_tab_disabled(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), false);
return tabs[p_tab].disabled;
}
-void Tabs::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button) {
+void TabBar::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].right_button = p_right_button;
_update_cache();
@@ -643,12 +648,12 @@ void Tabs::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button)
minimum_size_changed();
}
-Ref<Texture2D> Tabs::get_tab_right_button(int p_tab) const {
+Ref<Texture2D> TabBar::get_tab_right_button(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>());
return tabs[p_tab].right_button;
}
-void Tabs::_update_hover() {
+void TabBar::_update_hover() {
if (!is_inside_tree()) {
return;
}
@@ -685,7 +690,7 @@ void Tabs::_update_hover() {
}
}
-void Tabs::_update_cache() {
+void TabBar::_update_cache() {
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected"));
@@ -748,7 +753,7 @@ void Tabs::_update_cache() {
}
}
-void Tabs::_on_mouse_exited() {
+void TabBar::_on_mouse_exited() {
rb_hover = -1;
cb_hover = -1;
hover = -1;
@@ -756,7 +761,7 @@ void Tabs::_on_mouse_exited() {
update();
}
-void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
+void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
Tab t;
t.text = p_str;
t.xl_text = atr(p_str);
@@ -775,7 +780,7 @@ void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
minimum_size_changed();
}
-void Tabs::clear_tabs() {
+void TabBar::clear_tabs() {
tabs.clear();
current = 0;
previous = 0;
@@ -783,7 +788,7 @@ void Tabs::clear_tabs() {
update();
}
-void Tabs::remove_tab(int p_idx) {
+void TabBar::remove_tab(int p_idx) {
ERR_FAIL_INDEX(p_idx, tabs.size());
tabs.remove(p_idx);
if (current >= p_idx) {
@@ -805,7 +810,7 @@ void Tabs::remove_tab(int p_idx) {
_ensure_no_over_offset();
}
-Variant Tabs::get_drag_data(const Point2 &p_point) {
+Variant TabBar::get_drag_data(const Point2 &p_point) {
if (!drag_to_rearrange_enabled) {
return Variant();
}
@@ -839,7 +844,7 @@ Variant Tabs::get_drag_data(const Point2 &p_point) {
return drag_data;
}
-bool Tabs::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
+bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
if (!drag_to_rearrange_enabled) {
return false;
}
@@ -855,9 +860,9 @@ bool Tabs::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
if (from_path == to_path) {
return true;
} else if (get_tabs_rearrange_group() != -1) {
- // drag and drop between other Tabs
+ // drag and drop between other TabBars
Node *from_node = get_node(from_path);
- Tabs *from_tabs = Object::cast_to<Tabs>(from_node);
+ TabBar *from_tabs = Object::cast_to<TabBar>(from_node);
if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
return true;
}
@@ -866,7 +871,7 @@ bool Tabs::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
return false;
}
-void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) {
+void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
if (!drag_to_rearrange_enabled) {
return;
}
@@ -892,7 +897,7 @@ void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) {
} else if (get_tabs_rearrange_group() != -1) {
// drag and drop between Tabs
Node *from_node = get_node(from_path);
- Tabs *from_tabs = Object::cast_to<Tabs>(from_node);
+ TabBar *from_tabs = Object::cast_to<TabBar>(from_node);
if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
if (tab_from_id >= from_tabs->get_tab_count()) {
return;
@@ -912,7 +917,7 @@ void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) {
update();
}
-int Tabs::get_tab_idx_at_point(const Point2 &p_point) const {
+int TabBar::get_tab_idx_at_point(const Point2 &p_point) const {
int hover_now = -1;
for (int i = offset; i <= max_drawn_tab; i++) {
Rect2 rect = get_tab_rect(i);
@@ -924,17 +929,17 @@ int Tabs::get_tab_idx_at_point(const Point2 &p_point) const {
return hover_now;
}
-void Tabs::set_tab_align(TabAlign p_align) {
+void TabBar::set_tab_align(TabAlign p_align) {
ERR_FAIL_INDEX(p_align, ALIGN_MAX);
tab_align = p_align;
update();
}
-Tabs::TabAlign Tabs::get_tab_align() const {
+TabBar::TabAlign TabBar::get_tab_align() const {
return tab_align;
}
-void Tabs::set_clip_tabs(bool p_clip_tabs) {
+void TabBar::set_clip_tabs(bool p_clip_tabs) {
if (clip_tabs == p_clip_tabs) {
return;
}
@@ -943,11 +948,11 @@ void Tabs::set_clip_tabs(bool p_clip_tabs) {
minimum_size_changed();
}
-bool Tabs::get_clip_tabs() const {
+bool TabBar::get_clip_tabs() const {
return clip_tabs;
}
-void Tabs::move_tab(int from, int to) {
+void TabBar::move_tab(int from, int to) {
if (from == to) {
return;
}
@@ -963,7 +968,7 @@ void Tabs::move_tab(int from, int to) {
update();
}
-int Tabs::get_tab_width(int p_idx) const {
+int TabBar::get_tab_width(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, tabs.size(), 0);
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
@@ -1005,7 +1010,7 @@ int Tabs::get_tab_width(int p_idx) const {
return x;
}
-void Tabs::_ensure_no_over_offset() {
+void TabBar::_ensure_no_over_offset() {
if (!is_inside_tree()) {
return;
}
@@ -1031,7 +1036,7 @@ void Tabs::_ensure_no_over_offset() {
}
}
-void Tabs::ensure_tab_visible(int p_idx) {
+void TabBar::ensure_tab_visible(int p_idx) {
if (!is_inside_tree()) {
return;
}
@@ -1068,7 +1073,7 @@ void Tabs::ensure_tab_visible(int p_idx) {
}
}
-Rect2 Tabs::get_tab_rect(int p_tab) const {
+Rect2 TabBar::get_tab_rect(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2());
if (is_layout_rtl()) {
return Rect2(get_size().width - tabs[p_tab].ofs_cache - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
@@ -1077,97 +1082,97 @@ Rect2 Tabs::get_tab_rect(int p_tab) const {
}
}
-void Tabs::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) {
+void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) {
ERR_FAIL_INDEX(p_policy, CLOSE_BUTTON_MAX);
cb_displaypolicy = p_policy;
update();
}
-Tabs::CloseButtonDisplayPolicy Tabs::get_tab_close_display_policy() const {
+TabBar::CloseButtonDisplayPolicy TabBar::get_tab_close_display_policy() const {
return cb_displaypolicy;
}
-void Tabs::set_min_width(int p_width) {
+void TabBar::set_min_width(int p_width) {
min_width = p_width;
}
-void Tabs::set_scrolling_enabled(bool p_enabled) {
+void TabBar::set_scrolling_enabled(bool p_enabled) {
scrolling_enabled = p_enabled;
}
-bool Tabs::get_scrolling_enabled() const {
+bool TabBar::get_scrolling_enabled() const {
return scrolling_enabled;
}
-void Tabs::set_drag_to_rearrange_enabled(bool p_enabled) {
+void TabBar::set_drag_to_rearrange_enabled(bool p_enabled) {
drag_to_rearrange_enabled = p_enabled;
}
-bool Tabs::get_drag_to_rearrange_enabled() const {
+bool TabBar::get_drag_to_rearrange_enabled() const {
return drag_to_rearrange_enabled;
}
-void Tabs::set_tabs_rearrange_group(int p_group_id) {
+void TabBar::set_tabs_rearrange_group(int p_group_id) {
tabs_rearrange_group = p_group_id;
}
-int Tabs::get_tabs_rearrange_group() const {
+int TabBar::get_tabs_rearrange_group() const {
return tabs_rearrange_group;
}
-void Tabs::set_select_with_rmb(bool p_enabled) {
+void TabBar::set_select_with_rmb(bool p_enabled) {
select_with_rmb = p_enabled;
}
-bool Tabs::get_select_with_rmb() const {
+bool TabBar::get_select_with_rmb() const {
return select_with_rmb;
}
-void Tabs::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_update_hover"), &Tabs::_update_hover);
- ClassDB::bind_method(D_METHOD("get_tab_count"), &Tabs::get_tab_count);
- ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &Tabs::set_current_tab);
- ClassDB::bind_method(D_METHOD("get_current_tab"), &Tabs::get_current_tab);
- ClassDB::bind_method(D_METHOD("get_previous_tab"), &Tabs::get_previous_tab);
- ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &Tabs::set_tab_title);
- ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &Tabs::get_tab_title);
- ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &Tabs::set_tab_text_direction);
- ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &Tabs::get_tab_text_direction);
- ClassDB::bind_method(D_METHOD("set_tab_opentype_feature", "tab_idx", "tag", "values"), &Tabs::set_tab_opentype_feature);
- ClassDB::bind_method(D_METHOD("get_tab_opentype_feature", "tab_idx", "tag"), &Tabs::get_tab_opentype_feature);
- ClassDB::bind_method(D_METHOD("clear_tab_opentype_features", "tab_idx"), &Tabs::clear_tab_opentype_features);
- ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &Tabs::set_tab_language);
- ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &Tabs::get_tab_language);
- ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &Tabs::set_tab_icon);
- ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &Tabs::get_tab_icon);
- ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &Tabs::set_tab_disabled);
- ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &Tabs::get_tab_disabled);
- ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &Tabs::remove_tab);
- ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &Tabs::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>()));
- ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &Tabs::set_tab_align);
- ClassDB::bind_method(D_METHOD("get_tab_align"), &Tabs::get_tab_align);
- ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &Tabs::set_clip_tabs);
- ClassDB::bind_method(D_METHOD("get_clip_tabs"), &Tabs::get_clip_tabs);
- ClassDB::bind_method(D_METHOD("get_tab_offset"), &Tabs::get_tab_offset);
- ClassDB::bind_method(D_METHOD("get_offset_buttons_visible"), &Tabs::get_offset_buttons_visible);
- ClassDB::bind_method(D_METHOD("ensure_tab_visible", "idx"), &Tabs::ensure_tab_visible);
- ClassDB::bind_method(D_METHOD("get_tab_rect", "tab_idx"), &Tabs::get_tab_rect);
- ClassDB::bind_method(D_METHOD("move_tab", "from", "to"), &Tabs::move_tab);
- ClassDB::bind_method(D_METHOD("set_tab_close_display_policy", "policy"), &Tabs::set_tab_close_display_policy);
- ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &Tabs::get_tab_close_display_policy);
- ClassDB::bind_method(D_METHOD("set_scrolling_enabled", "enabled"), &Tabs::set_scrolling_enabled);
- ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &Tabs::get_scrolling_enabled);
- ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &Tabs::set_drag_to_rearrange_enabled);
- ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &Tabs::get_drag_to_rearrange_enabled);
- ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &Tabs::set_tabs_rearrange_group);
- ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &Tabs::get_tabs_rearrange_group);
-
- ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &Tabs::set_select_with_rmb);
- ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &Tabs::get_select_with_rmb);
+void TabBar::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_update_hover"), &TabBar::_update_hover);
+ ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count);
+ ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabBar::set_current_tab);
+ ClassDB::bind_method(D_METHOD("get_current_tab"), &TabBar::get_current_tab);
+ ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabBar::get_previous_tab);
+ ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title);
+ ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title);
+ ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction);
+ ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction);
+ ClassDB::bind_method(D_METHOD("set_tab_opentype_feature", "tab_idx", "tag", "values"), &TabBar::set_tab_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_tab_opentype_feature", "tab_idx", "tag"), &TabBar::get_tab_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_tab_opentype_features", "tab_idx"), &TabBar::clear_tab_opentype_features);
+ ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language);
+ ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language);
+ ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon);
+ ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon);
+ ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled);
+ ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabBar::get_tab_disabled);
+ ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &TabBar::remove_tab);
+ ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &TabBar::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &TabBar::set_tab_align);
+ ClassDB::bind_method(D_METHOD("get_tab_align"), &TabBar::get_tab_align);
+ ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &TabBar::set_clip_tabs);
+ ClassDB::bind_method(D_METHOD("get_clip_tabs"), &TabBar::get_clip_tabs);
+ ClassDB::bind_method(D_METHOD("get_tab_offset"), &TabBar::get_tab_offset);
+ ClassDB::bind_method(D_METHOD("get_offset_buttons_visible"), &TabBar::get_offset_buttons_visible);
+ ClassDB::bind_method(D_METHOD("ensure_tab_visible", "idx"), &TabBar::ensure_tab_visible);
+ ClassDB::bind_method(D_METHOD("get_tab_rect", "tab_idx"), &TabBar::get_tab_rect);
+ ClassDB::bind_method(D_METHOD("move_tab", "from", "to"), &TabBar::move_tab);
+ ClassDB::bind_method(D_METHOD("set_tab_close_display_policy", "policy"), &TabBar::set_tab_close_display_policy);
+ ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &TabBar::get_tab_close_display_policy);
+ ClassDB::bind_method(D_METHOD("set_scrolling_enabled", "enabled"), &TabBar::set_scrolling_enabled);
+ ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &TabBar::get_scrolling_enabled);
+ ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabBar::set_drag_to_rearrange_enabled);
+ ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled);
+ ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group);
+ ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group);
+
+ ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &TabBar::set_select_with_rmb);
+ ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &TabBar::get_select_with_rmb);
ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("tab_rmb_clicked", PropertyInfo(Variant::INT, "tab")));
- ADD_SIGNAL(MethodInfo("tab_closed", PropertyInfo(Variant::INT, "tab")));
+ ADD_SIGNAL(MethodInfo("tab_close_pressed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to")));
ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab")));
@@ -1190,6 +1195,6 @@ void Tabs::_bind_methods() {
BIND_ENUM_CONSTANT(CLOSE_BUTTON_MAX);
}
-Tabs::Tabs() {
- connect("mouse_exited", callable_mp(this, &Tabs::_on_mouse_exited));
+TabBar::TabBar() {
+ connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited));
}
diff --git a/scene/gui/tabs.h b/scene/gui/tab_bar.h
index b044453803..411a62b1d9 100644
--- a/scene/gui/tabs.h
+++ b/scene/gui/tab_bar.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* tabs.h */
+/* tab_bar.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,14 +28,14 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef TABS_H
-#define TABS_H
+#ifndef TAB_BAR_H
+#define TAB_BAR_H
#include "scene/gui/control.h"
#include "scene/resources/text_line.h"
-class Tabs : public Control {
- GDCLASS(Tabs, Control);
+class TabBar : public Control {
+ GDCLASS(TabBar, Control);
public:
enum TabAlign {
@@ -83,7 +83,6 @@ private:
Vector<Tab> tabs;
int current = 0;
int previous = 0;
- int _get_top_margin() const;
TabAlign tab_align = ALIGN_CENTER;
bool clip_tabs = true;
int rb_hover = -1;
@@ -187,10 +186,10 @@ public:
Rect2 get_tab_rect(int p_tab) const;
Size2 get_minimum_size() const override;
- Tabs();
+ TabBar();
};
-VARIANT_ENUM_CAST(Tabs::TabAlign);
-VARIANT_ENUM_CAST(Tabs::CloseButtonDisplayPolicy);
+VARIANT_ENUM_CAST(TabBar::TabAlign);
+VARIANT_ENUM_CAST(TabBar::CloseButtonDisplayPolicy);
-#endif // TABS_H
+#endif // TAB_BAR_H
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index c8a0501d8a..ff53d91ea3 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -78,7 +78,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) {
Popup *popup = get_popup();
- if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Point2 pos = mb->get_position();
Size2 size = get_size();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index dc9a3f337e..fbfeea5722 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()) {
@@ -1403,7 +1407,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mb->is_pressed()) {
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) {
if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
@@ -1414,7 +1418,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_scroll_up(3 * mb->get_factor());
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) {
if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
@@ -1425,13 +1429,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_scroll_down(3 * mb->get_factor());
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) {
+ if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT) {
+ if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
}
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
_reset_caret_blink_timer();
Point2i pos = get_line_column_at_pos(mpos);
@@ -1534,7 +1538,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
update();
}
- if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) {
+ if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ paste_primary_clipboard();
+ }
+
+ if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
_reset_caret_blink_timer();
Point2i pos = get_line_column_at_pos(mpos);
@@ -1566,11 +1574,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
grab_focus();
}
} else {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
+ }
}
// Notify to show soft keyboard.
@@ -1602,7 +1613,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
- if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging.
+ if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging.
_reset_caret_blink_timer();
if (draw_minimap && !dragging_selection) {
@@ -1670,7 +1681,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
// If a modifier has been pressed, and nothing else, return.
- if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) {
+ if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) {
return;
}
@@ -1887,7 +1898,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
// Handle Unicode (if no modifiers active). Tab has a value of 0x09.
- if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == KEY_TAB)) {
+ if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) {
handle_unicode_input(k->get_unicode());
accept_event();
return;
@@ -2596,6 +2607,14 @@ bool TextEdit::is_virtual_keyboard_enabled() const {
return virtual_keyboard_enabled;
}
+void TextEdit::set_middle_mouse_paste_enabled(bool p_enabled) {
+ middle_mouse_paste_enabled = p_enabled;
+}
+
+bool TextEdit::is_middle_mouse_paste_enabled() const {
+ return middle_mouse_paste_enabled;
+}
+
// Text manipulation
void TextEdit::clear() {
setting_text = true;
@@ -2915,6 +2934,13 @@ void TextEdit::paste() {
_paste_internal();
}
+void TextEdit::paste_primary_clipboard() {
+ if (GDVIRTUAL_CALL(_paste_primary_clipboard)) {
+ return;
+ }
+ _paste_primary_clipboard_internal();
+}
+
// Context menu.
PopupMenu *TextEdit::get_menu() const {
const_cast<TextEdit *>(this)->_generate_context_menu();
@@ -3370,7 +3396,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const {
int wrap_index = 0;
if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
- Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SGN(rows)));
+ Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows)));
wrap_index = f_ofs.y;
if (rows < 0) {
row = first_vis_line - (f_ofs.x - 1);
@@ -3445,7 +3471,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const {
int row = minimap_line + Math::floor(rows);
if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
- int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SGN(rows))).x - 1;
+ int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1;
if (rows < 0) {
row = minimap_line - f_ofs;
} else {
@@ -3623,6 +3649,8 @@ int TextEdit::get_caret_wrap_index() const {
}
String TextEdit::get_word_under_caret() const {
+ ERR_FAIL_INDEX_V(caret.line, text.size(), "");
+ ERR_FAIL_INDEX_V(caret.column, text[caret.line].length() + 1, "");
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
for (int i = 0; i < words.size(); i = i + 2) {
if (words[i] <= caret.column && words[i + 1] > caret.column) {
@@ -3645,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;
}
@@ -3658,8 +3697,10 @@ void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column
if (p_line >= 0) {
ERR_FAIL_INDEX(p_line, text.size());
selection.selecting_line = p_line;
+ selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length());
}
if (p_column >= 0) {
+ ERR_FAIL_INDEX(selection.selecting_line, text.size());
ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length());
selection.selecting_column = p_column;
}
@@ -4037,7 +4078,7 @@ int TextEdit::get_visible_line_count() const {
}
int TextEdit::get_total_visible_line_count() const {
- /* Returns the total number of (lines + wraped - hidden). */
+ /* Returns the total number of (lines + wrapped - hidden). */
if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
return text.size();
}
@@ -4534,6 +4575,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enabled"), &TextEdit::set_virtual_keyboard_enabled);
ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled);
+ ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enabled"), &TextEdit::set_middle_mouse_paste_enabled);
+ ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled);
+
// Text manipulation
ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear);
@@ -4573,6 +4617,7 @@ void TextEdit::_bind_methods() {
GDVIRTUAL_BIND(_cut)
GDVIRTUAL_BIND(_copy)
GDVIRTUAL_BIND(_paste)
+ GDVIRTUAL_BIND(_paste_primary_clipboard)
// Context Menu
BIND_ENUM_CONSTANT(MENU_CUT);
@@ -4688,6 +4733,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);
@@ -4847,8 +4895,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");
@@ -5139,6 +5188,24 @@ void TextEdit::_paste_internal() {
end_complex_operation();
}
+void TextEdit::_paste_primary_clipboard_internal() {
+ if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ return;
+ }
+
+ String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary();
+
+ Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
+ deselect();
+ set_caret_line(pos.y, true, false);
+ set_caret_column(pos.x);
+ if (!paste_buffer.is_empty()) {
+ insert_text_at_caret(paste_buffer);
+ }
+
+ grab_focus();
+}
+
/* Text. */
// Context menu.
void TextEdit::_generate_context_menu() {
@@ -5148,32 +5215,32 @@ void TextEdit::_generate_context_menu() {
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
- menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED);
- menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO);
- menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR);
- menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
+ menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
+ menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
+ menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
+ menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
menu_ctl = memnew(PopupMenu);
menu_ctl->set_name("CTLMenu");
- menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM);
- menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM);
- menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE);
- menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE);
- menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO);
- menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO);
- menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF);
+ menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
+ menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
+ menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
+ menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
+ menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
+ menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
+ menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
menu_ctl->add_separator();
- menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM);
- menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI);
- menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI);
- menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI);
- menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI);
+ menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
+ menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
+ menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
+ menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
+ menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
menu_ctl->add_separator();
- menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ);
- menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
- menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
- menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
+ menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
+ menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
+ menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
+ menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
@@ -5184,29 +5251,29 @@ void TextEdit::_generate_context_menu() {
// Reorganize context menu.
menu->clear();
if (editable) {
- menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0);
+ menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE);
}
- menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0);
+ menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
if (editable) {
- menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0);
+ menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE);
}
menu->add_separator();
if (is_selecting_enabled()) {
- menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0);
+ menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
}
if (editable) {
menu->add_item(RTR("Clear"), MENU_CLEAR);
menu->add_separator();
- menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0);
- menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0);
+ menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE);
+ menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE);
}
menu->add_separator();
- menu->add_submenu_item(RTR("Text writing direction"), "DirMenu");
+ menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu");
menu->add_separator();
- menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC);
+ menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
if (editable) {
- menu->add_submenu_item(RTR("Insert control character"), "CTLMenu");
+ menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu");
}
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
@@ -5219,25 +5286,25 @@ void TextEdit::_generate_context_menu() {
}
}
-int TextEdit::_get_menu_action_accelerator(const String &p_action) {
+Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
if (!events) {
- return 0;
+ return Key::NONE;
}
// Use first event in the list for the accelerator.
const List<Ref<InputEvent>>::Element *first_event = events->front();
if (!first_event) {
- return 0;
+ return Key::NONE;
}
const Ref<InputEventKey> event = first_event->get();
if (event.is_null()) {
- return 0;
+ return Key::NONE;
}
// Use physical keycode if non-zero
- if (event->get_physical_keycode() != 0) {
+ if (event->get_physical_keycode() != Key::NONE) {
return event->get_physical_keycode_with_modifiers();
} else {
return event->get_keycode_with_modifiers();
@@ -5392,10 +5459,10 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const {
/* Selection */
void TextEdit::_click_selection_held() {
- // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
+ // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
// and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem.
// I'm unsure if there's an actual fix that doesn't have a ton of side effects.
- if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) {
+ if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) {
switch (selection.selecting_mode) {
case SelectionMode::SELECTION_MODE_POINTER: {
_update_selection_mode_pointer();
@@ -5475,6 +5542,10 @@ void TextEdit::_update_selection_mode_word() {
}
}
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
+ }
+
update();
click_select_held->start();
@@ -5502,6 +5573,10 @@ void TextEdit::_update_selection_mode_line() {
set_caret_column(0);
select(selection.selecting_line, selection.selecting_column, line, col);
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
+ }
+
update();
click_select_held->start();
@@ -5687,7 +5762,7 @@ double TextEdit::_get_v_scroll_offset() const {
}
void TextEdit::_scroll_up(real_t p_delta) {
- if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) {
+ if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(-p_delta)) {
scrolling = false;
minimap_clicked = false;
}
@@ -5714,7 +5789,7 @@ void TextEdit::_scroll_up(real_t p_delta) {
}
void TextEdit::_scroll_down(real_t p_delta) {
- if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) {
+ if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(p_delta)) {
scrolling = false;
minimap_clicked = false;
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 16c1ee9ff9..1a7dc851b5 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -269,6 +269,7 @@ private:
bool context_menu_enabled = true;
bool shortcut_keys_enabled = true;
bool virtual_keyboard_enabled = true;
+ bool middle_mouse_paste_enabled = true;
// Overridable actions
String cut_copy_line = "";
@@ -279,7 +280,7 @@ private:
PopupMenu *menu_ctl = nullptr;
void _generate_context_menu();
- int _get_menu_action_accelerator(const String &p_action);
+ Key _get_menu_action_accelerator(const String &p_action);
/* Versioning */
struct TextOperation {
@@ -394,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);
@@ -586,12 +588,14 @@ protected:
virtual void _cut_internal();
virtual void _copy_internal();
virtual void _paste_internal();
+ virtual void _paste_primary_clipboard_internal();
GDVIRTUAL1(_handle_unicode_input, int)
GDVIRTUAL0(_backspace)
GDVIRTUAL0(_cut)
GDVIRTUAL0(_copy)
GDVIRTUAL0(_paste)
+ GDVIRTUAL0(_paste_primary_clipboard)
public:
/* General overrides. */
@@ -640,6 +644,9 @@ public:
void set_virtual_keyboard_enabled(bool p_enabled);
bool is_virtual_keyboard_enabled() const;
+ void set_middle_mouse_paste_enabled(bool p_enabled);
+ bool is_middle_mouse_paste_enabled() const;
+
// Text manipulation
void clear();
@@ -674,6 +681,7 @@ public:
void cut();
void copy();
void paste();
+ void paste_primary_clipboard();
// Context menu.
PopupMenu *get_menu() const;
@@ -746,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/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 35a098c6fd..fe11de128a 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -341,7 +341,11 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
} break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
double center_mapped_from_real_width = (width_total * 0.5 - topleft.x) / max_middle_real_size * max_middle_texture_size + topleft.x;
- double drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x);
+ double drift_from_unscaled_center = 0;
+ if (bottomright.y != topleft.y) { // To avoid division by zero.
+ drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x);
+ }
+
src_rect.position.x += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
src_rect.size.x = width_texture;
dst_rect.position.x += (width_total - width_filled) * 0.5;
@@ -351,7 +355,11 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
double center_mapped_from_real_width = (width_total * 0.5 - topleft.y) / max_middle_real_size * max_middle_texture_size + topleft.y;
- double drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y);
+ double drift_from_unscaled_center = 0;
+ if (bottomright.y != topleft.y) { // To avoid division by zero.
+ drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y);
+ }
+
src_rect.position.y += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
src_rect.size.y = width_texture;
dst_rect.position.y += (width_total - width_filled) * 0.5;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 3f041bf65a..27e617bdd0 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1955,7 +1955,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].custom_button) {
if (cache.hover_item == p_item && cache.hover_cell == i) {
- if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) {
+ if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
draw_style_box(cache.custom_button_pressed, ir);
} else {
draw_style_box(cache.custom_button_hover, ir);
@@ -2256,7 +2256,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) {
}
void Tree::_range_click_timeout() {
- if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) {
+ if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
Point2 pos = get_local_mouse_position() - cache.bg->get_offset();
if (show_column_titles) {
pos.y -= _get_title_button_height();
@@ -2284,7 +2284,7 @@ void Tree::_range_click_timeout() {
propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case)
blocked++;
- propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MOUSE_BUTTON_LEFT, mb);
+ propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MouseButton::LEFT, mb);
blocked--;
if (range_click_timer->is_one_shot()) {
@@ -2307,7 +2307,7 @@ void Tree::_range_click_timeout() {
}
}
-int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) {
+int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) {
int item_h = compute_item_height(p_item) + cache.vseparation;
bool skip = (p_item == root && hide_root);
@@ -2427,7 +2427,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width -= w + cache.button_margin;
}
- if (p_button == MOUSE_BUTTON_LEFT || (p_button == MOUSE_BUTTON_RIGHT && allow_rmb_select)) {
+ if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) {
/* process selection */
if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check
@@ -2439,10 +2439,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) {
- if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) {
+ if (!c.selected || p_button == MouseButton::RIGHT) {
p_item->select(col);
emit_signal(SNAME("multi_selected"), p_item, col, true);
- if (p_button == MOUSE_BUTTON_RIGHT) {
+ if (p_button == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
}
@@ -2459,21 +2459,21 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
bool inrange = false;
select_single_item(p_item, root, col, selected_item, &inrange);
- if (p_button == MOUSE_BUTTON_RIGHT) {
+ if (p_button == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
}
} else {
int icount = _count_selected_items(root);
- if (select_mode == SELECT_MULTI && icount > 1 && p_button != MOUSE_BUTTON_RIGHT) {
+ if (select_mode == SELECT_MULTI && icount > 1 && p_button != MouseButton::RIGHT) {
single_select_defer = p_item;
single_select_defer_column = col;
} else {
- if (p_button != MOUSE_BUTTON_RIGHT || !c.selected) {
+ if (p_button != MouseButton::RIGHT || !c.selected) {
select_single_item(p_item, root, col);
}
- if (p_button == MOUSE_BUTTON_RIGHT) {
+ if (p_button == MouseButton::RIGHT) {
emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
}
}
@@ -2543,7 +2543,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
/* touching the combo */
bool up = p_pos.y < (item_h / 2);
- if (p_button == MOUSE_BUTTON_LEFT) {
+ if (p_button == MouseButton::LEFT) {
if (range_click_timer->get_time_left() == 0) {
range_item_last = p_item;
range_up_last = up;
@@ -2560,13 +2560,13 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
item_edited(col, p_item);
- } else if (p_button == MOUSE_BUTTON_RIGHT) {
+ } else if (p_button == MouseButton::RIGHT) {
p_item->set_range(col, (up ? c.max : c.min));
item_edited(col, p_item);
- } else if (p_button == MOUSE_BUTTON_WHEEL_UP) {
+ } else if (p_button == MouseButton::WHEEL_UP) {
p_item->set_range(col, c.val + c.step);
item_edited(col, p_item);
- } else if (p_button == MOUSE_BUTTON_WHEEL_DOWN) {
+ } else if (p_button == MouseButton::WHEEL_DOWN) {
p_item->set_range(col, c.val - c.step);
item_edited(col, p_item);
}
@@ -2599,14 +2599,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
if (!p_item->cells[col].custom_button || !on_arrow) {
- item_edited(col, p_item, p_button == MOUSE_BUTTON_LEFT);
+ item_edited(col, p_item, p_button == MouseButton::LEFT);
}
click_handled = true;
return -1;
} break;
};
- if (!bring_up_editor || p_button != MOUSE_BUTTON_LEFT) {
+ if (!bring_up_editor || p_button != MouseButton::LEFT) {
return -1;
}
@@ -2646,7 +2646,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
item_h += child_h;
}
}
- if (p_item == root && p_button == MOUSE_BUTTON_RIGHT) {
+ if (p_item == root && p_button == MouseButton::RIGHT) {
emit_signal(SNAME("empty_rmb"), get_local_mouse_position());
}
}
@@ -2655,9 +2655,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
void Tree::_text_editor_modal_close() {
- if (Input::get_singleton()->is_key_pressed(KEY_ESCAPE) ||
- Input::get_singleton()->is_key_pressed(KEY_KP_ENTER) ||
- Input::get_singleton()->is_key_pressed(KEY_ENTER)) {
+ if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
+ Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
+ Input::get_singleton()->is_key_pressed(Key::ENTER)) {
return;
}
@@ -3048,7 +3048,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
return;
} else {
- if (k->get_keycode() != KEY_SHIFT) {
+ if (k->get_keycode() != Key::SHIFT) {
last_keypress = 0;
}
}
@@ -3164,7 +3164,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} else {
const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col];
float diff_y = -mm->get_relative().y;
- diff_y = Math::pow(ABS(diff_y), 1.8f) * SGN(diff_y);
+ diff_y = Math::pow(ABS(diff_y), 1.8f) * SIGN(diff_y);
diff_y *= 0.1;
range_drag_base = CLAMP(range_drag_base + c.step * diff_y, c.min, c.max);
popup_edited_item->set_range(popup_edited_item_col, range_drag_base);
@@ -3189,7 +3189,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
bool rtl = is_layout_rtl();
if (!b->is_pressed()) {
- if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (b->get_button_index() == MouseButton::LEFT) {
Point2 pos = b->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
@@ -3270,8 +3270,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
switch (b->get_button_index()) {
- case MOUSE_BUTTON_RIGHT:
- case MOUSE_BUTTON_LEFT: {
+ case MouseButton::RIGHT:
+ case MouseButton::LEFT: {
Ref<StyleBox> bg = cache.bg;
Point2 pos = b->get_position();
@@ -3284,7 +3284,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pos.y -= _get_title_button_height();
if (pos.y < 0) {
- if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (b->get_button_index() == MouseButton::LEFT) {
pos.x += cache.offset.x;
int len = 0;
for (int i = 0; i < columns.size(); i++) {
@@ -3302,7 +3302,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
if (!root || (!root->get_first_child() && hide_root)) {
- if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) {
+ if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) {
emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position());
}
break;
@@ -3329,7 +3329,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (b->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (b->get_button_index() == MouseButton::RIGHT) {
break;
}
@@ -3352,7 +3352,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
set_physics_process_internal(true);
}
- if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (b->get_button_index() == MouseButton::LEFT) {
if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
@@ -3365,7 +3365,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
} break;
- case MOUSE_BUTTON_WHEEL_UP: {
+ case MouseButton::WHEEL_UP: {
double prev_value = v_scroll->get_value();
v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
@@ -3373,7 +3373,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
} break;
- case MOUSE_BUTTON_WHEEL_DOWN: {
+ case MouseButton::WHEEL_DOWN: {
double prev_value = v_scroll->get_value();
v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
@@ -3570,7 +3570,9 @@ int Tree::_get_title_button_height() const {
void Tree::_notification(int p_what) {
if (p_what == NOTIFICATION_FOCUS_ENTER) {
- focus_in_id = get_viewport()->get_processed_events_count();
+ if (get_viewport()) {
+ focus_in_id = get_viewport()->get_processed_events_count();
+ }
}
if (p_what == NOTIFICATION_MOUSE_EXIT) {
if (cache.hover_type != Cache::CLICK_NONE) {
@@ -4035,7 +4037,7 @@ int Tree::get_column_minimum_width(int p_column) const {
// Check if the visible title of the column is wider.
if (show_column_titles) {
- min_width = MAX(cache.font->get_string_size(columns[p_column].title).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width);
+ min_width = MAX(cache.font->get_string_size(columns[p_column].title, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width);
}
if (!columns[p_column].clip_content) {
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 6ca9458e9b..2e4e1bd364 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -462,7 +462,7 @@ private:
void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item);
void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
- int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod);
+ int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod);
void _text_editor_submit(String p_text);
void _text_editor_modal_close();
void value_editor_changed(double p_value);
diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp
index 8734037a57..989aabc549 100644
--- a/scene/gui/video_player.cpp
+++ b/scene/gui/video_player.cpp
@@ -29,9 +29,9 @@
/*************************************************************************/
#include "video_player.h"
-#include "scene/scene_string_names.h"
#include "core/os/os.h"
+#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
int VideoPlayer::sp_get_channel_count() const {
@@ -55,7 +55,7 @@ bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) {
return false;
}
-// Called from main thread (eg VideoStreamPlaybackWebm::update)
+// Called from main thread (e.g. VideoStreamPlaybackTheora::update).
int VideoPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_frames) {
ERR_FAIL_NULL_V(p_udata, 0);
ERR_FAIL_NULL_V(p_data, 0);
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 916833c9a7..22e3c3bf24 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -274,6 +274,7 @@ void CanvasItem::_exit_canvas() {
void CanvasItem::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_FAIL_COND(!is_inside_tree());
_update_texture_filter_changed(false);
_update_texture_repeat_changed(false);
@@ -892,9 +893,9 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("draw_primitive", "points", "colors", "uvs", "texture", "width"), &CanvasItem::draw_primitive, DEFVAL(Ref<Texture2D>()), DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("draw_polygon", "points", "colors", "uvs", "texture"), &CanvasItem::draw_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("draw_colored_polygon", "points", "color", "uvs", "texture"), &CanvasItem::draw_colored_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref<Texture2D>()));
- ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &CanvasItem::draw_char, DEFVAL(""), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
+ ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &CanvasItem::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &CanvasItem::draw_char, DEFVAL(""), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "transform", "modulate"), &CanvasItem::draw_mesh, DEFVAL(Transform2D()), DEFVAL(Color(1, 1, 1, 1)));
ClassDB::bind_method(D_METHOD("draw_multimesh", "multimesh", "texture"), &CanvasItem::draw_multimesh);
ClassDB::bind_method(D_METHOD("draw_set_transform", "position", "rotation", "scale"), &CanvasItem::draw_set_transform, DEFVAL(0.0), DEFVAL(Size2(1.0, 1.0)));
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index ba9f47119d..04376ad809 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -34,10 +34,10 @@
#include "scene/main/node.h"
#include "scene/main/scene_tree.h"
#include "scene/resources/canvas_item_material.h"
+#include "scene/resources/font.h"
#include "servers/text_server.h"
class CanvasLayer;
-class Font;
class MultiMesh;
class StyleBox;
class Window;
@@ -235,9 +235,9 @@ public:
void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture);
- void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- real_t draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
+ void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ real_t draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", int p_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0));
void draw_set_transform_matrix(const Transform2D &p_matrix);
diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp
index 4540e42b4c..cd7534f73c 100644
--- a/scene/main/canvas_layer.cpp
+++ b/scene/main/canvas_layer.cpp
@@ -128,7 +128,7 @@ void CanvasLayer::_notification(int p_what) {
} else {
vp = Node::get_viewport();
}
- ERR_FAIL_COND(!vp);
+ ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized.");
vp->_canvas_layer_add(this);
viewport = vp->get_viewport_rid();
@@ -140,6 +140,8 @@ void CanvasLayer::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized.");
+
vp->_canvas_layer_remove(this);
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, canvas);
viewport = RID();
@@ -160,6 +162,8 @@ Size2 CanvasLayer::get_viewport_size() const {
return Size2(1, 1);
}
+ ERR_FAIL_NULL_V_MSG(vp, Size2(1, 1), "Viewport is not initialized.");
+
Rect2 r = vp->get_visible_rect();
return r.size;
}
@@ -169,7 +173,7 @@ RID CanvasLayer::get_viewport() const {
}
void CanvasLayer::set_custom_viewport(Node *p_viewport) {
- ERR_FAIL_NULL(p_viewport);
+ ERR_FAIL_NULL_MSG(p_viewport, "Cannot set viewport to nullptr.");
if (is_inside_tree()) {
vp->_canvas_layer_remove(this);
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, canvas);
@@ -252,7 +256,7 @@ void CanvasLayer::_update_follow_viewport(bool p_force_exit) {
void CanvasLayer::_validate_property(PropertyInfo &property) const {
if (!follow_viewport && property.name == "follow_viewport_scale") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 189aebb47d..44420fcc31 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -908,17 +908,12 @@ void Node::set_name(const String &p_name) {
}
}
-static bool node_hrcr = false;
static SafeRefCount node_hrcr_count;
void Node::init_node_hrcr() {
node_hrcr_count.init(1);
}
-void Node::set_human_readable_collision_renaming(bool p_enabled) {
- node_hrcr = p_enabled;
-}
-
#ifdef TOOLS_ENABLED
String Node::validate_child_name(Node *p_child) {
StringName name = p_child->data.name;
@@ -930,9 +925,8 @@ String Node::validate_child_name(Node *p_child) {
void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) {
/* Make sure the name is unique */
- if (node_hrcr || p_force_human_readable) {
+ if (p_force_human_readable) {
//this approach to autoset node names is human readable but very slow
- //it's turned on while running in the editor
StringName name = p_child->data.name;
_generate_serial_child_name(p_child, name);
@@ -1899,6 +1893,56 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const {
return node;
}
+#ifdef TOOLS_ENABLED
+void Node::set_property_pinned(const String &p_property, bool p_pinned) {
+ bool current_pinned = false;
+ bool has_pinned = has_meta("_edit_pinned_properties_");
+ Array pinned;
+ String psa = get_property_store_alias(p_property);
+ if (has_pinned) {
+ pinned = get_meta("_edit_pinned_properties_");
+ current_pinned = pinned.has(psa);
+ }
+
+ if (current_pinned != p_pinned) {
+ if (p_pinned) {
+ pinned.append(psa);
+ if (!has_pinned) {
+ set_meta("_edit_pinned_properties_", pinned);
+ }
+ } else {
+ pinned.erase(psa);
+ if (pinned.is_empty()) {
+ remove_meta("_edit_pinned_properties_");
+ }
+ }
+ }
+}
+
+bool Node::is_property_pinned(const StringName &p_property) const {
+ if (!has_meta("_edit_pinned_properties_")) {
+ return false;
+ }
+ Array pinned = get_meta("_edit_pinned_properties_");
+ String psa = get_property_store_alias(p_property);
+ return pinned.has(psa);
+}
+
+StringName Node::get_property_store_alias(const StringName &p_property) const {
+ return p_property;
+}
+#endif
+
+void Node::get_storable_properties(Set<StringName> &r_storable_properties) const {
+ List<PropertyInfo> pi;
+ get_property_list(&pi);
+ for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) {
+ if ((E->get().usage & PROPERTY_USAGE_STORAGE)) {
+ r_storable_properties.insert(E->get().name);
+ }
+ }
+}
+
String Node::to_string() {
if (get_script_instance()) {
bool valid;
@@ -2750,7 +2794,11 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path);
ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path);
- ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "_import_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_import_path", "_get_import_path");
+#ifdef TOOLS_ENABLED
+ ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned);
+#endif
+
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "_import_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_import_path", "_get_import_path");
{
MethodInfo mi;
@@ -2841,7 +2889,7 @@ void Node::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority");
ADD_GROUP("Editor Description", "editor_");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "set_editor_description", "get_editor_description");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT), "set_editor_description", "get_editor_description");
GDVIRTUAL_BIND(_process, "delta");
GDVIRTUAL_BIND(_physics_process, "delta");
diff --git a/scene/main/node.h b/scene/main/node.h
index e59a7a390a..2dd32a2e1d 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -363,6 +363,13 @@ public:
bool is_editable_instance(const Node *p_node) const;
Node *get_deepest_editable_node(Node *p_start_node) const;
+#ifdef TOOLS_ENABLED
+ void set_property_pinned(const String &p_property, bool p_pinned);
+ bool is_property_pinned(const StringName &p_property) const;
+ virtual StringName get_property_store_alias(const StringName &p_property) const;
+#endif
+ void get_storable_properties(Set<StringName> &r_storable_properties) const;
+
virtual String to_string() override;
/* NOTIFICATIONS */
@@ -437,7 +444,6 @@ public:
void queue_delete();
//hacks for speed
- static void set_human_readable_collision_renaming(bool p_enabled);
static void init_node_hrcr();
void force_parent_owned() { data.parent_owned = true; } //hack to avoid duplicate nodes
diff --git a/scene/main/resource_preloader.cpp b/scene/main/resource_preloader.cpp
index f4c90ee668..c44b55284d 100644
--- a/scene/main/resource_preloader.cpp
+++ b/scene/main/resource_preloader.cpp
@@ -147,7 +147,7 @@ void ResourcePreloader::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_resource", "name"), &ResourcePreloader::get_resource);
ClassDB::bind_method(D_METHOD("get_resource_list"), &ResourcePreloader::_get_resource_list);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "resources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_resources", "_get_resources");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "resources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_resources", "_get_resources");
}
ResourcePreloader::ResourcePreloader() {
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 19331c1906..7a4f9f9c52 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -136,7 +136,6 @@ private:
void _flush_ugc();
_FORCE_INLINE_ void _update_group_order(Group &g, bool p_use_priority = false);
- void _update_listener();
Array _get_nodes_in_group(const StringName &p_group);
@@ -265,9 +264,6 @@ public:
void set_pause(bool p_enabled);
bool is_paused() const;
- void set_camera(const RID &p_camera);
- RID get_camera() const;
-
#ifdef DEBUG_ENABLED
void set_debug_collisions_hint(bool p_enabled);
bool is_debugging_collisions_hint() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 3ab6e12e01..1ecc3c762a 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -584,9 +584,9 @@ void Viewport::_process_picking() {
physics_last_mouse_state.meta = mb->is_meta_pressed();
if (mb->is_pressed()) {
- physics_last_mouse_state.mouse_mask |= (MouseButton)(1 << (mb->get_button_index() - 1));
+ physics_last_mouse_state.mouse_mask |= mouse_button_to_mask(mb->get_button_index());
} else {
- physics_last_mouse_state.mouse_mask &= (MouseButton) ~(1 << (mb->get_button_index() - 1));
+ physics_last_mouse_state.mouse_mask &= ~mouse_button_to_mask(mb->get_button_index());
// If touch mouse raised, assume we don't know last mouse pos until new events come
if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) {
@@ -638,7 +638,13 @@ void Viewport::_process_picking() {
Vector2 point = canvas_transform.affine_inverse().xform(pos);
- int rc = ss2d->intersect_point_on_canvas(point, canvas_layer_id, res, 64, Set<RID>(), 0xFFFFFFFF, true, true, true);
+ PhysicsDirectSpaceState2D::PointParameters point_params;
+ point_params.position = point;
+ point_params.canvas_instance_id = canvas_layer_id;
+ point_params.collide_with_areas = true;
+ point_params.pick_point = true;
+
+ int rc = ss2d->intersect_point(point_params, res, 64);
for (int i = 0; i < rc; i++) {
if (res[i].collider_id.is_valid() && res[i].collider) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider);
@@ -690,7 +696,7 @@ void Viewport::_process_picking() {
if (co && camera_3d) {
_collision_object_3d_input_event(co, camera_3d, ev, Vector3(), Vector3(), 0);
captured = true;
- if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
physics_object_capture = ObjectID();
}
@@ -706,7 +712,7 @@ void Viewport::_process_picking() {
if (ObjectDB::get_instance(last_id) && last_object) {
// Good, exists.
_collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape);
- if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
+ if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
physics_object_capture = last_id;
}
}
@@ -715,10 +721,17 @@ void Viewport::_process_picking() {
if (camera_3d) {
Vector3 from = camera_3d->project_ray_origin(pos);
Vector3 dir = camera_3d->project_ray_normal(pos);
+ real_t far = camera_3d->far;
PhysicsDirectSpaceState3D *space = PhysicsServer3D::get_singleton()->space_get_direct_state(find_world_3d()->get_space());
if (space) {
- bool col = space->intersect_ray(from, from + dir * 10000, result, Set<RID>(), 0xFFFFFFFF, true, true, true);
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = from;
+ ray_params.to = from + dir * far;
+ ray_params.collide_with_areas = true;
+ ray_params.pick_ray = true;
+
+ bool col = space->intersect_ray(ray_params, result);
ObjectID new_collider;
if (col) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(result.collider);
@@ -727,7 +740,7 @@ void Viewport::_process_picking() {
last_object = co;
last_id = result.collider_id;
new_collider = last_id;
- if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
+ if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
physics_object_capture = last_id;
}
}
@@ -1079,8 +1092,11 @@ Transform2D Viewport::_get_input_pre_xform() const {
}
Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
- Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();
+ if (ev.is_null()) {
+ return ev; // No transformation defined for null event
+ }
+ Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();
return ev->xformed_by(ai);
}
@@ -1243,10 +1259,10 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu
Ref<InputEventMouseButton> mb = p_input;
bool cant_stop_me_now = (mb.is_valid() &&
- (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN ||
- mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP ||
- mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT ||
- mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT));
+ (mb->get_button_index() == MouseButton::WHEEL_DOWN ||
+ mb->get_button_index() == MouseButton::WHEEL_UP ||
+ mb->get_button_index() == MouseButton::WHEEL_LEFT ||
+ mb->get_button_index() == MouseButton::WHEEL_RIGHT));
Ref<InputEventPanGesture> pn = p_input;
cant_stop_me_now = pn.is_valid() || cant_stop_me_now;
@@ -1429,21 +1445,21 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
gui.last_mouse_pos = mpos;
if (mb->is_pressed()) {
Size2 pos = mpos;
- if (gui.mouse_focus_mask) {
+ if (gui.mouse_focus_mask != MouseButton::NONE) {
// Do not steal mouse focus and stuff while a focus mask exists.
- gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask.
+ gui.mouse_focus_mask |= mouse_button_to_mask(mb->get_button_index());
} else {
gui.mouse_focus = gui_find_control(pos);
gui.last_mouse_focus = gui.mouse_focus;
if (!gui.mouse_focus) {
- gui.mouse_focus_mask = 0;
+ gui.mouse_focus_mask = MouseButton::NONE;
return;
}
- gui.mouse_focus_mask = 1 << (mb->get_button_index() - 1);
+ gui.mouse_focus_mask = mouse_button_to_mask(mb->get_button_index());
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
gui.drag_accum = Vector2();
gui.drag_attempted = false;
}
@@ -1466,7 +1482,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
}
#endif
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus.
+ if (mb->get_button_index() == MouseButton::LEFT) { // Assign focus.
CanvasItem *ci = gui.mouse_focus;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
@@ -1497,7 +1513,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
set_input_as_handled();
- if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) {
// Alternate drop use (when using force_drag(), as proposed by #5342).
if (gui.mouse_focus) {
_gui_drop(gui.mouse_focus, pos, false);
@@ -1517,7 +1533,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
_gui_cancel_tooltip();
} else {
- if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) {
if (gui.drag_mouse_over) {
_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, false);
}
@@ -1535,7 +1551,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
// Change mouse accordingly.
}
- gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask.
+ gui.mouse_focus_mask &= ~mouse_button_to_mask(mb->get_button_index()); // Remove from mask.
if (!gui.mouse_focus) {
// Release event is only sent if a mouse focus (previously pressed button) exists.
@@ -1554,7 +1570,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
// Disable mouse focus if needed before calling input,
// this makes popups on mouse press event work better,
// as the release will never be received otherwise.
- if (gui.mouse_focus_mask == 0) {
+ if (gui.mouse_focus_mask == MouseButton::NONE) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
}
@@ -1574,7 +1590,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
over = gui_find_control(mpos);
}
- if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) {
+ if (gui.mouse_focus_mask == MouseButton::NONE && over != gui.mouse_over) {
if (gui.mouse_over) {
_gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT);
}
@@ -1602,7 +1618,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Control *over = nullptr;
// Drag & drop.
- if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) {
gui.drag_accum += mm->get_relative();
float len = gui.drag_accum.length();
if (len > 10) {
@@ -1616,7 +1632,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (gui.drag_data.get_type() != Variant::NIL) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
- gui.mouse_focus_mask = 0;
+ gui.mouse_focus_mask = MouseButton::NONE;
break;
} else {
Control *drag_preview = _gui_get_drag_preview();
@@ -1685,7 +1701,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
mm->set_speed(speed);
mm->set_relative(rel);
- if (mm->get_button_mask() == 0) {
+ if (mm->get_button_mask() == MouseButton::NONE) {
// Nothing pressed.
bool can_tooltip = true;
@@ -1738,7 +1754,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Control *c = over;
Vector2 cpos = pos;
while (c) {
- if (gui.mouse_focus_mask != 0 || c->has_point(cpos)) {
+ if (gui.mouse_focus_mask != MouseButton::NONE || c->has_point(cpos)) {
cursor_shape = c->get_cursor_shape(cpos);
} else {
cursor_shape = Control::CURSOR_ARROW;
@@ -1851,7 +1867,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse();
gui.drag_mouse_over_pos = localizer.xform(viewport_pos);
- if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) {
bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true);
if (!can_drop) {
@@ -2092,7 +2108,7 @@ void Viewport::_gui_remove_control(Control *p_control) {
if (gui.mouse_focus == p_control) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
- gui.mouse_focus_mask = 0;
+ gui.mouse_focus_mask = MouseButton::NONE;
}
if (gui.last_mouse_focus == p_control) {
gui.last_mouse_focus = nullptr;
@@ -2162,13 +2178,13 @@ void Viewport::_gui_accept_event() {
void Viewport::_drop_mouse_focus() {
Control *c = gui.mouse_focus;
- int mask = gui.mouse_focus_mask;
+ MouseButton mask = gui.mouse_focus_mask;
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
- gui.mouse_focus_mask = 0;
+ gui.mouse_focus_mask = MouseButton::NONE;
for (int i = 0; i < 3; i++) {
- if (mask & (1 << i)) {
+ if ((int)mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_position(c->get_local_mouse_position());
@@ -2189,7 +2205,10 @@ void Viewport::_drop_physics_mouseover(bool p_paused_only) {
if (physics_object_over.is_valid()) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over));
if (co) {
- if (!(p_paused_only && co->can_process())) {
+ if (!co->is_inside_tree()) {
+ physics_object_over = ObjectID();
+ physics_object_capture = ObjectID();
+ } else if (!(p_paused_only && co->can_process())) {
co->_mouse_exit();
physics_object_over = ObjectID();
physics_object_capture = ObjectID();
@@ -2210,7 +2229,7 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus
Object *o = ObjectDB::get_instance(E->key());
if (o) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
- if (co) {
+ if (co && co->is_inside_tree()) {
if (p_clean_all_frames && p_paused_only && co->can_process()) {
continue;
}
@@ -2236,7 +2255,7 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus
Object *o = ObjectDB::get_instance(E->key().first);
if (o) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
- if (co) {
+ if (co && co->is_inside_tree()) {
if (p_clean_all_frames && p_paused_only && co->can_process()) {
continue;
}
@@ -2274,11 +2293,11 @@ void Viewport::_post_gui_grab_click_focus() {
return;
}
- int mask = gui.mouse_focus_mask;
+ MouseButton mask = gui.mouse_focus_mask;
Point2 click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos);
for (int i = 0; i < 3; i++) {
- if (mask & (1 << i)) {
+ if ((int)mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
@@ -2296,7 +2315,7 @@ void Viewport::_post_gui_grab_click_focus() {
click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos);
for (int i = 0; i < 3; i++) {
- if (mask & (1 << i)) {
+ if ((int)mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
@@ -2393,7 +2412,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND_V(gui.subwindow_focused == nullptr, false);
Ref<InputEventMouseButton> mb = p_event;
- if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) {
if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) {
// Close window.
@@ -2518,7 +2537,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
// If the event is a mouse button, we need to check whether another window was clicked.
- if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
bool click_on_window = false;
for (int i = gui.sub_windows.size() - 1; i >= 0; i--) {
SubWindow &sw = gui.sub_windows.write[i];
@@ -2878,9 +2897,8 @@ bool Viewport::gui_is_dragging() const {
void Viewport::set_input_as_handled() {
_drop_physics_mouseover();
- if (handle_input_locally) {
- local_input_handled = true;
- } else {
+
+ if (!handle_input_locally) {
ERR_FAIL_COND(!is_inside_tree());
Viewport *vp = this;
while (true) {
@@ -2892,16 +2910,19 @@ void Viewport::set_input_as_handled() {
}
vp = vp->get_parent()->get_viewport();
}
- vp->set_input_as_handled();
+ if (vp != this) {
+ vp->set_input_as_handled();
+ return;
+ }
}
+
+ local_input_handled = true;
}
bool Viewport::is_input_handled() const {
- if (handle_input_locally) {
- return local_input_handled;
- } else {
- const Viewport *vp = this;
+ if (!handle_input_locally) {
ERR_FAIL_COND_V(!is_inside_tree(), false);
+ const Viewport *vp = this;
while (true) {
if (Object::cast_to<Window>(vp)) {
break;
@@ -2911,8 +2932,11 @@ bool Viewport::is_input_handled() const {
}
vp = vp->get_parent()->get_viewport();
}
- return vp->is_input_handled();
+ if (vp != this) {
+ return vp->is_input_handled();
+ }
}
+ return local_input_handled;
}
void Viewport::set_handle_input_locally(bool p_enable) {
@@ -3017,7 +3041,7 @@ void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
- gui.mouse_focus_mask = 0;
+ gui.mouse_focus_mask = MouseButton::NONE;
}
}
@@ -3849,7 +3873,7 @@ void SubViewport::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "size_2d_override_stretch"), "set_size_2d_override_stretch", "is_size_2d_override_stretch_enabled");
ADD_GROUP("Render Target", "render_target_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_clear_mode", PROPERTY_HINT_ENUM, "Always,Never,Next Frame"), "set_clear_mode", "get_clear_mode");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_update_mode", PROPERTY_HINT_ENUM, "Disabled,Once,When Visible,Always"), "set_update_mode", "get_update_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_update_mode", PROPERTY_HINT_ENUM, "Disabled,Once,When Visible,When Parent Visible,Always"), "set_update_mode", "get_update_mode");
BIND_ENUM_CONSTANT(CLEAR_MODE_ALWAYS);
BIND_ENUM_CONSTANT(CLEAR_MODE_NEVER);
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 1f19ff04c9..5320aea02a 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -244,7 +244,7 @@ private:
bool control = false;
bool shift = false;
bool meta = false;
- int mouse_mask = 0;
+ MouseButton mouse_mask = MouseButton::NONE;
} physics_last_mouse_state;
@@ -327,7 +327,7 @@ private:
Control *mouse_focus = nullptr;
Control *last_mouse_focus = nullptr;
Control *mouse_click_grabber = nullptr;
- int mouse_focus_mask = 0;
+ MouseButton mouse_focus_mask = MouseButton::NONE;
Control *key_focus = nullptr;
Control *mouse_over = nullptr;
Control *drag_mouse_over = nullptr;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index a0f62c853f..5da5a183f7 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -895,7 +895,7 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) {
if (EngineDebugger::is_active()) {
//quit from game window using F8
Ref<InputEventKey> k = p_ev;
- if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_F8) {
+ if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::F8) {
EngineDebugger::get_singleton()->send_message("request_quit", Array());
}
}
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
new file mode 100644
index 0000000000..7df601492b
--- /dev/null
+++ b/scene/property_utils.cpp
@@ -0,0 +1,188 @@
+/*************************************************************************/
+/* property_utils.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "property_utils.h"
+
+#include "core/config/engine.h"
+#include "core/templates/local_vector.h"
+#include "scene/resources/packed_scene.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_node.h"
+#endif // TOOLS_ENABLED
+
+bool PropertyUtils::is_property_value_different(const Variant &p_a, const Variant &p_b) {
+ if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) {
+ //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error
+ return !Math::is_equal_approx((float)p_a, (float)p_b);
+ } else {
+ // For our purposes, treating null object as NIL is the right thing to do
+ const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a;
+ const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b;
+ return a != b;
+ }
+}
+
+Variant PropertyUtils::get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache, bool p_update_exports, const Node *p_owner, bool *r_is_class_default) {
+ // This function obeys the way property values are set when an object is instantiated,
+ // which is the following (the latter wins):
+ // 1. Default value from builtin class
+ // 2. Default value from script exported variable (from the topmost script)
+ // 3. Value overrides from the instantiation/inheritance stack
+
+ if (r_is_class_default) {
+ *r_is_class_default = false;
+ }
+
+ Ref<Script> topmost_script;
+
+ if (const Node *node = Object::cast_to<Node>(p_object)) {
+ // Check inheritance/instantiation ancestors
+ const Vector<SceneState::PackState> &states_stack = p_states_stack_cache ? *p_states_stack_cache : PropertyUtils::get_node_states_stack(node, p_owner);
+ for (int i = 0; i < states_stack.size(); ++i) {
+ const SceneState::PackState &ia = states_stack[i];
+ bool found = false;
+ Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found);
+ if (found) {
+ return value_in_ancestor;
+ }
+ // Save script for later
+ bool has_script = false;
+ Variant script = ia.state->get_property_value(ia.node, SNAME("script"), has_script);
+ if (has_script) {
+ Ref<Script> scr = script;
+ if (scr.is_valid()) {
+ topmost_script = scr;
+ }
+ }
+ }
+ }
+
+ // Let's see what default is set by the topmost script having a default, if any
+ if (topmost_script.is_null()) {
+ topmost_script = p_object->get_script();
+ }
+ if (topmost_script.is_valid()) {
+ // Should be called in the editor only and not at runtime,
+ // otherwise it can cause problems because of missing instance state support
+ if (p_update_exports && Engine::get_singleton()->is_editor_hint()) {
+ topmost_script->update_exports();
+ }
+ Variant default_value;
+ if (topmost_script->get_property_default_value(p_property, default_value)) {
+ return default_value;
+ }
+ }
+
+ // Fall back to the default from the native class
+ if (r_is_class_default) {
+ *r_is_class_default = true;
+ }
+ return ClassDB::class_get_default_property_value(p_object->get_class_name(), p_property);
+}
+
+// Like SceneState::PackState, but using a raw pointer to avoid the cost of
+// updating the reference count during the internal work of the functions below
+namespace {
+struct _FastPackState {
+ SceneState *state = nullptr;
+ int node = -1;
+};
+} // namespace
+
+static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const NodePath &p_path, LocalVector<_FastPackState> &r_states_stack) {
+ bool found = false;
+
+ LocalVector<_FastPackState> inheritance_states;
+
+ Ref<SceneState> state = p_state;
+ while (state.is_valid()) {
+ int node = state->find_node_by_path(p_path);
+ if (node >= 0) {
+ // This one has state for this node
+ inheritance_states.push_back({ state.ptr(), node });
+ found = true;
+ }
+ state = state->get_base_scene_state();
+ }
+
+ for (int i = inheritance_states.size() - 1; i >= 0; --i) {
+ r_states_stack.push_back(inheritance_states[i]);
+ }
+
+ return found;
+}
+
+Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p_node, const Node *p_owner, bool *r_instantiated_by_owner) {
+ if (r_instantiated_by_owner) {
+ *r_instantiated_by_owner = true;
+ }
+
+ LocalVector<_FastPackState> states_stack;
+ {
+ const Node *owner = p_owner;
+#ifdef TOOLS_ENABLED
+ if (!p_owner && Engine::get_singleton()->is_editor_hint()) {
+ owner = EditorNode::get_singleton()->get_edited_scene();
+ }
+#endif
+
+ const Node *n = p_node;
+ while (n) {
+ if (n == owner) {
+ const Ref<SceneState> &state = n->get_scene_inherited_state();
+ if (_collect_inheritance_chain(state, n->get_path_to(p_node), states_stack)) {
+ if (r_instantiated_by_owner) {
+ *r_instantiated_by_owner = false;
+ }
+ }
+ break;
+ } else if (n->get_scene_file_path() != String()) {
+ const Ref<SceneState> &state = n->get_scene_instance_state();
+ _collect_inheritance_chain(state, n->get_path_to(p_node), states_stack);
+ }
+ n = n->get_owner();
+ }
+ }
+
+ // Convert to the proper type for returning, inverting the vector on the go
+ // (it was more convenient to fill the vector in reverse order)
+ Vector<SceneState::PackState> states_stack_ret;
+ {
+ states_stack_ret.resize(states_stack.size());
+ _FastPackState *ps = states_stack.ptr();
+ for (int i = states_stack.size() - 1; i >= 0; --i) {
+ states_stack_ret.write[i].state.reference_ptr(ps->state);
+ states_stack_ret.write[i].node = ps->node;
+ ++ps;
+ }
+ }
+ return states_stack_ret;
+}
diff --git a/scene/property_utils.h b/scene/property_utils.h
new file mode 100644
index 0000000000..fde9163548
--- /dev/null
+++ b/scene/property_utils.h
@@ -0,0 +1,51 @@
+/*************************************************************************/
+/* property_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef PROPERTY_UTILS_H
+#define PROPERTY_UTILS_H
+
+#include "scene/main/node.h"
+#include "scene/resources/packed_scene.h"
+
+class PropertyUtils {
+public:
+ static bool is_property_value_different(const Variant &p_a, const Variant &p_b);
+ // Gets the most pure default value, the one that would be set when the node has just been instantiated
+ static Variant get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache = nullptr, bool p_update_exports = false, const Node *p_owner = nullptr, bool *r_is_class_default = nullptr);
+
+ // Gets the instance/inheritance states of this node, in order of precedence,
+ // that is, from the topmost (the most able to override values) to the lowermost
+ // (Note that in nested instancing the one with the greatest precedence is the furthest
+ // in the tree, since every owner found while traversing towards the root gets a chance
+ // to override property values.)
+ static Vector<SceneState::PackState> get_node_states_stack(const Node *p_node, const Node *p_owner = nullptr, bool *r_instantiated_by_owner = nullptr);
+};
+
+#endif // PROPERTY_UTILS_H
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index bf8f7291be..056ace5e4e 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -63,6 +63,7 @@
#include "scene/2d/position_2d.h"
#include "scene/2d/ray_cast_2d.h"
#include "scene/2d/remote_transform_2d.h"
+#include "scene/2d/shape_cast_2d.h"
#include "scene/2d/skeleton_2d.h"
#include "scene/2d/sprite_2d.h"
#include "scene/2d/tile_map.h"
@@ -115,8 +116,8 @@
#include "scene/gui/spin_box.h"
#include "scene/gui/split_container.h"
#include "scene/gui/subviewport_container.h"
+#include "scene/gui/tab_bar.h"
#include "scene/gui/tab_container.h"
-#include "scene/gui/tabs.h"
#include "scene/gui/text_edit.h"
#include "scene/gui/texture_button.h"
#include "scene/gui/texture_progress_bar.h"
@@ -213,6 +214,7 @@
#include "scene/3d/collision_shape_3d.h"
#include "scene/3d/cpu_particles_3d.h"
#include "scene/3d/decal.h"
+#include "scene/3d/fog_volume.h"
#include "scene/3d/gpu_particles_3d.h"
#include "scene/3d/gpu_particles_collision_3d.h"
#include "scene/3d/importer_mesh_instance_3d.h"
@@ -245,6 +247,7 @@
#include "scene/3d/world_environment.h"
#include "scene/3d/xr_nodes.h"
#include "scene/resources/environment.h"
+#include "scene/resources/fog_material.h"
#include "scene/resources/importer_mesh.h"
#include "scene/resources/mesh_library.h"
#endif
@@ -262,7 +265,7 @@ static Ref<ResourceFormatLoaderShader> resource_loader_shader;
void register_scene_types() {
SceneStringNames::create();
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
Node::init_node_hrcr();
@@ -287,7 +290,7 @@ void register_scene_types() {
resource_loader_shader.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_shader, true);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(Object);
@@ -309,7 +312,7 @@ void register_scene_types() {
GDREGISTER_CLASS(ButtonGroup);
GDREGISTER_VIRTUAL_CLASS(BaseButton);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(Control);
GDREGISTER_CLASS(Button);
@@ -330,7 +333,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Panel);
GDREGISTER_VIRTUAL_CLASS(Range);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(TextureRect);
GDREGISTER_CLASS(ColorRect);
@@ -338,7 +341,7 @@ void register_scene_types() {
GDREGISTER_CLASS(ReferenceRect);
GDREGISTER_CLASS(AspectRatioContainer);
GDREGISTER_CLASS(TabContainer);
- GDREGISTER_CLASS(Tabs);
+ GDREGISTER_CLASS(TabBar);
GDREGISTER_VIRTUAL_CLASS(Separator);
GDREGISTER_CLASS(HSeparator);
GDREGISTER_CLASS(VSeparator);
@@ -352,7 +355,7 @@ void register_scene_types() {
GDREGISTER_CLASS(ScrollContainer);
GDREGISTER_CLASS(PanelContainer);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(TextureProgressBar);
GDREGISTER_CLASS(ItemList);
@@ -391,7 +394,7 @@ void register_scene_types() {
GDREGISTER_CLASS(GraphNode);
GDREGISTER_CLASS(GraphEdit);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
bool swap_cancel_ok = false;
if (DisplayServer::get_singleton()) {
@@ -431,9 +434,9 @@ void register_scene_types() {
GDREGISTER_CLASS(AnimationNodeTimeSeek);
GDREGISTER_CLASS(AnimationNodeTransition);
- GDREGISTER_CLASS(ShaderGlobalsOverride); //can be used in any shader
+ GDREGISTER_CLASS(ShaderGlobalsOverride); // can be used in any shader
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
/* REGISTER 3D */
@@ -450,6 +453,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Camera3D);
GDREGISTER_CLASS(AudioListener3D);
GDREGISTER_CLASS(XRCamera3D);
+ GDREGISTER_VIRTUAL_CLASS(XRNode3D);
GDREGISTER_CLASS(XRController3D);
GDREGISTER_CLASS(XRAnchor3D);
GDREGISTER_CLASS(XROrigin3D);
@@ -485,9 +489,9 @@ void register_scene_types() {
GDREGISTER_CLASS(Position3D);
GDREGISTER_CLASS(RootMotionView);
- ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor
+ ClassDB::set_class_enabled("RootMotionView", false); // disabled by default, enabled by editor
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_VIRTUAL_CLASS(CollisionObject3D);
GDREGISTER_VIRTUAL_CLASS(PhysicsBody3D);
@@ -519,6 +523,8 @@ void register_scene_types() {
GDREGISTER_CLASS(VisibleOnScreenNotifier3D);
GDREGISTER_CLASS(VisibleOnScreenEnabler3D);
GDREGISTER_CLASS(WorldEnvironment);
+ GDREGISTER_CLASS(FogVolume);
+ GDREGISTER_CLASS(FogMaterial);
GDREGISTER_CLASS(RemoteTransform3D);
GDREGISTER_VIRTUAL_CLASS(Joint3D);
@@ -532,7 +538,7 @@ void register_scene_types() {
GDREGISTER_CLASS(NavigationAgent3D);
GDREGISTER_CLASS(NavigationObstacle3D);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
#endif
/* REGISTER SHADER */
@@ -623,6 +629,7 @@ void register_scene_types() {
GDREGISTER_CLASS(VisualShaderNodeParticleSphereEmitter);
GDREGISTER_CLASS(VisualShaderNodeParticleBoxEmitter);
GDREGISTER_CLASS(VisualShaderNodeParticleRingEmitter);
+ GDREGISTER_CLASS(VisualShaderNodeParticleMeshEmitter);
GDREGISTER_CLASS(VisualShaderNodeParticleMultiplyByAxisAngle);
GDREGISTER_CLASS(VisualShaderNodeParticleConeVelocity);
GDREGISTER_CLASS(VisualShaderNodeParticleRandomness);
@@ -660,6 +667,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CollisionShape2D);
GDREGISTER_CLASS(CollisionPolygon2D);
GDREGISTER_CLASS(RayCast2D);
+ GDREGISTER_CLASS(ShapeCast2D);
GDREGISTER_CLASS(VisibleOnScreenNotifier2D);
GDREGISTER_CLASS(VisibleOnScreenEnabler2D);
GDREGISTER_CLASS(Polygon2D);
@@ -672,7 +680,7 @@ void register_scene_types() {
GDREGISTER_CLASS(OccluderPolygon2D);
GDREGISTER_CLASS(BackBufferCopy);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(Camera2D);
GDREGISTER_CLASS(AudioListener2D);
@@ -684,6 +692,7 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(TileSetSource);
GDREGISTER_CLASS(TileSetAtlasSource);
GDREGISTER_CLASS(TileSetScenesCollectionSource);
+ GDREGISTER_CLASS(TileMapPattern);
GDREGISTER_CLASS(TileData);
GDREGISTER_CLASS(TileMap);
GDREGISTER_CLASS(ParallaxBackground);
@@ -703,7 +712,7 @@ void register_scene_types() {
GDREGISTER_CLASS(PhysicalBone2D);
GDREGISTER_CLASS(SkeletonModification2DPhysicalBones);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
/* REGISTER RESOURCES */
@@ -744,7 +753,7 @@ void register_scene_types() {
GDREGISTER_CLASS(MeshLibrary);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_VIRTUAL_CLASS(Shape3D);
GDREGISTER_CLASS(SeparationRayShape3D);
@@ -766,7 +775,7 @@ void register_scene_types() {
ClassDB::register_class<SkeletonModification3DTwoBoneIK>();
ClassDB::register_class<SkeletonModification3DStackHolder>();
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(VelocityTracker3D);
#endif
@@ -785,7 +794,8 @@ void register_scene_types() {
GDREGISTER_CLASS(MeshTexture);
GDREGISTER_CLASS(CurveTexture);
GDREGISTER_CLASS(CurveXYZTexture);
- GDREGISTER_CLASS(GradientTexture);
+ GDREGISTER_CLASS(GradientTexture1D);
+ GDREGISTER_CLASS(GradientTexture2D);
GDREGISTER_CLASS(ProxyTexture);
GDREGISTER_CLASS(AnimatedTexture);
GDREGISTER_CLASS(CameraTexture);
@@ -821,7 +831,7 @@ void register_scene_types() {
GDREGISTER_CLASS(BitMap);
GDREGISTER_CLASS(Gradient);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(AudioStreamPlayer);
GDREGISTER_CLASS(AudioStreamPlayer2D);
@@ -831,7 +841,7 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(VideoStream);
GDREGISTER_CLASS(AudioStreamSample);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_VIRTUAL_CLASS(Shape2D);
GDREGISTER_CLASS(WorldBoundaryShape2D);
@@ -852,13 +862,13 @@ void register_scene_types() {
GDREGISTER_CLASS(NavigationAgent2D);
GDREGISTER_CLASS(NavigationObstacle2D);
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
GDREGISTER_VIRTUAL_CLASS(SceneState);
GDREGISTER_CLASS(PackedScene);
GDREGISTER_CLASS(SceneTree);
- GDREGISTER_VIRTUAL_CLASS(SceneTreeTimer); //sorry, you can't create it
+ GDREGISTER_VIRTUAL_CLASS(SceneTreeTimer); // sorry, you can't create it
#ifndef DISABLE_DEPRECATED
// Dropped in 4.0, near approximation.
@@ -915,6 +925,7 @@ void register_scene_types() {
ClassDB::add_compatibility_class("EditorSpatialGizmo", "EditorNode3DGizmo");
ClassDB::add_compatibility_class("EditorSpatialGizmoPlugin", "EditorNode3DGizmoPlugin");
ClassDB::add_compatibility_class("Generic6DOFJoint", "Generic6DOFJoint3D");
+ ClassDB::add_compatibility_class("GradientTexture", "GradientTexture1D");
ClassDB::add_compatibility_class("HeightMapShape", "HeightMapShape3D");
ClassDB::add_compatibility_class("HingeJoint", "HingeJoint3D");
ClassDB::add_compatibility_class("Joint", "Joint3D");
@@ -940,10 +951,8 @@ void register_scene_types() {
ClassDB::add_compatibility_class("Path", "Path3D");
ClassDB::add_compatibility_class("PathFollow", "PathFollow3D");
ClassDB::add_compatibility_class("PhysicalBone", "PhysicalBone3D");
- ClassDB::add_compatibility_class("Physics2DDirectBodyStateSW", "PhysicsDirectBodyState2DSW");
ClassDB::add_compatibility_class("Physics2DDirectBodyState", "PhysicsDirectBodyState2D");
ClassDB::add_compatibility_class("Physics2DDirectSpaceState", "PhysicsDirectSpaceState2D");
- ClassDB::add_compatibility_class("Physics2DServerSW", "PhysicsServer2DSW");
ClassDB::add_compatibility_class("Physics2DServer", "PhysicsServer2D");
ClassDB::add_compatibility_class("Physics2DShapeQueryParameters", "PhysicsShapeQueryParameters2D");
ClassDB::add_compatibility_class("Physics2DTestMotionResult", "PhysicsTestMotionResult2D");
@@ -1008,7 +1017,7 @@ void register_scene_types() {
#endif /* DISABLE_DEPRECATED */
- OS::get_singleton()->yield(); //may take time to init
+ OS::get_singleton()->yield(); // may take time to init
for (int i = 0; i < 20; i++) {
GLOBAL_DEF_BASIC(vformat("layer_names/2d_render/layer_%d", i + 1), "");
@@ -1086,7 +1095,7 @@ void unregister_scene_types() {
ResourceLoader::remove_resource_format_loader(resource_loader_shader);
resource_loader_shader.unref();
- //StandardMaterial3D is not initialised when 3D is disabled, so it shouldn't be cleaned up either
+ // StandardMaterial3D is not initialised when 3D is disabled, so it shouldn't be cleaned up either
#ifndef _3D_DISABLED
BaseMaterial3D::finish_shaders();
#endif // _3D_DISABLED
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index b4eec2530b..29aca6b321 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -30,21 +30,54 @@
#include "animation.h"
+#include "core/io/marshalls.h"
#include "core/math/geometry_3d.h"
#include "scene/scene_string_names.h"
bool Animation::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
- if (name.begins_with("tracks/")) {
+ if (p_name == SNAME("_compression")) {
+ ERR_FAIL_COND_V(tracks.size() > 0, false); //can only set compression if no tracks exist
+ Dictionary comp = p_value;
+ ERR_FAIL_COND_V(!comp.has("fps"), false);
+ ERR_FAIL_COND_V(!comp.has("bounds"), false);
+ ERR_FAIL_COND_V(!comp.has("pages"), false);
+ ERR_FAIL_COND_V(!comp.has("format_version"), false);
+ uint32_t format_version = comp["format_version"];
+ ERR_FAIL_COND_V(format_version > Compression::FORMAT_VERSION, false); // version does not match this supported version
+ compression.fps = comp["fps"];
+ Array bounds = comp["bounds"];
+ compression.bounds.resize(bounds.size());
+ for (int i = 0; i < bounds.size(); i++) {
+ compression.bounds[i] = bounds[i];
+ }
+ Array pages = comp["pages"];
+ compression.pages.resize(pages.size());
+ for (int i = 0; i < pages.size(); i++) {
+ Dictionary page = pages[i];
+ ERR_FAIL_COND_V(!page.has("data"), false);
+ ERR_FAIL_COND_V(!page.has("time_offset"), false);
+ compression.pages[i].data = page["data"];
+ compression.pages[i].time_offset = page["time_offset"];
+ }
+ compression.enabled = true;
+ return true;
+ } else if (name.begins_with("tracks/")) {
int track = name.get_slicec('/', 1).to_int();
String what = name.get_slicec('/', 2);
if (tracks.size() == track && what == "type") {
String type = p_value;
- if (type == "transform" || type == "transform3d") {
- add_track(TYPE_TRANSFORM3D);
+ if (type == "position_3d") {
+ add_track(TYPE_POSITION_3D);
+ } else if (type == "rotation_3d") {
+ add_track(TYPE_ROTATION_3D);
+ } else if (type == "scale_3d") {
+ add_track(TYPE_SCALE_3D);
+ } else if (type == "blend_shape") {
+ add_track(TYPE_BLEND_SHAPE);
} else if (type == "value") {
add_track(TYPE_VALUE);
} else if (type == "method") {
@@ -66,6 +99,34 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
if (what == "path") {
track_set_path(track, p_value);
+ } else if (what == "compressed_track") {
+ int index = p_value;
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)index, compression.bounds.size(), false);
+ Track *t = tracks[track];
+ t->interpolation = INTERPOLATION_LINEAR; //only linear supported
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ tt->compressed_track = index;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ rt->compressed_track = index;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ st->compressed_track = index;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ bst->compressed_track = index;
+ } break;
+ default: {
+ return false;
+ }
+ }
+ return true;
} else if (what == "interp") {
track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
} else if (what == "loop_wrap") {
@@ -75,35 +136,91 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
} else if (what == "enabled") {
track_set_enabled(track, p_value);
} else if (what == "keys" || what == "key_values") {
- if (track_get_type(track) == TYPE_TRANSFORM3D) {
- TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]);
+ if (track_get_type(track) == TYPE_POSITION_3D) {
+ PositionTrack *tt = static_cast<PositionTrack *>(tracks[track]);
Vector<real_t> values = p_value;
int vcount = values.size();
- ERR_FAIL_COND_V(vcount % TRANSFORM_TRACK_SIZE, false);
+ ERR_FAIL_COND_V(vcount % POSITION_TRACK_SIZE, false);
const real_t *r = values.ptr();
- int64_t count = vcount / TRANSFORM_TRACK_SIZE;
- tt->transforms.resize(count);
+ int64_t count = vcount / POSITION_TRACK_SIZE;
+ tt->positions.resize(count);
+ TKey<Vector3> *tw = tt->positions.ptrw();
for (int i = 0; i < count; i++) {
- TKey<TransformKey> &tk = tt->transforms.write[i];
- const real_t *ofs = &r[i * TRANSFORM_TRACK_SIZE];
+ TKey<Vector3> &tk = tw[i];
+ const real_t *ofs = &r[i * POSITION_TRACK_SIZE];
tk.time = ofs[0];
tk.transition = ofs[1];
- tk.value.loc.x = ofs[2];
- tk.value.loc.y = ofs[3];
- tk.value.loc.z = ofs[4];
+ tk.value.x = ofs[2];
+ tk.value.y = ofs[3];
+ tk.value.z = ofs[4];
+ }
+ } else if (track_get_type(track) == TYPE_ROTATION_3D) {
+ RotationTrack *rt = static_cast<RotationTrack *>(tracks[track]);
+ Vector<real_t> values = p_value;
+ int vcount = values.size();
+ ERR_FAIL_COND_V(vcount % ROTATION_TRACK_SIZE, false);
- tk.value.rot.x = ofs[5];
- tk.value.rot.y = ofs[6];
- tk.value.rot.z = ofs[7];
- tk.value.rot.w = ofs[8];
+ const real_t *r = values.ptr();
+
+ int64_t count = vcount / ROTATION_TRACK_SIZE;
+ rt->rotations.resize(count);
- tk.value.scale.x = ofs[9];
- tk.value.scale.y = ofs[10];
- tk.value.scale.z = ofs[11];
+ TKey<Quaternion> *rw = rt->rotations.ptrw();
+ for (int i = 0; i < count; i++) {
+ TKey<Quaternion> &rk = rw[i];
+ const real_t *ofs = &r[i * ROTATION_TRACK_SIZE];
+ rk.time = ofs[0];
+ rk.transition = ofs[1];
+
+ rk.value.x = ofs[2];
+ rk.value.y = ofs[3];
+ rk.value.z = ofs[4];
+ rk.value.w = ofs[5];
+ }
+ } else if (track_get_type(track) == TYPE_SCALE_3D) {
+ ScaleTrack *st = static_cast<ScaleTrack *>(tracks[track]);
+ Vector<real_t> values = p_value;
+ int vcount = values.size();
+ ERR_FAIL_COND_V(vcount % SCALE_TRACK_SIZE, false);
+
+ const real_t *r = values.ptr();
+
+ int64_t count = vcount / SCALE_TRACK_SIZE;
+ st->scales.resize(count);
+
+ TKey<Vector3> *sw = st->scales.ptrw();
+ for (int i = 0; i < count; i++) {
+ TKey<Vector3> &sk = sw[i];
+ const real_t *ofs = &r[i * SCALE_TRACK_SIZE];
+ sk.time = ofs[0];
+ sk.transition = ofs[1];
+
+ sk.value.x = ofs[2];
+ sk.value.y = ofs[3];
+ sk.value.z = ofs[4];
+ }
+ } else if (track_get_type(track) == TYPE_BLEND_SHAPE) {
+ BlendShapeTrack *st = static_cast<BlendShapeTrack *>(tracks[track]);
+ Vector<real_t> values = p_value;
+ int vcount = values.size();
+ ERR_FAIL_COND_V(vcount % BLEND_SHAPE_TRACK_SIZE, false);
+
+ const real_t *r = values.ptr();
+
+ int64_t count = vcount / BLEND_SHAPE_TRACK_SIZE;
+ st->blend_shapes.resize(count);
+
+ TKey<float> *sw = st->blend_shapes.ptrw();
+ for (int i = 0; i < count; i++) {
+ TKey<float> &sk = sw[i];
+ const real_t *ofs = &r[i * BLEND_SHAPE_TRACK_SIZE];
+ sk.time = ofs[0];
+ sk.transition = ofs[1];
+ sk.value = ofs[2];
}
} else if (track_get_type(track) == TYPE_VALUE) {
@@ -200,7 +317,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
Vector<real_t> times = d["times"];
Vector<real_t> values = d["points"];
- ERR_FAIL_COND_V(times.size() * 5 != values.size(), false);
+ ERR_FAIL_COND_V(times.size() * 6 != values.size(), false);
if (times.size()) {
int valcount = times.size();
@@ -213,11 +330,12 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
for (int i = 0; i < valcount; i++) {
bt->values.write[i].time = rt[i];
bt->values.write[i].transition = 0; //unused in bezier
- bt->values.write[i].value.value = rv[i * 5 + 0];
- bt->values.write[i].value.in_handle.x = rv[i * 5 + 1];
- bt->values.write[i].value.in_handle.y = rv[i * 5 + 2];
- bt->values.write[i].value.out_handle.x = rv[i * 5 + 3];
- bt->values.write[i].value.out_handle.y = rv[i * 5 + 4];
+ bt->values.write[i].value.value = rv[i * 6 + 0];
+ bt->values.write[i].value.in_handle.x = rv[i * 6 + 1];
+ bt->values.write[i].value.in_handle.y = rv[i * 6 + 2];
+ bt->values.write[i].value.out_handle.x = rv[i * 6 + 3];
+ bt->values.write[i].value.out_handle.y = rv[i * 6 + 4];
+ bt->values.write[i].value.handle_mode = (HandleMode)rv[i * 6 + 5];
}
}
@@ -307,10 +425,33 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
String name = p_name;
- if (name == "length") {
+ if (p_name == SNAME("_compression")) {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ Dictionary comp;
+ comp["fps"] = compression.fps;
+ Array bounds;
+ bounds.resize(compression.bounds.size());
+ for (uint32_t i = 0; i < compression.bounds.size(); i++) {
+ bounds[i] = compression.bounds[i];
+ }
+ comp["bounds"] = bounds;
+ Array pages;
+ pages.resize(compression.pages.size());
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ Dictionary page;
+ page["data"] = compression.pages[i].data;
+ page["time_offset"] = compression.pages[i].time_offset;
+ pages[i] = page;
+ }
+ comp["pages"] = pages;
+ comp["format_version"] = Compression::FORMAT_VERSION;
+
+ r_ret = comp;
+ return true;
+ } else if (name == "length") {
r_ret = length;
- } else if (name == "loop") {
- r_ret = loop;
+ } else if (name == "loop_mode") {
+ r_ret = loop_mode;
} else if (name == "step") {
r_ret = step;
} else if (name.begins_with("tracks/")) {
@@ -319,8 +460,17 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
ERR_FAIL_INDEX_V(track, tracks.size(), false);
if (what == "type") {
switch (track_get_type(track)) {
- case TYPE_TRANSFORM3D:
- r_ret = "transform";
+ case TYPE_POSITION_3D:
+ r_ret = "position_3d";
+ break;
+ case TYPE_ROTATION_3D:
+ r_ret = "rotation_3d";
+ break;
+ case TYPE_SCALE_3D:
+ r_ret = "scale_3d";
+ break;
+ case TYPE_BLEND_SHAPE:
+ r_ret = "blend_shape";
break;
case TYPE_VALUE:
r_ret = "value";
@@ -343,6 +493,34 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
} else if (what == "path") {
r_ret = track_get_path(track);
+ } else if (what == "compressed_track") {
+ ERR_FAIL_COND_V(!compression.enabled, false);
+ Track *t = tracks[track];
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ r_ret = tt->compressed_track;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ r_ret = rt->compressed_track;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ r_ret = st->compressed_track;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ r_ret = bst->compressed_track;
+ } break;
+ default: {
+ r_ret = Variant();
+ ERR_FAIL_V(false);
+ }
+ }
+
+ return true;
+
} else if (what == "interp") {
r_ret = track_get_interpolation_type(track);
} else if (what == "loop_wrap") {
@@ -352,31 +530,64 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
} else if (what == "enabled") {
r_ret = track_is_enabled(track);
} else if (what == "keys") {
- if (track_get_type(track) == TYPE_TRANSFORM3D) {
+ if (track_get_type(track) == TYPE_POSITION_3D) {
Vector<real_t> keys;
int kk = track_get_key_count(track);
- keys.resize(kk * TRANSFORM_TRACK_SIZE);
+ keys.resize(kk * POSITION_TRACK_SIZE);
real_t *w = keys.ptrw();
int idx = 0;
for (int i = 0; i < track_get_key_count(track); i++) {
Vector3 loc;
- Quaternion rot;
- Vector3 scale;
- transform_track_get_key(track, i, &loc, &rot, &scale);
+ position_track_get_key(track, i, &loc);
w[idx++] = track_get_key_time(track, i);
w[idx++] = track_get_key_transition(track, i);
w[idx++] = loc.x;
w[idx++] = loc.y;
w[idx++] = loc.z;
+ }
+ r_ret = keys;
+ return true;
+ } else if (track_get_type(track) == TYPE_ROTATION_3D) {
+ Vector<real_t> keys;
+ int kk = track_get_key_count(track);
+ keys.resize(kk * ROTATION_TRACK_SIZE);
+
+ real_t *w = keys.ptrw();
+
+ int idx = 0;
+ for (int i = 0; i < track_get_key_count(track); i++) {
+ Quaternion rot;
+ rotation_track_get_key(track, i, &rot);
+
+ w[idx++] = track_get_key_time(track, i);
+ w[idx++] = track_get_key_transition(track, i);
w[idx++] = rot.x;
w[idx++] = rot.y;
w[idx++] = rot.z;
w[idx++] = rot.w;
+ }
+
+ r_ret = keys;
+ return true;
+ } else if (track_get_type(track) == TYPE_SCALE_3D) {
+ Vector<real_t> keys;
+ int kk = track_get_key_count(track);
+ keys.resize(kk * SCALE_TRACK_SIZE);
+
+ real_t *w = keys.ptrw();
+
+ int idx = 0;
+ for (int i = 0; i < track_get_key_count(track); i++) {
+ Vector3 scale;
+ scale_track_get_key(track, i, &scale);
+
+ w[idx++] = track_get_key_time(track, i);
+ w[idx++] = track_get_key_transition(track, i);
w[idx++] = scale.x;
w[idx++] = scale.y;
w[idx++] = scale.z;
@@ -384,7 +595,25 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = keys;
return true;
+ } else if (track_get_type(track) == TYPE_BLEND_SHAPE) {
+ Vector<real_t> keys;
+ int kk = track_get_key_count(track);
+ keys.resize(kk * BLEND_SHAPE_TRACK_SIZE);
+
+ real_t *w = keys.ptrw();
+
+ int idx = 0;
+ for (int i = 0; i < track_get_key_count(track); i++) {
+ float bs;
+ blend_shape_track_get_key(track, i, &bs);
+
+ w[idx++] = track_get_key_time(track, i);
+ w[idx++] = track_get_key_transition(track, i);
+ w[idx++] = bs;
+ }
+ r_ret = keys;
+ return true;
} else if (track_get_type(track) == TYPE_VALUE) {
const ValueTrack *vt = static_cast<const ValueTrack *>(tracks[track]);
@@ -470,7 +699,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
int kk = bt->values.size();
key_times.resize(kk);
- key_points.resize(kk * 5);
+ key_points.resize(kk * 6);
real_t *wti = key_times.ptrw();
real_t *wpo = key_points.ptrw();
@@ -481,11 +710,12 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
for (int i = 0; i < kk; i++) {
wti[idx] = vls[i].time;
- wpo[idx * 5 + 0] = vls[i].value.value;
- wpo[idx * 5 + 1] = vls[i].value.in_handle.x;
- wpo[idx * 5 + 2] = vls[i].value.in_handle.y;
- wpo[idx * 5 + 3] = vls[i].value.out_handle.x;
- wpo[idx * 5 + 4] = vls[i].value.out_handle.y;
+ wpo[idx * 6 + 0] = vls[i].value.value;
+ wpo[idx * 6 + 1] = vls[i].value.in_handle.x;
+ wpo[idx * 6 + 2] = vls[i].value.in_handle.y;
+ wpo[idx * 6 + 3] = vls[i].value.out_handle.x;
+ wpo[idx * 6 + 4] = vls[i].value.out_handle.y;
+ wpo[idx * 6 + 5] = (double)vls[i].value.handle_mode;
idx++;
}
@@ -570,14 +800,21 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
}
void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (compression.enabled) {
+ p_list->push_back(PropertyInfo(Variant::DICTIONARY, "_compression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ }
for (int i = 0; i < tracks.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, "tracks/" + itos(i) + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tracks/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/interp", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/imported", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::STRING, "tracks/" + itos(i) + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/imported", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tracks/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ if (track_is_compressed(i)) {
+ p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/compressed_track", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ } else {
+ p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/interp", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ }
}
}
@@ -591,10 +828,22 @@ int Animation::add_track(TrackType p_type, int p_at_pos) {
}
switch (p_type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = memnew(TransformTrack);
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = memnew(PositionTrack);
tracks.insert(p_at_pos, tt);
} break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = memnew(RotationTrack);
+ tracks.insert(p_at_pos, rt);
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = memnew(ScaleTrack);
+ tracks.insert(p_at_pos, st);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = memnew(BlendShapeTrack);
+ tracks.insert(p_at_pos, bst);
+ } break;
case TYPE_VALUE: {
tracks.insert(p_at_pos, memnew(ValueTrack));
@@ -629,9 +878,28 @@ void Animation::remove_track(int p_track) {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- _clear(tt->transforms);
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND_MSG(tt->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
+ _clear(tt->positions);
+
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND_MSG(rt->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
+ _clear(rt->rotations);
+
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND_MSG(st->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
+ _clear(st->scales);
+
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND_MSG(bst->compressed_track >= 0, "Compressed tracks can't be manually removed. Call clear() to get rid of compression first.");
+ _clear(bst->blend_shapes);
} break;
case TYPE_VALUE: {
@@ -672,7 +940,7 @@ int Animation::get_track_count() const {
}
Animation::TrackType Animation::track_get_type(int p_track) const {
- ERR_FAIL_INDEX_V(p_track, tracks.size(), TYPE_TRANSFORM3D);
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), TYPE_VALUE);
return tracks[p_track]->type;
}
@@ -688,9 +956,9 @@ NodePath Animation::track_get_path(int p_track) const {
return tracks[p_track]->path;
}
-int Animation::find_track(const NodePath &p_path) const {
+int Animation::find_track(const NodePath &p_path, const TrackType p_type) const {
for (int i = 0; i < tracks.size(); i++) {
- if (tracks[i]->path == p_path) {
+ if (tracks[i]->path == p_path && tracks[i]->type == p_type) {
return i;
}
};
@@ -720,31 +988,6 @@ bool Animation::track_get_interpolation_loop_wrap(int p_track) const {
return tracks[p_track]->loop_wrap;
}
-// transform
-/*
-template<class T>
-int Animation::_insert_pos(double p_time, T& p_keys) {
- // simple, linear time inset that should be fast enough in reality.
-
- int idx=p_keys.size();
-
- while(true) {
-
-
- if (idx==0 || p_keys[idx-1].time < p_time) {
- //condition for insertion.
- p_keys.insert(idx,T());
- return idx;
- } else if (p_keys[idx-1].time == p_time) {
- // condition for replacing.
- return idx-1;
- }
-
- idx--;
- }
-}
-
-*/
template <class T, class V>
int Animation::_insert(double p_time, T &p_keys, const V &p_value) {
int idx = p_keys.size();
@@ -774,45 +1017,292 @@ void Animation::_clear(T &p_keys) {
p_keys.clear();
}
-Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const {
+////
+
+int Animation::position_track_insert_key(int p_track, double p_time, const Vector3 &p_position) {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, -1);
+
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+
+ ERR_FAIL_COND_V(tt->compressed_track >= 0, -1);
+
+ TKey<Vector3> tkey;
+ tkey.time = p_time;
+ tkey.value = p_position;
+
+ int ret = _insert(p_time, tt->positions, tkey);
+ emit_changed();
+ return ret;
+}
+
+Error Animation::position_track_get_key(int p_track, int p_key, Vector3 *r_position) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, ERR_INVALID_PARAMETER);
+
+ if (tt->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(tt->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_position = _uncompress_pos_scale(tt->compressed_track, key);
+ return OK;
+ }
+
+ ERR_FAIL_INDEX_V(p_key, tt->positions.size(), ERR_INVALID_PARAMETER);
+
+ *r_position = tt->positions[p_key].value;
+
+ return OK;
+}
+
+Error Animation::position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, ERR_INVALID_PARAMETER);
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER);
- ERR_FAIL_INDEX_V(p_key, tt->transforms.size(), ERR_INVALID_PARAMETER);
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
- if (r_loc) {
- *r_loc = tt->transforms[p_key].value.loc;
+ if (tt->compressed_track >= 0) {
+ if (_pos_scale_interpolate_compressed(tt->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
}
- if (r_rot) {
- *r_rot = tt->transforms[p_key].value.rot;
+
+ bool ok = false;
+
+ Vector3 tk = _interpolate(tt->positions, p_time, tt->interpolation, tt->loop_wrap, &ok);
+
+ if (!ok) {
+ return ERR_UNAVAILABLE;
}
- if (r_scale) {
- *r_scale = tt->transforms[p_key].value.scale;
+ *r_interpolation = tk;
+ return OK;
+}
+
+////
+
+int Animation::rotation_track_insert_key(int p_track, double p_time, const Quaternion &p_rotation) {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, -1);
+
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ ERR_FAIL_COND_V(rt->compressed_track >= 0, -1);
+
+ TKey<Quaternion> tkey;
+ tkey.time = p_time;
+ tkey.value = p_rotation;
+
+ int ret = _insert(p_time, rt->rotations, tkey);
+ emit_changed();
+ return ret;
+}
+
+Error Animation::rotation_track_get_key(int p_track, int p_key, Quaternion *r_rotation) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, ERR_INVALID_PARAMETER);
+
+ if (rt->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(rt->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_rotation = _uncompress_quaternion(key);
+ return OK;
}
+ ERR_FAIL_INDEX_V(p_key, rt->rotations.size(), ERR_INVALID_PARAMETER);
+
+ *r_rotation = rt->rotations[p_key].value;
+
+ return OK;
+}
+
+Error Animation::rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, ERR_INVALID_PARAMETER);
+
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ if (rt->compressed_track >= 0) {
+ if (_rotation_interpolate_compressed(rt->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
+ bool ok = false;
+
+ Quaternion tk = _interpolate(rt->rotations, p_time, rt->interpolation, rt->loop_wrap, &ok);
+
+ if (!ok) {
+ return ERR_UNAVAILABLE;
+ }
+ *r_interpolation = tk;
return OK;
}
-int Animation::transform_track_insert_key(int p_track, double p_time, const Vector3 &p_loc, const Quaternion &p_rot, const Vector3 &p_scale) {
+////
+
+int Animation::scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale) {
ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
Track *t = tracks[p_track];
- ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, -1);
+ ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, -1);
+
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
- TransformTrack *tt = static_cast<TransformTrack *>(t);
+ ERR_FAIL_COND_V(st->compressed_track >= 0, -1);
- TKey<TransformKey> tkey;
+ TKey<Vector3> tkey;
tkey.time = p_time;
- tkey.value.loc = p_loc;
- tkey.value.rot = p_rot;
- tkey.value.scale = p_scale;
+ tkey.value = p_scale;
- int ret = _insert(p_time, tt->transforms, tkey);
+ int ret = _insert(p_time, st->scales, tkey);
emit_changed();
return ret;
}
+Error Animation::scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, ERR_INVALID_PARAMETER);
+
+ if (st->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<3>(st->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_scale = _uncompress_pos_scale(st->compressed_track, key);
+ return OK;
+ }
+
+ ERR_FAIL_INDEX_V(p_key, st->scales.size(), ERR_INVALID_PARAMETER);
+
+ *r_scale = st->scales[p_key].value;
+
+ return OK;
+}
+
+Error Animation::scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, ERR_INVALID_PARAMETER);
+
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+
+ if (st->compressed_track >= 0) {
+ if (_pos_scale_interpolate_compressed(st->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
+ bool ok = false;
+
+ Vector3 tk = _interpolate(st->scales, p_time, st->interpolation, st->loop_wrap, &ok);
+
+ if (!ok) {
+ return ERR_UNAVAILABLE;
+ }
+ *r_interpolation = tk;
+ return OK;
+}
+
+int Animation::blend_shape_track_insert_key(int p_track, double p_time, float p_blend_shape) {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, -1);
+
+ BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
+
+ ERR_FAIL_COND_V(st->compressed_track >= 0, -1);
+
+ TKey<float> tkey;
+ tkey.time = p_time;
+ tkey.value = p_blend_shape;
+
+ int ret = _insert(p_time, st->blend_shapes, tkey);
+ emit_changed();
+ return ret;
+}
+
+Error Animation::blend_shape_track_get_key(int p_track, int p_key, float *r_blend_shape) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
+
+ if (bst->compressed_track >= 0) {
+ Vector3i key;
+ double time;
+ bool fetch_success = _fetch_compressed_by_index<1>(bst->compressed_track, p_key, key, time);
+ if (!fetch_success) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ *r_blend_shape = _uncompress_blend_shape(key);
+ return OK;
+ }
+
+ ERR_FAIL_INDEX_V(p_key, bst->blend_shapes.size(), ERR_INVALID_PARAMETER);
+
+ *r_blend_shape = bst->blend_shapes[p_key].value;
+
+ return OK;
+}
+
+Error Animation::blend_shape_track_interpolate(int p_track, double p_time, float *r_interpolation) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
+
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ if (bst->compressed_track >= 0) {
+ if (_blend_shape_interpolate_compressed(bst->compressed_track, p_time, *r_interpolation)) {
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+ }
+
+ bool ok = false;
+
+ float tk = _interpolate(bst->blend_shapes, p_time, bst->interpolation, bst->loop_wrap, &ok);
+
+ if (!ok) {
+ return ERR_UNAVAILABLE;
+ }
+ *r_interpolation = tk;
+ return OK;
+}
+
void Animation::track_remove_key_at_time(int p_track, double p_time) {
int idx = track_find_key(p_track, p_time, true);
ERR_FAIL_COND(idx < 0);
@@ -824,10 +1314,40 @@ void Animation::track_remove_key(int p_track, int p_idx) {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX(p_idx, tt->transforms.size());
- tt->transforms.remove(p_idx);
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+
+ ERR_FAIL_INDEX(p_idx, tt->positions.size());
+ tt->positions.remove(p_idx);
+
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ ERR_FAIL_COND(rt->compressed_track >= 0);
+
+ ERR_FAIL_INDEX(p_idx, rt->rotations.size());
+ rt->rotations.remove(p_idx);
+
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+
+ ERR_FAIL_COND(st->compressed_track >= 0);
+
+ ERR_FAIL_INDEX(p_idx, st->scales.size());
+ st->scales.remove(p_idx);
+
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ ERR_FAIL_COND(bst->compressed_track >= 0);
+
+ ERR_FAIL_INDEX(p_idx, bst->blend_shapes.size());
+ bst->blend_shapes.remove(p_idx);
} break;
case TYPE_VALUE: {
@@ -870,13 +1390,109 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- int k = _find(tt->transforms, p_time);
- if (k < 0 || k >= tt->transforms.size()) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+
+ if (tt->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(tt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
+ int k = _find(tt->positions, p_time);
+ if (k < 0 || k >= tt->positions.size()) {
+ return -1;
+ }
+ if (tt->positions[k].time != p_time && p_exact) {
+ return -1;
+ }
+ return k;
+
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+
+ if (rt->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(rt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
+ int k = _find(rt->rotations, p_time);
+ if (k < 0 || k >= rt->rotations.size()) {
return -1;
}
- if (tt->transforms[k].time != p_time && p_exact) {
+ if (rt->rotations[k].time != p_time && p_exact) {
+ return -1;
+ }
+ return k;
+
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+
+ if (st->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<3>(st->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
+ int k = _find(st->scales, p_time);
+ if (k < 0 || k >= st->scales.size()) {
+ return -1;
+ }
+ if (st->scales[k].time != p_time && p_exact) {
+ return -1;
+ }
+ return k;
+
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+
+ if (bst->compressed_track >= 0) {
+ double time;
+ double time_next;
+ Vector3i key;
+ Vector3i key_next;
+ uint32_t key_index;
+ bool fetch_compressed_success = _fetch_compressed<1>(bst->compressed_track, p_time, key, time, key_next, time_next, &key_index);
+ ERR_FAIL_COND_V(!fetch_compressed_success, -1);
+ if (p_exact && time != p_time) {
+ return -1;
+ }
+ return key_index;
+ }
+
+ int k = _find(bst->blend_shapes, p_time);
+ if (k < 0 || k >= bst->blend_shapes.size()) {
+ return -1;
+ }
+ if (bst->blend_shapes[k].time != p_time && p_exact) {
return -1;
}
return k;
@@ -952,24 +1568,27 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- Dictionary d = p_key;
- Vector3 loc;
- if (d.has("location")) {
- loc = d["location"];
- }
+ case TYPE_POSITION_3D: {
+ ERR_FAIL_COND((p_key.get_type() != Variant::VECTOR3) && (p_key.get_type() != Variant::VECTOR3I));
+ int idx = position_track_insert_key(p_track, p_time, p_key);
+ track_set_key_transition(p_track, idx, p_transition);
- Quaternion rot;
- if (d.has("rotation")) {
- rot = d["rotation"];
- }
+ } break;
+ case TYPE_ROTATION_3D: {
+ ERR_FAIL_COND((p_key.get_type() != Variant::QUATERNION) && (p_key.get_type() != Variant::BASIS));
+ int idx = rotation_track_insert_key(p_track, p_time, p_key);
+ track_set_key_transition(p_track, idx, p_transition);
- Vector3 scale;
- if (d.has("scale")) {
- scale = d["scale"];
- }
+ } break;
+ case TYPE_SCALE_3D: {
+ ERR_FAIL_COND((p_key.get_type() != Variant::VECTOR3) && (p_key.get_type() != Variant::VECTOR3I));
+ int idx = scale_track_insert_key(p_track, p_time, p_key);
+ track_set_key_transition(p_track, idx, p_transition);
- int idx = transform_track_insert_key(p_track, p_time, loc, rot, scale);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ ERR_FAIL_COND((p_key.get_type() != Variant::FLOAT) && (p_key.get_type() != Variant::INT));
+ int idx = blend_shape_track_insert_key(p_track, p_time, p_key);
track_set_key_transition(p_track, idx, p_transition);
} break;
@@ -1006,7 +1625,7 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke
BezierTrack *bt = static_cast<BezierTrack *>(t);
Array arr = p_key;
- ERR_FAIL_COND(arr.size() != 5);
+ ERR_FAIL_COND(arr.size() != 6);
TKey<BezierKey> k;
k.time = p_time;
@@ -1015,6 +1634,7 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke
k.value.in_handle.y = arr[2];
k.value.out_handle.x = arr[3];
k.value.out_handle.y = arr[4];
+ k.value.handle_mode = (HandleMode) int(arr[5]);
_insert(p_time, bt->values, k);
} break;
@@ -1054,9 +1674,33 @@ int Animation::track_get_key_count(int p_track) const {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- return tt->transforms.size();
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ return _get_compressed_key_count(tt->compressed_track);
+ }
+ return tt->positions.size();
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ return _get_compressed_key_count(rt->compressed_track);
+ }
+ return rt->rotations.size();
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ return _get_compressed_key_count(st->compressed_track);
+ }
+ return st->scales.size();
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ return _get_compressed_key_count(bst->compressed_track);
+ }
+ return bst->blend_shapes.size();
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1089,16 +1733,25 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), Variant());
-
- Dictionary d;
- d["location"] = tt->transforms[p_key_idx].value.loc;
- d["rotation"] = tt->transforms[p_key_idx].value.rot;
- d["scale"] = tt->transforms[p_key_idx].value.scale;
-
- return d;
+ case TYPE_POSITION_3D: {
+ Vector3 value;
+ position_track_get_key(p_track, p_key_idx, &value);
+ return value;
+ } break;
+ case TYPE_ROTATION_3D: {
+ Quaternion value;
+ rotation_track_get_key(p_track, p_key_idx, &value);
+ return value;
+ } break;
+ case TYPE_SCALE_3D: {
+ Vector3 value;
+ scale_track_get_key(p_track, p_key_idx, &value);
+ return value;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ float value;
+ blend_shape_track_get_key(p_track, p_key_idx, &value);
+ return value;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1120,12 +1773,13 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant());
Array arr;
- arr.resize(5);
+ arr.resize(6);
arr[0] = bt->values[p_key_idx].value.value;
arr[1] = bt->values[p_key_idx].value.in_handle.x;
arr[2] = bt->values[p_key_idx].value.in_handle.y;
arr[3] = bt->values[p_key_idx].value.out_handle.x;
arr[4] = bt->values[p_key_idx].value.out_handle.y;
+ arr[5] = (double)bt->values[p_key_idx].value.handle_mode;
return arr;
} break;
@@ -1157,10 +1811,53 @@ double Animation::track_get_key_time(int p_track, int p_key_idx) const {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), -1);
- return tt->transforms[p_key_idx].time;
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(tt->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, tt->positions.size(), -1);
+ return tt->positions[p_key_idx].time;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(rt->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, rt->rotations.size(), -1);
+ return rt->rotations[p_key_idx].time;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<3>(st->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
+ return st->scales[p_key_idx].time;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ Vector3i value;
+ double time;
+ bool fetch_compressed_success = _fetch_compressed_by_index<1>(bst->compressed_track, p_key_idx, value, time);
+ ERR_FAIL_COND_V(!fetch_compressed_success, false);
+ return time;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
+ return bst->blend_shapes[p_key_idx].time;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1202,13 +1899,44 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX(p_key_idx, tt->transforms.size());
- TKey<TransformKey> key = tt->transforms[p_key_idx];
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
+ TKey<Vector3> key = tt->positions[p_key_idx];
key.time = p_time;
- tt->transforms.remove(p_key_idx);
- _insert(p_time, tt->transforms, key);
+ tt->positions.remove(p_key_idx);
+ _insert(p_time, tt->positions, key);
+ return;
+ }
+ case TYPE_ROTATION_3D: {
+ RotationTrack *tt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->rotations.size());
+ TKey<Quaternion> key = tt->rotations[p_key_idx];
+ key.time = p_time;
+ tt->rotations.remove(p_key_idx);
+ _insert(p_time, tt->rotations, key);
+ return;
+ }
+ case TYPE_SCALE_3D: {
+ ScaleTrack *tt = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->scales.size());
+ TKey<Vector3> key = tt->scales[p_key_idx];
+ key.time = p_time;
+ tt->scales.remove(p_key_idx);
+ _insert(p_time, tt->scales, key);
+ return;
+ }
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *tt = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->blend_shapes.size());
+ TKey<float> key = tt->blend_shapes[p_key_idx];
+ key.time = p_time;
+ tt->blend_shapes.remove(p_key_idx);
+ _insert(p_time, tt->blend_shapes, key);
return;
}
case TYPE_VALUE: {
@@ -1266,10 +1994,37 @@ real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const {
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), -1);
- return tt->transforms[p_key_idx].transition;
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ return 1.0;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, tt->positions.size(), -1);
+ return tt->positions[p_key_idx].transition;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ return 1.0;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, rt->rotations.size(), -1);
+ return rt->rotations[p_key_idx].transition;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ return 1.0;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
+ return st->scales[p_key_idx].transition;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ return 1.0;
+ }
+ ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
+ return bst->blend_shapes[p_key_idx].transition;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1297,26 +2052,74 @@ real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const {
ERR_FAIL_V(0);
}
+bool Animation::track_is_compressed(int p_track) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+ Track *t = tracks[p_track];
+
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ return tt->compressed_track >= 0;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ return rt->compressed_track >= 0;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ return st->compressed_track >= 0;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ return bst->compressed_track >= 0;
+ } break;
+ default: {
+ return false; //animation does not really use transitions
+ } break;
+ }
+
+ ERR_FAIL_V(false);
+}
+
void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p_value) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX(p_key_idx, tt->transforms.size());
+ case TYPE_POSITION_3D: {
+ ERR_FAIL_COND((p_value.get_type() != Variant::VECTOR3) && (p_value.get_type() != Variant::VECTOR3I));
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
- Dictionary d = p_value;
+ tt->positions.write[p_key_idx].value = p_value;
- if (d.has("location")) {
- tt->transforms.write[p_key_idx].value.loc = d["location"];
- }
- if (d.has("rotation")) {
- tt->transforms.write[p_key_idx].value.rot = d["rotation"];
- }
- if (d.has("scale")) {
- tt->transforms.write[p_key_idx].value.scale = d["scale"];
- }
+ } break;
+ case TYPE_ROTATION_3D: {
+ ERR_FAIL_COND((p_value.get_type() != Variant::QUATERNION) && (p_value.get_type() != Variant::BASIS));
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(rt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, rt->rotations.size());
+
+ rt->rotations.write[p_key_idx].value = p_value;
+
+ } break;
+ case TYPE_SCALE_3D: {
+ ERR_FAIL_COND((p_value.get_type() != Variant::VECTOR3) && (p_value.get_type() != Variant::VECTOR3I));
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(st->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, st->scales.size());
+
+ st->scales.write[p_key_idx].value = p_value;
+
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ ERR_FAIL_COND((p_value.get_type() != Variant::FLOAT) && (p_value.get_type() != Variant::INT));
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(bst->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
+
+ bst->blend_shapes.write[p_key_idx].value = p_value;
} break;
case TYPE_VALUE: {
@@ -1345,13 +2148,14 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
ERR_FAIL_INDEX(p_key_idx, bt->values.size());
Array arr = p_value;
- ERR_FAIL_COND(arr.size() != 5);
+ ERR_FAIL_COND(arr.size() != 6);
bt->values.write[p_key_idx].value.value = arr[0];
bt->values.write[p_key_idx].value.in_handle.x = arr[1];
bt->values.write[p_key_idx].value.in_handle.y = arr[2];
bt->values.write[p_key_idx].value.out_handle.x = arr[3];
bt->values.write[p_key_idx].value.out_handle.y = arr[4];
+ bt->values.write[p_key_idx].value.handle_mode = (HandleMode) int(arr[5]);
} break;
case TYPE_AUDIO: {
@@ -1385,10 +2189,29 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
Track *t = tracks[p_track];
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- TransformTrack *tt = static_cast<TransformTrack *>(t);
- ERR_FAIL_INDEX(p_key_idx, tt->transforms.size());
- tt->transforms.write[p_key_idx].transition = p_transition;
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ ERR_FAIL_COND(tt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, tt->positions.size());
+ tt->positions.write[p_key_idx].transition = p_transition;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ ERR_FAIL_COND(rt->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, rt->rotations.size());
+ rt->rotations.write[p_key_idx].transition = p_transition;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ ERR_FAIL_COND(st->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, st->scales.size());
+ st->scales.write[p_key_idx].transition = p_transition;
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ ERR_FAIL_COND(bst->compressed_track >= 0);
+ ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
+ bst->blend_shapes.write[p_key_idx].transition = p_transition;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@@ -1413,7 +2236,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
}
template <class K>
-int Animation::_find(const Vector<K> &p_keys, double p_time) const {
+int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) const {
int len = p_keys.size();
if (len == 0) {
return -2;
@@ -1443,22 +2266,19 @@ int Animation::_find(const Vector<K> &p_keys, double p_time) const {
}
}
- if (keys[middle].time > p_time) {
- middle--;
+ if (!p_backward) {
+ if (keys[middle].time > p_time) {
+ middle--;
+ }
+ } else {
+ if (keys[middle].time < p_time) {
+ middle++;
+ }
}
return middle;
}
-Animation::TransformKey Animation::_interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, real_t p_c) const {
- TransformKey ret;
- ret.loc = _interpolate(p_a.loc, p_b.loc, p_c);
- ret.rot = _interpolate(p_a.rot, p_b.rot, p_c);
- ret.scale = _interpolate(p_a.scale, p_b.scale, p_c);
-
- return ret;
-}
-
Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const {
return p_a.lerp(p_b, p_c);
}
@@ -1477,16 +2297,6 @@ real_t Animation::_interpolate(const real_t &p_a, const real_t &p_b, real_t p_c)
return p_a * (1.0 - p_c) + p_b * p_c;
}
-Animation::TransformKey Animation::_cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, real_t p_c) const {
- Animation::TransformKey tk;
-
- tk.loc = p_a.loc.cubic_interpolate(p_b.loc, p_pre_a.loc, p_post_b.loc, p_c);
- tk.scale = p_a.scale.cubic_interpolate(p_b.scale, p_pre_a.scale, p_post_b.scale, p_c);
- tk.rot = p_a.rot.cubic_slerp(p_b.rot, p_pre_a.rot, p_post_b.rot, p_c);
-
- return tk;
-}
-
Vector3 Animation::_cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c) const {
return p_a.cubic_interpolate(p_b, p_pre_a, p_post_b, p_c);
}
@@ -1520,10 +2330,11 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a
real_t t2 = t * t;
real_t t3 = t2 * t;
- return 0.5f * ((p1 * 2.0f) +
- (-p0 + p2) * t +
- (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 +
- (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
+ return 0.5f *
+ ((p1 * 2.0f) +
+ (-p0 + p2) * t +
+ (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 +
+ (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
} else if ((vformat & (vformat - 1))) {
return p_a; //can't interpolate, mix of types
@@ -1585,7 +2396,7 @@ real_t Animation::_cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, c
}
template <class T>
-T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const {
+T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const {
int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end)
if (len <= 0) {
@@ -1603,7 +2414,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
return p_keys[0].value;
}
- int idx = _find(p_keys, p_time);
+ int idx = _find(p_keys, p_time, p_backward);
ERR_FAIL_COND_V(idx == -2, T());
@@ -1612,24 +2423,42 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
real_t c = 0.0;
// prepare for all cases of interpolation
- if (loop && p_loop_wrap) {
+ if ((loop_mode == LOOP_LINEAR || loop_mode == LOOP_PINGPONG) && p_loop_wrap) {
// loop
- if (idx >= 0) {
- if ((idx + 1) < len) {
- next = idx + 1;
- real_t delta = p_keys[next].time - p_keys[idx].time;
- real_t from = p_time - p_keys[idx].time;
-
- if (Math::is_zero_approx(delta)) {
- c = 0;
+ if (!p_backward) {
+ // no backward
+ if (idx >= 0) {
+ if (idx < len - 1) {
+ next = idx + 1;
+ real_t delta = p_keys[next].time - p_keys[idx].time;
+ real_t from = p_time - p_keys[idx].time;
+
+ if (Math::is_zero_approx(delta)) {
+ c = 0;
+ } else {
+ c = from / delta;
+ }
} else {
- c = from / delta;
+ next = 0;
+ real_t delta = (length - p_keys[idx].time) + p_keys[next].time;
+ real_t from = p_time - p_keys[idx].time;
+
+ if (Math::is_zero_approx(delta)) {
+ c = 0;
+ } else {
+ c = from / delta;
+ }
}
-
} else {
+ // on loop, behind first key
+ idx = len - 1;
next = 0;
- real_t delta = (length - p_keys[idx].time) + p_keys[next].time;
- real_t from = p_time - p_keys[idx].time;
+ real_t endtime = (length - p_keys[idx].time);
+ if (endtime < 0) { // may be keys past the end
+ endtime = 0;
+ }
+ real_t delta = endtime + p_keys[next].time;
+ real_t from = endtime + p_time;
if (Math::is_zero_approx(delta)) {
c = 0;
@@ -1637,49 +2466,81 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
c = from / delta;
}
}
-
} else {
- // on loop, behind first key
- idx = len - 1;
- next = 0;
- real_t endtime = (length - p_keys[idx].time);
- if (endtime < 0) { // may be keys past the end
- endtime = 0;
- }
- real_t delta = endtime + p_keys[next].time;
- real_t from = endtime + p_time;
-
- if (Math::is_zero_approx(delta)) {
- c = 0;
+ // backward
+ if (idx <= len - 1) {
+ if (idx > 0) {
+ next = idx - 1;
+ real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time);
+ real_t from = (length - p_time) - (length - p_keys[idx].time);
+
+ if (Math::is_zero_approx(delta))
+ c = 0;
+ else
+ c = from / delta;
+ } else {
+ next = len - 1;
+ real_t delta = p_keys[idx].time + (length - p_keys[next].time);
+ real_t from = (length - p_time) - (length - p_keys[idx].time);
+
+ if (Math::is_zero_approx(delta))
+ c = 0;
+ else
+ c = from / delta;
+ }
} else {
- c = from / delta;
+ // on loop, in front of last key
+ idx = 0;
+ next = len - 1;
+ real_t endtime = p_keys[idx].time;
+ if (endtime > length) // may be keys past the end
+ endtime = length;
+ real_t delta = p_keys[next].time - endtime;
+ real_t from = p_time - endtime;
+
+ if (Math::is_zero_approx(delta))
+ c = 0;
+ else
+ c = from / delta;
}
}
-
} else { // no loop
-
- if (idx >= 0) {
- if ((idx + 1) < len) {
- next = idx + 1;
- real_t delta = p_keys[next].time - p_keys[idx].time;
- real_t from = p_time - p_keys[idx].time;
-
- if (Math::is_zero_approx(delta)) {
- c = 0;
+ if (!p_backward) {
+ if (idx >= 0) {
+ if (idx < len - 1) {
+ next = idx + 1;
+ real_t delta = p_keys[next].time - p_keys[idx].time;
+ real_t from = p_time - p_keys[idx].time;
+
+ if (Math::is_zero_approx(delta)) {
+ c = 0;
+ } else {
+ c = from / delta;
+ }
} else {
- c = from / delta;
+ next = idx;
}
-
} else {
- next = idx;
+ idx = next = 0;
}
-
} else {
- // only allow extending first key to anim start if looping
- if (loop) {
- idx = next = 0;
+ if (idx <= len - 1) {
+ if (idx > 0) {
+ next = idx - 1;
+ real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time);
+ real_t from = (length - p_time) - (length - p_keys[idx].time);
+
+ if (Math::is_zero_approx(delta)) {
+ c = 0;
+ } else {
+ c = from / delta;
+ }
+
+ } else {
+ next = idx;
+ }
} else {
- result = false;
+ idx = next = len - 1;
}
}
}
@@ -1729,36 +2590,6 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
// do a barrel roll
}
-Error Animation::transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const {
- ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
- Track *t = tracks[p_track];
- ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER);
-
- TransformTrack *tt = static_cast<TransformTrack *>(t);
-
- bool ok = false;
-
- TransformKey tk = _interpolate(tt->transforms, p_time, tt->interpolation, tt->loop_wrap, &ok);
-
- if (!ok) {
- return ERR_UNAVAILABLE;
- }
-
- if (r_loc) {
- *r_loc = tk.loc;
- }
-
- if (r_rot) {
- *r_rot = tk.rot;
- }
-
- if (r_scale) {
- *r_scale = tk.scale;
- }
-
- return OK;
-}
-
Variant Animation::value_track_interpolate(int p_track, double p_time) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
Track *t = tracks[p_track];
@@ -1813,7 +2644,7 @@ void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, doub
}
}
-void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
+void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_VALUE);
@@ -1827,30 +2658,50 @@ void Animation::value_track_get_key_indices(int p_track, double p_time, double p
SWAP(from_time, to_time);
}
- if (loop) {
- from_time = Math::fposmod(from_time, length);
- to_time = Math::fposmod(to_time, length);
+ switch (loop_mode) {
+ case LOOP_NONE: {
+ if (from_time < 0) {
+ from_time = 0;
+ }
+ if (from_time > length) {
+ from_time = length;
+ }
- if (from_time > to_time) {
- // handle loop by splitting
- _value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
- _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
- return;
- }
- } else {
- if (from_time < 0) {
- from_time = 0;
- }
- if (from_time > length) {
- from_time = length;
- }
+ if (to_time < 0) {
+ to_time = 0;
+ }
+ if (to_time > length) {
+ to_time = length;
+ }
+ } break;
+ case LOOP_LINEAR: {
+ from_time = Math::fposmod(from_time, length);
+ to_time = Math::fposmod(to_time, length);
- if (to_time < 0) {
- to_time = 0;
- }
- if (to_time > length) {
- to_time = length;
- }
+ if (from_time > to_time) {
+ // handle loop by splitting
+ _value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
+ _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
+ return;
+ }
+ } break;
+ case LOOP_PINGPONG: {
+ from_time = Math::pingpong(from_time, length);
+ to_time = Math::pingpong(to_time, length);
+
+ if (p_pingponged == -1) {
+ // handle loop by splitting
+ _value_track_get_key_indices_in_range(vt, 0, from_time, p_indices);
+ _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
+ return;
+ }
+ if (p_pingponged == 1) {
+ // handle loop by splitting
+ _value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
+ _value_track_get_key_indices_in_range(vt, to_time, length, p_indices);
+ return;
+ }
+ } break;
}
_value_track_get_key_indices_in_range(vt, from_time, to_time, p_indices);
@@ -1909,7 +2760,7 @@ void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double
}
}
-void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
+void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
ERR_FAIL_INDEX(p_track, tracks.size());
const Track *t = tracks[p_track];
@@ -1920,104 +2771,309 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
SWAP(from_time, to_time);
}
- if (loop) {
- if (from_time > length || from_time < 0) {
- from_time = Math::fposmod(from_time, length);
- }
-
- if (to_time > length || to_time < 0) {
- to_time = Math::fposmod(to_time, length);
- }
-
- if (from_time > to_time) {
- // handle loop by splitting
-
- switch (t->type) {
- case TYPE_TRANSFORM3D: {
- const TransformTrack *tt = static_cast<const TransformTrack *>(t);
- _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
- _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
-
- } break;
- case TYPE_VALUE: {
- const ValueTrack *vt = static_cast<const ValueTrack *>(t);
- _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
-
- } break;
- case TYPE_METHOD: {
- const MethodTrack *mt = static_cast<const MethodTrack *>(t);
- _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
- _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
-
- } break;
- case TYPE_BEZIER: {
- const BezierTrack *bz = static_cast<const BezierTrack *>(t);
- _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
-
- } break;
- case TYPE_AUDIO: {
- const AudioTrack *ad = static_cast<const AudioTrack *>(t);
- _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+ switch (loop_mode) {
+ case LOOP_NONE: {
+ if (from_time < 0) {
+ from_time = 0;
+ }
+ if (from_time > length) {
+ from_time = length;
+ }
- } break;
- case TYPE_ANIMATION: {
- const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
- _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
- _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+ if (to_time < 0) {
+ to_time = 0;
+ }
+ if (to_time > length) {
+ to_time = length;
+ }
+ } break;
+ case LOOP_LINEAR: {
+ if (from_time > length || from_time < 0) {
+ from_time = Math::fposmod(from_time, length);
+ }
+ if (to_time > length || to_time < 0) {
+ to_time = Math::fposmod(to_time, length);
+ }
- } break;
+ if (from_time > to_time) {
+ // handle loop by splitting
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
+ _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
+ _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_VALUE: {
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_METHOD: {
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+ } break;
+ case TYPE_BEZIER: {
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_AUDIO: {
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_ANIMATION: {
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+ } break;
+ }
+ return;
+ }
+ } break;
+ case LOOP_PINGPONG: {
+ if (from_time > length || from_time < 0) {
+ from_time = Math::pingpong(from_time, length);
+ }
+ if (to_time > length || to_time < 0) {
+ to_time = Math::pingpong(to_time, length);
}
- return;
- }
- } else {
- if (from_time < 0) {
- from_time = 0;
- }
- if (from_time > length) {
- from_time = length;
- }
- if (to_time < 0) {
- to_time = 0;
- }
- if (to_time > length) {
- to_time = length;
- }
+ if ((int)Math::floor(abs(p_delta) / length) % 2 == 0) {
+ if (p_pingponged == -1) {
+ // handle loop by splitting
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+ }
+ } break;
+ case TYPE_VALUE: {
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_METHOD: {
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+ } break;
+ case TYPE_BEZIER: {
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_AUDIO: {
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+ } break;
+ case TYPE_ANIMATION: {
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, 0, from_time, p_indices);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+ } break;
+ }
+ return;
+ }
+ if (p_pingponged == 1) {
+ // handle loop by splitting
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->positions, to_time, length, p_indices);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
+ _track_get_key_indices_in_range(rt->rotations, to_time, length, p_indices);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
+ _track_get_key_indices_in_range(st->scales, to_time, length, p_indices);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices);
+ }
+ } break;
+ case TYPE_VALUE: {
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(vt->values, to_time, length, p_indices);
+ } break;
+ case TYPE_METHOD: {
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
+ _track_get_key_indices_in_range(mt->methods, to_time, length, p_indices);
+ } break;
+ case TYPE_BEZIER: {
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bz->values, to_time, length, p_indices);
+ } break;
+ case TYPE_AUDIO: {
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(ad->values, to_time, length, p_indices);
+ } break;
+ case TYPE_ANIMATION: {
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(an->values, to_time, length, p_indices);
+ } break;
+ }
+ return;
+ }
+ }
+ } break;
}
switch (t->type) {
- case TYPE_TRANSFORM3D: {
- const TransformTrack *tt = static_cast<const TransformTrack *>(t);
- _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices);
-
+ case TYPE_POSITION_3D: {
+ const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+ if (tt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+ if (rt->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+ if (st->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+ if (bst->compressed_track >= 0) {
+ _get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, p_indices);
+ } else {
+ _track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
+ }
} break;
case TYPE_VALUE: {
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
_track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
-
} break;
case TYPE_METHOD: {
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
_track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
-
} break;
case TYPE_BEZIER: {
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
_track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
-
} break;
case TYPE_AUDIO: {
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
_track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
-
} break;
case TYPE_ANIMATION: {
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
_track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
-
} break;
}
}
@@ -2055,7 +3111,7 @@ void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, do
}
}
-void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
+void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_METHOD);
@@ -2069,35 +3125,58 @@ void Animation::method_track_get_key_indices(int p_track, double p_time, double
SWAP(from_time, to_time);
}
- if (loop) {
- if (from_time > length || from_time < 0) {
- from_time = Math::fposmod(from_time, length);
- }
+ switch (loop_mode) {
+ case LOOP_NONE: {
+ if (from_time < 0) {
+ from_time = 0;
+ }
+ if (from_time > length) {
+ from_time = length;
+ }
- if (to_time > length || to_time < 0) {
- to_time = Math::fposmod(to_time, length);
- }
+ if (to_time < 0) {
+ to_time = 0;
+ }
+ if (to_time > length) {
+ to_time = length;
+ }
+ } break;
+ case LOOP_LINEAR: {
+ if (from_time > length || from_time < 0) {
+ from_time = Math::fposmod(from_time, length);
+ }
+ if (to_time > length || to_time < 0) {
+ to_time = Math::fposmod(to_time, length);
+ }
- if (from_time > to_time) {
- // handle loop by splitting
- _method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
- _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
- return;
- }
- } else {
- if (from_time < 0) {
- from_time = 0;
- }
- if (from_time > length) {
- from_time = length;
- }
+ if (from_time > to_time) {
+ // handle loop by splitting
+ _method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
+ _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
+ return;
+ }
+ } break;
+ case LOOP_PINGPONG: {
+ if (from_time > length || from_time < 0) {
+ from_time = Math::pingpong(from_time, length);
+ }
+ if (to_time > length || to_time < 0) {
+ to_time = Math::pingpong(to_time, length);
+ }
- if (to_time < 0) {
- to_time = 0;
- }
- if (to_time > length) {
- to_time = length;
- }
+ if (p_pingponged == -1) {
+ _method_track_get_key_indices_in_range(mt, 0, from_time, p_indices);
+ _method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
+ return;
+ }
+ if (p_pingponged == 1) {
+ _method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
+ _method_track_get_key_indices_in_range(mt, to_time, length, p_indices);
+ return;
+ }
+ } break;
+ default:
+ break;
}
_method_track_get_key_indices_in_range(mt, from_time, to_time, p_indices);
@@ -2129,7 +3208,7 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
-int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
+int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const HandleMode p_handle_mode) {
ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
Track *t = tracks[p_track];
ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1);
@@ -2147,6 +3226,7 @@ int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_valu
if (k.value.out_handle.x < 0) {
k.value.out_handle.x = 0;
}
+ k.value.handle_mode = p_handle_mode;
int key = _insert(p_time, bt->values, k);
@@ -2155,6 +3235,30 @@ int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_valu
return key;
}
+void Animation::bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, double p_balanced_value_time_ratio) {
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values.write[p_index].value.handle_mode = p_mode;
+
+ if (p_mode == HANDLE_MODE_BALANCED) {
+ Transform2D xform;
+ xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio));
+
+ Vector2 vec_in = xform.xform(bt->values[p_index].value.in_handle);
+ Vector2 vec_out = xform.xform(bt->values[p_index].value.out_handle);
+
+ bt->values.write[p_index].value.in_handle = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length());
+ }
+
+ emit_changed();
+}
+
void Animation::bezier_track_set_key_value(int p_track, int p_index, real_t p_value) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
@@ -2168,7 +3272,7 @@ void Animation::bezier_track_set_key_value(int p_track, int p_index, real_t p_va
emit_changed();
}
-void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) {
+void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_BEZIER);
@@ -2177,14 +3281,26 @@ void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const V
ERR_FAIL_INDEX(p_index, bt->values.size());
- bt->values.write[p_index].value.in_handle = p_handle;
- if (bt->values[p_index].value.in_handle.x > 0) {
- bt->values.write[p_index].value.in_handle.x = 0;
+ Vector2 in_handle = p_handle;
+ if (in_handle.x > 0) {
+ in_handle.x = 0;
}
+ bt->values.write[p_index].value.in_handle = in_handle;
+
+ if (bt->values[p_index].value.handle_mode == HANDLE_MODE_BALANCED) {
+ Transform2D xform;
+ xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio));
+
+ Vector2 vec_out = xform.xform(bt->values[p_index].value.out_handle);
+ Vector2 vec_in = xform.xform(in_handle);
+
+ bt->values.write[p_index].value.out_handle = xform.affine_inverse().xform(-vec_in.normalized() * vec_out.length());
+ }
+
emit_changed();
}
-void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) {
+void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_BEZIER);
@@ -2193,10 +3309,22 @@ void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const
ERR_FAIL_INDEX(p_index, bt->values.size());
- bt->values.write[p_index].value.out_handle = p_handle;
- if (bt->values[p_index].value.out_handle.x < 0) {
- bt->values.write[p_index].value.out_handle.x = 0;
+ Vector2 out_handle = p_handle;
+ if (out_handle.x < 0) {
+ out_handle.x = 0;
+ }
+ bt->values.write[p_index].value.out_handle = out_handle;
+
+ if (bt->values[p_index].value.handle_mode == HANDLE_MODE_BALANCED) {
+ Transform2D xform;
+ xform.set_scale(Vector2(1.0, 1.0 / p_balanced_value_time_ratio));
+
+ Vector2 vec_in = xform.xform(bt->values[p_index].value.in_handle);
+ Vector2 vec_out = xform.xform(out_handle);
+
+ bt->values.write[p_index].value.in_handle = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length());
}
+
emit_changed();
}
@@ -2212,6 +3340,18 @@ real_t Animation::bezier_track_get_key_value(int p_track, int p_index) const {
return bt->values[p_index].value.value;
}
+int Animation::bezier_track_get_key_handle_mode(int p_track, int p_index) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0);
+
+ return bt->values[p_index].value.handle_mode;
+}
+
Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
Track *t = tracks[p_track];
@@ -2483,13 +3623,13 @@ real_t Animation::get_length() const {
return length;
}
-void Animation::set_loop(bool p_enabled) {
- loop = p_enabled;
+void Animation::set_loop_mode(Animation::LoopMode p_loop_mode) {
+ loop_mode = p_loop_mode;
emit_changed();
}
-bool Animation::has_loop() const {
- return loop;
+Animation::LoopMode Animation::get_loop_mode() const {
+ return loop_mode;
}
void Animation::track_set_imported(int p_track, bool p_imported) {
@@ -2595,7 +3735,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_get_type", "track_idx"), &Animation::track_get_type);
ClassDB::bind_method(D_METHOD("track_get_path", "track_idx"), &Animation::track_get_path);
ClassDB::bind_method(D_METHOD("track_set_path", "track_idx", "path"), &Animation::track_set_path);
- ClassDB::bind_method(D_METHOD("find_track", "path"), &Animation::find_track);
+ ClassDB::bind_method(D_METHOD("find_track", "path", "type"), &Animation::find_track);
ClassDB::bind_method(D_METHOD("track_move_up", "track_idx"), &Animation::track_move_up);
ClassDB::bind_method(D_METHOD("track_move_down", "track_idx"), &Animation::track_move_down);
@@ -2608,7 +3748,11 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_set_enabled", "track_idx", "enabled"), &Animation::track_set_enabled);
ClassDB::bind_method(D_METHOD("track_is_enabled", "track_idx"), &Animation::track_is_enabled);
- ClassDB::bind_method(D_METHOD("transform_track_insert_key", "track_idx", "time", "location", "rotation", "scale"), &Animation::transform_track_insert_key);
+ ClassDB::bind_method(D_METHOD("position_track_insert_key", "track_idx", "time", "position"), &Animation::position_track_insert_key);
+ ClassDB::bind_method(D_METHOD("rotation_track_insert_key", "track_idx", "time", "rotation"), &Animation::rotation_track_insert_key);
+ ClassDB::bind_method(D_METHOD("scale_track_insert_key", "track_idx", "time", "scale"), &Animation::scale_track_insert_key);
+ ClassDB::bind_method(D_METHOD("blend_shape_track_insert_key", "track_idx", "time", "amount"), &Animation::blend_shape_track_insert_key);
+
ClassDB::bind_method(D_METHOD("track_insert_key", "track_idx", "time", "key", "transition"), &Animation::track_insert_key, DEFVAL(1));
ClassDB::bind_method(D_METHOD("track_remove_key", "track_idx", "key_idx"), &Animation::track_remove_key);
ClassDB::bind_method(D_METHOD("track_remove_key_at_time", "track_idx", "time"), &Animation::track_remove_key_at_time);
@@ -2628,7 +3772,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_set_interpolation_loop_wrap", "track_idx", "interpolation"), &Animation::track_set_interpolation_loop_wrap);
ClassDB::bind_method(D_METHOD("track_get_interpolation_loop_wrap", "track_idx"), &Animation::track_get_interpolation_loop_wrap);
- ClassDB::bind_method(D_METHOD("transform_track_interpolate", "track_idx", "time_sec"), &Animation::_transform_track_interpolate);
+ ClassDB::bind_method(D_METHOD("track_is_compressed", "track_idx"), &Animation::track_is_compressed);
+
ClassDB::bind_method(D_METHOD("value_track_set_update_mode", "track_idx", "mode"), &Animation::value_track_set_update_mode);
ClassDB::bind_method(D_METHOD("value_track_get_update_mode", "track_idx"), &Animation::value_track_get_update_mode);
@@ -2639,11 +3784,11 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("method_track_get_name", "track_idx", "key_idx"), &Animation::method_track_get_name);
ClassDB::bind_method(D_METHOD("method_track_get_params", "track_idx", "key_idx"), &Animation::method_track_get_params);
- ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track_idx", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()));
+ ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track_idx", "time", "value", "in_handle", "out_handle", "handle_mode"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(Animation::HandleMode::HANDLE_MODE_BALANCED));
ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "track_idx", "key_idx", "value"), &Animation::bezier_track_set_key_value);
- ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "track_idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle);
- ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "track_idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "track_idx", "key_idx", "in_handle", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_in_handle, DEFVAL(1.0));
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "track_idx", "key_idx", "out_handle", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_out_handle, DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "track_idx", "key_idx"), &Animation::bezier_track_get_key_value);
ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "track_idx", "key_idx"), &Animation::bezier_track_get_key_in_handle);
@@ -2659,6 +3804,9 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_handle_mode", "track_idx", "key_idx", "key_handle_mode", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_handle_mode, DEFVAL(1.0));
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_handle_mode", "track_idx", "key_idx"), &Animation::bezier_track_get_key_handle_mode);
+
ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key);
ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "track_idx", "key_idx"), &Animation::animation_track_get_key_animation);
@@ -2666,8 +3814,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
- ClassDB::bind_method(D_METHOD("set_loop", "enabled"), &Animation::set_loop);
- ClassDB::bind_method(D_METHOD("has_loop"), &Animation::has_loop);
+ ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &Animation::set_loop_mode);
+ ClassDB::bind_method(D_METHOD("get_loop_mode"), &Animation::get_loop_mode);
ClassDB::bind_method(D_METHOD("set_step", "size_sec"), &Animation::set_step);
ClassDB::bind_method(D_METHOD("get_step"), &Animation::get_step);
@@ -2675,14 +3823,19 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &Animation::clear);
ClassDB::bind_method(D_METHOD("copy_track", "track_idx", "to_animation"), &Animation::copy_track);
+ ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0));
+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001"), "set_length", "get_length");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step");
ADD_SIGNAL(MethodInfo("tracks_changed"));
BIND_ENUM_CONSTANT(TYPE_VALUE);
- BIND_ENUM_CONSTANT(TYPE_TRANSFORM3D);
+ BIND_ENUM_CONSTANT(TYPE_POSITION_3D);
+ BIND_ENUM_CONSTANT(TYPE_ROTATION_3D);
+ BIND_ENUM_CONSTANT(TYPE_SCALE_3D);
+ BIND_ENUM_CONSTANT(TYPE_BLEND_SHAPE);
BIND_ENUM_CONSTANT(TYPE_METHOD);
BIND_ENUM_CONSTANT(TYPE_BEZIER);
BIND_ENUM_CONSTANT(TYPE_AUDIO);
@@ -2696,6 +3849,13 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(UPDATE_DISCRETE);
BIND_ENUM_CONSTANT(UPDATE_TRIGGER);
BIND_ENUM_CONSTANT(UPDATE_CAPTURE);
+
+ BIND_ENUM_CONSTANT(LOOP_NONE);
+ BIND_ENUM_CONSTANT(LOOP_LINEAR);
+ BIND_ENUM_CONSTANT(LOOP_PINGPONG);
+
+ BIND_ENUM_CONSTANT(HANDLE_MODE_FREE);
+ BIND_ENUM_CONSTANT(HANDLE_MODE_BALANCED);
}
void Animation::clear() {
@@ -2703,225 +3863,1477 @@ void Animation::clear() {
memdelete(tracks[i]);
}
tracks.clear();
- loop = false;
+ loop_mode = LOOP_NONE;
length = 1;
+ compression.enabled = false;
+ compression.bounds.clear();
+ compression.pages.clear();
+ compression.fps = 120;
emit_changed();
emit_signal(SceneStringNames::get_singleton()->tracks_changed);
}
-bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, real_t p_alowed_linear_err, real_t p_alowed_angular_err, real_t p_max_optimizable_angle, const Vector3 &p_norm) {
- real_t c = (t1.time - t0.time) / (t2.time - t0.time);
- real_t t[3] = { -1, -1, -1 };
+bool Animation::_position_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_allowed_linear_err, real_t p_allowed_angular_error, const Vector3 &p_norm) {
+ const Vector3 &v0 = t0.value;
+ const Vector3 &v1 = t1.value;
+ const Vector3 &v2 = t2.value;
+
+ if (v0.is_equal_approx(v2)) {
+ //0 and 2 are close, let's see if 1 is close
+ if (!v0.is_equal_approx(v1)) {
+ //not close, not optimizable
+ return false;
+ }
+
+ } else {
+ Vector3 pd = (v2 - v0);
+ real_t d0 = pd.dot(v0);
+ real_t d1 = pd.dot(v1);
+ real_t d2 = pd.dot(v2);
+ if (d1 < d0 || d1 > d2) {
+ return false;
+ }
- { //translation
+ Vector3 s[2] = { v0, v2 };
+ real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
- const Vector3 &v0 = t0.value.loc;
- const Vector3 &v1 = t1.value.loc;
- const Vector3 &v2 = t2.value.loc;
+ if (d > pd.length() * p_allowed_linear_err) {
+ return false; //beyond allowed error for collinearity
+ }
- if (v0.is_equal_approx(v2)) {
- //0 and 2 are close, let's see if 1 is close
- if (!v0.is_equal_approx(v1)) {
- //not close, not optimizable
- return false;
+ if (p_norm != Vector3() && Math::acos(pd.normalized().dot(p_norm)) > p_allowed_angular_error) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Animation::_rotation_track_optimize_key(const TKey<Quaternion> &t0, const TKey<Quaternion> &t1, const TKey<Quaternion> &t2, real_t p_allowed_angular_error, float p_max_optimizable_angle) {
+ const Quaternion &q0 = t0.value;
+ const Quaternion &q1 = t1.value;
+ const Quaternion &q2 = t2.value;
+
+ //localize both to rotation from q0
+
+ if (q0.is_equal_approx(q2)) {
+ if (!q0.is_equal_approx(q1)) {
+ return false;
+ }
+
+ } else {
+ Quaternion r02 = (q0.inverse() * q2).normalized();
+ Quaternion r01 = (q0.inverse() * q1).normalized();
+
+ Vector3 v02, v01;
+ real_t a02, a01;
+
+ r02.get_axis_angle(v02, a02);
+ r01.get_axis_angle(v01, a01);
+
+ if (Math::abs(a02) > p_max_optimizable_angle) {
+ return false;
+ }
+
+ if (v01.dot(v02) < 0) {
+ //make sure both rotations go the same way to compare
+ v02 = -v02;
+ a02 = -a02;
+ }
+
+ real_t err_01 = Math::acos(v01.normalized().dot(v02.normalized())) / Math_PI;
+ if (err_01 > p_allowed_angular_error) {
+ //not rotating in the same axis
+ return false;
+ }
+
+ if (a01 * a02 < 0) {
+ //not rotating in the same direction
+ return false;
+ }
+
+ real_t tr = a01 / a02;
+ if (tr < 0 || tr > 1) {
+ return false; //rotating too much or too less
+ }
+ }
+
+ return true;
+}
+
+bool Animation::_scale_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_allowed_linear_error) {
+ const Vector3 &v0 = t0.value;
+ const Vector3 &v1 = t1.value;
+ const Vector3 &v2 = t2.value;
+
+ if (v0.is_equal_approx(v2)) {
+ //0 and 2 are close, let's see if 1 is close
+ if (!v0.is_equal_approx(v1)) {
+ //not close, not optimizable
+ return false;
+ }
+
+ } else {
+ Vector3 pd = (v2 - v0);
+ real_t d0 = pd.dot(v0);
+ real_t d1 = pd.dot(v1);
+ real_t d2 = pd.dot(v2);
+ if (d1 < d0 || d1 > d2) {
+ return false; //beyond segment range
+ }
+
+ Vector3 s[2] = { v0, v2 };
+ real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
+
+ if (d > pd.length() * p_allowed_linear_error) {
+ return false; //beyond allowed error for colinearity
+ }
+ }
+
+ return true;
+}
+
+bool Animation::_blend_shape_track_optimize_key(const TKey<float> &t0, const TKey<float> &t1, const TKey<float> &t2, real_t p_allowed_unit_error) {
+ float v0 = t0.value;
+ float v1 = t1.value;
+ float v2 = t2.value;
+
+ if (Math::is_equal_approx(v1, v2, p_allowed_unit_error)) {
+ //0 and 2 are close, let's see if 1 is close
+ if (!Math::is_equal_approx(v0, v1, p_allowed_unit_error)) {
+ //not close, not optimizable
+ return false;
+ }
+
+ } else {
+ /*
+ TODO eventually discuss a way to optimize these better.
+ float pd = (v2 - v0);
+ real_t d0 = pd.dot(v0);
+ real_t d1 = pd.dot(v1);
+ real_t d2 = pd.dot(v2);
+ if (d1 < d0 || d1 > d2) {
+ return false; //beyond segment range
+ }
+
+ float s[2] = { v0, v2 };
+ real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
+
+ if (d > pd.length() * p_allowed_linear_error) {
+ return false; //beyond allowed error for colinearity
+ }
+*/
+ }
+
+ return true;
+}
+
+void Animation::_position_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err) {
+ ERR_FAIL_INDEX(p_idx, tracks.size());
+ ERR_FAIL_COND(tracks[p_idx]->type != TYPE_POSITION_3D);
+ PositionTrack *tt = static_cast<PositionTrack *>(tracks[p_idx]);
+ bool prev_erased = false;
+ TKey<Vector3> first_erased;
+
+ Vector3 norm;
+
+ for (int i = 1; i < tt->positions.size() - 1; i++) {
+ TKey<Vector3> &t0 = tt->positions.write[i - 1];
+ TKey<Vector3> &t1 = tt->positions.write[i];
+ TKey<Vector3> &t2 = tt->positions.write[i + 1];
+
+ bool erase = _position_track_optimize_key(t0, t1, t2, p_allowed_linear_err, p_allowed_angular_err, norm);
+ if (erase && !prev_erased) {
+ norm = (t2.value - t1.value).normalized();
+ }
+
+ if (prev_erased && !_position_track_optimize_key(t0, first_erased, t2, p_allowed_linear_err, p_allowed_angular_err, norm)) {
+ //avoid error to go beyond first erased key
+ erase = false;
+ }
+
+ if (erase) {
+ if (!prev_erased) {
+ first_erased = t1;
+ prev_erased = true;
}
+ tt->positions.remove(i);
+ i--;
+
} else {
- Vector3 pd = (v2 - v0);
- real_t d0 = pd.dot(v0);
- real_t d1 = pd.dot(v1);
- real_t d2 = pd.dot(v2);
- if (d1 < d0 || d1 > d2) {
- return false;
- }
+ prev_erased = false;
+ norm = Vector3();
+ }
+ }
+}
+
+void Animation::_rotation_track_optimize(int p_idx, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
+ ERR_FAIL_INDEX(p_idx, tracks.size());
+ ERR_FAIL_COND(tracks[p_idx]->type != TYPE_ROTATION_3D);
+ RotationTrack *tt = static_cast<RotationTrack *>(tracks[p_idx]);
+ bool prev_erased = false;
+ TKey<Quaternion> first_erased;
- Vector3 s[2] = { v0, v2 };
- real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
+ for (int i = 1; i < tt->rotations.size() - 1; i++) {
+ TKey<Quaternion> &t0 = tt->rotations.write[i - 1];
+ TKey<Quaternion> &t1 = tt->rotations.write[i];
+ TKey<Quaternion> &t2 = tt->rotations.write[i + 1];
+
+ bool erase = _rotation_track_optimize_key(t0, t1, t2, p_allowed_angular_err, p_max_optimizable_angle);
+
+ if (prev_erased && !_rotation_track_optimize_key(t0, first_erased, t2, p_allowed_angular_err, p_max_optimizable_angle)) {
+ //avoid error to go beyond first erased key
+ erase = false;
+ }
- if (d > pd.length() * p_alowed_linear_err) {
- return false; //beyond allowed error for collinearity
+ if (erase) {
+ if (!prev_erased) {
+ first_erased = t1;
+ prev_erased = true;
}
- if (p_norm != Vector3() && Math::acos(pd.normalized().dot(p_norm)) > p_alowed_angular_err) {
- return false;
+ tt->rotations.remove(i);
+ i--;
+
+ } else {
+ prev_erased = false;
+ }
+ }
+}
+
+void Animation::_scale_track_optimize(int p_idx, real_t p_allowed_linear_err) {
+ ERR_FAIL_INDEX(p_idx, tracks.size());
+ ERR_FAIL_COND(tracks[p_idx]->type != TYPE_SCALE_3D);
+ ScaleTrack *tt = static_cast<ScaleTrack *>(tracks[p_idx]);
+ bool prev_erased = false;
+ TKey<Vector3> first_erased;
+
+ for (int i = 1; i < tt->scales.size() - 1; i++) {
+ TKey<Vector3> &t0 = tt->scales.write[i - 1];
+ TKey<Vector3> &t1 = tt->scales.write[i];
+ TKey<Vector3> &t2 = tt->scales.write[i + 1];
+
+ bool erase = _scale_track_optimize_key(t0, t1, t2, p_allowed_linear_err);
+
+ if (prev_erased && !_scale_track_optimize_key(t0, first_erased, t2, p_allowed_linear_err)) {
+ //avoid error to go beyond first erased key
+ erase = false;
+ }
+
+ if (erase) {
+ if (!prev_erased) {
+ first_erased = t1;
+ prev_erased = true;
}
- t[0] = (d1 - d0) / (d2 - d0);
+ tt->scales.remove(i);
+ i--;
+
+ } else {
+ prev_erased = false;
}
}
+}
+
+void Animation::_blend_shape_track_optimize(int p_idx, real_t p_allowed_linear_err) {
+ ERR_FAIL_INDEX(p_idx, tracks.size());
+ ERR_FAIL_COND(tracks[p_idx]->type != TYPE_BLEND_SHAPE);
+ BlendShapeTrack *tt = static_cast<BlendShapeTrack *>(tracks[p_idx]);
+ bool prev_erased = false;
+ TKey<float> first_erased;
+ first_erased.value = 0.0;
- { //rotation
+ for (int i = 1; i < tt->blend_shapes.size() - 1; i++) {
+ TKey<float> &t0 = tt->blend_shapes.write[i - 1];
+ TKey<float> &t1 = tt->blend_shapes.write[i];
+ TKey<float> &t2 = tt->blend_shapes.write[i + 1];
- const Quaternion &q0 = t0.value.rot;
- const Quaternion &q1 = t1.value.rot;
- const Quaternion &q2 = t2.value.rot;
+ bool erase = _blend_shape_track_optimize_key(t0, t1, t2, p_allowed_linear_err);
- //localize both to rotation from q0
+ if (prev_erased && !_blend_shape_track_optimize_key(t0, first_erased, t2, p_allowed_linear_err)) {
+ //avoid error to go beyond first erased key
+ erase = false;
+ }
- if (q0.is_equal_approx(q2)) {
- if (!q0.is_equal_approx(q1)) {
- return false;
+ if (erase) {
+ if (!prev_erased) {
+ first_erased = t1;
+ prev_erased = true;
}
+ tt->blend_shapes.remove(i);
+ i--;
+
} else {
- Quaternion r02 = (q0.inverse() * q2).normalized();
- Quaternion r01 = (q0.inverse() * q1).normalized();
+ prev_erased = false;
+ }
+ }
+}
+
+void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
+ for (int i = 0; i < tracks.size(); i++) {
+ if (track_is_compressed(i)) {
+ continue; //not possible to optimize compressed track
+ }
+ if (tracks[i]->type == TYPE_POSITION_3D) {
+ _position_track_optimize(i, p_allowed_linear_err, p_allowed_angular_err);
+ } else if (tracks[i]->type == TYPE_ROTATION_3D) {
+ _rotation_track_optimize(i, p_allowed_angular_err, p_max_optimizable_angle);
+ } else if (tracks[i]->type == TYPE_SCALE_3D) {
+ _scale_track_optimize(i, p_allowed_linear_err);
+ } else if (tracks[i]->type == TYPE_BLEND_SHAPE) {
+ _blend_shape_track_optimize(i, p_allowed_linear_err);
+ }
+ }
+}
- Vector3 v02, v01;
- real_t a02, a01;
+#define print_animc(m_str)
+//#define print_animc(m_str) print_line(m_str);
- r02.get_axis_angle(v02, a02);
- r01.get_axis_angle(v01, a01);
+struct AnimationCompressionDataState {
+ enum {
+ MIN_OPTIMIZE_PACKETS = 5,
+ MAX_PACKETS = 16
+ };
- if (Math::abs(a02) > p_max_optimizable_angle) {
- return false;
+ uint32_t components = 3;
+ LocalVector<uint8_t> data; //commited packets
+ struct PacketData {
+ int32_t data[3] = { 0, 0, 0 };
+ uint32_t frame = 0;
+ };
+
+ float split_tolerance = 1.5;
+
+ LocalVector<PacketData> temp_packets;
+
+ //used for rollback if the new frame does not fit
+ int32_t validated_packet_count = -1;
+
+ static int32_t _compute_delta16_signed(int32_t p_from, int32_t p_to) {
+ int32_t delta = p_to - p_from;
+ if (delta > 32767) {
+ return delta - 65536; // use wrap around
+ } else if (delta < -32768) {
+ return 65536 + delta; // use wrap around
+ }
+ return delta;
+ }
+
+ static uint32_t _compute_shift_bits_signed(int32_t p_delta) {
+ if (p_delta == 0) {
+ return 0;
+ } else if (p_delta < 0) {
+ p_delta = ABS(p_delta) - 1;
+ if (p_delta == 0) {
+ return 1;
+ }
+ }
+ return nearest_shift(p_delta);
+ }
+
+ void _compute_max_shifts(uint32_t p_from, uint32_t p_to, uint32_t *max_shifts, uint32_t &max_frame_delta_shift) const {
+ for (uint32_t j = 0; j < components; j++) {
+ max_shifts[j] = 0;
+ }
+ max_frame_delta_shift = 0;
+
+ for (uint32_t i = p_from + 1; i <= p_to; i++) {
+ int32_t frame_delta = temp_packets[i].frame - temp_packets[i - 1].frame;
+ max_frame_delta_shift = MAX(max_frame_delta_shift, nearest_shift(frame_delta));
+ for (uint32_t j = 0; j < components; j++) {
+ int32_t diff = _compute_delta16_signed(temp_packets[i - 1].data[j], temp_packets[i].data[j]);
+ uint32_t shift = _compute_shift_bits_signed(diff);
+ max_shifts[j] = MAX(shift, max_shifts[j]);
}
+ }
+ }
+
+ bool insert_key(uint32_t p_frame, const Vector3i &p_key) {
+ if (temp_packets.size() == MAX_PACKETS) {
+ commit_temp_packets();
+ }
+ PacketData packet;
+ packet.frame = p_frame;
+ for (int i = 0; i < 3; i++) {
+ ERR_FAIL_COND_V(p_key[i] > 65535, false); // Sanity check
+ packet.data[i] = p_key[i];
+ }
+
+ temp_packets.push_back(packet);
- if (v01.dot(v02) < 0) {
- //make sure both rotations go the same way to compare
- v02 = -v02;
- a02 = -a02;
+ if (temp_packets.size() >= MIN_OPTIMIZE_PACKETS) {
+ uint32_t max_shifts[3] = { 0, 0, 0 }; // Base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+ // Compute the average shift before the packet was added
+ _compute_max_shifts(0, temp_packets.size() - 2, max_shifts, max_frame_delta_shift);
+
+ float prev_packet_size_avg = 0;
+ prev_packet_size_avg = float(1 << max_frame_delta_shift);
+ for (uint32_t i = 0; i < components; i++) {
+ prev_packet_size_avg += float(1 << max_shifts[i]);
}
+ prev_packet_size_avg /= float(1 + components);
- real_t err_01 = Math::acos(v01.normalized().dot(v02.normalized())) / Math_PI;
- if (err_01 > p_alowed_angular_err) {
- //not rotating in the same axis
- return false;
+ _compute_max_shifts(temp_packets.size() - 2, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+
+ float new_packet_size_avg = 0;
+ new_packet_size_avg = float(1 << max_frame_delta_shift);
+ for (uint32_t i = 0; i < components; i++) {
+ new_packet_size_avg += float(1 << max_shifts[i]);
}
+ new_packet_size_avg /= float(1 + components);
- if (a01 * a02 < 0) {
- //not rotating in the same direction
- return false;
+ print_animc("packet count: " + rtos(temp_packets.size() - 1) + " size avg " + rtos(prev_packet_size_avg) + " new avg " + rtos(new_packet_size_avg));
+ float ratio = (prev_packet_size_avg < new_packet_size_avg) ? (new_packet_size_avg / prev_packet_size_avg) : (prev_packet_size_avg / new_packet_size_avg);
+
+ if (ratio > split_tolerance) {
+ print_animc("split!");
+ temp_packets.resize(temp_packets.size() - 1);
+ commit_temp_packets();
+ temp_packets.push_back(packet);
}
+ }
+
+ return temp_packets.size() == 1; // First key
+ }
- real_t tr = a01 / a02;
- if (tr < 0 || tr > 1) {
- return false; //rotating too much or too less
+ uint32_t get_temp_packet_size() const {
+ if (temp_packets.size() == 0) {
+ return 0;
+ } else if (temp_packets.size() == 1) {
+ return components == 1 ? 4 : 8; // 1 component packet is 16 bits and 16 bits unused. 3 component packets is 48 bits and 16 bits unused
+ }
+ uint32_t max_shifts[3] = { 0, 0, 0 }; //base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+
+ _compute_max_shifts(0, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+
+ uint32_t size_bits = 16; //base value (all 4 bits of shift sizes for x,y,z,time)
+ size_bits += max_frame_delta_shift * (temp_packets.size() - 1); //times
+ for (uint32_t j = 0; j < components; j++) {
+ size_bits += 16; //base value
+ uint32_t shift = max_shifts[j];
+ if (shift > 0) {
+ shift += 1; //if not zero, add sign bit
}
+ size_bits += shift * (temp_packets.size() - 1);
+ }
+ if (size_bits % 8 != 0) { //wrap to 8 bits
+ size_bits += 8 - (size_bits % 8);
+ }
+ uint32_t size_bytes = size_bits / 8; //wrap to words
+ if (size_bytes % 4 != 0) {
+ size_bytes += 4 - (size_bytes % 4);
+ }
+ return size_bytes;
+ }
- t[1] = tr;
+ static void _push_bits(LocalVector<uint8_t> &data, uint32_t &r_buffer, uint32_t &r_bits_used, uint32_t p_value, uint32_t p_bits) {
+ r_buffer |= p_value << r_bits_used;
+ r_bits_used += p_bits;
+ while (r_bits_used >= 8) {
+ uint8_t byte = r_buffer & 0xFF;
+ data.push_back(byte);
+ r_buffer >>= 8;
+ r_bits_used -= 8;
}
}
- { //scale
+ void commit_temp_packets() {
+ if (temp_packets.size() == 0) {
+ return; //nohing to do
+ }
+#define DEBUG_PACKET_PUSH
+#ifdef DEBUG_PACKET_PUSH
+#ifndef _MSC_VER
+#warning Debugging packet push, disable this code in production to gain a bit more import performance.
+#endif
+ uint32_t debug_packet_push = get_temp_packet_size();
+ uint32_t debug_data_size = data.size();
+#endif
+ // Store header
- const Vector3 &v0 = t0.value.scale;
- const Vector3 &v1 = t1.value.scale;
- const Vector3 &v2 = t2.value.scale;
+ uint8_t header[8];
+ uint32_t header_bytes = 0;
+ for (uint32_t i = 0; i < components; i++) {
+ encode_uint16(temp_packets[0].data[i], &header[header_bytes]);
+ header_bytes += 2;
+ }
- if (v0.is_equal_approx(v2)) {
- //0 and 2 are close, let's see if 1 is close
- if (!v0.is_equal_approx(v1)) {
- //not close, not optimizable
- return false;
+ uint32_t max_shifts[3] = { 0, 0, 0 }; //base sizes, 16 bit
+ uint32_t max_frame_delta_shift = 0;
+
+ if (temp_packets.size() > 1) {
+ _compute_max_shifts(0, temp_packets.size() - 1, max_shifts, max_frame_delta_shift);
+ uint16_t shift_header = (max_frame_delta_shift - 1) << 12;
+ for (uint32_t i = 0; i < components; i++) {
+ shift_header |= max_shifts[i] << (4 * i);
}
- } else {
- Vector3 pd = (v2 - v0);
- real_t d0 = pd.dot(v0);
- real_t d1 = pd.dot(v1);
- real_t d2 = pd.dot(v2);
- if (d1 < d0 || d1 > d2) {
- return false; //beyond segment range
+ encode_uint16(shift_header, &header[header_bytes]);
+ header_bytes += 2;
+ }
+
+ while (header_bytes % 4 != 0) {
+ header[header_bytes++] = 0;
+ }
+
+ for (uint32_t i = 0; i < header_bytes; i++) {
+ data.push_back(header[i]);
+ }
+
+ if (temp_packets.size() == 1) {
+ temp_packets.clear();
+ validated_packet_count = 0;
+ return; //only header stored, nothing else to do
+ }
+
+ uint32_t bit_buffer = 0;
+ uint32_t bits_used = 0;
+
+ for (uint32_t i = 1; i < temp_packets.size(); i++) {
+ uint32_t frame_delta = temp_packets[i].frame - temp_packets[i - 1].frame;
+ _push_bits(data, bit_buffer, bits_used, frame_delta, max_frame_delta_shift);
+
+ for (uint32_t j = 0; j < components; j++) {
+ if (max_shifts[j] == 0) {
+ continue; // Zero delta, do not store
+ }
+ int32_t delta = _compute_delta16_signed(temp_packets[i - 1].data[j], temp_packets[i].data[j]);
+
+ ERR_FAIL_COND(delta < -32768 || delta > 32767); //sanity check
+
+ uint16_t deltau;
+ if (delta < 0) {
+ deltau = (ABS(delta) - 1) | (1 << max_shifts[j]);
+ } else {
+ deltau = delta;
+ }
+ _push_bits(data, bit_buffer, bits_used, deltau, max_shifts[j] + 1); // Include sign bit
+ }
+ }
+ if (bits_used != 0) {
+ ERR_FAIL_COND(bit_buffer > 0xFF); // Sanity check
+ data.push_back(bit_buffer);
+ }
+
+ while (data.size() % 4 != 0) {
+ data.push_back(0); //pad to align with 4
+ }
+
+ temp_packets.clear();
+ validated_packet_count = 0;
+
+#ifdef DEBUG_PACKET_PUSH
+ ERR_FAIL_COND((data.size() - debug_data_size) != debug_packet_push);
+#endif
+ }
+};
+
+struct AnimationCompressionTimeState {
+ struct Packet {
+ uint32_t frame;
+ uint32_t offset;
+ uint32_t count;
+ };
+
+ LocalVector<Packet> packets;
+ //used for rollback
+ int32_t key_index = 0;
+ int32_t validated_packet_count = 0;
+ int32_t validated_key_index = -1;
+ bool needs_start_frame = false;
+};
+
+Vector3i Animation::_compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key, float p_time) {
+ Vector3i values;
+ TrackType tt = track_get_type(p_track);
+ switch (tt) {
+ case TYPE_POSITION_3D: {
+ Vector3 pos;
+ if (p_key >= 0) {
+ position_track_get_key(p_track, p_key, &pos);
+ } else {
+ position_track_interpolate(p_track, p_time, &pos);
+ }
+ pos = (pos - p_bounds.position) / p_bounds.size;
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(pos[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_ROTATION_3D: {
+ Quaternion rot;
+ if (p_key >= 0) {
+ rotation_track_get_key(p_track, p_key, &rot);
+ } else {
+ rotation_track_interpolate(p_track, p_time, &rot);
+ }
+ Vector3 axis = rot.get_axis();
+ float angle = rot.get_angle();
+ angle = Math::fposmod(double(angle), double(Math_PI * 2.0));
+ Vector2 oct = axis.octahedron_encode();
+ Vector3 rot_norm(oct.x, oct.y, angle / (Math_PI * 2.0)); // high resolution rotation in 0-1 angle.
+
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(rot_norm[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_SCALE_3D: {
+ Vector3 scale;
+ if (p_key >= 0) {
+ scale_track_get_key(p_track, p_key, &scale);
+ } else {
+ scale_track_interpolate(p_track, p_time, &scale);
+ }
+ scale = (scale - p_bounds.position) / p_bounds.size;
+ for (int j = 0; j < 3; j++) {
+ values[j] = CLAMP(int32_t(scale[j] * 65535.0), 0, 65535);
+ }
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ float blend;
+ if (p_key >= 0) {
+ blend_shape_track_get_key(p_track, p_key, &blend);
+ } else {
+ blend_shape_track_interpolate(p_track, p_time, &blend);
}
- Vector3 s[2] = { v0, v2 };
- real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
+ blend = (blend / float(Compression::BLEND_SHAPE_RANGE)) * 0.5 + 0.5;
+ values[0] = CLAMP(int32_t(blend * 65535.0), 0, 65535);
+ } break;
+ default: {
+ ERR_FAIL_V(Vector3i()); //sanity check
+ } break;
+ }
- if (d > pd.length() * p_alowed_linear_err) {
- return false; //beyond allowed error for collinearity
+ return values;
+}
+
+struct AnimationCompressionBufferBitsRead {
+ uint32_t buffer = 0;
+ uint32_t used = 0;
+ const uint8_t *src_data = nullptr;
+
+ _FORCE_INLINE_ uint32_t read(uint32_t p_bits) {
+ uint32_t output = 0;
+ uint32_t written = 0;
+ while (p_bits > 0) {
+ if (used == 0) {
+ used = 8;
+ buffer = *src_data;
+ src_data++;
}
+ uint32_t to_write = MIN(used, p_bits);
+ output |= (buffer & ((1 << to_write) - 1)) << written;
+ buffer >>= to_write;
+ used -= to_write;
+ p_bits -= to_write;
+ written += to_write;
+ }
+ return output;
+ }
+};
+
+void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tolerance) {
+ ERR_FAIL_COND_MSG(compression.enabled, "This animation is already compressed");
+
+ p_split_tolerance = CLAMP(p_split_tolerance, 1.1, 8.0);
+ compression.pages.clear();
+
+ uint32_t base_page_size = 0; // Before compressing pages, compute how large the "end page" datablock is.
+ LocalVector<uint32_t> tracks_to_compress;
+ LocalVector<AABB> track_bounds;
+ const uint32_t time_packet_size = 4;
- t[2] = (d1 - d0) / (d2 - d0);
+ const uint32_t track_header_size = 4 + 4 + 4; // pointer to time (4 bytes), amount of time keys (4 bytes) pointer to track data (4 bytes)
+
+ for (int i = 0; i < get_track_count(); i++) {
+ TrackType type = track_get_type(i);
+ if (type != TYPE_POSITION_3D && type != TYPE_ROTATION_3D && type != TYPE_SCALE_3D && type != TYPE_BLEND_SHAPE) {
+ continue;
+ }
+ if (track_get_key_count(i) == 0) {
+ continue; //do not compress, no keys
}
+ base_page_size += track_header_size; //pointer to beginning of each track timeline and amount of time keys
+ base_page_size += time_packet_size; //for end of track time marker
+ base_page_size += (type == TYPE_BLEND_SHAPE) ? 4 : 8; // at least the end of track packet (at much 8 bytes). This could be less, but have to be pessimistic.
+ tracks_to_compress.push_back(i);
+
+ AABB bounds;
+
+ if (type == TYPE_POSITION_3D) {
+ AABB aabb;
+ int kcount = track_get_key_count(i);
+ for (int j = 0; j < kcount; j++) {
+ Vector3 pos;
+ position_track_get_key(i, j, &pos);
+ if (j == 0) {
+ aabb.position = pos;
+ } else {
+ aabb.expand_to(pos);
+ }
+ }
+ for (int j = 0; j < 3; j++) {
+ //cant have zero
+ if (aabb.size[j] < CMP_EPSILON) {
+ aabb.size[j] = CMP_EPSILON;
+ }
+ }
+ bounds = aabb;
+ }
+ if (type == TYPE_SCALE_3D) {
+ AABB aabb;
+ int kcount = track_get_key_count(i);
+ for (int j = 0; j < kcount; j++) {
+ Vector3 scale;
+ scale_track_get_key(i, j, &scale);
+ if (j == 0) {
+ aabb.position = scale;
+ } else {
+ aabb.expand_to(scale);
+ }
+ }
+ for (int j = 0; j < 3; j++) {
+ //cant have zero
+ if (aabb.size[j] < CMP_EPSILON) {
+ aabb.size[j] = CMP_EPSILON;
+ }
+ }
+ bounds = aabb;
+ }
+
+ track_bounds.push_back(bounds);
}
- bool erase = false;
- if (t[0] == -1 && t[1] == -1 && t[2] == -1) {
- erase = true;
- } else {
- erase = true;
- real_t lt = -1.0;
- for (int j = 0; j < 3; j++) {
- //search for t on first, one must be it
- if (t[j] != -1) {
- lt = t[j]; //official t
- //validate rest
- for (int k = j + 1; k < 3; k++) {
- if (t[k] == -1) {
- continue;
+ if (tracks_to_compress.size() == 0) {
+ return; //nothing to compress
+ }
+
+ print_animc("Anim Compression:");
+ print_animc("-----------------");
+ print_animc("Tracks to compress: " + itos(tracks_to_compress.size()));
+
+ uint32_t current_frame = 0;
+ uint32_t base_page_frame = 0;
+ double frame_len = 1.0 / double(p_fps);
+ const uint32_t max_frames_per_page = 65536;
+
+ print_animc("Frame Len: " + rtos(frame_len));
+
+ LocalVector<AnimationCompressionDataState> data_tracks;
+ LocalVector<AnimationCompressionTimeState> time_tracks;
+
+ data_tracks.resize(tracks_to_compress.size());
+ time_tracks.resize(tracks_to_compress.size());
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].split_tolerance = p_split_tolerance;
+ if (track_get_type(tracks_to_compress[i]) == TYPE_BLEND_SHAPE) {
+ data_tracks[i].components = 1;
+ } else {
+ data_tracks[i].components = 3;
+ }
+ }
+
+ while (true) {
+ // Begin by finding the keyframe in all tracks with the time closest to the current time
+ const uint32_t FRAME_MAX = 0xFFFFFFFF;
+ const int32_t NO_TRACK_FOUND = -1;
+ uint32_t best_frame = FRAME_MAX;
+ uint32_t best_invalid_frame = FRAME_MAX;
+ int32_t best_frame_track = NO_TRACK_FOUND; // Default is -1, which means all keyframes for this page are exhausted.
+ bool start_frame = false;
+
+ for (uint32_t i = 0; i < tracks_to_compress.size(); i++) {
+ uint32_t uncomp_track = tracks_to_compress[i];
+
+ if (time_tracks[i].key_index == track_get_key_count(uncomp_track)) {
+ if (time_tracks[i].needs_start_frame) {
+ start_frame = true;
+ best_frame = base_page_frame;
+ best_frame_track = i;
+ time_tracks[i].needs_start_frame = false;
+ break;
+ } else {
+ continue; // This track is exhausted (all keys were added already), don't consider.
+ }
+ }
+
+ uint32_t key_frame = double(track_get_key_time(uncomp_track, time_tracks[i].key_index)) / frame_len;
+
+ if (time_tracks[i].needs_start_frame && key_frame > base_page_frame) {
+ start_frame = true;
+ best_frame = base_page_frame;
+ best_frame_track = i;
+ time_tracks[i].needs_start_frame = false;
+ break;
+ }
+
+ ERR_FAIL_COND(key_frame < base_page_frame); // Sanity check, should never happen
+
+ if (key_frame - base_page_frame >= max_frames_per_page) {
+ // Invalid because beyond the max frames allowed per page
+ best_invalid_frame = MIN(best_invalid_frame, key_frame);
+ } else if (key_frame < best_frame) {
+ best_frame = key_frame;
+ best_frame_track = i;
+ }
+ }
+
+ print_animc("*KEY*: Current Frame: " + itos(current_frame) + " Best Frame: " + rtos(best_frame) + " Best Track: " + itos(best_frame_track) + " Start: " + String(start_frame ? "true" : "false"));
+
+ if (!start_frame && best_frame > current_frame) {
+ // Any case where the current frame advanced, either because nothing was found or because something was found greater than the current one.
+ print_animc("\tAdvance Condition.");
+ bool rollback = false;
+
+ // The frame has advanced, time to validate the previous frame
+ uint32_t current_page_size = base_page_size;
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ uint32_t track_size = data_tracks[i].data.size(); // track size
+ track_size += data_tracks[i].get_temp_packet_size(); // Add the temporary data
+ if (track_size > Compression::MAX_DATA_TRACK_SIZE) {
+ rollback = true; //track to large, time track can't point to keys any longer, because key offset is 12 bits
+ break;
+ }
+ current_page_size += track_size;
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ current_page_size += time_tracks[i].packets.size() * 4; // time packet is 32 bits
+ }
+
+ if (!rollback && current_page_size > p_page_size) {
+ rollback = true;
+ }
+
+ print_animc("\tCurrent Page Size: " + itos(current_page_size) + "/" + itos(p_page_size) + " Rollback? " + String(rollback ? "YES!" : "no"));
+
+ if (rollback) {
+ // Not valid any longer, so rollback and commit page
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].temp_packets.resize(data_tracks[i].validated_packet_count);
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ time_tracks[i].key_index = time_tracks[i].validated_key_index; //rollback key
+ time_tracks[i].packets.resize(time_tracks[i].validated_packet_count);
+ }
+
+ } else {
+ // All valid, so save rollback information
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ data_tracks[i].validated_packet_count = data_tracks[i].temp_packets.size();
+ }
+ for (uint32_t i = 0; i < time_tracks.size(); i++) {
+ time_tracks[i].validated_key_index = time_tracks[i].key_index;
+ time_tracks[i].validated_packet_count = time_tracks[i].packets.size();
+ }
+
+ // Accept this frame as the frame being processed (as long as it exists)
+ if (best_frame != FRAME_MAX) {
+ current_frame = best_frame;
+ print_animc("\tValidated, New Current Frame: " + itos(current_frame));
+ }
+ }
+
+ if (rollback || best_frame == FRAME_MAX) {
+ // Commit the page if had to rollback or if no track was found
+ print_animc("\tCommiting page..");
+
+ // The end frame for the page depends entirely on whether its valid or
+ // no more keys were found.
+ // If not valid, then the end frame is the current frame (as this means the current frame is being rolled back
+ // If valid, then the end frame is the next invalid one (in case more frames exist), or the current frame in case no more frames exist.
+ uint32_t page_end_frame = (rollback || best_frame == FRAME_MAX) ? current_frame : best_invalid_frame;
+
+ print_animc("\tEnd Frame: " + itos(page_end_frame) + ", " + rtos(page_end_frame * frame_len) + "s");
+
+ // Add finalizer frames and commit pending tracks
+ uint32_t finalizer_local_frame = page_end_frame - base_page_frame;
+
+ uint32_t total_page_size = 0;
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ if (data_tracks[i].temp_packets.size() == 0 || (data_tracks[i].temp_packets[data_tracks[i].temp_packets.size() - 1].frame) < finalizer_local_frame) {
+ // Add finalizer frame if it makes sense
+ Vector3i values = _compress_key(tracks_to_compress[i], track_bounds[i], -1, page_end_frame * frame_len);
+
+ bool first_key = data_tracks[i].insert_key(finalizer_local_frame, values);
+ if (first_key) {
+ AnimationCompressionTimeState::Packet p;
+ p.count = 1;
+ p.frame = finalizer_local_frame;
+ p.offset = data_tracks[i].data.size();
+ time_tracks[i].packets.push_back(p);
+ } else {
+ ERR_FAIL_COND(time_tracks[i].packets.size() == 0);
+ time_tracks[i].packets[time_tracks[i].packets.size() - 1].count++;
+ }
}
- if (Math::abs(lt - t[k]) > p_alowed_linear_err) {
- erase = false;
- break;
+ data_tracks[i].commit_temp_packets();
+ total_page_size += data_tracks[i].data.size();
+ total_page_size += time_tracks[i].packets.size() * 4;
+ total_page_size += track_header_size;
+
+ print_animc("\tTrack " + itos(i) + " time packets: " + itos(time_tracks[i].packets.size()) + " Packet data: " + itos(data_tracks[i].data.size()));
+ }
+
+ print_animc("\tTotal page Size: " + itos(total_page_size) + "/" + itos(p_page_size));
+
+ // Create Page
+ Vector<uint8_t> page_data;
+ page_data.resize(total_page_size);
+ {
+ uint8_t *page_ptr = page_data.ptrw();
+ uint32_t base_offset = data_tracks.size() * track_header_size;
+
+ for (uint32_t i = 0; i < data_tracks.size(); i++) {
+ encode_uint32(base_offset, page_ptr + (track_header_size * i + 0));
+ uint16_t *key_time_ptr = (uint16_t *)(page_ptr + base_offset);
+ for (uint32_t j = 0; j < time_tracks[i].packets.size(); j++) {
+ key_time_ptr[j * 2 + 0] = uint16_t(time_tracks[i].packets[j].frame);
+ uint16_t ptr = time_tracks[i].packets[j].offset / 4;
+ ptr |= (time_tracks[i].packets[j].count - 1) << 12;
+ key_time_ptr[j * 2 + 1] = ptr;
+ base_offset += 4;
+ }
+ encode_uint32(time_tracks[i].packets.size(), page_ptr + (track_header_size * i + 4));
+ encode_uint32(base_offset, page_ptr + (track_header_size * i + 8));
+ memcpy(page_ptr + base_offset, data_tracks[i].data.ptr(), data_tracks[i].data.size());
+ base_offset += data_tracks[i].data.size();
+
+ //reset track
+ data_tracks[i].data.clear();
+ data_tracks[i].temp_packets.clear();
+ data_tracks[i].validated_packet_count = -1;
+
+ time_tracks[i].needs_start_frame = true; //Not required the first time, but from now on it is.
+ time_tracks[i].packets.clear();
+ time_tracks[i].validated_key_index = -1;
+ time_tracks[i].validated_packet_count = 0;
}
}
- break;
+
+ Compression::Page page;
+ page.data = page_data;
+ page.time_offset = base_page_frame * frame_len;
+ compression.pages.push_back(page);
+
+ if (!rollback && best_invalid_frame == FRAME_MAX) {
+ break; // No more pages to add.
+ }
+
+ current_frame = page_end_frame;
+ base_page_frame = page_end_frame;
+
+ continue; // Start over
}
}
- ERR_FAIL_COND_V(lt == -1, false);
+ // A key was found for the current frame and all is ok
- if (erase) {
- if (Math::abs(lt - c) > p_alowed_linear_err) {
- //todo, evaluate changing the transition if this fails?
- //this could be done as a second pass and would be
- //able to optimize more
- erase = false;
+ uint32_t comp_track = best_frame_track;
+ Vector3i values;
+
+ if (start_frame) {
+ // Interpolate
+ values = _compress_key(tracks_to_compress[comp_track], track_bounds[comp_track], -1, base_page_frame * frame_len);
+ } else {
+ uint32_t key = time_tracks[comp_track].key_index;
+ values = _compress_key(tracks_to_compress[comp_track], track_bounds[comp_track], key);
+ time_tracks[comp_track].key_index++; //goto next key (but could be rolled back if beyond page size).
+ }
+
+ bool first_key = data_tracks[comp_track].insert_key(best_frame - base_page_frame, values);
+ if (first_key) {
+ AnimationCompressionTimeState::Packet p;
+ p.count = 1;
+ p.frame = best_frame - base_page_frame;
+ p.offset = data_tracks[comp_track].data.size();
+ time_tracks[comp_track].packets.push_back(p);
+ } else {
+ ERR_CONTINUE(time_tracks[comp_track].packets.size() == 0);
+ time_tracks[comp_track].packets[time_tracks[comp_track].packets.size() - 1].count++;
+ }
+ }
+
+ compression.bounds = track_bounds;
+ compression.fps = p_fps;
+ compression.enabled = true;
+
+ for (uint32_t i = 0; i < tracks_to_compress.size(); i++) {
+ Track *t = tracks[tracks_to_compress[i]];
+ t->interpolation = INTERPOLATION_LINEAR; //only linear supported
+ switch (t->type) {
+ case TYPE_POSITION_3D: {
+ PositionTrack *tt = static_cast<PositionTrack *>(t);
+ tt->positions.clear();
+ tt->compressed_track = i;
+ } break;
+ case TYPE_ROTATION_3D: {
+ RotationTrack *rt = static_cast<RotationTrack *>(t);
+ rt->rotations.clear();
+ rt->compressed_track = i;
+ } break;
+ case TYPE_SCALE_3D: {
+ ScaleTrack *st = static_cast<ScaleTrack *>(t);
+ st->scales.clear();
+ st->compressed_track = i;
+ print_line("Scale Bounds " + itos(i) + ": " + track_bounds[i]);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
+ bst->blend_shapes.clear();
+ bst->compressed_track = i;
+ } break;
+ default: {
+ }
+ }
+ }
+#if 1
+ uint32_t orig_size = 0;
+ for (int i = 0; i < get_track_count(); i++) {
+ switch (track_get_type(i)) {
+ case TYPE_SCALE_3D:
+ case TYPE_POSITION_3D: {
+ orig_size += sizeof(TKey<Vector3>) * track_get_key_count(i);
+ } break;
+ case TYPE_ROTATION_3D: {
+ orig_size += sizeof(TKey<Quaternion>) * track_get_key_count(i);
+ } break;
+ case TYPE_BLEND_SHAPE: {
+ orig_size += sizeof(TKey<float>) * track_get_key_count(i);
+ } break;
+ default: {
}
}
}
- return erase;
+ uint32_t new_size = 0;
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ new_size += compression.pages[i].data.size();
+ }
+
+ print_line("Original size: " + itos(orig_size) + " - Compressed size: " + itos(new_size) + " " + String::num(float(new_size) / float(orig_size) * 100, 2) + "% pages: " + itos(compression.pages.size()));
+#endif
}
-void Animation::_transform_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
- ERR_FAIL_INDEX(p_idx, tracks.size());
- ERR_FAIL_COND(tracks[p_idx]->type != TYPE_TRANSFORM3D);
- TransformTrack *tt = static_cast<TransformTrack *>(tracks[p_idx]);
- bool prev_erased = false;
- TKey<TransformKey> first_erased;
+bool Animation::_rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
- Vector3 norm;
+ if (!_fetch_compressed<3>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
- for (int i = 1; i < tt->transforms.size() - 1; i++) {
- TKey<TransformKey> &t0 = tt->transforms.write[i - 1];
- TKey<TransformKey> &t1 = tt->transforms.write[i];
- TKey<TransformKey> &t2 = tt->transforms.write[i + 1];
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_quaternion(current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_quaternion(next);
+ } else {
+ double c = (p_time - time_current) / (time_next - time_current);
+ Quaternion from = _uncompress_quaternion(current);
+ Quaternion to = _uncompress_quaternion(next);
+ r_ret = from.slerp(to, c);
+ }
- bool erase = _transform_track_optimize_key(t0, t1, t2, p_allowed_linear_err, p_allowed_angular_err, p_max_optimizable_angle, norm);
- if (erase && !prev_erased) {
- norm = (t2.value.loc - t1.value.loc).normalized();
+ return true;
+}
+
+bool Animation::_pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
+
+ if (!_fetch_compressed<3>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
+
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_pos_scale(p_compressed_track, current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_pos_scale(p_compressed_track, next);
+ } else {
+ double c = (p_time - time_current) / (time_next - time_current);
+ Vector3 from = _uncompress_pos_scale(p_compressed_track, current);
+ Vector3 to = _uncompress_pos_scale(p_compressed_track, next);
+ r_ret = from.lerp(to, c);
+ }
+
+ return true;
+}
+bool Animation::_blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const {
+ Vector3i current;
+ Vector3i next;
+ double time_current;
+ double time_next;
+
+ if (!_fetch_compressed<1>(p_compressed_track, p_time, current, time_current, next, time_next)) {
+ return false; //some sort of problem
+ }
+
+ if (time_current >= p_time || time_current == time_next) {
+ r_ret = _uncompress_blend_shape(current);
+ } else if (p_time >= time_next) {
+ r_ret = _uncompress_blend_shape(next);
+ } else {
+ float c = (p_time - time_current) / (time_next - time_current);
+ float from = _uncompress_blend_shape(current);
+ float to = _uncompress_blend_shape(next);
+ r_ret = Math::lerp(from, to, c);
+ }
+
+ return true;
+}
+
+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_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), false);
+ p_time = CLAMP(p_time, 0, length);
+ if (key_index) {
+ *key_index = 0;
+ }
+
+ double frame_to_sec = 1.0 / double(compression.fps);
+
+ int32_t page_index = -1;
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ if (compression.pages[i].time_offset > p_time) {
+ break;
}
+ page_index = i;
+ }
- if (prev_erased && !_transform_track_optimize_key(t0, first_erased, t2, p_allowed_linear_err, p_allowed_angular_err, p_max_optimizable_angle, norm)) {
- //avoid error to go beyond first erased key
- erase = false;
+ ERR_FAIL_COND_V(page_index == -1, false); //should not happen
+
+ double page_base_time = compression.pages[page_index].time_offset;
+ const uint8_t *page_data = compression.pages[page_index].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ int32_t packet_idx = 0;
+ double packet_time = double(time_keys[0]) * frame_to_sec + page_base_time;
+ uint32_t base_frame = time_keys[0];
+
+ for (uint32_t i = 1; i < time_key_count; i++) {
+ uint32_t f = time_keys[i * 2 + 0];
+ double frame_time = double(f) * frame_to_sec + page_base_time;
+
+ if (frame_time > p_time) {
+ break;
}
- if (erase) {
- if (!prev_erased) {
- first_erased = t1;
- prev_erased = true;
+ if (key_index) {
+ (*key_index) += (time_keys[(i - 1) * 2 + 1] >> 12) + 1;
+ }
+
+ packet_idx = i;
+ packet_time = frame_time;
+ base_frame = f;
+ }
+
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ uint16_t time_key_data = time_keys[packet_idx * 2 + 1];
+ uint32_t data_offset = (time_key_data & 0xFFF) * 4; // lower 12 bits
+ uint32_t data_count = (time_key_data >> 12) + 1;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ uint16_t decode[COMPONENTS];
+ uint16_t decode_next[COMPONENTS];
+
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ decode[i] = data_key[i];
+ decode_next[i] = data_key[i];
+ }
+
+ double next_time = packet_time;
+
+ if (p_time > packet_time) { // If its equal or less, then don't bother
+ if (data_count > 1) {
+ //decode forward
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ bit_width[i] = (data_key[COMPONENTS] >> (i * 4)) & 0xF;
}
- tt->transforms.remove(i);
- i--;
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
- } else {
- prev_erased = false;
- norm = Vector3();
+ AnimationCompressionBufferBitsRead buffer;
+
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (uint32_t i = 1; i < data_count; i++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ base_frame += frame_delta;
+
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ if (bit_width[j] == 0) {
+ continue; // do none
+ }
+ uint32_t valueu = buffer.read(bit_width[j] + 1);
+ bool sign = valueu & (1 << bit_width[j]);
+ int16_t value = valueu & ((1 << bit_width[j]) - 1);
+ if (sign) {
+ value = -value - 1;
+ }
+
+ decode_next[j] += value;
+ }
+
+ next_time = double(base_frame) * frame_to_sec + page_base_time;
+ if (p_time < next_time) {
+ break;
+ }
+
+ packet_time = next_time;
+
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ decode[j] = decode_next[j];
+ }
+
+ if (key_index) {
+ (*key_index)++;
+ }
+ }
+ }
+
+ if (p_time > next_time) { // > instead of >= because if its equal, then it will be properly interpolated anyway
+ // So, the last frame found still has a time that is less than the required frame,
+ // will have to interpolate with the first frame of the next timekey.
+
+ if ((uint32_t)packet_idx < time_key_count - 1) { // Sanity check but should not matter much, otherwise current next packet is last packet
+
+ uint16_t time_key_data_next = time_keys[(packet_idx + 1) * 2 + 1];
+ uint32_t data_offset_next = (time_key_data_next & 0xFFF) * 4; // Lower 12 bits
+
+ const uint16_t *data_key_next = (const uint16_t *)(data_keys_base + data_offset_next);
+ base_frame = time_keys[(packet_idx + 1) * 2 + 0];
+ next_time = double(base_frame) * frame_to_sec + page_base_time;
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ decode_next[i] = data_key_next[i];
+ }
+ }
+ }
+ }
+
+ r_current_time = packet_time;
+ r_next_time = next_time;
+
+ for (uint32_t i = 0; i < COMPONENTS; i++) {
+ r_current_value[i] = decode[i];
+ r_next_value[i] = decode_next[i];
+ }
+
+ return true;
+}
+
+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_UNSIGNED_INDEX(p_compressed_track, compression.bounds.size());
+
+ double frame_to_sec = 1.0 / double(compression.fps);
+ uint32_t key_index = 0;
+
+ for (uint32_t p = 0; p < compression.pages.size(); p++) {
+ if (compression.pages[p].time_offset >= p_time + p_delta) {
+ // Page beyond range
+ return;
+ }
+
+ // Page within range
+
+ uint32_t page_index = p;
+
+ double page_base_time = compression.pages[page_index].time_offset;
+ const uint8_t *page_data = compression.pages[page_index].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ for (uint32_t i = 0; i < time_key_count; i++) {
+ uint32_t f = time_keys[i * 2 + 0];
+ double frame_time = f * frame_to_sec + page_base_time;
+ if (frame_time >= p_time + p_delta) {
+ return;
+ } else if (frame_time >= p_time) {
+ r_indices->push_back(key_index);
+ }
+
+ key_index++;
+
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ uint16_t time_key_data = time_keys[i * 2 + 1];
+ uint32_t data_offset = (time_key_data & 0xFFF) * 4; // lower 12 bits
+ uint32_t data_count = (time_key_data >> 12) + 1;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ if (data_count > 1) {
+ //decode forward
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t j = 0; j < COMPONENTS; j++) {
+ bit_width[j] = (data_key[COMPONENTS] >> (j * 4)) & 0xF;
+ }
+
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
+
+ AnimationCompressionBufferBitsRead buffer;
+
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (uint32_t j = 1; j < data_count; j++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ f += frame_delta;
+
+ frame_time = f * frame_to_sec + page_base_time;
+ if (frame_time >= p_time + p_delta) {
+ return;
+ } else if (frame_time >= p_time) {
+ r_indices->push_back(key_index);
+ }
+
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ if (bit_width[k] == 0) {
+ continue; // do none
+ }
+ buffer.read(bit_width[k] + 1); // skip
+ }
+
+ key_index++;
+ }
+ }
}
}
}
-void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
- for (int i = 0; i < tracks.size(); i++) {
- if (tracks[i]->type == TYPE_TRANSFORM3D) {
- _transform_track_optimize(i, p_allowed_linear_err, p_allowed_angular_err, p_max_optimizable_angle);
+int Animation::_get_compressed_key_count(uint32_t p_compressed_track) const {
+ ERR_FAIL_COND_V(!compression.enabled, -1);
+ ERR_FAIL_UNSIGNED_INDEX_V(p_compressed_track, compression.bounds.size(), -1);
+
+ int key_count = 0;
+
+ for (uint32_t i = 0; i < compression.pages.size(); i++) {
+ const uint8_t *page_data = compression.pages[i].data.ptr();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+
+ for (uint32_t j = 0; j < time_key_count; j++) {
+ key_count += (time_keys[j * 2 + 1] >> 12) + 1;
+ }
+ }
+
+ return key_count;
+}
+
+Quaternion Animation::_uncompress_quaternion(const Vector3i &p_value) const {
+ Vector3 axis = Vector3::octahedron_decode(Vector2(float(p_value.x) / 65535.0, float(p_value.y) / 65535.0));
+ float angle = (float(p_value.z) / 65535.0) * 2.0 * Math_PI;
+ return Quaternion(axis, angle);
+}
+Vector3 Animation::_uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const {
+ Vector3 pos_norm(float(p_value.x) / 65535.0, float(p_value.y) / 65535.0, float(p_value.z) / 65535.0);
+ return compression.bounds[p_compressed_track].position + pos_norm * compression.bounds[p_compressed_track].size;
+}
+float Animation::_uncompress_blend_shape(const Vector3i &p_value) const {
+ float bsn = float(p_value.x) / 65535.0;
+ return (bsn * 2.0 - 1.0) * float(Compression::BLEND_SHAPE_RANGE);
+}
+
+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_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();
+#ifndef _MSC_VER
+#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported
+#endif
+ const uint32_t *indices = (const uint32_t *)page_data;
+ const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]];
+ uint32_t time_key_count = indices[p_compressed_track * 3 + 1];
+ const uint8_t *data_keys_base = (const uint8_t *)&page_data[indices[p_compressed_track * 3 + 2]];
+
+ for (uint32_t j = 0; j < time_key_count; j++) {
+ uint32_t subkeys = (time_keys[j * 2 + 1] >> 12) + 1;
+ if ((uint32_t)p_index < subkeys) {
+ uint16_t data_offset = (time_keys[j * 2 + 1] & 0xFFF) * 4;
+
+ const uint16_t *data_key = (const uint16_t *)(data_keys_base + data_offset);
+
+ uint16_t frame = time_keys[j * 2 + 0];
+ uint16_t decode[COMPONENTS];
+
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ decode[k] = data_key[k];
+ }
+
+ if (p_index > 0) {
+ uint32_t bit_width[COMPONENTS];
+ for (uint32_t k = 0; k < COMPONENTS; k++) {
+ bit_width[k] = (data_key[COMPONENTS] >> (k * 4)) & 0xF;
+ }
+ uint32_t frame_bit_width = (data_key[COMPONENTS] >> 12) + 1;
+
+ AnimationCompressionBufferBitsRead buffer;
+ buffer.src_data = (const uint8_t *)&data_key[COMPONENTS + 1];
+
+ for (int k = 0; k < p_index; k++) {
+ uint32_t frame_delta = buffer.read(frame_bit_width);
+ frame += frame_delta;
+ for (uint32_t l = 0; l < COMPONENTS; l++) {
+ if (bit_width[l] == 0) {
+ continue; // do none
+ }
+ uint32_t valueu = buffer.read(bit_width[l] + 1);
+ bool sign = valueu & (1 << bit_width[l]);
+ int16_t value = valueu & ((1 << bit_width[l]) - 1);
+ if (sign) {
+ value = -value - 1;
+ }
+
+ decode[l] += value;
+ }
+ }
+ }
+
+ r_time = compression.pages[i].time_offset + double(frame) / double(compression.fps);
+ for (uint32_t l = 0; l < COMPONENTS; l++) {
+ r_value[l] = decode[l];
+ }
+
+ return true;
+
+ } else {
+ p_index -= subkeys;
+ }
}
}
+
+ return false;
}
Animation::Animation() {}
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 9a410bd566..8e4287e4fb 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -32,6 +32,7 @@
#define ANIMATION_H
#include "core/io/resource.h"
+#include "core/templates/local_vector.h"
#define ANIM_MIN_LENGTH 0.001
@@ -42,7 +43,10 @@ class Animation : public Resource {
public:
enum TrackType {
TYPE_VALUE, ///< Set a value in a property, can be interpolated.
- TYPE_TRANSFORM3D, ///< Transform a node or a bone.
+ TYPE_POSITION_3D, ///< Position 3D track
+ TYPE_ROTATION_3D, ///< Rotation 3D track
+ TYPE_SCALE_3D, ///< Scale 3D track
+ TYPE_BLEND_SHAPE, ///< Blend Shape track
TYPE_METHOD, ///< Call any method on a specific node.
TYPE_BEZIER, ///< Bezier curve
TYPE_AUDIO,
@@ -60,7 +64,17 @@ public:
UPDATE_DISCRETE,
UPDATE_TRIGGER,
UPDATE_CAPTURE,
+ };
+
+ enum LoopMode {
+ LOOP_NONE,
+ LOOP_LINEAR,
+ LOOP_PINGPONG,
+ };
+ enum HandleMode {
+ HANDLE_MODE_FREE,
+ HANDLE_MODE_BALANCED,
};
private:
@@ -86,21 +100,41 @@ private:
T value;
};
- struct TransformKey {
- Vector3 loc;
- Quaternion rot;
- Vector3 scale;
+ const int32_t POSITION_TRACK_SIZE = 5;
+ const int32_t ROTATION_TRACK_SIZE = 6;
+ const int32_t SCALE_TRACK_SIZE = 5;
+ const int32_t BLEND_SHAPE_TRACK_SIZE = 3;
+
+ /* POSITION TRACK */
+
+ struct PositionTrack : public Track {
+ Vector<TKey<Vector3>> positions;
+ int32_t compressed_track = -1;
+ PositionTrack() { type = TYPE_POSITION_3D; }
};
- // Not necessarily the same size as Transform3D. The amount of numbers in Animation::Key and TransformKey.
- const int32_t TRANSFORM_TRACK_SIZE = 12;
+ /* ROTATION TRACK */
- /* TRANSFORM TRACK */
+ struct RotationTrack : public Track {
+ Vector<TKey<Quaternion>> rotations;
+ int32_t compressed_track = -1;
+ RotationTrack() { type = TYPE_ROTATION_3D; }
+ };
+
+ /* SCALE TRACK */
+
+ struct ScaleTrack : public Track {
+ Vector<TKey<Vector3>> scales;
+ int32_t compressed_track = -1;
+ ScaleTrack() { type = TYPE_SCALE_3D; }
+ };
- struct TransformTrack : public Track {
- Vector<TKey<TransformKey>> transforms;
+ /* BLEND SHAPE TRACK */
- TransformTrack() { type = TYPE_TRANSFORM3D; }
+ struct BlendShapeTrack : public Track {
+ Vector<TKey<float>> blend_shapes;
+ int32_t compressed_track = -1;
+ BlendShapeTrack() { type = TYPE_BLEND_SHAPE; }
};
/* PROPERTY VALUE TRACK */
@@ -128,10 +162,10 @@ private:
};
/* BEZIER TRACK */
-
struct BezierKey {
Vector2 in_handle; //relative (x always <0)
Vector2 out_handle; //relative (x always >0)
+ HandleMode handle_mode = HANDLE_MODE_BALANCED;
real_t value = 0.0;
};
@@ -184,23 +218,21 @@ private:
int _insert(double p_time, T &p_keys, const V &p_value);
template <class K>
- inline int _find(const Vector<K> &p_keys, double p_time) const;
- _FORCE_INLINE_ Animation::TransformKey _interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, real_t p_c) const;
+ inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false) const;
_FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const;
_FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const;
_FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const;
_FORCE_INLINE_ real_t _interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const;
- _FORCE_INLINE_ Animation::TransformKey _cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, real_t p_c) const;
_FORCE_INLINE_ Vector3 _cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c) const;
_FORCE_INLINE_ Quaternion _cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c) const;
_FORCE_INLINE_ Variant _cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c) const;
_FORCE_INLINE_ real_t _cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const;
template <class T>
- _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const;
+ _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const;
template <class T>
_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const;
@@ -210,22 +242,94 @@ private:
double length = 1.0;
real_t step = 0.1;
- bool loop = false;
+ LoopMode loop_mode = LOOP_NONE;
+ int pingponged = 0;
+
+ /* Animation compression page format (version 1):
+ *
+ * Animation uses bitwidth based compression separated into small pages. The intention is that pages fit easily in the cache, so decoding is cache efficient.
+ * The page-based nature also makes future animation streaming from disk possible.
+ *
+ * Actual format:
+ *
+ * num_compressed_tracks = bounds.size()
+ * header : (x num_compressed_tracks)
+ * -------
+ * timeline_keys_offset : uint32_t - offset to time keys
+ * timeline_size : uint32_t - amount of time keys
+ * data_keys_offset : uint32_t offset to key data
+ *
+ * time key (uint32_t):
+ * ------------------
+ * frame : bits 0-15 - time offset of key, computed as: page.time_offset + frame * (1.0/fps)
+ * data_key_offset : bits 16-27 - offset to key data, computed as: data_keys_offset * 4 + data_key_offset
+ * data_key_count : bits 28-31 - amount of data keys pointed to, computed as: data_key_count+1 (max 16)
+ *
+ * data key:
+ * ---------
+ * X / Blend Shape : uint16_t - X coordinate of XYZ vector key, or Blend Shape value. If Blend shape, Y and Z are not present and can be ignored.
+ * Y : uint16_t
+ * Z : uint16_t
+ * If data_key_count+1 > 1 (if more than 1 key is stored):
+ * data_bitwidth : uint16_t - This is only present if data_key_count > 1. Contains delta bitwidth information.
+ * X / Blend Shape delta bitwidth: bits 0-3 -
+ * if 0, nothing is present for X (use the first key-value for subsequent keys),
+ * else assume the number of bits present for each element (+ 1 for sign). Assumed always 16 bits, delta max signed 15 bits, with underflow and overflow supported.
+ * Y delta bitwidth : bits 4-7
+ * Z delta bitwidth : bits 8-11
+ * FRAME delta bitwidth : 12-15 bits - always present (obviously), actual bitwidth is FRAME+1
+ * Data key is 4 bytes long for Blend Shapes, 8 bytes long for pos/rot/scale.
+ *
+ * delta keys:
+ * -----------
+ * Compressed format is packed in the following format after the data key, containing delta keys one after the next in a tightly bit packed fashion.
+ * FRAME bits -> X / Blend Shape Bits (if bitwidth > 0) -> Y Bits (if not Blend Shape and Y Bitwidth > 0) -> Z Bits (if not Blend Shape and Z Bitwidth > 0)
+ *
+ * data key format:
+ * ----------------
+ * Decoding keys means starting from the base key and going key by key applying deltas until the proper position is reached needed for interpolation.
+ * Resulting values are uint32_t
+ * data for X / Blend Shape, Y and Z must be normalized first: unorm = float(data) / 65535.0
+ * **Blend Shape**: (unorm * 2.0 - 1.0) * Compression::BLEND_SHAPE_RANGE
+ * **Pos/Scale**: unorm_vec3 * bounds[track].size + bounds[track].position
+ * **Rotation**: Quaternion(Vector3::octahedron_decode(unorm_vec3.xy),unorm_vec3.z * Math_PI * 2.0)
+ * **Frame**: page.time_offset + frame * (1.0/fps)
+ */
+
+ struct Compression {
+ enum {
+ MAX_DATA_TRACK_SIZE = 16384,
+ BLEND_SHAPE_RANGE = 8, // - 8.0 to 8.0
+ FORMAT_VERSION = 1
+ };
+ struct Page {
+ Vector<uint8_t> data;
+ double time_offset;
+ };
+
+ uint32_t fps = 120;
+ LocalVector<Page> pages;
+ LocalVector<AABB> bounds; //used by position and scale tracks (which contain index to track and index to bounds).
+ bool enabled = false;
+ } compression;
+
+ Vector3i _compress_key(uint32_t p_track, const AABB &p_bounds, int32_t p_key = -1, float p_time = 0.0);
+ bool _rotation_interpolate_compressed(uint32_t p_compressed_track, double p_time, Quaternion &r_ret) const;
+ bool _pos_scale_interpolate_compressed(uint32_t p_compressed_track, double p_time, Vector3 &r_ret) const;
+ bool _blend_shape_interpolate_compressed(uint32_t p_compressed_track, double p_time, float &r_ret) const;
+ template <uint32_t COMPONENTS>
+ bool _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 = nullptr) const;
+ template <uint32_t COMPONENTS>
+ bool _fetch_compressed_by_index(uint32_t p_compressed_track, int p_index, Vector3i &r_value, double &r_time) const;
+ int _get_compressed_key_count(uint32_t p_compressed_track) const;
+ template <uint32_t COMPONENTS>
+ void _get_compressed_key_indices_in_range(uint32_t p_compressed_track, double p_time, double p_delta, List<int> *r_indices) const;
+ _FORCE_INLINE_ Quaternion _uncompress_quaternion(const Vector3i &p_value) const;
+ _FORCE_INLINE_ Vector3 _uncompress_pos_scale(uint32_t p_compressed_track, const Vector3i &p_value) const;
+ _FORCE_INLINE_ float _uncompress_blend_shape(const Vector3i &p_value) const;
// bind helpers
private:
- Array _transform_track_interpolate(int p_track, double p_time) const {
- Vector3 loc;
- Quaternion rot;
- Vector3 scale;
- transform_track_interpolate(p_track, p_time, &loc, &rot, &scale);
- Array ret;
- ret.push_back(loc);
- ret.push_back(rot);
- ret.push_back(scale);
- return ret;
- }
-
Vector<int> _value_track_get_key_indices(int p_track, double p_time, double p_delta) const {
List<int> idxs;
value_track_get_key_indices(p_track, p_time, p_delta, &idxs);
@@ -247,8 +351,15 @@ private:
return idxr;
}
- bool _transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, real_t p_alowed_linear_err, real_t p_alowed_angular_err, real_t p_max_optimizable_angle, const Vector3 &p_norm);
- void _transform_track_optimize(int p_idx, real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125);
+ bool _position_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_alowed_linear_err, real_t p_allowed_angular_error, const Vector3 &p_norm);
+ bool _rotation_track_optimize_key(const TKey<Quaternion> &t0, const TKey<Quaternion> &t1, const TKey<Quaternion> &t2, real_t p_allowed_angular_error, float p_max_optimizable_angle);
+ bool _scale_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_allowed_linear_error);
+ bool _blend_shape_track_optimize_key(const TKey<float> &t0, const TKey<float> &t1, const TKey<float> &t2, real_t p_allowed_unit_error);
+
+ void _position_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err);
+ void _rotation_track_optimize(int p_idx, real_t p_allowed_angular_err, real_t p_max_optimizable_angle);
+ void _scale_track_optimize(int p_idx, real_t p_allowed_linear_err);
+ void _blend_shape_track_optimize(int p_idx, real_t p_allowed_unit_error);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -268,8 +379,7 @@ public:
void track_set_path(int p_track, const NodePath &p_path);
NodePath track_get_path(int p_track) const;
- int find_track(const NodePath &p_path) const;
- // transform
+ int find_track(const NodePath &p_path, const TrackType p_type) const;
void track_move_up(int p_track);
void track_move_down(int p_track);
@@ -293,17 +403,34 @@ public:
Variant track_get_key_value(int p_track, int p_key_idx) const;
double track_get_key_time(int p_track, int p_key_idx) const;
real_t track_get_key_transition(int p_track, int p_key_idx) const;
+ bool track_is_compressed(int p_track) const;
+
+ int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position);
+ Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const;
+ Error position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const;
+
+ int rotation_track_insert_key(int p_track, double p_time, const Quaternion &p_rotation);
+ Error rotation_track_get_key(int p_track, int p_key, Quaternion *r_rotation) const;
+ Error rotation_track_interpolate(int p_track, double p_time, Quaternion *r_interpolation) const;
+
+ int scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale);
+ Error scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) const;
+ Error scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const;
+
+ int blend_shape_track_insert_key(int p_track, double p_time, float p_blend);
+ Error blend_shape_track_get_key(int p_track, int p_key, float *r_blend) const;
+ Error blend_shape_track_interpolate(int p_track, double p_time, float *r_blend) const;
- int transform_track_insert_key(int p_track, double p_time, const Vector3 &p_loc, const Quaternion &p_rot = Quaternion(), const Vector3 &p_scale = Vector3());
- Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
- int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
+ int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const HandleMode p_handle_mode = HandleMode::HANDLE_MODE_BALANCED);
+ void bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, double p_balanced_value_time_ratio = 1.0);
void bezier_track_set_key_value(int p_track, int p_index, real_t p_value);
- void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle);
- void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle);
+ void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio = 1.0);
+ void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle, double p_balanced_value_time_ratio = 1.0);
real_t bezier_track_get_key_value(int p_track, int p_index) const;
+ int bezier_track_get_key_handle_mode(int p_track, int p_index) const;
Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const;
Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const;
@@ -324,26 +451,24 @@ public:
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
bool track_get_interpolation_loop_wrap(int p_track) const;
- Error transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const;
-
Variant value_track_interpolate(int p_track, double p_time) const;
- void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
+ void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
void value_track_set_update_mode(int p_track, UpdateMode p_mode);
UpdateMode value_track_get_update_mode(int p_track) const;
- void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
+ void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
Vector<Variant> method_track_get_params(int p_track, int p_key_idx) const;
StringName method_track_get_name(int p_track, int p_key_idx) const;
void copy_track(int p_track, Ref<Animation> p_to_animation);
- void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
+ void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
void set_length(real_t p_length);
real_t get_length() const;
- void set_loop(bool p_enabled);
- bool has_loop() const;
+ void set_loop_mode(LoopMode p_loop_mode);
+ LoopMode get_loop_mode() const;
void set_step(real_t p_step);
real_t get_step() const;
@@ -351,6 +476,7 @@ public:
void clear();
void optimize(real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125);
+ void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests
Animation();
~Animation();
@@ -359,5 +485,7 @@ public:
VARIANT_ENUM_CAST(Animation::TrackType);
VARIANT_ENUM_CAST(Animation::InterpolationType);
VARIANT_ENUM_CAST(Animation::UpdateMode);
+VARIANT_ENUM_CAST(Animation::HandleMode);
+VARIANT_ENUM_CAST(Animation::LoopMode);
#endif
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index d018103e64..d3fab802c5 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -299,7 +299,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int
if (loop_format != AudioStreamSample::LOOP_DISABLED && offset < loop_begin_fp) {
/* loopstart reached */
- if (loop_format == AudioStreamSample::LOOP_PING_PONG) {
+ if (loop_format == AudioStreamSample::LOOP_PINGPONG) {
/* bounce ping pong */
offset = loop_begin_fp + (loop_begin_fp - offset);
increment = -increment;
@@ -320,7 +320,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int
if (loop_format != AudioStreamSample::LOOP_DISABLED && offset >= loop_end_fp) {
/* loopend reached */
- if (loop_format == AudioStreamSample::LOOP_PING_PONG) {
+ if (loop_format == AudioStreamSample::LOOP_PINGPONG) {
/* bounce ping pong */
offset = loop_end_fp - (offset - loop_end_fp);
increment = -increment;
@@ -636,7 +636,7 @@ void AudioStreamSample::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamSample::save_to_wav);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
@@ -650,7 +650,7 @@ void AudioStreamSample::_bind_methods() {
BIND_ENUM_CONSTANT(LOOP_DISABLED);
BIND_ENUM_CONSTANT(LOOP_FORWARD);
- BIND_ENUM_CONSTANT(LOOP_PING_PONG);
+ BIND_ENUM_CONSTANT(LOOP_PINGPONG);
BIND_ENUM_CONSTANT(LOOP_BACKWARD);
}
diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h
index 24198e3c98..0eb34be9bf 100644
--- a/scene/resources/audio_stream_sample.h
+++ b/scene/resources/audio_stream_sample.h
@@ -92,7 +92,7 @@ public:
enum LoopMode {
LOOP_DISABLED,
LOOP_FORWARD,
- LOOP_PING_PONG,
+ LOOP_PINGPONG,
LOOP_BACKWARD
};
diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp
index 49ed9dcb82..16f1507c34 100644
--- a/scene/resources/bit_map.cpp
+++ b/scene/resources/bit_map.cpp
@@ -674,7 +674,7 @@ void BitMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("grow_mask", "pixels", "rect"), &BitMap::grow_mask);
ClassDB::bind_method(D_METHOD("opaque_to_polygons", "rect", "epsilon"), &BitMap::_opaque_to_polygons_bind, DEFVAL(2.0));
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
BitMap::BitMap() {}
diff --git a/scene/resources/camera_effects.cpp b/scene/resources/camera_effects.cpp
index b633196424..0df372ea1b 100644
--- a/scene/resources/camera_effects.cpp
+++ b/scene/resources/camera_effects.cpp
@@ -149,7 +149,7 @@ void CameraEffects::_validate_property(PropertyInfo &property) const {
if ((!dof_blur_far_enabled && (property.name == "dof_blur_far_distance" || property.name == "dof_blur_far_transition")) ||
(!dof_blur_near_enabled && (property.name == "dof_blur_near_distance" || property.name == "dof_blur_near_transition")) ||
(!override_exposure_enabled && property.name == "override_exposure")) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/resources/concave_polygon_shape_3d.cpp b/scene/resources/concave_polygon_shape_3d.cpp
index 3fed700383..03854683bd 100644
--- a/scene/resources/concave_polygon_shape_3d.cpp
+++ b/scene/resources/concave_polygon_shape_3d.cpp
@@ -108,7 +108,7 @@ void ConcavePolygonShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_backface_collision_enabled", "enabled"), &ConcavePolygonShape3D::set_backface_collision_enabled);
ClassDB::bind_method(D_METHOD("is_backface_collision_enabled"), &ConcavePolygonShape3D::is_backface_collision_enabled);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backface_collision"), "set_backface_collision_enabled", "is_backface_collision_enabled");
}
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 9dc76dcf44..b530a72033 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -522,7 +522,7 @@ void Curve::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_min_value", "get_min_value");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_max_value", "get_max_value");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_SIGNAL(MethodInfo(SIGNAL_RANGE_CHANGED));
@@ -1006,7 +1006,7 @@ void Curve2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_data"), &Curve2D::_set_data);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
Curve2D::Curve2D() {
@@ -1699,7 +1699,7 @@ void Curve3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_data"), &Curve3D::_set_data);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_GROUP("Up Vector", "up_vector_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled");
diff --git a/scene/resources/default_theme/SCsub b/scene/resources/default_theme/SCsub
index 0fb6bb2c62..3667ab7c14 100644
--- a/scene/resources/default_theme/SCsub
+++ b/scene/resources/default_theme/SCsub
@@ -2,8 +2,6 @@
Import("env")
-import os
-import os.path
from platform_methods import run_in_subprocess
import default_theme_builders
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 54bb7a82cf..a1d76ef352 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));
@@ -785,30 +793,30 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("icon_separation", "TabContainer", 4 * scale);
theme->set_constant("outline_size", "TabContainer", 0);
- // Tabs
+ // TabBar
- theme->set_stylebox("tab_selected", "Tabs", sb_expand(make_stylebox(tab_current_png, 4, 3, 4, 1, 16, 3, 16, 2), 2, 2, 2, 2));
- theme->set_stylebox("tab_unselected", "Tabs", sb_expand(make_stylebox(tab_behind_png, 5, 4, 5, 1, 16, 5, 16, 2), 3, 3, 3, 3));
- theme->set_stylebox("tab_disabled", "Tabs", sb_expand(make_stylebox(tab_disabled_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3));
- theme->set_stylebox("close_bg_pressed", "Tabs", make_stylebox(button_pressed_png, 4, 4, 4, 4));
- theme->set_stylebox("close_bg_highlight", "Tabs", make_stylebox(button_normal_png, 4, 4, 4, 4));
+ theme->set_stylebox("tab_selected", "TabBar", sb_expand(make_stylebox(tab_current_png, 4, 3, 4, 1, 16, 3, 16, 2), 2, 2, 2, 2));
+ theme->set_stylebox("tab_unselected", "TabBar", sb_expand(make_stylebox(tab_behind_png, 5, 4, 5, 1, 16, 5, 16, 2), 3, 3, 3, 3));
+ theme->set_stylebox("tab_disabled", "TabBar", sb_expand(make_stylebox(tab_disabled_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3));
+ theme->set_stylebox("close_bg_pressed", "TabBar", make_stylebox(button_pressed_png, 4, 4, 4, 4));
+ theme->set_stylebox("close_bg_highlight", "TabBar", make_stylebox(button_normal_png, 4, 4, 4, 4));
- theme->set_icon("increment", "Tabs", make_icon(scroll_button_right_png));
- theme->set_icon("increment_highlight", "Tabs", make_icon(scroll_button_right_hl_png));
- theme->set_icon("decrement", "Tabs", make_icon(scroll_button_left_png));
- theme->set_icon("decrement_highlight", "Tabs", make_icon(scroll_button_left_hl_png));
- theme->set_icon("close", "Tabs", make_icon(tab_close_png));
+ theme->set_icon("increment", "TabBar", make_icon(scroll_button_right_png));
+ theme->set_icon("increment_highlight", "TabBar", make_icon(scroll_button_right_hl_png));
+ theme->set_icon("decrement", "TabBar", make_icon(scroll_button_left_png));
+ theme->set_icon("decrement_highlight", "TabBar", make_icon(scroll_button_left_hl_png));
+ theme->set_icon("close", "TabBar", make_icon(tab_close_png));
- theme->set_font("font", "Tabs", Ref<Font>());
- theme->set_font_size("font_size", "Tabs", -1);
+ theme->set_font("font", "TabBar", Ref<Font>());
+ theme->set_font_size("font_size", "TabBar", -1);
- theme->set_color("font_selected_color", "Tabs", control_font_hover_color);
- theme->set_color("font_unselected_color", "Tabs", control_font_low_color);
- theme->set_color("font_disabled_color", "Tabs", control_font_disabled_color);
- theme->set_color("font_outline_color", "Tabs", Color(1, 1, 1));
+ theme->set_color("font_selected_color", "TabBar", control_font_hover_color);
+ theme->set_color("font_unselected_color", "TabBar", control_font_low_color);
+ theme->set_color("font_disabled_color", "TabBar", control_font_disabled_color);
+ theme->set_color("font_outline_color", "TabBar", Color(1, 1, 1));
- theme->set_constant("hseparation", "Tabs", 4 * scale);
- theme->set_constant("outline_size", "Tabs", 0);
+ theme->set_constant("hseparation", "TabBar", 4 * scale);
+ theme->set_constant("outline_size", "TabBar", 0);
// Separators
@@ -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));
@@ -931,7 +940,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("shadow_offset_x", "RichTextLabel", 1 * scale);
theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale);
- theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale);
+ theme->set_constant("shadow_outline_size", "RichTextLabel", 1 * scale);
theme->set_constant("line_separation", "RichTextLabel", 0 * scale);
theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale);
@@ -1030,7 +1039,6 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) {
dynamic_font_data.instantiate();
dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size);
dynamic_font->add_data(dynamic_font_data);
- dynamic_font->set_base_size(default_font_size);
default_font = dynamic_font;
}
diff --git a/scene/resources/default_theme/overbright_indicator.png b/scene/resources/default_theme/overbright_indicator.png
index 89f800c230..e13f15dd02 100644
--- a/scene/resources/default_theme/overbright_indicator.png
+++ b/scene/resources/default_theme/overbright_indicator.png
Binary files differ
diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h
index 6a556c1112..57ff9a5325 100644
--- a/scene/resources/default_theme/theme_data.h
+++ b/scene/resources/default_theme/theme_data.h
@@ -50,6 +50,10 @@ static const unsigned char checked_disabled_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x99, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x73, 0x72, 0x7b, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x82, 0x80, 0x8a, 0x90, 0x90, 0x93, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x93, 0x93, 0x95, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x7d, 0x7d, 0x7f, 0x58, 0x58, 0x5b, 0xa4, 0xa4, 0xa4, 0x9e, 0x9e, 0xa0, 0x9e, 0x9e, 0x9e, 0x9b, 0x9b, 0x9c, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x93, 0x93, 0x94, 0x8f, 0x8f, 0x8f, 0x86, 0x86, 0x88, 0x85, 0x85, 0x86, 0x82, 0x82, 0x83, 0x81, 0x81, 0x83, 0x7f, 0x7f, 0x81, 0x7c, 0x7c, 0x7e, 0x7a, 0x7a, 0x7d, 0x78, 0x78, 0x7b, 0x71, 0x71, 0x74, 0x68, 0x68, 0x6c, 0x66, 0x66, 0x6a, 0x65, 0x65, 0x68, 0x63, 0x63, 0x66, 0x5f, 0x5f, 0x63, 0x5c, 0x5c, 0x60, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x59, 0x59, 0x5c, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0x10, 0x13, 0xbb, 0xf, 0x0, 0x0, 0x0, 0x10, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x27, 0x50, 0x66, 0x68, 0x6a, 0x81, 0xb4, 0xb4, 0xdd, 0xfa, 0xfa, 0xfb, 0xfb, 0x5b, 0xd1, 0xf1, 0xe6, 0x0, 0x0, 0x0, 0x96, 0x49, 0x44, 0x41, 0x54, 0x78, 0x5e, 0x5d, 0x8f, 0xc9, 0x12, 0x82, 0x30, 0x14, 0x4, 0x87, 0x18, 0x50, 0x51, 0x44, 0x25, 0x42, 0x4, 0x77, 0xc4, 0x8d, 0x97, 0x0, 0xf9, 0xff, 0x8f, 0xb3, 0x88, 0xa4, 0x4a, 0xed, 0x63, 0x5f, 0xa6, 0x7, 0xf8, 0x7, 0x1e, 0xe3, 0x7e, 0x60, 0x19, 0x4f, 0x46, 0x1e, 0x0, 0x36, 0x8d, 0x4c, 0x67, 0x59, 0x65, 0x33, 0x6, 0x80, 0x47, 0xad, 0x56, 0x3d, 0xb7, 0x3c, 0x5d, 0x70, 0x0, 0xbe, 0xd1, 0x44, 0x65, 0x4d, 0x94, 0xc8, 0xc2, 0xf8, 0x0, 0x82, 0x4e, 0x91, 0x94, 0x15, 0x5d, 0xd2, 0xec, 0xde, 0x5, 0x83, 0x38, 0xc8, 0xe3, 0x63, 0x23, 0xce, 0xca, 0x9, 0x7a, 0x6e, 0xf3, 0x93, 0x48, 0x1a, 0x27, 0x14, 0x35, 0x3b, 0xb9, 0x5e, 0x56, 0xe4, 0x84, 0x22, 0xba, 0xa, 0x51, 0xd0, 0xb7, 0xa8, 0xcb, 0xfd, 0xcb, 0x9, 0x3b, 0xfb, 0x41, 0xdb, 0x59, 0x17, 0xa6, 0x94, 0x6e, 0xe3, 0x3e, 0x8c, 0x85, 0xf1, 0x90, 0x6e, 0xe6, 0x21, 0xfb, 0x39, 0xe7, 0x73, 0xe6, 0xfd, 0x5f, 0x7, 0xde, 0xc3, 0xb5, 0x16, 0x87, 0xb0, 0x9e, 0x42, 0x46, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
+static const unsigned char checker_bg_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x0, 0x0, 0x0, 0x0, 0xe1, 0x64, 0xe1, 0x57, 0x0, 0x0, 0x0, 0x14, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xfc, 0xcf, 0xc0, 0xc0, 0xd0, 0x0, 0xc4, 0xf8, 0x18, 0xf5, 0x84, 0x19, 0x0, 0x9f, 0x5f, 0xa, 0x1, 0xf8, 0xef, 0x65, 0xf4, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
static const unsigned char close_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x62, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x16, 0xe0, 0x8c, 0xe0, 0x11, 0x43, 0xe6, 0xf3, 0x88, 0x71, 0x46, 0xa0, 0x48, 0x73, 0xfc, 0xe3, 0xb8, 0xcc, 0x23, 0x86, 0x90, 0xe6, 0xb8, 0xcc, 0xf1, 0xf, 0x49, 0x9, 0x8f, 0x28, 0xe7, 0x25, 0x8e, 0xff, 0x1c, 0xd7, 0xb9, 0x24, 0x91, 0x79, 0xdc, 0x12, 0x40, 0xe, 0xa6, 0x12, 0x54, 0x69, 0x4c, 0x25, 0xb7, 0x38, 0xae, 0x21, 0xa4, 0x31, 0x94, 0x80, 0x24, 0x81, 0xf0, 0x36, 0x48, 0x1a, 0xaf, 0x2, 0x88, 0x5b, 0xf0, 0x5a, 0x81, 0xa1, 0x4, 0xe1, 0x34, 0x84, 0x73, 0xb1, 0x4a, 0xa3, 0x7b, 0x9a, 0x70, 0x40, 0x11, 0xe, 0x6a, 0xca, 0x1, 0x0, 0x2a, 0x28, 0x37, 0x83, 0x3e, 0x27, 0xb0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
@@ -218,10 +222,6 @@ static const unsigned char indeterminate_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x36, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x38, 0x37, 0x40, 0x20, 0x20, 0x24, 0x20, 0x20, 0x24, 0x38, 0x36, 0x40, 0x20, 0x20, 0x25, 0x1e, 0x1e, 0x22, 0x1f, 0x1f, 0x23, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0xfe, 0xfe, 0xfe, 0x98, 0x4d, 0x2d, 0x9a, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xfa, 0xfb, 0xb4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x77, 0x5e, 0x7f, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xc1, 0x12, 0x80, 0x20, 0x8, 0x45, 0xd1, 0x4, 0x14, 0xd, 0xc5, 0xfa, 0xff, 0x9f, 0x8d, 0x9c, 0x6c, 0x9a, 0xb4, 0xe9, 0x2e, 0xcf, 0x42, 0x9e, 0xcb, 0x32, 0xe4, 0x0, 0xc9, 0xb7, 0x8, 0xc1, 0x19, 0x40, 0x60, 0xc9, 0x2d, 0xe1, 0x0, 0x6, 0xc8, 0x45, 0x6b, 0x4b, 0xb, 0xa3, 0x1, 0x89, 0x6e, 0x57, 0x2a, 0x64, 0xe0, 0x73, 0xed, 0x50, 0xb3, 0x9f, 0xc3, 0x7e, 0xf7, 0x5, 0x7f, 0x6f, 0xc, 0x67, 0x9f, 0xc3, 0xe2, 0x39, 0xc, 0x52, 0xec, 0xd3, 0xd7, 0x4, 0xb3, 0xcf, 0xbd, 0x3a, 0x0, 0xa0, 0xa2, 0x8, 0xbc, 0xf6, 0x84, 0x3a, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
-static const unsigned char indeterminate_disabled_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x58, 0x58, 0x5b, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0x9e, 0x9e, 0x9e, 0x8c, 0x93, 0x80, 0x95, 0x0, 0x0, 0x0, 0x14, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xb4, 0xfa, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xb9, 0x8d, 0x1e, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xd1, 0xe, 0x80, 0x20, 0x8, 0x85, 0xe1, 0x4, 0xd4, 0x30, 0x51, 0xb6, 0xde, 0xff, 0x5d, 0x23, 0x97, 0xad, 0xa5, 0xad, 0xff, 0xf2, 0xbb, 0x90, 0xe3, 0xb2, 0xc, 0x39, 0x40, 0xf2, 0x2d, 0x42, 0x70, 0x6, 0x10, 0x58, 0x6b, 0x4b, 0x39, 0x80, 0x1, 0x72, 0x91, 0xdc, 0x92, 0xc2, 0x68, 0x40, 0x2a, 0xdb, 0x95, 0x28, 0x19, 0xf8, 0x9a, 0x3b, 0xe4, 0xea, 0xe7, 0xb0, 0xdf, 0x7d, 0xc1, 0xdf, 0x1b, 0xc3, 0xd9, 0xe7, 0xb0, 0x74, 0xe, 0x83, 0x98, 0xfa, 0xf4, 0x35, 0xc2, 0xec, 0x73, 0xaf, 0xe, 0x57, 0x20, 0x8, 0x2c, 0x1a, 0x56, 0xe5, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
-};
-
static const unsigned char line_edit_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x4, 0x3, 0x0, 0x0, 0x0, 0x7f, 0x1c, 0xd2, 0x8e, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x17, 0x16, 0x1a, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x21, 0x1f, 0x25, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x1d, 0x1c, 0x21, 0x1d, 0x1c, 0x21, 0x24, 0x22, 0x29, 0x28, 0x26, 0x2d, 0x28, 0x26, 0x2e, 0x2b, 0x2a, 0x31, 0x2c, 0x2a, 0x32, 0xff, 0xff, 0xff, 0xb9, 0x11, 0x56, 0x3e, 0x0, 0x0, 0x0, 0x8, 0x74, 0x52, 0x4e, 0x53, 0x6f, 0xef, 0xf7, 0xf7, 0xf0, 0xf9, 0xf1, 0xee, 0xcf, 0x21, 0xd2, 0xdf, 0x0, 0x0, 0x0, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0x54, 0x36, 0x36, 0x12, 0x60, 0xf0, 0x98, 0xb5, 0x6a, 0x65, 0xb, 0x43, 0xe4, 0x9e, 0x33, 0xa7, 0xa7, 0x32, 0x58, 0x9d, 0x39, 0x73, 0x66, 0x31, 0x16, 0x12, 0x22, 0xb, 0x52, 0xd9, 0xc6, 0xc0, 0x2, 0xd4, 0x55, 0x0, 0x0, 0xc, 0x14, 0x1a, 0x90, 0x55, 0x1a, 0xec, 0xdb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
@@ -275,7 +275,7 @@ static const unsigned char option_button_pressed_mirrored_png[] = {
};
static const unsigned char overbright_indicator_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x1, 0x85, 0x69, 0x43, 0x43, 0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x0, 0x0, 0x78, 0x9c, 0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x1c, 0xc5, 0x5f, 0x53, 0xa5, 0x2a, 0x2d, 0xe, 0x16, 0x11, 0x75, 0xc8, 0x50, 0x9d, 0x2c, 0x8a, 0x8a, 0x38, 0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0xa, 0xad, 0x3a, 0x98, 0x5c, 0xfa, 0x21, 0x34, 0x69, 0x48, 0x52, 0x5c, 0x1c, 0x5, 0xd7, 0x82, 0x83, 0x1f, 0x8b, 0x55, 0x7, 0x17, 0x67, 0x5d, 0x1d, 0x5c, 0x5, 0x41, 0xf0, 0x3, 0xc4, 0xc5, 0xd5, 0x49, 0xd1, 0x45, 0x4a, 0xfc, 0x5f, 0x5a, 0x68, 0x11, 0xe3, 0xc1, 0x71, 0x3f, 0xde, 0xdd, 0x7b, 0xdc, 0xbd, 0x3, 0x84, 0x6a, 0x91, 0x69, 0x56, 0xdb, 0x18, 0xa0, 0xe9, 0xb6, 0x99, 0x8c, 0xc7, 0xc4, 0x74, 0x66, 0x45, 0xc, 0xbc, 0xa2, 0x13, 0x3, 0x8, 0xa1, 0x17, 0xa3, 0x32, 0xb3, 0x8c, 0x59, 0x49, 0x4a, 0xc0, 0x73, 0x7c, 0xdd, 0xc3, 0xc7, 0xd7, 0xbb, 0x28, 0xcf, 0xf2, 0x3e, 0xf7, 0xe7, 0x8, 0xa9, 0x59, 0x8b, 0x1, 0x3e, 0x91, 0x78, 0x86, 0x19, 0xa6, 0x4d, 0xbc, 0x4e, 0x3c, 0xb5, 0x69, 0x1b, 0x9c, 0xf7, 0x89, 0xc3, 0xac, 0x20, 0xab, 0xc4, 0xe7, 0xc4, 0x23, 0x26, 0x5d, 0x90, 0xf8, 0x91, 0xeb, 0x4a, 0x9d, 0xdf, 0x38, 0xe7, 0x5d, 0x16, 0x78, 0x66, 0xd8, 0x4c, 0x25, 0xe7, 0x88, 0xc3, 0xc4, 0x62, 0xbe, 0x85, 0x95, 0x16, 0x66, 0x5, 0x53, 0x23, 0x9e, 0x24, 0x8e, 0xa8, 0x9a, 0x4e, 0xf9, 0x42, 0xba, 0xce, 0x2a, 0xe7, 0x2d, 0xce, 0x5a, 0xb1, 0xcc, 0x1a, 0xf7, 0xe4, 0x2f, 0xc, 0x66, 0xf5, 0xe5, 0x25, 0xae, 0xd3, 0x1c, 0x44, 0x1c, 0xb, 0x58, 0x84, 0x4, 0x11, 0xa, 0xca, 0xd8, 0x40, 0x11, 0x36, 0xa2, 0xb4, 0xea, 0xa4, 0x58, 0x48, 0xd2, 0x7e, 0xcc, 0xc3, 0xdf, 0xef, 0xfa, 0x25, 0x72, 0x29, 0xe4, 0xda, 0x0, 0x23, 0xc7, 0x3c, 0x4a, 0xd0, 0x20, 0xbb, 0x7e, 0xf0, 0x3f, 0xf8, 0xdd, 0xad, 0x95, 0x9b, 0x18, 0xaf, 0x27, 0x5, 0x63, 0x40, 0xfb, 0x8b, 0xe3, 0x7c, 0xc, 0x1, 0x81, 0x5d, 0xa0, 0x56, 0x71, 0x9c, 0xef, 0x63, 0xc7, 0xa9, 0x9d, 0x0, 0xfe, 0x67, 0xe0, 0x4a, 0x6f, 0xfa, 0x4b, 0x55, 0x60, 0xfa, 0x93, 0xf4, 0x4a, 0x53, 0x8b, 0x1c, 0x1, 0xdd, 0xdb, 0xc0, 0xc5, 0x75, 0x53, 0x53, 0xf6, 0x80, 0xcb, 0x1d, 0xa0, 0xef, 0xc9, 0x90, 0x4d, 0xd9, 0x95, 0xfc, 0x34, 0x85, 0x5c, 0xe, 0x78, 0x3f, 0xa3, 0x6f, 0xca, 0x0, 0x3d, 0xb7, 0x40, 0xd7, 0x6a, 0xbd, 0xb7, 0xc6, 0x3e, 0x4e, 0x1f, 0x80, 0x14, 0x75, 0x95, 0xb8, 0x1, 0xe, 0xe, 0x81, 0xe1, 0x3c, 0x65, 0xaf, 0x79, 0xbc, 0xbb, 0xa3, 0xb5, 0xb7, 0x7f, 0xcf, 0x34, 0xfa, 0xfb, 0x1, 0x8e, 0x80, 0x72, 0xb2, 0xed, 0x78, 0xfa, 0x7b, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc4, 0x0, 0x0, 0xe, 0xc4, 0x1, 0x95, 0x2b, 0xe, 0x1b, 0x0, 0x0, 0x0, 0x15, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 0x63, 0x63, 0x66, 0x0, 0x0, 0x3, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x4c, 0x39, 0x3a, 0xe, 0x0, 0x0, 0x0, 0x6, 0x74, 0x52, 0x4e, 0x53, 0xff, 0xff, 0xff, 0x7f, 0x0, 0x80, 0x2c, 0x16, 0xc1, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x62, 0x4b, 0x47, 0x44, 0x6, 0x61, 0x66, 0xb8, 0x7d, 0x0, 0x0, 0x0, 0x32, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0x0, 0x1, 0x46, 0x65, 0x17, 0x17, 0x30, 0x43, 0xc8, 0x4, 0x50, 0x88, 0x1c, 0x52, 0x1, 0x0, 0x2, 0x40, 0x14, 0xbb, 0x70, 0x8b, 0x40, 0xff, 0x2c, 0x18, 0xbe, 0xc6, 0xed, 0x8d, 0x42, 0xa1, 0x50, 0x28, 0x14, 0xa, 0x85, 0xbd, 0xb0, 0x13, 0xfc, 0x71, 0x1, 0xca, 0xf, 0x19, 0x62, 0x24, 0xd6, 0x8, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0x5f, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0xcd, 0xcd, 0x4b, 0xe, 0x80, 0x20, 0xc, 0x45, 0xd1, 0x9b, 0xea, 0xbe, 0x40, 0x97, 0x87, 0x8b, 0xae, 0x13, 0x34, 0x88, 0x1f, 0xa, 0x9d, 0xf8, 0x92, 0xe, 0xcf, 0x2d, 0x80, 0xda, 0x4f, 0x14, 0x26, 0x5, 0x49, 0x14, 0x53, 0xcb, 0x42, 0x58, 0xe, 0xbc, 0x51, 0xcd, 0x85, 0x9b, 0x81, 0x16, 0xfe, 0xc, 0x58, 0xf0, 0x6b, 0xc0, 0x8a, 0x1f, 0x3, 0x3d, 0xf8, 0x16, 0xe8, 0xc5, 0x97, 0x40, 0x81, 0x53, 0x9b, 0x55, 0x81, 0x91, 0xcf, 0x67, 0x20, 0xc6, 0x75, 0xe8, 0x73, 0x9e, 0xc, 0x7f, 0xce, 0x73, 0x61, 0x80, 0xd9, 0x83, 0x7f, 0xb0, 0x1d, 0xec, 0x30, 0xf0, 0x78, 0x5a, 0x7, 0xa8, 0x21, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const unsigned char panel_bg_png[] = {
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index 9a3f081a8b..4c25ed589b 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -782,7 +782,7 @@ void Environment::_update_fog() {
// Volumetric Fog
void Environment::_update_volumetric_fog() {
- RS::get_singleton()->environment_set_volumetric_fog(environment, volumetric_fog_enabled, volumetric_fog_density, volumetric_fog_light, volumetric_fog_light_energy, volumetric_fog_length, volumetric_fog_detail_spread, volumetric_fog_gi_inject, volumetric_fog_temporal_reproject, volumetric_fog_temporal_reproject_amount);
+ RS::get_singleton()->environment_set_volumetric_fog(environment, volumetric_fog_enabled, volumetric_fog_density, volumetric_fog_albedo, volumetric_fog_emission, volumetric_fog_emission_energy, volumetric_fog_anisotropy, volumetric_fog_length, volumetric_fog_detail_spread, volumetric_fog_gi_inject, volumetric_fog_temporal_reproject, volumetric_fog_temporal_reproject_amount, volumetric_fog_ambient_inject);
}
void Environment::set_volumetric_fog_enabled(bool p_enable) {
@@ -795,26 +795,39 @@ bool Environment::is_volumetric_fog_enabled() const {
return volumetric_fog_enabled;
}
void Environment::set_volumetric_fog_density(float p_density) {
- p_density = CLAMP(p_density, 0.0000001, 1.0);
volumetric_fog_density = p_density;
_update_volumetric_fog();
}
float Environment::get_volumetric_fog_density() const {
return volumetric_fog_density;
}
-void Environment::set_volumetric_fog_light(Color p_color) {
- volumetric_fog_light = p_color;
+void Environment::set_volumetric_fog_albedo(Color p_color) {
+ volumetric_fog_albedo = p_color;
_update_volumetric_fog();
}
-Color Environment::get_volumetric_fog_light() const {
- return volumetric_fog_light;
+Color Environment::get_volumetric_fog_albedo() const {
+ return volumetric_fog_albedo;
}
-void Environment::set_volumetric_fog_light_energy(float p_begin) {
- volumetric_fog_light_energy = p_begin;
+void Environment::set_volumetric_fog_emission(Color p_color) {
+ volumetric_fog_emission = p_color;
_update_volumetric_fog();
}
-float Environment::get_volumetric_fog_light_energy() const {
- return volumetric_fog_light_energy;
+Color Environment::get_volumetric_fog_emission() const {
+ return volumetric_fog_emission;
+}
+void Environment::set_volumetric_fog_emission_energy(float p_begin) {
+ volumetric_fog_emission_energy = p_begin;
+ _update_volumetric_fog();
+}
+float Environment::get_volumetric_fog_emission_energy() const {
+ return volumetric_fog_emission_energy;
+}
+void Environment::set_volumetric_fog_anisotropy(float p_anisotropy) {
+ volumetric_fog_anisotropy = p_anisotropy;
+ _update_volumetric_fog();
+}
+float Environment::get_volumetric_fog_anisotropy() const {
+ return volumetric_fog_anisotropy;
}
void Environment::set_volumetric_fog_length(float p_length) {
volumetric_fog_length = p_length;
@@ -824,6 +837,7 @@ float Environment::get_volumetric_fog_length() const {
return volumetric_fog_length;
}
void Environment::set_volumetric_fog_detail_spread(float p_detail_spread) {
+ p_detail_spread = CLAMP(p_detail_spread, 0.5, 6.0);
volumetric_fog_detail_spread = p_detail_spread;
_update_volumetric_fog();
}
@@ -838,6 +852,13 @@ void Environment::set_volumetric_fog_gi_inject(float p_gi_inject) {
float Environment::get_volumetric_fog_gi_inject() const {
return volumetric_fog_gi_inject;
}
+void Environment::set_volumetric_fog_ambient_inject(float p_ambient_inject) {
+ volumetric_fog_ambient_inject = p_ambient_inject;
+ _update_volumetric_fog();
+}
+float Environment::get_volumetric_fog_ambient_inject() const {
+ return volumetric_fog_ambient_inject;
+}
void Environment::set_volumetric_fog_temporal_reprojection_enabled(bool p_enable) {
volumetric_fog_temporal_reproject = p_enable;
@@ -896,7 +917,7 @@ float Environment::get_adjustment_saturation() const {
void Environment::set_adjustment_color_correction(Ref<Texture> p_color_correction) {
adjustment_color_correction = p_color_correction;
- Ref<GradientTexture> grad_tex = p_color_correction;
+ Ref<GradientTexture1D> grad_tex = p_color_correction;
if (grad_tex.is_valid()) {
if (!grad_tex->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment))) {
grad_tex->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment));
@@ -933,39 +954,39 @@ void Environment::_update_adjustment() {
void Environment::_validate_property(PropertyInfo &property) const {
if (property.name == "sky" || property.name == "sky_custom_fov" || property.name == "sky_rotation" || property.name == "ambient_light/sky_contribution") {
if (bg_mode != BG_SKY && ambient_source != AMBIENT_SOURCE_SKY && reflection_source != REFLECTION_SOURCE_SKY) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
if (property.name == "fog_aerial_perspective") {
if (bg_mode != BG_SKY) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
if (property.name == "glow_intensity" && glow_blend_mode == GLOW_BLEND_MODE_MIX) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
if (property.name == "glow_mix" && glow_blend_mode != GLOW_BLEND_MODE_MIX) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
if (property.name == "background_color") {
if (bg_mode != BG_COLOR && ambient_source != AMBIENT_SOURCE_COLOR) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
if (property.name == "background_canvas_max_layer") {
if (bg_mode != BG_CANVAS) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
}
}
if (property.name == "background_camera_feed_id") {
if (bg_mode != BG_CAMERA_FEED) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
@@ -997,7 +1018,7 @@ void Environment::_validate_property(PropertyInfo &property) const {
String enabled = prefix + "enabled";
if (property.name.begins_with(prefix) && property.name != enabled && !bool(get(enabled))) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
return;
}
@@ -1010,7 +1031,7 @@ void Environment::_validate_property(PropertyInfo &property) const {
String prefix = String(*prefixes);
if (property.name.begins_with(prefix)) {
- property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
+ property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL;
return;
}
@@ -1291,22 +1312,28 @@ void Environment::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_density", PROPERTY_HINT_RANGE, "0,16,0.0001"), "set_fog_density", "get_fog_density");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_aerial_perspective", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_fog_aerial_perspective", "get_fog_aerial_perspective");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_lesser,or_greater"), "set_fog_height", "get_fog_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_density", PROPERTY_HINT_RANGE, "0,128,0.001,or_greater"), "set_fog_height_density", "get_fog_height_density");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fog_height_density", PROPERTY_HINT_RANGE, "-16,16,0.0001,or_lesser,or_greater"), "set_fog_height_density", "get_fog_height_density");
ClassDB::bind_method(D_METHOD("set_volumetric_fog_enabled", "enabled"), &Environment::set_volumetric_fog_enabled);
ClassDB::bind_method(D_METHOD("is_volumetric_fog_enabled"), &Environment::is_volumetric_fog_enabled);
- ClassDB::bind_method(D_METHOD("set_volumetric_fog_light", "color"), &Environment::set_volumetric_fog_light);
- ClassDB::bind_method(D_METHOD("get_volumetric_fog_light"), &Environment::get_volumetric_fog_light);
+ ClassDB::bind_method(D_METHOD("set_volumetric_fog_emission", "color"), &Environment::set_volumetric_fog_emission);
+ ClassDB::bind_method(D_METHOD("get_volumetric_fog_emission"), &Environment::get_volumetric_fog_emission);
+ ClassDB::bind_method(D_METHOD("set_volumetric_fog_albedo", "color"), &Environment::set_volumetric_fog_albedo);
+ ClassDB::bind_method(D_METHOD("get_volumetric_fog_albedo"), &Environment::get_volumetric_fog_albedo);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_density", "density"), &Environment::set_volumetric_fog_density);
ClassDB::bind_method(D_METHOD("get_volumetric_fog_density"), &Environment::get_volumetric_fog_density);
- ClassDB::bind_method(D_METHOD("set_volumetric_fog_light_energy", "begin"), &Environment::set_volumetric_fog_light_energy);
- ClassDB::bind_method(D_METHOD("get_volumetric_fog_light_energy"), &Environment::get_volumetric_fog_light_energy);
+ ClassDB::bind_method(D_METHOD("set_volumetric_fog_emission_energy", "begin"), &Environment::set_volumetric_fog_emission_energy);
+ ClassDB::bind_method(D_METHOD("get_volumetric_fog_emission_energy"), &Environment::get_volumetric_fog_emission_energy);
+ ClassDB::bind_method(D_METHOD("set_volumetric_fog_anisotropy", "anisotropy"), &Environment::set_volumetric_fog_anisotropy);
+ ClassDB::bind_method(D_METHOD("get_volumetric_fog_anisotropy"), &Environment::get_volumetric_fog_anisotropy);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_length", "length"), &Environment::set_volumetric_fog_length);
ClassDB::bind_method(D_METHOD("get_volumetric_fog_length"), &Environment::get_volumetric_fog_length);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_detail_spread", "detail_spread"), &Environment::set_volumetric_fog_detail_spread);
ClassDB::bind_method(D_METHOD("get_volumetric_fog_detail_spread"), &Environment::get_volumetric_fog_detail_spread);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_gi_inject", "gi_inject"), &Environment::set_volumetric_fog_gi_inject);
ClassDB::bind_method(D_METHOD("get_volumetric_fog_gi_inject"), &Environment::get_volumetric_fog_gi_inject);
+ ClassDB::bind_method(D_METHOD("set_volumetric_fog_ambient_inject", "enabled"), &Environment::set_volumetric_fog_ambient_inject);
+ ClassDB::bind_method(D_METHOD("get_volumetric_fog_ambient_inject"), &Environment::get_volumetric_fog_ambient_inject);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_temporal_reprojection_enabled", "enabled"), &Environment::set_volumetric_fog_temporal_reprojection_enabled);
ClassDB::bind_method(D_METHOD("is_volumetric_fog_temporal_reprojection_enabled"), &Environment::is_volumetric_fog_temporal_reprojection_enabled);
ClassDB::bind_method(D_METHOD("set_volumetric_fog_temporal_reprojection_amount", "temporal_reprojection_amount"), &Environment::set_volumetric_fog_temporal_reprojection_amount);
@@ -1315,11 +1342,14 @@ void Environment::_bind_methods() {
ADD_GROUP("Volumetric Fog", "volumetric_fog_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "volumetric_fog_enabled"), "set_volumetric_fog_enabled", "is_volumetric_fog_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_density", PROPERTY_HINT_RANGE, "0,1,0.0001,or_greater"), "set_volumetric_fog_density", "get_volumetric_fog_density");
- ADD_PROPERTY(PropertyInfo(Variant::COLOR, "volumetric_fog_light", PROPERTY_HINT_COLOR_NO_ALPHA), "set_volumetric_fog_light", "get_volumetric_fog_light");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_light_energy", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_light_energy", "get_volumetric_fog_light_energy");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_RANGE, "0.00,16,0.01,exp"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "volumetric_fog_albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_volumetric_fog_albedo", "get_volumetric_fog_albedo");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "volumetric_fog_emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_volumetric_fog_emission", "get_volumetric_fog_emission");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_emission_energy", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_emission_energy", "get_volumetric_fog_emission_energy");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_anisotropy", PROPERTY_HINT_RANGE, "-0.9,0.9,0.01"), "set_volumetric_fog_anisotropy", "get_volumetric_fog_anisotropy");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_length", "get_volumetric_fog_length");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_detail_spread", PROPERTY_HINT_EXP_EASING, "0.01,16,0.01"), "set_volumetric_fog_detail_spread", "get_volumetric_fog_detail_spread");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_detail_spread", PROPERTY_HINT_EXP_EASING), "set_volumetric_fog_detail_spread", "get_volumetric_fog_detail_spread");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_ambient_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_ambient_inject", "get_volumetric_fog_ambient_inject");
ADD_SUBGROUP("Temporal Reprojection", "volumetric_fog_temporal_reprojection_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "volumetric_fog_temporal_reprojection_enabled"), "set_volumetric_fog_temporal_reprojection_enabled", "is_volumetric_fog_temporal_reprojection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_temporal_reprojection_amount", PROPERTY_HINT_RANGE, "0.0,0.999,0.001"), "set_volumetric_fog_temporal_reprojection_amount", "get_volumetric_fog_temporal_reprojection_amount");
@@ -1381,11 +1411,6 @@ void Environment::_bind_methods() {
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_DISABLED);
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_75_PERCENT);
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_50_PERCENT);
-
- BIND_ENUM_CONSTANT(VOLUMETRIC_FOG_SHADOW_FILTER_DISABLED);
- BIND_ENUM_CONSTANT(VOLUMETRIC_FOG_SHADOW_FILTER_LOW);
- BIND_ENUM_CONSTANT(VOLUMETRIC_FOG_SHADOW_FILTER_MEDIUM);
- BIND_ENUM_CONSTANT(VOLUMETRIC_FOG_SHADOW_FILTER_HIGH);
}
Environment::Environment() {
diff --git a/scene/resources/environment.h b/scene/resources/environment.h
index 46842754f4..024bef34de 100644
--- a/scene/resources/environment.h
+++ b/scene/resources/environment.h
@@ -90,13 +90,6 @@ public:
GLOW_BLEND_MODE_MIX,
};
- enum VolumetricFogShadowFilter {
- VOLUMETRIC_FOG_SHADOW_FILTER_DISABLED,
- VOLUMETRIC_FOG_SHADOW_FILTER_LOW,
- VOLUMETRIC_FOG_SHADOW_FILTER_MEDIUM,
- VOLUMETRIC_FOG_SHADOW_FILTER_HIGH,
- };
-
private:
RID environment;
@@ -190,12 +183,15 @@ private:
// Volumetric Fog
bool volumetric_fog_enabled = false;
- float volumetric_fog_density = 0.01;
- Color volumetric_fog_light = Color(0.0, 0.0, 0.0);
- float volumetric_fog_light_energy = 1.0;
+ float volumetric_fog_density = 0.05;
+ Color volumetric_fog_albedo = Color(1.0, 1.0, 1.0);
+ Color volumetric_fog_emission = Color(0.0, 0.0, 0.0);
+ float volumetric_fog_emission_energy = 1.0;
+ float volumetric_fog_anisotropy = 0.2;
float volumetric_fog_length = 64.0;
float volumetric_fog_detail_spread = 2.0;
float volumetric_fog_gi_inject = 0.0;
+ float volumetric_fog_ambient_inject = false;
bool volumetric_fog_temporal_reproject = true;
float volumetric_fog_temporal_reproject_amount = 0.9;
void _update_volumetric_fog();
@@ -375,16 +371,22 @@ public:
bool is_volumetric_fog_enabled() const;
void set_volumetric_fog_density(float p_density);
float get_volumetric_fog_density() const;
- void set_volumetric_fog_light(Color p_color);
- Color get_volumetric_fog_light() const;
- void set_volumetric_fog_light_energy(float p_begin);
- float get_volumetric_fog_light_energy() const;
+ void set_volumetric_fog_albedo(Color p_color);
+ Color get_volumetric_fog_albedo() const;
+ void set_volumetric_fog_emission(Color p_color);
+ Color get_volumetric_fog_emission() const;
+ void set_volumetric_fog_emission_energy(float p_begin);
+ float get_volumetric_fog_emission_energy() const;
+ void set_volumetric_fog_anisotropy(float p_anisotropy);
+ float get_volumetric_fog_anisotropy() const;
void set_volumetric_fog_length(float p_length);
float get_volumetric_fog_length() const;
void set_volumetric_fog_detail_spread(float p_detail_spread);
float get_volumetric_fog_detail_spread() const;
void set_volumetric_fog_gi_inject(float p_gi_inject);
float get_volumetric_fog_gi_inject() const;
+ void set_volumetric_fog_ambient_inject(float p_ambient_inject);
+ float get_volumetric_fog_ambient_inject() const;
void set_volumetric_fog_temporal_reprojection_enabled(bool p_enable);
bool is_volumetric_fog_temporal_reprojection_enabled() const;
void set_volumetric_fog_temporal_reprojection_amount(float p_amount);
@@ -413,6 +415,5 @@ VARIANT_ENUM_CAST(Environment::ToneMapper)
VARIANT_ENUM_CAST(Environment::SDFGICascades)
VARIANT_ENUM_CAST(Environment::SDFGIYScale)
VARIANT_ENUM_CAST(Environment::GlowBlendMode)
-VARIANT_ENUM_CAST(Environment::VolumetricFogShadowFilter)
#endif // ENVIRONMENT_H
diff --git a/scene/resources/fog_material.cpp b/scene/resources/fog_material.cpp
new file mode 100644
index 0000000000..978aaca33d
--- /dev/null
+++ b/scene/resources/fog_material.cpp
@@ -0,0 +1,181 @@
+/*************************************************************************/
+/* fog_material.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "fog_material.h"
+
+#include "core/version.h"
+
+Mutex FogMaterial::shader_mutex;
+RID FogMaterial::shader;
+
+void FogMaterial::set_density(float p_density) {
+ density = p_density;
+ RS::get_singleton()->material_set_param(_get_material(), "density", density);
+}
+
+float FogMaterial::get_density() const {
+ return density;
+}
+
+void FogMaterial::set_albedo(Color p_albedo) {
+ albedo = p_albedo;
+ RS::get_singleton()->material_set_param(_get_material(), "albedo", albedo);
+}
+
+Color FogMaterial::get_albedo() const {
+ return albedo;
+}
+
+void FogMaterial::set_emission(Color p_emission) {
+ emission = p_emission;
+ RS::get_singleton()->material_set_param(_get_material(), "emission", emission);
+}
+
+Color FogMaterial::get_emission() const {
+ return emission;
+}
+
+void FogMaterial::set_height_falloff(float p_falloff) {
+ height_falloff = MAX(p_falloff, 0.0f);
+ RS::get_singleton()->material_set_param(_get_material(), "height_falloff", height_falloff);
+}
+
+float FogMaterial::get_height_falloff() const {
+ return height_falloff;
+}
+
+void FogMaterial::set_edge_fade(float p_edge_fade) {
+ edge_fade = MAX(p_edge_fade, 0.0f);
+ RS::get_singleton()->material_set_param(_get_material(), "edge_fade", edge_fade);
+}
+
+float FogMaterial::get_edge_fade() const {
+ return edge_fade;
+}
+
+void FogMaterial::set_density_texture(const Ref<Texture3D> &p_texture) {
+ density_texture = p_texture;
+ RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
+ RS::get_singleton()->material_set_param(_get_material(), "density_texture", tex_rid);
+}
+
+Ref<Texture3D> FogMaterial::get_density_texture() const {
+ return density_texture;
+}
+
+Shader::Mode FogMaterial::get_shader_mode() const {
+ return Shader::MODE_FOG;
+}
+
+RID FogMaterial::get_shader_rid() const {
+ _update_shader();
+ return shader;
+}
+
+RID FogMaterial::get_rid() const {
+ _update_shader();
+ if (!shader_set) {
+ RS::get_singleton()->material_set_shader(_get_material(), shader);
+ shader_set = true;
+ }
+ return _get_material();
+}
+
+void FogMaterial::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_density", "density"), &FogMaterial::set_density);
+ ClassDB::bind_method(D_METHOD("get_density"), &FogMaterial::get_density);
+ ClassDB::bind_method(D_METHOD("set_albedo", "albedo"), &FogMaterial::set_albedo);
+ ClassDB::bind_method(D_METHOD("get_albedo"), &FogMaterial::get_albedo);
+ ClassDB::bind_method(D_METHOD("set_emission", "emission"), &FogMaterial::set_emission);
+ ClassDB::bind_method(D_METHOD("get_emission"), &FogMaterial::get_emission);
+ ClassDB::bind_method(D_METHOD("set_height_falloff", "height_falloff"), &FogMaterial::set_height_falloff);
+ ClassDB::bind_method(D_METHOD("get_height_falloff"), &FogMaterial::get_height_falloff);
+ ClassDB::bind_method(D_METHOD("set_edge_fade", "edge_fade"), &FogMaterial::set_edge_fade);
+ ClassDB::bind_method(D_METHOD("get_edge_fade"), &FogMaterial::get_edge_fade);
+ ClassDB::bind_method(D_METHOD("set_density_texture", "density_texture"), &FogMaterial::set_density_texture);
+ ClassDB::bind_method(D_METHOD("get_density_texture"), &FogMaterial::get_density_texture);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "0.0,16.0,0.0001,or_greater,or_lesser"), "set_density", "get_density");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo", "get_albedo");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_falloff", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_height_falloff", "get_height_falloff");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_fade", PROPERTY_HINT_EXP_EASING), "set_edge_fade", "get_edge_fade");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "density_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_density_texture", "get_density_texture");
+}
+
+void FogMaterial::cleanup_shader() {
+ if (shader.is_valid()) {
+ RS::get_singleton()->free(shader);
+ }
+}
+
+void FogMaterial::_update_shader() {
+ shader_mutex.lock();
+ if (shader.is_null()) {
+ shader = RS::get_singleton()->shader_create();
+
+ // Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
+ RS::get_singleton()->shader_set_code(shader, R"(
+// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s FogMaterial.
+
+shader_type fog;
+
+uniform float density : hint_range(0, 1, 0.0001) = 1.0;
+uniform vec4 albedo : hint_color = vec4(1.0);
+uniform vec4 emission : hint_color = vec4(0, 0, 0, 1);
+uniform float height_falloff = 0.0;
+uniform float edge_fade = 0.1;
+uniform sampler3D density_texture: hint_white;
+
+
+void fog() {
+ DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0);
+ DENSITY *= texture(density_texture, UVW).r;
+ DENSITY *= pow(clamp(-SDF / min(min(EXTENTS.x, EXTENTS.y), EXTENTS.z), 0.0, 1.0), edge_fade);
+ ALBEDO = albedo.rgb;
+ EMISSION = emission.rgb;
+}
+)");
+ }
+ shader_mutex.unlock();
+}
+
+FogMaterial::FogMaterial() {
+ set_density(1.0);
+ set_albedo(Color(1, 1, 1, 1));
+ set_emission(Color(0, 0, 0, 1));
+
+ set_height_falloff(0.0);
+ set_edge_fade(0.1);
+}
+
+FogMaterial::~FogMaterial() {
+ RS::get_singleton()->material_set_shader(_get_material(), RID());
+}
diff --git a/scene/animation/animation_cache.h b/scene/resources/fog_material.h
index c856e644f7..e256bd4719 100644
--- a/scene/animation/animation_cache.h
+++ b/scene/resources/fog_material.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* animation_cache.h */
+/* fog_material.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,57 +28,60 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef ANIMATION_CACHE_H
-#define ANIMATION_CACHE_H
+#ifndef FOG_MATERIAL_H
+#define FOG_MATERIAL_H
-#include "scene/3d/skeleton_3d.h"
-#include "scene/resources/animation.h"
+#include "scene/resources/material.h"
-class AnimationCache : public Object {
- GDCLASS(AnimationCache, Object);
+class FogMaterial : public Material {
+ GDCLASS(FogMaterial, Material);
- struct Path {
- RES resource;
- Object *object = nullptr;
-#ifndef _3D_DISABLED
- Skeleton3D *skeleton = nullptr;
- Node3D *node_3d = nullptr;
-#endif // _3D_DISABLED
- Node *node = nullptr;
+private:
+ float density = 1.0;
+ Color albedo = Color(1, 1, 1, 1);
+ Color emission = Color(0, 0, 0, 0);
- int bone_idx = -1;
- Vector<StringName> subpath;
- bool valid = false;
- };
+ float height_falloff = 0.0;
- Set<Node *> connected_nodes;
- Vector<Path> path_cache;
+ float edge_fade = 0.1;
- Node *root = nullptr;
- Ref<Animation> animation;
- bool cache_dirty = true;
- bool cache_valid = false;
+ Ref<Texture3D> density_texture;
- void _node_exit_tree(Node *p_node);
-
- void _clear_cache();
- void _update_cache();
- void _animation_changed();
+ static Mutex shader_mutex;
+ static RID shader;
+ static void _update_shader();
+ mutable bool shader_set = false;
protected:
static void _bind_methods();
public:
- void set_track_transform(int p_idx, const Transform3D &p_transform);
- void set_track_value(int p_idx, const Variant &p_value);
- void call_track(int p_idx, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ void set_density(float p_density);
+ float get_density() const;
+
+ void set_albedo(Color p_color);
+ Color get_albedo() const;
+
+ void set_emission(Color p_color);
+ Color get_emission() const;
+
+ void set_height_falloff(float p_falloff);
+ float get_height_falloff() const;
+
+ void set_edge_fade(float p_edge_fade);
+ float get_edge_fade() const;
+
+ void set_density_texture(const Ref<Texture3D> &p_texture);
+ Ref<Texture3D> get_density_texture() const;
- void set_all(float p_time, float p_delta = 0);
+ virtual Shader::Mode get_shader_mode() const override;
+ virtual RID get_shader_rid() const override;
+ virtual RID get_rid() const override;
- void set_animation(const Ref<Animation> &p_animation);
- void set_root(Node *p_root);
+ static void cleanup_shader();
- AnimationCache();
+ FogMaterial();
+ virtual ~FogMaterial();
};
-#endif // ANIMATION_CACHE_H
+#endif // FOG_MATERIAL_H
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 04e2b0dc70..d9de47afc7 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -70,6 +70,15 @@ void FontData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased);
ClassDB::bind_method(D_METHOD("is_antialiased"), &FontData::is_antialiased);
+ ClassDB::bind_method(D_METHOD("set_font_name", "name"), &FontData::set_font_name);
+ ClassDB::bind_method(D_METHOD("get_font_name"), &FontData::get_font_name);
+
+ ClassDB::bind_method(D_METHOD("set_font_style_name", "name"), &FontData::set_font_style_name);
+ ClassDB::bind_method(D_METHOD("get_font_style_name"), &FontData::get_font_style_name);
+
+ ClassDB::bind_method(D_METHOD("set_font_style", "style"), &FontData::set_font_style);
+ ClassDB::bind_method(D_METHOD("get_font_style"), &FontData::get_font_style);
+
ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &FontData::set_multichannel_signed_distance_field);
ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &FontData::is_multichannel_signed_distance_field);
@@ -190,6 +199,15 @@ bool FontData::_set(const StringName &p_name, const Variant &p_value) {
} else if (tokens[0] == "antialiased") {
set_antialiased(p_value);
return true;
+ } else if (tokens[0] == "font_name") {
+ set_font_name(p_value);
+ return true;
+ } else if (tokens[0] == "style_name") {
+ set_font_style_name(p_value);
+ return true;
+ } else if (tokens[0] == "font_style") {
+ set_font_style(p_value);
+ return true;
} else if (tokens[0] == "multichannel_signed_distance_field") {
set_multichannel_signed_distance_field(p_value);
return true;
@@ -295,6 +313,15 @@ bool FontData::_get(const StringName &p_name, Variant &r_ret) const {
} else if (tokens[0] == "antialiased") {
r_ret = is_antialiased();
return true;
+ } else if (tokens[0] == "font_name") {
+ r_ret = get_font_name();
+ return true;
+ } else if (tokens[0] == "style_name") {
+ r_ret = get_font_style_name();
+ return true;
+ } else if (tokens[0] == "font_style") {
+ r_ret = get_font_style();
+ return true;
} else if (tokens[0] == "multichannel_signed_distance_field") {
r_ret = is_multichannel_signed_distance_field();
return true;
@@ -394,6 +421,9 @@ bool FontData::_get(const StringName &p_name, Variant &r_ret) const {
void FontData::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
+ p_list->push_back(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
+ p_list->push_back(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
+ p_list->push_back(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
p_list->push_back(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
p_list->push_back(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
p_list->push_back(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE));
@@ -510,6 +540,36 @@ PackedByteArray FontData::get_data() const {
return data;
}
+void FontData::set_font_name(const String &p_name) {
+ _ensure_rid(0);
+ TS->font_set_name(cache[0], p_name);
+}
+
+String FontData::get_font_name() const {
+ _ensure_rid(0);
+ return TS->font_get_name(cache[0]);
+}
+
+void FontData::set_font_style_name(const String &p_name) {
+ _ensure_rid(0);
+ TS->font_set_style_name(cache[0], p_name);
+}
+
+String FontData::get_font_style_name() const {
+ _ensure_rid(0);
+ return TS->font_get_style_name(cache[0]);
+}
+
+void FontData::set_font_style(uint32_t p_style) {
+ _ensure_rid(0);
+ TS->font_set_style(cache[0], p_style);
+}
+
+uint32_t FontData::get_font_style() const {
+ _ensure_rid(0);
+ return TS->font_get_style(cache[0]);
+}
+
void FontData::set_antialiased(bool p_antialiased) {
if (antialiased != p_antialiased) {
antialiased = p_antialiased;
@@ -1072,10 +1132,6 @@ void Font::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_data"), &Font::clear_data);
ClassDB::bind_method(D_METHOD("remove_data", "idx"), &Font::remove_data);
- ClassDB::bind_method(D_METHOD("set_base_size", "size"), &Font::set_base_size);
- ClassDB::bind_method(D_METHOD("get_base_size"), &Font::get_base_size);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "base_size"), "set_base_size", "get_base_size");
-
ClassDB::bind_method(D_METHOD("set_variation_coordinates", "variation_coordinates"), &Font::set_variation_coordinates);
ClassDB::bind_method(D_METHOD("get_variation_coordinates"), &Font::get_variation_coordinates);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "variation_coordinates"), "set_variation_coordinates", "get_variation_coordinates");
@@ -1087,20 +1143,20 @@ void Font::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_top"), "set_spacing", "get_spacing", TextServer::SPACING_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "spacing_bottom"), "set_spacing", "get_spacing", TextServer::SPACING_BOTTOM);
- ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_height", "size"), &Font::get_height, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(DEFAULT_FONT_SIZE));
- ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(-1), DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "width", "size", "flags"), &Font::get_multiline_string_size, DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
+ ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND));
- ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
+ ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE));
+ ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0)));
ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char);
ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars);
@@ -1195,7 +1251,6 @@ void Font::reset_state() {
data.clear();
rids.clear();
- base_size = 16;
variation_coordinates.clear();
spacing_bottom = 0;
spacing_top = 0;
@@ -1308,14 +1363,6 @@ void Font::remove_data(int p_idx) {
notify_property_list_changed();
}
-void Font::set_base_size(int p_size) {
- base_size = p_size;
-}
-
-int Font::get_base_size() const {
- return base_size;
-}
-
void Font::set_variation_coordinates(const Dictionary &p_variation_coordinates) {
_data_changed();
variation_coordinates = p_variation_coordinates;
@@ -1355,51 +1402,46 @@ int Font::get_spacing(TextServer::SpacingType p_spacing) const {
}
real_t Font::get_height(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size));
+ ret = MAX(ret, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size));
}
return ret + spacing_bottom + spacing_top;
}
real_t Font::get_ascent(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_ascent(rids[i], size));
+ ret = MAX(ret, TS->font_get_ascent(rids[i], p_size));
}
return ret + spacing_top;
}
real_t Font::get_descent(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_descent(rids[i], size));
+ ret = MAX(ret, TS->font_get_descent(rids[i], p_size));
}
return ret + spacing_bottom;
}
real_t Font::get_underline_position(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_underline_position(rids[i], size));
+ ret = MAX(ret, TS->font_get_underline_position(rids[i], p_size));
}
return ret + spacing_top;
}
real_t Font::get_underline_thickness(int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
real_t ret = 0.f;
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
- ret = MAX(ret, TS->font_get_underline_thickness(rids[i], size));
+ ret = MAX(ret, TS->font_get_underline_thickness(rids[i], p_size));
}
return ret;
}
@@ -1407,8 +1449,6 @@ real_t Font::get_underline_thickness(int p_size) const {
Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, real_t p_width, uint16_t p_flags) const {
ERR_FAIL_COND_V(data.is_empty(), Size2());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1418,14 +1458,14 @@ Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, re
hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
hash = hash_djb2_one_64(p_flags, hash);
}
- hash = hash_djb2_one_64(size, hash);
+ hash = hash_djb2_one_64(p_size, hash);
Ref<TextLine> buffer;
if (cache.has(hash)) {
buffer = cache.get(hash);
} else {
buffer.instantiate();
- buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
cache.insert(hash, buffer);
}
return buffer->get_size();
@@ -1434,8 +1474,6 @@ Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, re
Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int p_size, uint16_t p_flags) const {
ERR_FAIL_COND_V(data.is_empty(), Size2());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1443,14 +1481,14 @@ Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int
uint64_t hash = p_text.hash64();
uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
wrp_hash = hash_djb2_one_64(p_flags, wrp_hash);
- wrp_hash = hash_djb2_one_64(size, wrp_hash);
+ wrp_hash = hash_djb2_one_64(p_size, wrp_hash);
Ref<TextParagraph> lines_buffer;
if (cache_wrap.has(wrp_hash)) {
lines_buffer = cache_wrap.get(wrp_hash);
} else {
lines_buffer.instantiate();
- lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
lines_buffer->set_width(p_width);
lines_buffer->set_flags(p_flags);
cache_wrap.insert(wrp_hash, lines_buffer);
@@ -1473,8 +1511,6 @@ Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int
void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, real_t p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const {
ERR_FAIL_COND(data.is_empty());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1484,14 +1520,14 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t
hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
hash = hash_djb2_one_64(p_flags, hash);
}
- hash = hash_djb2_one_64(size, hash);
+ hash = hash_djb2_one_64(p_size, hash);
Ref<TextLine> buffer;
if (cache.has(hash)) {
buffer = cache.get(hash);
} else {
buffer.instantiate();
- buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
cache.insert(hash, buffer);
}
@@ -1515,8 +1551,6 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t
void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint16_t p_flags) const {
ERR_FAIL_COND(data.is_empty());
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
}
@@ -1524,14 +1558,14 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S
uint64_t hash = p_text.hash64();
uint64_t wrp_hash = hash_djb2_one_64(hash_djb2_one_float(p_width), hash);
wrp_hash = hash_djb2_one_64(p_flags, wrp_hash);
- wrp_hash = hash_djb2_one_64(size, wrp_hash);
+ wrp_hash = hash_djb2_one_64(p_size, wrp_hash);
Ref<TextParagraph> lines_buffer;
if (cache_wrap.has(wrp_hash)) {
lines_buffer = cache_wrap.get(wrp_hash);
} else {
lines_buffer.instantiate();
- lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ lines_buffer->add_string(p_text, Ref<Font>(this), p_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
lines_buffer->set_width(p_width);
lines_buffer->set_flags(p_flags);
cache_wrap.insert(wrp_hash, lines_buffer);
@@ -1573,16 +1607,14 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S
}
Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const {
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
if (data[i]->has_char(p_char)) {
- int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0);
- Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], size, glyph_a).x, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size));
+ int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0);
+ Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x, TS->font_get_ascent(rids[i], p_size) + TS->font_get_descent(rids[i], p_size));
if ((p_next != 0) && data[i]->has_char(p_next)) {
- int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0);
- ret.x -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x;
+ int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0);
+ ret.x -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x;
}
return ret;
}
@@ -1591,22 +1623,20 @@ Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const {
}
real_t Font::draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate) const {
- int size = (p_size <= 0) ? base_size : p_size;
-
for (int i = 0; i < data.size(); i++) {
_ensure_rid(i);
if (data[i]->has_char(p_char)) {
- int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0);
- real_t ret = TS->font_get_glyph_advance(rids[i], size, glyph_a).x;
+ int32_t glyph_a = TS->font_get_glyph_index(rids[i], p_size, p_char, 0);
+ real_t ret = TS->font_get_glyph_advance(rids[i], p_size, glyph_a).x;
if ((p_next != 0) && data[i]->has_char(p_next)) {
- int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0);
- ret -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x;
+ int32_t glyph_b = TS->font_get_glyph_index(rids[i], p_size, p_next, 0);
+ ret -= TS->font_get_kerning(rids[i], p_size, Vector2i(glyph_a, glyph_b)).x;
}
if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) {
- TS->font_draw_glyph_outline(rids[i], p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate);
+ TS->font_draw_glyph_outline(rids[i], p_canvas_item, p_size, p_outline_size, p_pos, glyph_a, p_outline_modulate);
}
- TS->font_draw_glyph(rids[i], p_canvas_item, size, p_pos, glyph_a, p_modulate);
+ TS->font_draw_glyph(rids[i], p_canvas_item, p_size, p_pos, glyph_a, p_modulate);
return ret;
}
}
diff --git a/scene/resources/font.h b/scene/resources/font.h
index e65fdb0751..4d9ee72c84 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -79,6 +79,15 @@ public:
virtual PackedByteArray get_data() const;
// Common properties.
+ virtual void set_font_name(const String &p_name);
+ virtual String get_font_name() const;
+
+ virtual void set_font_style_name(const String &p_name);
+ virtual String get_font_style_name() const;
+
+ virtual void set_font_style(uint32_t p_style);
+ virtual uint32_t get_font_style() const;
+
virtual void set_antialiased(bool p_antialiased);
virtual bool is_antialiased() const;
@@ -219,7 +228,6 @@ class Font : public Resource {
mutable Vector<RID> rids;
// Font config.
- int base_size = 16;
Dictionary variation_coordinates;
int spacing_bottom = 0;
int spacing_top = 0;
@@ -237,6 +245,8 @@ protected:
virtual void reset_state() override;
public:
+ static const int DEFAULT_FONT_SIZE = 16;
+
Dictionary get_feature_list() const;
// Font data.
@@ -249,9 +259,6 @@ public:
virtual void remove_data(int p_idx);
// Font configuration.
- virtual void set_base_size(int p_size);
- virtual int get_base_size() const;
-
virtual void set_variation_coordinates(const Dictionary &p_variation_coordinates);
virtual Dictionary get_variation_coordinates() const;
@@ -259,26 +266,26 @@ public:
virtual int get_spacing(TextServer::SpacingType p_spacing) const;
// Font metrics.
- virtual real_t get_height(int p_size = -1) const;
- virtual real_t get_ascent(int p_size = -1) const;
- virtual real_t get_descent(int p_size = -1) const;
- virtual real_t get_underline_position(int p_size = -1) const;
- virtual real_t get_underline_thickness(int p_size = -1) const;
+ virtual real_t get_height(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_ascent(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_descent(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_underline_position(int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t get_underline_thickness(int p_size = DEFAULT_FONT_SIZE) const;
// Drawing string.
- virtual Size2 get_string_size(const String &p_text, int p_size = -1, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = -1, uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const;
+ virtual Size2 get_string_size(const String &p_text, int p_size = DEFAULT_FONT_SIZE, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = DEFAULT_FONT_SIZE, uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const;
- virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
- virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
+ virtual void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint16_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const;
// Helper functions.
virtual bool has_char(char32_t p_char) const;
virtual String get_supported_chars() const;
// Drawing char.
- virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) const;
- virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
+ virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE) const;
+ virtual real_t draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const;
Vector<RID> get_rids() const;
diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp
index 7b9b942142..4559b4ce0a 100644
--- a/scene/resources/gradient.cpp
+++ b/scene/resources/gradient.cpp
@@ -51,6 +51,8 @@ void Gradient::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_offset", "point", "offset"), &Gradient::set_offset);
ClassDB::bind_method(D_METHOD("get_offset", "point"), &Gradient::get_offset);
+ ClassDB::bind_method(D_METHOD("reverse"), &Gradient::reverse);
+
ClassDB::bind_method(D_METHOD("set_color", "point", "color"), &Gradient::set_color);
ClassDB::bind_method(D_METHOD("get_color", "point"), &Gradient::get_color);
@@ -64,8 +66,18 @@ void Gradient::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_colors", "colors"), &Gradient::set_colors);
ClassDB::bind_method(D_METHOD("get_colors"), &Gradient::get_colors);
+ ClassDB::bind_method(D_METHOD("set_interpolation_mode", "interpolation_mode"), &Gradient::set_interpolation_mode);
+ ClassDB::bind_method(D_METHOD("get_interpolation_mode"), &Gradient::get_interpolation_mode);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode");
+
+ ADD_GROUP("Raw data", "");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "offsets"), "set_offsets", "get_offsets");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "colors"), "set_colors", "get_colors");
+
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR);
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT);
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC);
}
Vector<float> Gradient::get_offsets() const {
@@ -86,6 +98,15 @@ Vector<Color> Gradient::get_colors() const {
return colors;
}
+void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+ interpolation_mode = p_interp_mode;
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+Gradient::InterpolationMode Gradient::get_interpolation_mode() {
+ return interpolation_mode;
+}
+
void Gradient::set_offsets(const Vector<float> &p_offsets) {
points.resize(p_offsets.size());
for (int i = 0; i < points.size(); i++) {
@@ -127,6 +148,15 @@ void Gradient::remove_point(int p_index) {
emit_signal(CoreStringNames::get_singleton()->changed);
}
+void Gradient::reverse() {
+ for (int i = 0; i < points.size(); i++) {
+ points.write[i].offset = 1.0 - points[i].offset;
+ }
+
+ _update_sorting();
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
void Gradient::set_points(Vector<Gradient::Point> &p_points) {
points = p_points;
is_sorted = false;
diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h
index cf5b179c45..eb438d0bba 100644
--- a/scene/resources/gradient.h
+++ b/scene/resources/gradient.h
@@ -38,6 +38,12 @@ class Gradient : public Resource {
OBJ_SAVE_TYPE(Gradient);
public:
+ enum InterpolationMode {
+ GRADIENT_INTERPOLATE_LINEAR,
+ GRADIENT_INTERPOLATE_CONSTANT,
+ GRADIENT_INTERPOLATE_CUBIC,
+ };
+
struct Point {
float offset = 0.0;
Color color;
@@ -49,6 +55,8 @@ public:
private:
Vector<Point> points;
bool is_sorted = true;
+ InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR;
+
_FORCE_INLINE_ void _update_sorting() {
if (!is_sorted) {
points.sort();
@@ -65,9 +73,9 @@ public:
void add_point(float p_offset, const Color &p_color);
void remove_point(int p_index);
-
void set_points(Vector<Point> &p_points);
Vector<Point> &get_points();
+ void reverse();
void set_offset(int pos, const float offset);
float get_offset(int pos);
@@ -81,6 +89,13 @@ public:
void set_colors(const Vector<Color> &p_colors);
Vector<Color> get_colors() const;
+ void set_interpolation_mode(InterpolationMode p_interp_mode);
+ InterpolationMode get_interpolation_mode();
+
+ _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) {
+ return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
+ }
+
_FORCE_INLINE_ Color get_color_at_offset(float p_offset) {
if (points.is_empty()) {
return Color(0, 0, 0, 1);
@@ -88,7 +103,7 @@ public:
_update_sorting();
- //binary search
+ // Binary search.
int low = 0;
int high = points.size() - 1;
int middle = 0;
@@ -111,7 +126,7 @@ public:
}
}
- //return interpolated value
+ // Return interpolated value.
if (points[middle].offset > p_offset) {
middle--;
}
@@ -125,10 +140,44 @@ public:
}
const Point &pointFirst = points[first];
const Point &pointSecond = points[second];
- return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+
+ switch (interpolation_mode) {
+ case GRADIENT_INTERPOLATE_LINEAR: {
+ return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+ } break;
+ case GRADIENT_INTERPOLATE_CONSTANT: {
+ return pointFirst.color;
+ } break;
+ case GRADIENT_INTERPOLATE_CUBIC: {
+ int p0 = first - 1;
+ int p3 = second + 1;
+ if (p3 >= points.size()) {
+ p3 = second;
+ }
+ if (p0 < 0) {
+ p0 = first;
+ }
+ const Point &pointP0 = points[p0];
+ const Point &pointP3 = points[p3];
+
+ float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset);
+ float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x);
+ float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x);
+ float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x);
+ float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x);
+
+ return Color(r, g, b, a);
+ } break;
+ default: {
+ // Fallback to linear interpolation.
+ return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+ }
+ }
}
int get_points_count() const;
};
+VARIANT_ENUM_CAST(Gradient::InterpolationMode);
+
#endif // GRADIENT_H
diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp
index af69b799cc..7afa4c91f0 100644
--- a/scene/resources/importer_mesh.cpp
+++ b/scene/resources/importer_mesh.cpp
@@ -37,26 +37,34 @@
#include <cstdint>
void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
- ERR_FAIL_COND(arrays.size() != RS::ARRAY_MAX);
+ _split_normals(arrays, p_indices, p_normals);
- const PackedVector3Array &vertices = arrays[RS::ARRAY_VERTEX];
+ for (BlendShape &blend_shape : blend_shape_data) {
+ _split_normals(blend_shape.arrays, p_indices, p_normals);
+ }
+}
+
+void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
+ ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX);
+
+ const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX];
int current_vertex_count = vertices.size();
int new_vertex_count = p_indices.size();
int final_vertex_count = current_vertex_count + new_vertex_count;
const int *indices_ptr = p_indices.ptr();
- for (int i = 0; i < arrays.size(); i++) {
+ for (int i = 0; i < r_arrays.size(); i++) {
if (i == RS::ARRAY_INDEX) {
continue;
}
- if (arrays[i].get_type() == Variant::NIL) {
+ if (r_arrays[i].get_type() == Variant::NIL) {
continue;
}
- switch (arrays[i].get_type()) {
+ switch (r_arrays[i].get_type()) {
case Variant::PACKED_VECTOR3_ARRAY: {
- PackedVector3Array data = arrays[i];
+ PackedVector3Array data = r_arrays[i];
data.resize(final_vertex_count);
Vector3 *data_ptr = data.ptrw();
if (i == RS::ARRAY_NORMAL) {
@@ -67,55 +75,55 @@ void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, con
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
case Variant::PACKED_VECTOR2_ARRAY: {
- PackedVector2Array data = arrays[i];
+ PackedVector2Array data = r_arrays[i];
data.resize(final_vertex_count);
Vector2 *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
- PackedFloat32Array data = arrays[i];
+ PackedFloat32Array data = r_arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
float *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements);
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
case Variant::PACKED_INT32_ARRAY: {
- PackedInt32Array data = arrays[i];
+ PackedInt32Array data = r_arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
int32_t *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements);
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
case Variant::PACKED_BYTE_ARRAY: {
- PackedByteArray data = arrays[i];
+ PackedByteArray data = r_arrays[i];
int elements = data.size() / current_vertex_count;
data.resize(final_vertex_count * elements);
uint8_t *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements);
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
case Variant::PACKED_COLOR_ARRAY: {
- PackedColorArray data = arrays[i];
+ PackedColorArray data = r_arrays[i];
data.resize(final_vertex_count);
Color *data_ptr = data.ptrw();
for (int j = 0; j < new_vertex_count; j++) {
data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
}
- arrays[i] = data;
+ r_arrays[i] = data;
} break;
default: {
ERR_FAIL_MSG("Unhandled array type.");
@@ -261,9 +269,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) {
continue;
}
- if (get_blend_shape_count()) {
- continue;
- }
surfaces.write[i].lods.clear();
Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX];
@@ -992,7 +997,7 @@ Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() {
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y);
-struct EditorSceneImporterMeshLightmapSurface {
+struct EditorSceneFormatImporterMeshLightmapSurface {
Ref<Material> material;
LocalVector<SurfaceTool::Vertex> vertices;
Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX;
@@ -1010,7 +1015,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform,
LocalVector<float> uv;
LocalVector<Pair<int, int>> uv_indices;
- Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces;
+ Vector<EditorSceneFormatImporterMeshLightmapSurface> lightmap_surfaces;
// Keep only the scale
Basis basis = p_base_transform.get_basis();
@@ -1022,7 +1027,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform,
Basis normal_basis = transform.basis.inverse().transposed();
for (int i = 0; i < get_surface_count(); i++) {
- EditorSceneImporterMeshLightmapSurface s;
+ EditorSceneFormatImporterMeshLightmapSurface s;
s.primitive = get_surface_primitive_type(i);
ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap.");
@@ -1238,5 +1243,5 @@ void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint);
ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
}
diff --git a/scene/resources/importer_mesh.h b/scene/resources/importer_mesh.h
index 89909f17f0..8576312a8a 100644
--- a/scene/resources/importer_mesh.h
+++ b/scene/resources/importer_mesh.h
@@ -70,6 +70,7 @@ class ImporterMesh : public Resource {
};
void split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
+ static void _split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
};
Vector<Surface> surfaces;
Vector<String> blend_shapes;
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index abb3381c4e..8399b14a56 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -1086,7 +1086,7 @@ void BaseMaterial3D::_update_shader() {
code += " ALPHA = 1.0;\n";
} else if (transparency != TRANSPARENCY_DISABLED || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) {
- code += " ALPHA = albedo.a * albedo_tex.a;\n";
+ code += " ALPHA *= albedo.a * albedo_tex.a;\n";
}
if (transparency == TRANSPARENCY_ALPHA_HASH) {
code += " ALPHA_HASH_SCALE = alpha_hash_scale;\n";
@@ -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";
}
@@ -1695,7 +1695,7 @@ BaseMaterial3D::TextureFilter BaseMaterial3D::get_texture_filter() const {
void BaseMaterial3D::_validate_feature(const String &text, Feature feature, PropertyInfo &property) const {
if (property.name.begins_with(text) && property.name != text + "_enabled" && !features[feature]) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
@@ -1729,23 +1729,23 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const {
}
if (property.name == "billboard_keep_scale" && billboard_mode == BILLBOARD_DISABLED) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (property.name == "grow_amount" && !grow_enabled) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (property.name == "point_size" && !flags[FLAG_USE_POINT_SIZE]) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if (property.name == "proximity_fade_distance" && !proximity_fade_enabled) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
if ((property.name == "distance_fade_max_distance" || property.name == "distance_fade_min_distance") && distance_fade == DISTANCE_FADE_DISABLED) {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
// you can only enable anti-aliasing (in materials) on alpha scissor and alpha hash
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 18e6a51118..51b4e1fbd8 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -1577,7 +1577,7 @@ void ArrayMesh::regen_normal_maps() {
}
//dirty hack
-bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) = NULL;
+bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) = nullptr;
struct ArrayMeshLightmapSurface {
Ref<Material> material;
@@ -1842,8 +1842,8 @@ void ArrayMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_surfaces", "surfaces"), &ArrayMesh::_set_surfaces);
ClassDB::bind_method(D_METHOD("_get_surfaces"), &ArrayMesh::_get_surfaces);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_blend_shape_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_blend_shape_names", "_get_blend_shape_names");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_surfaces", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_surfaces", "_get_surfaces");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_blend_shape_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_blend_shape_names", "_get_blend_shape_names");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_surfaces", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_surfaces", "_get_surfaces");
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_shape_mode", PROPERTY_HINT_ENUM, "Normalized,Relative"), "set_blend_shape_mode", "get_blend_shape_mode");
ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, ""), "set_custom_aabb", "get_custom_aabb");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shadow_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ArrayMesh"), "set_shadow_mesh", "get_shadow_mesh");
diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h
index 8d5571d67c..a95b4d4a5e 100644
--- a/scene/resources/mesh.h
+++ b/scene/resources/mesh.h
@@ -278,7 +278,6 @@ public:
int surface_get_array_index_len(int p_idx) const override;
uint32_t surface_get_format(int p_idx) const override;
PrimitiveType surface_get_primitive_type(int p_idx) const override;
- bool surface_is_alpha_sorting_enabled(int p_idx) const;
virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) override;
virtual Ref<Material> surface_get_material(int p_idx) const override;
diff --git a/scene/resources/mesh_data_tool.cpp b/scene/resources/mesh_data_tool.cpp
index 04b2437ae8..9ecd8ec2f3 100644
--- a/scene/resources/mesh_data_tool.cpp
+++ b/scene/resources/mesh_data_tool.cpp
@@ -421,6 +421,7 @@ Vector<int> MeshDataTool::get_vertex_bones(int p_idx) const {
void MeshDataTool::set_vertex_bones(int p_idx, const Vector<int> &p_bones) {
ERR_FAIL_INDEX(p_idx, vertices.size());
+ ERR_FAIL_COND(p_bones.size() != 4);
vertices.write[p_idx].bones = p_bones;
format |= Mesh::ARRAY_FORMAT_BONES;
}
@@ -432,6 +433,7 @@ Vector<float> MeshDataTool::get_vertex_weights(int p_idx) const {
void MeshDataTool::set_vertex_weights(int p_idx, const Vector<float> &p_weights) {
ERR_FAIL_INDEX(p_idx, vertices.size());
+ ERR_FAIL_COND(p_weights.size() != 4);
vertices.write[p_idx].weights = p_weights;
format |= Mesh::ARRAY_FORMAT_WEIGHTS;
}
diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp
index d87056f55d..db091ec37b 100644
--- a/scene/resources/navigation_mesh.cpp
+++ b/scene/resources/navigation_mesh.cpp
@@ -477,8 +477,8 @@ void NavigationMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationMesh::_set_polygons);
ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationMesh::_get_polygons);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type/sample_partition_type", PROPERTY_HINT_ENUM, "Watershed,Monotone,Layers"), "set_sample_partition_type", "get_sample_partition_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Both"), "set_parsed_geometry_type", "get_parsed_geometry_type");
diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h
index 1cdf7a07ed..009239838f 100644
--- a/scene/resources/navigation_mesh.h
+++ b/scene/resources/navigation_mesh.h
@@ -85,7 +85,7 @@ protected:
float cell_size = 0.3f;
float cell_height = 0.2f;
float agent_height = 2.0f;
- float agent_radius = 0.6f;
+ float agent_radius = 1.0f;
float agent_max_climb = 0.9f;
float agent_max_slope = 45.0f;
float region_min_size = 8.0f;
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 60cda637ca..3f4765d20f 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -34,10 +34,12 @@
#include "core/config/project_settings.h"
#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
+#include "editor/editor_inspector.h"
#include "scene/2d/node_2d.h"
#include "scene/3d/node_3d.h"
#include "scene/gui/control.h"
#include "scene/main/instance_placeholder.h"
+#include "scene/property_utils.h"
#define PACKED_SCENE_VERSION 2
@@ -45,6 +47,30 @@ bool SceneState::can_instantiate() const {
return nodes.size() > 0;
}
+static Array _sanitize_node_pinned_properties(Node *p_node) {
+ if (!p_node->has_meta("_edit_pinned_properties_")) {
+ return Array();
+ }
+ Array pinned = p_node->get_meta("_edit_pinned_properties_");
+ if (pinned.is_empty()) {
+ return Array();
+ }
+ Set<StringName> storable_properties;
+ p_node->get_storable_properties(storable_properties);
+ int i = 0;
+ do {
+ if (storable_properties.has(pinned[i])) {
+ i++;
+ } else {
+ pinned.remove(i);
+ }
+ } while (i < pinned.size());
+ if (pinned.is_empty()) {
+ p_node->remove_meta("_edit_pinned_properties_");
+ }
+ return pinned;
+}
+
Node *SceneState::instantiate(GenEditState p_edit_state) const {
// nodes where instancing failed (because something is missing)
List<Node *> stray_instances;
@@ -227,7 +253,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
} else {
Node *base = i == 0 ? node : ret_nodes[0];
- if (p_edit_state == GEN_EDIT_STATE_MAIN) {
+ if (p_edit_state == GEN_EDIT_STATE_MAIN || p_edit_state == GEN_EDIT_STATE_MAIN_INHERITED) {
//for the main scene, use the resource as is
res->configure_for_local_scene(base, resources_local_to_scene);
resources_local_to_scene[res] = res;
@@ -289,6 +315,13 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
node->_set_owner_nocheck(owner);
}
}
+
+ // we only want to deal with pinned flag if instancing as pure main (no instance, no inheriting)
+ if (p_edit_state == GEN_EDIT_STATE_MAIN) {
+ _sanitize_node_pinned_properties(node);
+ } else {
+ node->remove_meta("_edit_pinned_properties_");
+ }
}
ret_nodes[i] = node;
@@ -415,61 +448,22 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
// with the instance states, we can query for identical properties/groups
// and only save what has changed
- List<PackState> pack_state_stack;
-
- bool instantiated_by_owner = true;
-
- {
- Node *n = p_node;
-
- while (n) {
- if (n == p_owner) {
- Ref<SceneState> state = n->get_scene_inherited_state();
- if (state.is_valid()) {
- int node = state->find_node_by_path(n->get_path_to(p_node));
- if (node >= 0) {
- //this one has state for this node, save
- PackState ps;
- ps.node = node;
- ps.state = state;
- pack_state_stack.push_back(ps);
- instantiated_by_owner = false;
- }
- }
-
- if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) {
- if (p_node->get_scene_instance_load_placeholder()) {
- //it's a placeholder, use the placeholder path
- nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map);
- nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER;
- } else {
- //must instance ourselves
- Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path());
- if (!instance.is_valid()) {
- return ERR_CANT_OPEN;
- }
+ bool instantiated_by_owner = false;
+ Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, p_owner, &instantiated_by_owner);
- nd.instance = _vm_get_variant(instance, variant_map);
- }
- }
- n = nullptr;
- } else {
- if (n->get_scene_file_path() != String()) {
- //is an instance
- Ref<SceneState> state = n->get_scene_instance_state();
- if (state.is_valid()) {
- int node = state->find_node_by_path(n->get_path_to(p_node));
- if (node >= 0) {
- //this one has state for this node, save
- PackState ps;
- ps.node = node;
- ps.state = state;
- pack_state_stack.push_back(ps);
- }
- }
- }
- n = n->get_owner();
+ if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) {
+ if (p_node->get_scene_instance_load_placeholder()) {
+ //it's a placeholder, use the placeholder path
+ nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map);
+ nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER;
+ } else {
+ //must instance ourselves
+ Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path());
+ if (!instance.is_valid()) {
+ return ERR_CANT_OPEN;
}
+
+ nd.instance = _vm_get_variant(instance, variant_map);
}
}
@@ -478,88 +472,37 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
List<PropertyInfo> plist;
p_node->get_property_list(&plist);
- StringName type = p_node->get_class();
- Ref<Script> script = p_node->get_script();
- if (Engine::get_singleton()->is_editor_hint() && script.is_valid()) {
- // Should be called in the editor only and not at runtime,
- // otherwise it can cause problems because of missing instance state support.
- script->update_exports();
- }
+ Array pinned_props = _sanitize_node_pinned_properties(p_node);
for (const PropertyInfo &E : plist) {
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
- String name = E.name;
- Variant value = p_node->get(E.name);
-
- bool isdefault = false;
- Variant default_value = ClassDB::class_get_default_property_value(type, name);
-
- if (default_value.get_type() != Variant::NIL) {
- isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value));
- }
+ Variant forced_value;
- if (!isdefault && script.is_valid() && script->get_property_default_value(name, default_value)) {
- isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value));
- }
- // the version above makes more sense, because it does not rely on placeholder or usage flag
- // in the script, just the default value function.
- // if (E.usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE) {
- // isdefault = true; //is script default value
- // }
-
- if (pack_state_stack.size()) {
- // we are on part of an instantiated subscene
- // or part of instantiated scene.
- // only save what has been changed
- // only save changed properties in instance
-
- if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E.name == "__meta__") {
- //property has requested that no instance state is saved, sorry
- //also, meta won't be overridden or saved
+ // If instance or inheriting, not saving if property requested so, or it's meta
+ if (states_stack.size()) {
+ if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE)) {
continue;
}
-
- bool exists = false;
- Variant original;
-
- for (List<PackState>::Element *F = pack_state_stack.back(); F; F = F->prev()) {
- //check all levels of pack to see if the property exists somewhere
- const PackState &ps = F->get();
-
- original = ps.state->get_property_value(ps.node, E.name, exists);
- if (exists) {
- break;
- }
- }
-
- if (exists) {
- //check if already exists and did not change
- if (value.get_type() == Variant::FLOAT && original.get_type() == Variant::FLOAT) {
- //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error
- float a = value;
- float b = original;
-
- if (Math::is_equal_approx(a, b)) {
- continue;
- }
- } else if (bool(Variant::evaluate(Variant::OP_EQUAL, value, original))) {
- continue;
+ // Meta is normally not saved in instances/inherited (see GH-12838), but we need to save the pinned list
+ if (E.name == "__meta__") {
+ if (pinned_props.size()) {
+ Dictionary meta_override;
+ meta_override["_edit_pinned_properties_"] = pinned_props;
+ forced_value = meta_override;
}
}
+ }
- if (!exists && isdefault) {
- //does not exist in original node, but it's the default value
- //so safe to skip too.
- continue;
- }
+ StringName name = E.name;
+ Variant value = forced_value.get_type() == Variant::NIL ? p_node->get(name) : forced_value;
- } else {
- if (isdefault) {
- //it's the default value, no point in saving it
+ if (!pinned_props.has(name) && forced_value.get_type() == Variant::NIL) {
+ Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &states_stack, true);
+ if (!PropertyUtils::is_property_value_different(value, default_value)) {
continue;
}
}
@@ -585,10 +528,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
*/
bool skip = false;
- for (const PackState &F : pack_state_stack) {
+ for (const SceneState::PackState &ia : states_stack) {
//check all levels of pack to see if the group was added somewhere
- const PackState &ps = F;
- if (ps.state->is_node_in_group(ps.node, gi.name)) {
+ if (ia.state->is_node_in_group(ia.node, gi.name)) {
skip = true;
break;
}
@@ -618,7 +560,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
// Save the right type. If this node was created by an instance
// then flag that the node should not be created but reused
- if (pack_state_stack.is_empty() && !is_editable_instance) {
+ if (states_stack.is_empty() && !is_editable_instance) {
//this node is not part of an instancing process, so save the type
nd.type = _nm_get_string(p_node->get_class(), name_map);
} else {
@@ -635,7 +577,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
bool save_node = nd.properties.size() || nd.groups.size(); // some local properties or groups exist
save_node = save_node || p_node == p_owner; // owner is always saved
- save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instantiated
+ save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instanced
int idx = nodes.size();
int parent_node = NO_PARENT_SAVED;
@@ -932,7 +874,7 @@ void SceneState::clear() {
base_scene_idx = -1;
}
-Ref<SceneState> SceneState::_get_base_scene_state() const {
+Ref<SceneState> SceneState::get_base_scene_state() const {
if (base_scene_idx >= 0) {
Ref<PackedScene> ps = variants[base_scene_idx];
if (ps.is_valid()) {
@@ -947,8 +889,8 @@ int SceneState::find_node_by_path(const NodePath &p_node) const {
ERR_FAIL_COND_V_MSG(node_path_cache.size() == 0, -1, "This operation requires the node cache to have been built.");
if (!node_path_cache.has(p_node)) {
- if (_get_base_scene_state().is_valid()) {
- int idx = _get_base_scene_state()->find_node_by_path(p_node);
+ if (get_base_scene_state().is_valid()) {
+ int idx = get_base_scene_state()->find_node_by_path(p_node);
if (idx != -1) {
int rkey = _find_base_scene_node_remap_key(idx);
if (rkey == -1) {
@@ -963,11 +905,11 @@ int SceneState::find_node_by_path(const NodePath &p_node) const {
int nid = node_path_cache[p_node];
- if (_get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) {
+ if (get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) {
//for nodes that _do_ exist in current scene, still try to look for
//the node in the instantiated scene, as a property may be missing
//from the local one
- int idx = _get_base_scene_state()->find_node_by_path(p_node);
+ int idx = get_base_scene_state()->find_node_by_path(p_node);
if (idx != -1) {
base_scene_node_remap[nid] = idx;
}
@@ -1007,7 +949,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property,
//property not found, try on instance
if (base_scene_node_remap.has(p_node)) {
- return _get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found);
+ return get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found);
}
return Variant();
@@ -1026,7 +968,7 @@ bool SceneState::is_node_in_group(int p_node, const StringName &p_group) const {
}
if (base_scene_node_remap.has(p_node)) {
- return _get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group);
+ return get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group);
}
return false;
@@ -1065,7 +1007,7 @@ bool SceneState::is_connection(int p_node, const StringName &p_signal, int p_to_
}
if (base_scene_node_remap.has(p_node) && base_scene_node_remap.has(p_to_node)) {
- return _get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method);
+ return get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method);
}
return false;
@@ -1488,7 +1430,7 @@ bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p
}
}
- ss = ss->_get_base_scene_state();
+ ss = ss->get_base_scene_state();
} while (ss.is_valid());
return false;
@@ -1610,6 +1552,7 @@ void SceneState::_bind_methods() {
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN);
+ BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN_INHERITED);
}
SceneState::SceneState() {
@@ -1651,7 +1594,7 @@ Node *PackedScene::instantiate(GenEditState p_edit_state) const {
s->set_scene_instance_state(state);
}
- if (get_path() != "" && get_path().find("::") == -1) {
+ if (!is_built_in()) {
s->set_scene_file_path(get_path());
}
@@ -1701,6 +1644,7 @@ void PackedScene::_bind_methods() {
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN);
+ BIND_ENUM_CONSTANT(GEN_EDIT_STATE_MAIN_INHERITED);
}
PackedScene::PackedScene() {
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index 55708f7914..a03da558e4 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -69,11 +69,6 @@ class SceneState : public RefCounted {
Vector<int> groups;
};
- struct PackState {
- Ref<SceneState> state;
- int node = -1;
- };
-
Vector<NodeData> nodes;
struct ConnectionData {
@@ -94,8 +89,6 @@ class SceneState : public RefCounted {
uint64_t last_modified_time = 0;
- _FORCE_INLINE_ Ref<SceneState> _get_base_scene_state() const;
-
static bool disable_placeholders;
Vector<String> _get_node_groups(int p_idx) const;
@@ -117,6 +110,12 @@ public:
GEN_EDIT_STATE_DISABLED,
GEN_EDIT_STATE_INSTANCE,
GEN_EDIT_STATE_MAIN,
+ GEN_EDIT_STATE_MAIN_INHERITED,
+ };
+
+ struct PackState {
+ Ref<SceneState> state;
+ int node = -1;
};
static void set_disable_placeholders(bool p_disable);
@@ -139,6 +138,8 @@ public:
bool can_instantiate() const;
Node *instantiate(GenEditState p_edit_state) const;
+ Ref<SceneState> get_base_scene_state() const;
+
//unbuild API
int get_node_count() const;
@@ -207,6 +208,7 @@ public:
GEN_EDIT_STATE_DISABLED,
GEN_EDIT_STATE_INSTANCE,
GEN_EDIT_STATE_MAIN,
+ GEN_EDIT_STATE_MAIN_INHERITED,
};
Error pack(Node *p_scene);
diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp
index d9ec0bfd69..e77f5a0be7 100644
--- a/scene/resources/particles_material.cpp
+++ b/scene/resources/particles_material.cpp
@@ -1413,7 +1413,7 @@ void ParticlesMaterial::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE);
ADD_GROUP("Color", "");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture1D"), "set_color_ramp", "get_color_ramp");
ADD_GROUP("Hue Variation", "hue_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION);
diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp
index 4dd3c874cb..ec2022ed2f 100644
--- a/scene/resources/polygon_path_finder.cpp
+++ b/scene/resources/polygon_path_finder.cpp
@@ -556,7 +556,7 @@ void PolygonPathFinder::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_data"), &PolygonPathFinder::_set_data);
ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
PolygonPathFinder::PolygonPathFinder() {
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 77d915aef9..ea3b72af1b 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1492,7 +1492,7 @@ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) {
} else {
if (internal_resources.has(res)) {
return "SubResource( \"" + internal_resources[res] + "\" )";
- } else if (res->get_path().length() && res->get_path().find("::") == -1) {
+ } else if (!res->is_built_in()) {
if (res->get_path() == local_path) { //circular reference attempt
return "null";
}
@@ -1515,7 +1515,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
return;
}
- if (!p_main && (!bundle_resources) && res->get_path().length() && res->get_path().find("::") == -1) {
+ if (!p_main && (!bundle_resources) && !res->is_built_in()) {
if (res->get_path() == local_path) {
ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded.");
return;
@@ -1728,7 +1728,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r
for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) {
RES res = E->get();
- if (E->next() && (res->get_path() == "" || res->get_path().find("::") != -1)) {
+ if (E->next() && res->is_built_in()) {
if (res->get_scene_unique_id() != "") {
if (used_unique_ids.has(res->get_scene_unique_id())) {
res->set_scene_unique_id(""); // Repeated.
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 242e20f3b0..84fa07e4f4 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -49,6 +49,8 @@ void Shader::set_code(const String &p_code) {
mode = MODE_PARTICLES;
} else if (type == "sky") {
mode = MODE_SKY;
+ } else if (type == "fog") {
+ mode = MODE_FOG;
} else {
mode = MODE_SPATIAL;
}
@@ -95,28 +97,36 @@ RID Shader::get_rid() const {
return shader;
}
-void Shader::set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture) {
+void Shader::set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture, int p_index) {
if (p_texture.is_valid()) {
- default_textures[p_param] = p_texture;
- RS::get_singleton()->shader_set_default_texture_param(shader, p_param, p_texture->get_rid());
+ if (!default_textures.has(p_param)) {
+ default_textures[p_param] = Map<int, Ref<Texture2D>>();
+ }
+ default_textures[p_param][p_index] = p_texture;
+ RS::get_singleton()->shader_set_default_texture_param(shader, p_param, p_texture->get_rid(), p_index);
} else {
- default_textures.erase(p_param);
- RS::get_singleton()->shader_set_default_texture_param(shader, p_param, RID());
+ if (default_textures.has(p_param) && default_textures[p_param].has(p_index)) {
+ default_textures[p_param].erase(p_index);
+
+ if (default_textures[p_param].is_empty()) {
+ default_textures.erase(p_param);
+ }
+ }
+ RS::get_singleton()->shader_set_default_texture_param(shader, p_param, RID(), p_index);
}
emit_changed();
}
-Ref<Texture2D> Shader::get_default_texture_param(const StringName &p_param) const {
- if (default_textures.has(p_param)) {
- return default_textures[p_param];
- } else {
- return Ref<Texture2D>();
+Ref<Texture2D> Shader::get_default_texture_param(const StringName &p_param, int p_index) const {
+ if (default_textures.has(p_param) && default_textures[p_param].has(p_index)) {
+ return default_textures[p_param][p_index];
}
+ return Ref<Texture2D>();
}
void Shader::get_default_texture_param_list(List<StringName> *r_textures) const {
- for (const KeyValue<StringName, Ref<Texture2D>> &E : default_textures) {
+ for (const KeyValue<StringName, Map<int, Ref<Texture2D>>> &E : default_textures) {
r_textures->push_back(E.key);
}
}
@@ -138,17 +148,18 @@ void Shader::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_code", "code"), &Shader::set_code);
ClassDB::bind_method(D_METHOD("get_code"), &Shader::get_code);
- ClassDB::bind_method(D_METHOD("set_default_texture_param", "param", "texture"), &Shader::set_default_texture_param);
- ClassDB::bind_method(D_METHOD("get_default_texture_param", "param"), &Shader::get_default_texture_param);
+ ClassDB::bind_method(D_METHOD("set_default_texture_param", "param", "texture", "index"), &Shader::set_default_texture_param, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_default_texture_param", "param", "index"), &Shader::get_default_texture_param, DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_param", "name"), &Shader::has_param);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_code", "get_code");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_code", "get_code");
BIND_ENUM_CONSTANT(MODE_SPATIAL);
BIND_ENUM_CONSTANT(MODE_CANVAS_ITEM);
BIND_ENUM_CONSTANT(MODE_PARTICLES);
BIND_ENUM_CONSTANT(MODE_SKY);
+ BIND_ENUM_CONSTANT(MODE_FOG);
}
Shader::Shader() {
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 6563181ca2..c688dc1bab 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -46,6 +46,7 @@ public:
MODE_CANVAS_ITEM,
MODE_PARTICLES,
MODE_SKY,
+ MODE_FOG,
MODE_MAX
};
@@ -58,7 +59,7 @@ private:
// conversion fast and save memory.
mutable bool params_cache_dirty = true;
mutable Map<StringName, StringName> params_cache; //map a shader param to a material param..
- Map<StringName, Ref<Texture2D>> default_textures;
+ Map<StringName, Map<int, Ref<Texture2D>>> default_textures;
virtual void _update_shader() const; //used for visual shader
protected:
@@ -74,8 +75,8 @@ public:
void get_param_list(List<PropertyInfo> *p_params) const;
bool has_param(const StringName &p_param) const;
- void set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture);
- Ref<Texture2D> get_default_texture_param(const StringName &p_param) const;
+ void set_default_texture_param(const StringName &p_param, const Ref<Texture2D> &p_texture, int p_index = 0);
+ Ref<Texture2D> get_default_texture_param(const StringName &p_param, int p_index = 0) const;
void get_default_texture_param_list(List<StringName> *r_textures) const;
virtual bool is_text_shader() const;
diff --git a/scene/resources/shape_2d.h b/scene/resources/shape_2d.h
index 14bdd60e4b..7c5d1344e8 100644
--- a/scene/resources/shape_2d.h
+++ b/scene/resources/shape_2d.h
@@ -64,7 +64,6 @@ public:
static bool is_collision_outline_enabled();
- Shape2D();
~Shape2D();
};
diff --git a/scene/resources/skeleton_modification_2d_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp
index 6eab8d4afe..bea42109cb 100644
--- a/scene/resources/skeleton_modification_2d_ccdik.cpp
+++ b/scene/resources/skeleton_modification_2d_ccdik.cpp
@@ -205,8 +205,8 @@ void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *
} else {
// How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint.
// Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node.
- float joint_to_tip = operation_transform.get_origin().angle_to_point(p_tip->get_global_position());
- float joint_to_target = operation_transform.get_origin().angle_to_point(p_target->get_global_position());
+ float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin());
+ float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin());
operation_transform.set_rotation(
operation_transform.get_rotation() + (joint_to_target - joint_to_tip));
}
diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp
index 84abc9d020..31045455a3 100644
--- a/scene/resources/skeleton_modification_2d_jiggle.cpp
+++ b/scene/resources/skeleton_modification_2d_jiggle.cpp
@@ -194,9 +194,13 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
PhysicsDirectSpaceState2D::RayResult ray_result;
+ PhysicsDirectSpaceState2D::RayParameters ray_params;
+ ray_params.from = operation_bone_trans.get_origin();
+ ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position;
+ ray_params.collision_mask = collision_mask;
+
// Add exception support?
- bool ray_hit = space_state->intersect_ray(operation_bone_trans.get_origin(), jiggle_data_chain[p_joint_idx].dynamic_position,
- ray_result, Set<RID>(), collision_mask);
+ bool ray_hit = space_state->intersect_ray(ray_params, ray_result);
if (ray_hit) {
jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
diff --git a/scene/resources/skeleton_modification_2d_lookat.cpp b/scene/resources/skeleton_modification_2d_lookat.cpp
index 2da770f012..740937fc44 100644
--- a/scene/resources/skeleton_modification_2d_lookat.cpp
+++ b/scene/resources/skeleton_modification_2d_lookat.cpp
@@ -241,7 +241,7 @@ int SkeletonModification2DLookAt::get_bone_index() const {
void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) {
ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!");
- if (is_setup) {
+ if (is_setup && stack) {
if (stack->skeleton) {
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
bone_idx = p_bone_idx;
diff --git a/scene/resources/skeleton_modification_3d_fabrik.cpp b/scene/resources/skeleton_modification_3d_fabrik.cpp
index e615615924..dedea3e282 100644
--- a/scene/resources/skeleton_modification_3d_fabrik.cpp
+++ b/scene/resources/skeleton_modification_3d_fabrik.cpp
@@ -149,6 +149,11 @@ void SkeletonModification3DFABRIK::_execute(real_t p_delta) {
return;
}
+ // Make sure the transform cache is the correct size
+ if (fabrik_transforms.size() != fabrik_data_chain.size()) {
+ fabrik_transforms.resize(fabrik_data_chain.size());
+ }
+
// Verify that all joints have a valid bone ID, and that all bone lengths are zero or more
// Also, while we are here, apply magnet positions.
for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) {
@@ -162,27 +167,24 @@ void SkeletonModification3DFABRIK::_execute(real_t p_delta) {
if (_print_execution_error(fabrik_data_chain[i].length < 0, "FABRIK Joint " + itos(i) + " has an invalid joint length. Cannot execute!")) {
return;
}
-
- Transform3D local_pose_override = stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[i].bone_idx);
+ fabrik_transforms[i] = stack->skeleton->get_bone_global_pose(fabrik_data_chain[i].bone_idx);
// Apply magnet positions:
if (stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) {
int parent_bone_idx = stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx);
- Transform3D conversion_transform = (stack->skeleton->get_bone_global_pose(parent_bone_idx) * stack->skeleton->get_bone_rest(parent_bone_idx));
- local_pose_override.origin += conversion_transform.basis.xform_inv(fabrik_data_chain[i].magnet_position);
+ Transform3D conversion_transform = (stack->skeleton->get_bone_global_pose(parent_bone_idx));
+ fabrik_transforms[i].origin += conversion_transform.basis.xform_inv(fabrik_data_chain[i].magnet_position);
} else {
- local_pose_override.origin += fabrik_data_chain[i].magnet_position;
+ fabrik_transforms[i].origin += fabrik_data_chain[i].magnet_position;
}
-
- stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, local_pose_override, stack->strength, true);
}
+ Transform3D origin_global_pose_trans = stack->skeleton->get_bone_global_pose_no_override(fabrik_data_chain[0].bone_idx);
target_global_pose = stack->skeleton->world_transform_to_global_pose(node_target->get_global_transform());
- origin_global_pose = stack->skeleton->local_pose_to_global_pose(
- fabrik_data_chain[0].bone_idx, stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[0].bone_idx));
+ origin_global_pose = origin_global_pose_trans;
final_joint_idx = fabrik_data_chain.size() - 1;
- real_t target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length();
+ real_t target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin);
chain_iterations = 0;
while (target_distance > chain_tolerance) {
@@ -190,7 +192,7 @@ void SkeletonModification3DFABRIK::_execute(real_t p_delta) {
chain_forwards();
// update the target distance
- target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length();
+ target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin);
// update chain iterations
chain_iterations += 1;
@@ -205,7 +207,7 @@ void SkeletonModification3DFABRIK::_execute(real_t p_delta) {
void SkeletonModification3DFABRIK::chain_backwards() {
int final_bone_idx = fabrik_data_chain[final_joint_idx].bone_idx;
- Transform3D final_joint_trans = stack->skeleton->local_pose_to_global_pose(final_bone_idx, stack->skeleton->get_bone_local_pose_override(final_bone_idx));
+ Transform3D final_joint_trans = fabrik_transforms[final_joint_idx];
// Get the direction the final bone is facing in.
stack->skeleton->update_bone_rest_forward_vector(final_bone_idx);
@@ -220,52 +222,46 @@ void SkeletonModification3DFABRIK::chain_backwards() {
// set the position of the final joint to the target position
final_joint_trans.origin = target_global_pose.origin - (direction * fabrik_data_chain[final_joint_idx].length);
- final_joint_trans = stack->skeleton->global_pose_to_local_pose(final_bone_idx, final_joint_trans);
- stack->skeleton->set_bone_local_pose_override(final_bone_idx, final_joint_trans, stack->strength, true);
+ fabrik_transforms[final_joint_idx] = final_joint_trans;
// for all other joints, move them towards the target
int i = final_joint_idx;
while (i >= 1) {
- int next_bone_idx = fabrik_data_chain[i].bone_idx;
- Transform3D next_bone_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx));
+ Transform3D next_bone_trans = fabrik_transforms[i];
i -= 1;
- int current_bone_idx = fabrik_data_chain[i].bone_idx;
- Transform3D current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, stack->skeleton->get_bone_local_pose_override(current_bone_idx));
+ Transform3D current_trans = fabrik_transforms[i];
real_t length = fabrik_data_chain[i].length / (current_trans.origin.distance_to(next_bone_trans.origin));
current_trans.origin = next_bone_trans.origin.lerp(current_trans.origin, length);
- // Apply it back to the skeleton
- stack->skeleton->set_bone_local_pose_override(current_bone_idx, stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans), stack->strength, true);
+ // Save the result
+ fabrik_transforms[i] = current_trans;
}
}
void SkeletonModification3DFABRIK::chain_forwards() {
// Set root at the initial position.
- int origin_bone_idx = fabrik_data_chain[0].bone_idx;
- Transform3D root_transform = stack->skeleton->local_pose_to_global_pose(origin_bone_idx, stack->skeleton->get_bone_local_pose_override(origin_bone_idx));
+ Transform3D root_transform = fabrik_transforms[0];
+
root_transform.origin = origin_global_pose.origin;
- stack->skeleton->set_bone_local_pose_override(origin_bone_idx, stack->skeleton->global_pose_to_local_pose(origin_bone_idx, root_transform), stack->strength, true);
+ fabrik_transforms[0] = origin_global_pose;
for (uint32_t i = 0; i < fabrik_data_chain.size() - 1; i++) {
- int current_bone_idx = fabrik_data_chain[i].bone_idx;
- Transform3D current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, stack->skeleton->get_bone_local_pose_override(current_bone_idx));
- int next_bone_idx = fabrik_data_chain[i + 1].bone_idx;
- Transform3D next_bone_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx));
+ Transform3D current_trans = fabrik_transforms[i];
+ Transform3D next_bone_trans = fabrik_transforms[i + 1];
real_t length = fabrik_data_chain[i].length / (next_bone_trans.origin.distance_to(current_trans.origin));
next_bone_trans.origin = current_trans.origin.lerp(next_bone_trans.origin, length);
- // Apply it back to the skeleton
- stack->skeleton->set_bone_local_pose_override(next_bone_idx, stack->skeleton->global_pose_to_local_pose(next_bone_idx, next_bone_trans), stack->strength, true);
+ // Save the result
+ fabrik_transforms[i + 1] = next_bone_trans;
}
}
void SkeletonModification3DFABRIK::chain_apply() {
for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) {
int current_bone_idx = fabrik_data_chain[i].bone_idx;
- Transform3D current_trans = stack->skeleton->get_bone_local_pose_override(current_bone_idx);
- current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, current_trans);
+ Transform3D current_trans = fabrik_transforms[i];
// If this is the last bone in the chain...
if (i == fabrik_data_chain.size() - 1) {
@@ -280,8 +276,7 @@ void SkeletonModification3DFABRIK::chain_apply() {
current_trans.basis = target_global_pose.basis.orthonormalized().scaled(current_trans.basis.get_scale());
}
} else { // every other bone in the chain...
- int next_bone_idx = fabrik_data_chain[i + 1].bone_idx;
- Transform3D next_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx));
+ Transform3D next_trans = fabrik_transforms[i + 1];
// Get the forward direction that the basis is facing in right now.
stack->skeleton->update_bone_rest_forward_vector(current_bone_idx);
@@ -290,9 +285,7 @@ void SkeletonModification3DFABRIK::chain_apply() {
current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(next_trans.origin));
current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll);
}
- current_trans = stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans);
- current_trans.origin = Vector3(0, 0, 0);
- stack->skeleton->set_bone_local_pose_override(current_bone_idx, current_trans, stack->strength, true);
+ stack->skeleton->set_bone_local_pose_override(current_bone_idx, stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans), stack->strength, true);
}
// Update all the bones so the next modification has up-to-date data.
@@ -374,6 +367,7 @@ int SkeletonModification3DFABRIK::get_fabrik_data_chain_length() {
void SkeletonModification3DFABRIK::set_fabrik_data_chain_length(int p_length) {
ERR_FAIL_COND(p_length < 0);
fabrik_data_chain.resize(p_length);
+ fabrik_transforms.resize(p_length);
execution_error_found = false;
notify_property_list_changed();
}
@@ -513,8 +507,11 @@ void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_join
Transform3D node_trans = tip_node->get_global_transform();
node_trans = stack->skeleton->world_transform_to_global_pose(node_trans);
- node_trans = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, node_trans);
- fabrik_data_chain[p_joint_idx].length = node_trans.origin.length();
+ //node_trans = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, node_trans);
+ //fabrik_data_chain[p_joint_idx].length = node_trans.origin.length();
+
+ fabrik_data_chain[p_joint_idx].length = stack->skeleton->get_bone_global_pose(fabrik_data_chain[p_joint_idx].bone_idx).origin.distance_to(node_trans.origin);
+
} else { // Use child bone(s) to update joint length, if possible
Vector<int> bone_children = stack->skeleton->get_bone_children(fabrik_data_chain[p_joint_idx].bone_idx);
if (bone_children.size() <= 0) {
@@ -522,10 +519,13 @@ void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_join
return;
}
+ Transform3D bone_trans = stack->skeleton->get_bone_global_pose(fabrik_data_chain[p_joint_idx].bone_idx);
+
real_t final_length = 0;
for (int i = 0; i < bone_children.size(); i++) {
Transform3D child_transform = stack->skeleton->get_bone_global_pose(bone_children[i]);
- final_length += stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length();
+ final_length += bone_trans.origin.distance_to(child_transform.origin);
+ //final_length += stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length();
}
fabrik_data_chain[p_joint_idx].length = final_length / bone_children.size();
}
diff --git a/scene/resources/skeleton_modification_3d_fabrik.h b/scene/resources/skeleton_modification_3d_fabrik.h
index 9b5da883d4..6c58b8a07a 100644
--- a/scene/resources/skeleton_modification_3d_fabrik.h
+++ b/scene/resources/skeleton_modification_3d_fabrik.h
@@ -55,6 +55,8 @@ private:
};
LocalVector<FABRIK_Joint_Data> fabrik_data_chain;
+ LocalVector<Transform3D> fabrik_transforms;
+
NodePath target_node;
ObjectID target_node_cache;
diff --git a/scene/resources/skeleton_modification_3d_jiggle.cpp b/scene/resources/skeleton_modification_3d_jiggle.cpp
index 1fb7dad2ad..2535f2b987 100644
--- a/scene/resources/skeleton_modification_3d_jiggle.cpp
+++ b/scene/resources/skeleton_modification_3d_jiggle.cpp
@@ -172,7 +172,12 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D
return;
}
- Transform3D new_bone_trans = stack->skeleton->local_pose_to_global_pose(jiggle_data_chain[p_joint_idx].bone_idx, stack->skeleton->get_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx));
+ Transform3D bone_local_pos = stack->skeleton->get_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx);
+ if (bone_local_pos == Transform3D()) {
+ bone_local_pos = stack->skeleton->get_bone_pose(jiggle_data_chain[p_joint_idx].bone_idx);
+ }
+
+ Transform3D new_bone_trans = stack->skeleton->local_pose_to_global_pose(jiggle_data_chain[p_joint_idx].bone_idx, bone_local_pos);
Vector3 target_position = stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform()).origin;
jiggle_data_chain[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta;
@@ -201,8 +206,12 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D
Transform3D new_bone_trans_world = stack->skeleton->global_pose_to_world_transform(new_bone_trans);
Transform3D dynamic_position_world = stack->skeleton->global_pose_to_world_transform(Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position));
- bool ray_hit = space_state->intersect_ray(new_bone_trans_world.origin, dynamic_position_world.get_origin(),
- ray_result, Set<RID>(), collision_mask);
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = new_bone_trans_world.origin;
+ ray_params.to = dynamic_position_world.get_origin();
+ ray_params.collision_mask = collision_mask;
+
+ bool ray_hit = space_state->intersect_ray(ray_params, ray_result);
if (ray_hit) {
jiggle_data_chain[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
diff --git a/scene/resources/skeleton_modification_3d_lookat.cpp b/scene/resources/skeleton_modification_3d_lookat.cpp
index afdb077e71..f3b0f41d60 100644
--- a/scene/resources/skeleton_modification_3d_lookat.cpp
+++ b/scene/resources/skeleton_modification_3d_lookat.cpp
@@ -96,8 +96,10 @@ void SkeletonModification3DLookAt::_execute(real_t p_delta) {
if (_print_execution_error(bone_idx <= -1, "Bone index is invalid. Cannot execute modification!")) {
return;
}
-
Transform3D new_bone_trans = stack->skeleton->get_bone_local_pose_override(bone_idx);
+ if (new_bone_trans == Transform3D()) {
+ new_bone_trans = stack->skeleton->get_bone_pose(bone_idx);
+ }
Vector3 target_pos = stack->skeleton->global_pose_to_local_pose(bone_idx, stack->skeleton->world_transform_to_global_pose(target->get_global_transform())).origin;
// Lock the rotation to a plane relative to the bone by changing the target position
diff --git a/scene/resources/skeleton_modification_3d_twoboneik.cpp b/scene/resources/skeleton_modification_3d_twoboneik.cpp
index ae7a3bab7e..93ec155a88 100644
--- a/scene/resources/skeleton_modification_3d_twoboneik.cpp
+++ b/scene/resources/skeleton_modification_3d_twoboneik.cpp
@@ -178,7 +178,16 @@ void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) {
}
Transform3D pole_trans = stack->skeleton->world_transform_to_global_pose(pole->get_global_transform());
- bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx));
+ Transform3D bone_one_local_pos = stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx);
+ if (bone_one_local_pos == Transform3D()) {
+ bone_one_local_pos = stack->skeleton->get_bone_pose(joint_one_bone_idx);
+ }
+ Transform3D bone_two_local_pos = stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx);
+ if (bone_two_local_pos == Transform3D()) {
+ bone_two_local_pos = stack->skeleton->get_bone_pose(joint_two_bone_idx);
+ }
+
+ bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, bone_one_local_pos);
bone_one_trans = bone_one_trans.looking_at(pole_trans.origin, Vector3(0, 1, 0));
bone_one_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_one_bone_idx, bone_one_trans.basis);
stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx);
@@ -186,7 +195,7 @@ void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) {
stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans), stack->strength, true);
stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx);
- bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx));
+ bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, bone_two_local_pos);
bone_two_trans = bone_two_trans.looking_at(target_trans.origin, Vector3(0, 1, 0));
bone_two_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_two_bone_idx, bone_two_trans.basis);
stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx);
@@ -194,8 +203,17 @@ void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) {
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans), stack->strength, true);
stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx);
} else {
- bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx));
- bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx));
+ Transform3D bone_one_local_pos = stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx);
+ if (bone_one_local_pos == Transform3D()) {
+ bone_one_local_pos = stack->skeleton->get_bone_pose(joint_one_bone_idx);
+ }
+ Transform3D bone_two_local_pos = stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx);
+ if (bone_two_local_pos == Transform3D()) {
+ bone_two_local_pos = stack->skeleton->get_bone_pose(joint_two_bone_idx);
+ }
+
+ bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, bone_one_local_pos);
+ bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, bone_two_local_pos);
}
Transform3D bone_two_tip_trans;
@@ -455,7 +473,7 @@ void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() {
joint_two_length = 0;
for (int i = 0; i < bone_two_children.size(); i++) {
joint_two_length += bone_two_rest_trans.origin.distance_to(
- stack->skeleton->local_pose_to_global_pose(bone_two_children[i], stack->skeleton->get_bone_rest(bone_two_children[i])).origin);
+ stack->skeleton->get_bone_global_pose(bone_two_children[i]).origin);
}
joint_two_length = joint_two_length / bone_two_children.size();
} else {
diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp
index db9fe62b4d..25c3e9d2ef 100644
--- a/scene/resources/skeleton_modification_stack_2d.cpp
+++ b/scene/resources/skeleton_modification_stack_2d.cpp
@@ -195,6 +195,7 @@ void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonMo
}
void SkeletonModificationStack2D::set_modification_count(int p_count) {
+ ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero.");
modifications.resize(p_count);
notify_property_list_changed();
diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp
index a724b732b9..301811f0b6 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);
}
@@ -148,6 +149,7 @@ void SkeletonModificationStack3D::set_modification(int p_mod_idx, Ref<SkeletonMo
}
void SkeletonModificationStack3D::set_modification_count(int p_count) {
+ ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero.");
modifications.resize(p_count);
notify_property_list_changed();
}
diff --git a/scene/resources/skin.cpp b/scene/resources/skin.cpp
index 710612ae05..15cdb86bab 100644
--- a/scene/resources/skin.cpp
+++ b/scene/resources/skin.cpp
@@ -133,7 +133,7 @@ void Skin::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, "bind_count", PROPERTY_HINT_RANGE, "0,16384,1,or_greater"));
for (int i = 0; i < get_bind_count(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bind/" + itos(i) + "/name"));
- p_list->push_back(PropertyInfo(Variant::INT, "bind/" + itos(i) + "/bone", PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NOEDITOR : PROPERTY_USAGE_DEFAULT));
+ p_list->push_back(PropertyInfo(Variant::INT, "bind/" + itos(i) + "/bone", PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT));
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, "bind/" + itos(i) + "/pose"));
}
}
diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp
index 01afb00283..5524d59dc7 100644
--- a/scene/resources/sprite_frames.cpp
+++ b/scene/resources/sprite_frames.cpp
@@ -233,7 +233,7 @@ void SpriteFrames::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_animations"), &SpriteFrames::_set_animations);
ClassDB::bind_method(D_METHOD("_get_animations"), &SpriteFrames::_get_animations);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); //compatibility
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); //compatibility
}
SpriteFrames::SpriteFrames() {
diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp
index 3381043d29..b960944d99 100644
--- a/scene/resources/style_box.cpp
+++ b/scene/resources/style_box.cpp
@@ -790,7 +790,7 @@ float StyleBoxFlat::get_style_margin(Side p_side) const {
void StyleBoxFlat::_validate_property(PropertyInfo &property) const {
if (!anti_aliased && property.name == "anti_aliasing_size") {
- property.usage = PROPERTY_USAGE_NOEDITOR;
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index a8cd872408..455af8a40c 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -1174,9 +1174,11 @@ Vector<int> SurfaceTool::generate_lod(float p_threshold, int p_target_index_coun
Vector<int> lod;
ERR_FAIL_COND_V(simplify_func == nullptr, lod);
+ ERR_FAIL_COND_V(p_target_index_count < 0, lod);
ERR_FAIL_COND_V(vertex_array.size() == 0, lod);
ERR_FAIL_COND_V(index_array.size() == 0, lod);
ERR_FAIL_COND_V(index_array.size() % 3 != 0, lod);
+ ERR_FAIL_COND_V(index_array.size() < (unsigned int)p_target_index_count, lod);
lod.resize(index_array.size());
LocalVector<float> vertices; //uses floats
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index b2e18e2451..1b7fc64267 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -38,6 +38,11 @@ void TextParagraph::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "direction", PROPERTY_HINT_ENUM, "Auto,Light-to-right,Right-to-left"), "set_direction", "get_direction");
+ ClassDB::bind_method(D_METHOD("set_custom_punctuation", "custom_punctuation"), &TextParagraph::set_custom_punctuation);
+ ClassDB::bind_method(D_METHOD("get_custom_punctuation"), &TextParagraph::get_custom_punctuation);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_punctuation"), "set_custom_punctuation", "get_custom_punctuation");
+
ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &TextParagraph::set_orientation);
ClassDB::bind_method(D_METHOD("get_orientation"), &TextParagraph::get_orientation);
@@ -84,7 +89,7 @@ void TextParagraph::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width"), "set_width", "get_width");
- ClassDB::bind_method(D_METHOD("get_non_wraped_size"), &TextParagraph::get_non_wraped_size);
+ ClassDB::bind_method(D_METHOD("get_non_wrapped_size"), &TextParagraph::get_non_wrapped_size);
ClassDB::bind_method(D_METHOD("get_size"), &TextParagraph::get_size);
ClassDB::bind_method(D_METHOD("get_rid"), &TextParagraph::get_rid);
@@ -304,6 +309,15 @@ TextServer::Direction TextParagraph::get_direction() const {
return TS->shaped_text_get_direction(rid);
}
+void TextParagraph::set_custom_punctuation(const String &p_punct) {
+ TS->shaped_text_set_custom_punctuation(rid, p_punct);
+ lines_dirty = true;
+}
+
+String TextParagraph::get_custom_punctuation() const {
+ return TS->shaped_text_get_custom_punctuation(rid);
+}
+
void TextParagraph::set_orientation(TextServer::Orientation p_orientation) {
TS->shaped_text_set_orientation(rid, p_orientation);
TS->shaped_text_set_orientation(dropcap_rid, p_orientation);
@@ -417,7 +431,7 @@ float TextParagraph::get_width() const {
return width;
}
-Size2 TextParagraph::get_non_wraped_size() const {
+Size2 TextParagraph::get_non_wrapped_size() const {
const_cast<TextParagraph *>(this)->_shape_lines();
if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) {
return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y + spacing_top + spacing_bottom);
diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h
index 69c50559df..4c4af43d14 100644
--- a/scene/resources/text_paragraph.h
+++ b/scene/resources/text_paragraph.h
@@ -96,6 +96,9 @@ public:
void set_bidi_override(const Array &p_override);
+ void set_custom_punctuation(const String &p_punct);
+ String get_custom_punctuation() const;
+
bool set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
void clear_dropcap();
@@ -120,7 +123,7 @@ public:
void set_max_lines_visible(int p_lines);
int get_max_lines_visible() const;
- Size2 get_non_wraped_size() const;
+ Size2 get_non_wrapped_size() const;
Size2 get_size() const;
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 1b3a7a5f2a..311bd9524b 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -32,6 +32,7 @@
#include "core/core_string_names.h"
#include "core/io/image_loader.h"
+#include "core/math/geometry_2d.h"
#include "core/os/os.h"
#include "mesh.h"
#include "scene/resources/bit_map.h"
@@ -1502,6 +1503,7 @@ Ref<Curve> CurveTexture::get_curve() const {
}
void CurveTexture::set_texture_mode(TextureMode p_mode) {
+ ERR_FAIL_COND(p_mode < TEXTURE_MODE_RGB || p_mode > TEXTURE_MODE_RED);
if (texture_mode == p_mode) {
return;
}
@@ -1727,53 +1729,53 @@ CurveXYZTexture::~CurveXYZTexture() {
//////////////////
-GradientTexture::GradientTexture() {
+GradientTexture1D::GradientTexture1D() {
_queue_update();
}
-GradientTexture::~GradientTexture() {
+GradientTexture1D::~GradientTexture1D() {
if (texture.is_valid()) {
RS::get_singleton()->free(texture);
}
}
-void GradientTexture::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture::set_gradient);
- ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture::get_gradient);
+void GradientTexture1D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture1D::set_gradient);
+ ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture1D::get_gradient);
- ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture::set_width);
+ ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture1D::set_width);
// The `get_width()` method is already exposed by the parent class Texture2D.
- ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture::set_use_hdr);
- ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture::is_using_hdr);
+ ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture1D::set_use_hdr);
+ ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture1D::is_using_hdr);
- ClassDB::bind_method(D_METHOD("_update"), &GradientTexture::_update);
+ ClassDB::bind_method(D_METHOD("_update"), &GradientTexture1D::_update);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient");
ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr");
}
-void GradientTexture::set_gradient(Ref<Gradient> p_gradient) {
+void GradientTexture1D::set_gradient(Ref<Gradient> p_gradient) {
if (p_gradient == gradient) {
return;
}
if (gradient.is_valid()) {
- gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture::_update));
+ gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update));
}
gradient = p_gradient;
if (gradient.is_valid()) {
- gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture::_update));
+ gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update));
}
_update();
emit_changed();
}
-Ref<Gradient> GradientTexture::get_gradient() const {
+Ref<Gradient> GradientTexture1D::get_gradient() const {
return gradient;
}
-void GradientTexture::_queue_update() {
+void GradientTexture1D::_queue_update() {
if (update_pending) {
return;
}
@@ -1782,7 +1784,7 @@ void GradientTexture::_queue_update() {
call_deferred(SNAME("_update"));
}
-void GradientTexture::_update() {
+void GradientTexture1D::_update() {
update_pending = false;
if (gradient.is_null()) {
@@ -1837,17 +1839,17 @@ void GradientTexture::_update() {
emit_changed();
}
-void GradientTexture::set_width(int p_width) {
+void GradientTexture1D::set_width(int p_width) {
ERR_FAIL_COND(p_width <= 0);
width = p_width;
_queue_update();
}
-int GradientTexture::get_width() const {
+int GradientTexture1D::get_width() const {
return width;
}
-void GradientTexture::set_use_hdr(bool p_enabled) {
+void GradientTexture1D::set_use_hdr(bool p_enabled) {
if (p_enabled == use_hdr) {
return;
}
@@ -1856,17 +1858,271 @@ void GradientTexture::set_use_hdr(bool p_enabled) {
_queue_update();
}
-bool GradientTexture::is_using_hdr() const {
+bool GradientTexture1D::is_using_hdr() const {
return use_hdr;
}
-Ref<Image> GradientTexture::get_image() const {
+Ref<Image> GradientTexture1D::get_image() const {
if (!texture.is_valid()) {
return Ref<Image>();
}
return RenderingServer::get_singleton()->texture_2d_get(texture);
}
+GradientTexture2D::GradientTexture2D() {
+ _queue_update();
+}
+
+GradientTexture2D::~GradientTexture2D() {
+ if (texture.is_valid()) {
+ RS::get_singleton()->free(texture);
+ }
+}
+
+void GradientTexture2D::set_gradient(Ref<Gradient> p_gradient) {
+ if (gradient == p_gradient) {
+ return;
+ }
+ if (gradient.is_valid()) {
+ gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture2D::_queue_update));
+ }
+ gradient = p_gradient;
+ if (gradient.is_valid()) {
+ gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture2D::_queue_update));
+ }
+ _queue_update();
+}
+
+Ref<Gradient> GradientTexture2D::get_gradient() const {
+ return gradient;
+}
+
+void GradientTexture2D::_queue_update() {
+ if (update_pending) {
+ return;
+ }
+ update_pending = true;
+ call_deferred("_update");
+}
+
+void GradientTexture2D::_update() {
+ update_pending = false;
+
+ if (gradient.is_null()) {
+ return;
+ }
+ Ref<Image> image;
+ image.instantiate();
+
+ if (gradient->get_points_count() <= 1) { // No need to interpolate.
+ image->create(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8);
+ image->fill((gradient->get_points_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1));
+ } else {
+ if (use_hdr) {
+ image->create(width, height, false, Image::FORMAT_RGBAF);
+ Gradient &g = **gradient;
+ // `create()` isn't available for non-uint8_t data, so fill in the data manually.
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ float ofs = _get_gradient_offset_at(x, y);
+ image->set_pixel(x, y, g.get_color_at_offset(ofs));
+ }
+ }
+ } else {
+ Vector<uint8_t> data;
+ data.resize(width * height * 4);
+ {
+ uint8_t *wd8 = data.ptrw();
+ Gradient &g = **gradient;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ float ofs = _get_gradient_offset_at(x, y);
+ const Color &c = g.get_color_at_offset(ofs);
+
+ wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255));
+ wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255));
+ wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255));
+ wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255));
+ }
+ }
+ }
+ image->create(width, height, false, Image::FORMAT_RGBA8, data);
+ }
+ }
+
+ if (texture.is_valid()) {
+ RID new_texture = RS::get_singleton()->texture_2d_create(image);
+ RS::get_singleton()->texture_replace(texture, new_texture);
+ } else {
+ texture = RS::get_singleton()->texture_2d_create(image);
+ }
+ emit_changed();
+}
+
+float GradientTexture2D::_get_gradient_offset_at(int x, int y) const {
+ if (fill_to == fill_from) {
+ return 0;
+ }
+ float ofs = 0;
+ Vector2 pos;
+ if (width > 1) {
+ pos.x = static_cast<float>(x) / (width - 1);
+ }
+ if (height > 1) {
+ pos.y = static_cast<float>(y) / (height - 1);
+ }
+ if (fill == Fill::FILL_LINEAR) {
+ Vector2 segment[2];
+ segment[0] = fill_from;
+ segment[1] = fill_to;
+ Vector2 closest = Geometry2D::get_closest_point_to_segment_uncapped(pos, &segment[0]);
+ ofs = (closest - fill_from).length() / (fill_to - fill_from).length();
+ if ((closest - fill_from).dot(fill_to - fill_from) < 0) {
+ ofs *= -1;
+ }
+ } else if (fill == Fill::FILL_RADIAL) {
+ ofs = (pos - fill_from).length() / (fill_to - fill_from).length();
+ }
+ if (repeat == Repeat::REPEAT_NONE) {
+ ofs = CLAMP(ofs, 0.0, 1.0);
+ } else if (repeat == Repeat::REPEAT) {
+ ofs = Math::fmod(ofs, 1.0f);
+ if (ofs < 0) {
+ ofs = 1 + ofs;
+ }
+ } else if (repeat == Repeat::REPEAT_MIRROR) {
+ ofs = Math::abs(ofs);
+ ofs = Math::fmod(ofs, 2.0f);
+ if (ofs > 1.0) {
+ ofs = 2.0 - ofs;
+ }
+ }
+ return ofs;
+}
+
+void GradientTexture2D::set_width(int p_width) {
+ width = p_width;
+ _queue_update();
+}
+
+int GradientTexture2D::get_width() const {
+ return width;
+}
+
+void GradientTexture2D::set_height(int p_height) {
+ height = p_height;
+ _queue_update();
+}
+int GradientTexture2D::get_height() const {
+ return height;
+}
+
+void GradientTexture2D::set_use_hdr(bool p_enabled) {
+ if (p_enabled == use_hdr) {
+ return;
+ }
+
+ use_hdr = p_enabled;
+ _queue_update();
+}
+
+bool GradientTexture2D::is_using_hdr() const {
+ return use_hdr;
+}
+
+void GradientTexture2D::set_fill_from(Vector2 p_fill_from) {
+ fill_from = p_fill_from;
+ _queue_update();
+}
+
+Vector2 GradientTexture2D::get_fill_from() const {
+ return fill_from;
+}
+
+void GradientTexture2D::set_fill_to(Vector2 p_fill_to) {
+ fill_to = p_fill_to;
+ _queue_update();
+}
+
+Vector2 GradientTexture2D::get_fill_to() const {
+ return fill_to;
+}
+
+void GradientTexture2D::set_fill(Fill p_fill) {
+ fill = p_fill;
+ _queue_update();
+}
+
+GradientTexture2D::Fill GradientTexture2D::get_fill() const {
+ return fill;
+}
+
+void GradientTexture2D::set_repeat(Repeat p_repeat) {
+ repeat = p_repeat;
+ _queue_update();
+}
+
+GradientTexture2D::Repeat GradientTexture2D::get_repeat() const {
+ return repeat;
+}
+
+RID GradientTexture2D::get_rid() const {
+ if (!texture.is_valid()) {
+ texture = RS::get_singleton()->texture_2d_placeholder_create();
+ }
+ return texture;
+}
+
+Ref<Image> GradientTexture2D::get_image() const {
+ if (!texture.is_valid()) {
+ return Ref<Image>();
+ }
+ return RenderingServer::get_singleton()->texture_2d_get(texture);
+}
+
+void GradientTexture2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture2D::set_gradient);
+ ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture2D::get_gradient);
+
+ ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture2D::set_width);
+ ClassDB::bind_method(D_METHOD("set_height", "height"), &GradientTexture2D::set_height);
+
+ ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture2D::set_use_hdr);
+ ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture2D::is_using_hdr);
+
+ ClassDB::bind_method(D_METHOD("set_fill", "fill"), &GradientTexture2D::set_fill);
+ ClassDB::bind_method(D_METHOD("get_fill"), &GradientTexture2D::get_fill);
+ ClassDB::bind_method(D_METHOD("set_fill_from", "fill_from"), &GradientTexture2D::set_fill_from);
+ ClassDB::bind_method(D_METHOD("get_fill_from"), &GradientTexture2D::get_fill_from);
+ ClassDB::bind_method(D_METHOD("set_fill_to", "fill_to"), &GradientTexture2D::set_fill_to);
+ ClassDB::bind_method(D_METHOD("get_fill_to"), &GradientTexture2D::get_fill_to);
+
+ ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &GradientTexture2D::set_repeat);
+ ClassDB::bind_method(D_METHOD("get_repeat"), &GradientTexture2D::get_repeat);
+
+ ClassDB::bind_method(D_METHOD("_update"), &GradientTexture2D::_update);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_width", "get_width");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr");
+
+ ADD_GROUP("Fill", "fill_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill", PROPERTY_HINT_ENUM, "Linear,Radial"), "set_fill", "get_fill");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_from"), "set_fill_from", "get_fill_from");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_to"), "set_fill_to", "get_fill_to");
+
+ ADD_GROUP("Repeat", "repeat_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat", PROPERTY_HINT_ENUM, "No Repeat,Repeat,Mirror Repeat"), "set_repeat", "get_repeat");
+
+ BIND_ENUM_CONSTANT(FILL_LINEAR);
+ BIND_ENUM_CONSTANT(FILL_RADIAL);
+
+ BIND_ENUM_CONSTANT(REPEAT_NONE);
+ BIND_ENUM_CONSTANT(REPEAT);
+ BIND_ENUM_CONSTANT(REPEAT_MIRROR);
+}
+
//////////////////////////////////////
void ProxyTexture::_bind_methods() {
@@ -2615,15 +2871,6 @@ RID CameraTexture::get_rid() const {
}
}
-void CameraTexture::set_flags(uint32_t p_flags) {
- // not supported
-}
-
-uint32_t CameraTexture::get_flags() const {
- // not supported
- return 0;
-}
-
Ref<Image> CameraTexture::get_image() const {
// not (yet) supported
return Ref<Image>();
diff --git a/scene/resources/texture.h b/scene/resources/texture.h
index 862b9a47a6..5b69711de6 100644
--- a/scene/resources/texture.h
+++ b/scene/resources/texture.h
@@ -669,8 +669,8 @@ public:
~CurveXYZTexture();
};
-class GradientTexture : public Texture2D {
- GDCLASS(GradientTexture, Texture2D);
+class GradientTexture1D : public Texture2D {
+ GDCLASS(GradientTexture1D, Texture2D);
public:
struct Point {
@@ -710,10 +710,81 @@ public:
virtual Ref<Image> get_image() const override;
- GradientTexture();
- virtual ~GradientTexture();
+ GradientTexture1D();
+ virtual ~GradientTexture1D();
};
+class GradientTexture2D : public Texture2D {
+ GDCLASS(GradientTexture2D, Texture2D);
+
+public:
+ enum Fill {
+ FILL_LINEAR,
+ FILL_RADIAL,
+ };
+ enum Repeat {
+ REPEAT_NONE,
+ REPEAT,
+ REPEAT_MIRROR,
+ };
+
+private:
+ Ref<Gradient> gradient;
+ mutable RID texture;
+
+ int width = 64;
+ int height = 64;
+
+ bool use_hdr = false;
+
+ Vector2 fill_from;
+ Vector2 fill_to = Vector2(1, 0);
+
+ Fill fill = FILL_LINEAR;
+ Repeat repeat = REPEAT_NONE;
+
+ float _get_gradient_offset_at(int x, int y) const;
+
+ bool update_pending = false;
+ void _queue_update();
+ void _update();
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_gradient(Ref<Gradient> p_gradient);
+ Ref<Gradient> get_gradient() const;
+
+ void set_width(int p_width);
+ virtual int get_width() const override;
+ void set_height(int p_height);
+ virtual int get_height() const override;
+
+ void set_use_hdr(bool p_enabled);
+ bool is_using_hdr() const;
+
+ void set_fill(Fill p_fill);
+ Fill get_fill() const;
+ void set_fill_from(Vector2 p_fill_from);
+ Vector2 get_fill_from() const;
+ void set_fill_to(Vector2 p_fill_to);
+ Vector2 get_fill_to() const;
+
+ void set_repeat(Repeat p_repeat);
+ Repeat get_repeat() const;
+
+ virtual RID get_rid() const override;
+ virtual bool has_alpha() const override { return true; }
+ virtual Ref<Image> get_image() const override;
+
+ GradientTexture2D();
+ virtual ~GradientTexture2D();
+};
+
+VARIANT_ENUM_CAST(GradientTexture2D::Fill);
+VARIANT_ENUM_CAST(GradientTexture2D::Repeat);
+
class ProxyTexture : public Texture2D {
GDCLASS(ProxyTexture, Texture2D);
@@ -829,9 +900,6 @@ public:
virtual RID get_rid() const override;
virtual bool has_alpha() const override;
- virtual void set_flags(uint32_t p_flags);
- virtual uint32_t get_flags() const;
-
virtual Ref<Image> get_image() const override;
void set_camera_feed_id(int p_new_id);
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index 65cdc1e24e..99977a20f2 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -169,7 +169,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const {
const StringName *key2 = nullptr;
while ((key2 = font_size_map[*key].next(key2))) {
- list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2));
+ list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2, PROPERTY_HINT_RANGE, "0,256,1,or_greater"));
}
}
@@ -1655,7 +1655,7 @@ void Theme::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_base_scale", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,or_greater"), "set_default_base_scale", "get_default_base_scale");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size", PROPERTY_HINT_RANGE, "0,256,1,or_greater"), "set_default_font_size", "get_default_font_size");
BIND_ENUM_CONSTANT(DATA_TYPE_COLOR);
BIND_ENUM_CONSTANT(DATA_TYPE_CONSTANT);
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index e19ca38b82..8c92445338 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -31,6 +31,7 @@
#include "tile_set.h"
#include "core/core_string_names.h"
+#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/templates/local_vector.h"
@@ -39,8 +40,275 @@
#include "scene/resources/convex_polygon_shape_2d.h"
#include "servers/navigation_server_2d.h"
+/////////////////////////////// TileMapPattern //////////////////////////////////////
+
+void TileMapPattern::_set_tile_data(const Vector<int> &p_data) {
+ int c = p_data.size();
+ const int *r = p_data.ptr();
+
+ int offset = 3;
+ ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data.");
+
+ clear();
+
+ for (int i = 0; i < c; i += offset) {
+ const uint8_t *ptr = (const uint8_t *)&r[i];
+ uint8_t local[12];
+ for (int j = 0; j < 12; j++) {
+ local[j] = ptr[j];
+ }
+
+#ifdef BIG_ENDIAN_ENABLED
+ SWAP(local[0], local[3]);
+ SWAP(local[1], local[2]);
+ SWAP(local[4], local[7]);
+ SWAP(local[5], local[6]);
+ SWAP(local[8], local[11]);
+ SWAP(local[9], local[10]);
+#endif
+
+ int16_t x = decode_uint16(&local[0]);
+ int16_t y = decode_uint16(&local[2]);
+ uint16_t source_id = decode_uint16(&local[4]);
+ uint16_t atlas_coords_x = decode_uint16(&local[6]);
+ uint16_t atlas_coords_y = decode_uint16(&local[8]);
+ uint16_t alternative_tile = decode_uint16(&local[10]);
+ set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
+ }
+ emit_signal(SNAME("changed"));
+}
+
+Vector<int> TileMapPattern::_get_tile_data() const {
+ // Export tile data to raw format
+ Vector<int> data;
+ data.resize(pattern.size() * 3);
+ int *w = data.ptrw();
+
+ // Save in highest format
+
+ int idx = 0;
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ uint8_t *ptr = (uint8_t *)&w[idx];
+ encode_uint16((int16_t)(E.key.x), &ptr[0]);
+ encode_uint16((int16_t)(E.key.y), &ptr[2]);
+ encode_uint16(E.value.source_id, &ptr[4]);
+ encode_uint16(E.value.coord_x, &ptr[6]);
+ encode_uint16(E.value.coord_y, &ptr[8]);
+ encode_uint16(E.value.alternative_tile, &ptr[10]);
+ idx += 3;
+ }
+
+ return data;
+}
+
+void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords));
+
+ size = size.max(p_coords + Vector2i(1, 1));
+ pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile);
+ emit_changed();
+}
+
+bool TileMapPattern::has_cell(const Vector2i &p_coords) const {
+ return pattern.has(p_coords);
+}
+
+void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) {
+ ERR_FAIL_COND(!pattern.has(p_coords));
+
+ pattern.erase(p_coords);
+ if (p_update_size) {
+ size = Vector2i();
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ size = size.max(E.key + Vector2i(1, 1));
+ }
+ }
+ emit_changed();
+}
+
+int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE);
+
+ return pattern[p_coords].source_id;
+}
+
+Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
+
+ return pattern[p_coords].get_atlas_coords();
+}
+
+int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
+ ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
+
+ return pattern[p_coords].alternative_tile;
+}
+
+TypedArray<Vector2i> TileMapPattern::get_used_cells() const {
+ // Returns the cells used in the tilemap.
+ TypedArray<Vector2i> a;
+ a.resize(pattern.size());
+ int i = 0;
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ Vector2i p(E.key.x, E.key.y);
+ a[i++] = p;
+ }
+
+ return a;
+}
+
+Vector2i TileMapPattern::get_size() const {
+ return size;
+}
+
+void TileMapPattern::set_size(const Vector2i &p_size) {
+ for (const KeyValue<Vector2i, TileMapCell> &E : pattern) {
+ Vector2i coords = E.key;
+ if (p_size.x <= coords.x || p_size.y <= coords.y) {
+ ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords));
+ };
+ }
+
+ size = p_size;
+ emit_changed();
+}
+
+bool TileMapPattern::is_empty() const {
+ return pattern.is_empty();
+};
+
+void TileMapPattern::clear() {
+ size = Vector2i();
+ pattern.clear();
+ emit_changed();
+};
+
+bool TileMapPattern::_set(const StringName &p_name, const Variant &p_value) {
+ if (p_name == "tile_data") {
+ if (p_value.is_array()) {
+ _set_tile_data(p_value);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+bool TileMapPattern::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "tile_data") {
+ r_ret = _get_tile_data();
+ return true;
+ }
+ return false;
+}
+
+void TileMapPattern::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+}
+
+void TileMapPattern::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_set_tile_data", "data"), &TileMapPattern::_set_tile_data);
+ ClassDB::bind_method(D_METHOD("_get_tile_data"), &TileMapPattern::_get_tile_data);
+
+ ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
+ ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
+ ClassDB::bind_method(D_METHOD("remove_cell", "coords", "update_size"), &TileMapPattern::remove_cell);
+ ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
+ ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords);
+ ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile);
+
+ ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells);
+ ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size);
+ ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size);
+ ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty);
+}
+
/////////////////////////////// TileSet //////////////////////////////////////
+bool TileSet::TerrainsPattern::is_valid() const {
+ return valid;
+}
+
+bool TileSet::TerrainsPattern::is_erase_pattern() const {
+ return not_empty_terrains_count == 0;
+}
+
+bool TileSet::TerrainsPattern::operator<(const TerrainsPattern &p_terrains_pattern) const {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) {
+ return is_valid_bit[i] < p_terrains_pattern.is_valid_bit[i];
+ }
+ }
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) {
+ return bits[i] < p_terrains_pattern.bits[i];
+ }
+ }
+ return false;
+}
+
+bool TileSet::TerrainsPattern::operator==(const TerrainsPattern &p_terrains_pattern) const {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) {
+ return false;
+ }
+ if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void TileSet::TerrainsPattern::set_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain) {
+ ERR_FAIL_COND(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX);
+ ERR_FAIL_COND(!is_valid_bit[p_peering_bit]);
+ ERR_FAIL_COND(p_terrain < -1);
+
+ // Update the "is_erase_pattern" status.
+ if (p_terrain >= 0 && bits[p_peering_bit] < 0) {
+ not_empty_terrains_count++;
+ } else if (p_terrain < 0 && bits[p_peering_bit] >= 0) {
+ not_empty_terrains_count--;
+ }
+
+ bits[p_peering_bit] = p_terrain;
+}
+
+int TileSet::TerrainsPattern::get_terrain(TileSet::CellNeighbor p_peering_bit) const {
+ ERR_FAIL_COND_V(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX, -1);
+ ERR_FAIL_COND_V(!is_valid_bit[p_peering_bit], -1);
+ return bits[p_peering_bit];
+}
+
+void TileSet::TerrainsPattern::set_terrains_from_array(Array p_terrains) {
+ int in_array_index = 0;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (is_valid_bit[i]) {
+ ERR_FAIL_COND(in_array_index >= p_terrains.size());
+ set_terrain(TileSet::CellNeighbor(i), p_terrains[in_array_index]);
+ in_array_index++;
+ }
+ }
+}
+
+Array TileSet::TerrainsPattern::get_terrains_as_array() const {
+ Array output;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (is_valid_bit[i]) {
+ output.push_back(bits[i]);
+ }
+ }
+ return output;
+}
+TileSet::TerrainsPattern::TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set) {
+ ERR_FAIL_COND(p_terrain_set < 0);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ is_valid_bit[i] = (p_tile_set->is_valid_peering_bit_terrain(p_terrain_set, TileSet::CellNeighbor(i)));
+ bits[i] = -1;
+ }
+ valid = true;
+}
+
const int TileSet::INVALID_SOURCE = -1;
const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = {
@@ -117,6 +385,64 @@ 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 < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ CellNeighbor bit = CellNeighbor(i);
+ if (is_valid_peering_bit_terrain(terrain_set, bit)) {
+ int terrain = terrains_pattern.get_terrain(bit);
+ 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(this, i);
+
+ 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
@@ -137,6 +463,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;
@@ -152,6 +479,7 @@ void TileSet::remove_source(int p_source_id) {
source_ids.erase(p_source_id);
source_ids.sort();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -171,6 +499,9 @@ void TileSet::set_source_id(int p_source_id, int p_new_source_id) {
source_ids.append(p_new_source_id);
source_ids.sort();
+ _compute_next_source_id();
+
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -359,6 +690,7 @@ void TileSet::add_terrain_set(int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -371,6 +703,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();
}
@@ -381,6 +714,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();
}
@@ -392,6 +726,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();
}
@@ -426,6 +761,7 @@ void TileSet::add_terrain(int p_terrain_set, int p_index) {
}
notify_property_list_changed();
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -441,6 +777,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();
}
@@ -454,6 +791,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();
}
@@ -980,6 +1318,103 @@ void TileSet::clear_tile_proxies() {
emit_changed();
}
+int TileSet::add_pattern(Ref<TileMapPattern> p_pattern, int p_index) {
+ ERR_FAIL_COND_V(!p_pattern.is_valid(), -1);
+ ERR_FAIL_COND_V_MSG(p_pattern->is_empty(), -1, "Cannot add an empty pattern to the TileSet.");
+ for (unsigned int i = 0; i < patterns.size(); i++) {
+ ERR_FAIL_COND_V_MSG(patterns[i] == p_pattern, -1, "TileSet has already this pattern.");
+ }
+ ERR_FAIL_COND_V(p_index > (int)patterns.size(), -1);
+ if (p_index < 0) {
+ p_index = patterns.size();
+ }
+ patterns.insert(p_index, p_pattern);
+ emit_changed();
+ return p_index;
+}
+
+Ref<TileMapPattern> TileSet::get_pattern(int p_index) {
+ ERR_FAIL_INDEX_V(p_index, (int)patterns.size(), Ref<TileMapPattern>());
+ return patterns[p_index];
+}
+
+void TileSet::remove_pattern(int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)patterns.size());
+ patterns.remove(p_index);
+ emit_changed();
+}
+
+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_terrains_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) {
@@ -1294,6 +1729,7 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) {
}
void TileSet::_source_changed() {
+ terrains_cache_dirty = true;
emit_changed();
}
@@ -2481,6 +2917,12 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
return false;
+ } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
+ int pattern_index = components[0].trim_prefix("pattern_").to_int();
+ for (int i = patterns.size(); i <= pattern_index; i++) {
+ add_pattern(p_value);
+ }
+ return true;
}
#ifndef DISABLE_DEPRECATED
@@ -2604,6 +3046,13 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
return false;
+ } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) {
+ int pattern_index = components[0].trim_prefix("pattern_").to_int();
+ if (pattern_index < 0 || pattern_index >= (int)patterns.size()) {
+ return false;
+ }
+ r_ret = patterns[pattern_index];
+ return true;
}
return false;
@@ -2675,15 +3124,20 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
// Sources.
// Note: sources have to be listed in at the end as some TileData rely on the TileSet properties being initialized first.
for (const KeyValue<int, Ref<TileSetSource>> &E_source : sources) {
- p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
// Tile Proxies.
// Note: proxies need to be set after sources are set.
p_list->push_back(PropertyInfo(Variant::NIL, "Tile Proxies", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+
+ // Patterns.
+ for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("pattern_%d", pattern_index), PROPERTY_HINT_RESOURCE_TYPE, "TileMapPattern", PROPERTY_USAGE_NO_EDITOR));
+ }
}
void TileSet::_validate_property(PropertyInfo &property) const {
@@ -2797,6 +3251,12 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies);
ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies);
+ // Patterns
+ ClassDB::bind_method(D_METHOD("add_pattern", "pattern", "index"), &TileSet::add_pattern, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_pattern", "index"), &TileSet::get_pattern, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_pattern", "index"), &TileSet::remove_pattern);
+ ClassDB::bind_method(D_METHOD("get_patterns_count"), &TileSet::get_patterns_count);
+
ADD_GROUP("Rendering", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping");
ADD_ARRAY("occlusion_layers", "occlusion_layer_");
@@ -2899,6 +3359,10 @@ void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) {
}
}
+const TileSet *TileSetAtlasSource::get_tile_set() const {
+ return tile_set;
+}
+
void TileSetAtlasSource::notify_tile_data_properties_should_change() {
// Set the TileSet on all TileData.
for (KeyValue<Vector2i, TileAlternativesData> &E_tile : tiles) {
@@ -3062,8 +3526,18 @@ void TileSetAtlasSource::reset_state() {
}
void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) {
+ if (texture.is_valid()) {
+ texture->disconnect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture));
+ }
+
texture = p_texture;
+ if (texture.is_valid()) {
+ texture->connect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture));
+ }
+
+ _clear_tiles_outside_texture();
+ _queue_update_padded_texture();
emit_changed();
}
@@ -3079,8 +3553,11 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) {
margins = p_margins;
}
+ _clear_tiles_outside_texture();
+ _queue_update_padded_texture();
emit_changed();
}
+
Vector2i TileSetAtlasSource::get_margins() const {
return margins;
}
@@ -3093,8 +3570,11 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) {
separation = p_separation;
}
+ _clear_tiles_outside_texture();
+ _queue_update_padded_texture();
emit_changed();
}
+
Vector2i TileSetAtlasSource::get_separation() const {
return separation;
}
@@ -3107,12 +3587,28 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
texture_region_size = p_tile_size;
}
+ _clear_tiles_outside_texture();
+ _queue_update_padded_texture();
emit_changed();
}
+
Vector2i TileSetAtlasSource::get_texture_region_size() const {
return texture_region_size;
}
+void TileSetAtlasSource::set_use_texture_padding(bool p_use_padding) {
+ if (use_texture_padding == p_use_padding) {
+ return;
+ }
+ use_texture_padding = p_use_padding;
+ _queue_update_padded_texture();
+ emit_changed();
+}
+
+bool TileSetAtlasSource::get_use_texture_padding() const {
+ return use_texture_padding;
+}
+
Vector2i TileSetAtlasSource::get_atlas_grid_size() const {
Ref<Texture2D> texture = get_texture();
if (!texture.is_valid()) {
@@ -3273,35 +3769,35 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
List<PropertyInfo> tile_property_list;
// size_in_atlas
- property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (E_tile.value.size_in_atlas == Vector2i(1, 1)) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
tile_property_list.push_back(property_info);
// next_alternative_id
- property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (E_tile.value.next_alternative_id == 1) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
tile_property_list.push_back(property_info);
// animation_columns.
- property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (E_tile.value.animation_columns == 0) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
tile_property_list.push_back(property_info);
// animation_separation.
- property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (E_tile.value.animation_separation == Vector2i()) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
tile_property_list.push_back(property_info);
// animation_speed.
- property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (E_tile.value.animation_speed == 1.0) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
@@ -3313,7 +3809,7 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
// animation_frame_*.
bool store_durations = tiles[E_tile.key].animation_frames_durations.size() >= 2;
for (int i = 0; i < (int)tiles[E_tile.key].animation_frames_durations.size(); i++) {
- property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR);
+ property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
if (!store_durations) {
property_info.usage ^= PROPERTY_USAGE_STORAGE;
}
@@ -3322,7 +3818,7 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const {
for (const KeyValue<int, TileData *> &E_alternative : E_tile.value.alternatives) {
// Add a dummy property to show the alternative exists.
- tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
// Get the alternative tile's properties and append them to the list of properties.
List<PropertyInfo> alternative_property_list;
@@ -3352,7 +3848,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1);
- ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile, tiles are already present in the space the tile would cover.");
+ ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile. The tile is outside the texture or tiles are already present in the space the tile would cover.");
// Initialize the tile data.
TileAlternativesData tad;
@@ -3371,6 +3867,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
tiles_ids.sort();
_create_coords_mapping_cache(p_atlas_coords);
+ _queue_update_padded_texture();
emit_signal(SNAME("changed"));
}
@@ -3391,6 +3888,8 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) {
tiles_ids.erase(p_atlas_coords);
tiles_ids.sort();
+ _queue_update_padded_texture();
+
emit_signal(SNAME("changed"));
}
@@ -3419,6 +3918,7 @@ void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coord
tiles[p_atlas_coords].animation_columns = p_frame_columns;
_create_coords_mapping_cache(p_atlas_coords);
+ _queue_update_padded_texture();
emit_signal(SNAME("changed"));
}
@@ -3441,6 +3941,7 @@ void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_co
tiles[p_atlas_coords].animation_separation = p_separation;
_create_coords_mapping_cache(p_atlas_coords);
+ _queue_update_padded_texture();
emit_signal(SNAME("changed"));
}
@@ -3468,19 +3969,24 @@ void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_
ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
ERR_FAIL_COND(p_frames_count < 1);
+ int old_size = tiles[p_atlas_coords].animation_frames_durations.size();
+ if (p_frames_count == old_size) {
+ return;
+ }
+
TileAlternativesData &tad = tiles[p_atlas_coords];
bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords);
ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover.");
_clear_coords_mapping_cache(p_atlas_coords);
- int old_size = tiles[p_atlas_coords].animation_frames_durations.size();
tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count);
for (int i = old_size; i < p_frames_count; i++) {
tiles[p_atlas_coords].animation_frames_durations[i] = 1.0;
}
_create_coords_mapping_cache(p_atlas_coords);
+ _queue_update_padded_texture();
notify_property_list_changed();
@@ -3550,9 +4056,7 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s
return false;
}
if (coords.x >= atlas_grid_size.x || coords.y >= atlas_grid_size.y) {
- if (!(_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] == p_ignored_tile)) {
- return false; // Only accept tiles outside the atlas if they are part of the ignored tile.
- }
+ return false;
}
}
}
@@ -3560,6 +4064,37 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s
return true;
}
+PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref<Texture2D> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) {
+ ERR_FAIL_COND_V(p_margins.x < 0 || p_margins.y < 0, PackedVector2Array());
+ ERR_FAIL_COND_V(p_separation.x < 0 || p_separation.y < 0, PackedVector2Array());
+ ERR_FAIL_COND_V(p_texture_region_size.x <= 0 || p_texture_region_size.y <= 0, PackedVector2Array());
+
+ // Compute the new atlas grid size.
+ Size2 new_grid_size;
+ if (p_texture.is_valid()) {
+ Size2i valid_area = p_texture->get_size() - p_margins;
+
+ // Compute the number of valid tiles in the tiles atlas
+ if (valid_area.x >= p_texture_region_size.x && valid_area.y >= p_texture_region_size.y) {
+ valid_area -= p_texture_region_size;
+ new_grid_size = Size2i(1, 1) + valid_area / (p_texture_region_size + p_separation);
+ }
+ }
+
+ Vector<Vector2> output;
+ for (KeyValue<Vector2i, TileAlternativesData> &E : tiles) {
+ for (unsigned int frame = 0; frame < E.value.animation_frames_durations.size(); frame++) {
+ Vector2i frame_coords = E.key + (E.value.size_in_atlas + E.value.animation_separation) * ((E.value.animation_columns > 0) ? Vector2i(frame % E.value.animation_columns, frame / E.value.animation_columns) : Vector2i(frame, 0));
+ frame_coords += E.value.size_in_atlas;
+ if (frame_coords.x > new_grid_size.x || frame_coords.y > new_grid_size.y) {
+ output.push_back(E.key);
+ break;
+ }
+ }
+ }
+ return output;
+}
+
Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const {
ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i());
@@ -3590,6 +4125,31 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_
return effective_texture_offset;
}
+// Getters for texture and tile region (padded or not)
+Ref<Texture2D> TileSetAtlasSource::get_runtime_texture() const {
+ if (use_texture_padding) {
+ return padded_texture;
+ } else {
+ return texture;
+ }
+}
+
+Rect2i TileSetAtlasSource::get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const {
+ ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
+ ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i());
+
+ Rect2i src_rect = get_tile_texture_region(p_atlas_coords, p_frame);
+ if (use_texture_padding) {
+ const TileAlternativesData &tad = tiles[p_atlas_coords];
+ Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0));
+ Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1);
+
+ return Rect2i(base_pos, src_rect.size);
+ } else {
+ return src_rect;
+ }
+}
+
void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) {
ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
@@ -3620,38 +4180,11 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_
tiles[new_atlas_coords].size_in_atlas = new_size;
_create_coords_mapping_cache(new_atlas_coords);
+ _queue_update_padded_texture();
emit_signal(SNAME("changed"));
}
-bool TileSetAtlasSource::has_tiles_outside_texture() {
- Vector2i grid_size = get_atlas_grid_size();
- Vector<Vector2i> to_remove;
-
- for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
- if (E.key.x >= grid_size.x || E.key.y >= grid_size.y) {
- return true;
- }
- }
-
- return false;
-}
-
-void TileSetAtlasSource::clear_tiles_outside_texture() {
- Vector2i grid_size = get_atlas_grid_size();
- Vector<Vector2i> to_remove;
-
- for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
- if (E.key.x >= grid_size.x || E.key.y >= grid_size.y) {
- to_remove.append(E.key);
- }
- }
-
- for (int i = 0; i < to_remove.size(); i++) {
- remove_tile(to_remove[i]);
- }
-}
-
int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) {
ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override));
@@ -3739,11 +4272,14 @@ void TileSetAtlasSource::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation);
ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size);
ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size);
+ ClassDB::bind_method(D_METHOD("set_use_texture_padding", "use_texture_padding"), &TileSetAtlasSource::set_use_texture_padding);
+ ClassDB::bind_method(D_METHOD("get_use_texture_padding"), &TileSetAtlasSource::get_use_texture_padding);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR), "set_texture", "get_texture");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_texture_region_size", "get_texture_region_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_texture_padding", "get_use_texture_padding");
// Base tiles
ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1)));
@@ -3752,7 +4288,7 @@ void TileSetAtlasSource::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas);
ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS));
-
+ ClassDB::bind_method(D_METHOD("get_tiles_to_be_removed_on_change", "texture", "margins", "separation", "texture_region_size"), &TileSetAtlasSource::get_tiles_to_be_removed_on_change);
ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords);
ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns);
@@ -3777,9 +4313,12 @@ void TileSetAtlasSource::_bind_methods() {
// Helpers.
ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size);
- ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture);
- ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture);
ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0));
+
+ // Getters for texture and tile region (padded or not)
+ ClassDB::bind_method(D_METHOD("_update_padded_texture"), &TileSetAtlasSource::_update_padded_texture);
+ ClassDB::bind_method(D_METHOD("get_runtime_texture"), &TileSetAtlasSource::get_runtime_texture);
+ ClassDB::bind_method(D_METHOD("get_runtime_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_runtime_tile_texture_region);
}
TileSetAtlasSource::~TileSetAtlasSource() {
@@ -3852,6 +4391,83 @@ void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) {
}
}
+void TileSetAtlasSource::_clear_tiles_outside_texture() {
+ LocalVector<Vector2i> to_remove;
+
+ for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
+ if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) {
+ to_remove.push_back(E.key);
+ }
+ }
+
+ for (unsigned int i = 0; i < to_remove.size(); i++) {
+ remove_tile(to_remove[i]);
+ }
+}
+
+void TileSetAtlasSource::_queue_update_padded_texture() {
+ padded_texture_needs_update = true;
+ call_deferred("_update_padded_texture");
+}
+
+void TileSetAtlasSource::_update_padded_texture() {
+ if (!padded_texture_needs_update) {
+ return;
+ }
+ padded_texture_needs_update = false;
+ padded_texture = Ref<ImageTexture>();
+
+ if (!texture.is_valid()) {
+ return;
+ }
+
+ if (!use_texture_padding) {
+ return;
+ }
+
+ Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2));
+
+ Ref<Image> src = texture->get_image();
+
+ Ref<Image> image;
+ image.instantiate();
+ image->create(size.x, size.y, false, Image::FORMAT_RGBA8);
+
+ for (KeyValue<Vector2i, TileAlternativesData> kv : tiles) {
+ for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) {
+ // Compute the source rects.
+ Rect2i src_rect = get_tile_texture_region(kv.key, frame);
+
+ Rect2i top_src_rect = Rect2i(src_rect.position, Vector2i(src_rect.size.x, 1));
+ Rect2i bottom_src_rect = Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(src_rect.size.x, 1));
+ Rect2i left_src_rect = Rect2i(src_rect.position, Vector2i(1, src_rect.size.y));
+ Rect2i right_src_rect = Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, src_rect.size.y));
+
+ // Copy the tile and the paddings.
+ Vector2i frame_coords = kv.key + (kv.value.size_in_atlas + kv.value.animation_separation) * ((kv.value.animation_columns > 0) ? Vector2i(frame % kv.value.animation_columns, frame / kv.value.animation_columns) : Vector2i(frame, 0));
+ Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1);
+
+ image->blit_rect(*src, src_rect, base_pos);
+
+ image->blit_rect(*src, top_src_rect, base_pos + Vector2i(0, -1));
+ image->blit_rect(*src, bottom_src_rect, base_pos + Vector2i(0, src_rect.size.y));
+ image->blit_rect(*src, left_src_rect, base_pos + Vector2i(-1, 0));
+ image->blit_rect(*src, right_src_rect, base_pos + Vector2i(src_rect.size.x, 0));
+
+ image->set_pixelv(base_pos + Vector2i(-1, -1), src->get_pixelv(src_rect.position));
+ image->set_pixelv(base_pos + Vector2i(src_rect.size.x, -1), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, 0)));
+ image->set_pixelv(base_pos + Vector2i(-1, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(0, src_rect.size.y - 1)));
+ image->set_pixelv(base_pos + Vector2i(src_rect.size.x, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, src_rect.size.y - 1)));
+ }
+ }
+
+ if (!padded_texture.is_valid()) {
+ padded_texture.instantiate();
+ }
+ padded_texture->create_from_image(image);
+ emit_changed();
+}
+
/////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
void TileSetScenesCollectionSource::_compute_next_alternative_id() {
@@ -4243,6 +4859,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)");
@@ -4350,6 +4997,9 @@ real_t TileData::get_constant_angular_velocity(int p_layer_id) const {
void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) {
ERR_FAIL_INDEX(p_layer_id, physics.size());
ERR_FAIL_COND(p_polygons_count < 0);
+ if (p_polygons_count == physics.write[p_layer_id].polygons.size()) {
+ return;
+ }
physics.write[p_layer_id].polygons.resize(p_polygons_count);
notify_property_list_changed();
emit_signal(SNAME("changed"));
@@ -4438,8 +5088,8 @@ int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_i
Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const {
ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0);
ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref<ConvexPolygonShape2D>());
- ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[shape_index].shapes.size(), Ref<ConvexPolygonShape2D>());
- return physics[p_layer_id].polygons[shape_index].shapes[shape_index];
+ ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[p_polygon_index].shapes.size(), Ref<ConvexPolygonShape2D>());
+ return physics[p_layer_id].polygons[p_polygon_index].shapes[shape_index];
}
// Terrain
@@ -4486,6 +5136,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(tile_set, terrain_set);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, TileSet::CellNeighbor(i))) {
+ output.set_terrain(TileSet::CellNeighbor(i), 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());
@@ -4543,9 +5205,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(layer_index < 0, false);
if (components[1] == "polygon") {
Ref<OccluderPolygon2D> polygon = p_value;
- if (!polygon.is_valid()) {
- return false;
- }
if (layer_index >= occluders.size()) {
if (tile_set) {
@@ -4617,9 +5276,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(layer_index < 0, false);
if (components[1] == "polygon") {
Ref<NavigationPolygon> polygon = p_value;
- if (!polygon.is_valid()) {
- return false;
- }
if (layer_index >= navigation.size()) {
if (tile_set) {
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 716b66405f..d2238c26d2 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -60,6 +60,84 @@ class TileSetPluginAtlasRendering;
class TileSetPluginAtlasPhysics;
class TileSetPluginAtlasNavigation;
+union TileMapCell {
+ struct {
+ int32_t source_id : 16;
+ int16_t coord_x : 16;
+ int16_t coord_y : 16;
+ int32_t alternative_tile : 16;
+ };
+
+ uint64_t _u64t;
+ TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE
+ source_id = p_source_id;
+ set_atlas_coords(p_atlas_coords);
+ alternative_tile = p_alternative_tile;
+ }
+
+ Vector2i get_atlas_coords() const {
+ return Vector2i(coord_x, coord_y);
+ }
+
+ void set_atlas_coords(const Vector2i &r_coords) {
+ coord_x = r_coords.x;
+ coord_y = r_coords.y;
+ }
+
+ bool operator<(const TileMapCell &p_other) const {
+ if (source_id == p_other.source_id) {
+ if (coord_x == p_other.coord_x) {
+ if (coord_y == p_other.coord_y) {
+ return alternative_tile < p_other.alternative_tile;
+ } else {
+ return coord_y < p_other.coord_y;
+ }
+ } else {
+ return coord_x < p_other.coord_x;
+ }
+ } else {
+ return source_id < p_other.source_id;
+ }
+ }
+
+ bool operator!=(const TileMapCell &p_other) const {
+ return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile);
+ }
+};
+
+class TileMapPattern : public Resource {
+ GDCLASS(TileMapPattern, Resource);
+
+ Vector2i size;
+ Map<Vector2i, TileMapCell> pattern;
+
+ void _set_tile_data(const Vector<int> &p_data);
+ Vector<int> _get_tile_data() const;
+
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ static void _bind_methods();
+
+public:
+ void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0);
+ bool has_cell(const Vector2i &p_coords) const;
+ void remove_cell(const Vector2i &p_coords, bool p_update_size = true);
+ int get_cell_source_id(const Vector2i &p_coords) const;
+ Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
+ int get_cell_alternative_tile(const Vector2i &p_coords) const;
+
+ TypedArray<Vector2i> get_used_cells() const;
+
+ Vector2i get_size() const;
+ void set_size(const Vector2i &p_size);
+ bool is_empty() const;
+
+ void clear();
+};
+
class TileSet : public Resource {
GDCLASS(TileSet, Resource);
@@ -176,6 +254,30 @@ public:
Vector2 offset;
};
+ class TerrainsPattern {
+ bool valid = false;
+ int bits[TileSet::CELL_NEIGHBOR_MAX];
+ bool is_valid_bit[TileSet::CELL_NEIGHBOR_MAX];
+
+ int not_empty_terrains_count = 0;
+
+ public:
+ bool is_valid() const;
+ bool is_erase_pattern() const;
+
+ bool operator<(const TerrainsPattern &p_terrains_pattern) const;
+ bool operator==(const TerrainsPattern &p_terrains_pattern) const;
+
+ void set_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain);
+ int get_terrain(TileSet::CellNeighbor p_peering_bit) const;
+
+ void set_terrains_from_array(Array p_terrains);
+ Array get_terrains_as_array() const;
+
+ TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set);
+ TerrainsPattern() {}
+ };
+
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -225,6 +327,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;
@@ -245,6 +351,8 @@ private:
int next_source_id = 0;
// ---------------------
+ LocalVector<Ref<TileMapPattern>> patterns;
+
void _compute_next_source_id();
void _source_changed();
@@ -384,6 +492,17 @@ public:
void cleanup_invalid_tile_proxies();
void clear_tile_proxies();
+ // Patterns.
+ int add_pattern(Ref<TileMapPattern> p_pattern, int p_index = -1);
+ Ref<TileMapPattern> get_pattern(int p_index);
+ 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_terrains_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>());
@@ -479,8 +598,16 @@ private:
void _compute_next_alternative_id(const Vector2i p_atlas_coords);
- void _create_coords_mapping_cache(Vector2i p_atlas_coords);
void _clear_coords_mapping_cache(Vector2i p_atlas_coords);
+ void _create_coords_mapping_cache(Vector2i p_atlas_coords);
+
+ void _clear_tiles_outside_texture();
+
+ bool use_texture_padding = true;
+ Ref<ImageTexture> padded_texture;
+ bool padded_texture_needs_update = false;
+ void _queue_update_padded_texture();
+ void _update_padded_texture();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -492,6 +619,7 @@ protected:
public:
// Not exposed.
virtual void set_tile_set(const TileSet *p_tile_set) override;
+ const TileSet *get_tile_set() const;
virtual void notify_tile_data_properties_should_change() override;
virtual void add_occlusion_layer(int p_index) override;
virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override;
@@ -523,6 +651,10 @@ public:
void set_texture_region_size(Vector2i p_tile_size);
Vector2i get_texture_region_size() const;
+ // Padding.
+ void set_use_texture_padding(bool p_use_padding);
+ bool get_use_texture_padding() const;
+
// Base tiles.
void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1));
void remove_tile(Vector2i p_atlas_coords);
@@ -534,7 +666,7 @@ public:
virtual Vector2i get_tile_id(int p_index) const override;
bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const;
-
+ PackedVector2Array get_tiles_to_be_removed_on_change(Ref<Texture2D> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size);
Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
// Animation.
@@ -565,11 +697,13 @@ public:
// Helpers.
Vector2i get_atlas_grid_size() const;
- bool has_tiles_outside_texture();
- void clear_tiles_outside_texture();
Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;
+ // Getters for texture and tile region (padded or not)
+ Ref<Texture2D> get_runtime_texture() const;
+ Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
+
~TileSetAtlasSource();
};
@@ -647,7 +781,7 @@ private:
};
Vector2 linear_velocity;
- float angular_velocity = 0.0;
+ double angular_velocity = 0.0;
Vector<PolygonShapeTileData> polygons;
};
Vector<PhysicsLayerTileData> physics;
@@ -698,6 +832,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;
@@ -745,6 +882,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;
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 934d16bd7e..0bdc81330e 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -36,6 +36,11 @@
#include "visual_shader_particle_nodes.h"
#include "visual_shader_sdf_nodes.h"
+String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) {
+ static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" };
+ return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id);
+}
+
bool VisualShaderNode::is_simple_decl() const {
return simple_decl;
}
@@ -113,6 +118,10 @@ bool VisualShaderNode::is_output_port_expandable(int p_port) const {
return false;
}
+bool VisualShaderNode::has_output_port_preview(int p_port) const {
+ return true;
+}
+
void VisualShaderNode::_set_output_ports_expanded(const Array &p_values) {
for (int i = 0; i < p_values.size(); i++) {
expanded_output_ports[p_values[i]] = true;
@@ -195,6 +204,10 @@ Vector<StringName> VisualShaderNode::get_editable_properties() const {
return Vector<StringName>();
}
+Map<StringName, String> VisualShaderNode::get_editable_properties_names() const {
+ return Map<StringName, String>();
+}
+
Array VisualShaderNode::get_default_input_values() const {
Array ret;
for (const KeyValue<int, Variant> &E : default_input_values) {
@@ -242,8 +255,8 @@ void VisualShaderNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_default_input_values"), &VisualShaderNode::get_default_input_values);
ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_default_input_values", "get_default_input_values");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "expanded_output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_output_ports_expanded", "_get_output_ports_expanded");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_default_input_values", "get_default_input_values");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "expanded_output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_output_ports_expanded", "_get_output_ports_expanded");
ADD_SIGNAL(MethodInfo("editor_refresh_request"));
BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR);
@@ -346,7 +359,7 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::
}
String code = " {\n";
String _code;
- GDVIRTUAL_CALL(_get_code, input_vars, output_vars, (int)p_mode, (int)p_type, _code);
+ GDVIRTUAL_CALL(_get_code, input_vars, output_vars, p_mode, p_type, _code);
bool nend = _code.ends_with("\n");
_code = _code.insert(0, " ");
_code = _code.replace("\n", "\n ");
@@ -363,7 +376,7 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::
String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
String ret;
- if (GDVIRTUAL_CALL(_get_global_code, (int)p_mode, ret)) {
+ if (GDVIRTUAL_CALL(_get_global_code, p_mode, ret)) {
String code = "// " + get_caption() + "\n";
code += ret;
code += "\n";
@@ -427,7 +440,7 @@ void VisualShaderNodeCustom::_bind_methods() {
ClassDB::bind_method(D_METHOD("_is_initialized"), &VisualShaderNodeCustom::_is_initialized);
ClassDB::bind_method(D_METHOD("_set_input_port_default_value", "port", "value"), &VisualShaderNodeCustom::_set_input_port_default_value);
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "initialized", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_initialized", "_is_initialized");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "initialized", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_initialized", "_is_initialized");
}
VisualShaderNodeCustom::VisualShaderNodeCustom() {
@@ -1092,6 +1105,7 @@ static const char *type_string[VisualShader::TYPE_MAX] = {
"start_custom",
"process_custom",
"sky",
+ "fog",
};
bool VisualShader::_set(const StringName &p_name, const Variant &p_value) {
@@ -1241,7 +1255,7 @@ void VisualShader::reset_state() {
}
void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
//mode
- p_list->push_back(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky"));
+ p_list->push_back(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog"));
//render modes
Map<String, String> blend_mode_enums;
@@ -1286,20 +1300,20 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
prop_name += "/" + itos(E.key);
if (E.key != NODE_ID_OUTPUT) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
}
- p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
if (Object::cast_to<VisualShaderNodeGroupBase>(E.value.node.ptr()) != nullptr) {
- p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
- p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
if (Object::cast_to<VisualShaderNodeExpression>(E.value.node.ptr()) != nullptr) {
- p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
- p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
}
@@ -1632,7 +1646,7 @@ void VisualShader::_update_shader() const {
Vector<VisualShader::DefaultTextureParam> default_tex_params;
Set<StringName> classes;
Map<int, int> insertion_pos;
- static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles", "sky" };
+ static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles", "sky", "fog" };
global_code += String() + "shader_type " + shader_mode_str[shader_mode] + ";\n";
@@ -1680,7 +1694,7 @@ void VisualShader::_update_shader() const {
global_code += "render_mode " + render_mode + ";\n\n";
}
- static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky" };
+ static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" };
String global_expressions;
Set<String> used_uniform_names;
@@ -1751,7 +1765,7 @@ void VisualShader::_update_shader() const {
StringBuilder func_code;
bool is_empty_func = false;
- if (shader_mode != Shader::MODE_PARTICLES && shader_mode != Shader::MODE_SKY) {
+ if (shader_mode != Shader::MODE_PARTICLES && shader_mode != Shader::MODE_SKY && shader_mode != Shader::MODE_FOG) {
is_empty_func = true;
}
@@ -1820,6 +1834,7 @@ void VisualShader::_update_shader() const {
code += " vec3 __vec3_buff2;\n";
code += " float __scalar_buff1;\n";
code += " float __scalar_buff2;\n";
+ code += " int __scalar_ibuff;\n";
code += " vec3 __ndiff = normalize(__diff);\n\n";
}
if (has_start) {
@@ -1917,6 +1932,10 @@ void VisualShader::_update_shader() const {
global_compute_code += " return mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n";
global_compute_code += "}\n\n";
+ global_compute_code += "vec2 __get_random_unit_vec2(inout uint seed) {\n";
+ global_compute_code += " return normalize(vec2(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n";
+ global_compute_code += "}\n\n";
+
global_compute_code += "vec3 __get_random_unit_vec3(inout uint seed) {\n";
global_compute_code += " return normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n";
global_compute_code += "}\n\n";
@@ -1943,7 +1962,9 @@ void VisualShader::_update_shader() const {
const_cast<VisualShader *>(this)->set_code(final_code);
for (int i = 0; i < default_tex_params.size(); i++) {
- const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].param);
+ for (int j = 0; j < default_tex_params[i].params.size(); j++) {
+ const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].params[j], j);
+ }
}
if (previous_code != final_code) {
const_cast<VisualShader *>(this)->emit_signal(SNAME("changed"));
@@ -2012,8 +2033,8 @@ void VisualShader::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_shader"), &VisualShader::_update_shader);
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "engine_version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_engine_version", "get_engine_version");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "engine_version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_engine_version", "get_engine_version");
ADD_PROPERTY_DEFAULT("code", ""); // Inherited from Shader, prevents showing default code as override in docs.
@@ -2026,6 +2047,7 @@ void VisualShader::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_START_CUSTOM);
BIND_ENUM_CONSTANT(TYPE_PROCESS_CUSTOM);
BIND_ENUM_CONSTANT(TYPE_SKY);
+ BIND_ENUM_CONSTANT(TYPE_FOG);
BIND_ENUM_CONSTANT(TYPE_MAX);
BIND_CONSTANT(NODE_ID_INVALID);
@@ -2303,11 +2325,35 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "sky_coords", "vec3(SKY_COORDS, 0.0)" },
{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+ // Fog, Fog
+
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "world_position", "WORLD_POSITION" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "object_position", "OBJECT_POSITION" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "uvw", "UVW" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "extents", "EXTENTS" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "sdf", "SDF" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
{ Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr },
};
const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = {
+ // Spatial, Vertex
+
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "vec3(0.0, 1.0, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "vec3(1.0, 0.0, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0, 1.0, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
// Spatial, Fragment
+
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "vec3(0.0, 1.0, 0.0)" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "vec3(1.0, 0.0, 0.0)" },
@@ -2315,44 +2361,63 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV, 0.0)" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
-
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" },
- { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "1.0" },
-
- { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0, 1.0, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
// Spatial, Light
- { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" },
- { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV, 0.0)" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0, 1.0, 0.0)" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
// Canvas Item, Vertex
+
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
// Canvas Item, Fragment
+
+ { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
// Canvas Item, Light
+
+ { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
-
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" },
{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
// Particles
+
{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+ // Sky
+
+ { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" },
+ { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+ // Fog
+
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
{ Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr },
};
@@ -2413,13 +2478,10 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T
case PORT_TYPE_VECTOR: {
code = " " + p_output_vars[0] + " = vec3(0.0);\n";
} break;
- case PORT_TYPE_TRANSFORM: {
- code = " " + p_output_vars[0] + " = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
- } break;
case PORT_TYPE_BOOLEAN: {
code = " " + p_output_vars[0] + " = false;\n";
} break;
- default: //default (none found) is scalar
+ default:
break;
}
}
@@ -2804,7 +2866,7 @@ void VisualShaderNodeUniformRef::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_uniform_type"), &VisualShaderNodeUniformRef::_get_uniform_type);
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "uniform_name", PROPERTY_HINT_ENUM, ""), "set_uniform_name", "get_uniform_name");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "uniform_type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_uniform_type", "_get_uniform_type");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "uniform_type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uniform_type", "_get_uniform_type");
}
Vector<StringName> VisualShaderNodeUniformRef::get_editable_properties() const {
@@ -2903,6 +2965,13 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "fog_alpha", "FOG.a" },
////////////////////////////////////////////////////////////////////////
+ // Fog, Fog.
+ ////////////////////////////////////////////////////////////////////////
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "density", "DENSITY" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" },
+ { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "emission", "EMISSION" },
+
+ ////////////////////////////////////////////////////////////////////////
{ Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr },
};
@@ -3383,7 +3452,7 @@ void VisualShaderNodeGroupBase::add_input_port(int p_id, int p_type, const Strin
count++;
}
- inputs.erase(index, count);
+ inputs = inputs.left(index) + inputs.substr(index + count);
inputs = inputs.insert(index, itos(i));
index += inputs_strings[i].size();
}
@@ -3406,7 +3475,7 @@ void VisualShaderNodeGroupBase::remove_input_port(int p_id) {
}
index += inputs_strings[i].size();
}
- inputs.erase(index, count);
+ inputs = inputs.left(index) + inputs.substr(index + count);
inputs_strings = inputs.split(";", false);
inputs = inputs.substr(0, index);
@@ -3459,7 +3528,7 @@ void VisualShaderNodeGroupBase::add_output_port(int p_id, int p_type, const Stri
count++;
}
- outputs.erase(index, count);
+ outputs = outputs.left(index) + outputs.substr(index + count);
outputs = outputs.insert(index, itos(i));
index += outputs_strings[i].size();
}
@@ -3482,7 +3551,7 @@ void VisualShaderNodeGroupBase::remove_output_port(int p_id) {
}
index += outputs_strings[i].size();
}
- outputs.erase(index, count);
+ outputs = outputs.left(index) + outputs.substr(index + count);
outputs_strings = outputs.split(";", false);
outputs = outputs.substr(0, index);
@@ -3534,8 +3603,7 @@ void VisualShaderNodeGroupBase::set_input_port_type(int p_id, int p_type) {
index += inputs_strings[i].size();
}
- inputs.erase(index, count);
-
+ inputs = inputs.left(index) + inputs.substr(index + count);
inputs = inputs.insert(index, itos(p_type));
_apply_port_changes();
@@ -3570,8 +3638,7 @@ void VisualShaderNodeGroupBase::set_input_port_name(int p_id, const String &p_na
index += inputs_strings[i].size();
}
- inputs.erase(index, count);
-
+ inputs = inputs.left(index) + inputs.substr(index + count);
inputs = inputs.insert(index, p_name);
_apply_port_changes();
@@ -3606,7 +3673,7 @@ void VisualShaderNodeGroupBase::set_output_port_type(int p_id, int p_type) {
index += output_strings[i].size();
}
- outputs.erase(index, count);
+ outputs = outputs.left(index) + outputs.substr(index + count);
outputs = outputs.insert(index, itos(p_type));
@@ -3642,7 +3709,7 @@ void VisualShaderNodeGroupBase::set_output_port_name(int p_id, const String &p_n
index += output_strings[i].size();
}
- outputs.erase(index, count);
+ outputs = outputs.left(index) + outputs.substr(index + count);
outputs = outputs.insert(index, p_name);
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index b3efac02aa..5bb9d45f39 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -57,6 +57,7 @@ public:
TYPE_START_CUSTOM,
TYPE_PROCESS_CUSTOM,
TYPE_SKY,
+ TYPE_FOG,
TYPE_MAX
};
@@ -69,7 +70,7 @@ public:
struct DefaultTextureParam {
StringName name;
- Ref<Texture2D> param;
+ List<Ref<Texture2D>> params;
};
private:
@@ -254,6 +255,8 @@ public:
void set_input_port_connected(int p_port, bool p_connected);
virtual bool is_generate_input_var(int p_port) const;
+ virtual bool has_output_port_preview(int p_port) const;
+
virtual bool is_output_port_expandable(int p_port) const;
void _set_output_ports_expanded(const Array &p_data);
Array _get_output_ports_expanded() const;
@@ -269,6 +272,7 @@ public:
void set_disabled(bool p_disabled = true);
virtual Vector<StringName> get_editable_properties() const;
+ virtual Map<StringName, String> get_editable_properties_names() const;
virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const;
virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const;
@@ -324,8 +328,8 @@ protected:
GDVIRTUAL0RC(int, _get_output_port_count)
GDVIRTUAL1RC(int, _get_output_port_type, int)
GDVIRTUAL1RC(String, _get_output_port_name, int)
- GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, int, int)
- GDVIRTUAL1RC(String, _get_global_code, int)
+ GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, Shader::Mode, VisualShader::Type)
+ GDVIRTUAL1RC(String, _get_global_code, Shader::Mode)
GDVIRTUAL0RC(bool, _is_highend)
protected:
@@ -693,4 +697,6 @@ public:
VisualShaderNodeGlobalExpression();
};
+extern String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name);
+
#endif // VISUAL_SHADER_H
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index e45dfdcb1b..951870fe34 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -494,15 +494,10 @@ String VisualShaderNodeTexture::get_input_port_default_hint(int p_port) const {
return "";
}
-static String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) {
- static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt" };
- return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id);
-}
-
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "tex");
- dtp.param = texture;
+ dtp.params.push_back(texture);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
@@ -900,7 +895,7 @@ String VisualShaderNodeCurveTexture::generate_code(Shader::Mode p_mode, VisualSh
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "curve");
- dtp.param = texture;
+ dtp.params.push_back(texture);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
@@ -918,6 +913,7 @@ bool VisualShaderNodeCurveTexture::is_use_prop_slots() const {
}
VisualShaderNodeCurveTexture::VisualShaderNodeCurveTexture() {
+ set_input_port_default_value(0, 0.0);
simple_decl = true;
allow_v_resize = false;
}
@@ -984,7 +980,7 @@ String VisualShaderNodeCurveXYZTexture::generate_code(Shader::Mode p_mode, Visua
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveXYZTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "curve3d");
- dtp.param = texture;
+ dtp.params.push_back(texture);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
@@ -1002,6 +998,7 @@ bool VisualShaderNodeCurveXYZTexture::is_use_prop_slots() const {
}
VisualShaderNodeCurveXYZTexture::VisualShaderNodeCurveXYZTexture() {
+ set_input_port_default_value(0, 0.0);
simple_decl = true;
allow_v_resize = false;
}
@@ -1165,7 +1162,7 @@ String VisualShaderNodeTexture2DArray::get_input_port_name(int p_port) const {
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture2DArray::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "tex3d");
- dtp.param = texture_array;
+ dtp.params.push_back(texture_array);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
@@ -1222,7 +1219,7 @@ String VisualShaderNodeTexture3D::get_input_port_name(int p_port) const {
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture3D::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "tex3d");
- dtp.param = texture;
+ dtp.params.push_back(texture);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
@@ -1321,7 +1318,7 @@ bool VisualShaderNodeCubemap::is_output_port_expandable(int p_port) const {
Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCubemap::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
VisualShader::DefaultTextureParam dtp;
dtp.name = make_unique_id(p_type, p_id, "cube");
- dtp.param = cube_map;
+ dtp.params.push_back(cube_map);
Vector<VisualShader::DefaultTextureParam> ret;
ret.push_back(dtp);
return ret;
diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp
index 5fe801e037..7dd4eed15b 100644
--- a/scene/resources/visual_shader_particle_nodes.cpp
+++ b/scene/resources/visual_shader_particle_nodes.cpp
@@ -30,6 +30,8 @@
#include "visual_shader_particle_nodes.h"
+#include "core/core_string_names.h"
+
// VisualShaderNodeParticleEmitter
int VisualShaderNodeParticleEmitter::get_output_port_count() const {
@@ -47,6 +49,42 @@ String VisualShaderNodeParticleEmitter::get_output_port_name(int p_port) const {
return String();
}
+bool VisualShaderNodeParticleEmitter::has_output_port_preview(int p_port) const {
+ return false;
+}
+
+void VisualShaderNodeParticleEmitter::set_mode_2d(bool p_enabled) {
+ mode_2d = p_enabled;
+ emit_changed();
+}
+
+bool VisualShaderNodeParticleEmitter::is_mode_2d() const {
+ return mode_2d;
+}
+
+Vector<StringName> VisualShaderNodeParticleEmitter::get_editable_properties() const {
+ Vector<StringName> props;
+ props.push_back("mode_2d");
+ return props;
+}
+
+Map<StringName, String> VisualShaderNodeParticleEmitter::get_editable_properties_names() const {
+ Map<StringName, String> names;
+ names.insert("mode_2d", TTR("2D Mode"));
+ return names;
+}
+
+bool VisualShaderNodeParticleEmitter::is_show_prop_names() const {
+ return true;
+}
+
+void VisualShaderNodeParticleEmitter::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_mode_2d", "enabled"), &VisualShaderNodeParticleEmitter::set_mode_2d);
+ ClassDB::bind_method(D_METHOD("is_mode_2d"), &VisualShaderNodeParticleEmitter::is_mode_2d);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_2d"), "set_mode_2d", "is_mode_2d");
+}
+
VisualShaderNodeParticleEmitter::VisualShaderNodeParticleEmitter() {
}
@@ -75,15 +113,27 @@ String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) co
String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
String code;
+
+ code += "vec2 __get_random_point_in_circle(inout uint seed, float radius, float inner_radius) {\n";
+ code += " return __get_random_unit_vec2(seed) * __randf_range(seed, inner_radius, radius);\n";
+ code += "}\n\n";
+
code += "vec3 __get_random_point_in_sphere(inout uint seed, float radius, float inner_radius) {\n";
code += " return __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n";
code += "}\n\n";
+
return code;
}
String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String code;
- code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n";
+
+ if (mode_2d) {
+ code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_circle(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n";
+ } else {
+ code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n";
+ }
+
return code;
}
@@ -118,16 +168,27 @@ String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const
String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
String code;
- code += "vec3 __get_random_point_in_box(inout uint seed, vec3 extents) {\n";
+
+ code += "vec2 __get_random_point_in_box2d(inout uint seed, vec2 extents) {\n";
+ code += " vec2 half_extents = extents / 2.0;\n";
+ code += " return vec2(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y));\n";
+ code += "}\n\n";
+
+ code += "vec3 __get_random_point_in_box3d(inout uint seed, vec3 extents) {\n";
code += " vec3 half_extents = extents / 2.0;\n";
code += " return vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n";
code += "}\n\n";
+
return code;
}
String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String code;
- code += " " + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n";
+ if (mode_2d) {
+ code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_box2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ".xy), 0.0);\n";
+ } else {
+ code += " " + p_output_vars[0] + " = __get_random_point_in_box3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n";
+ }
return code;
}
@@ -162,17 +223,31 @@ String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) cons
String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
String code;
- code += "vec3 __get_random_point_on_ring(inout uint seed, float radius, float inner_radius, float height) {\n";
- code += " float angle = __rand_from_seed(seed) * PI * 2.0;\n";
+
+ code += "vec2 __get_random_point_on_ring2d(inout uint seed, float radius, float inner_radius) {\n";
+ code += " float angle = __rand_from_seed(seed) * TAU;\n";
+ code += " vec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n";
+ code += " return vec2(ring.x, ring.y);\n";
+ code += "}\n\n";
+
+ code += "vec3 __get_random_point_on_ring3d(inout uint seed, float radius, float inner_radius, float height) {\n";
+ code += " float angle = __rand_from_seed(seed) * TAU;\n";
code += " vec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n";
code += " return vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n";
code += "}\n\n";
+
return code;
}
String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String code;
- code = " " + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n";
+
+ if (mode_2d) {
+ code = " " + p_output_vars[0] + " = vec3(__get_random_point_on_ring2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n";
+ } else {
+ code = " " + p_output_vars[0] + " = __get_random_point_on_ring3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n";
+ }
+
return code;
}
@@ -182,6 +257,283 @@ VisualShaderNodeParticleRingEmitter::VisualShaderNodeParticleRingEmitter() {
set_input_port_default_value(2, 0.0);
}
+// VisualShaderNodeParticleMeshEmitter
+
+String VisualShaderNodeParticleMeshEmitter::get_caption() const {
+ return "MeshEmitter";
+}
+
+int VisualShaderNodeParticleMeshEmitter::get_output_port_count() const {
+ return 2;
+}
+
+VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_output_port_type(int p_port) const {
+ switch (p_port) {
+ case 0:
+ return PORT_TYPE_VECTOR; // position
+ case 1:
+ return PORT_TYPE_VECTOR; // normal
+ }
+ return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleMeshEmitter::get_output_port_name(int p_port) const {
+ switch (p_port) {
+ case 0:
+ return "position";
+ case 1:
+ return "normal";
+ }
+ return String();
+}
+
+int VisualShaderNodeParticleMeshEmitter::get_input_port_count() const {
+ return 0;
+}
+
+VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_input_port_type(int p_port) const {
+ return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleMeshEmitter::get_input_port_name(int p_port) const {
+ return String();
+}
+
+String VisualShaderNodeParticleMeshEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
+ String code;
+
+ if (mesh.is_valid()) {
+ code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_vx") + ";\n";
+ code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_nm") + ";\n";
+ }
+
+ return code;
+}
+
+String VisualShaderNodeParticleMeshEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+ String code;
+
+ code += " __scalar_ibuff = int(__rand_from_seed(__seed) * 65535.0) % " + itos(position_texture->get_width()) + ";\n";
+
+ if (position_texture->get_width() == 0) {
+ code += " " + p_output_vars[0] + " = vec3(0.0);\n";
+ } else {
+ if (mode_2d) {
+ code += " " + p_output_vars[0] + " = vec3(";
+ code += "texelFetch(";
+ code += make_unique_id(p_type, p_id, "mesh_vx") + ", ";
+ code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n";
+ } else {
+ code += " " + p_output_vars[0] + " = texelFetch(";
+ code += make_unique_id(p_type, p_id, "mesh_vx") + ", ";
+ code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n";
+ }
+ }
+
+ if (normal_texture->get_width() == 0) {
+ code += " " + p_output_vars[1] + " = vec3(0.0);\n";
+ } else {
+ if (mode_2d) {
+ code += " " + p_output_vars[1] + " = vec3(";
+ code += "texelFetch(";
+ code += make_unique_id(p_type, p_id, "mesh_nm") + ", ";
+ code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n";
+ } else {
+ code += " " + p_output_vars[1] + " = texelFetch(";
+ code += make_unique_id(p_type, p_id, "mesh_nm") + ", ";
+ code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n";
+ }
+ }
+
+ return code;
+}
+
+Vector<VisualShader::DefaultTextureParam> VisualShaderNodeParticleMeshEmitter::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
+ VisualShader::DefaultTextureParam dtp_vx;
+ dtp_vx.name = make_unique_id(p_type, p_id, "mesh_vx");
+ dtp_vx.params.push_back(position_texture);
+
+ VisualShader::DefaultTextureParam dtp_nm;
+ dtp_nm.name = make_unique_id(p_type, p_id, "mesh_nm");
+ dtp_nm.params.push_back(normal_texture);
+
+ Vector<VisualShader::DefaultTextureParam> ret;
+ ret.push_back(dtp_vx);
+ ret.push_back(dtp_nm);
+ return ret;
+}
+
+void VisualShaderNodeParticleMeshEmitter::update_texture() {
+ if (!mesh.is_valid()) {
+ return;
+ }
+
+ Vector<Vector3> vertices;
+ Vector<Vector3> normals;
+
+ if (use_all_surfaces) {
+ for (int i = 0; i < max_surface_index; i++) {
+ Array vertex_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_VERTEX];
+ for (int j = 0; j < vertex_array.size(); j++) {
+ vertices.push_back((Vector3)vertex_array[j]);
+ }
+
+ Array normal_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_NORMAL];
+ for (int j = 0; j < vertex_array.size(); j++) {
+ normals.push_back((Vector3)vertex_array[j]);
+ }
+ }
+ } else {
+ Array vertex_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_VERTEX];
+ for (int i = 0; i < vertex_array.size(); i++) {
+ vertices.push_back((Vector3)vertex_array[i]);
+ }
+
+ Array normal_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_NORMAL];
+ for (int i = 0; i < normal_array.size(); i++) {
+ normals.push_back((Vector3)normal_array[i]);
+ }
+ }
+
+ // vertices
+ {
+ Ref<Image> image;
+ image.instantiate();
+ image->create(vertices.size(), 1, false, Image::Format::FORMAT_RGBF);
+
+ for (int i = 0; i < vertices.size(); i++) {
+ Vector3 v = vertices[i];
+ image->set_pixel(i, 0, Color(v.x, v.y, v.z));
+ }
+ if (position_texture->get_width() != vertices.size()) {
+ position_texture->create_from_image(image);
+ } else {
+ position_texture->update(image);
+ }
+ }
+
+ // normals
+ {
+ Ref<Image> image;
+ image.instantiate();
+ image->create(normals.size(), 1, false, Image::Format::FORMAT_RGBF);
+
+ for (int i = 0; i < normals.size(); i++) {
+ Vector3 v = normals[i];
+ image->set_pixel(i, 0, Color(v.x, v.y, v.z));
+ }
+ if (normal_texture->get_width() != normals.size()) {
+ normal_texture->create_from_image(image);
+ } else {
+ normal_texture->update(image);
+ }
+ }
+}
+
+void VisualShaderNodeParticleMeshEmitter::set_mesh(Ref<Mesh> p_mesh) {
+ if (mesh == p_mesh) {
+ return;
+ }
+
+ if (p_mesh.is_valid()) {
+ max_surface_index = p_mesh->get_surface_count();
+ } else {
+ max_surface_index = 0;
+ }
+
+ if (mesh.is_valid()) {
+ Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture);
+
+ if (mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) {
+ mesh->disconnect(CoreStringNames::get_singleton()->changed, callable);
+ }
+ }
+
+ mesh = p_mesh;
+
+ if (mesh.is_valid()) {
+ Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture);
+
+ if (!mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) {
+ mesh->connect(CoreStringNames::get_singleton()->changed, callable);
+ }
+ }
+
+ emit_changed();
+}
+
+Ref<Mesh> VisualShaderNodeParticleMeshEmitter::get_mesh() const {
+ return mesh;
+}
+
+void VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces(bool p_enabled) {
+ if (use_all_surfaces == p_enabled) {
+ return;
+ }
+ use_all_surfaces = p_enabled;
+ emit_changed();
+}
+
+bool VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces() const {
+ return use_all_surfaces;
+}
+
+void VisualShaderNodeParticleMeshEmitter::set_surface_index(int p_surface_index) {
+ if (p_surface_index == surface_index || p_surface_index < 0 || p_surface_index >= max_surface_index) {
+ return;
+ }
+ surface_index = p_surface_index;
+ emit_changed();
+}
+
+int VisualShaderNodeParticleMeshEmitter::get_surface_index() const {
+ return surface_index;
+}
+
+Vector<StringName> VisualShaderNodeParticleMeshEmitter::get_editable_properties() const {
+ Vector<StringName> props = VisualShaderNodeParticleEmitter::get_editable_properties();
+
+ props.push_back("mesh");
+ props.push_back("use_all_surfaces");
+ if (!use_all_surfaces) {
+ props.push_back("surface_index");
+ }
+
+ return props;
+}
+
+Map<StringName, String> VisualShaderNodeParticleMeshEmitter::get_editable_properties_names() const {
+ Map<StringName, String> names = VisualShaderNodeParticleEmitter::get_editable_properties_names();
+
+ names.insert("mesh", TTR("Mesh"));
+ names.insert("use_all_surfaces", TTR("Use All Surfaces"));
+ if (!use_all_surfaces) {
+ names.insert("surface_index", TTR("Surface Index"));
+ }
+
+ return names;
+}
+
+void VisualShaderNodeParticleMeshEmitter::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &VisualShaderNodeParticleMeshEmitter::set_mesh);
+ ClassDB::bind_method(D_METHOD("get_mesh"), &VisualShaderNodeParticleMeshEmitter::get_mesh);
+ ClassDB::bind_method(D_METHOD("set_use_all_surfaces", "enabled"), &VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces);
+ ClassDB::bind_method(D_METHOD("is_use_all_surfaces"), &VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces);
+ ClassDB::bind_method(D_METHOD("set_surface_index", "surface_index"), &VisualShaderNodeParticleMeshEmitter::set_surface_index);
+ ClassDB::bind_method(D_METHOD("get_surface_index"), &VisualShaderNodeParticleMeshEmitter::get_surface_index);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_all_surfaces"), "set_use_all_surfaces", "is_use_all_surfaces");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "surface_index"), "set_surface_index", "get_surface_index");
+}
+
+VisualShaderNodeParticleMeshEmitter::VisualShaderNodeParticleMeshEmitter() {
+ connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture));
+
+ position_texture.instantiate();
+ normal_texture.instantiate();
+}
+
// VisualShaderNodeParticleMultiplyByAxisAngle
void VisualShaderNodeParticleMultiplyByAxisAngle::_bind_methods() {
@@ -261,10 +613,13 @@ bool VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode() const {
Vector<StringName> VisualShaderNodeParticleMultiplyByAxisAngle::get_editable_properties() const {
Vector<StringName> props;
props.push_back("degrees_mode");
- props.push_back("axis_amount");
return props;
}
+bool VisualShaderNodeParticleMultiplyByAxisAngle::has_output_port_preview(int p_port) const {
+ return false;
+}
+
VisualShaderNodeParticleMultiplyByAxisAngle::VisualShaderNodeParticleMultiplyByAxisAngle() {
set_input_port_default_value(1, Vector3(1, 0, 0));
set_input_port_default_value(2, 0.0);
@@ -313,6 +668,10 @@ String VisualShaderNodeParticleConeVelocity::get_output_port_name(int p_port) co
return String();
}
+bool VisualShaderNodeParticleConeVelocity::has_output_port_preview(int p_port) const {
+ return false;
+}
+
String VisualShaderNodeParticleConeVelocity::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String code;
code += " __radians = radians(" + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n";
@@ -421,6 +780,10 @@ VisualShaderNodeParticleRandomness::OpType VisualShaderNodeParticleRandomness::g
return op_type;
}
+bool VisualShaderNodeParticleRandomness::has_output_port_preview(int p_port) const {
+ return false;
+}
+
VisualShaderNodeParticleRandomness::VisualShaderNodeParticleRandomness() {
set_input_port_default_value(0, 0.0);
set_input_port_default_value(1, 1.0);
@@ -521,6 +884,10 @@ VisualShaderNodeParticleAccelerator::Mode VisualShaderNodeParticleAccelerator::g
return mode;
}
+bool VisualShaderNodeParticleAccelerator::has_output_port_preview(int p_port) const {
+ return false;
+}
+
VisualShaderNodeParticleAccelerator::VisualShaderNodeParticleAccelerator() {
set_input_port_default_value(0, Vector3(1, 1, 1));
set_input_port_default_value(1, 0.0);
diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h
index f5435c3511..0d0f21e4bf 100644
--- a/scene/resources/visual_shader_particle_nodes.h
+++ b/scene/resources/visual_shader_particle_nodes.h
@@ -38,10 +38,22 @@
class VisualShaderNodeParticleEmitter : public VisualShaderNode {
GDCLASS(VisualShaderNodeParticleEmitter, VisualShaderNode);
+protected:
+ bool mode_2d = false;
+ static void _bind_methods();
+
public:
virtual int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override;
+
+ void set_mode_2d(bool p_enabled);
+ bool is_mode_2d() const;
+
+ Vector<StringName> get_editable_properties() const override;
+ virtual Map<StringName, String> get_editable_properties_names() const override;
+ bool is_show_prop_names() const override;
VisualShaderNodeParticleEmitter();
};
@@ -94,6 +106,51 @@ public:
VisualShaderNodeParticleRingEmitter();
};
+class VisualShaderNodeParticleMeshEmitter : public VisualShaderNodeParticleEmitter {
+ GDCLASS(VisualShaderNodeParticleMeshEmitter, VisualShaderNodeParticleEmitter);
+ Ref<Mesh> mesh;
+ bool use_all_surfaces = true;
+ int surface_index = 0;
+ int max_surface_index = 0;
+
+ Ref<ImageTexture> position_texture;
+ Ref<ImageTexture> normal_texture;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const override;
+
+ virtual int get_output_port_count() const override;
+ virtual PortType get_output_port_type(int p_port) const override;
+ virtual String get_output_port_name(int p_port) const override;
+
+ virtual int get_input_port_count() const override;
+ virtual PortType get_input_port_type(int p_port) const override;
+ virtual String get_input_port_name(int p_port) const override;
+
+ virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
+ virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+ void update_texture();
+
+ void set_mesh(Ref<Mesh> p_mesh);
+ Ref<Mesh> get_mesh() const;
+
+ void set_use_all_surfaces(bool p_enabled);
+ bool is_use_all_surfaces() const;
+
+ void set_surface_index(int p_surface_index);
+ int get_surface_index() const;
+
+ Vector<StringName> get_editable_properties() const override;
+ Map<StringName, String> get_editable_properties_names() const override;
+ Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override;
+
+ VisualShaderNodeParticleMeshEmitter();
+};
+
class VisualShaderNodeParticleMultiplyByAxisAngle : public VisualShaderNode {
GDCLASS(VisualShaderNodeParticleMultiplyByAxisAngle, VisualShaderNode);
bool degrees_mode = true;
@@ -112,6 +169,7 @@ public:
virtual int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
@@ -135,6 +193,7 @@ public:
virtual int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
@@ -168,6 +227,7 @@ public:
virtual int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
@@ -209,6 +269,7 @@ public:
virtual int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp
index 42047f104f..0e1b343eac 100644
--- a/scene/resources/world_3d.cpp
+++ b/scene/resources/world_3d.cpp
@@ -144,9 +144,9 @@ World3D::World3D() {
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/3d/default_gravity", 9.8));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/3d/default_gravity_vector", Vector3(0, -1, 0)));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/3d/default_linear_damp", 0.1));
- ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_linear_damp", PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_linear_damp", PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/3d/default_angular_damp", 0.1));
- ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_angular_damp", PropertyInfo(Variant::FLOAT, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"));
+ ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_angular_damp", PropertyInfo(Variant::FLOAT, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"));
navigation_map = NavigationServer3D::get_singleton()->map_create();
NavigationServer3D::get_singleton()->map_set_active(navigation_map, true);