summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/animated_sprite.cpp1
-rw-r--r--scene/2d/camera_2d.cpp10
-rw-r--r--scene/2d/cpu_particles_2d.cpp72
-rw-r--r--scene/2d/cpu_particles_2d.h4
-rw-r--r--scene/2d/line_2d.cpp1
-rw-r--r--scene/2d/mesh_instance_2d.cpp2
-rw-r--r--scene/2d/multimesh_instance_2d.cpp111
-rw-r--r--scene/2d/multimesh_instance_2d.h65
-rw-r--r--scene/2d/physics_body_2d.cpp32
-rw-r--r--scene/2d/position_2d.cpp36
-rw-r--r--scene/2d/position_2d.h5
-rw-r--r--scene/2d/tile_map.cpp16
-rw-r--r--scene/3d/arvr_nodes.cpp4
-rw-r--r--scene/3d/camera.cpp7
-rw-r--r--scene/3d/camera.h2
-rw-r--r--scene/3d/collision_shape.cpp4
-rw-r--r--scene/3d/cpu_particles.cpp62
-rw-r--r--scene/3d/cpu_particles.h2
-rw-r--r--scene/3d/physics_body.cpp32
-rw-r--r--scene/3d/visual_instance.h1
-rw-r--r--scene/animation/animation_blend_tree.cpp2
-rw-r--r--scene/animation/animation_tree.cpp2
-rw-r--r--scene/animation/animation_tree_player.cpp2
-rw-r--r--scene/animation/tween.cpp595
-rw-r--r--scene/animation/tween.h1
-rw-r--r--scene/gui/base_button.cpp12
-rw-r--r--scene/gui/control.cpp10
-rw-r--r--scene/gui/file_dialog.cpp23
-rw-r--r--scene/gui/file_dialog.h1
-rw-r--r--scene/gui/gradient_edit.cpp9
-rw-r--r--scene/gui/gradient_edit.h2
-rw-r--r--scene/gui/graph_node.cpp4
-rw-r--r--scene/gui/item_list.cpp20
-rw-r--r--scene/gui/line_edit.cpp7
-rw-r--r--scene/gui/option_button.cpp1
-rw-r--r--scene/gui/popup_menu.cpp56
-rw-r--r--scene/gui/popup_menu.h7
-rw-r--r--scene/gui/rich_text_label.cpp19
-rw-r--r--scene/gui/text_edit.cpp10
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/gui/tree.cpp61
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/main/node.cpp4
-rw-r--r--scene/main/scene_tree.cpp2
-rw-r--r--scene/register_scene_types.cpp17
-rw-r--r--scene/resources/animation.cpp75
-rw-r--r--scene/resources/animation.h1
-rw-r--r--scene/resources/default_theme/default_theme.cpp15
-rw-r--r--scene/resources/default_theme/icon_visibility.pngbin0 -> 488 bytes
-rw-r--r--scene/resources/default_theme/theme_data.h4
-rw-r--r--scene/resources/environment.cpp22
-rw-r--r--scene/resources/environment.h4
-rw-r--r--scene/resources/height_map_shape.cpp1
-rw-r--r--scene/resources/mesh.cpp2
-rw-r--r--scene/resources/mesh.h5
-rw-r--r--scene/resources/packed_scene.cpp4
-rw-r--r--scene/resources/particles_material.cpp1
-rw-r--r--scene/resources/primitive_meshes.cpp6
-rw-r--r--scene/resources/primitive_meshes.h1
-rw-r--r--scene/resources/resource_format_text.cpp6
-rw-r--r--scene/resources/texture.cpp107
-rw-r--r--scene/resources/texture.h35
-rw-r--r--scene/resources/tile_set.cpp32
-rw-r--r--scene/resources/tile_set.h1
-rw-r--r--scene/resources/visual_shader.cpp2
65 files changed, 1293 insertions, 375 deletions
diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp
index 25ad6bd5c9..5bf70e12b7 100644
--- a/scene/2d/animated_sprite.cpp
+++ b/scene/2d/animated_sprite.cpp
@@ -646,6 +646,7 @@ void AnimatedSprite::_reset_timeout() {
void AnimatedSprite::set_animation(const StringName &p_animation) {
ERR_EXPLAIN(vformat("There is no animation with name '%s'.", p_animation));
+ ERR_FAIL_COND(frames == NULL);
ERR_FAIL_COND(frames->get_animation_names().find(p_animation) == -1);
if (animation == p_animation)
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 11846654c5..a0d74dd283 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -29,10 +29,11 @@
/*************************************************************************/
#include "camera_2d.h"
+
+#include "core/engine.h"
#include "core/math/math_funcs.h"
#include "scene/scene_string_names.h"
#include "servers/visual_server.h"
-#include <editor/editor_node.h>
void Camera2D::_update_scroll() {
@@ -44,15 +45,16 @@ void Camera2D::_update_scroll() {
return;
}
+ if (!viewport)
+ return;
+
if (current) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Transform2D xform = get_camera_transform();
- if (viewport) {
- viewport->set_canvas_transform(xform);
- }
+ viewport->set_canvas_transform(xform);
Size2 screen_size = viewport->get_visible_rect().size;
Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2());
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp
index ff27e0a29a..536a05dceb 100644
--- a/scene/2d/cpu_particles_2d.cpp
+++ b/scene/2d/cpu_particles_2d.cpp
@@ -29,8 +29,9 @@
/*************************************************************************/
#include "cpu_particles_2d.h"
-#include "particles_2d.h"
+
#include "scene/2d/canvas_item.h"
+#include "scene/2d/particles_2d.h"
#include "scene/resources/particles_material.h"
#include "servers/visual_server.h"
@@ -324,9 +325,9 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv
case PARAM_ANGULAR_VELOCITY: {
_adjust_curve_range(p_curve, -360, 360);
} break;
- /*case PARAM_ORBIT_VELOCITY: {
+ case PARAM_ORBIT_VELOCITY: {
_adjust_curve_range(p_curve, -500, 500);
- } break;*/
+ } break;
case PARAM_LINEAR_ACCEL: {
_adjust_curve_range(p_curve, -200, 200);
} break;
@@ -489,12 +490,6 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const {
if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) {
property.usage = 0;
}
-
- /*
- if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) {
- property.usage = 0;
- }
- */
}
static uint32_t idhash(uint32_t x) {
@@ -545,6 +540,8 @@ void CPUParticles2D::_particles_process(float p_delta) {
velocity_xform[2] = Vector2();
}
+ float system_phase = time / lifetime;
+
for (int i = 0; i < pcount; i++) {
Particle &p = parray[i];
@@ -552,21 +549,26 @@ void CPUParticles2D::_particles_process(float p_delta) {
if (!emitting && !p.active)
continue;
- float restart_time = (float(i) / float(pcount)) * lifetime;
float local_delta = p_delta;
+ // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle.
+ // While we use time in tests later on, for randomness we use the phase as done in the
+ // original shader code, and we later multiply by lifetime to get the time.
+ float restart_phase = float(i) / float(pcount);
+
if (randomness_ratio > 0.0) {
uint32_t seed = cycle;
- if (restart_time >= time) {
+ if (restart_phase >= system_phase) {
seed -= uint32_t(1);
}
seed *= uint32_t(pcount);
seed += uint32_t(i);
float random = float(idhash(seed) % uint32_t(65536)) / 65536.0;
- restart_time += randomness_ratio * random * 1.0 / float(pcount);
+ restart_phase += randomness_ratio * random * 1.0 / float(pcount);
}
- restart_time *= (1.0 - explosiveness_ratio);
+ restart_phase *= (1.0 - explosiveness_ratio);
+ float restart_time = restart_phase * lifetime;
bool restart = false;
if (time > prev_time) {
@@ -688,16 +690,12 @@ void CPUParticles2D::_particles_process(float p_delta) {
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]);
}
- /*
- float tex_orbit_velocity = 0.0;
-
- if (flags[FLAG_DISABLE_Z]) {
- if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) {
- tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]);
- }
+ float tex_orbit_velocity = 0.0;
+ if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
+ tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]);
}
-*/
+
float tex_angular_velocity = 0.0;
if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]);
@@ -748,22 +746,19 @@ void CPUParticles2D::_particles_process(float p_delta) {
force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2();
//apply tangential acceleration;
Vector2 yx = Vector2(diff.y, diff.x);
- force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2();
+ force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2();
//apply attractor forces
p.velocity += force * local_delta;
//orbit velocity
-#if 0
- if (flags[FLAG_DISABLE_Z]) {
-
- float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);
- if (orbit_amount != 0.0) {
- float ang = orbit_amount * DELTA * pi * 2.0;
- mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));
- TRANSFORM[3].xy -= diff.xy;
- TRANSFORM[3].xy += rot * diff.xy;
- }
+ float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]);
+ if (orbit_amount != 0.0) {
+ float ang = orbit_amount * local_delta * Math_PI * 2.0;
+ // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix,
+ // but we use -ang here to reproduce its behavior.
+ Transform2D rot = Transform2D(-ang, Vector2());
+ p.transform[2] -= diff;
+ p.transform[2] += rot.basis_xform(diff);
}
-#endif
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
p.velocity = p.velocity.normalized() * tex_linear_velocity;
}
@@ -1174,15 +1169,16 @@ void CPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount");
ADD_GROUP("Time", "");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
ADD_GROUP("Drawing", "");
+ // No visibility_rect property contrarily to Particles2D, it's updated automatically.
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture");
@@ -1263,12 +1259,10 @@ void CPUParticles2D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY);
- /*
ADD_GROUP("Orbit Velocity", "orbit_");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY);
-*/
ADD_GROUP("Linear Accel", "linear_");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL);
@@ -1324,6 +1318,8 @@ void CPUParticles2D::_bind_methods() {
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY);
+ BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); // Unused, but exposed for consistency with 3D.
+ BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); // Unused, but exposed for consistency with 3D.
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT);
@@ -1362,7 +1358,7 @@ CPUParticles2D::CPUParticles2D() {
set_spread(45);
set_flatness(0);
set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1);
- //set_param(PARAM_ORBIT_VELOCITY, 0);
+ set_param(PARAM_ORBIT_VELOCITY, 0);
set_param(PARAM_LINEAR_ACCEL, 0);
set_param(PARAM_ANGULAR_VELOCITY, 0);
set_param(PARAM_RADIAL_ACCEL, 0);
diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h
index 23d2586331..79444407ee 100644
--- a/scene/2d/cpu_particles_2d.h
+++ b/scene/2d/cpu_particles_2d.h
@@ -68,6 +68,8 @@ public:
enum Flags {
FLAG_ALIGN_Y_TO_VELOCITY,
+ FLAG_ROTATE_Y, // Unused, but exposed for consistency with 3D.
+ FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D.
FLAG_MAX
};
@@ -116,7 +118,7 @@ private:
const Particle *particles;
bool operator()(int p_a, int p_b) const {
- return particles[p_a].time < particles[p_b].time;
+ return particles[p_a].time > particles[p_b].time;
}
};
diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp
index ba06b3ebff..5ba184b324 100644
--- a/scene/2d/line_2d.cpp
+++ b/scene/2d/line_2d.cpp
@@ -105,6 +105,7 @@ void Line2D::set_point_position(int i, Vector2 pos) {
}
Vector2 Line2D::get_point_position(int i) const {
+ ERR_FAIL_INDEX_V(i, _points.size(), Vector2());
return _points.get(i);
}
diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp
index b382ca7b33..bcd4bca940 100644
--- a/scene/2d/mesh_instance_2d.cpp
+++ b/scene/2d/mesh_instance_2d.cpp
@@ -50,6 +50,8 @@ void MeshInstance2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &MeshInstance2D::set_normal_map);
ClassDB::bind_method(D_METHOD("get_normal_map"), &MeshInstance2D::get_normal_map);
+ ADD_SIGNAL(MethodInfo("texture_changed"));
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_map", "get_normal_map");
diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp
new file mode 100644
index 0000000000..ca75302163
--- /dev/null
+++ b/scene/2d/multimesh_instance_2d.cpp
@@ -0,0 +1,111 @@
+/*************************************************************************/
+/* multimesh_instance_2d.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "multimesh_instance_2d.h"
+
+void MultiMeshInstance2D::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_DRAW) {
+ if (multimesh.is_valid()) {
+ draw_multimesh(multimesh, texture, normal_map);
+ }
+ }
+}
+
+void MultiMeshInstance2D::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance2D::set_multimesh);
+ ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance2D::get_multimesh);
+
+ ClassDB::bind_method(D_METHOD("set_texture", "texture"), &MultiMeshInstance2D::set_texture);
+ ClassDB::bind_method(D_METHOD("get_texture"), &MultiMeshInstance2D::get_texture);
+
+ ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &MultiMeshInstance2D::set_normal_map);
+ ClassDB::bind_method(D_METHOD("get_normal_map"), &MultiMeshInstance2D::get_normal_map);
+
+ ADD_SIGNAL(MethodInfo("texture_changed"));
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_map", "get_normal_map");
+}
+
+void MultiMeshInstance2D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
+
+ multimesh = p_multimesh;
+ update();
+}
+
+Ref<MultiMesh> MultiMeshInstance2D::get_multimesh() const {
+
+ return multimesh;
+}
+
+void MultiMeshInstance2D::set_texture(const Ref<Texture> &p_texture) {
+
+ if (p_texture == texture)
+ return;
+ texture = p_texture;
+ update();
+ emit_signal("texture_changed");
+ _change_notify("texture");
+}
+
+Ref<Texture> MultiMeshInstance2D::get_texture() const {
+
+ return texture;
+}
+
+void MultiMeshInstance2D::set_normal_map(const Ref<Texture> &p_texture) {
+
+ normal_map = p_texture;
+ update();
+}
+
+Ref<Texture> MultiMeshInstance2D::get_normal_map() const {
+
+ return normal_map;
+}
+
+Rect2 MultiMeshInstance2D::_edit_get_rect() const {
+
+ if (multimesh.is_valid()) {
+ AABB aabb = multimesh->get_aabb();
+ return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
+ }
+
+ return Node2D::_edit_get_rect();
+}
+
+MultiMeshInstance2D::MultiMeshInstance2D() {
+}
+
+MultiMeshInstance2D::~MultiMeshInstance2D() {
+}
diff --git a/scene/2d/multimesh_instance_2d.h b/scene/2d/multimesh_instance_2d.h
new file mode 100644
index 0000000000..3795497183
--- /dev/null
+++ b/scene/2d/multimesh_instance_2d.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* multimesh_instance_2d.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef MULTIMESH_INSTANCE_2D_H
+#define MULTIMESH_INSTANCE_2D_H
+
+#include "scene/2d/node_2d.h"
+#include "scene/resources/multimesh.h"
+
+class MultiMeshInstance2D : public Node2D {
+ GDCLASS(MultiMeshInstance2D, Node2D);
+
+ Ref<MultiMesh> multimesh;
+
+ Ref<Texture> texture;
+ Ref<Texture> normal_map;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_multimesh(const Ref<MultiMesh> &p_multimesh);
+ Ref<MultiMesh> get_multimesh() const;
+
+ void set_texture(const Ref<Texture> &p_texture);
+ Ref<Texture> get_texture() const;
+
+ void set_normal_map(const Ref<Texture> &p_texture);
+ Ref<Texture> get_normal_map() const;
+
+ virtual Rect2 _edit_get_rect() const;
+
+ MultiMeshInstance2D();
+ ~MultiMeshInstance2D();
+};
+
+#endif // MULTIMESH_INSTANCE_2D_H
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 578c9aa5f9..2bd9e5e2a2 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -203,8 +203,8 @@ void StaticBody2D::set_friction(real_t p_friction) {
return;
}
- ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_friction < 0 || p_friction > 1);
@@ -217,8 +217,8 @@ void StaticBody2D::set_friction(real_t p_friction) {
real_t StaticBody2D::get_friction() const {
- ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 1;
@@ -233,8 +233,8 @@ void StaticBody2D::set_bounce(real_t p_bounce) {
return;
}
- ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1);
@@ -247,8 +247,8 @@ void StaticBody2D::set_bounce(real_t p_bounce) {
real_t StaticBody2D::get_bounce() const {
- ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 0;
@@ -630,8 +630,8 @@ void RigidBody2D::set_friction(real_t p_friction) {
return;
}
- ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_friction < 0 || p_friction > 1);
@@ -643,8 +643,8 @@ void RigidBody2D::set_friction(real_t p_friction) {
}
real_t RigidBody2D::get_friction() const {
- ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 1;
@@ -659,8 +659,8 @@ void RigidBody2D::set_bounce(real_t p_bounce) {
return;
}
- ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1);
@@ -672,8 +672,8 @@ void RigidBody2D::set_bounce(real_t p_bounce) {
}
real_t RigidBody2D::get_bounce() const {
- ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 0;
diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp
index bed6f8a816..f0c46a5fb7 100644
--- a/scene/2d/position_2d.cpp
+++ b/scene/2d/position_2d.cpp
@@ -33,15 +33,19 @@
#include "core/engine.h"
#include "scene/resources/texture.h"
+const float DEFAULT_GIZMO_EXTENTS = 10.0;
+
void Position2D::_draw_cross() {
- draw_line(Point2(-10, 0), Point2(+10, 0), Color(1, 0.5, 0.5));
- draw_line(Point2(0, -10), Point2(0, +10), Color(0.5, 1, 0.5));
+ float extents = get_gizmo_extents();
+ draw_line(Point2(-extents, 0), Point2(+extents, 0), Color(1, 0.5, 0.5));
+ draw_line(Point2(0, -extents), Point2(0, +extents), Color(0.5, 1, 0.5));
}
Rect2 Position2D::_edit_get_rect() const {
- return Rect2(Point2(-10, -10), Size2(20, 20));
+ float extents = get_gizmo_extents();
+ return Rect2(Point2(-extents, -extents), Size2(extents * 2, extents * 2));
}
bool Position2D::_edit_use_rect() const {
@@ -66,5 +70,31 @@ void Position2D::_notification(int p_what) {
}
}
+void Position2D::set_gizmo_extents(float p_extents) {
+ if (p_extents == DEFAULT_GIZMO_EXTENTS) {
+ set_meta("_gizmo_extents_", Variant());
+ } else {
+ set_meta("_gizmo_extents_", p_extents);
+ }
+
+ update();
+}
+
+float Position2D::get_gizmo_extents() const {
+ if (has_meta("_gizmo_extents_")) {
+ return get_meta("_gizmo_extents_");
+ } else {
+ return DEFAULT_GIZMO_EXTENTS;
+ }
+}
+
+void Position2D::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("_set_gizmo_extents", "extents"), &Position2D::set_gizmo_extents);
+ ClassDB::bind_method(D_METHOD("_get_gizmo_extents"), &Position2D::get_gizmo_extents);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "gizmo_extents", PROPERTY_HINT_RANGE, "0,1000,0.1,or_greater", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_gizmo_extents", "_get_gizmo_extents");
+}
+
Position2D::Position2D() {
}
diff --git a/scene/2d/position_2d.h b/scene/2d/position_2d.h
index c95315fea3..ec73c02d2b 100644
--- a/scene/2d/position_2d.h
+++ b/scene/2d/position_2d.h
@@ -41,10 +41,15 @@ class Position2D : public Node2D {
protected:
void _notification(int p_what);
+ static void _bind_methods();
public:
virtual Rect2 _edit_get_rect() const;
virtual bool _edit_use_rect() const;
+
+ void set_gizmo_extents(float p_extents);
+ float get_gizmo_extents() const;
+
Position2D();
};
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 86be8762d4..b3b6f3175d 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -220,8 +220,8 @@ void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const
xform.elements[1].y = -xform.elements[1].y;
offset.y = s.y - offset.y;
}
-
- offset += cell_size / 2 - s / 2;
+ /* For a future CheckBox to Center Texture:
+ offset += cell_size / 2 - s / 2; */
xform.elements[2] += offset;
}
@@ -372,10 +372,11 @@ void TileMap::update_dirty_quadrants() {
if (c.transpose) {
SWAP(tile_ofs.x, tile_ofs.y);
+ /* For a future CheckBox to Center Texture:
rect.position.x += cell_size.x / 2 - rect.size.y / 2;
rect.position.y += cell_size.y / 2 - rect.size.x / 2;
} else {
- rect.position += cell_size / 2 - rect.size / 2;
+ rect.position += cell_size / 2 - rect.size / 2; */
}
if (c.flip_h) {
@@ -868,8 +869,17 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) {
_make_quadrant_dirty(Q);
} else if (tile_set->tile_get_tile_mode(id) == TileSet::SINGLE_TILE) {
+
E->get().autotile_coord_x = 0;
E->get().autotile_coord_y = 0;
+ } else if (tile_set->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) {
+
+ if (tile_set->autotile_get_bitmask(id, Vector2(p_x, p_y)) == TileSet::BIND_CENTER) {
+ Vector2 coord = tile_set->atlastile_get_subtile_by_priority(id, this, Vector2(p_x, p_y));
+
+ E->get().autotile_coord_x = (int)coord.x;
+ E->get().autotile_coord_y = (int)coord.y;
+ }
}
}
}
diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp
index 4e88948ce2..95eec41fb2 100644
--- a/scene/3d/arvr_nodes.cpp
+++ b/scene/3d/arvr_nodes.cpp
@@ -390,7 +390,7 @@ String ARVRController::get_configuration_warning() const {
};
ARVRController::ARVRController() {
- controller_id = 0;
+ controller_id = 1;
is_active = true;
button_states = 0;
};
@@ -530,7 +530,7 @@ Ref<Mesh> ARVRAnchor::get_mesh() const {
}
ARVRAnchor::ARVRAnchor() {
- anchor_id = 0;
+ anchor_id = 1;
is_active = true;
};
diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp
index a00f2173c0..c7d6919a2b 100644
--- a/scene/3d/camera.cpp
+++ b/scene/3d/camera.cpp
@@ -869,6 +869,11 @@ void ClippedCamera::clear_exceptions() {
exclude.clear();
}
+float ClippedCamera::get_clip_offset() const {
+
+ return clip_offset;
+}
+
void ClippedCamera::set_clip_to_areas(bool p_clip) {
clip_to_areas = p_clip;
@@ -912,6 +917,8 @@ void ClippedCamera::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_clip_to_areas", "enable"), &ClippedCamera::set_clip_to_areas);
ClassDB::bind_method(D_METHOD("is_clip_to_areas_enabled"), &ClippedCamera::is_clip_to_areas_enabled);
+ ClassDB::bind_method(D_METHOD("get_clip_offset"), &ClippedCamera::get_clip_offset);
+
ClassDB::bind_method(D_METHOD("set_clip_to_bodies", "enable"), &ClippedCamera::set_clip_to_bodies);
ClassDB::bind_method(D_METHOD("is_clip_to_bodies_enabled"), &ClippedCamera::is_clip_to_bodies_enabled);
diff --git a/scene/3d/camera.h b/scene/3d/camera.h
index 1cd729199d..c0a4f77435 100644
--- a/scene/3d/camera.h
+++ b/scene/3d/camera.h
@@ -234,6 +234,8 @@ public:
void remove_exception(const Object *p_object);
void clear_exceptions();
+ float get_clip_offset() const;
+
ClippedCamera();
~ClippedCamera();
};
diff --git a/scene/3d/collision_shape.cpp b/scene/3d/collision_shape.cpp
index 3190e1e0b2..219ea56681 100644
--- a/scene/3d/collision_shape.cpp
+++ b/scene/3d/collision_shape.cpp
@@ -228,7 +228,9 @@ void CollisionShape::_update_debug_shape() {
void CollisionShape::_shape_changed() {
// If this is a heightfield shape our center may have changed
- _update_in_shape_owner(true);
+ if (parent) {
+ _update_in_shape_owner(true);
+ }
if (is_inside_tree() && get_tree()->is_debugging_collisions_hint() && !debug_shape_dirty) {
debug_shape_dirty = true;
diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp
index 138c446fea..b42649b35a 100644
--- a/scene/3d/cpu_particles.cpp
+++ b/scene/3d/cpu_particles.cpp
@@ -302,9 +302,9 @@ void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve)
case PARAM_ANGULAR_VELOCITY: {
_adjust_curve_range(p_curve, -360, 360);
} break;
- /*case PARAM_ORBIT_VELOCITY: {
+ case PARAM_ORBIT_VELOCITY: {
_adjust_curve_range(p_curve, -500, 500);
- } break;*/
+ } break;
case PARAM_LINEAR_ACCEL: {
_adjust_curve_range(p_curve, -200, 200);
} break;
@@ -461,11 +461,10 @@ void CPUParticles::_validate_property(PropertyInfo &property) const {
if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) {
property.usage = 0;
}
- /*
+
if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) {
property.usage = 0;
}
- */
}
static uint32_t idhash(uint32_t x) {
@@ -515,6 +514,8 @@ void CPUParticles::_particles_process(float p_delta) {
velocity_xform = emission_xform.basis;
}
+ float system_phase = time / lifetime;
+
for (int i = 0; i < pcount; i++) {
Particle &p = parray[i];
@@ -522,21 +523,26 @@ void CPUParticles::_particles_process(float p_delta) {
if (!emitting && !p.active)
continue;
- float restart_time = (float(i) / float(pcount)) * lifetime;
float local_delta = p_delta;
+ // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle.
+ // While we use time in tests later on, for randomness we use the phase as done in the
+ // original shader code, and we later multiply by lifetime to get the time.
+ float restart_phase = float(i) / float(pcount);
+
if (randomness_ratio > 0.0) {
uint32_t seed = cycle;
- if (restart_time >= time) {
+ if (restart_phase >= system_phase) {
seed -= uint32_t(1);
}
seed *= uint32_t(pcount);
seed += uint32_t(i);
float random = float(idhash(seed) % uint32_t(65536)) / 65536.0;
- restart_time += randomness_ratio * random * 1.0 / float(pcount);
+ restart_phase += randomness_ratio * random * 1.0 / float(pcount);
}
- restart_time *= (1.0 - explosiveness_ratio);
+ restart_phase *= (1.0 - explosiveness_ratio);
+ float restart_time = restart_phase * lifetime;
bool restart = false;
if (time > prev_time) {
@@ -691,16 +697,14 @@ void CPUParticles::_particles_process(float p_delta) {
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]);
}
- /*
- float tex_orbit_velocity = 0.0;
+ float tex_orbit_velocity = 0.0;
if (flags[FLAG_DISABLE_Z]) {
-
- if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) {
- tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]);
+ if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
+ tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]);
}
}
-*/
+
float tex_angular_velocity = 0.0;
if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]);
@@ -754,8 +758,9 @@ void CPUParticles::_particles_process(float p_delta) {
//apply tangential acceleration;
if (flags[FLAG_DISABLE_Z]) {
- Vector3 yx = Vector3(diff.y, 0, diff.x);
- force += yx.length() > 0.0 ? (yx * Vector3(-1.0, 0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3();
+ Vector2 yx = Vector2(diff.y, diff.x);
+ Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized();
+ force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3();
} else {
Vector3 crossDiff = diff.normalized().cross(gravity.normalized());
@@ -764,18 +769,18 @@ void CPUParticles::_particles_process(float p_delta) {
//apply attractor forces
p.velocity += force * local_delta;
//orbit velocity
-#if 0
if (flags[FLAG_DISABLE_Z]) {
-
- float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);
+ float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]);
if (orbit_amount != 0.0) {
- float ang = orbit_amount * DELTA * pi * 2.0;
- mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));
- TRANSFORM[3].xy -= diff.xy;
- TRANSFORM[3].xy += rot * diff.xy;
+ float ang = orbit_amount * local_delta * Math_PI * 2.0;
+ // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix,
+ // but we use -ang here to reproduce its behavior.
+ Transform2D rot = Transform2D(-ang, Vector2());
+ Vector2 rotv = rot.basis_xform(Vector2(diff.x, diff.y));
+ p.transform.origin -= Vector3(diff.x, diff.y, 0);
+ p.transform.origin += Vector3(rotv.x, rotv.y, 0);
}
}
-#endif
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
p.velocity = p.velocity.normalized() * tex_linear_velocity;
}
@@ -1171,7 +1176,7 @@ void CPUParticles::convert_from_particles(Node *p_particles) {
CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
- // CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
+ CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
CONVERT_PARAM(PARAM_LINEAR_ACCEL);
CONVERT_PARAM(PARAM_RADIAL_ACCEL);
CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
@@ -1314,12 +1319,10 @@ void CPUParticles::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY);
- /*
ADD_GROUP("Orbit Velocity", "orbit_");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY);
-*/
ADD_GROUP("Linear Accel", "linear_");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL);
@@ -1362,7 +1365,7 @@ void CPUParticles::_bind_methods() {
BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY);
- //BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY);
+ BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_LINEAR_ACCEL);
BIND_ENUM_CONSTANT(PARAM_RADIAL_ACCEL);
BIND_ENUM_CONSTANT(PARAM_TANGENTIAL_ACCEL);
@@ -1376,6 +1379,7 @@ void CPUParticles::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY);
BIND_ENUM_CONSTANT(FLAG_ROTATE_Y);
+ BIND_ENUM_CONSTANT(FLAG_DISABLE_Z);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT);
@@ -1414,7 +1418,7 @@ CPUParticles::CPUParticles() {
set_spread(45);
set_flatness(0);
set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1);
- //set_param(PARAM_ORBIT_VELOCITY, 0);
+ set_param(PARAM_ORBIT_VELOCITY, 0);
set_param(PARAM_LINEAR_ACCEL, 0);
set_param(PARAM_RADIAL_ACCEL, 0);
set_param(PARAM_TANGENTIAL_ACCEL, 0);
diff --git a/scene/3d/cpu_particles.h b/scene/3d/cpu_particles.h
index b863a3cb3f..6a989251f1 100644
--- a/scene/3d/cpu_particles.h
+++ b/scene/3d/cpu_particles.h
@@ -53,7 +53,7 @@ public:
PARAM_INITIAL_LINEAR_VELOCITY,
PARAM_ANGULAR_VELOCITY,
- //PARAM_ORBIT_VELOCITY,
+ PARAM_ORBIT_VELOCITY,
PARAM_LINEAR_ACCEL,
PARAM_RADIAL_ACCEL,
PARAM_TANGENTIAL_ACCEL,
diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp
index 57af951110..3624e04434 100644
--- a/scene/3d/physics_body.cpp
+++ b/scene/3d/physics_body.cpp
@@ -192,8 +192,8 @@ void StaticBody::set_friction(real_t p_friction) {
return;
}
- ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_friction < 0 || p_friction > 1);
@@ -206,8 +206,8 @@ void StaticBody::set_friction(real_t p_friction) {
real_t StaticBody::get_friction() const {
- ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 1;
@@ -222,8 +222,8 @@ void StaticBody::set_bounce(real_t p_bounce) {
return;
}
- ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1);
@@ -236,8 +236,8 @@ void StaticBody::set_bounce(real_t p_bounce) {
real_t StaticBody::get_bounce() const {
- ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 0;
@@ -636,8 +636,8 @@ void RigidBody::set_friction(real_t p_friction) {
return;
}
- ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_friction < 0 || p_friction > 1);
@@ -649,8 +649,8 @@ void RigidBody::set_friction(real_t p_friction) {
}
real_t RigidBody::get_friction() const {
- ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 1;
@@ -665,8 +665,8 @@ void RigidBody::set_bounce(real_t p_bounce) {
return;
}
- ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1);
@@ -677,8 +677,8 @@ void RigidBody::set_bounce(real_t p_bounce) {
physics_material_override->set_bounce(p_bounce);
}
real_t RigidBody::get_bounce() const {
- ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.")
- WARN_DEPRECATED
+ ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.");
+ WARN_DEPRECATED;
if (physics_material_override.is_null()) {
return 0;
}
diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h
index 0e7d9be505..3b924e0454 100644
--- a/scene/3d/visual_instance.h
+++ b/scene/3d/visual_instance.h
@@ -89,6 +89,7 @@ class GeometryInstance : public VisualInstance {
public:
enum Flags {
FLAG_USE_BAKED_LIGHT = VS::INSTANCE_FLAG_USE_BAKED_LIGHT,
+ FLAG_DRAW_NEXT_FRAME_IF_VISIBLE = VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE,
FLAG_MAX = VS::INSTANCE_FLAG_MAX,
};
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index e9b38ae990..20a09696e1 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -1049,7 +1049,7 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node
return CONNECTION_ERROR_NO_INPUT;
}
- if (!nodes.has(p_input_node)) {
+ if (p_input_node == p_output_node) {
return CONNECTION_ERROR_SAME_NODE;
}
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 8247728b9d..54f0fdc26a 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -316,7 +316,7 @@ String AnimationNode::get_caption() const {
void AnimationNode::add_input(const String &p_name) {
//root nodes can't add inputs
- ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL)
+ ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL);
Input input;
ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
input.name = p_name;
diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp
index 3cc90c2ad6..5c3e123ac3 100644
--- a/scene/animation/animation_tree_player.cpp
+++ b/scene/animation/animation_tree_player.cpp
@@ -404,7 +404,7 @@ void AnimationTreePlayer::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
ERR_EXPLAIN("AnimationTreePlayer has been deprecated. Use AnimationTree instead.");
- WARN_DEPRECATED
+ WARN_DEPRECATED;
if (!processing) {
//make sure that a previous process state was not saved
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 23998183b8..c70e58564f 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -34,10 +34,14 @@
void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8, const Variant &p_arg9, const Variant &p_arg10) {
+ // Add a new pending command and reference it
pending_commands.push_back(PendingCommand());
PendingCommand &cmd = pending_commands.back()->get();
+ // Update the command with the target key
cmd.key = p_key;
+
+ // Determine command argument count
int &count = cmd.args;
if (p_arg10.get_type() != Variant::NIL)
count = 10;
@@ -59,6 +63,9 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const
count = 2;
else if (p_arg1.get_type() != Variant::NIL)
count = 1;
+
+ // Add the specified arguments to the command
+ // TODO: Make this a switch statement?
if (count > 0)
cmd.arg[0] = p_arg1;
if (count > 1)
@@ -83,10 +90,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const
void Tween::_process_pending_commands() {
+ // For each pending command...
for (List<PendingCommand>::Element *E = pending_commands.front(); E; E = E->next()) {
+ // Get the command
PendingCommand &cmd = E->get();
Variant::CallError err;
+
+ // Grab all of the arguments for the command
Variant *arg[10] = {
&cmd.arg[0],
&cmd.arg[1],
@@ -99,16 +110,20 @@ void Tween::_process_pending_commands() {
&cmd.arg[8],
&cmd.arg[9],
};
+
+ // Execute the command (and retrieve any errors)
this->call(cmd.key, (const Variant **)arg, cmd.args, err);
}
+
+ // Clear the pending commands
pending_commands.clear();
}
bool Tween::_set(const StringName &p_name, const Variant &p_value) {
+ // Set the correct attribute based on the given name
String name = p_name;
-
- if (name == "playback/speed" || name == "speed") { //bw compatibility
+ if (name == "playback/speed" || name == "speed") { // Backwards compatibility
set_speed_scale(p_value);
} else if (name == "playback/active") {
@@ -122,69 +137,78 @@ bool Tween::_set(const StringName &p_name, const Variant &p_value) {
bool Tween::_get(const StringName &p_name, Variant &r_ret) const {
+ // Get the correct attribute based on the given name
String name = p_name;
-
- if (name == "playback/speed") { //bw compatibility
-
+ if (name == "playback/speed") { // Backwards compatibility
r_ret = speed_scale;
- } else if (name == "playback/active") {
+ } else if (name == "playback/active") {
r_ret = is_active();
- } else if (name == "playback/repeat") {
+ } else if (name == "playback/repeat") {
r_ret = is_repeat();
}
-
return true;
}
void Tween::_get_property_list(List<PropertyInfo> *p_list) const {
-
+ // Add the property info for the Tween object
p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::REAL, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01"));
}
void Tween::_notification(int p_what) {
-
+ // What notification did we receive?
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
-
+ // Are we not already active?
if (!is_active()) {
- //make sure that a previous process state was not saved
- //only process if "processing" is set
+ // Make sure that a previous process state was not saved
+ // Only process if "processing" is set
set_physics_process_internal(false);
set_process_internal(false);
}
} break;
- case NOTIFICATION_READY: {
+ case NOTIFICATION_READY: {
+ // Do nothing
} break;
+
case NOTIFICATION_INTERNAL_PROCESS: {
+ // Are we processing during physics time?
if (tween_process_mode == TWEEN_PROCESS_PHYSICS)
+ // Do nothing since we aren't aligned with physics when we should be
break;
+ // Should we update?
if (is_active())
+ // Update the tweens
_tween_process(get_process_delta_time());
} break;
- case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ // Are we processing during 'regular' time?
if (tween_process_mode == TWEEN_PROCESS_IDLE)
+ // Do nothing since we whould only process during idle time
break;
+ // Should we update?
if (is_active())
+ // Update the tweens
_tween_process(get_physics_process_delta_time());
} break;
- case NOTIFICATION_EXIT_TREE: {
+ case NOTIFICATION_EXIT_TREE: {
+ // We've left the tree. Stop all tweens
stop_all();
} break;
}
}
void Tween::_bind_methods() {
-
+ // Bind getters and setters
ClassDB::bind_method(D_METHOD("is_active"), &Tween::is_active);
ClassDB::bind_method(D_METHOD("set_active", "active"), &Tween::set_active);
@@ -197,6 +221,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Tween::set_tween_process_mode);
ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Tween::get_tween_process_mode);
+ // Bind the various Tween control methods
ClassDB::bind_method(D_METHOD("start"), &Tween::start);
ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Tween::reset, DEFVAL(""));
ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all);
@@ -211,6 +236,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("tell"), &Tween::tell);
ClassDB::bind_method(D_METHOD("get_runtime"), &Tween::get_runtime);
+ // Bind interpolation and follow methods
ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_property, DEFVAL(0));
ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_method, DEFVAL(0));
ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5"), &Tween::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()));
@@ -220,18 +246,22 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_property, DEFVAL(0));
ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_method, DEFVAL(0));
+ // Add the Tween signals
ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));
ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::REAL, "elapsed"), PropertyInfo(Variant::OBJECT, "value")));
ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));
ADD_SIGNAL(MethodInfo("tween_all_completed"));
+ // Add the properties and tie them to the getters and setters
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
+ // Bind Idle vs Physics process
BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE);
+ // Bind the Transition type constants
BIND_ENUM_CONSTANT(TRANS_LINEAR);
BIND_ENUM_CONSTANT(TRANS_SINE);
BIND_ENUM_CONSTANT(TRANS_QUINT);
@@ -244,6 +274,7 @@ void Tween::_bind_methods() {
BIND_ENUM_CONSTANT(TRANS_BOUNCE);
BIND_ENUM_CONSTANT(TRANS_BACK);
+ // Bind the easing constants
BIND_ENUM_CONSTANT(EASE_IN);
BIND_ENUM_CONSTANT(EASE_OUT);
BIND_ENUM_CONSTANT(EASE_IN_OUT);
@@ -252,27 +283,30 @@ void Tween::_bind_methods() {
Variant &Tween::_get_initial_val(InterpolateData &p_data) {
+ // What type of data are we interpolating?
switch (p_data.type) {
case INTER_PROPERTY:
case INTER_METHOD:
case FOLLOW_PROPERTY:
case FOLLOW_METHOD:
+ // Simply use the given initial value
return p_data.initial_val;
case TARGETING_PROPERTY:
case TARGETING_METHOD: {
-
+ // Get the object that is being targeted
Object *object = ObjectDB::get_instance(p_data.target_id);
ERR_FAIL_COND_V(object == NULL, p_data.initial_val);
+ // Are we targeting a property or a method?
static Variant initial_val;
if (p_data.type == TARGETING_PROPERTY) {
-
+ // Get the property from the target object
bool valid = false;
initial_val = object->get_indexed(p_data.target_key, &valid);
ERR_FAIL_COND_V(!valid, p_data.initial_val);
} else {
-
+ // Call the method and get the initial value from it
Variant::CallError error;
initial_val = object->call(p_data.target_key[0], NULL, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
@@ -281,64 +315,75 @@ Variant &Tween::_get_initial_val(InterpolateData &p_data) {
}
case INTER_CALLBACK:
+ // Callback does not have a special initial value
break;
}
+ // If we've made it here, just return the delta value as the initial value
return p_data.delta_val;
}
Variant &Tween::_get_delta_val(InterpolateData &p_data) {
+ // What kind of data are we interpolating?
switch (p_data.type) {
case INTER_PROPERTY:
case INTER_METHOD:
+ // Simply return the given delta value
return p_data.delta_val;
case FOLLOW_PROPERTY:
case FOLLOW_METHOD: {
-
+ // We're following an object, so grab that instance
Object *target = ObjectDB::get_instance(p_data.target_id);
ERR_FAIL_COND_V(target == NULL, p_data.initial_val);
+ // We want to figure out the final value
Variant final_val;
-
if (p_data.type == FOLLOW_PROPERTY) {
-
+ // Read the property as-is
bool valid = false;
final_val = target->get_indexed(p_data.target_key, &valid);
ERR_FAIL_COND_V(!valid, p_data.initial_val);
} else {
-
+ // We're looking at a method. Call the method on the target object
Variant::CallError error;
final_val = target->call(p_data.target_key[0], NULL, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
}
- // convert INT to REAL is better for interpolaters
+ // If we're looking at an INT value, instead convert it to a REAL
+ // This is better for interpolation
if (final_val.get_type() == Variant::INT) final_val = final_val.operator real_t();
+
+ // Calculate the delta based on the initial value and the final value
_calc_delta_val(p_data.initial_val, final_val, p_data.delta_val);
return p_data.delta_val;
}
case TARGETING_PROPERTY:
case TARGETING_METHOD: {
-
+ // Grab the initial value from the data to calculate delta
Variant initial_val = _get_initial_val(p_data);
- // convert INT to REAL is better for interpolaters
+
+ // If we're looking at an INT value, instead convert it to a REAL
+ // This is better for interpolation
if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t();
- //_calc_delta_val(p_data.initial_val, p_data.final_val, p_data.delta_val);
+ // Calculate the delta based on the initial value and the final value
_calc_delta_val(initial_val, p_data.final_val, p_data.delta_val);
return p_data.delta_val;
}
case INTER_CALLBACK:
+ // Callbacks have no special delta
break;
}
+ // If we've made it here, use the initial value as the delta
return p_data.initial_val;
}
Variant Tween::_run_equation(InterpolateData &p_data) {
-
+ // Get the initial and delta values from the data
Variant &initial_val = _get_initial_val(p_data);
Variant &delta_val = _get_delta_val(p_data);
Variant result;
@@ -346,48 +391,59 @@ Variant Tween::_run_equation(InterpolateData &p_data) {
#define APPLY_EQUATION(element) \
r.element = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, i.element, d.element, p_data.duration);
+ // What type of data are we interpolating?
switch (initial_val.get_type()) {
case Variant::BOOL:
+ // Run the boolean specific equation (checking if it is at least 0.5)
result = (_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, initial_val, delta_val, p_data.duration)) >= 0.5;
break;
case Variant::INT:
+ // Run the integer specific equation
result = (int)_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int)initial_val, (int)delta_val, p_data.duration);
break;
case Variant::REAL:
+ // Run the REAL specific equation
result = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (real_t)initial_val, (real_t)delta_val, p_data.duration);
break;
case Variant::VECTOR2: {
+ // Get vectors for initial and delta values
Vector2 i = initial_val;
Vector2 d = delta_val;
Vector2 r;
+ // Execute the equation and mutate the r vector
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
-
result = r;
} break;
case Variant::VECTOR3: {
+ // Get vectors for initial and delta values
Vector3 i = initial_val;
Vector3 d = delta_val;
Vector3 r;
+ // Execute the equation and mutate the r vector
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
APPLY_EQUATION(z);
-
result = r;
} break;
case Variant::BASIS: {
+ // Get the basis for initial and delta values
Basis i = initial_val;
Basis d = delta_val;
Basis r;
+ // Execute the equation on all the basis and mutate the r basis
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(elements[0][0]);
APPLY_EQUATION(elements[0][1]);
APPLY_EQUATION(elements[0][2]);
@@ -397,55 +453,63 @@ Variant Tween::_run_equation(InterpolateData &p_data) {
APPLY_EQUATION(elements[2][0]);
APPLY_EQUATION(elements[2][1]);
APPLY_EQUATION(elements[2][2]);
-
result = r;
} break;
case Variant::TRANSFORM2D: {
+ // Get the transforms for initial and delta values
Transform2D i = initial_val;
Transform2D d = delta_val;
Transform2D r;
+ // Execute the equation on the transforms and mutate the r transform
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(elements[0][0]);
APPLY_EQUATION(elements[0][1]);
APPLY_EQUATION(elements[1][0]);
APPLY_EQUATION(elements[1][1]);
APPLY_EQUATION(elements[2][0]);
APPLY_EQUATION(elements[2][1]);
-
result = r;
} break;
case Variant::QUAT: {
+ // Get the quaternian for the initial and delta values
Quat i = initial_val;
Quat d = delta_val;
Quat r;
+ // Execute the equation on the quaternian values and mutate the r quaternian
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
APPLY_EQUATION(z);
APPLY_EQUATION(w);
-
result = r;
} break;
case Variant::AABB: {
+ // Get the AABB's for the initial and delta values
AABB i = initial_val;
AABB d = delta_val;
AABB r;
+ // Execute the equation for the position and size of the AABB's and mutate the r AABB
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(position.x);
APPLY_EQUATION(position.y);
APPLY_EQUATION(position.z);
APPLY_EQUATION(size.x);
APPLY_EQUATION(size.y);
APPLY_EQUATION(size.z);
-
result = r;
} break;
case Variant::TRANSFORM: {
+ // Get the transforms for the initial and delta values
Transform i = initial_val;
Transform d = delta_val;
Transform r;
+ // Execute the equation for each of the transforms and their origin and mutate the r transform
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(basis.elements[0][0]);
APPLY_EQUATION(basis.elements[0][1]);
APPLY_EQUATION(basis.elements[0][2]);
@@ -458,40 +522,45 @@ Variant Tween::_run_equation(InterpolateData &p_data) {
APPLY_EQUATION(origin.x);
APPLY_EQUATION(origin.y);
APPLY_EQUATION(origin.z);
-
result = r;
} break;
case Variant::COLOR: {
+ // Get the Color for initial and delta value
Color i = initial_val;
Color d = delta_val;
Color r;
+ // Apply the equation on the Color RGBA, and mutate the r color
+ // This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(r);
APPLY_EQUATION(g);
APPLY_EQUATION(b);
APPLY_EQUATION(a);
-
result = r;
} break;
default: {
+ // If unknown, just return the initial value
result = initial_val;
} break;
};
#undef APPLY_EQUATION
-
+ // Return the result that was computed
return result;
}
bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {
+ // Get the object we want to apply the new value to
Object *object = ObjectDB::get_instance(p_data.id);
ERR_FAIL_COND_V(object == NULL, false);
+ // What kind of data are we mutating?
switch (p_data.type) {
case INTER_PROPERTY:
case FOLLOW_PROPERTY:
case TARGETING_PROPERTY: {
+ // Simply set the property on the object
bool valid = false;
object->set_indexed(p_data.key, value, &valid);
return valid;
@@ -500,85 +569,112 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {
case INTER_METHOD:
case FOLLOW_METHOD:
case TARGETING_METHOD: {
+ // We want to call the method on the target object
Variant::CallError error;
+
+ // Do we have a non-nil value passed in?
if (value.get_type() != Variant::NIL) {
+ // Pass it as an argument to the function call
Variant *arg[1] = { &value };
object->call(p_data.key[0], (const Variant **)arg, 1, error);
} else {
+ // Don't pass any argument
object->call(p_data.key[0], NULL, 0, error);
}
+ // Did we get an error from the function call?
if (error.error == Variant::CallError::CALL_OK)
return true;
return false;
}
case INTER_CALLBACK:
+ // Nothing to apply for a callback
break;
};
+ // No issues found!
return true;
}
void Tween::_tween_process(float p_delta) {
-
+ // Process all of the pending commands
_process_pending_commands();
+ // If the scale is 0, make no progress on the tweens
if (speed_scale == 0)
return;
- p_delta *= speed_scale;
+ // Update the delta and whether we are pending an update
+ p_delta *= speed_scale;
pending_update++;
- // if repeat and all interpolates was finished then reset all interpolates
- bool all_finished = true;
- if (repeat) {
+ // Are we repeating the interpolations?
+ if (repeat) {
+ // For each interpolation...
+ bool repeats_finished = true;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the data from it
InterpolateData &data = E->get();
+ // Is not finished?
if (!data.finish) {
- all_finished = false;
+ // We aren't finished yet, no need to check the rest
+ repeats_finished = false;
break;
}
}
- if (all_finished)
+ // If we are all finished, we can reset all of the tweens
+ if (repeats_finished)
reset_all();
}
- all_finished = true;
+ // Are all of the tweens complete?
+ bool all_finished = true;
+
+ // For each tween we wish to interpolate...
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
+ // Get the data from it
InterpolateData &data = E->get();
+
+ // Track if we hit one that isn't finished yet
all_finished = all_finished && data.finish;
+ // Is the data not active or already finished? No need to go any further
if (!data.active || data.finish)
continue;
+ // Get the target object for this interpolation
Object *object = ObjectDB::get_instance(data.id);
if (object == NULL)
continue;
+ // Are we still delaying this tween?
bool prev_delaying = data.elapsed <= data.delay;
data.elapsed += p_delta;
if (data.elapsed < data.delay)
continue;
else if (prev_delaying) {
-
+ // We can apply the tween's value to the data and emit that the tween has started
_apply_tween_value(data, data.initial_val);
emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));
}
+ // Are we at the end of the tween?
if (data.elapsed > (data.delay + data.duration)) {
-
+ // Set the elapsed time to the end and mark this one as finished
data.elapsed = data.delay + data.duration;
data.finish = true;
}
+ // Are we interpolating a callback?
if (data.type == INTER_CALLBACK) {
+ // Is the tween completed?
if (data.finish) {
+ // Are we calling this callback deferred or immediately?
if (data.call_deferred) {
-
+ // Run the deferred function callback, applying the correct number of arguments
switch (data.args) {
case 0:
object->call_deferred(data.key[0]);
@@ -600,6 +696,7 @@ void Tween::_tween_process(float p_delta) {
break;
}
} else {
+ // Call the function directly with the arguments
Variant::CallError error;
Variant *arg[5] = {
&data.arg[0],
@@ -612,23 +709,35 @@ void Tween::_tween_process(float p_delta) {
}
}
} else {
+ // We can apply the value directly
Variant result = _run_equation(data);
_apply_tween_value(data, result);
+
+ // Emit that the tween has taken a step
emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);
}
+ // Is the tween now finished?
if (data.finish) {
+ // Set it to the final value directly
_apply_tween_value(data, data.final_val);
+
+ // Mark the tween as completed and emit the signal
data.elapsed = 0;
emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false));
- // not repeat mode, remove completed action
+
+ // If we are not repeating the tween, remove it
if (!repeat)
call_deferred("_remove_by_uid", data.uid);
- } else if (!repeat)
+ } else if (!repeat) {
+ // Check whether all tweens are finished
all_finished = all_finished && data.finish;
+ }
}
+ // One less update left to go
pending_update--;
+ // If all tweens are completed, we no longer need to be active
if (all_finished) {
set_active(false);
emit_signal("tween_all_completed");
@@ -636,76 +745,75 @@ void Tween::_tween_process(float p_delta) {
}
void Tween::set_tween_process_mode(TweenProcessMode p_mode) {
-
tween_process_mode = p_mode;
}
Tween::TweenProcessMode Tween::get_tween_process_mode() const {
-
return tween_process_mode;
}
bool Tween::is_active() const {
-
return is_processing_internal() || is_physics_processing_internal();
}
void Tween::set_active(bool p_active) {
-
+ // Do nothing if it's the same active mode that we currently are
if (is_active() == p_active)
return;
+ // Depending on physics or idle, set processing
switch (tween_process_mode) {
-
case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break;
case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break;
}
}
bool Tween::is_repeat() const {
-
return repeat;
}
void Tween::set_repeat(bool p_repeat) {
-
repeat = p_repeat;
}
void Tween::set_speed_scale(float p_speed) {
-
speed_scale = p_speed;
}
float Tween::get_speed_scale() const {
-
return speed_scale;
}
bool Tween::start() {
+ // Are there any pending updates?
if (pending_update != 0) {
+ // Start the tweens after deferring
call_deferred("start");
return true;
}
+ // We want to be activated
set_active(true);
return true;
}
bool Tween::reset(Object *p_object, StringName p_key) {
-
+ // Find all interpolations that use the same object and target string
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the target object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == NULL)
continue;
+ // Do we have the correct object and key?
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
-
+ // Reset the tween to the initial state
data.elapsed = 0;
data.finish = false;
+
+ // Also apply the initial state if there isn't a delay
if (data.delay == 0)
_apply_tween_value(data, data.initial_val);
}
@@ -715,13 +823,15 @@ bool Tween::reset(Object *p_object, StringName p_key) {
}
bool Tween::reset_all() {
-
+ // Go through all interpolations
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the target data and set it back to the initial state
InterpolateData &data = E->get();
data.elapsed = 0;
data.finish = false;
+
+ // If there isn't a delay, apply the value to the object
if (data.delay == 0)
_apply_tween_value(data, data.initial_val);
}
@@ -730,15 +840,19 @@ bool Tween::reset_all() {
}
bool Tween::stop(Object *p_object, StringName p_key) {
-
+ // Find the tween that has the given target object and string key
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
+ // Get the object the tween is targeting
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == NULL)
continue;
+
+ // Is this the correct object and does it have the given key?
if (object == p_object && (data.concatenated_key == p_key || p_key == ""))
+ // Disable the tween
data.active = false;
}
pending_update--;
@@ -746,12 +860,13 @@ bool Tween::stop(Object *p_object, StringName p_key) {
}
bool Tween::stop_all() {
-
+ // We no longer need to be active since all tweens have been stopped
set_active(false);
+ // For each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Simply set it inactive
InterpolateData &data = E->get();
data.active = false;
}
@@ -760,16 +875,20 @@ bool Tween::stop_all() {
}
bool Tween::resume(Object *p_object, StringName p_key) {
-
+ // We need to be activated
+ // TODO: What if no tween is found??
set_active(true);
+ // Find the tween that uses the given target object and string key
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Grab the object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == NULL)
continue;
+
+ // If the object and string key match, activate it
if (object == p_object && (data.concatenated_key == p_key || p_key == ""))
data.active = true;
}
@@ -778,12 +897,14 @@ bool Tween::resume(Object *p_object, StringName p_key) {
}
bool Tween::resume_all() {
-
+ // Set ourselves active so we can process tweens
+ // TODO: What if there are no tweens? We get set to active for no reason!
set_active(true);
+ // For each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Simply grab it and set it to active
InterpolateData &data = E->get();
data.active = true;
}
@@ -792,35 +913,46 @@ bool Tween::resume_all() {
}
bool Tween::remove(Object *p_object, StringName p_key) {
+ // If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("remove", p_object, p_key);
return true;
}
+
+ // For each interpolation...
List<List<InterpolateData>::Element *> for_removal;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the target object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == NULL)
continue;
+
+ // If the target object and string key match, queue it for removal
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
for_removal.push_back(E);
}
}
+
+ // For each interpolation we wish to remove...
for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) {
+ // Erase it
interpolates.erase(E->get());
}
return true;
}
void Tween::_remove_by_uid(int uid) {
+ // If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("_remove_by_uid", uid);
return;
}
+ // Find the interpolation that matches the given UID
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
if (uid == E->get().uid) {
+ // It matches, erase it and stop looking
E->erase();
break;
}
@@ -829,49 +961,61 @@ void Tween::_remove_by_uid(int uid) {
void Tween::_push_interpolate_data(InterpolateData &p_data) {
pending_update++;
+
+ // Add the new interpolation
p_data.uid = ++uid;
interpolates.push_back(p_data);
+
pending_update--;
}
bool Tween::remove_all() {
-
+ // If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("remove_all");
return true;
}
+ // We no longer need to be active
set_active(false);
+
+ // Clear out all interpolations and reset the uid
interpolates.clear();
uid = 0;
+
return true;
}
bool Tween::seek(real_t p_time) {
-
+ // Go through each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the target data
InterpolateData &data = E->get();
+ // Update the elapsed data to be set to the target time
data.elapsed = p_time;
- if (data.elapsed < data.delay) {
+ // Are we at the end?
+ if (data.elapsed < data.delay) {
+ // There is still time left to go
data.finish = false;
continue;
} else if (data.elapsed >= (data.delay + data.duration)) {
-
- data.finish = true;
+ // We are past the end of it, set the elapsed time to the end and mark as finished
data.elapsed = (data.delay + data.duration);
+ data.finish = true;
} else {
+ // We are not finished with this interpolation yet
data.finish = false;
}
+ // If we are a callback, do nothing special
if (data.type == INTER_CALLBACK) {
continue;
}
+ // Run the equation on the data and apply the value
Variant result = _run_equation(data);
-
_apply_tween_value(data, result);
}
pending_update--;
@@ -879,13 +1023,16 @@ bool Tween::seek(real_t p_time) {
}
real_t Tween::tell() const {
-
+ // We want to grab the position of the furthest along tween
pending_update++;
real_t pos = 0;
- for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
+ // For each interpolation...
+ for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
+ // Get the data and figure out if it's position is further along than the previous ones
const InterpolateData &data = E->get();
if (data.elapsed > pos)
+ // Save it if so
pos = data.elapsed;
}
pending_update--;
@@ -893,55 +1040,63 @@ real_t Tween::tell() const {
}
real_t Tween::get_runtime() const {
-
+ // If the tween isn't moving, it'll last forever
if (speed_scale == 0) {
return INFINITY;
}
pending_update++;
+
+ // For each interpolation...
real_t runtime = 0;
for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
-
+ // Get the tween data and see if it's runtime is greater than the previous tweens
const InterpolateData &data = E->get();
real_t t = data.delay + data.duration;
if (t > runtime)
+ // This is the longest running tween
runtime = t;
}
pending_update--;
+ // Adjust the runtime for the current speed scale
return runtime / speed_scale;
}
bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) {
+ // Get the initial, final, and delta values
const Variant &initial_val = p_initial_val;
const Variant &final_val = p_final_val;
Variant &delta_val = p_delta_val;
+ // What kind of data are we interpolating?
switch (initial_val.get_type()) {
case Variant::BOOL:
- //delta_val = p_final_val;
- delta_val = (int)p_final_val - (int)p_initial_val;
- break;
-
+ // We'll treat booleans just like integers
case Variant::INT:
+ // Compute the integer delta
delta_val = (int)final_val - (int)initial_val;
break;
case Variant::REAL:
+ // Convert to REAL and find the delta
delta_val = (real_t)final_val - (real_t)initial_val;
break;
case Variant::VECTOR2:
+ // Convert to Vectors and find the delta
delta_val = final_val.operator Vector2() - initial_val.operator Vector2();
break;
case Variant::VECTOR3:
+ // Convert to Vectors and find the delta
delta_val = final_val.operator Vector3() - initial_val.operator Vector3();
break;
case Variant::BASIS: {
+ // Build a new basis which is the delta between the initial and final values
Basis i = initial_val;
Basis f = final_val;
delta_val = Basis(f.elements[0][0] - i.elements[0][0],
@@ -956,6 +1111,7 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final
} break;
case Variant::TRANSFORM2D: {
+ // Build a new transform which is the difference between the initial and final values
Transform2D i = initial_val;
Transform2D f = final_val;
Transform2D d = Transform2D();
@@ -967,15 +1123,21 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final
d[2][1] = f.elements[2][1] - i.elements[2][1];
delta_val = d;
} break;
+
case Variant::QUAT:
+ // Convert to quaternianls and find the delta
delta_val = final_val.operator Quat() - initial_val.operator Quat();
break;
+
case Variant::AABB: {
+ // Build a new AABB and use the new position and sizes to make a delta
AABB i = initial_val;
AABB f = final_val;
delta_val = AABB(f.position - i.position, f.size - i.size);
} break;
+
case Variant::TRANSFORM: {
+ // Build a new transform which is the difference between the initial and final values
Transform i = initial_val;
Transform f = final_val;
Transform d;
@@ -994,124 +1156,157 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final
delta_val = d;
} break;
+
case Variant::COLOR: {
+ // Make a new color which is the difference between each the color's RGBA attributes
Color i = initial_val;
Color f = final_val;
delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a);
} break;
default:
+ // TODO: Should move away from a 'magic string'?
ERR_PRINT("Invalid param type, except(int/real/vector2/vector/matrix/matrix32/quat/aabb/transform/color)");
return false;
};
return true;
}
-bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
- if (pending_update != 0) {
- _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
- return true;
- }
- p_property = p_property.get_as_property_path();
-
- if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames());
-
- // convert INT to REAL is better for interpolaters
- if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
- if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
-
- ERR_FAIL_COND_V(p_object == NULL, false);
- ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
- ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
- ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
- ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
- ERR_FAIL_COND_V(p_delay < 0, false);
+bool Tween::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
- bool prop_valid = false;
- p_object->get_indexed(p_property.get_subnames(), &prop_valid);
- ERR_FAIL_COND_V(!prop_valid, false);
+ // TODO: Add initialization+implementation for remaining interpolation types
+ // TODO: Fix this method's organization to take advantage of the type
+ // Make a new interpolation data
InterpolateData data;
data.active = true;
- data.type = INTER_PROPERTY;
+ data.type = p_interpolation_type;
data.finish = false;
data.elapsed = 0;
+ // Validate and apply interpolation data
+
+ // Give it the object
+ ERR_EXPLAIN("Invalid object provided to Tween!");
+ ERR_FAIL_COND_V(p_object == NULL, false); // Is the object real
+ ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); // Is the object a valid instance?
data.id = p_object->get_instance_id();
- data.key = p_property.get_subnames();
- data.concatenated_key = p_property.get_concatenated_subnames();
+
+ // Validate the initial and final values
+ ERR_EXPLAIN("Initial value type does not match final value type!"); // TODO: Print both types to make debugging easier
+ ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); // Do the initial and final value types match?
data.initial_val = p_initial_val;
data.final_val = p_final_val;
+
+ // Check the Duration
+ ERR_EXPLAIN("Only non-negative duration values allowed in Tweens!");
+ ERR_FAIL_COND_V(p_duration < 0, false); // Is the tween duration non-negative
data.duration = p_duration;
+
+ // Tween Delay
+ ERR_EXPLAIN("Only non-negative delay values allowed in Tweens!");
+ ERR_FAIL_COND_V(p_delay < 0, false); // Is the delay non-negative?
+ data.delay = p_delay;
+
+ // Transition type
+ ERR_EXPLAIN("Invalid transition type provided to Tween");
+ ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); // Is the transition type valid
data.trans_type = p_trans_type;
+
+ // Easing type
+ ERR_EXPLAIN("Invalid easing type provided to Tween");
+ ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); // Is the easing type valid
data.ease_type = p_ease_type;
- data.delay = p_delay;
+ // Is the property defined?
+ if (p_property) {
+ // Check that the object actually contains the given property
+ bool prop_valid = false;
+ p_object->get_indexed(p_property->get_subnames(), &prop_valid);
+ ERR_EXPLAIN("Tween target object has no property named: " + p_property->get_concatenated_subnames());
+ ERR_FAIL_COND_V(!prop_valid, false);
+
+ data.key = p_property->get_subnames();
+ data.concatenated_key = p_property->get_concatenated_subnames();
+ }
+
+ // Is the method defined?
+ if (p_method) {
+ // Does the object even have the requested method?
+ ERR_EXPLAIN("Tween target object has no method named: " + *p_method); // TODO: Fix this error message
+ ERR_FAIL_COND_V(!p_object->has_method(*p_method), false);
+
+ data.key.push_back(*p_method);
+ data.concatenated_key = *p_method;
+ }
+
+ // Is there not a valid delta?
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
+ // Add this interpolation to the total
_push_interpolate_data(data);
return true;
}
-bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+ // If we are busy updating, call this function again later
if (pending_update != 0) {
- _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
+ _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
- // convert INT to REAL is better for interpolaters
- if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
- if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
- ERR_FAIL_COND_V(p_object == NULL, false);
- ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
- ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
- ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
- ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
- ERR_FAIL_COND_V(p_delay < 0, false);
+ // Get the property from the node path
+ p_property = p_property.get_as_property_path();
- ERR_EXPLAIN("Object has no method named: %s" + p_method);
- ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
+ // If no initial value given, grab the initial value from the object
+ // TODO: Is this documented? This is very useful and removes a lot of clutter from tweens!
+ if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames());
- InterpolateData data;
- data.active = true;
- data.type = INTER_METHOD;
- data.finish = false;
- data.elapsed = 0;
+ // Convert any integers into REALs as they are better for interpolation
+ if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
+ if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
- data.id = p_object->get_instance_id();
- data.key.push_back(p_method);
- data.concatenated_key = p_method;
- data.initial_val = p_initial_val;
- data.final_val = p_final_val;
- data.duration = p_duration;
- data.trans_type = p_trans_type;
- data.ease_type = p_ease_type;
- data.delay = p_delay;
+ // Build the interpolation data
+ bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, NULL, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
+ return result;
+}
- if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
- return false;
+bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+ // If we are busy updating, call this function again later
+ if (pending_update != 0) {
+ _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
+ return true;
+ }
- _push_interpolate_data(data);
- return true;
+ // Convert any integers into REALs as they are better for interpolation
+ if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
+ if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
+
+ // Build the interpolation data
+ bool result = _build_interpolation(INTER_METHOD, p_object, NULL, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
+ return result;
}
bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) {
-
+ // If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
return true;
}
+ // Check that the target object is valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
+
+ // Duration cannot be negative
ERR_FAIL_COND_V(p_duration < 0, false);
+ // Check whether the object even has the callback
ERR_EXPLAIN("Object has no callback named: %s" + p_callback);
ERR_FAIL_COND_V(!p_object->has_method(p_callback), false);
+ // Build a new InterpolationData
InterpolateData data;
data.active = true;
data.type = INTER_CALLBACK;
@@ -1119,12 +1314,14 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c
data.call_deferred = false;
data.elapsed = 0;
+ // Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_callback);
data.concatenated_key = p_callback;
data.duration = p_duration;
data.delay = 0;
+ // Add arguments to the interpolation
int args = 0;
if (p_arg5.get_type() != Variant::NIL)
args = 5;
@@ -1146,23 +1343,30 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
+ // Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) {
-
+ // If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
return true;
}
+
+ // Check that the target object is valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
+
+ // No negative durations allowed
ERR_FAIL_COND_V(p_duration < 0, false);
+ // Confirm the callback exists on the object
ERR_EXPLAIN("Object has no callback named: %s" + p_callback);
ERR_FAIL_COND_V(!p_object->has_method(p_callback), false);
+ // Create a new InterpolateData for the callback
InterpolateData data;
data.active = true;
data.type = INTER_CALLBACK;
@@ -1170,12 +1374,14 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S
data.call_deferred = true;
data.elapsed = 0;
+ // Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_callback);
data.concatenated_key = p_callback;
data.duration = p_duration;
data.delay = 0;
+ // Collect arguments for the callback
int args = 0;
if (p_arg5.get_type() != Variant::NIL)
args = 5;
@@ -1197,32 +1403,46 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
+ // Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+ // If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
+
+ // Get the two properties from their paths
p_property = p_property.get_as_property_path();
p_target_property = p_target_property.get_as_property_path();
+ // If no initial value is given, grab it from the source object
+ // TODO: Is this documented? It's really helpful for decluttering tweens
if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames());
- // convert INT to REAL is better for interpolaters
+ // Convert initial INT values to REAL as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
+ // Confirm the source and target objects are valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
ERR_FAIL_COND_V(p_target == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
+
+ // No negative durations
+ ERR_FAIL_COND_V(p_duration < 0, false);
+
+ // Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
+
+ // No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
+ // Confirm the source and target objects have the desired properties
bool prop_valid = false;
p_object->get_indexed(p_property.get_subnames(), &prop_valid);
ERR_FAIL_COND_V(!prop_valid, false);
@@ -1231,16 +1451,20 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini
Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid);
ERR_FAIL_COND_V(!target_prop_valid, false);
- // convert INT to REAL is better for interpolaters
+ // Convert target INT to REAL since it is better for interpolation
if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t();
+
+ // Verify that the target value and initial value are the same type
ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false);
+ // Create a new InterpolateData
InterpolateData data;
data.active = true;
data.type = FOLLOW_PROPERTY;
data.finish = false;
data.elapsed = 0;
+ // Give the InterpolateData it's configuration
data.id = p_object->get_instance_id();
data.key = p_property.get_subnames();
data.concatenated_key = p_property.get_concatenated_subnames();
@@ -1252,46 +1476,59 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini
data.ease_type = p_ease_type;
data.delay = p_delay;
+ // Add the interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+ // If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
- // convert INT to REAL is better for interpolaters
+ // Convert initial INT values to REAL as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
+ // Verify the source and target objects are valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
ERR_FAIL_COND_V(p_target == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
+
+ // No negative durations
+ ERR_FAIL_COND_V(p_duration < 0, false);
+
+ // Ensure that the transition and ease types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
+
+ // No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
+ // Confirm both objects have the target methods
ERR_EXPLAIN("Object has no method named: %s" + p_method);
ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
ERR_EXPLAIN("Target has no method named: %s" + p_target_method);
ERR_FAIL_COND_V(!p_target->has_method(p_target_method), false);
+ // Call the method to get the target value
Variant::CallError error;
Variant target_val = p_target->call(p_target_method, NULL, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false);
- // convert INT to REAL is better for interpolaters
+ // Convert target INT values to REAL as they are better for interpolation
if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t();
ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false);
+ // Make the new InterpolateData for the method follow
InterpolateData data;
data.active = true;
data.type = FOLLOW_METHOD;
data.finish = false;
data.elapsed = 0;
+ // Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_method);
data.concatenated_key = p_method;
@@ -1303,31 +1540,41 @@ bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initi
data.ease_type = p_ease_type;
data.delay = p_delay;
+ // Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
-
+ // If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
+ // Grab the target property and the target property
p_property = p_property.get_as_property_path();
p_initial_property = p_initial_property.get_as_property_path();
- // convert INT to REAL is better for interpolaters
+ // Convert the initial INT values to REAL as they are better for Interpolation
if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
+ // Verify both objects are valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
ERR_FAIL_COND_V(p_initial == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
+
+ // No negative durations
+ ERR_FAIL_COND_V(p_duration < 0, false);
+
+ // Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
+
+ // No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
+ // Ensure the initial and target properties exist on their objects
bool prop_valid = false;
p_object->get_indexed(p_property.get_subnames(), &prop_valid);
ERR_FAIL_COND_V(!prop_valid, false);
@@ -1336,16 +1583,18 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_
Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid);
ERR_FAIL_COND_V(!initial_prop_valid, false);
- // convert INT to REAL is better for interpolaters
+ // Convert the initial INT value to REAL as it is better for interpolation
if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t();
ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false);
+ // Build the InterpolateData object
InterpolateData data;
data.active = true;
data.type = TARGETING_PROPERTY;
data.finish = false;
data.elapsed = 0;
+ // Give the data it's configuration
data.id = p_object->get_instance_id();
data.key = p_property.get_subnames();
data.concatenated_key = p_property.get_concatenated_subnames();
@@ -1358,49 +1607,64 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_
data.ease_type = p_ease_type;
data.delay = p_delay;
+ // Ensure there is a valid delta
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
+ // Add the interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
+ // If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
- // convert INT to REAL is better for interpolaters
+
+ // Convert final INT values to REAL as they are better for interpolation
if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
+ // Make sure the given objects are valid
ERR_FAIL_COND_V(p_object == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);
ERR_FAIL_COND_V(p_initial == NULL, false);
ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false);
- ERR_FAIL_COND_V(p_duration <= 0, false);
+
+ // No negative durations
+ ERR_FAIL_COND_V(p_duration < 0, false);
+
+ // Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
+
+ // No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
+ // Make sure both objects have the given method
ERR_EXPLAIN("Object has no method named: %s" + p_method);
ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
ERR_EXPLAIN("Initial Object has no method named: %s" + p_initial_method);
ERR_FAIL_COND_V(!p_initial->has_method(p_initial_method), false);
+ // Call the method to get the initial value
Variant::CallError error;
Variant initial_val = p_initial->call(p_initial_method, NULL, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false);
- // convert INT to REAL is better for interpolaters
+ // Convert initial INT values to REAL as they aer better for interpolation
if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t();
ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false);
+ // Build the new InterpolateData object
InterpolateData data;
data.active = true;
data.type = TARGETING_METHOD;
data.finish = false;
data.elapsed = 0;
+ // Configure the data
data.id = p_object->get_instance_id();
data.key.push_back(p_method);
data.concatenated_key = p_method;
@@ -1413,16 +1677,17 @@ bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_in
data.ease_type = p_ease_type;
data.delay = p_delay;
+ // Ensure there is a valid delta
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
+ // Add the interpolation
_push_interpolate_data(data);
return true;
}
Tween::Tween() {
-
- //String autoplay;
+ // Initialize tween attributes
tween_process_mode = TWEEN_PROCESS_IDLE;
repeat = false;
speed_scale = 1;
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index 6fe3bffdbe..64ce099ecd 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -135,6 +135,7 @@ private:
void _tween_process(float p_delta);
void _remove_by_uid(int uid);
void _push_interpolate_data(InterpolateData &p_data);
+ bool _build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 5ef2557383..fadf5e432c 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -216,9 +216,7 @@ void BaseButton::set_pressed(bool p_pressed) {
if (p_pressed) {
_unpress_group();
}
- if (toggle_mode) {
- _toggled(status.pressed);
- }
+ _toggled(status.pressed);
update();
}
@@ -337,9 +335,6 @@ bool BaseButton::is_keep_pressed_outside() const {
void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) {
- if (shortcut.is_null() == p_shortcut.is_null())
- return;
-
shortcut = p_shortcut;
set_process_unhandled_input(shortcut.is_valid());
}
@@ -356,11 +351,10 @@ void BaseButton::_unhandled_input(Ref<InputEvent> p_event) {
return; //ignore because of modal window
if (is_toggle_mode()) {
- set_pressed(!is_pressed());
- emit_signal("toggled", is_pressed());
+ set_pressed(!is_pressed()); // Also calls _toggled() internally.
}
- emit_signal("pressed");
+ _pressed();
}
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 76275c2420..9d7c08d3f3 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -29,23 +29,23 @@
/*************************************************************************/
#include "control.h"
-#include "core/project_settings.h"
-#include "scene/main/canvas_layer.h"
-#include "scene/main/viewport.h"
-#include "servers/visual_server.h"
#include "core/message_queue.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/print_string.h"
+#include "core/project_settings.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
+#include "scene/main/canvas_layer.h"
+#include "scene/main/viewport.h"
#include "scene/scene_string_names.h"
+#include "servers/visual_server.h"
+
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#endif
-#include <stdio.h>
Dictionary Control::_edit_get_state() const {
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index bdb1342019..bc8dcf0e82 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -48,8 +48,9 @@ void FileDialog::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
- refresh->set_icon(get_icon("reload"));
dir_up->set_icon(get_icon("parent_folder"));
+ refresh->set_icon(get_icon("reload"));
+ show_hidden->set_icon(get_icon("toggle_hidden"));
}
if (p_what == NOTIFICATION_POPUP_HIDE) {
@@ -393,20 +394,19 @@ void FileDialog::update_file_list() {
List<String> files;
List<String> dirs;
- bool isdir;
- bool ishidden;
- bool show_hidden = show_hidden_files;
+ bool is_dir;
+ bool is_hidden;
String item;
- while ((item = dir_access->get_next(&isdir)) != "") {
+ while ((item = dir_access->get_next(&is_dir)) != "") {
if (item == "." || item == "..")
continue;
- ishidden = dir_access->current_is_hidden();
+ is_hidden = dir_access->current_is_hidden();
- if (show_hidden || !ishidden) {
- if (!isdir)
+ if (show_hidden_files || !is_hidden) {
+ if (!is_dir)
files.push_back(item);
else
dirs.push_back(item);
@@ -873,6 +873,13 @@ FileDialog::FileDialog() {
refresh->connect("pressed", this, "_update_file_list");
hbc->add_child(refresh);
+ show_hidden = memnew(ToolButton);
+ show_hidden->set_toggle_mode(true);
+ show_hidden->set_pressed(is_showing_hidden_files());
+ show_hidden->set_tooltip(RTR("Toggle Hidden Files"));
+ show_hidden->connect("toggled", this, "set_show_hidden_files");
+ hbc->add_child(show_hidden);
+
drives = memnew(OptionButton);
hbc->add_child(drives);
drives->connect("item_selected", this, "_select_drive");
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 85edac0b12..9f7ea1a2f2 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -90,6 +90,7 @@ private:
ToolButton *dir_up;
ToolButton *refresh;
+ ToolButton *show_hidden;
Vector<String> filters;
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index cfbc9d6d18..75f5f79873 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -29,10 +29,17 @@
/*************************************************************************/
#include "gradient_edit.h"
+
#include "core/os/keyboard.h"
-#include "editor/editor_scale.h"
+#ifdef TOOLS_ENABLED
+#include "editor/editor_scale.h"
#define SPACING (3 * EDSCALE)
+#define POINT_WIDTH (8 * EDSCALE)
+#else
+#define SPACING 3
+#define POINT_WIDTH 8
+#endif
GradientEdit::GradientEdit() {
grabbed = -1;
diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h
index 662278a17b..6f31107729 100644
--- a/scene/gui/gradient_edit.h
+++ b/scene/gui/gradient_edit.h
@@ -36,8 +36,6 @@
#include "scene/resources/default_theme/theme_data.h"
#include "scene/resources/gradient.h"
-#define POINT_WIDTH (8 * EDSCALE)
-
class GradientEdit : public Control {
GDCLASS(GradientEdit, Control);
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index e8692d56d2..6463ee5ad5 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -159,9 +159,7 @@ void GraphNode::_resort() {
fit_child_in_rect(c, r);
cache_y.push_back(vofs + size.y * 0.5);
- if (vofs > 0)
- vofs += sep;
- vofs += size.y;
+ vofs += size.y + sep;
}
update();
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 406c8e4f48..91b76839d7 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -749,9 +749,21 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
search_string = "";
}
- search_string += String::chr(k->get_unicode());
- for (int i = 0; i < items.size(); i++) {
- if (items[i].text.begins_with(search_string)) {
+ if (String::chr(k->get_unicode()) != search_string)
+ search_string += String::chr(k->get_unicode());
+
+ for (int i = current + 1; i <= items.size(); i++) {
+ if (i == items.size()) {
+ if (current == 0)
+ break;
+ else
+ i = 0;
+ }
+
+ if (i == current)
+ break;
+
+ if (items[i].text.findn(search_string) == 0) {
set_current(i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -1248,7 +1260,7 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
Rect2 rc = items[i].rect_cache;
if (i % current_columns == current_columns - 1) {
- rc.size.width = get_size().width; //not right but works
+ rc.size.width = get_size().width - rc.position.x; //make sure you can still select the last item when clicking past the column
}
if (rc.has_point(pos)) {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 1f778bc516..da6bff8ab8 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -44,7 +44,7 @@
static bool _is_text_char(CharType c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+ return !is_symbol(c);
}
void LineEdit::_gui_input(Ref<InputEvent> p_event) {
@@ -539,7 +539,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (handled) {
accept_event();
- } else if (!k->get_alt() && !k->get_command()) {
+ } else if (!k->get_command()) {
if (k->get_unicode() >= 32 && k->get_scancode() != KEY_DELETE) {
if (editable) {
@@ -923,7 +923,8 @@ void LineEdit::cut_text() {
void LineEdit::paste_text() {
- String paste_buffer = OS::get_singleton()->get_clipboard();
+ // Strip escape characters like \n and \t as they can't be displayed on LineEdit.
+ String paste_buffer = OS::get_singleton()->get_clipboard().strip_escapes();
if (paste_buffer != "") {
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 694065497a..7027fceb84 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -354,6 +354,7 @@ OptionButton::OptionButton() {
add_child(popup);
popup->set_pass_on_modal_close_click(false);
popup->set_notify_transform(true);
+ popup->set_allow_search(true);
popup->connect("id_pressed", this, "_selected");
popup->connect("id_focused", this, "_focused");
popup->connect("popup_hide", this, "set_pressed", varray(false));
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 984307530d..2cac345dae 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -31,6 +31,7 @@
#include "popup_menu.h"
#include "core/os/input.h"
#include "core/os/keyboard.h"
+#include "core/os/os.h"
#include "core/print_string.h"
#include "core/translation.h"
@@ -380,6 +381,43 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
_scroll(-pan_gesture->get_delta().y, pan_gesture->get_position());
}
}
+
+ Ref<InputEventKey> k = p_event;
+
+ if (allow_search && k.is_valid() && k->get_unicode()) {
+
+ uint64_t now = OS::get_singleton()->get_ticks_msec();
+ uint64_t diff = now - search_time_msec;
+ uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000));
+ search_time_msec = now;
+
+ if (diff > max_interval) {
+ search_string = "";
+ }
+
+ if (String::chr(k->get_unicode()) != search_string)
+ search_string += String::chr(k->get_unicode());
+
+ for (int i = mouse_over + 1; i <= items.size(); i++) {
+ if (i == items.size()) {
+ if (mouse_over <= 0)
+ break;
+ else
+ i = 0;
+ }
+
+ if (i == mouse_over)
+ break;
+
+ if (items[i].text.findn(search_string) == 0) {
+ mouse_over = i;
+ emit_signal("id_focused", i);
+ update();
+ accept_event();
+ break;
+ }
+ }
+ }
}
bool PopupMenu::has_point(const Point2 &p_point) const {
@@ -1289,6 +1327,16 @@ float PopupMenu::get_submenu_popup_delay() const {
return submenu_timer->get_wait_time();
}
+void PopupMenu::set_allow_search(bool p_allow) {
+
+ allow_search = p_allow;
+}
+
+bool PopupMenu::get_allow_search() const {
+
+ return allow_search;
+}
+
void PopupMenu::set_hide_on_window_lose_focus(bool p_enabled) {
hide_on_window_lose_focus = p_enabled;
@@ -1407,6 +1455,9 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hide_on_window_lose_focus", "enable"), &PopupMenu::set_hide_on_window_lose_focus);
ClassDB::bind_method(D_METHOD("is_hide_on_window_lose_focus"), &PopupMenu::is_hide_on_window_lose_focus);
+ ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
+ ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
+
ClassDB::bind_method(D_METHOD("_submenu_timeout"), &PopupMenu::_submenu_timeout);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
@@ -1414,6 +1465,7 @@ void PopupMenu::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
@@ -1435,6 +1487,10 @@ PopupMenu::PopupMenu() {
initial_button_mask = 0;
during_grabbed_click = false;
+ allow_search = false;
+ search_time_msec = 0;
+ search_string = "";
+
set_focus_mode(FOCUS_ALL);
set_as_toplevel(true);
set_hide_on_item_selection(true);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 687006d58d..babdd21281 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -112,6 +112,10 @@ class PopupMenu : public Popup {
void _ref_shortcut(Ref<ShortCut> p_sc);
void _unref_shortcut(Ref<ShortCut> p_sc);
+ bool allow_search;
+ uint64_t search_time_msec;
+ String search_string;
+
protected:
virtual bool has_point(const Point2 &p_point) const;
@@ -206,6 +210,9 @@ public:
void set_submenu_popup_delay(float p_time);
float get_submenu_popup_delay() const;
+ void set_allow_search(bool p_allow);
+ bool get_allow_search() const;
+
virtual void popup(const Rect2 &p_bounds = Rect2());
void set_hide_on_window_lose_focus(bool p_enabled);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index da452e3f10..8891f679ee 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -307,6 +307,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
switch (it->type) {
+ case ITEM_ALIGN: {
+
+ ItemAlign *align_it = static_cast<ItemAlign *>(it);
+
+ align = align_it->align;
+
+ } break;
case ITEM_TEXT: {
ItemText *text = static_cast<ItemText *>(it);
@@ -592,7 +599,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
//assign actual widths
for (int i = 0; i < table->columns.size(); i++) {
table->columns.write[i].width = table->columns[i].min_width;
- if (table->columns[i].expand)
+ if (table->columns[i].expand && total_ratio > 0)
table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
table->total_width += table->columns[i].width + hseparation;
}
@@ -1411,9 +1418,13 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
if (p_enter)
current = p_item;
- if (p_ensure_newline && current_frame->lines[current_frame->lines.size() - 1].from) {
- _invalidate_current_line(current_frame);
- current_frame->lines.resize(current_frame->lines.size() + 1);
+ if (p_ensure_newline) {
+ Item *from = current_frame->lines[current_frame->lines.size() - 1].from;
+ // only create a new line for Item types that generate content/layout, ignore those that represent formatting/styling
+ if (from && from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) {
+ _invalidate_current_line(current_frame);
+ current_frame->lines.resize(current_frame->lines.size() + 1);
+ }
}
if (current_frame->lines[current_frame->lines.size() - 1].from == NULL) {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 3117d8c59f..83ce71e959 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -50,7 +50,7 @@ inline bool _is_symbol(CharType c) {
static bool _is_text_char(CharType c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+ return !is_symbol(c);
}
static bool _is_whitespace(CharType c) {
@@ -4078,7 +4078,7 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_
cursor.line = p_row;
int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index);
- if (is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) {
+ if (n_col != 0 && is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) {
Vector<String> rows = get_wrap_rows_text(p_row);
int row_end_col = 0;
for (int i = 0; i < p_wrap_index + 1; i++) {
@@ -4682,6 +4682,8 @@ bool TextEdit::has_keyword_color(String p_keyword) const {
}
Color TextEdit::get_keyword_color(String p_keyword) const {
+
+ ERR_FAIL_COND_V(!keywords.has(p_keyword), Color());
return keywords[p_keyword];
}
@@ -6342,14 +6344,14 @@ int TextEdit::get_info_gutter_width() const {
return info_gutter_width;
}
-void TextEdit::set_hiding_enabled(int p_enabled) {
+void TextEdit::set_hiding_enabled(bool p_enabled) {
if (!p_enabled)
unhide_all_lines();
hiding_enabled = p_enabled;
update();
}
-int TextEdit::is_hiding_enabled() const {
+bool TextEdit::is_hiding_enabled() const {
return hiding_enabled;
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 68e590f1e6..0c26602d2b 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -697,8 +697,8 @@ public:
void set_info_gutter_width(int p_gutter_width);
int get_info_gutter_width() const;
- void set_hiding_enabled(int p_enabled);
- int is_hiding_enabled() const;
+ void set_hiding_enabled(bool p_enabled);
+ bool is_hiding_enabled() const;
void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e5313061da..522c1ecb6a 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -39,7 +39,7 @@
#include "scene/main/viewport.h"
#ifdef TOOLS_ENABLED
-#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
#endif
#include <limits.h>
@@ -318,7 +318,7 @@ void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName
void TreeItem::set_collapsed(bool p_collapsed) {
- if (collapsed == p_collapsed)
+ if (collapsed == p_collapsed || !tree)
return;
collapsed = p_collapsed;
TreeItem *ci = tree->selected_item;
@@ -344,8 +344,7 @@ void TreeItem::set_collapsed(bool p_collapsed) {
}
_changed_notify();
- if (tree)
- tree->emit_signal("item_collapsed", this);
+ tree->emit_signal("item_collapsed", this);
}
bool TreeItem::is_collapsed() {
@@ -389,7 +388,7 @@ TreeItem *TreeItem::get_children() {
return children;
}
-TreeItem *TreeItem::get_prev_visible() {
+TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
TreeItem *current = this;
@@ -398,8 +397,20 @@ TreeItem *TreeItem::get_prev_visible() {
if (!prev) {
current = current->parent;
- if (!current || (current == tree->root && tree->hide_root))
+ if (current == tree->root && tree->hide_root) {
return NULL;
+ } else if (!current) {
+ if (p_wrap) {
+ current = this;
+ TreeItem *temp = this->get_next_visible();
+ while (temp) {
+ current = temp;
+ temp = temp->get_next_visible();
+ }
+ } else {
+ return NULL;
+ }
+ }
} else {
current = prev;
@@ -415,7 +426,7 @@ TreeItem *TreeItem::get_prev_visible() {
return current;
}
-TreeItem *TreeItem::get_next_visible() {
+TreeItem *TreeItem::get_next_visible(bool p_wrap) {
TreeItem *current = this;
@@ -433,10 +444,14 @@ TreeItem *TreeItem::get_next_visible() {
current = current->parent;
}
- if (current == NULL)
- return NULL;
- else
+ if (!current) {
+ if (p_wrap)
+ return tree->root;
+ else
+ return NULL;
+ } else {
current = current->next;
+ }
}
return current;
@@ -740,8 +755,8 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent);
ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children);
- ClassDB::bind_method(D_METHOD("get_next_visible"), &TreeItem::get_next_visible);
- ClassDB::bind_method(D_METHOD("get_prev_visible"), &TreeItem::get_prev_visible);
+ ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child);
@@ -3491,6 +3506,7 @@ void Tree::scroll_to_item(TreeItem *p_item) {
TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {
+ TreeItem *from = p_at;
while (p_at) {
for (int i = 0; i < columns.size(); i++) {
@@ -3502,9 +3518,12 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c
}
if (p_backwards)
- p_at = p_at->get_prev_visible();
+ p_at = p_at->get_prev_visible(true);
else
- p_at = p_at->get_next_visible();
+ p_at = p_at->get_next_visible(true);
+
+ if ((p_at) == from)
+ break;
}
return NULL;
@@ -3512,10 +3531,14 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c
TreeItem *Tree::search_item_text(const String &p_find, int *r_col, bool p_selectable) {
- if (!root)
+ TreeItem *from = get_selected();
+
+ if (!from)
+ from = root;
+ if (!from)
return NULL;
- return _search_item_text(root, p_find, r_col, p_selectable);
+ return _search_item_text(from->get_next_visible(true), p_find, r_col, p_selectable);
}
void Tree::_do_incr_search(const String &p_add) {
@@ -3524,7 +3547,7 @@ void Tree::_do_incr_search(const String &p_add) {
uint64_t diff = time - last_keypress;
if (diff > uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)))
incr_search = p_add;
- else
+ else if (incr_search != p_add)
incr_search += p_add;
last_keypress = time;
@@ -3697,6 +3720,10 @@ String Tree::get_tooltip(const Point2 &p_pos) const {
const TreeItem::Cell &c = it->cells[col];
int col_width = get_column_width(col);
+
+ for (int i = 0; i < col; i++)
+ pos.x -= get_column_width(i);
+
for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture> b = c.buttons[j].texture;
Size2 size = b->get_size() + cache.button_pressed->get_minimum_size();
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 26d64baafb..45d451eb72 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -238,8 +238,8 @@ public:
TreeItem *get_parent();
TreeItem *get_children();
- TreeItem *get_prev_visible();
- TreeItem *get_next_visible();
+ TreeItem *get_prev_visible(bool p_wrap = false);
+ TreeItem *get_next_visible(bool p_wrap = false);
void remove_child(TreeItem *p_item);
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 2f23c11748..ee23b24b3c 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1165,7 +1165,7 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) {
ERR_FAIL_NULL(p_child);
if (p_child == this) {
- ERR_EXPLAIN("Can't add child '" + p_child->get_name() + "' to itself.")
+ ERR_EXPLAIN("Can't add child '" + p_child->get_name() + "' to itself.");
ERR_FAIL_COND(p_child == this); // adding to itself!
}
@@ -1199,7 +1199,7 @@ void Node::add_child_below_node(Node *p_node, Node *p_child, bool p_legible_uniq
if (is_a_parent_of(p_node)) {
move_child(p_child, p_node->get_position_in_parent() + 1);
} else {
- WARN_PRINTS("Cannot move under node " + p_node->get_name() + " as " + p_child->get_name() + " does not share a parent.")
+ WARN_PRINTS("Cannot move under node " + p_node->get_name() + " as " + p_child->get_name() + " does not share a parent.");
}
}
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 65cda73a23..7f0cebd492 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -1244,7 +1244,7 @@ void SceneTree::_update_root_rect() {
root->update_canvas_items(); //force them to update just in case
if (use_font_oversampling) {
- WARN_PRINT("Font oversampling does not work in 'Viewport' stretch mode, only '2D'.")
+ WARN_PRINT("Font oversampling does not work in 'Viewport' stretch mode, only '2D'.");
}
} break;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 5440c88fa8..0423fcb5f0 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -48,6 +48,7 @@
#include "scene/2d/light_occluder_2d.h"
#include "scene/2d/line_2d.h"
#include "scene/2d/mesh_instance_2d.h"
+#include "scene/2d/multimesh_instance_2d.h"
#include "scene/2d/navigation_2d.h"
#include "scene/2d/parallax_background.h"
#include "scene/2d/parallax_layer.h"
@@ -144,7 +145,6 @@
#include "scene/resources/material.h"
#include "scene/resources/mesh.h"
#include "scene/resources/mesh_data_tool.h"
-#include "scene/resources/mesh_library.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/particles_material.h"
#include "scene/resources/physics_material.h"
@@ -209,6 +209,7 @@
#include "scene/3d/visibility_notifier.h"
#include "scene/animation/skeleton_ik.h"
#include "scene/resources/environment.h"
+#include "scene/resources/mesh_library.h"
#endif
static Ref<ResourceFormatSaverText> resource_saver_text;
@@ -468,7 +469,7 @@ void register_scene_types() {
OS::get_singleton()->yield(); //may take time to init
#endif
- ClassDB::register_class<MeshLibrary>();
+
AcceptDialog::set_swap_ok_cancel(GLOBAL_DEF("gui/common/swap_ok_cancel", bool(OS::get_singleton()->get_swap_ok_cancel())));
ClassDB::register_class<Shader>();
@@ -543,6 +544,7 @@ void register_scene_types() {
ClassDB::register_class<Position2D>();
ClassDB::register_class<Line2D>();
ClassDB::register_class<MeshInstance2D>();
+ ClassDB::register_class<MultiMeshInstance2D>();
ClassDB::register_virtual_class<CollisionObject2D>();
ClassDB::register_virtual_class<PhysicsBody2D>();
ClassDB::register_class<StaticBody2D>();
@@ -587,9 +589,13 @@ void register_scene_types() {
SceneTree::add_idle_callback(ParticlesMaterial::flush_changes);
ParticlesMaterial::init_shaders();
-#ifndef _3D_DISABLED
ClassDB::register_virtual_class<Mesh>();
ClassDB::register_class<ArrayMesh>();
+ ClassDB::register_class<MultiMesh>();
+ ClassDB::register_class<SurfaceTool>();
+ ClassDB::register_class<MeshDataTool>();
+
+#ifndef _3D_DISABLED
ClassDB::register_virtual_class<PrimitiveMesh>();
ClassDB::register_class<CapsuleMesh>();
ClassDB::register_class<CubeMesh>();
@@ -603,7 +609,6 @@ void register_scene_types() {
SceneTree::add_idle_callback(SpatialMaterial::flush_changes);
SpatialMaterial::init_shaders();
- ClassDB::register_class<MultiMesh>();
ClassDB::register_class<MeshLibrary>();
OS::get_singleton()->yield(); //may take time to init
@@ -619,9 +624,6 @@ void register_scene_types() {
ClassDB::register_class<ConvexPolygonShape>();
ClassDB::register_class<ConcavePolygonShape>();
- ClassDB::register_class<SurfaceTool>();
- ClassDB::register_class<MeshDataTool>();
-
OS::get_singleton()->yield(); //may take time to init
ClassDB::register_class<SpatialVelocityTracker>();
@@ -644,6 +646,7 @@ void register_scene_types() {
ClassDB::register_class<GradientTexture>();
ClassDB::register_class<ProxyTexture>();
ClassDB::register_class<AnimatedTexture>();
+ ClassDB::register_class<CameraTexture>();
ClassDB::register_class<CubeMap>();
ClassDB::register_virtual_class<TextureLayered>();
ClassDB::register_class<Texture3D>();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 6ab78326b2..e4da659b0d 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -93,7 +93,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]);
PoolVector<float> values = p_value;
int vcount = values.size();
- ERR_FAIL_COND_V(vcount % 12, false); // shuld be multiple of 11
+ ERR_FAIL_COND_V(vcount % 12, false); // should be multiple of 11
PoolVector<float>::Read r = values.read();
@@ -1298,6 +1298,78 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const {
ERR_FAIL_V(-1);
}
+void Animation::track_set_key_time(int p_track, int p_key_idx, float p_time) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ TransformTrack *tt = static_cast<TransformTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, tt->transforms.size());
+ TKey<TransformKey> key = tt->transforms[p_key_idx];
+ key.time = p_time;
+ tt->transforms.remove(p_key_idx);
+ _insert(p_time, tt->transforms, key);
+ return;
+ }
+ case TYPE_VALUE: {
+
+ ValueTrack *vt = static_cast<ValueTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, vt->values.size());
+ TKey<Variant> key = vt->values[p_key_idx];
+ key.time = p_time;
+ vt->values.remove(p_key_idx);
+ _insert(p_time, vt->values, key);
+ return;
+ }
+ case TYPE_METHOD: {
+
+ MethodTrack *mt = static_cast<MethodTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, mt->methods.size());
+ MethodKey key = mt->methods[p_key_idx];
+ key.time = p_time;
+ mt->methods.remove(p_key_idx);
+ _insert(p_time, mt->methods, key);
+ return;
+ }
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, bt->values.size());
+ TKey<BezierKey> key = bt->values[p_key_idx];
+ key.time = p_time;
+ bt->values.remove(p_key_idx);
+ _insert(p_time, bt->values, key);
+ return;
+ }
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, at->values.size());
+ TKey<AudioKey> key = at->values[p_key_idx];
+ key.time = p_time;
+ at->values.remove(p_key_idx);
+ _insert(p_time, at->values, key);
+ return;
+ }
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, at->values.size());
+ TKey<StringName> key = at->values[p_key_idx];
+ key.time = p_time;
+ at->values.remove(p_key_idx);
+ _insert(p_time, at->values, key);
+ return;
+ }
+ }
+
+ ERR_FAIL();
+}
+
float Animation::track_get_key_transition(int p_track, int p_key_idx) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
@@ -2691,6 +2763,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_remove_key_at_position", "idx", "position"), &Animation::track_remove_key_at_position);
ClassDB::bind_method(D_METHOD("track_set_key_value", "idx", "key", "value"), &Animation::track_set_key_value);
ClassDB::bind_method(D_METHOD("track_set_key_transition", "idx", "key_idx", "transition"), &Animation::track_set_key_transition);
+ ClassDB::bind_method(D_METHOD("track_set_key_time", "idx", "key_idx", "time"), &Animation::track_set_key_time);
ClassDB::bind_method(D_METHOD("track_get_key_transition", "idx", "key_idx"), &Animation::track_get_key_transition);
ClassDB::bind_method(D_METHOD("track_get_key_count", "idx"), &Animation::track_get_key_count);
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 36ebaa25d5..59f2ae24c7 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -305,6 +305,7 @@ public:
void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1);
void track_set_key_transition(int p_track, int p_key_idx, float p_transition);
void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value);
+ void track_set_key_time(int p_track, int p_key_idx, float p_time);
int track_find_key(int p_track, float p_time, bool p_exact = false) const;
void track_remove_key(int p_track, int p_idx);
void track_remove_key_at_position(int p_track, float p_pos);
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index bdb6c78782..2c5dfc375c 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -541,8 +541,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// File Dialog
- theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png));
theme->set_icon("parent_folder", "FileDialog", make_icon(icon_parent_folder_png));
+ theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png));
+ theme->set_icon("toggle_hidden", "FileDialog", make_icon(icon_visibility_png));
// Popup
@@ -582,14 +583,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// GraphNode
- Ref<StyleBoxTexture> graphsb = make_stylebox(graph_node_png, 6, 24, 6, 5, 16, 24, 16, 5);
- Ref<StyleBoxTexture> graphsbcomment = make_stylebox(graph_node_comment_png, 6, 24, 6, 5, 16, 24, 16, 5);
- Ref<StyleBoxTexture> graphsbcommentselected = make_stylebox(graph_node_comment_focus_png, 6, 24, 6, 5, 16, 24, 16, 5);
- Ref<StyleBoxTexture> graphsbselected = make_stylebox(graph_node_selected_png, 6, 24, 6, 5, 16, 24, 16, 5);
+ Ref<StyleBoxTexture> graphsb = make_stylebox(graph_node_png, 6, 24, 6, 5, 16, 24, 16, 6);
+ Ref<StyleBoxTexture> graphsbcomment = make_stylebox(graph_node_comment_png, 6, 24, 6, 5, 16, 24, 16, 6);
+ Ref<StyleBoxTexture> graphsbcommentselected = make_stylebox(graph_node_comment_focus_png, 6, 24, 6, 5, 16, 24, 16, 6);
+ Ref<StyleBoxTexture> graphsbselected = make_stylebox(graph_node_selected_png, 6, 24, 6, 5, 16, 24, 16, 6);
Ref<StyleBoxTexture> graphsbdefault = make_stylebox(graph_node_default_png, 4, 4, 4, 4, 6, 4, 4, 4);
Ref<StyleBoxTexture> graphsbdeffocus = make_stylebox(graph_node_default_focus_png, 4, 4, 4, 4, 6, 4, 4, 4);
- Ref<StyleBoxTexture> graph_bpoint = make_stylebox(graph_node_breakpoint_png, 6, 24, 6, 5, 16, 24, 16, 5);
- Ref<StyleBoxTexture> graph_position = make_stylebox(graph_node_position_png, 6, 24, 6, 5, 16, 24, 16, 5);
+ Ref<StyleBoxTexture> graph_bpoint = make_stylebox(graph_node_breakpoint_png, 6, 24, 6, 5, 16, 24, 16, 6);
+ Ref<StyleBoxTexture> graph_position = make_stylebox(graph_node_position_png, 6, 24, 6, 5, 16, 24, 16, 6);
//graphsb->set_expand_margin_size(MARGIN_LEFT,10);
//graphsb->set_expand_margin_size(MARGIN_RIGHT,10);
diff --git a/scene/resources/default_theme/icon_visibility.png b/scene/resources/default_theme/icon_visibility.png
new file mode 100644
index 0000000000..6402571f3e
--- /dev/null
+++ b/scene/resources/default_theme/icon_visibility.png
Binary files differ
diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h
index 87b8afd6a3..5e13a6625a 100644
--- a/scene/resources/default_theme/theme_data.h
+++ b/scene/resources/default_theme/theme_data.h
@@ -194,6 +194,10 @@ static const unsigned char icon_stop_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x2, 0x7c, 0x60, 0x26, 0x28, 0xf3, 0xf0, 0x3f, 0x76, 0x8, 0x94, 0xa2, 0x97, 0x82, 0x51, 0x5, 0x84, 0x23, 0x8b, 0x30, 0x0, 0x0, 0x66, 0x60, 0x11, 0xdc, 0x92, 0xb3, 0xb7, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
+static const unsigned char icon_visibility_png[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0xdb, 0xe1, 0x4f, 0xe0, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc4, 0x0, 0x0, 0xe, 0xc4, 0x1, 0x95, 0x2b, 0xe, 0x1b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xdf, 0xdf, 0xdf, 0xe3, 0xe3, 0xe3, 0xe6, 0xe6, 0xe6, 0xd5, 0xd5, 0xd5, 0xd8, 0xd8, 0xd8, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xb7, 0x7e, 0xd, 0xb6, 0x0, 0x0, 0x0, 0x32, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x8, 0x9, 0xa, 0xc, 0xd, 0xe, 0xf, 0x11, 0x12, 0x13, 0x2e, 0x2f, 0x32, 0x33, 0x36, 0x37, 0x38, 0x48, 0x49, 0x4b, 0x50, 0x53, 0x55, 0x56, 0x6c, 0x6d, 0x6e, 0x70, 0x77, 0x79, 0x7b, 0x7c, 0xc5, 0xd7, 0xd8, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xf0, 0xf2, 0xf3, 0xf4, 0xfe, 0x5e, 0x62, 0x1a, 0x26, 0x0, 0x0, 0x0, 0x86, 0x49, 0x44, 0x41, 0x54, 0x18, 0x19, 0x8d, 0xc1, 0xb, 0x16, 0x42, 0x40, 0x0, 0x86, 0xd1, 0x2f, 0xa2, 0x77, 0x2a, 0x85, 0xde, 0x91, 0x5e, 0x33, 0x8a, 0x7f, 0xff, 0x9b, 0xcb, 0x99, 0x63, 0x1, 0xee, 0xa5, 0x9f, 0xc1, 0x3e, 0x6f, 0xea, 0xfc, 0xe0, 0xd1, 0x99, 0xdd, 0xe5, 0x94, 0xb, 0x9c, 0xf9, 0x57, 0x26, 0x9, 0x82, 0x5d, 0xa1, 0xdf, 0x92, 0x96, 0xf7, 0x94, 0x99, 0xd0, 0x1a, 0x1b, 0x7d, 0x7c, 0xe0, 0x2c, 0x25, 0x6c, 0x2b, 0x1b, 0x93, 0x4a, 0x17, 0xa0, 0x94, 0x2, 0xac, 0x64, 0x8, 0xa5, 0x12, 0x78, 0x48, 0x1, 0x56, 0x32, 0x8c, 0xa4, 0x7, 0x70, 0x93, 0x76, 0xc4, 0xd6, 0x6c, 0xc8, 0xa4, 0x2b, 0x30, 0xac, 0x54, 0x8c, 0x69, 0x4d, 0xad, 0xcc, 0x90, 0xd6, 0xba, 0x91, 0x49, 0xc3, 0x30, 0xb3, 0x6a, 0x22, 0x9c, 0xd5, 0x5b, 0xce, 0x2b, 0xa2, 0xe3, 0x9f, 0xf2, 0xba, 0xce, 0x8f, 0x3e, 0xbd, 0xfc, 0x1, 0xdb, 0xf3, 0x10, 0xc5, 0x78, 0x85, 0x14, 0x89, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
static const unsigned char icon_zoom_less_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x13, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x18, 0x31, 0xe0, 0xc1, 0x7f, 0x3c, 0x90, 0xb0, 0x82, 0x11, 0x2, 0x0, 0xbf, 0x57, 0x36, 0x25, 0x52, 0x24, 0x7b, 0x26, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index 52dfffda5b..7c3867beaa 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -111,6 +111,11 @@ void Environment::set_ambient_light_sky_contribution(float p_energy) {
VS::get_singleton()->environment_set_ambient_light(environment, ambient_color, ambient_energy, ambient_sky_contribution);
}
+void Environment::set_camera_feed_id(int p_camera_feed_id) {
+ camera_feed_id = p_camera_feed_id;
+ VS::get_singleton()->environment_set_camera_feed_id(environment, camera_feed_id);
+};
+
Environment::BGMode Environment::get_background() const {
return bg_mode;
@@ -165,6 +170,10 @@ float Environment::get_ambient_light_sky_contribution() const {
return ambient_sky_contribution;
}
+int Environment::get_camera_feed_id(void) const {
+
+ return camera_feed_id;
+}
void Environment::set_tonemapper(ToneMapper p_tone_mapper) {
@@ -321,6 +330,12 @@ void Environment::_validate_property(PropertyInfo &property) const {
}
}
+ if (property.name == "background_camera_feed_id") {
+ if (bg_mode != BG_CAMERA_FEED) {
+ property.usage = PROPERTY_USAGE_NOEDITOR;
+ }
+ }
+
static const char *hide_prefixes[] = {
"fog_",
"auto_exposure_",
@@ -946,6 +961,7 @@ void Environment::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_ambient_light_color", "color"), &Environment::set_ambient_light_color);
ClassDB::bind_method(D_METHOD("set_ambient_light_energy", "energy"), &Environment::set_ambient_light_energy);
ClassDB::bind_method(D_METHOD("set_ambient_light_sky_contribution", "energy"), &Environment::set_ambient_light_sky_contribution);
+ ClassDB::bind_method(D_METHOD("set_camera_feed_id", "camera_feed_id"), &Environment::set_camera_feed_id);
ClassDB::bind_method(D_METHOD("get_background"), &Environment::get_background);
ClassDB::bind_method(D_METHOD("get_sky"), &Environment::get_sky);
@@ -959,9 +975,10 @@ void Environment::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_ambient_light_color"), &Environment::get_ambient_light_color);
ClassDB::bind_method(D_METHOD("get_ambient_light_energy"), &Environment::get_ambient_light_energy);
ClassDB::bind_method(D_METHOD("get_ambient_light_sky_contribution"), &Environment::get_ambient_light_sky_contribution);
+ ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &Environment::get_camera_feed_id);
ADD_GROUP("Background", "background_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "background_mode", PROPERTY_HINT_ENUM, "Clear Color,Custom Color,Sky,Color+Sky,Canvas,Keep"), "set_background", "get_background");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "background_mode", PROPERTY_HINT_ENUM, "Clear Color,Custom Color,Sky,Color+Sky,Canvas,Keep,Camera Feed"), "set_background", "get_background");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "background_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_sky", "get_sky");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "background_sky_custom_fov", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_sky_custom_fov", "get_sky_custom_fov");
ADD_PROPERTY(PropertyInfo(Variant::BASIS, "background_sky_orientation"), "set_sky_orientation", "get_sky_orientation");
@@ -970,6 +987,7 @@ void Environment::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "background_color"), "set_bg_color", "get_bg_color");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "background_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_bg_energy", "get_bg_energy");
ADD_PROPERTY(PropertyInfo(Variant::INT, "background_canvas_max_layer", PROPERTY_HINT_RANGE, "-1000,1000,1"), "set_canvas_max_layer", "get_canvas_max_layer");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "background_camera_feed_id", PROPERTY_HINT_RANGE, "1,10,1"), "set_camera_feed_id", "get_camera_feed_id");
ADD_GROUP("Ambient Light", "ambient_light_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ambient_light_color"), "set_ambient_light_color", "get_ambient_light_color");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "ambient_light_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_ambient_light_energy", "get_ambient_light_energy");
@@ -1265,6 +1283,7 @@ void Environment::_bind_methods() {
BIND_ENUM_CONSTANT(BG_SKY);
BIND_ENUM_CONSTANT(BG_COLOR_SKY);
BIND_ENUM_CONSTANT(BG_CANVAS);
+ BIND_ENUM_CONSTANT(BG_CAMERA_FEED);
BIND_ENUM_CONSTANT(BG_MAX);
BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_ADDITIVE);
@@ -1310,6 +1329,7 @@ Environment::Environment() :
ambient_energy = 1.0;
//ambient_sky_contribution = 1.0;
set_ambient_light_sky_contribution(1.0);
+ set_camera_feed_id(1);
tone_mapper = TONE_MAPPER_LINEAR;
tonemap_exposure = 1.0;
diff --git a/scene/resources/environment.h b/scene/resources/environment.h
index a54f13a88f..acce9c09a2 100644
--- a/scene/resources/environment.h
+++ b/scene/resources/environment.h
@@ -49,6 +49,7 @@ public:
BG_COLOR_SKY,
BG_CANVAS,
BG_KEEP,
+ BG_CAMERA_FEED,
BG_MAX
};
@@ -98,6 +99,7 @@ private:
Color ambient_color;
float ambient_energy;
float ambient_sky_contribution;
+ int camera_feed_id;
ToneMapper tone_mapper;
float tonemap_exposure;
@@ -192,6 +194,7 @@ public:
void set_ambient_light_color(const Color &p_color);
void set_ambient_light_energy(float p_energy);
void set_ambient_light_sky_contribution(float p_energy);
+ void set_camera_feed_id(int p_camera_feed_id);
BGMode get_background() const;
Ref<Sky> get_sky() const;
@@ -205,6 +208,7 @@ public:
Color get_ambient_light_color() const;
float get_ambient_light_energy() const;
float get_ambient_light_sky_contribution() const;
+ int get_camera_feed_id(void) const;
void set_tonemapper(ToneMapper p_tone_mapper);
ToneMapper get_tonemapper() const;
diff --git a/scene/resources/height_map_shape.cpp b/scene/resources/height_map_shape.cpp
index 8cd271dab0..f763700d52 100644
--- a/scene/resources/height_map_shape.cpp
+++ b/scene/resources/height_map_shape.cpp
@@ -85,6 +85,7 @@ void HeightMapShape::_update_shape() {
d["min_height"] = min_height;
d["max_height"] = max_height;
PhysicsServer::get_singleton()->shape_set_data(get_shape(), d);
+ Shape::_update_shape();
}
void HeightMapShape::set_map_width(int p_new) {
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 67639858ce..6443b44bb6 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -489,6 +489,7 @@ void Mesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_surface_count"), &Mesh::get_surface_count);
ClassDB::bind_method(D_METHOD("surface_get_arrays", "surf_idx"), &Mesh::surface_get_arrays);
ClassDB::bind_method(D_METHOD("surface_get_blend_shape_arrays", "surf_idx"), &Mesh::surface_get_blend_shape_arrays);
+ ClassDB::bind_method(D_METHOD("surface_set_material", "surf_idx", "material"), &Mesh::surface_set_material);
ClassDB::bind_method(D_METHOD("surface_get_material", "surf_idx"), &Mesh::surface_get_material);
BIND_ENUM_CONSTANT(PRIMITIVE_POINTS);
@@ -1305,7 +1306,6 @@ void ArrayMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("surface_get_array_index_len", "surf_idx"), &ArrayMesh::surface_get_array_index_len);
ClassDB::bind_method(D_METHOD("surface_get_format", "surf_idx"), &ArrayMesh::surface_get_format);
ClassDB::bind_method(D_METHOD("surface_get_primitive_type", "surf_idx"), &ArrayMesh::surface_get_primitive_type);
- ClassDB::bind_method(D_METHOD("surface_set_material", "surf_idx", "material"), &ArrayMesh::surface_set_material);
ClassDB::bind_method(D_METHOD("surface_find_by_name", "name"), &ArrayMesh::surface_find_by_name);
ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name);
ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name);
diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h
index 1457d283bd..b38791b9a6 100644
--- a/scene/resources/mesh.h
+++ b/scene/resources/mesh.h
@@ -128,6 +128,7 @@ public:
virtual Array surface_get_blend_shape_arrays(int p_surface) const = 0;
virtual uint32_t surface_get_format(int p_idx) const = 0;
virtual PrimitiveType surface_get_primitive_type(int p_idx) const = 0;
+ virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) = 0;
virtual Ref<Material> surface_get_material(int p_idx) const = 0;
virtual int get_blend_shape_count() const = 0;
virtual StringName get_blend_shape_name(int p_index) const = 0;
@@ -215,8 +216,8 @@ public:
PrimitiveType surface_get_primitive_type(int p_idx) const;
bool surface_is_alpha_sorting_enabled(int p_idx) const;
- void surface_set_material(int p_idx, const Ref<Material> &p_material);
- Ref<Material> surface_get_material(int p_idx) const;
+ virtual void surface_set_material(int p_idx, const Ref<Material> &p_material);
+ virtual Ref<Material> surface_get_material(int p_idx) const;
int surface_find_by_name(const String &p_name) const;
void surface_set_name(int p_idx, const String &p_name);
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 2c6f30f429..99286668ce 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -92,8 +92,8 @@ Node *SceneState::instance(GenEditState p_edit_state) const {
if (i > 0) {
- ERR_EXPLAIN(vformat("Invalid scene: node %s does not specify its parent node.", snames[n.name]))
- ERR_FAIL_COND_V(n.parent == -1, NULL)
+ ERR_EXPLAIN(vformat("Invalid scene: node %s does not specify its parent node.", snames[n.name]));
+ ERR_FAIL_COND_V(n.parent == -1, NULL);
NODE_FROM_ID(nparent, n.parent);
#ifdef DEBUG_ENABLED
if (!nparent && (n.parent & FLAG_ID_IS_PATH)) {
diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp
index ef67e6ea80..758475b75e 100644
--- a/scene/resources/particles_material.cpp
+++ b/scene/resources/particles_material.cpp
@@ -1186,6 +1186,7 @@ void ParticlesMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY);
BIND_ENUM_CONSTANT(FLAG_ROTATE_Y);
+ BIND_ENUM_CONSTANT(FLAG_DISABLE_Z);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT);
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index db58fe7823..04d13c8869 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -157,6 +157,12 @@ Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const {
return primitive_type;
}
+void PrimitiveMesh::surface_set_material(int p_idx, const Ref<Material> &p_material) {
+ ERR_FAIL_INDEX(p_idx, 1);
+
+ set_material(p_material);
+}
+
Ref<Material> PrimitiveMesh::surface_get_material(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, 1, NULL);
diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h
index 88a26801b7..0045a48736 100644
--- a/scene/resources/primitive_meshes.h
+++ b/scene/resources/primitive_meshes.h
@@ -72,6 +72,7 @@ public:
virtual Array surface_get_blend_shape_arrays(int p_surface) const;
virtual uint32_t surface_get_format(int p_idx) const;
virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const;
+ virtual void surface_set_material(int p_idx, const Ref<Material> &p_material);
virtual Ref<Material> surface_get_material(int p_idx) const;
virtual int get_blend_shape_count() const;
virtual StringName get_blend_shape_name(int p_index) const;
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 6e7bb27e74..0921c0dae9 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1712,15 +1712,15 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r
Vector<StringName> groups = state->get_node_groups(i);
String header = "[node";
- header += " name=\"" + String(name) + "\"";
+ header += " name=\"" + String(name).c_escape() + "\"";
if (type != StringName()) {
header += " type=\"" + String(type) + "\"";
}
if (path != NodePath()) {
- header += " parent=\"" + String(path.simplified()) + "\"";
+ header += " parent=\"" + String(path.simplified()).c_escape() + "\"";
}
if (owner != NodePath() && owner != NodePath(".")) {
- header += " owner=\"" + String(owner.simplified()) + "\"";
+ header += " owner=\"" + String(owner.simplified()).c_escape() + "\"";
}
if (index >= 0) {
header += " index=\"" + itos(index) + "\"";
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 92172912c2..6e0bc43296 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -36,6 +36,7 @@
#include "core/os/os.h"
#include "mesh.h"
#include "scene/resources/bit_map.h"
+#include "servers/camera/camera_feed.h"
Size2 Texture::get_size() const {
@@ -232,7 +233,7 @@ Image::Format ImageTexture::get_format() const {
#ifndef DISABLE_DEPRECATED
Error ImageTexture::load(const String &p_path) {
- WARN_DEPRECATED
+ WARN_DEPRECATED;
Ref<Image> img;
img.instance();
Error err = img->load(p_path);
@@ -2498,3 +2499,107 @@ String ResourceFormatLoaderTextureLayered::get_resource_type(const String &p_pat
return "TextureArray";
return "";
}
+
+void CameraTexture::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_camera_feed_id", "feed_id"), &CameraTexture::set_camera_feed_id);
+ ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &CameraTexture::get_camera_feed_id);
+
+ ClassDB::bind_method(D_METHOD("set_which_feed", "which_feed"), &CameraTexture::set_which_feed);
+ ClassDB::bind_method(D_METHOD("get_which_feed"), &CameraTexture::get_which_feed);
+
+ ClassDB::bind_method(D_METHOD("set_camera_active", "active"), &CameraTexture::set_camera_active);
+ ClassDB::bind_method(D_METHOD("get_camera_active"), &CameraTexture::get_camera_active);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "camera_feed_id"), "set_camera_feed_id", "get_camera_feed_id");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "which_feed"), "set_which_feed", "get_which_feed");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active");
+}
+
+int CameraTexture::get_width() const {
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+ if (feed.is_valid()) {
+ return feed->get_base_width();
+ } else {
+ return 0;
+ }
+}
+
+int CameraTexture::get_height() const {
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+ if (feed.is_valid()) {
+ return feed->get_base_height();
+ } else {
+ return 0;
+ }
+}
+
+bool CameraTexture::has_alpha() const {
+ return false;
+}
+
+RID CameraTexture::get_rid() const {
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+ if (feed.is_valid()) {
+ return feed->get_texture(which_feed);
+ } else {
+ return RID();
+ }
+}
+
+void CameraTexture::set_flags(uint32_t p_flags) {
+ // not supported
+}
+
+uint32_t CameraTexture::get_flags() const {
+ // not supported
+ return 0;
+}
+
+Ref<Image> CameraTexture::get_data() const {
+ // not (yet) supported
+ return Ref<Image>();
+}
+
+void CameraTexture::set_camera_feed_id(int p_new_id) {
+ camera_feed_id = p_new_id;
+ _change_notify();
+}
+
+int CameraTexture::get_camera_feed_id() const {
+ return camera_feed_id;
+}
+
+void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) {
+ which_feed = p_which;
+ _change_notify();
+}
+
+CameraServer::FeedImage CameraTexture::get_which_feed() const {
+ return which_feed;
+}
+
+void CameraTexture::set_camera_active(bool p_active) {
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+ if (feed.is_valid()) {
+ feed->set_active(p_active);
+ _change_notify();
+ }
+}
+
+bool CameraTexture::get_camera_active() const {
+ Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
+ if (feed.is_valid()) {
+ return feed->is_active();
+ } else {
+ return false;
+ }
+}
+
+CameraTexture::CameraTexture() {
+ camera_feed_id = 0;
+ which_feed = CameraServer::FEED_RGBA_IMAGE;
+}
+
+CameraTexture::~CameraTexture() {
+ // nothing to do here yet
+}
diff --git a/scene/resources/texture.h b/scene/resources/texture.h
index 58287b7593..021673f072 100644
--- a/scene/resources/texture.h
+++ b/scene/resources/texture.h
@@ -39,6 +39,7 @@
#include "core/resource.h"
#include "scene/resources/curve.h"
#include "scene/resources/gradient.h"
+#include "servers/camera_server.h"
#include "servers/visual_server.h"
/**
@@ -740,4 +741,38 @@ public:
~AnimatedTexture();
};
+class CameraTexture : public Texture {
+ GDCLASS(CameraTexture, Texture)
+
+private:
+ int camera_feed_id;
+ CameraServer::FeedImage which_feed;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual int get_width() const;
+ virtual int get_height() const;
+ virtual RID get_rid() const;
+ virtual bool has_alpha() const;
+
+ virtual void set_flags(uint32_t p_flags);
+ virtual uint32_t get_flags() const;
+
+ virtual Ref<Image> get_data() const;
+
+ void set_camera_feed_id(int p_new_id);
+ int get_camera_feed_id() const;
+
+ void set_which_feed(CameraServer::FeedImage p_which);
+ CameraServer::FeedImage get_which_feed() const;
+
+ void set_camera_active(bool p_active);
+ bool get_camera_active() const;
+
+ CameraTexture();
+ ~CameraTexture();
+};
+
#endif
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index 5b5968c10f..d09fac47f0 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -319,6 +319,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
}
p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "occluder_offset"));
@@ -646,6 +647,36 @@ Vector2 TileSet::autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask,
}
}
+Vector2 TileSet::atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node, const Vector2 &p_tile_location) {
+
+ ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2());
+ //First try to forward selection to script
+ if (get_script_instance() != NULL) {
+ if (get_script_instance()->has_method("_forward_atlas_subtile_selection")) {
+ Variant ret = get_script_instance()->call("_forward_atlas_subtile_selection", p_id, p_tilemap_node, p_tile_location);
+ if (ret.get_type() == Variant::VECTOR2) {
+ return ret;
+ }
+ }
+ }
+
+ Vector2 coord = tile_get_region(p_id).size / autotile_get_size(p_id);
+
+ List<Vector2> coords;
+ for (int x = 0; x < coord.x; x++) {
+ for (int y = 0; y < coord.y; y++) {
+ for (int i = 0; i < autotile_get_subtile_priority(p_id, Vector2(x, y)); i++) {
+ coords.push_back(Vector2(x, y));
+ }
+ }
+ }
+ if (coords.size() == 0) {
+ return autotile_get_icon_coordinate(p_id);
+ } else {
+ return coords[Math::random(0, (int)coords.size())];
+ }
+}
+
void TileSet::tile_set_name(int p_id, const String &p_name) {
ERR_FAIL_COND(!tile_map.has(p_id));
@@ -1141,6 +1172,7 @@ void TileSet::_bind_methods() {
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_tile_bound", PropertyInfo(Variant::INT, "drawn_id"), PropertyInfo(Variant::INT, "neighbor_id")));
BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_subtile_selection", PropertyInfo(Variant::INT, "autotile_id"), PropertyInfo(Variant::INT, "bitmask"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location")));
+ BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_atlas_subtile_selection", PropertyInfo(Variant::INT, "atlastile_id"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location")));
BIND_ENUM_CONSTANT(BITMASK_2X2);
BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL);
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index fb84cee218..5fc22b9fc6 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -195,6 +195,7 @@ public:
uint32_t autotile_get_bitmask(int p_id, Vector2 p_coord);
const Map<Vector2, uint32_t> &autotile_get_bitmask_map(int p_id);
Vector2 autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node = NULL, const Vector2 &p_tile_location = Vector2());
+ Vector2 atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node = NULL, const Vector2 &p_tile_location = Vector2());
void tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape);
Ref<Shape2D> tile_get_shape(int p_id, int p_shape_id) const;
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 9b2e410985..dd595d9ff8 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -297,7 +297,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
if (MAX(0, from_port_type - 2) != (MAX(0, to_port_type - 2))) {
ERR_EXPLAIN("Incompatible port types (scalar/vec/bool with transform");
- ERR_FAIL_V(ERR_INVALID_PARAMETER)
+ ERR_FAIL_V(ERR_INVALID_PARAMETER);
return ERR_INVALID_PARAMETER;
}