summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/physics_body_2d.cpp12
-rw-r--r--scene/2d/skeleton_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp134
-rw-r--r--scene/2d/tile_map.h3
-rw-r--r--scene/3d/camera_3d.cpp2
-rw-r--r--scene/3d/camera_3d.h5
-rw-r--r--scene/3d/mesh_instance_3d.cpp2
-rw-r--r--scene/3d/physics_body_3d.cpp25
-rw-r--r--scene/3d/visual_instance_3d.cpp4
-rw-r--r--scene/animation/animation_mixer.cpp222
-rw-r--r--scene/animation/animation_mixer.h103
-rw-r--r--scene/animation/animation_player.cpp16
-rw-r--r--scene/animation/animation_player.h3
-rw-r--r--scene/animation/tween.cpp17
-rw-r--r--scene/debugger/scene_debugger.cpp5
-rw-r--r--scene/gui/color_picker.cpp6
-rw-r--r--scene/gui/control.cpp12
-rw-r--r--scene/gui/control.h2
-rw-r--r--scene/gui/graph_edit.cpp2
-rw-r--r--scene/gui/item_list.cpp8
-rw-r--r--scene/gui/label.cpp15
-rw-r--r--scene/gui/popup_menu.cpp6
-rw-r--r--scene/gui/rich_text_label.cpp94
-rw-r--r--scene/gui/tree.cpp2
-rw-r--r--scene/main/canvas_item.cpp4
-rw-r--r--scene/main/node.cpp2
-rw-r--r--scene/main/scene_tree.cpp10
-rw-r--r--scene/main/viewport.cpp165
-rw-r--r--scene/main/viewport.h5
-rw-r--r--scene/main/window.cpp6
-rw-r--r--scene/register_scene_types.cpp1
-rw-r--r--scene/resources/animation.cpp787
-rw-r--r--scene/resources/animation.h14
-rw-r--r--scene/resources/camera_attributes.cpp9
-rw-r--r--scene/resources/font.cpp50
-rw-r--r--scene/resources/immediate_mesh.cpp13
-rw-r--r--scene/resources/packed_scene.cpp4
-rw-r--r--scene/resources/particle_process_material.cpp15
-rw-r--r--scene/resources/resource_format_text.cpp2
-rw-r--r--scene/resources/skeleton_modification_stack_2d.cpp2
-rw-r--r--scene/resources/visual_shader.cpp2
-rw-r--r--scene/resources/visual_shader_nodes.cpp2
42 files changed, 1249 insertions, 546 deletions
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index baa3b0bb90..6af5a8dd80 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -431,7 +431,9 @@ struct _RigidBody2DInOut {
void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) {
if (!freeze || freeze_mode != FREEZE_MODE_KINEMATIC) {
+ set_block_transform_notify(true);
set_global_transform(p_state->get_transform());
+ set_block_transform_notify(false);
}
linear_velocity = p_state->get_linear_velocity();
@@ -446,16 +448,20 @@ void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) {
void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
lock_callback();
- set_block_transform_notify(true); // don't want notify (would feedback loop)
-
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform2D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform2D new_transform = get_global_transform();
+
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
- set_block_transform_notify(false); // want it back
if (contact_monitor) {
contact_monitor->locked = true;
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 9e811fb568..527bbaf956 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -546,7 +546,7 @@ void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const {
PropertyInfo(Variant::OBJECT, PNAME("modification_stack"),
PROPERTY_HINT_RESOURCE_TYPE,
"SkeletonModificationStack2D",
- PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_ALWAYS_DUPLICATE));
+ PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
}
void Skeleton2D::_make_bone_setup_dirty() {
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 1f70d4b558..6381526f58 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -686,6 +686,8 @@ void TileMapLayer::_physics_update() {
void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_what) {
Transform2D gl_transform = tile_map_node->get_global_transform();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
bool in_editor = false;
#ifdef TOOLS_ENABLED
in_editor = Engine::get_singleton()->is_editor_hint();
@@ -693,6 +695,7 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (p_what == DIRTY_FLAGS_TILE_MAP_XFORM) {
if (tile_map_node->is_inside_tree() && (!tile_map_node->is_collision_animatable() || in_editor)) {
+ // Move the collisison shapes along with the TileMap.
for (KeyValue<Vector2i, CellData> &kv : tile_map) {
const CellData &cell_data = kv.value;
@@ -700,12 +703,13 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (body.is_valid()) {
Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body]));
xform = gl_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
}
}
}
}
} else if (p_what == DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM) {
+ // With collisions animatable, move the collisison shapes along with the TileMap only on local xform change (they are synchornized on physics tick instead).
if (tile_map_node->is_inside_tree() && tile_map_node->is_collision_animatable() && !in_editor) {
for (KeyValue<Vector2i, CellData> &kv : tile_map) {
const CellData &cell_data = kv.value;
@@ -714,7 +718,22 @@ void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_wha
if (body.is_valid()) {
Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body]));
xform = gl_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+ }
+ }
+ }
+ } else if (p_what == DIRTY_FLAGS_TILE_MAP_IN_TREE) {
+ // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport).
+ if (tile_map_node->is_inside_tree()) {
+ RID space = tile_map_node->get_world_2d()->get_space();
+
+ for (KeyValue<Vector2i, CellData> &kv : tile_map) {
+ const CellData &cell_data = kv.value;
+
+ for (RID body : cell_data.bodies) {
+ if (body.is_valid()) {
+ ps->body_set_space(body, space);
}
}
}
@@ -2498,6 +2517,11 @@ Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const {
}
TileMapLayer::~TileMapLayer() {
+ if (!tile_map_node) {
+ // Temporary layer.
+ return;
+ }
+
in_destructor = true;
clear();
internal_update();
@@ -3599,8 +3623,9 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
format = (TileMapLayer::DataFormat)(p_value.operator int64_t()); // Set format used for loading.
return true;
}
+ }
#ifndef DISABLE_DEPRECATED
- } else if (p_name == "tile_data") { // Kept for compatibility reasons.
+ else if (p_name == "tile_data") { // Kept for compatibility reasons.
if (p_value.is_array()) {
if (layers.size() == 0) {
Ref<TileMapLayer> new_layer;
@@ -3614,10 +3639,12 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
return false;
- } else if (p_name == "rendering_quadrant_size") {
+ } else if (p_name == "cell_quadrant_size") {
set_rendering_quadrant_size(p_value);
+ return true;
+ }
#endif // DISABLE_DEPRECATED
- } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
+ else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
int index = components[0].trim_prefix("layer_").to_int();
if (index < 0) {
return false;
@@ -3674,7 +3701,14 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "format") {
r_ret = TileMapLayer::FORMAT_MAX - 1; // When saving, always save highest format.
return true;
- } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
+ }
+#ifndef DISABLE_DEPRECATED
+ else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons.
+ r_ret = get_rendering_quadrant_size();
+ return true;
+ }
+#endif
+ else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
int index = components[0].trim_prefix("layer_").to_int();
if (index < 0 || index >= (int)layers.size()) {
return false;
@@ -3714,16 +3748,88 @@ 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_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
+
+#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \
+ { \
+ const String property_name = vformat("layer_%d/" m_name, i); \
+ p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \
+ }
+
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, "suffix:px"));
- p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE));
- p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/navigation_enabled", i), PROPERTY_HINT_NONE));
+ MAKE_LAYER_PROPERTY(Variant::STRING, "name", "");
+ MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", "");
+ MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", "");
+ MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", "");
+ MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px");
+ MAKE_LAYER_PROPERTY(Variant::INT, "z_index", "");
+ MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", "");
p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
}
+
+#undef MAKE_LAYER_PROPERTY
+}
+
+bool TileMap::_property_can_revert(const StringName &p_name) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() == 2 && components[0].begins_with("layer_")) {
+ int index = components[0].trim_prefix("layer_").to_int();
+ if (index <= 0 || index >= (int)layers.size()) {
+ return false;
+ }
+
+ if (components[1] == "name") {
+ return layers[index]->get_name() != default_layer->get_name();
+ } else if (components[1] == "enabled") {
+ return layers[index]->is_enabled() != default_layer->is_enabled();
+ } else if (components[1] == "modulate") {
+ return layers[index]->get_modulate() != default_layer->get_modulate();
+ } else if (components[1] == "y_sort_enabled") {
+ return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled();
+ } else if (components[1] == "y_sort_origin") {
+ return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin();
+ } else if (components[1] == "z_index") {
+ return layers[index]->get_z_index() != default_layer->get_z_index();
+ } else if (components[1] == "navigation_enabled") {
+ return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled();
+ }
+ }
+
+ return false;
+}
+
+bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (components.size() == 2 && components[0].begins_with("layer_")) {
+ int index = components[0].trim_prefix("layer_").to_int();
+ if (index <= 0 || index >= (int)layers.size()) {
+ return false;
+ }
+
+ if (components[1] == "name") {
+ r_property = default_layer->get_name();
+ return true;
+ } else if (components[1] == "enabled") {
+ r_property = default_layer->is_enabled();
+ return true;
+ } else if (components[1] == "modulate") {
+ r_property = default_layer->get_modulate();
+ return true;
+ } else if (components[1] == "y_sort_enabled") {
+ r_property = default_layer->is_y_sort_enabled();
+ return true;
+ } else if (components[1] == "y_sort_origin") {
+ r_property = default_layer->get_y_sort_origin();
+ return true;
+ } else if (components[1] == "z_index") {
+ r_property = default_layer->get_z_index();
+ return true;
+ } else if (components[1] == "navigation_enabled") {
+ r_property = default_layer->is_navigation_enabled();
+ return true;
+ }
+ }
+
+ return false;
}
Vector2 TileMap::map_to_local(const Vector2i &p_pos) const {
@@ -4747,6 +4853,8 @@ TileMap::TileMap() {
new_layer->set_tile_map(this);
new_layer->set_layer_index_in_tile_map_node(0);
layers.push_back(new_layer);
+
+ default_layer.instantiate();
}
TileMap::~TileMap() {
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index a16595629c..1136e4190d 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -461,6 +461,7 @@ private:
// Layers.
LocalVector<Ref<TileMapLayer>> layers;
+ Ref<TileMapLayer> default_layer; // Dummy layer to fetch default values.
int selected_layer = -1;
bool pending_update = false;
@@ -479,6 +480,8 @@ 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;
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 37ceb9d1a1..8dc17a1c62 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -557,7 +557,7 @@ void Camera3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1,degrees"), "set_fov", "get_fov");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,16384,0.001,or_greater,suffix:m"), "set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_frustum_offset", "get_frustum_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp,suffix:m"), "set_near", "get_near");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp,suffix:m"), "set_far", "get_far");
diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h
index aa302ded4a..8de607806e 100644
--- a/scene/3d/camera_3d.h
+++ b/scene/3d/camera_3d.h
@@ -36,6 +36,11 @@
#include "scene/resources/camera_attributes.h"
#include "scene/resources/environment.h"
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
class Camera3D : public Node3D {
GDCLASS(Camera3D, Node3D);
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 78b02d74d5..d83d55d121 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -99,7 +99,7 @@ void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const {
if (mesh.is_valid()) {
for (int i = 0; i < mesh->get_surface_count(); i++) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT));
}
}
}
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index a5f5ae6e61..ed64c16564 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -485,7 +485,9 @@ struct _RigidBodyInOut {
};
void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) {
+ set_ignore_transform_notification(true);
set_global_transform(p_state->get_transform());
+ set_ignore_transform_notification(false);
linear_velocity = p_state->get_linear_velocity();
angular_velocity = p_state->get_angular_velocity();
@@ -501,16 +503,20 @@ void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) {
void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
lock_callback();
- set_ignore_transform_notification(true);
-
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform3D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform3D new_transform = get_global_transform();
+
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
- set_ignore_transform_notification(false);
_on_transform_changed();
if (contact_monitor) {
@@ -2927,7 +2933,10 @@ void PhysicalBone3D::_notification(int p_what) {
}
void PhysicalBone3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) {
+ set_ignore_transform_notification(true);
set_global_transform(p_state->get_transform());
+ set_ignore_transform_notification(false);
+
linear_velocity = p_state->get_linear_velocity();
angular_velocity = p_state->get_angular_velocity();
}
@@ -2937,16 +2946,20 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
return;
}
- set_ignore_transform_notification(true);
-
if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
_sync_body_state(p_state);
+ Transform3D old_transform = get_global_transform();
GDVIRTUAL_CALL(_integrate_forces, p_state);
+ Transform3D new_transform = get_global_transform();
+
+ if (new_transform != old_transform) {
+ // Update the physics server with the new transform, to prevent it from being overwritten at the sync below.
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
+ }
}
_sync_body_state(p_state);
- set_ignore_transform_notification(false);
_on_transform_changed();
Transform3D global_transform(p_state->get_transform());
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 8026b12c2b..3b1faca17e 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -501,8 +501,8 @@ void GeometryInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_aabb"), &GeometryInstance3D::get_aabb);
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::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_overlay", "get_material_overlay");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT), "set_material_override", "get_material_override");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT), "set_material_overlay", "get_material_overlay");
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,suffix:m"), "set_extra_cull_margin", "get_extra_cull_margin");
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index fb17dae832..bba3dc6d7d 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -445,7 +445,7 @@ bool AnimationMixer::is_active() const {
void AnimationMixer::set_root_node(const NodePath &p_path) {
root_node = p_path;
- clear_caches();
+ _clear_caches();
}
NodePath AnimationMixer::get_root_node() const {
@@ -454,7 +454,7 @@ NodePath AnimationMixer::get_root_node() const {
void AnimationMixer::set_deterministic(bool p_deterministic) {
deterministic = p_deterministic;
- clear_caches();
+ _clear_caches();
}
bool AnimationMixer::is_deterministic() const {
@@ -563,7 +563,7 @@ void AnimationMixer::_clear_audio_streams() {
void AnimationMixer::_clear_playing_caches() {
for (const TrackCache *E : playing_caches) {
if (ObjectDB::get_instance(E->object_id)) {
- E->object->call(SNAME("stop"));
+ E->object->call(SNAME("stop"), true);
}
}
playing_caches.clear();
@@ -667,6 +667,7 @@ bool AnimationMixer::_update_caches() {
track_value->init_value = reset_anim->track_get_key_value(rt, 0);
}
}
+
} break;
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
@@ -812,6 +813,7 @@ bool AnimationMixer::_update_caches() {
track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
}
}
+
} break;
case Animation::TYPE_AUDIO: {
TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
@@ -868,43 +870,26 @@ bool AnimationMixer::_update_caches() {
track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
- // TODO: Currently, misc type cannot be blended. In the future,
- // it should have a separate blend weight, just as bool is converted to 0 and 1.
+ // TODO: Currently, misc type cannot be blended.
+ // In the future, it should have a separate blend weight, just as bool is converted to 0 and 1.
// Then, it should provide the correct precedence value.
+ bool skip_update_mode_warning = false;
if (track_value->is_continuous) {
- switch (track_value->init_value.get_type()) {
- case Variant::NIL:
- case Variant::STRING_NAME:
- case Variant::NODE_PATH:
- case Variant::RID:
- case Variant::OBJECT:
- case Variant::CALLABLE:
- case Variant::SIGNAL:
- case Variant::DICTIONARY:
- case Variant::ARRAY:
- case Variant::PACKED_BYTE_ARRAY:
- case Variant::PACKED_INT32_ARRAY:
- case Variant::PACKED_INT64_ARRAY:
- case Variant::PACKED_FLOAT32_ARRAY:
- case Variant::PACKED_FLOAT64_ARRAY:
- case Variant::PACKED_STRING_ARRAY:
- case Variant::PACKED_VECTOR2_ARRAY:
- case Variant::PACKED_VECTOR3_ARRAY:
- case Variant::PACKED_COLOR_ARRAY: {
- WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
- track_value->is_continuous = false;
- break;
- }
- default: {
- }
+ if (!Animation::is_variant_interpolatable(track_value->init_value)) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
+ track_value->is_continuous = false;
+ skip_update_mode_warning = true;
+ }
+ if (track_value->init_value.is_string()) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm.");
}
}
- if (was_continuous != track_value->is_continuous) {
- WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
+ if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
}
if (was_using_angle != track_value->is_using_angle) {
- WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different interpolation types for rotation between some animations may be blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
}
}
@@ -950,9 +935,7 @@ bool AnimationMixer::_update_caches() {
void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
_blend_init();
if (_blend_pre_process(p_delta, track_count, track_map)) {
- if (!deterministic) {
- _blend_calc_total_weight();
- }
+ _blend_calc_total_weight();
_blend_process(p_delta, p_update_only);
_blend_apply();
_blend_post_process();
@@ -1024,7 +1007,8 @@ void AnimationMixer::_blend_init() {
} break;
case Animation::TYPE_VALUE: {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
- t->value = t->init_value;
+ t->value = Animation::cast_to_blendwise(t->init_value);
+ t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0;
} break;
case Animation::TYPE_BEZIER: {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
@@ -1111,7 +1095,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
if (!deterministic) {
- // If undeterministic, do normalization.
+ // If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
if (Math::is_zero_approx(track->total_weight)) {
continue;
@@ -1434,13 +1418,15 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU);
} else {
- if (t->init_value.get_type() == Variant::BOOL) {
- value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t());
- t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend);
- } else {
- value = Animation::subtract_variant(value, t->init_value);
- t->value = Animation::blend_variant(t->value, value, blend);
+ value = Animation::cast_to_blendwise(value);
+ if (t->init_value.is_array()) {
+ t->element_size = MAX(t->element_size.operator int(), (value.operator Array()).size());
+ } else if (t->init_value.is_string()) {
+ real_t length = Animation::subtract_variant((real_t)(value.operator Array()).size(), (real_t)(t->init_value.operator String()).length());
+ t->element_size = Animation::blend_variant(t->element_size, length, blend);
}
+ value = Animation::subtract_variant(value, Animation::cast_to_blendwise(t->init_value));
+ t->value = Animation::blend_variant(t->value, value, blend);
}
} else {
if (seeked) {
@@ -1710,10 +1696,23 @@ void AnimationMixer::_blend_apply() {
break; // Don't overwrite the value set by UPDATE_DISCRETE.
}
- if (t->init_value.get_type() == Variant::BOOL) {
- t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5);
- } else {
- t->object->set_indexed(t->subpath, t->value);
+ // Trim unused elements if init array/string is not blended.
+ if (t->value.is_array()) {
+ int actual_blended_size = (int)Math::round(Math::abs(t->element_size.operator real_t()));
+ if (actual_blended_size < (t->value.operator Array()).size()) {
+ real_t abs_weight = Math::abs(track->total_weight);
+ if (abs_weight >= 1.0) {
+ (t->value.operator Array()).resize(actual_blended_size);
+ } else if (t->init_value.is_string()) {
+ (t->value.operator Array()).resize(Animation::interpolate_variant((t->init_value.operator String()).length(), actual_blended_size, abs_weight));
+ }
+ }
+ }
+
+ // t->object isn't safe here, get instance from id (GH-85365).
+ Object *obj = ObjectDB::get_instance(t->object_id);
+ if (obj) {
+ obj->set_indexed(t->subpath, Animation::cast_from_blendwise(t->value, t->init_value.get_type()));
}
} break;
@@ -1789,7 +1788,7 @@ void AnimationMixer::_blend_apply() {
void AnimationMixer::_call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
// Separate function to use alloca() more efficiently
- const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
+ const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * p_params.size());
const Variant *args = p_params.ptr();
uint32_t argcount = p_params.size();
for (uint32_t i = 0; i < argcount; i++) {
@@ -1874,7 +1873,6 @@ bool AnimationMixer::is_reset_on_save_enabled() const {
return reset_on_save;
}
-#ifdef TOOLS_ENABLED
bool AnimationMixer::can_apply_reset() const {
return has_animation(SceneStringNames::get_singleton()->RESET);
}
@@ -1935,7 +1933,6 @@ void AnimationMixer::_build_backup_track_cache() {
if (asp) {
t->object->call(SNAME("set_stream"), Ref<AudioStream>());
}
- track = memnew(TrackCache); // Make disable this track cache.
} break;
default: {
} // The rest don't matter.
@@ -1965,29 +1962,6 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
return backup;
}
-Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
- if (!p_user_initiated && dummy) {
- return Ref<AnimatedValuesBackup>();
- }
- ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
-
- Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
- ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
-
- Ref<AnimatedValuesBackup> backup_current = make_backup();
- if (p_user_initiated) {
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Animation Apply Reset"));
- ur->add_do_method(this, "_reset");
- ur->add_undo_method(this, "_restore", backup_current);
- ur->commit_action();
- } else {
- reset();
- }
-
- return backup_current;
-}
-
void AnimationMixer::reset() {
ERR_FAIL_COND(!can_apply_reset());
@@ -2016,6 +1990,30 @@ void AnimationMixer::restore(const Ref<AnimatedValuesBackup> &p_backup) {
track_cache = HashMap<NodePath, AnimationMixer::TrackCache *>();
cache_valid = false;
}
+
+#ifdef TOOLS_ENABLED
+Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
+ if (!p_user_initiated && dummy) {
+ return Ref<AnimatedValuesBackup>();
+ }
+ ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
+
+ Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
+ ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
+
+ Ref<AnimatedValuesBackup> backup_current = make_backup();
+ if (p_user_initiated) {
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Animation Apply Reset"));
+ ur->add_do_method(this, "_reset");
+ ur->add_undo_method(this, "_restore", backup_current);
+ ur->commit_action();
+ } else {
+ reset();
+ }
+
+ return backup_current;
+}
#endif // TOOLS_ENABLED
/* -------------------------------------------- */
@@ -2134,6 +2132,9 @@ void AnimationMixer::_bind_methods() {
ADD_SIGNAL(MethodInfo(SNAME("animation_finished"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
ADD_SIGNAL(MethodInfo(SNAME("animation_started"), PropertyInfo(Variant::STRING_NAME, "anim_name")));
ADD_SIGNAL(MethodInfo(SNAME("caches_cleared")));
+
+ ClassDB::bind_method(D_METHOD("_reset"), &AnimationMixer::reset);
+ ClassDB::bind_method(D_METHOD("_restore", "backup"), &AnimationMixer::restore);
}
AnimationMixer::AnimationMixer() {
@@ -2142,3 +2143,76 @@ AnimationMixer::AnimationMixer() {
AnimationMixer::~AnimationMixer() {
}
+
+void AnimatedValuesBackup::set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) {
+ clear_data();
+
+ for (const KeyValue<NodePath, AnimationMixer::TrackCache *> &E : p_data) {
+ AnimationMixer::TrackCache *track = get_cache_copy(E.value);
+ if (!track) {
+ continue; // Some types of tracks do not get a copy and must be ignored.
+ }
+
+ data.insert(E.key, track);
+ }
+}
+
+HashMap<NodePath, AnimationMixer::TrackCache *> AnimatedValuesBackup::get_data() const {
+ HashMap<NodePath, AnimationMixer::TrackCache *> ret;
+ for (const KeyValue<NodePath, AnimationMixer::TrackCache *> &E : data) {
+ AnimationMixer::TrackCache *track = get_cache_copy(E.value);
+ ERR_CONTINUE(!track); // Backup shouldn't contain tracks that cannot be copied, this is a mistake.
+
+ ret.insert(E.key, track);
+ }
+ return ret;
+}
+
+void AnimatedValuesBackup::clear_data() {
+ for (KeyValue<NodePath, AnimationMixer::TrackCache *> &K : data) {
+ memdelete(K.value);
+ }
+ data.clear();
+}
+
+AnimationMixer::TrackCache *AnimatedValuesBackup::get_cache_copy(AnimationMixer::TrackCache *p_cache) const {
+ switch (p_cache->type) {
+ case Animation::TYPE_VALUE: {
+ AnimationMixer::TrackCacheValue *src = static_cast<AnimationMixer::TrackCacheValue *>(p_cache);
+ AnimationMixer::TrackCacheValue *tc = memnew(AnimationMixer::TrackCacheValue(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_POSITION_3D:
+ case Animation::TYPE_ROTATION_3D:
+ case Animation::TYPE_SCALE_3D: {
+ AnimationMixer::TrackCacheTransform *src = static_cast<AnimationMixer::TrackCacheTransform *>(p_cache);
+ AnimationMixer::TrackCacheTransform *tc = memnew(AnimationMixer::TrackCacheTransform(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_BLEND_SHAPE: {
+ AnimationMixer::TrackCacheBlendShape *src = static_cast<AnimationMixer::TrackCacheBlendShape *>(p_cache);
+ AnimationMixer::TrackCacheBlendShape *tc = memnew(AnimationMixer::TrackCacheBlendShape(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_BEZIER: {
+ AnimationMixer::TrackCacheBezier *src = static_cast<AnimationMixer::TrackCacheBezier *>(p_cache);
+ AnimationMixer::TrackCacheBezier *tc = memnew(AnimationMixer::TrackCacheBezier(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_AUDIO: {
+ AnimationMixer::TrackCacheAudio *src = static_cast<AnimationMixer::TrackCacheAudio *>(p_cache);
+ AnimationMixer::TrackCacheAudio *tc = memnew(AnimationMixer::TrackCacheAudio(*src));
+ return tc;
+ }
+
+ case Animation::TYPE_METHOD:
+ case Animation::TYPE_ANIMATION: {
+ // Nothing to do here.
+ } break;
+ }
+ return nullptr;
+}
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index 6aa050d1fb..0cd204b384 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -38,14 +38,12 @@
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_polyphonic.h"
-#ifdef TOOLS_ENABLED
class AnimatedValuesBackup;
-#endif // TOOLS_ENABLED
class AnimationMixer : public Node {
GDCLASS(AnimationMixer, Node);
-#ifdef TOOLS_ENABLED
friend AnimatedValuesBackup;
+#ifdef TOOLS_ENABLED
bool editing = false;
bool dummy = false;
#endif // TOOLS_ENABLED
@@ -143,6 +141,17 @@ protected:
Object *object = nullptr;
ObjectID object_id;
real_t total_weight = 0.0;
+
+ TrackCache() = default;
+ TrackCache(const TrackCache &p_other) :
+ root_motion(p_other.root_motion),
+ setup_pass(p_other.setup_pass),
+ type(p_other.type),
+ object(p_other.object),
+ object_id(p_other.object_id),
+ total_weight(p_other.total_weight) {}
+
+ virtual ~TrackCache() {}
};
struct TrackCacheTransform : public TrackCache {
@@ -161,9 +170,28 @@ protected:
Quaternion rot;
Vector3 scale;
+ TrackCacheTransform(const TrackCacheTransform &p_other) :
+ TrackCache(p_other),
+#ifndef _3D_DISABLED
+ node_3d(p_other.node_3d),
+ skeleton(p_other.skeleton),
+#endif
+ bone_idx(p_other.bone_idx),
+ loc_used(p_other.loc_used),
+ rot_used(p_other.rot_used),
+ scale_used(p_other.scale_used),
+ init_loc(p_other.init_loc),
+ init_rot(p_other.init_rot),
+ init_scale(p_other.init_scale),
+ loc(p_other.loc),
+ rot(p_other.rot),
+ scale(p_other.scale) {
+ }
+
TrackCacheTransform() {
type = Animation::TYPE_POSITION_3D;
}
+ ~TrackCacheTransform() {}
};
struct RootMotionCache {
@@ -177,7 +205,16 @@ protected:
float init_value = 0;
float value = 0;
int shape_index = -1;
+
+ TrackCacheBlendShape(const TrackCacheBlendShape &p_other) :
+ TrackCache(p_other),
+ mesh_3d(p_other.mesh_3d),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ shape_index(p_other.shape_index) {}
+
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
+ ~TrackCacheBlendShape() {}
};
struct TrackCacheValue : public TrackCache {
@@ -186,20 +223,45 @@ protected:
Vector<StringName> subpath;
bool is_continuous = false;
bool is_using_angle = false;
+ Variant element_size;
+
+ TrackCacheValue(const TrackCacheValue &p_other) :
+ TrackCache(p_other),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ subpath(p_other.subpath),
+ is_continuous(p_other.is_continuous),
+ is_using_angle(p_other.is_using_angle),
+ element_size(p_other.element_size) {}
+
TrackCacheValue() { type = Animation::TYPE_VALUE; }
+ ~TrackCacheValue() {
+ // Clear ref to avoid leaking.
+ init_value = Variant();
+ value = Variant();
+ }
};
struct TrackCacheMethod : public TrackCache {
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
+ ~TrackCacheMethod() {}
};
struct TrackCacheBezier : public TrackCache {
real_t init_value = 0.0;
real_t value = 0.0;
Vector<StringName> subpath;
+
+ TrackCacheBezier(const TrackCacheBezier &p_other) :
+ TrackCache(p_other),
+ init_value(p_other.init_value),
+ value(p_other.value),
+ subpath(p_other.subpath) {}
+
TrackCacheBezier() {
type = Animation::TYPE_BEZIER;
}
+ ~TrackCacheBezier() {}
};
// Audio stream information for each audio stream placed on the track.
@@ -225,9 +287,16 @@ protected:
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
+ TrackCacheAudio(const TrackCacheAudio &p_other) :
+ TrackCache(p_other),
+ audio_stream(p_other.audio_stream),
+ audio_stream_playback(p_other.audio_stream_playback),
+ playing_streams(p_other.playing_streams) {}
+
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
}
+ ~TrackCacheAudio() {}
};
struct TrackCacheAnimation : public TrackCache {
@@ -236,6 +305,7 @@ protected:
TrackCacheAnimation() {
type = Animation::TYPE_ANIMATION;
}
+ ~TrackCacheAnimation() {}
};
RootMotionCache root_motion_cache;
@@ -350,35 +420,40 @@ public:
void set_reset_on_save_enabled(bool p_enabled);
bool is_reset_on_save_enabled() const;
+ bool can_apply_reset() const;
+ void _build_backup_track_cache();
+ Ref<AnimatedValuesBackup> make_backup();
+ void restore(const Ref<AnimatedValuesBackup> &p_backup);
+ void reset();
+
#ifdef TOOLS_ENABLED
+ Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
+
void set_editing(bool p_editing);
bool is_editing() const;
void set_dummy(bool p_dummy);
bool is_dummy() const;
-
- bool can_apply_reset() const;
- void _build_backup_track_cache();
- Ref<AnimatedValuesBackup> make_backup();
- Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
- void restore(const Ref<AnimatedValuesBackup> &p_backup);
- void reset();
#endif // TOOLS_ENABLED
+
AnimationMixer();
~AnimationMixer();
};
-#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
HashMap<NodePath, AnimationMixer::TrackCache *> data;
public:
- void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) { data = p_data; };
- HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const { return data; };
+ void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data);
+ HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const;
+ void clear_data();
+
+ AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const;
+
+ ~AnimatedValuesBackup() { clear_data(); }
};
-#endif
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeProcess);
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeMethod);
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index b3285d4cfc..6e7aec379b 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -151,6 +151,7 @@ void AnimationPlayer::_notification(int p_what) {
if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) {
set_active(true);
play(autoplay);
+ _check_immediately_after_start();
}
} break;
}
@@ -521,8 +522,9 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) {
return;
}
- playback.current.pos = p_time;
+ _check_immediately_after_start();
+ playback.current.pos = p_time;
if (!playback.current.from) {
if (playback.assigned) {
ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned));
@@ -536,6 +538,18 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) {
playback.seeked = true;
if (p_update) {
_process_animation(0, p_update_only);
+ playback.seeked = false; // If animation was proceeded here, no more seek in internal process.
+ }
+}
+
+void AnimationPlayer::advance(double p_time) {
+ _check_immediately_after_start();
+ AnimationMixer::advance(p_time);
+}
+
+void AnimationPlayer::_check_immediately_after_start() {
+ if (playback.started) {
+ _process_animation(0); // Force process current key for Discrete/Method/Audio/AnimationPlayback. Then, started flag is cleared.
}
}
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 51beb67260..74f9323e2b 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -111,6 +111,7 @@ private:
void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false);
void _blend_playback_data(double p_delta, bool p_started);
void _stop_internal(bool p_reset, bool p_keep_state);
+ void _check_immediately_after_start();
bool playing = false;
@@ -183,6 +184,8 @@ public:
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+ virtual void advance(double p_time) override;
+
AnimationPlayer();
~AnimationPlayer();
};
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index c778129eb6..d6a6af3f08 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -104,7 +104,15 @@ Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePat
CHECK_VALID();
Vector<StringName> property_subnames = p_property.get_as_property_path().get_subnames();
- if (!_validate_type_match(p_target->get_indexed(property_subnames), p_to)) {
+#ifdef DEBUG_ENABLED
+ bool prop_valid;
+ const Variant &prop_value = p_target->get_indexed(property_subnames, &prop_valid);
+ ERR_FAIL_COND_V_MSG(!prop_valid, nullptr, vformat("The tweened property \"%s\" does not exist in object \"%s\".", p_property, p_target));
+#else
+ const Variant &prop_value = p_target->get_indexed(property_subnames);
+#endif
+
+ if (!_validate_type_match(prop_value, p_to)) {
return nullptr;
}
@@ -412,13 +420,8 @@ Variant Tween::interpolate_variant(const Variant &p_initial_val, const Variant &
ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant());
ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant());
- // Special case for bool.
- if (p_initial_val.get_type() == Variant::BOOL) {
- return run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration) >= 0.5;
- }
-
Variant ret = Animation::add_variant(p_initial_val, p_delta_val);
- ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration));
+ ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration), p_initial_val.is_string());
return ret;
}
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 79cd1056dd..5603b2dbe4 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -72,6 +72,11 @@ void SceneDebugger::deinitialize() {
}
}
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
#ifdef DEBUG_ENABLED
Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
SceneTree *scene_tree = SceneTree::get_singleton();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 669ce11e5d..f250662be0 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -89,6 +89,10 @@ void ColorPicker::_notification(int p_what) {
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle);
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle);
+ if (current_shape != SHAPE_NONE) {
+ btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
+ }
+
internal_margin->begin_bulk_theme_override();
internal_margin->add_theme_constant_override(SNAME("margin_bottom"), theme_cache.content_margin);
internal_margin->add_theme_constant_override(SNAME("margin_left"), theme_cache.content_margin);
@@ -1792,8 +1796,6 @@ ColorPicker::ColorPicker() {
shape_popup->set_item_checked(current_shape, true);
shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape));
- btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
-
add_mode(new ColorModeRGB(this));
add_mode(new ColorModeHSV(this));
add_mode(new ColorModeRAW(this));
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index c7ff5980cb..ed54bd000c 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1653,6 +1653,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) {
data.custom_minimum_size = p_custom;
update_minimum_size();
+ update_configuration_warnings();
}
Size2 Control::get_custom_minimum_size() const {
@@ -1831,9 +1832,18 @@ bool Control::has_point(const Point2 &p_point) const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_filter, 3);
+
+ if (data.mouse_filter == p_filter) {
+ return;
+ }
+
data.mouse_filter = p_filter;
notify_property_list_changed();
update_configuration_warnings();
+
+ if (get_viewport()) {
+ get_viewport()->_gui_update_mouse_over();
+ }
}
Control::MouseFilter Control::get_mouse_filter() const {
@@ -3568,6 +3578,8 @@ void Control::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF);
BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER);
BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT);
BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index abbdc42fa4..db1bd3a346 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -368,6 +368,8 @@ public:
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
+ NOTIFICATION_MOUSE_ENTER_SELF = 60,
+ NOTIFICATION_MOUSE_EXIT_SELF = 61,
};
// Editor plugin interoperability.
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 8dddbf78cf..69023d2056 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -2015,7 +2015,7 @@ GraphEdit::GraphEdit() {
top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
connections_layer = memnew(Control);
- add_child(connections_layer, false);
+ add_child(connections_layer, false, INTERNAL_MODE_FRONT);
connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
connections_layer->set_name("_connection_layer");
connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset.
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 343301e9c4..02d44caa1c 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1431,11 +1431,11 @@ void ItemList::force_update_list_size() {
}
}
- for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
- items.write[j].rect_cache.size.y = max_h;
- }
-
if (all_fit) {
+ for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
+ items.write[j].rect_cache.size.y = max_h;
+ }
+
float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
float max = MAX(page, ofs.y + max_h);
if (auto_height) {
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 0d48cb1549..2fbd29b048 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
+#include "scene/gui/container.h"
#include "scene/theme/theme_db.h"
#include "servers/text_server.h"
@@ -44,6 +45,7 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
autowrap_mode = p_mode;
lines_dirty = true;
queue_redraw();
+ update_configuration_warnings();
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
update_minimum_size();
@@ -327,6 +329,19 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
PackedStringArray Label::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
+ // FIXME: This is not ideal and the sizing model should be fixed,
+ // but for now we have to warn about this impossible to resolve combination.
+ // See GH-83546.
+ if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) {
+ // If the Label happens to be the root node of the edited scene, we don't need
+ // to check what its parent is. It's going to be some node from the editor tree
+ // and it can be a container, but that makes no difference to the user.
+ Container *parent_container = Object::cast_to<Container>(get_parent_control());
+ if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) {
+ warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container."));
+ }
+ }
+
// Ensure that the font can render all of the required glyphs.
Ref<Font> font;
if (settings.is_valid()) {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 0cda27ec24..d6b8dd0202 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -1487,7 +1487,11 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
}
void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
- ERR_FAIL_COND_MSG(p_submenu.validate_node_name() != p_submenu, "Invalid node name for submenu, the following characters are not allowed:\n" + String::get_invalid_node_name_characters());
+ String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names.
+ if (submenu_name_safe.validate_node_name() != submenu_name_safe) {
+ ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true)));
+ }
+
Item item;
item.text = p_label;
item.xl_text = atr(p_label);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 40f8736244..35d3a4dc14 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -830,37 +830,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
prefix = segment + prefix;
}
}
- if (!prefix.is_empty()) {
- Ref<Font> font = theme_cache.normal_font;
- int font_size = theme_cache.normal_font_size;
-
- ItemFont *font_it = _find_font(l.from);
- if (font_it) {
- if (font_it->font.is_valid()) {
- font = font_it->font;
- }
- if (font_it->font_size > 0) {
- font_size = font_it->font_size;
- }
- }
- ItemFontSize *font_size_it = _find_font_size(l.from);
- if (font_size_it && font_size_it->font_size > 0) {
- font_size = font_size_it->font_size;
- }
- if (rtl) {
- float offx = 0.0f;
- if (!lrtl && p_frame == main) { // Skip Scrollbar.
- offx -= scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- } else {
- float offx = 0.0f;
- if (lrtl && p_frame == main) { // Skip Scrollbar.
- offx += scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- }
- }
// Draw dropcap.
int dc_lines = l.text_buf->get_dropcap_lines();
@@ -924,6 +893,30 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} break;
}
+ if (!prefix.is_empty() && line == 0) {
+ Ref<Font> font = theme_cache.normal_font;
+ int font_size = theme_cache.normal_font_size;
+
+ ItemFont *font_it = _find_font(l.from);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ }
+ if (font_it->font_size > 0) {
+ font_size = font_it->font_size;
+ }
+ }
+ ItemFontSize *font_size_it = _find_font_size(l.from);
+ if (font_size_it && font_size_it->font_size > 0) {
+ font_size = font_size_it->font_size;
+ }
+ if (rtl) {
+ font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ } else {
+ font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ }
+ }
+
if (line <= dc_lines) {
if (rtl) {
off.x -= h_off;
@@ -977,11 +970,20 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
coff.x = rect.size.width - table->columns[col].width - coff.x;
}
if (row % 2 == 0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true);
+ Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
} else {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true);
+ Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
+ }
+ Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border;
+ if (bc.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false);
}
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false);
}
for (int j = 0; j < (int)frame->lines.size(); j++) {
@@ -1204,14 +1206,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Vector2 ul_start;
bool ul_started = false;
+ Color ul_color_prev;
Color ul_color;
Vector2 dot_ul_start;
bool dot_ul_started = false;
+ Color dot_ul_color_prev;
Color dot_ul_color;
Vector2 st_start;
bool st_started = false;
+ Color st_color_prev;
Color st_color;
for (int i = 0; i < gl_size; i++) {
@@ -1219,16 +1224,18 @@ 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);
Color font_color = _find_color(it, p_base_color);
if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) {
- if (ul_started && font_color != ul_color) {
+ if (ul_started && font_color != ul_color_prev) {
float y_off = TS->shaped_text_get_underline_position(rid);
float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color_prev = font_color;
ul_color = font_color;
ul_color.a *= 0.5;
} else if (!ul_started) {
ul_started = true;
ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color_prev = font_color;
ul_color = font_color;
ul_color.a *= 0.5;
}
@@ -1239,16 +1246,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
}
if (_find_hint(it, nullptr) && underline_hint) {
- if (dot_ul_started && font_color != dot_ul_color) {
+ if (dot_ul_started && font_color != dot_ul_color_prev) {
float y_off = TS->shaped_text_get_underline_position(rid);
float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
dot_ul_start = p_ofs + Vector2(off.x, off.y);
+ dot_ul_color_prev = font_color;
dot_ul_color = font_color;
dot_ul_color.a *= 0.5;
} else if (!dot_ul_started) {
dot_ul_started = true;
dot_ul_start = p_ofs + Vector2(off.x, off.y);
+ dot_ul_color_prev = font_color;
dot_ul_color = font_color;
dot_ul_color.a *= 0.5;
}
@@ -1259,16 +1268,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
}
if (_find_strikethrough(it)) {
- if (st_started && font_color != st_color) {
+ if (st_started && font_color != st_color_prev) {
float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
st_start = p_ofs + Vector2(off.x, off.y);
+ st_color_prev = font_color;
st_color = font_color;
st_color.a *= 0.5;
} else if (!st_started) {
st_started = true;
st_start = p_ofs + Vector2(off.x, off.y);
+ st_color_prev = font_color;
st_color = font_color;
st_color.a *= 0.5;
}
@@ -3214,6 +3225,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_
ERR_FAIL_COND(p_image.is_null());
ERR_FAIL_COND(p_image->get_width() == 0);
ERR_FAIL_COND(p_image->get_height() == 0);
+ ERR_FAIL_COND(p_width < 0);
+ ERR_FAIL_COND(p_height < 0);
+
ItemImage *item = memnew(ItemImage);
if (p_region.has_area()) {
@@ -3247,6 +3261,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask>
ERR_FAIL_COND(p_image->get_height() == 0);
}
+ ERR_FAIL_COND(p_width < 0);
+ ERR_FAIL_COND(p_height < 0);
+
bool reshape = false;
Item *it = main;
@@ -3298,6 +3315,9 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask>
}
}
if ((p_mask & UPDATE_SIZE) || (p_mask & UPDATE_REGION) || (p_mask & UPDATE_TEXTURE)) {
+ ERR_FAIL_COND(item->image.is_null());
+ ERR_FAIL_COND(item->image->get_width() == 0);
+ ERR_FAIL_COND(item->image->get_height() == 0);
Size2 new_size = _get_image_size(item->image, item->rq_size.width, item->rq_size.height, item->region);
if (item->size != new_size) {
reshape = true;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index a4c239cf53..1d06ce05ae 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -4544,6 +4544,8 @@ TreeItem *Tree::get_selected() const {
void Tree::set_selected(TreeItem *p_item, int p_column) {
ERR_FAIL_INDEX(p_column, columns.size());
ERR_FAIL_NULL(p_item);
+ ERR_FAIL_COND_MSG(p_item->get_tree() != this, "The provided TreeItem does not belong to this Tree. Ensure that the TreeItem is a part of the Tree before setting it as selected.");
+
select_single_item(p_item, get_root(), p_column);
}
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index a350b97bc8..4ee81e5cb0 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -462,6 +462,10 @@ void CanvasItem::set_as_top_level(bool p_top_level) {
_enter_canvas();
_notify_transform();
+
+ if (get_viewport()) {
+ get_viewport()->canvas_item_top_level_changed();
+ }
}
void CanvasItem::_top_level_changed() {
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 9ce036616b..e730f47607 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1870,7 +1870,7 @@ void Node::_acquire_unique_name_in_owner() {
Node **which = data.owner->data.owned_unique_nodes.getptr(key);
if (which != nullptr && *which != this) {
String which_path = is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which);
- WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name."),
+ WARN_PRINT(vformat("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name.",
get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), which_path, which_path));
data.unique_name_in_owner = false;
return;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index ebe4f4c59d..cf80bd6c6f 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -515,6 +515,10 @@ bool SceneTree::process(double p_time) {
_flush_delete_queue();
+ if (unlikely(pending_new_scene)) {
+ _flush_scene_change();
+ }
+
process_timers(p_time, false); //go through timers
process_tweens(p_time, false);
@@ -550,10 +554,6 @@ bool SceneTree::process(double p_time) {
#endif // _3D_DISABLED
#endif // TOOLS_ENABLED
- if (unlikely(pending_new_scene)) {
- _flush_scene_change();
- }
-
return _quit;
}
@@ -1847,7 +1847,7 @@ SceneTree::SceneTree() {
ProjectSettings::get_singleton()->set("rendering/environment/defaults/default_environment", "");
} else {
// File was erased, notify user.
- ERR_PRINT(RTR("Default Environment as specified in the project setting \"rendering/environment/defaults/default_environment\" could not be loaded."));
+ ERR_PRINT("Default Environment as specified in the project setting \"rendering/environment/defaults/default_environment\" could not be loaded.");
}
}
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 2b28f21f57..e9ea09a3b1 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2408,8 +2408,8 @@ void Viewport::_gui_hide_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui_release_focus();
}
- if (gui.mouse_over == p_control) {
- _drop_mouse_over();
+ if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
+ _drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
@@ -2431,8 +2431,8 @@ void Viewport::_gui_remove_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui.key_focus = nullptr;
}
- if (gui.mouse_over == p_control) {
- _drop_mouse_over();
+ if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
+ _drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
@@ -2442,6 +2442,94 @@ void Viewport::_gui_remove_control(Control *p_control) {
}
}
+void Viewport::canvas_item_top_level_changed() {
+ _gui_update_mouse_over();
+}
+
+void Viewport::_gui_update_mouse_over() {
+ if (gui.mouse_over == nullptr || gui.mouse_over_hierarchy.is_empty()) {
+ return;
+ }
+
+ // Rebuild the mouse over hierarchy.
+ LocalVector<Control *> new_mouse_over_hierarchy;
+ LocalVector<Control *> needs_enter;
+ LocalVector<int> needs_exit;
+
+ CanvasItem *ancestor = gui.mouse_over;
+ bool removing = false;
+ bool reached_top = false;
+ while (ancestor) {
+ Control *ancestor_control = Object::cast_to<Control>(ancestor);
+ if (ancestor_control) {
+ int found = gui.mouse_over_hierarchy.find(ancestor_control);
+ if (found >= 0) {
+ // Remove the node if the propagation chain has been broken or it is now MOUSE_FILTER_IGNORE.
+ if (removing || ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_IGNORE) {
+ needs_exit.push_back(found);
+ }
+ }
+ if (found == 0) {
+ if (removing) {
+ // Stop if the chain has been broken and the top of the hierarchy has been reached.
+ break;
+ }
+ reached_top = true;
+ }
+ if (!removing && ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
+ new_mouse_over_hierarchy.push_back(ancestor_control);
+ // Add the node if it was not found and it is now not MOUSE_FILTER_IGNORE.
+ if (found < 0) {
+ needs_enter.push_back(ancestor_control);
+ }
+ }
+ if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
+ // MOUSE_FILTER_STOP breaks the propagation chain.
+ if (reached_top) {
+ break;
+ }
+ removing = true;
+ }
+ }
+ if (ancestor->is_set_as_top_level()) {
+ // Top level breaks the propagation chain.
+ if (reached_top) {
+ break;
+ } else {
+ removing = true;
+ ancestor = Object::cast_to<CanvasItem>(ancestor->get_parent());
+ continue;
+ }
+ }
+ ancestor = ancestor->get_parent_item();
+ }
+ if (needs_exit.is_empty() && needs_enter.is_empty()) {
+ return;
+ }
+
+ // Send Mouse Exit Self notification.
+ if (gui.mouse_over && !needs_exit.is_empty() && needs_exit[0] == (int)gui.mouse_over_hierarchy.size() - 1) {
+ gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
+ gui.mouse_over = nullptr;
+ }
+
+ // Send Mouse Exit notifications.
+ for (int exit_control_index : needs_exit) {
+ gui.mouse_over_hierarchy[exit_control_index]->notification(Control::NOTIFICATION_MOUSE_EXIT);
+ }
+
+ // Update the mouse over hierarchy.
+ gui.mouse_over_hierarchy.resize(new_mouse_over_hierarchy.size());
+ for (int i = 0; i < (int)new_mouse_over_hierarchy.size(); i++) {
+ gui.mouse_over_hierarchy[i] = new_mouse_over_hierarchy[new_mouse_over_hierarchy.size() - 1 - i];
+ }
+
+ // Send Mouse Enter notifications.
+ for (int i = needs_enter.size() - 1; i >= 0; i--) {
+ needs_enter[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
+ }
+}
+
Window *Viewport::get_base_window() const {
ERR_READ_THREAD_GUARD_V(nullptr);
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
@@ -3069,16 +3157,60 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
// Look for Controls at mouse position.
Control *over = gui_find_control(p_pos);
bool notify_embedded_viewports = false;
- if (over != gui.mouse_over) {
- if (gui.mouse_over) {
- _drop_mouse_over();
+ if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) {
+ // Find the common ancestor of `gui.mouse_over` and `over`.
+ Control *common_ancestor = nullptr;
+ LocalVector<Control *> over_ancestors;
+
+ if (over) {
+ // Get all ancestors that the mouse is currently over and need an enter signal.
+ CanvasItem *ancestor = over;
+ while (ancestor) {
+ Control *ancestor_control = Object::cast_to<Control>(ancestor);
+ if (ancestor_control) {
+ if (ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) {
+ int found = gui.mouse_over_hierarchy.find(ancestor_control);
+ if (found >= 0) {
+ common_ancestor = gui.mouse_over_hierarchy[found];
+ break;
+ }
+ over_ancestors.push_back(ancestor_control);
+ }
+ if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) {
+ // MOUSE_FILTER_STOP breaks the propagation chain.
+ break;
+ }
+ }
+ if (ancestor->is_set_as_top_level()) {
+ // Top level breaks the propagation chain.
+ break;
+ }
+ ancestor = ancestor->get_parent_item();
+ }
+ }
+
+ if (gui.mouse_over || !gui.mouse_over_hierarchy.is_empty()) {
+ // Send Mouse Exit Self and Mouse Exit notifications.
+ _drop_mouse_over(common_ancestor);
} else {
_drop_physics_mouseover();
}
- gui.mouse_over = over;
if (over) {
- over->notification(Control::NOTIFICATION_MOUSE_ENTER);
+ gui.mouse_over = over;
+ gui.mouse_over_hierarchy.reserve(gui.mouse_over_hierarchy.size() + over_ancestors.size());
+
+ // Send Mouse Enter notifications to parents first.
+ for (int i = over_ancestors.size() - 1; i >= 0; i--) {
+ over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER);
+ gui.mouse_over_hierarchy.push_back(over_ancestors[i]);
+ }
+
+ // Send Mouse Enter Self notification.
+ if (gui.mouse_over) {
+ gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF);
+ }
+
notify_embedded_viewports = true;
}
}
@@ -3119,7 +3251,7 @@ void Viewport::_mouse_leave_viewport() {
notification(NOTIFICATION_VP_MOUSE_EXIT);
}
-void Viewport::_drop_mouse_over() {
+void Viewport::_drop_mouse_over(Control *p_until_control) {
_gui_cancel_tooltip();
SubViewportContainer *c = Object::cast_to<SubViewportContainer>(gui.mouse_over);
if (c) {
@@ -3131,10 +3263,19 @@ void Viewport::_drop_mouse_over() {
v->_mouse_leave_viewport();
}
}
- if (gui.mouse_over->is_inside_tree()) {
- gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT);
+ if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
+ gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
}
gui.mouse_over = nullptr;
+
+ // Send Mouse Exit notifications to children first. Don't send to p_until_control or above.
+ int notification_until = p_until_control ? gui.mouse_over_hierarchy.find(p_until_control) + 1 : 0;
+ for (int i = gui.mouse_over_hierarchy.size() - 1; i >= notification_until; i--) {
+ if (gui.mouse_over_hierarchy[i]->is_inside_tree()) {
+ gui.mouse_over_hierarchy[i]->notification(Control::NOTIFICATION_MOUSE_EXIT);
+ }
+ }
+ gui.mouse_over_hierarchy.resize(notification_until);
}
void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 65777c973f..82a9bfc438 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -361,6 +361,7 @@ private:
BitField<MouseButtonMask> mouse_focus_mask;
Control *key_focus = nullptr;
Control *mouse_over = nullptr;
+ LocalVector<Control *> mouse_over_hierarchy;
Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr.
Window *windowmanager_window_over = nullptr; // Only used in root Viewport.
Control *drag_mouse_over = nullptr;
@@ -429,6 +430,7 @@ private:
void _gui_remove_control(Control *p_control);
void _gui_hide_control(Control *p_control);
+ void _gui_update_mouse_over();
void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control);
void _gui_set_drag_preview(Control *p_base, Control *p_control);
@@ -455,7 +457,7 @@ private:
void _canvas_layer_add(CanvasLayer *p_canvas_layer);
void _canvas_layer_remove(CanvasLayer *p_canvas_layer);
- void _drop_mouse_over();
+ void _drop_mouse_over(Control *p_until_control = nullptr);
void _drop_mouse_focus();
void _drop_physics_mouseover(bool p_paused_only = false);
@@ -494,6 +496,7 @@ protected:
public:
void canvas_parent_mark_dirty(Node *p_node);
+ void canvas_item_top_level_changed();
uint64_t get_processed_events_count() const { return event_count; }
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 2c28dc31d6..823b0c6f5b 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -681,6 +681,9 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) {
void Window::_event_callback(DisplayServer::WindowEvent p_event) {
switch (p_event) {
case DisplayServer::WINDOW_EVENT_MOUSE_ENTER: {
+ if (!is_inside_tree()) {
+ return;
+ }
Window *root = get_tree()->get_root();
if (root->gui.windowmanager_window_over) {
#ifdef DEV_ENABLED
@@ -696,6 +699,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
}
} break;
case DisplayServer::WINDOW_EVENT_MOUSE_EXIT: {
+ if (!is_inside_tree()) {
+ return;
+ }
Window *root = get_tree()->get_root();
if (!root->gui.windowmanager_window_over) {
#ifdef DEV_ENABLED
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index b7c98b0ea9..3727c788fd 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -1220,6 +1220,7 @@ void unregister_scene_types() {
PhysicalSkyMaterial::cleanup_shader();
PanoramaSkyMaterial::cleanup_shader();
ProceduralSkyMaterial::cleanup_shader();
+ FogMaterial::cleanup_shader();
#endif // _3D_DISABLED
ParticleProcessMaterial::finish_shaders();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index af1f9da2b5..b1b3bab937 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -5476,469 +5476,622 @@ bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_in
}
// Helper math functions for Variant.
+bool Animation::is_variant_interpolatable(const Variant p_value) {
+ Variant::Type type = p_value.get_type();
+ return (type >= Variant::BOOL && type <= Variant::STRING_NAME) || type == Variant::ARRAY || type >= Variant::PACKED_INT32_ARRAY; // PackedByteArray is unsigned, so it would be better to ignore since blending uses float.
+}
+
+Variant Animation::cast_to_blendwise(const Variant p_value) {
+ switch (p_value.get_type()) {
+ case Variant::BOOL:
+ case Variant::INT: {
+ return p_value.operator real_t();
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ return string_to_array(p_value);
+ } break;
+ case Variant::RECT2I: {
+ return p_value.operator Rect2();
+ } break;
+ case Variant::VECTOR2I: {
+ return p_value.operator Vector2();
+ } break;
+ case Variant::VECTOR3I: {
+ return p_value.operator Vector3();
+ } break;
+ case Variant::VECTOR4I: {
+ return p_value.operator Vector4();
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ return p_value.operator PackedFloat32Array();
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ return p_value.operator PackedFloat64Array();
+ } break;
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
+Variant Animation::cast_from_blendwise(const Variant p_value, const Variant::Type p_type) {
+ switch (p_type) {
+ case Variant::BOOL: {
+ return p_value.operator real_t() >= 0.5;
+ } break;
+ case Variant::INT: {
+ return (int)Math::round(p_value.operator real_t());
+ } break;
+ case Variant::STRING: {
+ return array_to_string(p_value);
+ } break;
+ case Variant::STRING_NAME: {
+ return StringName(array_to_string(p_value));
+ } break;
+ case Variant::RECT2I: {
+ return Rect2i(p_value.operator Rect2().round());
+ } break;
+ case Variant::VECTOR2I: {
+ return Vector2i(p_value.operator Vector2().round());
+ } break;
+ case Variant::VECTOR3I: {
+ return Vector3i(p_value.operator Vector3().round());
+ } break;
+ case Variant::VECTOR4I: {
+ return Vector4i(p_value.operator Vector4().round());
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ PackedFloat32Array old_val = p_value.operator PackedFloat32Array();
+ PackedInt32Array new_val;
+ new_val.resize(old_val.size());
+ int *new_val_w = new_val.ptrw();
+ for (int i = 0; i < old_val.size(); i++) {
+ new_val_w[i] = (int32_t)Math::round(old_val[i]);
+ }
+ return new_val;
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ PackedFloat64Array old_val = p_value.operator PackedFloat64Array();
+ PackedInt64Array new_val;
+ for (int i = 0; i < old_val.size(); i++) {
+ new_val.push_back((int64_t)Math::round(old_val[i]));
+ }
+ return new_val;
+ } break;
+ default: {
+ } break;
+ }
+ return p_value;
+}
+
+Variant Animation::string_to_array(const Variant p_value) {
+ if (!p_value.is_string()) {
+ return p_value;
+ };
+ const String &str = p_value.operator String();
+ PackedFloat32Array arr;
+ for (int i = 0; i < str.length(); i++) {
+ arr.push_back((float)str[i]);
+ }
+ return arr;
+}
+
+Variant Animation::array_to_string(const Variant p_value) {
+ if (!p_value.is_array()) {
+ return p_value;
+ };
+ const PackedFloat32Array &arr = p_value.operator PackedFloat32Array();
+ String str;
+ for (int i = 0; i < arr.size(); i++) {
+ char32_t c = (char32_t)Math::round(arr[i]);
+ if (c == 0 || (c & 0xfffff800) == 0xd800 || c > 0x10ffff) {
+ c = ' ';
+ }
+ str += c;
+ }
+ return str;
+}
+
Variant Animation::add_variant(const Variant &a, const Variant &b) {
- if (a.get_type() != b.get_type()) {
+ if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::BOOL: {
- return (a.operator real_t()) + (b.operator real_t()); // It is cast for interpolation.
- }
+ } break;
+ case Variant::FLOAT: {
+ return (a.operator real_t()) + (b.operator real_t());
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position, ra.size + rb.size);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(ra.position + rb.position, ra.size + rb.size);
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal, pa.d + pb.d);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position, aa.size + ab.size);
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()) * (b.operator Basis());
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * (b.operator Quaternion());
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * (b.operator Transform2D());
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * (b.operator Transform3D());
- }
+ } break;
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(add_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
+ } break;
+ case Variant::BOOL:
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ // Specialized for Tween.
+ return b;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return Variant::evaluate(Variant::OP_ADD, a, b);
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = add_variant(arr_a[i], arr_b[i]);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ result.resize(max_size);
+ if (is_a_larger) {
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = add_variant(arr_a[i], lesser_last);
+ }
+ } else {
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = add_variant(lesser_last, arr_b[i]);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return Variant::evaluate(Variant::OP_ADD, a, b);
}
Variant Animation::subtract_variant(const Variant &a, const Variant &b) {
- if (a.get_type() != b.get_type()) {
+ if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::BOOL: {
- return (a.operator real_t()) - (b.operator real_t()); // It is cast for interpolation.
- }
+ } break;
+ case Variant::FLOAT: {
+ return (a.operator real_t()) - (b.operator real_t());
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position - rb.position, ra.size - rb.size);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(ra.position - rb.position, ra.size - rb.size);
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal - pb.normal, pa.d - pb.d);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position - ab.position, aa.size - ab.size);
- }
+ } break;
case Variant::BASIS: {
return (b.operator Basis()).inverse() * (a.operator Basis());
- }
+ } break;
case Variant::QUATERNION: {
return (b.operator Quaternion()).inverse() * (a.operator Quaternion());
- }
+ } break;
case Variant::TRANSFORM2D: {
return (b.operator Transform2D()).affine_inverse() * (a.operator Transform2D());
- }
+ } break;
case Variant::TRANSFORM3D: {
return (b.operator Transform3D()).affine_inverse() * (a.operator Transform3D());
- }
+ } break;
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(subtract_variant(cast_to_blendwise(a), cast_to_blendwise(b)), a.get_type());
+ } break;
+ case Variant::BOOL:
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ // Specialized for Tween.
+ return a;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = subtract_variant(arr_a[i], arr_b[i]);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ result.resize(max_size);
+ if (is_a_larger) {
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = subtract_variant(arr_a[i], lesser_last);
+ }
+ } else {
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = subtract_variant(lesser_last, arr_b[i]);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return Variant::evaluate(Variant::OP_SUBTRACT, a, b);
}
Variant Animation::blend_variant(const Variant &a, const Variant &b, float c) {
- if (a.get_type() != b.get_type()) {
- if (a.is_num() && b.is_num()) {
- double va = a;
- double vb = b;
- return va + vb * c;
- }
+ if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::INT: {
- return int64_t((a.operator int64_t()) + (b.operator int64_t()) * c + 0.5);
- }
+ } break;
case Variant::FLOAT: {
- return (a.operator double()) + (b.operator double()) * c;
- }
+ return (a.operator real_t()) + (b.operator real_t()) * c;
+ } break;
case Variant::VECTOR2: {
return (a.operator Vector2()) + (b.operator Vector2()) * c;
- }
- case Variant::VECTOR2I: {
- const Vector2i va = a.operator Vector2i();
- const Vector2i vb = b.operator Vector2i();
- return Vector2i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5));
- }
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position + rb.position * c, ra.size + rb.size * c);
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(int32_t(ra.position.x + rb.position.x * c + 0.5), int32_t(ra.position.y + rb.position.y * c + 0.5), int32_t(ra.size.x + rb.size.x * c + 0.5), int32_t(ra.size.y + rb.size.y * c + 0.5));
- }
+ } break;
case Variant::VECTOR3: {
return (a.operator Vector3()) + (b.operator Vector3()) * c;
- }
- case Variant::VECTOR3I: {
- const Vector3i va = a.operator Vector3i();
- const Vector3i vb = b.operator Vector3i();
- return Vector3i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5));
- }
+ } break;
case Variant::VECTOR4: {
return (a.operator Vector4()) + (b.operator Vector4()) * c;
- }
- case Variant::VECTOR4I: {
- const Vector4i va = a.operator Vector4i();
- const Vector4i vb = b.operator Vector4i();
- return Vector4i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5), int32_t(va.w + vb.w * c + 0.5));
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal + pb.normal * c, pa.d + pb.d * c);
- }
+ } break;
case Variant::COLOR: {
return (a.operator Color()) + (b.operator Color()) * c;
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position + ab.position * c, aa.size + ab.size * c);
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()) + (b.operator Basis()) * c;
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()) * Quaternion().slerp((b.operator Quaternion()), c);
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()) * Transform2D().interpolate_with((b.operator Transform2D()), c);
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()) * Transform3D().interpolate_with((b.operator Transform3D()), c);
- }
+ } break;
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY: {
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(blend_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ Array arr_a = cast_to_blendwise(a);
+ Array arr_b = cast_to_blendwise(b);
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+ int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
+ if (is_a_larger) {
+ arr_a.resize(mid_size);
+ } else {
+ arr_b.resize(mid_size);
+ }
+ return cast_from_blendwise(blend_variant(arr_a, arr_b, c), a.get_type());
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
default: {
- return c < 0.5 ? a : b;
- }
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = blend_variant(arr_a[i], arr_b[i], c);
+ }
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
+ result.resize(max_size);
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = blend_variant(arr_a[i], lesser_last, c);
+ }
+ } else if (!is_a_larger && !Math::is_zero_approx(c)) {
+ result.resize(max_size);
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = blend_variant(lesser_last, arr_b[i], c);
+ }
+ }
+ }
+ return result;
+ }
+ } break;
}
+ return c < 0.5 ? a : b;
}
-Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c) {
- if (a.get_type() != b.get_type()) {
- if (a.is_num() && b.is_num()) {
- double va = a;
- double vb = b;
- return va + (vb - va) * c;
- }
+Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element) {
+ if (a.get_type() != b.get_type() && !a.is_array()) {
return a;
}
switch (a.get_type()) {
case Variant::NIL: {
return Variant();
- }
- case Variant::INT: {
- const int64_t va = a.operator int64_t();
- return int64_t(va + ((b.operator int64_t()) - va) * c);
- }
+ } break;
case Variant::FLOAT: {
- const double va = a.operator double();
- return va + ((b.operator double()) - va) * c;
- }
+ const real_t va = a.operator real_t();
+ return va + ((b.operator real_t()) - va) * c;
+ } break;
case Variant::VECTOR2: {
return (a.operator Vector2()).lerp(b.operator Vector2(), c);
- }
- case Variant::VECTOR2I: {
- const Vector2i va = a.operator Vector2i();
- const Vector2i vb = b.operator Vector2i();
- return Vector2i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c));
- }
+ } break;
case Variant::RECT2: {
const Rect2 ra = a.operator Rect2();
const Rect2 rb = b.operator Rect2();
return Rect2(ra.position.lerp(rb.position, c), ra.size.lerp(rb.size, c));
- }
- case Variant::RECT2I: {
- const Rect2i ra = a.operator Rect2i();
- const Rect2i rb = b.operator Rect2i();
- return Rect2i(int32_t(ra.position.x + (rb.position.x - ra.position.x) * c), int32_t(ra.position.y + (rb.position.y - ra.position.y) * c), int32_t(ra.size.x + (rb.size.x - ra.size.x) * c), int32_t(ra.size.y + (rb.size.y - ra.size.y) * c));
- }
+ } break;
case Variant::VECTOR3: {
return (a.operator Vector3()).lerp(b.operator Vector3(), c);
- }
- case Variant::VECTOR3I: {
- const Vector3i va = a.operator Vector3i();
- const Vector3i vb = b.operator Vector3i();
- return Vector3i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c));
- }
+ } break;
case Variant::VECTOR4: {
return (a.operator Vector4()).lerp(b.operator Vector4(), c);
- }
- case Variant::VECTOR4I: {
- const Vector4i va = a.operator Vector4i();
- const Vector4i vb = b.operator Vector4i();
- return Vector4i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c), int32_t(va.w + (vb.w - va.w) * c));
- }
+ } break;
case Variant::PLANE: {
const Plane pa = a.operator Plane();
const Plane pb = b.operator Plane();
return Plane(pa.normal.lerp(pb.normal, c), pa.d + (pb.d - pa.d) * c);
- }
+ } break;
case Variant::COLOR: {
return (a.operator Color()).lerp(b.operator Color(), c);
- }
+ } break;
case Variant::AABB: {
const ::AABB aa = a.operator ::AABB();
const ::AABB ab = b.operator ::AABB();
return ::AABB(aa.position.lerp(ab.position, c), aa.size.lerp(ab.size, c));
- }
+ } break;
case Variant::BASIS: {
return (a.operator Basis()).lerp(b.operator Basis(), c);
- }
+ } break;
case Variant::QUATERNION: {
return (a.operator Quaternion()).slerp(b.operator Quaternion(), c);
- }
+ } break;
case Variant::TRANSFORM2D: {
return (a.operator Transform2D()).interpolate_with(b.operator Transform2D(), c);
- }
+ } break;
case Variant::TRANSFORM3D: {
return (a.operator Transform3D()).interpolate_with(b.operator Transform3D(), c);
- }
- case Variant::STRING: {
- // This is pretty funny and bizarre, but artists like to use it for typewriter effects.
- const String sa = a.operator String();
- const String sb = b.operator String();
- String dst;
- int sa_len = sa.length();
- int sb_len = sb.length();
- int csize = sa_len + (sb_len - sa_len) * c;
- if (csize == 0) {
- return "";
- }
- dst.resize(csize + 1);
- dst[csize] = 0;
- int split = csize / 2;
-
- for (int i = 0; i < csize; i++) {
- char32_t chr = ' ';
-
- if (i < split) {
- if (i < sa.length()) {
- chr = sa[i];
- } else if (i < sb.length()) {
- chr = sb[i];
- }
-
- } else {
- if (i < sb.length()) {
- chr = sb[i];
- } else if (i < sa.length()) {
- chr = sa[i];
- }
- }
-
- dst[i] = chr;
- }
-
- return dst;
- }
- case Variant::PACKED_INT32_ARRAY: {
- const Vector<int32_t> arr_a = a;
- const Vector<int32_t> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<int32_t> v;
- v.resize(sz);
- {
- int32_t *vw = v.ptrw();
- const int32_t *ar = arr_a.ptr();
- const int32_t *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
+ } break;
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::RECT2I:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I:
+ case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY: {
- const Vector<int64_t> arr_a = a;
- const Vector<int64_t> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<int64_t> v;
- v.resize(sz);
- {
- int64_t *vw = v.ptrw();
- const int64_t *ar = arr_a.ptr();
- const int64_t *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
- case Variant::PACKED_FLOAT32_ARRAY: {
- const Vector<float> arr_a = a;
- const Vector<float> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
+ // Fallback the interpolatable value which needs casting.
+ return cast_from_blendwise(interpolate_variant(cast_to_blendwise(a), cast_to_blendwise(b), c), a.get_type());
+ } break;
+ case Variant::STRING:
+ case Variant::STRING_NAME: {
+ Array arr_a = cast_to_blendwise(a);
+ Array arr_b = cast_to_blendwise(b);
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+ int mid_size = interpolate_variant(arr_a.size(), arr_b.size(), c);
+ if (is_a_larger) {
+ arr_a.resize(mid_size);
} else {
- Vector<float> v;
- v.resize(sz);
- {
- float *vw = v.ptrw();
- const float *ar = arr_a.ptr();
- const float *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
+ arr_b.resize(mid_size);
}
- }
- case Variant::PACKED_FLOAT64_ARRAY: {
- const Vector<double> arr_a = a;
- const Vector<double> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<double> v;
- v.resize(sz);
- {
- double *vw = v.ptrw();
- const double *ar = arr_a.ptr();
- const double *br = arr_b.ptr();
-
- Variant va;
- for (int i = 0; i < sz; i++) {
- va = interpolate_variant(ar[i], br[i], c);
- vw[i] = va;
- }
- }
- return v;
- }
- }
- case Variant::PACKED_VECTOR2_ARRAY: {
- const Vector<Vector2> arr_a = a;
- const Vector<Vector2> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Vector2> v;
- v.resize(sz);
- {
- Vector2 *vw = v.ptrw();
- const Vector2 *ar = arr_a.ptr();
- const Vector2 *br = arr_b.ptr();
-
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
- }
+ return cast_from_blendwise(interpolate_variant(arr_a, arr_b, c, true), a.get_type());
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Skip.
+ } break;
+ default: {
+ if (a.is_array()) {
+ const Array arr_a = a.operator Array();
+ const Array arr_b = b.operator Array();
+
+ int min_size = arr_a.size();
+ int max_size = arr_b.size();
+ bool is_a_larger = inform_variant_array(min_size, max_size);
+
+ Array result;
+ result.set_typed(MAX(arr_a.get_typed_builtin(), arr_b.get_typed_builtin()), StringName(), Variant());
+ result.resize(min_size);
+ int i = 0;
+ for (; i < min_size; i++) {
+ result[i] = interpolate_variant(arr_a[i], arr_b[i], c);
}
- return v;
- }
- }
- case Variant::PACKED_VECTOR3_ARRAY: {
- const Vector<Vector3> arr_a = a;
- const Vector<Vector3> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Vector3> v;
- v.resize(sz);
- {
- Vector3 *vw = v.ptrw();
- const Vector3 *ar = arr_a.ptr();
- const Vector3 *br = arr_b.ptr();
-
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
+ if (min_size != max_size) {
+ // Process with last element of the lesser array.
+ // This is pretty funny and bizarre, but artists like to use it for polygon animation.
+ Variant lesser_last;
+ if (is_a_larger && !Math::is_equal_approx(c, 1.0f)) {
+ result.resize(max_size);
+ if (p_snap_array_element) {
+ c = 0;
+ }
+ if (i > 0) {
+ lesser_last = arr_b[i - 1];
+ } else {
+ Variant vz = arr_a[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = interpolate_variant(arr_a[i], lesser_last, c);
+ }
+ } else if (!is_a_larger && !Math::is_zero_approx(c)) {
+ result.resize(max_size);
+ if (p_snap_array_element) {
+ c = 1;
+ }
+ if (i > 0) {
+ lesser_last = arr_a[i - 1];
+ } else {
+ Variant vz = arr_b[i];
+ vz.zero();
+ lesser_last = vz;
+ }
+ for (; i < max_size; i++) {
+ result[i] = interpolate_variant(lesser_last, arr_b[i], c);
+ }
}
}
- return v;
+ return result;
}
- }
- case Variant::PACKED_COLOR_ARRAY: {
- const Vector<Color> arr_a = a;
- const Vector<Color> arr_b = b;
- int sz = arr_a.size();
- if (sz == 0 || arr_b.size() != sz) {
- return a;
- } else {
- Vector<Color> v;
- v.resize(sz);
- {
- Color *vw = v.ptrw();
- const Color *ar = arr_a.ptr();
- const Color *br = arr_b.ptr();
+ } break;
+ }
+ return c < 0.5 ? a : b;
+}
- for (int i = 0; i < sz; i++) {
- vw[i] = ar[i].lerp(br[i], c);
- }
- }
- return v;
- }
- }
- default: {
- return c < 0.5 ? a : b;
- }
+bool Animation::inform_variant_array(int &r_min, int &r_max) {
+ if (r_min <= r_max) {
+ return false;
}
+ SWAP(r_min, r_max);
+ return true;
}
Animation::Animation() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index c128c14e49..2579b6c8ce 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -373,6 +373,8 @@ protected:
static void _bind_methods();
+ static bool inform_variant_array(int &r_min, int &r_max); // Returns true if max and min are swapped.
+
public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
@@ -487,11 +489,19 @@ public:
void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3);
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
- // Helper math functions for Variant.
+ // Helper functions for Variant.
+ static bool is_variant_interpolatable(const Variant p_value);
+
+ static Variant cast_to_blendwise(const Variant p_value);
+ static Variant cast_from_blendwise(const Variant p_value, const Variant::Type p_type);
+
+ static Variant string_to_array(const Variant p_value);
+ static Variant array_to_string(const Variant p_value);
+
static Variant add_variant(const Variant &a, const Variant &b);
static Variant subtract_variant(const Variant &a, const Variant &b);
static Variant blend_variant(const Variant &a, const Variant &b, float c);
- static Variant interpolate_variant(const Variant &a, const Variant &b, float c);
+ static Variant interpolate_variant(const Variant &a, const Variant &b, float c, bool p_snap_array_element = false);
Animation();
~Animation();
diff --git a/scene/resources/camera_attributes.cpp b/scene/resources/camera_attributes.cpp
index 7c46729af3..af5df165b3 100644
--- a/scene/resources/camera_attributes.cpp
+++ b/scene/resources/camera_attributes.cpp
@@ -286,10 +286,10 @@ void CameraAttributesPractical::_bind_methods() {
ADD_GROUP("DOF Blur", "dof_blur_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_far_enabled"), "set_dof_blur_far_enabled", "is_dof_blur_far_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_far_distance", "get_dof_blur_far_distance");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01,exp"), "set_dof_blur_far_transition", "get_dof_blur_far_transition");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01"), "set_dof_blur_far_transition", "get_dof_blur_far_transition");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_near_enabled"), "set_dof_blur_near_enabled", "is_dof_blur_near_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_near_distance", "get_dof_blur_near_distance");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01,exp"), "set_dof_blur_near_transition", "get_dof_blur_near_transition");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "-1,8192,0.01"), "set_dof_blur_near_transition", "get_dof_blur_near_transition");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dof_blur_amount", "get_dof_blur_amount");
ADD_GROUP("Auto Exposure", "auto_exposure_");
@@ -373,6 +373,11 @@ real_t CameraAttributesPhysical::get_fov() const {
return frustum_fov;
}
+#ifdef MINGW_ENABLED
+#undef near
+#undef far
+#endif
+
void CameraAttributesPhysical::_update_frustum() {
//https://en.wikipedia.org/wiki/Circle_of_confusion#Circle_of_confusion_diameter_limit_based_on_d/1500
Vector2i sensor_size = Vector2i(36, 24); // Matches high-end DSLR, could be made variable if there is demand.
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 235f98b28e..13b22ae12c 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -1406,7 +1406,7 @@ Error FontFile::load_bitmap_font(const String &p_path) {
oversampling = 1.0f;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, vformat(RTR("Cannot open font from file: %s."), p_path));
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, vformat("Cannot open font from file: %s.", p_path));
int base_size = 16;
int height = 0;
@@ -1425,7 +1425,7 @@ Error FontFile::load_bitmap_font(const String &p_path) {
f->get_buffer((unsigned char *)&magic, 4);
if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') {
// Binary BMFont file.
- ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(RTR("Version %d of BMFont is not supported (should be 3)."), (int)magic[3]));
+ ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat("Version %d of BMFont is not supported (should be 3).", (int)magic[3]));
uint8_t block_type = f->get_8();
uint32_t block_size = f->get_32();
@@ -1435,7 +1435,7 @@ Error FontFile::load_bitmap_font(const String &p_path) {
uint64_t off = f->get_position();
switch (block_type) {
case 1: /* info */ {
- ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, RTR("Invalid BMFont info block size."));
+ ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, "Invalid BMFont info block size.");
base_size = f->get_16();
if (base_size <= 0) {
base_size = 16;
@@ -1496,7 +1496,7 @@ Error FontFile::load_bitmap_font(const String &p_path) {
set_fixed_size(base_size);
} break;
case 2: /* common */ {
- ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, RTR("Invalid BMFont common block size."));
+ ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, "Invalid BMFont common block size.");
height = f->get_16();
ascent = f->get_16();
f->get_32(); // scale, skip
@@ -1534,40 +1534,40 @@ Error FontFile::load_bitmap_font(const String &p_path) {
Ref<Image> img;
img.instantiate();
Error err = ImageLoader::load_image(file, img);
- ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat(RTR("Can't load font texture: %s."), file));
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: %s.", file));
if (packed) {
if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_packed_8bit(img, page, base_size);
} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_packed_4bit(img, page, base_size);
} else {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format.");
}
} else {
if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
set_texture_image(0, Vector2i(base_size, 0), page, img);
} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_rgba_4bit(img, page, base_size);
} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
_convert_mono_8bit(img, page, first_ol_ch, base_size, 1);
} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_4bit(img, page, first_cm_ch, base_size, 1);
} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
} else {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format.");
}
}
}
@@ -1669,7 +1669,7 @@ Error FontFile::load_bitmap_font(const String &p_path) {
}
} break;
default: {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Invalid BMFont block type."));
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Invalid BMFont block type.");
} break;
}
f->seek(off + block_size);
@@ -1825,17 +1825,17 @@ Error FontFile::load_bitmap_font(const String &p_path) {
Ref<Image> img;
img.instantiate();
Error err = ImageLoader::load_image(file, img);
- ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat(RTR("Can't load font texture: %s."), file));
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, vformat("Can't load font texture: %s.", file));
if (packed) {
if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_packed_8bit(img, page, base_size);
} else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_packed_4bit(img, page, base_size);
} else {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format.");
}
} else {
if ((ch[3] == 0) && (ch[0] == 4) && (ch[1] == 4) && (ch[2] == 4) && img->get_format() == Image::FORMAT_RGBA8) { // might be RGBA8 color, no outline (color part of the image should be sold white, but some apps designed for Godot 3 generate color fonts with this config)
@@ -1843,24 +1843,24 @@ Error FontFile::load_bitmap_font(const String &p_path) {
set_texture_image(0, Vector2i(base_size, 0), page, img);
} else if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
set_texture_image(0, Vector2i(base_size, 0), page, img);
} else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_rgba_4bit(img, page, base_size);
} else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
_convert_mono_8bit(img, page, first_ol_ch, base_size, 1);
} else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_4bit(img, page, first_cm_ch, base_size, 1);
} else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
outline = 0;
- ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, "Unsupported BMFont texture format.");
_convert_mono_8bit(img, page, first_gl_ch, base_size, 0);
} else {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format."));
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Unsupported BMFont texture format.");
}
}
}
diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp
index 3507df8bd8..dde556c264 100644
--- a/scene/resources/immediate_mesh.cpp
+++ b/scene/resources/immediate_mesh.cpp
@@ -166,7 +166,7 @@ void ImmediateMesh::surface_end() {
normal_tangent_stride += sizeof(uint32_t);
}
uint32_t tangent_offset = 0;
- if (uses_tangents) {
+ if (uses_tangents || uses_normals) {
format |= ARRAY_FORMAT_TANGENT;
tangent_offset = vertex_stride * vertices.size() + normal_tangent_stride;
normal_tangent_stride += sizeof(uint32_t);
@@ -202,9 +202,16 @@ void ImmediateMesh::surface_end() {
*normal = value;
}
- if (uses_tangents) {
+ if (uses_tangents || uses_normals) {
uint32_t *tangent = (uint32_t *)&surface_vertex_ptr[i * normal_tangent_stride + tangent_offset];
- Vector2 t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d);
+ Vector2 t;
+ if (uses_tangents) {
+ t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d);
+ } else {
+ Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[i].normalized());
+ t = tan.octahedron_tangent_encode(1.0);
+ }
+
uint32_t value = 0;
value |= (uint16_t)CLAMP(t.x * 65535, 0, 65535);
value |= (uint16_t)CLAMP(t.y * 65535, 0, 65535) << 16;
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 2456212327..1f6e453e88 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -138,7 +138,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
int nc = nodes.size();
- ERR_FAIL_COND_V(nc == 0, nullptr);
+ ERR_FAIL_COND_V_MSG(nc == 0, nullptr, vformat("Failed to instantiate scene state of \"%s\", node count is 0. Make sure the PackedScene resource is valid.", path));
const StringName *snames = nullptr;
int sname_count = names.size();
@@ -219,7 +219,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
Ref<PackedScene> sdata = props[n.instance & FLAG_MASK];
ERR_FAIL_COND_V(!sdata.is_valid(), nullptr);
node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE);
- ERR_FAIL_NULL_V(node, nullptr);
+ ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path()));
}
} else if (n.type == TYPE_INSTANTIATED) {
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 6f8df70548..4ed1980826 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -86,9 +86,9 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->tangent_accel_texture = "tangent_accel_texture";
shader_names->damping_texture = "damping_texture";
shader_names->scale_texture = "scale_curve";
- shader_names->hue_variation_texture = "hue_variation_texture";
- shader_names->anim_speed_texture = "anim_speed_texture";
- shader_names->anim_offset_texture = "anim_offset_texture";
+ shader_names->hue_variation_texture = "hue_rot_curve";
+ shader_names->anim_speed_texture = "animation_speed_curve";
+ shader_names->anim_offset_texture = "animation_offset_curve";
shader_names->directional_velocity_texture = "directional_velocity_curve";
shader_names->scale_over_velocity_texture = "scale_over_velocity_curve";
@@ -762,8 +762,11 @@ void ParticleProcessMaterial::_update_shader() {
code += "}\n";
- code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform){\n";
+ code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n";
code += " vec3 radial_displacement = vec3(0.0);\n";
+ code += " if (delta < 0.001){\n";
+ code += " return radial_displacement;\n";
+ code += " }\n";
code += " float radial_displacement_multiplier = 1.0;\n";
if (tex_parameters[PARAM_RADIAL_VELOCITY].is_valid()) {
code += " radial_displacement_multiplier = texture(radial_velocity_curve, vec2(lifetime)).r;\n";
@@ -774,7 +777,7 @@ void ParticleProcessMaterial::_update_shader() {
code += " }else{radial_displacement = get_random_direction_from_spread(alt_seed, 360.0)* param.radial_velocity;} \n";
code += " if (radial_displacement_multiplier * param.radial_velocity < 0.0){\n // Prevent inwards velocity to flicker once the point is reached.";
code += " if (length(radial_displacement) > 0.01){\n";
- code += " radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz - global_pivot));\n";
+ code += " radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz - global_pivot) / delta);\n";
code += " }\n";
code += " \n";
code += " return radial_displacement;\n";
@@ -923,7 +926,7 @@ void ParticleProcessMaterial::_update_shader() {
}
code += " // calculate all velocity\n";
code += " \n";
- code += " controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM);\n";
+ code += " controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
code += " \n";
if (tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
code += " controlled_displacement += process_directional_displacement(dynamic_params, lifetime_percent, TRANSFORM, EMISSION_TRANSFORM);\n";
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index dd39f88352..19718f12be 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -577,6 +577,8 @@ Error ResourceLoaderText::load() {
if (do_assign) {
if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
+ } else if (!path.is_resource_file()) {
+ res->set_path_cache(path);
}
res->set_scene_unique_id(id);
}
diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp
index 5703185374..dcc69d4831 100644
--- a/scene/resources/skeleton_modification_stack_2d.cpp
+++ b/scene/resources/skeleton_modification_stack_2d.cpp
@@ -37,7 +37,7 @@ void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list)
PropertyInfo(Variant::OBJECT, "modifications/" + itos(i),
PROPERTY_HINT_RESOURCE_TYPE,
"SkeletonModification2D",
- PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_ALWAYS_DUPLICATE));
+ PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE));
}
}
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 69925fc19f..423ef3867f 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -963,13 +963,13 @@ void VisualShader::remove_node(Type p_type, int p_id) {
for (List<Connection>::Element *E = g->connections.front(); E;) {
List<Connection>::Element *N = E->next();
if (E->get().from_node == p_id || E->get().to_node == p_id) {
- g->connections.erase(E);
if (E->get().from_node == p_id) {
g->nodes[E->get().to_node].prev_connected_nodes.erase(p_id);
g->nodes[E->get().to_node].node->set_input_port_connected(E->get().to_port, false);
} else if (E->get().to_node == p_id) {
g->nodes[E->get().from_node].next_connected_nodes.erase(p_id);
}
+ g->connections.erase(E);
}
E = N;
}
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 71b8af6625..ccd730eef2 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -6346,6 +6346,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_
if (!repeat_code.is_empty()) {
if (!has_colon) {
code += " : ";
+ has_colon = true;
} else {
code += ", ";
}
@@ -6353,6 +6354,7 @@ String get_sampler_hint(VisualShaderNodeTextureParameter::TextureType p_texture_
}
}
+ // source
{
String source_code;