summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/canvas_item.cpp3
-rw-r--r--scene/2d/navigation2d.cpp2
-rw-r--r--scene/2d/skeleton_2d.cpp46
-rw-r--r--scene/2d/skeleton_2d.h3
-rw-r--r--scene/2d/tile_map.cpp51
-rw-r--r--scene/3d/particles.cpp4
-rw-r--r--scene/3d/path.cpp216
-rw-r--r--scene/3d/path.h43
-rw-r--r--scene/3d/reflection_probe.cpp4
-rw-r--r--scene/3d/scenario_fx.cpp7
-rw-r--r--scene/3d/sprite_3d.cpp86
-rw-r--r--scene/3d/sprite_3d.h4
-rw-r--r--scene/3d/vehicle_body.cpp11
-rw-r--r--scene/animation/animation_blend_space.cpp558
-rw-r--r--scene/animation/animation_blend_space.h94
-rw-r--r--scene/animation/animation_blend_tree.cpp1112
-rw-r--r--scene/animation/animation_blend_tree.h328
-rw-r--r--scene/animation/animation_graph_player.cpp1211
-rw-r--r--scene/animation/animation_graph_player.h267
-rw-r--r--scene/animation/animation_player.cpp295
-rw-r--r--scene/animation/animation_player.h38
-rw-r--r--scene/animation/animation_tree_player.cpp7
-rw-r--r--scene/animation/animation_tree_player.h1
-rw-r--r--scene/animation/tween.cpp12
-rw-r--r--scene/animation/tween.h1
-rw-r--r--scene/gui/container.cpp5
-rw-r--r--scene/gui/control.cpp8
-rw-r--r--scene/gui/file_dialog.cpp1
-rw-r--r--scene/gui/gradient_edit.cpp1
-rw-r--r--scene/gui/graph_edit.cpp33
-rw-r--r--scene/gui/graph_edit.h8
-rw-r--r--scene/gui/line_edit.cpp25
-rw-r--r--scene/gui/popup.cpp34
-rw-r--r--scene/gui/popup_menu.cpp49
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/progress_bar.cpp6
-rw-r--r--scene/gui/scroll_container.cpp17
-rw-r--r--scene/gui/scroll_container.h3
-rw-r--r--scene/gui/tab_container.cpp40
-rw-r--r--scene/gui/text_edit.cpp17
-rw-r--r--scene/main/node.cpp130
-rw-r--r--scene/main/node.h24
-rw-r--r--scene/main/scene_tree.cpp16
-rw-r--r--scene/main/scene_tree.h3
-rw-r--r--scene/main/viewport.cpp54
-rw-r--r--scene/main/viewport.h1
-rw-r--r--scene/register_scene_types.cpp19
-rw-r--r--scene/resources/animation.cpp1046
-rw-r--r--scene/resources/animation.h83
-rw-r--r--scene/resources/audio_stream_sample.cpp8
-rw-r--r--scene/resources/curve.cpp125
-rw-r--r--scene/resources/curve.h6
-rw-r--r--scene/resources/default_theme/default_theme.cpp1
-rw-r--r--scene/resources/primitive_meshes.cpp68
-rw-r--r--scene/resources/style_box.cpp9
-rw-r--r--scene/resources/texture.cpp2
-rw-r--r--scene/resources/tile_set.cpp3
-rw-r--r--scene/resources/tile_set.h1
-rw-r--r--scene/scene_string_names.cpp2
-rw-r--r--scene/scene_string_names.h2
60 files changed, 5941 insertions, 317 deletions
diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp
index 27bdeda4a8..f1c09594da 100644
--- a/scene/2d/canvas_item.cpp
+++ b/scene/2d/canvas_item.cpp
@@ -272,8 +272,7 @@ bool CanvasItem::is_visible_in_tree() const {
void CanvasItem::_propagate_visibility_changed(bool p_visible) {
- if (!first_draw)
- notification(NOTIFICATION_VISIBILITY_CHANGED);
+ notification(NOTIFICATION_VISIBILITY_CHANGED);
if (p_visible)
update(); //todo optimize
diff --git a/scene/2d/navigation2d.cpp b/scene/2d/navigation2d.cpp
index 4737e14111..16e0386952 100644
--- a/scene/2d/navigation2d.cpp
+++ b/scene/2d/navigation2d.cpp
@@ -671,7 +671,7 @@ Object *Navigation2D::get_closest_point_owner(const Vector2 &p_point) {
if (Geometry::is_point_in_triangle(p_point, _get_vertex(p.edges[0].point), _get_vertex(p.edges[i - 1].point), _get_vertex(p.edges[i].point))) {
- E->get().owner;
+ return E->get().owner;
}
}
}
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 0595cc43b8..8ceffb3c27 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -89,8 +89,8 @@ void Bone2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length);
ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length);
- ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D,"rest"),"set_rest","get_rest");
- ADD_PROPERTY(PropertyInfo(Variant::REAL,"default_length",PROPERTY_HINT_RANGE,"1,1024,1"),"set_default_length","get_default_length");
+ ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length");
}
void Bone2D::set_rest(const Transform2D &p_rest) {
@@ -120,8 +120,7 @@ void Bone2D::apply_rest() {
void Bone2D::set_default_length(float p_length) {
- default_length=p_length;
-
+ default_length = p_length;
}
float Bone2D::get_default_length() const {
@@ -129,7 +128,7 @@ float Bone2D::get_default_length() const {
}
int Bone2D::get_index_in_skeleton() const {
- ERR_FAIL_COND_V(!skeleton,-1);
+ ERR_FAIL_COND_V(!skeleton, -1);
skeleton->_update_bone_setup();
return skeleton_index;
}
@@ -137,22 +136,21 @@ String Bone2D::get_configuration_warning() const {
String warning = Node2D::get_configuration_warning();
if (!skeleton) {
- if (warning!=String()) {
- warning+="\n";
+ if (warning != String()) {
+ warning += "\n";
}
if (parent_bone) {
- warning+=TTR("This Bone2D chain should end at a Skeleton2D node.");
+ warning += TTR("This Bone2D chain should end at a Skeleton2D node.");
} else {
- warning+=TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node.");
+ warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node.");
}
}
- if (rest==Transform2D(0,0,0,0,0,0)) {
- if (warning!=String()) {
- warning+="\n";
+ if (rest == Transform2D(0, 0, 0, 0, 0, 0)) {
+ if (warning != String()) {
+ warning += "\n";
}
- warning+=TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one.");
-
+ warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one.");
}
return warning;
@@ -161,12 +159,12 @@ String Bone2D::get_configuration_warning() const {
Bone2D::Bone2D() {
skeleton = NULL;
parent_bone = NULL;
- skeleton_index=-1;
- default_length=16;
+ skeleton_index = -1;
+ default_length = 16;
set_notify_local_transform(true);
//this is a clever hack so the bone knows no rest has been set yet, allowing to show an error.
- for(int i=0;i<3;i++) {
- rest[i]=Vector2(0,0);
+ for (int i = 0; i < 3; i++) {
+ rest[i] = Vector2(0, 0);
}
}
@@ -194,12 +192,12 @@ void Skeleton2D::_update_bone_setup() {
for (int i = 0; i < bones.size(); i++) {
bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
- bones[i].bone->skeleton_index=i;
+ bones[i].bone->skeleton_index = i;
Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
if (parent_bone) {
- bones[i].parent_index=parent_bone->skeleton_index;
+ bones[i].parent_index = parent_bone->skeleton_index;
} else {
- bones[i].parent_index=-1;
+ bones[i].parent_index = -1;
}
}
@@ -230,8 +228,8 @@ void Skeleton2D::_update_transform() {
for (int i = 0; i < bones.size(); i++) {
- ERR_CONTINUE(bones[i].parent_index>=i);
- if (bones[i].parent_index>=0) {
+ ERR_CONTINUE(bones[i].parent_index >= i);
+ if (bones[i].parent_index >= 0) {
bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
} else {
bones[i].accum_transform = bones[i].bone->get_transform();
@@ -277,7 +275,7 @@ void Skeleton2D::_notification(int p_what) {
}
if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
- VS::get_singleton()->skeleton_set_base_transform_2d(skeleton,get_global_transform());
+ VS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
}
}
diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h
index b86cf3be81..9d0a061457 100644
--- a/scene/2d/skeleton_2d.h
+++ b/scene/2d/skeleton_2d.h
@@ -38,12 +38,13 @@ class Skeleton2D;
class Bone2D : public Node2D {
GDCLASS(Bone2D, Node2D)
+ friend class Skeleton2D;
+
Bone2D *parent_bone;
Skeleton2D *skeleton;
Transform2D rest;
float default_length;
-friend class Skeleton2D;
int skeleton_index;
protected:
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 5294189c65..98275510d6 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -466,10 +466,12 @@ void TileMap::_update_dirty_quadrants() {
Transform2D xform;
xform.set_origin(offset.floor());
- Vector2 shape_ofs = tile_set->tile_get_shape_offset(c.id, i);
+ Vector2 shape_ofs = shapes[i].shape_transform.get_origin();
_fix_cell_transform(xform, c, shape_ofs + center_ofs, s);
+ xform *= shapes[i].shape_transform.untranslated();
+
if (debug_canvas_item.is_valid()) {
vs->canvas_item_add_set_transform(debug_canvas_item, xform);
shape->draw(debug_canvas_item, debug_collision_color);
@@ -845,16 +847,37 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) {
if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
mask |= TileSet::BIND_BOTTOMRIGHT;
}
- } else if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3) {
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) {
- mask |= TileSet::BIND_TOPLEFT;
+ } else {
+ if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3_MINIMAL) {
+ if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
+ mask |= TileSet::BIND_TOPLEFT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
+ mask |= TileSet::BIND_TOPRIGHT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
+ mask |= TileSet::BIND_BOTTOMLEFT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
+ mask |= TileSet::BIND_BOTTOMRIGHT;
+ }
+ } else {
+ if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) {
+ mask |= TileSet::BIND_TOPLEFT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) {
+ mask |= TileSet::BIND_TOPRIGHT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) {
+ mask |= TileSet::BIND_BOTTOMLEFT;
+ }
+ if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) {
+ mask |= TileSet::BIND_BOTTOMRIGHT;
+ }
}
if (tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1))) {
mask |= TileSet::BIND_TOP;
}
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) {
- mask |= TileSet::BIND_TOPRIGHT;
- }
if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) {
mask |= TileSet::BIND_LEFT;
}
@@ -862,15 +885,9 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) {
if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) {
mask |= TileSet::BIND_RIGHT;
}
- if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) {
- mask |= TileSet::BIND_BOTTOMLEFT;
- }
if (tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1))) {
mask |= TileSet::BIND_BOTTOM;
}
- if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) {
- mask |= TileSet::BIND_BOTTOMRIGHT;
- }
}
Vector2 coord = tile_set->autotile_get_subtile_for_bitmask(id, mask, this, Vector2(p_x, p_y));
E->get().autotile_coord_x = (int)coord.x;
@@ -962,6 +979,14 @@ void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord)
c.autotile_coord_x = p_coord.x;
c.autotile_coord_y = p_coord.y;
tile_map[pk] = c;
+
+ PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size());
+ Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk);
+
+ if (!Q)
+ return;
+
+ _make_quadrant_dirty(Q);
}
Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const {
diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp
index 7b5eb8ebc3..2b3a62fcdc 100644
--- a/scene/3d/particles.cpp
+++ b/scene/3d/particles.cpp
@@ -1444,7 +1444,7 @@ void ParticlesMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_color_modifier", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_trail_color_modifier", "get_trail_color_modifier");
ADD_GROUP("Emission Shape", "emission_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_point_texture", "get_emission_point_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_normal_texture", "get_emission_normal_texture");
@@ -1483,7 +1483,7 @@ void ParticlesMaterial::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL);
ADD_GROUP("Damping", "");
- ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING);
+ ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING);
ADD_GROUP("Angle", "");
diff --git a/scene/3d/path.cpp b/scene/3d/path.cpp
index 154dcb4259..9acaa15641 100644
--- a/scene/3d/path.cpp
+++ b/scene/3d/path.cpp
@@ -329,3 +329,219 @@ PathFollow::PathFollow() {
cubic = true;
loop = true;
}
+
+//////////////
+
+void OrientedPathFollow::_update_transform() {
+
+ if (!path)
+ return;
+
+ Ref<Curve3D> c = path->get_curve();
+ if (!c.is_valid())
+ return;
+
+ int count = c->get_point_count();
+ if (count < 2)
+ return;
+
+ if (delta_offset == 0) {
+ return;
+ }
+
+ float offset = get_offset();
+ float bl = c->get_baked_length();
+ float bi = c->get_bake_interval();
+ float o = offset;
+ float o_next = offset + bi;
+
+ if (has_loop()) {
+ o = Math::fposmod(o, bl);
+ o_next = Math::fposmod(o_next, bl);
+ } else if (o_next >= bl) {
+ o = bl - bi;
+ o_next = bl;
+ }
+
+ bool cubic = get_cubic_interpolation();
+ Vector3 pos = c->interpolate_baked(o, cubic);
+ Vector3 forward = c->interpolate_baked(o_next, cubic) - pos;
+
+ if (forward.length_squared() < CMP_EPSILON2)
+ forward = Vector3(0, 0, 1);
+ else
+ forward.normalize();
+
+ Vector3 up = c->interpolate_baked_up_vector(o, true);
+
+ if (o_next < o) {
+ Vector3 up1 = c->interpolate_baked_up_vector(o_next, true);
+ Vector3 axis = up.cross(up1);
+
+ if (axis.length_squared() < CMP_EPSILON2)
+ axis = forward;
+ else
+ axis.normalize();
+
+ up.rotate(axis, up.angle_to(up1) * 0.5f);
+ }
+
+ Transform t = get_transform();
+ Vector3 scale = t.basis.get_scale();
+
+ Vector3 sideways = up.cross(forward).normalized();
+ up = forward.cross(sideways).normalized();
+
+ t.basis.set(sideways, up, forward);
+ t.basis.scale_local(scale);
+
+ t.origin = pos + sideways * get_h_offset() + up * get_v_offset();
+
+ set_transform(t);
+}
+
+void OrientedPathFollow::_notification(int p_what) {
+
+ switch (p_what) {
+
+ case NOTIFICATION_ENTER_TREE: {
+
+ Node *parent = get_parent();
+ if (parent) {
+ path = Object::cast_to<Path>(parent);
+ if (path) {
+ _update_transform();
+ }
+ }
+
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+
+ path = NULL;
+ } break;
+ }
+}
+
+void OrientedPathFollow::set_cubic_interpolation(bool p_enable) {
+
+ cubic = p_enable;
+}
+
+bool OrientedPathFollow::get_cubic_interpolation() const {
+
+ return cubic;
+}
+
+void OrientedPathFollow::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "offset") {
+
+ float max = 10000;
+ if (path && path->get_curve().is_valid())
+ max = path->get_curve()->get_baked_length();
+
+ property.hint_string = "0," + rtos(max) + ",0.01";
+ }
+}
+
+void OrientedPathFollow::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_offset", "offset"), &OrientedPathFollow::set_offset);
+ ClassDB::bind_method(D_METHOD("get_offset"), &OrientedPathFollow::get_offset);
+
+ ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &OrientedPathFollow::set_h_offset);
+ ClassDB::bind_method(D_METHOD("get_h_offset"), &OrientedPathFollow::get_h_offset);
+
+ ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &OrientedPathFollow::set_v_offset);
+ ClassDB::bind_method(D_METHOD("get_v_offset"), &OrientedPathFollow::get_v_offset);
+
+ ClassDB::bind_method(D_METHOD("set_unit_offset", "unit_offset"), &OrientedPathFollow::set_unit_offset);
+ ClassDB::bind_method(D_METHOD("get_unit_offset"), &OrientedPathFollow::get_unit_offset);
+
+ ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &OrientedPathFollow::set_cubic_interpolation);
+ ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &OrientedPathFollow::get_cubic_interpolation);
+
+ ClassDB::bind_method(D_METHOD("set_loop", "loop"), &OrientedPathFollow::set_loop);
+ ClassDB::bind_method(D_METHOD("has_loop"), &OrientedPathFollow::has_loop);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01"), "set_offset", "get_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "h_offset"), "set_h_offset", "get_h_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_offset"), "set_v_offset", "get_v_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
+}
+
+void OrientedPathFollow::set_offset(float p_offset) {
+ delta_offset = p_offset - offset;
+ offset = p_offset;
+
+ if (path)
+ _update_transform();
+ _change_notify("offset");
+ _change_notify("unit_offset");
+}
+
+void OrientedPathFollow::set_h_offset(float p_h_offset) {
+
+ h_offset = p_h_offset;
+ if (path)
+ _update_transform();
+}
+
+float OrientedPathFollow::get_h_offset() const {
+
+ return h_offset;
+}
+
+void OrientedPathFollow::set_v_offset(float p_v_offset) {
+
+ v_offset = p_v_offset;
+ if (path)
+ _update_transform();
+}
+
+float OrientedPathFollow::get_v_offset() const {
+
+ return v_offset;
+}
+
+float OrientedPathFollow::get_offset() const {
+
+ return offset;
+}
+
+void OrientedPathFollow::set_unit_offset(float p_unit_offset) {
+
+ if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length())
+ set_offset(p_unit_offset * path->get_curve()->get_baked_length());
+}
+
+float OrientedPathFollow::get_unit_offset() const {
+
+ if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length())
+ return get_offset() / path->get_curve()->get_baked_length();
+ else
+ return 0;
+}
+
+void OrientedPathFollow::set_loop(bool p_loop) {
+
+ loop = p_loop;
+}
+
+bool OrientedPathFollow::has_loop() const {
+
+ return loop;
+}
+
+OrientedPathFollow::OrientedPathFollow() {
+
+ offset = 0;
+ delta_offset = 0;
+ h_offset = 0;
+ v_offset = 0;
+ path = NULL;
+ cubic = true;
+ loop = true;
+}
diff --git a/scene/3d/path.h b/scene/3d/path.h
index 2ed686ac3c..f73bf17dfe 100644
--- a/scene/3d/path.h
+++ b/scene/3d/path.h
@@ -111,4 +111,47 @@ public:
VARIANT_ENUM_CAST(PathFollow::RotationMode);
+class OrientedPathFollow : public Spatial {
+
+ GDCLASS(OrientedPathFollow, Spatial);
+
+private:
+ Path *path;
+ real_t delta_offset; // change in offset since last _update_transform
+ real_t offset;
+ real_t h_offset;
+ real_t v_offset;
+ bool cubic;
+ bool loop;
+
+ void _update_transform();
+
+protected:
+ virtual void _validate_property(PropertyInfo &property) const;
+
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_offset(float p_offset);
+ float get_offset() const;
+
+ void set_h_offset(float p_h_offset);
+ float get_h_offset() const;
+
+ void set_v_offset(float p_v_offset);
+ float get_v_offset() const;
+
+ void set_unit_offset(float p_unit_offset);
+ float get_unit_offset() const;
+
+ void set_loop(bool p_loop);
+ bool has_loop() const;
+
+ void set_cubic_interpolation(bool p_enable);
+ bool get_cubic_interpolation() const;
+
+ OrientedPathFollow();
+};
+
#endif // PATH_H
diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp
index 7e3a87cbd4..4d50945062 100644
--- a/scene/3d/reflection_probe.cpp
+++ b/scene/3d/reflection_probe.cpp
@@ -241,8 +241,8 @@ void ReflectionProbe::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Once,Always"), "set_update_mode", "get_update_mode");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "intensity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_intensity", "get_intensity");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "0,16384,0.1,or_greater"), "set_max_distance", "get_max_distance");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "extents"), "set_extents", "get_extents");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "origin_offset"), "set_origin_offset", "get_origin_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "box_projection"), "set_enable_box_projection", "is_box_projection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_shadows"), "set_enable_shadows", "are_shadows_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
diff --git a/scene/3d/scenario_fx.cpp b/scene/3d/scenario_fx.cpp
index d5bff676cb..26cbfc0b11 100644
--- a/scene/3d/scenario_fx.cpp
+++ b/scene/3d/scenario_fx.cpp
@@ -93,9 +93,10 @@ String WorldEnvironment::get_configuration_warning() const {
return TTR("Only one WorldEnvironment is allowed per scene (or set of instanced scenes).");
}
- if (environment.is_valid() && get_viewport() && !get_viewport()->get_camera() && environment->get_background() != Environment::BG_CANVAS) {
- return TTR("This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set this environment's Background Mode to Canvas (for 2D scenes).");
- }
+ // Commenting this warning for now, I think it makes no sense. If anyone can figure out what its supposed to do, feedback welcome. Else it should be deprecated.
+ //if (environment.is_valid() && get_viewport() && !get_viewport()->get_camera() && environment->get_background() != Environment::BG_CANVAS) {
+ // return TTR("This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set this environment's Background Mode to Canvas (for 2D scenes).");
+ //}
return String();
}
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 232855c978..036a748c83 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -185,6 +185,9 @@ void SpriteBase3D::_queue_update() {
if (pending_update)
return;
+ triangle_mesh.unref();
+ update_gizmo();
+
pending_update = true;
call_deferred(SceneStringNames::get_singleton()->_im_update);
}
@@ -198,6 +201,66 @@ PoolVector<Face3> SpriteBase3D::get_faces(uint32_t p_usage_flags) const {
return PoolVector<Face3>();
}
+Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const {
+ if (triangle_mesh.is_valid())
+ return triangle_mesh;
+
+ PoolVector<Vector3> faces;
+ faces.resize(6);
+ PoolVector<Vector3>::Write facesw = faces.write();
+
+ Rect2 final_rect = get_item_rect();
+
+ if (final_rect.size.x == 0 || final_rect.size.y == 0)
+ return Ref<TriangleMesh>();
+
+ float pixel_size = get_pixel_size();
+
+ Vector2 vertices[4] = {
+
+ (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,
+ (final_rect.position + final_rect.size) * pixel_size,
+ (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,
+ final_rect.position * pixel_size,
+
+ };
+
+ int x_axis = ((axis + 1) % 3);
+ int y_axis = ((axis + 2) % 3);
+
+ if (axis != Vector3::AXIS_Z) {
+ SWAP(x_axis, y_axis);
+
+ for (int i = 0; i < 4; i++) {
+ if (axis == Vector3::AXIS_Y) {
+ vertices[i].y = -vertices[i].y;
+ } else if (axis == Vector3::AXIS_X) {
+ vertices[i].x = -vertices[i].x;
+ }
+ }
+ }
+
+ static const int indices[6] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+
+ for (int j = 0; j < 6; j++) {
+ int i = indices[j];
+ Vector3 vtx;
+ vtx[x_axis] = vertices[i][0];
+ vtx[y_axis] = vertices[i][1];
+ facesw[j] = vtx;
+ }
+
+ facesw = PoolVector<Vector3>::Write();
+
+ triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh));
+ triangle_mesh->create(faces);
+
+ return triangle_mesh;
+}
+
void SpriteBase3D::set_draw_flag(DrawFlags p_flag, bool p_enable) {
ERR_FAIL_INDEX(p_flag, FLAG_MAX);
@@ -255,6 +318,7 @@ void SpriteBase3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &SpriteBase3D::get_alpha_cut_mode);
ClassDB::bind_method(D_METHOD("get_item_rect"), &SpriteBase3D::get_item_rect);
+ ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &SpriteBase3D::generate_triangle_mesh);
ClassDB::bind_method(D_METHOD("_queue_update"), &SpriteBase3D::_queue_update);
ClassDB::bind_method(D_METHOD("_im_update"), &SpriteBase3D::_im_update);
@@ -366,6 +430,16 @@ void Sprite3D::_draw() {
final_rect.position * pixel_size,
};
+
+ Vector2 src_tsize = Vector2(texture->get_width(), texture->get_height());
+
+ // Properly setup UVs for impostor textures (AtlasTexture).
+ Ref<AtlasTexture> atlas_tex = texture;
+ if (atlas_tex != NULL) {
+ src_tsize[0] = atlas_tex->get_atlas()->get_width();
+ src_tsize[1] = atlas_tex->get_atlas()->get_height();
+ }
+
Vector2 uvs[4] = {
final_src_rect.position / tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize,
@@ -656,6 +730,16 @@ void AnimatedSprite3D::_draw() {
final_rect.position * pixel_size,
};
+
+ Vector2 src_tsize = Vector2(texture->get_width(), texture->get_height());
+
+ // Properly setup UVs for impostor textures (AtlasTexture).
+ Ref<AtlasTexture> atlas_tex = texture;
+ if (atlas_tex != NULL) {
+ src_tsize[0] = atlas_tex->get_atlas()->get_width();
+ src_tsize[1] = atlas_tex->get_atlas()->get_height();
+ }
+
Vector2 uvs[4] = {
final_src_rect.position / tsize,
(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize,
@@ -881,7 +965,7 @@ Rect2 AnimatedSprite3D::get_item_rect() const {
return Rect2(0, 0, 1, 1);
Size2i s = t->get_size();
- Point2 ofs = offset;
+ Point2 ofs = get_offset();
if (centered)
ofs -= s / 2;
diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h
index 23e1d96b4b..a4705a8970 100644
--- a/scene/3d/sprite_3d.h
+++ b/scene/3d/sprite_3d.h
@@ -38,6 +38,8 @@ class SpriteBase3D : public GeometryInstance {
GDCLASS(SpriteBase3D, GeometryInstance);
+ mutable Ref<TriangleMesh> triangle_mesh; //cached
+
public:
enum DrawFlags {
FLAG_TRANSPARENT,
@@ -133,6 +135,7 @@ public:
virtual AABB get_aabb() const;
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
+ Ref<TriangleMesh> generate_triangle_mesh() const;
SpriteBase3D();
~SpriteBase3D();
@@ -192,7 +195,6 @@ class AnimatedSprite3D : public SpriteBase3D {
int frame;
bool centered;
- Point2 offset;
float timeout;
diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp
index ee8d249981..385956dc16 100644
--- a/scene/3d/vehicle_body.cpp
+++ b/scene/3d/vehicle_body.cpp
@@ -583,11 +583,14 @@ void VehicleBody::_resolve_single_bilateral(PhysicsDirectBodyState *s, const Vec
rel_vel = normal.dot(vel);
// !BAS! We had this set to 0.4, in bullet its 0.2
- // real_t contactDamping = real_t(0.2);
+ real_t contactDamping = real_t(0.2);
+
+ if (p_rollInfluence > 0.0) {
+ // !BAS! But seeing we apply this frame by frame, makes more sense to me to make this time based
+ // keeping in mind our anti roll factor if it is set
+ contactDamping = s->get_step() / p_rollInfluence;
+ }
- // !BAS! But seeing we apply this frame by frame, makes more sense to me to make this time based
- // keeping in mind our anti roll factor
- real_t contactDamping = s->get_step() / p_rollInfluence;
#define ONLY_USE_LINEAR_MASS
#ifdef ONLY_USE_LINEAR_MASS
real_t massTerm = real_t(1.) / ((1.0 / mass) + b2invmass);
diff --git a/scene/animation/animation_blend_space.cpp b/scene/animation/animation_blend_space.cpp
new file mode 100644
index 0000000000..44b2d980e2
--- /dev/null
+++ b/scene/animation/animation_blend_space.cpp
@@ -0,0 +1,558 @@
+#include "animation_blend_space.h"
+#include "math/delaunay.h"
+
+void AnimationNodeBlendSpace::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) {
+ ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(p_node->get_parent().is_valid());
+ ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used);
+
+ if (p_at_index == -1 || p_at_index == blend_points_used) {
+ p_at_index = blend_points_used;
+ } else {
+ for (int i = blend_points_used; i > p_at_index; i--) {
+ blend_points[i] = blend_points[i - 1];
+ }
+ for (int i = 0; i < triangles.size(); i++) {
+ for (int j = 0; j < 3; j++) {
+ if (triangles[i].points[j] >= p_at_index) {
+ triangles[i].points[j]++;
+ }
+ }
+ }
+ }
+ blend_points[p_at_index].node = p_node;
+ blend_points[p_at_index].position = p_position;
+
+ blend_points[p_at_index].node->set_parent(this);
+ blend_points[p_at_index].node->set_graph_player(get_graph_player());
+ blend_points_used++;
+
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+}
+
+void AnimationNodeBlendSpace::set_blend_point_position(int p_point, const Vector2 &p_position) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+ blend_points[p_point].position = p_position;
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+}
+void AnimationNodeBlendSpace::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+ ERR_FAIL_COND(p_node.is_null());
+
+ if (blend_points[p_point].node.is_valid()) {
+ blend_points[p_point].node->set_parent(NULL);
+ blend_points[p_point].node->set_graph_player(NULL);
+ }
+ blend_points[p_point].node = p_node;
+ blend_points[p_point].node->set_parent(this);
+ blend_points[p_point].node->set_graph_player(get_graph_player());
+}
+Vector2 AnimationNodeBlendSpace::get_blend_point_position(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Vector2());
+ return blend_points[p_point].position;
+}
+Ref<AnimationRootNode> AnimationNodeBlendSpace::get_blend_point_node(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>());
+ return blend_points[p_point].node;
+}
+void AnimationNodeBlendSpace::remove_blend_point(int p_point) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+
+ blend_points[p_point].node->set_parent(NULL);
+ blend_points[p_point].node->set_graph_player(NULL);
+
+ for (int i = 0; i < triangles.size(); i++) {
+ bool erase = false;
+ for (int j = 0; j < 3; j++) {
+ if (triangles[i].points[j] == p_point) {
+ erase = true;
+ break;
+ } else if (triangles[i].points[j] > p_point) {
+ triangles[i].points[j]--;
+ }
+ }
+ if (erase) {
+ triangles.remove(i);
+
+ i--;
+ }
+ }
+
+ for (int i = p_point; i < blend_points_used - 1; i++) {
+ blend_points[i] = blend_points[i + 1];
+ }
+ blend_points_used--;
+}
+
+int AnimationNodeBlendSpace::get_blend_point_count() const {
+
+ return blend_points_used;
+}
+
+bool AnimationNodeBlendSpace::has_triangle(int p_x, int p_y, int p_z) const {
+
+ ERR_FAIL_INDEX_V(p_x, blend_points_used, false);
+ ERR_FAIL_INDEX_V(p_y, blend_points_used, false);
+ ERR_FAIL_INDEX_V(p_z, blend_points_used, false);
+
+ BlendTriangle t;
+ t.points[0] = p_x;
+ t.points[1] = p_y;
+ t.points[2] = p_z;
+
+ SortArray<int> sort;
+ sort.sort(t.points, 3);
+
+ for (int i = 0; i < triangles.size(); i++) {
+ bool all_equal = true;
+ for (int j = 0; j < 3; j++) {
+ if (triangles[i].points[j] != t.points[j]) {
+ all_equal = false;
+ break;
+ }
+ }
+ if (all_equal)
+ return true;
+ }
+
+ return false;
+}
+
+void AnimationNodeBlendSpace::add_triangle(int p_x, int p_y, int p_z, int p_at_index) {
+
+ ERR_FAIL_INDEX(p_x, blend_points_used);
+ ERR_FAIL_INDEX(p_y, blend_points_used);
+ ERR_FAIL_INDEX(p_z, blend_points_used);
+
+ _update_triangles();
+
+ BlendTriangle t;
+ t.points[0] = p_x;
+ t.points[1] = p_y;
+ t.points[2] = p_z;
+
+ SortArray<int> sort;
+ sort.sort(t.points, 3);
+
+ for (int i = 0; i < triangles.size(); i++) {
+ bool all_equal = true;
+ for (int j = 0; j < 3; j++) {
+ if (triangles[i].points[j] != t.points[j]) {
+ all_equal = false;
+ break;
+ }
+ }
+ ERR_FAIL_COND(all_equal);
+ }
+
+ if (p_at_index == -1 || p_at_index == triangles.size()) {
+ triangles.push_back(t);
+ } else {
+ triangles.insert(p_at_index, t);
+ }
+}
+int AnimationNodeBlendSpace::get_triangle_point(int p_triangle, int p_point) {
+
+ _update_triangles();
+
+ ERR_FAIL_INDEX_V(p_point, 3, -1);
+ ERR_FAIL_INDEX_V(p_triangle, triangles.size(), -1);
+ return triangles[p_triangle].points[p_point];
+}
+void AnimationNodeBlendSpace::remove_triangle(int p_triangle) {
+ ERR_FAIL_INDEX(p_triangle, triangles.size());
+
+ triangles.remove(p_triangle);
+}
+
+int AnimationNodeBlendSpace::get_triangle_count() const {
+ return triangles.size();
+}
+
+void AnimationNodeBlendSpace::set_min_space(const Vector2 &p_min) {
+
+ min_space = p_min;
+ if (min_space.x >= max_space.x) {
+ min_space.x = max_space.x - 1;
+ }
+ if (min_space.y >= max_space.y) {
+ min_space.y = max_space.y - 1;
+ }
+}
+Vector2 AnimationNodeBlendSpace::get_min_space() const {
+ return min_space;
+}
+
+void AnimationNodeBlendSpace::set_max_space(const Vector2 &p_max) {
+
+ max_space = p_max;
+ if (max_space.x <= min_space.x) {
+ max_space.x = min_space.x + 1;
+ }
+ if (max_space.y <= min_space.y) {
+ max_space.y = min_space.y + 1;
+ }
+}
+Vector2 AnimationNodeBlendSpace::get_max_space() const {
+ return max_space;
+}
+
+void AnimationNodeBlendSpace::set_snap(const Vector2 &p_snap) {
+ snap = p_snap;
+}
+Vector2 AnimationNodeBlendSpace::get_snap() const {
+ return snap;
+}
+
+void AnimationNodeBlendSpace::set_blend_pos(const Vector2 &p_pos) {
+ blend_pos = p_pos;
+}
+Vector2 AnimationNodeBlendSpace::get_blend_pos() const {
+ return blend_pos;
+}
+
+void AnimationNodeBlendSpace::set_x_label(const String &p_label) {
+ x_label = p_label;
+}
+String AnimationNodeBlendSpace::get_x_label() const {
+ return x_label;
+}
+
+void AnimationNodeBlendSpace::set_y_label(const String &p_label) {
+ y_label = p_label;
+}
+String AnimationNodeBlendSpace::get_y_label() const {
+ return y_label;
+}
+
+void AnimationNodeBlendSpace::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
+ if (p_index == blend_points_used) {
+ add_blend_point(p_node, Vector2());
+ } else {
+ set_blend_point_node(p_index, p_node);
+ }
+}
+
+void AnimationNodeBlendSpace::_set_triangles(const Vector<int> &p_triangles) {
+
+ if (auto_triangles)
+ return;
+ ERR_FAIL_COND(p_triangles.size() % 3 != 0);
+ for (int i = 0; i < p_triangles.size(); i += 3) {
+ add_triangle(p_triangles[i + 0], p_triangles[i + 1], p_triangles[i + 2]);
+ }
+}
+
+Vector<int> AnimationNodeBlendSpace::_get_triangles() const {
+
+ Vector<int> t;
+ if (auto_triangles && trianges_dirty)
+ return t;
+
+ t.resize(triangles.size() * 3);
+ for (int i = 0; i < triangles.size(); i++) {
+ t[i * 3 + 0] = triangles[i].points[0];
+ t[i * 3 + 1] = triangles[i].points[1];
+ t[i * 3 + 2] = triangles[i].points[2];
+ }
+ return t;
+}
+
+void AnimationNodeBlendSpace::_update_triangles() {
+
+ if (!auto_triangles || !trianges_dirty)
+ return;
+
+ trianges_dirty = false;
+ triangles.clear();
+ if (blend_points_used < 3)
+ return;
+
+ Vector<Vector2> points;
+ points.resize(blend_points_used);
+ for (int i = 0; i < blend_points_used; i++) {
+ points[i] = blend_points[i].position;
+ }
+
+ Vector<Delaunay2D::Triangle> triangles = Delaunay2D::triangulate(points);
+
+ for (int i = 0; i < triangles.size(); i++) {
+ add_triangle(triangles[i].points[0], triangles[i].points[1], triangles[i].points[2]);
+ }
+}
+
+Vector2 AnimationNodeBlendSpace::get_closest_point(const Vector2 &p_point) {
+
+ _update_triangles();
+
+ if (triangles.size() == 0)
+ return Vector2();
+
+ Vector2 best_point;
+ bool first = true;
+
+ for (int i = 0; i < triangles.size(); i++) {
+ Vector2 points[3];
+ for (int j = 0; j < 3; j++) {
+ points[j] = get_blend_point_position(get_triangle_point(i, j));
+ }
+
+ if (Geometry::is_point_in_triangle(p_point, points[0], points[1], points[2])) {
+
+ return p_point;
+ }
+
+ for (int j = 0; j < 3; j++) {
+ Vector2 s[2] = {
+ points[j],
+ points[(j + 1) % 3]
+ };
+ Vector2 closest = Geometry::get_closest_point_to_segment_2d(p_point, s);
+ if (first || closest.distance_to(p_point) < best_point.distance_to(p_point)) {
+ best_point = closest;
+ first = false;
+ }
+ }
+ }
+
+ return best_point;
+}
+
+void AnimationNodeBlendSpace::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
+
+ if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) {
+ r_weights[0] = 1;
+ r_weights[1] = 0;
+ r_weights[2] = 0;
+ return;
+ }
+ if (p_pos.distance_squared_to(p_points[1]) < CMP_EPSILON2) {
+ r_weights[0] = 0;
+ r_weights[1] = 1;
+ r_weights[2] = 0;
+ return;
+ }
+ if (p_pos.distance_squared_to(p_points[2]) < CMP_EPSILON2) {
+ r_weights[0] = 0;
+ r_weights[1] = 0;
+ r_weights[2] = 1;
+ return;
+ }
+
+ Vector2 v0 = p_points[1] - p_points[0];
+ Vector2 v1 = p_points[2] - p_points[0];
+ Vector2 v2 = p_pos - p_points[0];
+
+ float d00 = v0.dot(v0);
+ float d01 = v0.dot(v1);
+ float d11 = v1.dot(v1);
+ float d20 = v2.dot(v0);
+ float d21 = v2.dot(v1);
+ float denom = (d00 * d11 - d01 * d01);
+ if (denom == 0) {
+ r_weights[0] = 1;
+ r_weights[1] = 0;
+ r_weights[2] = 0;
+ return;
+ }
+ float v = (d11 * d20 - d01 * d21) / denom;
+ float w = (d00 * d21 - d01 * d20) / denom;
+ float u = 1.0f - v - w;
+
+ r_weights[0] = u;
+ r_weights[1] = v;
+ r_weights[2] = w;
+}
+
+float AnimationNodeBlendSpace::process(float p_time, bool p_seek) {
+
+ _update_triangles();
+
+ if (triangles.size() == 0)
+ return 0;
+
+ Vector2 best_point;
+ bool first = true;
+ int blend_triangle = -1;
+ float blend_weights[3] = { 0, 0, 0 };
+
+ for (int i = 0; i < triangles.size(); i++) {
+ Vector2 points[3];
+ for (int j = 0; j < 3; j++) {
+ points[j] = get_blend_point_position(get_triangle_point(i, j));
+ }
+
+ if (Geometry::is_point_in_triangle(blend_pos, points[0], points[1], points[2])) {
+
+ blend_triangle = i;
+ _blend_triangle(blend_pos, points, blend_weights);
+ break;
+ }
+
+ for (int j = 0; j < 3; j++) {
+ Vector2 s[2] = {
+ points[j],
+ points[(j + 1) % 3]
+ };
+ Vector2 closest = Geometry::get_closest_point_to_segment_2d(blend_pos, s);
+ if (first || closest.distance_to(blend_pos) < best_point.distance_to(blend_pos)) {
+ best_point = closest;
+ blend_triangle = i;
+ first = false;
+ float d = s[0].distance_to(s[1]);
+ if (d == 0.0) {
+ blend_weights[j] = 1.0;
+ blend_weights[(j + 1) % 3] = 0.0;
+ blend_weights[(j + 2) % 3] = 0.0;
+ } else {
+ float c = s[0].distance_to(closest) / d;
+
+ blend_weights[j] = 1.0 - c;
+ blend_weights[(j + 1) % 3] = c;
+ blend_weights[(j + 2) % 3] = 0.0;
+ }
+ }
+ }
+ }
+
+ ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here
+
+ int triangle_points[3];
+ for (int j = 0; j < 3; j++) {
+ triangle_points[j] = get_triangle_point(blend_triangle, j);
+ }
+
+ first = true;
+ float mind;
+ for (int i = 0; i < blend_points_used; i++) {
+
+ bool found = false;
+ for (int j = 0; j < 3; j++) {
+ if (i == triangle_points[j]) {
+ //blend with the given weight
+ float t = blend_node(blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false);
+ if (first || t < mind) {
+ mind = t;
+ first = false;
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ //ignore
+ blend_node(blend_points[i].node, p_time, p_seek, 0, FILTER_IGNORE, false);
+ }
+ }
+ return mind;
+}
+
+String AnimationNodeBlendSpace::get_caption() const {
+ return "BlendSpace";
+}
+
+void AnimationNodeBlendSpace::_validate_property(PropertyInfo &property) const {
+ if (property.name.begins_with("blend_point_")) {
+ String left = property.name.get_slicec('/', 0);
+ int idx = left.get_slicec('_', 2).to_int();
+ if (idx >= blend_points_used) {
+ property.usage = 0;
+ }
+ }
+ AnimationRootNode::_validate_property(property);
+}
+
+void AnimationNodeBlendSpace::set_auto_triangles(bool p_enable) {
+ auto_triangles = p_enable;
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+}
+
+bool AnimationNodeBlendSpace::get_auto_triangles() const {
+ return auto_triangles;
+}
+
+void AnimationNodeBlendSpace::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace::add_blend_point, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace::set_blend_point_position);
+ ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace::get_blend_point_position);
+ ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace::set_blend_point_node);
+ ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace::get_blend_point_node);
+ ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace::remove_blend_point);
+ ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace::get_blend_point_count);
+
+ ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace::add_triangle, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace::get_triangle_point);
+ ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace::remove_triangle);
+ ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace::get_triangle_count);
+
+ ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace::set_min_space);
+ ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace::get_min_space);
+
+ ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace::set_max_space);
+ ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace::get_max_space);
+
+ ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace::set_snap);
+ ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace::get_snap);
+
+ ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace::set_blend_pos);
+ ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace::get_blend_pos);
+
+ ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace::set_x_label);
+ ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace::get_x_label);
+
+ ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace::set_y_label);
+ ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace::get_y_label);
+
+ ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace::_add_blend_point);
+
+ ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace::_set_triangles);
+ ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace::_get_triangles);
+
+ ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace::set_auto_triangles);
+ ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace::get_auto_triangles);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_auto_triangles", "get_auto_triangles");
+
+ for (int i = 0; i < MAX_BLEND_POINTS; i++) {
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ }
+
+ ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
+
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label");
+}
+
+AnimationNodeBlendSpace::AnimationNodeBlendSpace() {
+
+ auto_triangles = true;
+ blend_points_used = 0;
+ max_space = Vector2(1, 1);
+ min_space = Vector2(-1, -1);
+ snap = Vector2(0.1, 0.1);
+ x_label = "x";
+ y_label = "y";
+ trianges_dirty = false;
+}
+
+AnimationNodeBlendSpace::~AnimationNodeBlendSpace() {
+
+ for (int i = 0; i < blend_points_used; i++) {
+ blend_points[i].node->set_parent(this);
+ blend_points[i].node->set_graph_player(get_graph_player());
+ }
+}
diff --git a/scene/animation/animation_blend_space.h b/scene/animation/animation_blend_space.h
new file mode 100644
index 0000000000..3eda3d2d39
--- /dev/null
+++ b/scene/animation/animation_blend_space.h
@@ -0,0 +1,94 @@
+#ifndef ANIMATION_BLEND_SPACE_H
+#define ANIMATION_BLEND_SPACE_H
+
+#include "scene/animation/animation_graph_player.h"
+
+class AnimationNodeBlendSpace : public AnimationRootNode {
+ GDCLASS(AnimationNodeBlendSpace, AnimationRootNode)
+
+ enum {
+ MAX_BLEND_POINTS = 64
+ };
+
+ struct BlendPoint {
+ Ref<AnimationRootNode> node;
+ Vector2 position;
+ };
+
+ BlendPoint blend_points[MAX_BLEND_POINTS];
+ int blend_points_used;
+
+ struct BlendTriangle {
+ int points[3];
+ };
+
+ Vector<BlendTriangle> triangles;
+
+ Vector2 blend_pos;
+ Vector2 max_space;
+ Vector2 min_space;
+ Vector2 snap;
+ String x_label;
+ String y_label;
+
+ void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node);
+ void _set_triangles(const Vector<int> &p_triangles);
+ Vector<int> _get_triangles() const;
+
+ void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights);
+
+ bool auto_triangles;
+ bool trianges_dirty;
+
+ void _update_triangles();
+
+protected:
+ virtual void _validate_property(PropertyInfo &property) const;
+ static void _bind_methods();
+
+public:
+ void add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1);
+ void set_blend_point_position(int p_point, const Vector2 &p_position);
+ void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node);
+ Vector2 get_blend_point_position(int p_point) const;
+ Ref<AnimationRootNode> get_blend_point_node(int p_point) const;
+ void remove_blend_point(int p_point);
+ int get_blend_point_count() const;
+
+ bool has_triangle(int p_x, int p_y, int p_z) const;
+ void add_triangle(int p_x, int p_y, int p_z, int p_at_index = -1);
+ int get_triangle_point(int p_triangle, int p_point);
+ void remove_triangle(int p_triangle);
+ int get_triangle_count() const;
+
+ void set_min_space(const Vector2 &p_min);
+ Vector2 get_min_space() const;
+
+ void set_max_space(const Vector2 &p_max);
+ Vector2 get_max_space() const;
+
+ void set_snap(const Vector2 &p_snap);
+ Vector2 get_snap() const;
+
+ void set_blend_pos(const Vector2 &p_pos);
+ Vector2 get_blend_pos() const;
+
+ void set_x_label(const String &p_label);
+ String get_x_label() const;
+
+ void set_y_label(const String &p_label);
+ String get_y_label() const;
+
+ float process(float p_time, bool p_seek);
+ String get_caption() const;
+
+ Vector2 get_closest_point(const Vector2 &p_point);
+
+ void set_auto_triangles(bool p_enable);
+ bool get_auto_triangles() const;
+
+ AnimationNodeBlendSpace();
+ ~AnimationNodeBlendSpace();
+};
+
+#endif // ANIMATION_BLEND_SPACE_H
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
new file mode 100644
index 0000000000..ced0cbbbba
--- /dev/null
+++ b/scene/animation/animation_blend_tree.cpp
@@ -0,0 +1,1112 @@
+#include "animation_blend_tree.h"
+#include "scene/scene_string_names.h"
+
+void AnimationNodeAnimation::set_animation(const StringName &p_name) {
+ animation = p_name;
+}
+
+StringName AnimationNodeAnimation::get_animation() const {
+ return animation;
+}
+
+float AnimationNodeAnimation::get_playback_time() const {
+ return time;
+}
+
+void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "animation") {
+ AnimationGraphPlayer *gp = get_graph_player();
+ if (gp && gp->has_node(gp->get_animation_player())) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+ if (ap) {
+ List<StringName> names;
+ ap->get_animation_list(&names);
+ String anims;
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (E != names.front()) {
+ anims += ",";
+ }
+ anims += String(E->get());
+ }
+ if (anims != String()) {
+ property.hint = PROPERTY_HINT_ENUM;
+ property.hint_string = anims;
+ }
+ }
+ }
+ }
+
+ AnimationRootNode::_validate_property(property);
+}
+
+float AnimationNodeAnimation::process(float p_time, bool p_seek) {
+
+ AnimationPlayer *ap = get_player();
+ ERR_FAIL_COND_V(!ap, 0);
+
+ Ref<Animation> anim = ap->get_animation(animation);
+ if (!anim.is_valid()) {
+
+ Ref<AnimationNodeBlendTree> tree = get_parent();
+ if (tree.is_valid()) {
+ String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this));
+ make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation));
+
+ } else {
+ make_invalid(vformat(RTR("Animation not found: '%s'"), animation));
+ }
+
+ return 0;
+ }
+
+ if (p_seek) {
+ time = p_time;
+ step = 0;
+ } else {
+ time = MAX(0, time + p_time);
+ step = p_time;
+ }
+
+ float anim_size = anim->get_length();
+
+ if (anim->has_loop()) {
+
+ if (anim_size) {
+ time = Math::fposmod(time, anim_size);
+ }
+
+ } else if (time > anim_size) {
+
+ time = anim_size;
+ }
+
+ blend_animation(animation, time, step, p_seek, 1.0);
+
+ return anim_size - time;
+}
+
+String AnimationNodeAnimation::get_caption() const {
+ return "Animation";
+}
+
+void AnimationNodeAnimation::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
+ ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
+
+ ClassDB::bind_method(D_METHOD("get_playback_time"), &AnimationNodeAnimation::get_playback_time);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation");
+}
+
+AnimationNodeAnimation::AnimationNodeAnimation() {
+ last_version = 0;
+ skip = false;
+ time = 0;
+ step = 0;
+}
+
+////////////////////////////////////////////////////////
+
+void AnimationNodeOneShot::set_fadein_time(float p_time) {
+
+ fade_in = p_time;
+}
+
+void AnimationNodeOneShot::set_fadeout_time(float p_time) {
+
+ fade_out = p_time;
+}
+
+float AnimationNodeOneShot::get_fadein_time() const {
+
+ return fade_in;
+}
+float AnimationNodeOneShot::get_fadeout_time() const {
+
+ return fade_out;
+}
+
+void AnimationNodeOneShot::set_autorestart(bool p_active) {
+
+ autorestart = p_active;
+}
+void AnimationNodeOneShot::set_autorestart_delay(float p_time) {
+
+ autorestart_delay = p_time;
+}
+void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) {
+
+ autorestart_random_delay = p_time;
+}
+
+bool AnimationNodeOneShot::has_autorestart() const {
+
+ return autorestart;
+}
+float AnimationNodeOneShot::get_autorestart_delay() const {
+
+ return autorestart_delay;
+}
+float AnimationNodeOneShot::get_autorestart_random_delay() const {
+
+ return autorestart_random_delay;
+}
+
+void AnimationNodeOneShot::set_mix_mode(MixMode p_mix) {
+
+ mix = p_mix;
+}
+AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const {
+
+ return mix;
+}
+
+void AnimationNodeOneShot::start() {
+ active = true;
+ do_start = true;
+}
+void AnimationNodeOneShot::stop() {
+ active = false;
+}
+bool AnimationNodeOneShot::is_active() const {
+
+ return active;
+}
+
+String AnimationNodeOneShot::get_caption() const {
+ return "OneShot";
+}
+
+bool AnimationNodeOneShot::has_filter() const {
+ return true;
+}
+
+float AnimationNodeOneShot::process(float p_time, bool p_seek) {
+
+ if (!active) {
+ //make it as if this node doesn't exist, pass input 0 by.
+ return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ }
+
+ bool os_seek = p_seek;
+
+ if (p_seek)
+ time = p_time;
+ if (do_start) {
+ time = 0;
+ os_seek = true;
+ }
+
+ float blend;
+
+ if (time < fade_in) {
+
+ if (fade_in > 0)
+ blend = time / fade_in;
+ else
+ blend = 0; //wtf
+
+ } else if (!do_start && remaining < fade_out) {
+
+ if (fade_out)
+ blend = (remaining / fade_out);
+ else
+ blend = 1.0;
+ } else
+ blend = 1.0;
+
+ float main_rem;
+ if (mix == MIX_MODE_ADD) {
+ main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ } else {
+ main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync);
+ }
+
+ float os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false);
+
+ if (do_start) {
+ remaining = os_rem;
+ do_start = false;
+ }
+
+ if (!p_seek) {
+ time += p_time;
+ remaining = os_rem;
+ if (remaining <= 0)
+ active = false;
+ }
+
+ return MAX(main_rem, remaining);
+}
+void AnimationNodeOneShot::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeOneShot::is_using_sync() const {
+
+ return sync;
+}
+
+void AnimationNodeOneShot::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_fadein_time", "time"), &AnimationNodeOneShot::set_fadein_time);
+ ClassDB::bind_method(D_METHOD("get_fadein_time"), &AnimationNodeOneShot::get_fadein_time);
+
+ ClassDB::bind_method(D_METHOD("set_fadeout_time", "time"), &AnimationNodeOneShot::set_fadeout_time);
+ ClassDB::bind_method(D_METHOD("get_fadeout_time"), &AnimationNodeOneShot::get_fadeout_time);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart", "enable"), &AnimationNodeOneShot::set_autorestart);
+ ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::has_autorestart);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart_delay", "enable"), &AnimationNodeOneShot::set_autorestart_delay);
+ ClassDB::bind_method(D_METHOD("get_autorestart_delay"), &AnimationNodeOneShot::get_autorestart_delay);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart_random_delay", "enable"), &AnimationNodeOneShot::set_autorestart_random_delay);
+ ClassDB::bind_method(D_METHOD("get_autorestart_random_delay"), &AnimationNodeOneShot::get_autorestart_random_delay);
+
+ ClassDB::bind_method(D_METHOD("set_mix_mode", "mode"), &AnimationNodeOneShot::set_mix_mode);
+ ClassDB::bind_method(D_METHOD("get_mix_mode"), &AnimationNodeOneShot::get_mix_mode);
+
+ ClassDB::bind_method(D_METHOD("start"), &AnimationNodeOneShot::start);
+ ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeOneShot::stop);
+ ClassDB::bind_method(D_METHOD("is_active"), &AnimationNodeOneShot::is_active);
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time");
+
+ ADD_GROUP("autorestart_", "Auto Restart");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart");
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_delay", "get_autorestart_delay");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_random_delay", "get_autorestart_random_delay");
+
+ ADD_GROUP("", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+
+ BIND_CONSTANT(MIX_MODE_BLEND)
+ BIND_CONSTANT(MIX_MODE_ADD)
+}
+
+AnimationNodeOneShot::AnimationNodeOneShot() {
+
+ add_input("in");
+ add_input("shot");
+
+ time = 0;
+ fade_in = 0.1;
+ fade_out = 0.1;
+ autorestart = false;
+ autorestart_delay = 1;
+ autorestart_remaining = 0;
+ mix = MIX_MODE_BLEND;
+ active = false;
+ do_start = false;
+ sync = false;
+}
+
+////////////////////////////////////////////////
+
+void AnimationNodeAdd::set_amount(float p_amount) {
+ amount = p_amount;
+}
+
+float AnimationNodeAdd::get_amount() const {
+ return amount;
+}
+
+String AnimationNodeAdd::get_caption() const {
+ return "Add";
+}
+void AnimationNodeAdd::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeAdd::is_using_sync() const {
+
+ return sync;
+}
+
+bool AnimationNodeAdd::has_filter() const {
+
+ return true;
+}
+
+float AnimationNodeAdd::process(float p_time, bool p_seek) {
+
+ float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync);
+
+ return rem0;
+}
+
+void AnimationNodeAdd::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd::set_amount);
+ ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd::get_amount);
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+
+AnimationNodeAdd::AnimationNodeAdd() {
+
+ add_input("in");
+ add_input("add");
+ amount = 0;
+ sync = false;
+}
+
+/////////////////////////////////////////////
+
+void AnimationNodeBlend2::set_amount(float p_amount) {
+ amount = p_amount;
+}
+
+float AnimationNodeBlend2::get_amount() const {
+ return amount;
+}
+String AnimationNodeBlend2::get_caption() const {
+ return "Blend2";
+}
+
+float AnimationNodeBlend2::process(float p_time, bool p_seek) {
+
+ float rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync);
+ float rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync);
+
+ return amount > 0.5 ? rem1 : rem0; //hacky but good enough
+}
+
+void AnimationNodeBlend2::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeBlend2::is_using_sync() const {
+
+ return sync;
+}
+
+bool AnimationNodeBlend2::has_filter() const {
+
+ return true;
+}
+void AnimationNodeBlend2::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend2::set_amount);
+ ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend2::get_amount);
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend2::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend2::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend2::AnimationNodeBlend2() {
+ add_input("in");
+ add_input("blend");
+ sync = false;
+
+ amount = 0;
+}
+
+//////////////////////////////////////
+
+void AnimationNodeBlend3::set_amount(float p_amount) {
+ amount = p_amount;
+}
+
+float AnimationNodeBlend3::get_amount() const {
+ return amount;
+}
+
+String AnimationNodeBlend3::get_caption() const {
+ return "Blend3";
+}
+
+void AnimationNodeBlend3::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeBlend3::is_using_sync() const {
+
+ return sync;
+}
+
+float AnimationNodeBlend3::process(float p_time, bool p_seek) {
+
+ float rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync);
+ float rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync);
+ float rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync);
+
+ return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough
+}
+
+void AnimationNodeBlend3::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend3::set_amount);
+ ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend3::get_amount);
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend3::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend3::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend3::AnimationNodeBlend3() {
+ add_input("-blend");
+ add_input("in");
+ add_input("+blend");
+ sync = false;
+ amount = 0;
+}
+
+/////////////////////////////////
+
+void AnimationNodeTimeScale::set_scale(float p_scale) {
+ scale = p_scale;
+}
+
+float AnimationNodeTimeScale::get_scale() const {
+ return scale;
+}
+
+String AnimationNodeTimeScale::get_caption() const {
+ return "TimeScale";
+}
+
+float AnimationNodeTimeScale::process(float p_time, bool p_seek) {
+
+ if (p_seek) {
+ return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false);
+ } else {
+ return blend_input(0, p_time * scale, false, 1.0, FILTER_IGNORE, false);
+ }
+}
+
+void AnimationNodeTimeScale::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_scale", "scale"), &AnimationNodeTimeScale::set_scale);
+ ClassDB::bind_method(D_METHOD("get_scale"), &AnimationNodeTimeScale::get_scale);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"), "set_scale", "get_scale");
+}
+AnimationNodeTimeScale::AnimationNodeTimeScale() {
+ add_input("in");
+ scale = 1.0;
+}
+
+////////////////////////////////////
+
+void AnimationNodeTimeSeek::set_seek_pos(float p_seek_pos) {
+ seek_pos = p_seek_pos;
+}
+
+float AnimationNodeTimeSeek::get_seek_pos() const {
+ return seek_pos;
+}
+
+String AnimationNodeTimeSeek::get_caption() const {
+ return "Seek";
+}
+
+float AnimationNodeTimeSeek::process(float p_time, bool p_seek) {
+
+ if (p_seek) {
+ return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false);
+ } else if (seek_pos >= 0) {
+ float ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false);
+ seek_pos = -1;
+ _change_notify("seek_pos");
+ return ret;
+ } else {
+ return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false);
+ }
+}
+
+void AnimationNodeTimeSeek::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_seek_pos", "seek_pos"), &AnimationNodeTimeSeek::set_seek_pos);
+ ClassDB::bind_method(D_METHOD("get_seek_pos"), &AnimationNodeTimeSeek::get_seek_pos);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "seek_pos", PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater"), "set_seek_pos", "get_seek_pos");
+}
+AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
+ add_input("in");
+ seek_pos = -1;
+}
+
+/////////////////////////////////////////////////
+
+String AnimationNodeTransition::get_caption() const {
+ return "Transition";
+}
+
+void AnimationNodeTransition::_update_inputs() {
+ while (get_input_count() < enabled_inputs) {
+ add_input(inputs[get_input_count()].name);
+ }
+
+ while (get_input_count() > enabled_inputs) {
+ remove_input(get_input_count() - 1);
+ }
+}
+
+void AnimationNodeTransition::set_enabled_inputs(int p_inputs) {
+ ERR_FAIL_INDEX(p_inputs, MAX_INPUTS);
+ enabled_inputs = p_inputs;
+ _update_inputs();
+}
+
+int AnimationNodeTransition::get_enabled_inputs() {
+ return enabled_inputs;
+}
+
+void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) {
+ ERR_FAIL_INDEX(p_input, MAX_INPUTS);
+ inputs[p_input].auto_advance = p_enable;
+}
+
+bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false);
+ return inputs[p_input].auto_advance;
+}
+
+void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) {
+ ERR_FAIL_INDEX(p_input, MAX_INPUTS);
+ inputs[p_input].name = p_name;
+ set_input_name(p_input, p_name);
+}
+
+String AnimationNodeTransition::get_input_caption(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String());
+ return inputs[p_input].name;
+}
+
+void AnimationNodeTransition::set_current(int p_current) {
+
+ if (current == p_current)
+ return;
+ ERR_FAIL_INDEX(p_current, enabled_inputs);
+
+ Ref<AnimationNodeBlendTree> tree = get_parent();
+
+ if (tree.is_valid() && current >= 0) {
+ prev = current;
+ prev_xfading = xfade;
+ prev_time = time;
+ time = 0;
+ current = p_current;
+ switched = true;
+ _change_notify("current");
+ } else {
+ current = p_current;
+ }
+}
+
+int AnimationNodeTransition::get_current() const {
+ return current;
+}
+void AnimationNodeTransition::set_cross_fade_time(float p_fade) {
+ xfade = p_fade;
+}
+
+float AnimationNodeTransition::get_cross_fade_time() const {
+ return xfade;
+}
+
+float AnimationNodeTransition::process(float p_time, bool p_seek) {
+
+ if (prev < 0) { // process current animation, check for transition
+
+ float rem = blend_input(current, p_time, p_seek, 1.0, FILTER_IGNORE, false);
+
+ if (p_seek)
+ time = p_time;
+ else
+ time += p_time;
+
+ if (inputs[current].auto_advance && rem <= xfade) {
+
+ set_current((current + 1) % enabled_inputs);
+ }
+
+ return rem;
+ } else { // cross-fading from prev to current
+
+ float blend = xfade ? (prev_xfading / xfade) : 1;
+
+ float rem;
+
+ if (!p_seek && switched) { //just switched, seek to start of current
+
+ rem = blend_input(current, 0, true, 1.0 - blend, FILTER_IGNORE, false);
+ } else {
+
+ rem = blend_input(current, p_time, p_seek, 1.0 - blend, FILTER_IGNORE, false);
+ }
+
+ switched = false;
+
+ if (p_seek) { // don't seek prev animation
+ blend_input(prev, 0, false, blend, FILTER_IGNORE, false);
+ time = p_time;
+ } else {
+ blend_input(prev, p_time, false, blend, FILTER_IGNORE, false);
+ time += p_time;
+ prev_xfading -= p_time;
+ if (prev_xfading < 0) {
+ prev = -1;
+ }
+ }
+
+ return rem;
+ }
+}
+
+void AnimationNodeTransition::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "current" && enabled_inputs > 0) {
+ property.hint = PROPERTY_HINT_ENUM;
+ String anims;
+ for (int i = 0; i < enabled_inputs; i++) {
+ if (i > 0) {
+ anims += ",";
+ }
+ anims += inputs[i].name;
+ }
+ property.hint_string = anims;
+ }
+
+ if (property.name.begins_with("input_")) {
+ String n = property.name.get_slicec('/', 0).get_slicec('_', 1);
+ if (n != "count") {
+ int idx = n.to_int();
+ if (idx >= enabled_inputs) {
+ property.usage = 0;
+ }
+ }
+ }
+
+ AnimationNode::_validate_property(property);
+}
+
+void AnimationNodeTransition::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs);
+ ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs);
+
+ ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance);
+ ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance);
+
+ ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption);
+ ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption);
+
+ ClassDB::bind_method(D_METHOD("set_current", "index"), &AnimationNodeTransition::set_current);
+ ClassDB::bind_method(D_METHOD("get_current"), &AnimationNodeTransition::get_current);
+
+ ClassDB::bind_method(D_METHOD("set_cross_fade_time", "time"), &AnimationNodeTransition::set_cross_fade_time);
+ ClassDB::bind_method(D_METHOD("get_cross_fade_time"), &AnimationNodeTransition::get_cross_fade_time);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current", PROPERTY_HINT_RANGE, "0,64,1"), "set_current", "get_current");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01"), "set_cross_fade_time", "get_cross_fade_time");
+
+ for (int i = 0; i < MAX_INPUTS; i++) {
+ ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name"), "set_input_caption", "get_input_caption", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance"), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i);
+ }
+}
+
+AnimationNodeTransition::AnimationNodeTransition() {
+ enabled_inputs = 0;
+ xfade = 0;
+ current = -1;
+ prev = -1;
+ prev_time = 0;
+ prev_xfading = 0;
+ switched = false;
+ for (int i = 0; i < MAX_INPUTS; i++) {
+ inputs[i].auto_advance = false;
+ inputs[i].name = itos(i + 1);
+ }
+}
+
+/////////////////////
+
+String AnimationNodeOutput::get_caption() const {
+ return "Output";
+}
+
+float AnimationNodeOutput::process(float p_time, bool p_seek) {
+ return blend_input(0, p_time, p_seek, 1.0);
+}
+
+AnimationNodeOutput::AnimationNodeOutput() {
+ add_input("output");
+}
+
+///////////////////////////////////////////////////////
+void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node) {
+
+ ERR_FAIL_COND(nodes.has(p_name));
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(p_node->get_parent().is_valid());
+ ERR_FAIL_COND(p_node->get_graph_player() != NULL);
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(String(p_name).find("/") != -1);
+ nodes[p_name] = p_node;
+
+ p_node->set_parent(this);
+ p_node->set_graph_player(get_graph_player());
+
+ emit_changed();
+}
+
+Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!nodes.has(p_name), Ref<AnimationNode>());
+
+ return nodes[p_name];
+}
+
+StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const {
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ if (E->get() == p_node) {
+ return E->key();
+ }
+ }
+
+ ERR_FAIL_V(StringName());
+}
+bool AnimationNodeBlendTree::has_node(const StringName &p_name) const {
+ return nodes.has(p_name);
+}
+void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
+
+ ERR_FAIL_COND(!nodes.has(p_name));
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output
+
+ {
+ //erase node connections
+ Ref<AnimationNode> node = nodes[p_name];
+ for (int i = 0; i < node->get_input_count(); i++) {
+ node->set_input_connection(i, StringName());
+ }
+ node->set_parent(NULL);
+ node->set_graph_player(NULL);
+ }
+
+ nodes.erase(p_name);
+
+ //erase connections to name
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ for (int i = 0; i < node->get_input_count(); i++) {
+ if (node->get_input_connection(i) == p_name) {
+ node->set_input_connection(i, StringName());
+ }
+ }
+ }
+
+ emit_changed();
+}
+
+void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) {
+
+ ERR_FAIL_COND(!nodes.has(p_name));
+ ERR_FAIL_COND(nodes.has(p_new_name));
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output);
+
+ nodes[p_new_name] = nodes[p_name];
+ nodes.erase(p_name);
+
+ //rename connections
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ for (int i = 0; i < node->get_input_count(); i++) {
+ if (node->get_input_connection(i) == p_name) {
+ node->set_input_connection(i, p_new_name);
+ }
+ }
+ }
+}
+
+void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) {
+
+ ERR_FAIL_COND(!nodes.has(p_output_node));
+ ERR_FAIL_COND(!nodes.has(p_input_node));
+ ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_input_node == p_output_node);
+
+ Ref<AnimationNode> input = nodes[p_input_node];
+ ERR_FAIL_INDEX(p_input_index, input->get_input_count());
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ for (int i = 0; i < node->get_input_count(); i++) {
+ StringName output = node->get_input_connection(i);
+ ERR_FAIL_COND(output == p_output_node);
+ }
+ }
+
+ input->set_input_connection(p_input_index, p_output_node);
+ emit_changed();
+}
+
+void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_input_index) {
+
+ ERR_FAIL_COND(!nodes.has(p_node));
+
+ Ref<AnimationNode> input = nodes[p_node];
+ ERR_FAIL_INDEX(p_input_index, input->get_input_count());
+
+ input->set_input_connection(p_input_index, StringName());
+}
+
+float AnimationNodeBlendTree::get_connection_activity(const StringName &p_input_node, int p_input_index) const {
+
+ ERR_FAIL_COND_V(!nodes.has(p_input_node), 0);
+
+ Ref<AnimationNode> input = nodes[p_input_node];
+ ERR_FAIL_INDEX_V(p_input_index, input->get_input_count(), 0);
+
+ return input->get_input_activity(p_input_index);
+}
+
+AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const {
+
+ if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) {
+ return CONNECTION_ERROR_NO_OUTPUT;
+ }
+
+ if (!nodes.has(p_input_node)) {
+ return CONNECTION_ERROR_NO_INPUT;
+ }
+
+ if (!nodes.has(p_input_node)) {
+ return CONNECTION_ERROR_SAME_NODE;
+ }
+
+ Ref<AnimationNode> input = nodes[p_input_node];
+
+ if (p_input_index < 0 || p_input_index >= input->get_input_count()) {
+ return CONNECTION_ERROR_NO_INPUT_INDEX;
+ }
+
+ if (input->get_input_connection(p_input_index) != StringName()) {
+ return CONNECTION_ERROR_CONNECTION_EXISTS;
+ }
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ for (int i = 0; i < node->get_input_count(); i++) {
+ StringName output = node->get_input_connection(i);
+ if (output == p_output_node) {
+ return CONNECTION_ERROR_CONNECTION_EXISTS;
+ }
+ }
+ }
+ return CONNECTION_OK;
+}
+
+void AnimationNodeBlendTree::get_node_connections(List<NodeConnection> *r_connections) const {
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ for (int i = 0; i < node->get_input_count(); i++) {
+ StringName output = node->get_input_connection(i);
+ if (output != StringName()) {
+ NodeConnection nc;
+ nc.input_node = E->key();
+ nc.input_index = i;
+ nc.output_node = output;
+ r_connections->push_back(nc);
+ }
+ }
+ }
+}
+
+String AnimationNodeBlendTree::get_caption() const {
+ return "BlendTree";
+}
+
+float AnimationNodeBlendTree::process(float p_time, bool p_seek) {
+
+ Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output];
+ return blend_node(output, p_time, p_seek, 1.0);
+}
+
+void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) {
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ r_list->push_back(E->key());
+ }
+}
+
+void AnimationNodeBlendTree::set_graph_offset(const Vector2 &p_graph_offset) {
+
+ graph_offset = p_graph_offset;
+}
+
+Vector2 AnimationNodeBlendTree::get_graph_offset() const {
+
+ return graph_offset;
+}
+
+void AnimationNodeBlendTree::set_graph_player(AnimationGraphPlayer *p_player) {
+
+ AnimationNode::set_graph_player(p_player);
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ Ref<AnimationNode> node = E->get();
+ node->set_graph_player(p_player);
+ }
+}
+
+bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) {
+
+ String name = p_name;
+ if (name.begins_with("nodes/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ Ref<AnimationNode> anode = p_value;
+ if (anode.is_valid()) {
+ add_node(node_name, p_value);
+ }
+ return true;
+ }
+
+ if (what == "position") {
+
+ if (nodes.has(node_name)) {
+ nodes[node_name]->set_position(p_value);
+ }
+ return true;
+ }
+ } else if (name == "node_connections") {
+
+ Array conns = p_value;
+ ERR_FAIL_COND_V(conns.size() % 3 != 0, false);
+
+ for (int i = 0; i < conns.size(); i += 3) {
+ connect_node(conns[i], conns[i + 1], conns[i + 2]);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) const {
+
+ String name = p_name;
+ if (name.begins_with("nodes/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ if (nodes.has(node_name)) {
+ r_ret = nodes[node_name];
+ return true;
+ }
+ }
+
+ if (what == "position") {
+
+ if (nodes.has(node_name)) {
+ r_ret = nodes[node_name]->get_position();
+ return true;
+ }
+ }
+ } else if (name == "node_connections") {
+ List<NodeConnection> nc;
+ get_node_connections(&nc);
+ Array conns;
+ conns.resize(nc.size() * 3);
+
+ int idx = 0;
+ for (List<NodeConnection>::Element *E = nc.front(); E; E = E->next()) {
+ conns[idx * 3 + 0] = E->get().input_node;
+ conns[idx * 3 + 1] = E->get().input_index;
+ conns[idx * 3 + 2] = E->get().output_node;
+ idx++;
+ }
+
+ r_ret = conns;
+ return true;
+ }
+
+ return false;
+}
+void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
+
+ List<StringName> names;
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ names.push_back(E->key());
+ }
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ String name = E->get();
+ if (name != "output") {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+ }
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ }
+
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+}
+
+void AnimationNodeBlendTree::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeBlendTree::add_node);
+ ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeBlendTree::get_node);
+ ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeBlendTree::remove_node);
+ ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeBlendTree::rename_node);
+ ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeBlendTree::has_node);
+ ClassDB::bind_method(D_METHOD("connect_node", "input_node", "input_index", "output_node"), &AnimationNodeBlendTree::connect_node);
+ ClassDB::bind_method(D_METHOD("disconnect_node", "input_node", "input_index"), &AnimationNodeBlendTree::disconnect_node);
+
+ ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset);
+ ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset);
+
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset");
+
+ BIND_CONSTANT(CONNECTION_OK);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT_INDEX);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT);
+ BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE);
+ BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS);
+}
+
+AnimationNodeBlendTree::AnimationNodeBlendTree() {
+
+ Ref<AnimationNodeOutput> output;
+ output.instance();
+ output->set_position(Vector2(300, 150));
+ output->set_parent(this);
+ nodes["output"] = output;
+}
+
+AnimationNodeBlendTree::~AnimationNodeBlendTree() {
+
+ for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+ E->get()->set_parent(NULL);
+ E->get()->set_graph_player(NULL);
+ }
+}
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
new file mode 100644
index 0000000000..7623fd3e57
--- /dev/null
+++ b/scene/animation/animation_blend_tree.h
@@ -0,0 +1,328 @@
+#ifndef ANIMATION_BLEND_TREE_H
+#define ANIMATION_BLEND_TREE_H
+
+#include "scene/animation/animation_graph_player.h"
+
+class AnimationNodeAnimation : public AnimationRootNode {
+
+ GDCLASS(AnimationNodeAnimation, AnimationRootNode);
+
+ StringName animation;
+
+ uint64_t last_version;
+ float time;
+ float step;
+ bool skip;
+
+protected:
+ void _validate_property(PropertyInfo &property) const;
+
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+
+ void set_animation(const StringName &p_name);
+ StringName get_animation() const;
+
+ float get_playback_time() const;
+
+ AnimationNodeAnimation();
+};
+
+class AnimationNodeOneShot : public AnimationNode {
+ GDCLASS(AnimationNodeOneShot, AnimationNode);
+
+public:
+ enum MixMode {
+ MIX_MODE_BLEND,
+ MIX_MODE_ADD
+ };
+
+private:
+ bool active;
+ bool do_start;
+ float fade_in;
+ float fade_out;
+
+ bool autorestart;
+ float autorestart_delay;
+ float autorestart_random_delay;
+ MixMode mix;
+
+ float time;
+ float remaining;
+ float autorestart_remaining;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+
+ void set_fadein_time(float p_time);
+ void set_fadeout_time(float p_time);
+
+ float get_fadein_time() const;
+ float get_fadeout_time() const;
+
+ void set_autorestart(bool p_active);
+ void set_autorestart_delay(float p_time);
+ void set_autorestart_random_delay(float p_time);
+
+ bool has_autorestart() const;
+ float get_autorestart_delay() const;
+ float get_autorestart_random_delay() const;
+
+ void set_mix_mode(MixMode p_mix);
+ MixMode get_mix_mode() const;
+
+ void start();
+ void stop();
+ bool is_active() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ virtual float process(float p_time, bool p_seek);
+
+ AnimationNodeOneShot();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode)
+
+class AnimationNodeAdd : public AnimationNode {
+ GDCLASS(AnimationNodeAdd, AnimationNode);
+
+ float amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+
+ void set_amount(float p_amount);
+ float get_amount() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ virtual float process(float p_time, bool p_seek);
+
+ AnimationNodeAdd();
+};
+
+class AnimationNodeBlend2 : public AnimationNode {
+ GDCLASS(AnimationNodeBlend2, AnimationNode);
+
+ float amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+
+ void set_amount(float p_amount);
+ float get_amount() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ AnimationNodeBlend2();
+};
+
+class AnimationNodeBlend3 : public AnimationNode {
+ GDCLASS(AnimationNodeBlend3, AnimationNode);
+
+ float amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+
+ void set_amount(float p_amount);
+ float get_amount() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ float process(float p_time, bool p_seek);
+ AnimationNodeBlend3();
+};
+
+class AnimationNodeTimeScale : public AnimationNode {
+ GDCLASS(AnimationNodeTimeScale, AnimationNode);
+
+ float scale;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+
+ void set_scale(float p_scale);
+ float get_scale() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTimeScale();
+};
+
+class AnimationNodeTimeSeek : public AnimationNode {
+ GDCLASS(AnimationNodeTimeSeek, AnimationNode);
+
+ float seek_pos;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const;
+
+ void set_seek_pos(float p_sec);
+ float get_seek_pos() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTimeSeek();
+};
+
+class AnimationNodeTransition : public AnimationNode {
+ GDCLASS(AnimationNodeTransition, AnimationNode);
+
+ enum {
+ MAX_INPUTS = 32
+ };
+ struct InputData {
+
+ String name;
+ bool auto_advance;
+ InputData() { auto_advance = false; }
+ };
+
+ InputData inputs[MAX_INPUTS];
+ int enabled_inputs;
+
+ float prev_time;
+ float prev_xfading;
+ int prev;
+ bool switched;
+
+ float time;
+ int current;
+
+ float xfade;
+
+ void _update_inputs();
+
+protected:
+ static void _bind_methods();
+ void _validate_property(PropertyInfo &property) const;
+
+public:
+ virtual String get_caption() const;
+
+ void set_enabled_inputs(int p_inputs);
+ int get_enabled_inputs();
+
+ void set_input_as_auto_advance(int p_input, bool p_enable);
+ bool is_input_set_as_auto_advance(int p_input) const;
+
+ void set_input_caption(int p_input, const String &p_name);
+ String get_input_caption(int p_input) const;
+
+ void set_current(int p_current);
+ int get_current() const;
+
+ void set_cross_fade_time(float p_fade);
+ float get_cross_fade_time() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTransition();
+};
+
+class AnimationNodeOutput : public AnimationNode {
+ GDCLASS(AnimationNodeOutput, AnimationNode)
+public:
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+ AnimationNodeOutput();
+};
+
+/////
+
+class AnimationNodeBlendTree : public AnimationRootNode {
+ GDCLASS(AnimationNodeBlendTree, AnimationRootNode)
+
+ Map<StringName, Ref<AnimationNode> > nodes;
+
+ Vector2 graph_offset;
+
+protected:
+ static void _bind_methods();
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+public:
+ enum ConnectionError {
+ CONNECTION_OK,
+ CONNECTION_ERROR_NO_INPUT,
+ CONNECTION_ERROR_NO_INPUT_INDEX,
+ CONNECTION_ERROR_NO_OUTPUT,
+ CONNECTION_ERROR_SAME_NODE,
+ CONNECTION_ERROR_CONNECTION_EXISTS,
+ //no need to check for cycles due to tree topology
+ };
+
+ void add_node(const StringName &p_name, Ref<AnimationNode> p_node);
+ Ref<AnimationNode> get_node(const StringName &p_name) const;
+ void remove_node(const StringName &p_name);
+ void rename_node(const StringName &p_name, const StringName &p_new_name);
+ bool has_node(const StringName &p_name) const;
+ StringName get_node_name(const Ref<AnimationNode> &p_node) const;
+
+ void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node);
+ void disconnect_node(const StringName &p_node, int p_input_index);
+ float get_connection_activity(const StringName &p_input_node, int p_input_index) const;
+
+ struct NodeConnection {
+ StringName input_node;
+ int input_index;
+ StringName output_node;
+ };
+
+ ConnectionError can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const;
+ void get_node_connections(List<NodeConnection> *r_connections) const;
+
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+
+ void get_node_list(List<StringName> *r_list);
+
+ void set_graph_offset(const Vector2 &p_graph_offset);
+ Vector2 get_graph_offset() const;
+
+ virtual void set_graph_player(AnimationGraphPlayer *p_player);
+ AnimationNodeBlendTree();
+ ~AnimationNodeBlendTree();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError)
+
+#endif // ANIMATION_BLEND_TREE_H
diff --git a/scene/animation/animation_graph_player.cpp b/scene/animation/animation_graph_player.cpp
new file mode 100644
index 0000000000..b8efecebe9
--- /dev/null
+++ b/scene/animation/animation_graph_player.cpp
@@ -0,0 +1,1211 @@
+#include "animation_graph_player.h"
+#include "animation_blend_tree.h"
+#include "core/method_bind_ext.gen.inc"
+#include "engine.h"
+#include "scene/scene_string_names.h"
+#include "servers/audio/audio_stream.h"
+
+void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) {
+
+ ERR_FAIL_COND(!state);
+ ERR_FAIL_COND(!state->player->has_animation(p_animation));
+
+ Ref<Animation> animation = state->player->get_animation(p_animation);
+
+ if (animation.is_null()) {
+
+ Ref<AnimationNodeBlendTree> btree = get_parent();
+ if (btree.is_valid()) {
+ String name = btree->get_node_name(Ref<AnimationNodeAnimation>(this));
+ make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, p_animation));
+ } else {
+ make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation));
+ }
+ return;
+ }
+
+ ERR_FAIL_COND(!animation.is_valid());
+
+ AnimationState anim_state;
+ anim_state.blend = p_blend;
+ anim_state.track_blends = &blends;
+ anim_state.delta = p_delta;
+ anim_state.time = p_time;
+ anim_state.animation = animation;
+ anim_state.seeked = p_seeked;
+
+ state->animation_states.push_back(anim_state);
+}
+
+float AnimationNode::_pre_process(State *p_state, float p_time, bool p_seek) {
+ state = p_state;
+ float t = process(p_time, p_seek);
+ state = NULL;
+ return t;
+}
+
+void AnimationNode::make_invalid(const String &p_reason) {
+ ERR_FAIL_COND(!state);
+ state->valid = false;
+ if (state->invalid_reasons != String()) {
+ state->invalid_reasons += "\n";
+ }
+ state->invalid_reasons += "- " + p_reason;
+}
+
+float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
+ ERR_FAIL_COND_V(!state, 0);
+ ERR_FAIL_COND_V(!get_graph_player(), 0); //should not happen, but used to catch bugs
+
+ Ref<AnimationNodeBlendTree> tree = get_parent();
+
+ if (!tree.is_valid() && get_graph_player()->get_graph_root().ptr() != this) {
+ make_invalid(RTR("Can't blend input because node is not in a tree"));
+ return 0;
+ }
+
+ ERR_FAIL_COND_V(!tree.is_valid(), 0); //should not happen
+
+ StringName anim_name = inputs[p_input].connected_to;
+
+ Ref<AnimationNode> node = tree->get_node(anim_name);
+
+ if (node.is_null()) {
+
+ String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this));
+ make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name));
+ return 0;
+ }
+
+ inputs[p_input].last_pass = state->last_pass;
+
+ return _blend_node(node, p_time, p_seek, p_blend, p_filter, p_optimize, &inputs[p_input].activity);
+}
+
+float AnimationNode::blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
+
+ return _blend_node(p_node, p_time, p_seek, p_blend, p_filter, p_optimize);
+}
+
+float AnimationNode::_blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) {
+
+ ERR_FAIL_COND_V(!p_node.is_valid(), 0);
+ ERR_FAIL_COND_V(!state, 0);
+
+ int blend_count = blends.size();
+
+ if (p_node->blends.size() != blend_count) {
+ p_node->blends.resize(blend_count);
+ }
+
+ float *blendw = p_node->blends.ptrw();
+ const float *blendr = blends.ptr();
+
+ bool any_valid = false;
+
+ if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) {
+
+ for (int i = 0; i < blend_count; i++) {
+ blendw[i] = 0.0; //all to zero by default
+ }
+
+ const NodePath *K = NULL;
+ while ((K = filter.next(K))) {
+ if (!state->track_map.has(*K)) {
+ continue;
+ }
+ int idx = state->track_map[*K];
+ blendw[idx] = 1.0; //filtered goes to one
+ }
+
+ switch (p_filter) {
+ case FILTER_IGNORE:
+ break; //will not happen anyway
+ case FILTER_PASS: {
+ //values filtered pass, the rest dont
+ for (int i = 0; i < blend_count; i++) {
+ if (blendw[i] == 0) //not filtered, does not pass
+ continue;
+
+ blendw[i] = blendr[i] * p_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+
+ } break;
+ case FILTER_STOP: {
+
+ //values filtered dont pass, the rest are blended
+
+ for (int i = 0; i < blend_count; i++) {
+ if (blendw[i] > 0) //filtered, does not pass
+ continue;
+
+ blendw[i] = blendr[i] * p_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+
+ } break;
+ case FILTER_BLEND: {
+
+ //filtered values are blended, the rest are passed without blending
+
+ for (int i = 0; i < blend_count; i++) {
+ if (blendw[i] == 1.0) {
+ blendw[i] = blendr[i] * p_blend; //filtered, blend
+ } else {
+ blendw[i] = blendr[i]; //not filtered, do not blend
+ }
+
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+
+ } break;
+ }
+ } else {
+ for (int i = 0; i < blend_count; i++) {
+
+ //regular blend
+ blendw[i] = blendr[i] * p_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+ }
+
+ if (r_max) {
+ *r_max = 0;
+ for (int i = 0; i < blend_count; i++) {
+ *r_max = MAX(*r_max, blendw[i]);
+ }
+ }
+
+ if (!p_seek && p_optimize && !any_valid) //pointless to go on, all are zero
+ return 0;
+
+ return p_node->_pre_process(state, p_time, p_seek);
+}
+
+int AnimationNode::get_input_count() const {
+
+ return inputs.size();
+}
+String AnimationNode::get_input_name(int p_input) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), String());
+ return inputs[p_input].name;
+}
+
+float AnimationNode::get_input_activity(int p_input) const {
+
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
+ if (!get_graph_player())
+ return 0;
+
+ if (get_graph_player()->get_last_process_pass() != inputs[p_input].last_pass) {
+ return 0;
+ }
+ return inputs[p_input].activity;
+}
+StringName AnimationNode::get_input_connection(int p_input) {
+
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), StringName());
+ return inputs[p_input].connected_to;
+}
+
+void AnimationNode::set_input_connection(int p_input, const StringName &p_connection) {
+
+ ERR_FAIL_INDEX(p_input, inputs.size());
+ inputs[p_input].connected_to = p_connection;
+}
+
+String AnimationNode::get_caption() const {
+ return "Node";
+}
+
+void AnimationNode::add_input(const String &p_name) {
+ //root nodes cant add inputs
+ 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;
+ input.activity = 0;
+ input.last_pass = 0;
+ inputs.push_back(input);
+ emit_changed();
+}
+
+void AnimationNode::set_input_name(int p_input, const String &p_name) {
+ ERR_FAIL_INDEX(p_input, inputs.size());
+ ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+ inputs[p_input].name = p_name;
+ emit_changed();
+}
+
+void AnimationNode::remove_input(int p_index) {
+ ERR_FAIL_INDEX(p_index, inputs.size());
+ inputs.remove(p_index);
+ emit_changed();
+}
+
+void AnimationNode::set_parent(AnimationNode *p_parent) {
+ parent = p_parent; //do not use ref because parent contains children
+}
+
+Ref<AnimationNode> AnimationNode::get_parent() const {
+ if (parent) {
+ return Ref<AnimationNode>(parent);
+ }
+
+ return Ref<AnimationNode>();
+}
+
+AnimationGraphPlayer *AnimationNode::get_graph_player() const {
+
+ return player;
+}
+
+AnimationPlayer *AnimationNode::get_player() const {
+ ERR_FAIL_COND_V(!state, NULL);
+ return state->player;
+}
+
+float AnimationNode::process(float p_time, bool p_seek) {
+
+ if (get_script_instance()) {
+ return get_script_instance()->call("process", p_time, p_seek);
+ }
+
+ return 0;
+}
+
+void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) {
+ if (p_enable) {
+ filter[p_path] = true;
+ } else {
+ filter.erase(p_path);
+ }
+}
+
+void AnimationNode::set_filter_enabled(bool p_enable) {
+ filter_enabled = p_enable;
+}
+
+bool AnimationNode::is_filter_enabled() const {
+ return filter_enabled;
+}
+
+bool AnimationNode::is_path_filtered(const NodePath &p_path) const {
+ return filter.has(p_path);
+}
+
+bool AnimationNode::has_filter() const {
+ return false;
+}
+
+void AnimationNode::set_position(const Vector2 &p_position) {
+ position = p_position;
+}
+
+Vector2 AnimationNode::get_position() const {
+ return position;
+}
+
+void AnimationNode::set_graph_player(AnimationGraphPlayer *p_player) {
+
+ if (player != NULL && p_player == NULL) {
+ emit_signal("removed_from_graph");
+ }
+ player = p_player;
+}
+
+Array AnimationNode::_get_filters() const {
+
+ Array paths;
+
+ const NodePath *K = NULL;
+ while ((K = filter.next(K))) {
+ paths.push_back(String(*K)); //use strings, so sorting is possible
+ }
+ paths.sort(); //done so every time the scene is saved, it does not change
+
+ return paths;
+}
+void AnimationNode::_set_filters(const Array &p_filters) {
+ filter.clear();
+ for (int i = 0; i < p_filters.size(); i++) {
+ set_filter_path(p_filters[i], true);
+ }
+}
+
+void AnimationNode::_validate_property(PropertyInfo &property) const {
+ if (!has_filter() && (property.name == "filter_enabled" || property.name == "filters")) {
+ property.usage = 0;
+ }
+}
+
+void AnimationNode::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count);
+ ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name);
+ ClassDB::bind_method(D_METHOD("get_input_connection", "input"), &AnimationNode::get_input_connection);
+ ClassDB::bind_method(D_METHOD("get_input_activity", "input"), &AnimationNode::get_input_activity);
+
+ ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input);
+ ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input);
+
+ ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path);
+ ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered);
+
+ ClassDB::bind_method(D_METHOD("set_filter_enabled", "enable"), &AnimationNode::set_filter_enabled);
+ ClassDB::bind_method(D_METHOD("is_filter_enabled"), &AnimationNode::is_filter_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_position", "position"), &AnimationNode::set_position);
+ ClassDB::bind_method(D_METHOD("get_position"), &AnimationNode::get_position);
+
+ ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters);
+ ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
+
+ ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation);
+ ClassDB::bind_method(D_METHOD("blend_node", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters");
+
+ BIND_VMETHOD(MethodInfo("process", PropertyInfo(Variant::REAL, "time"), PropertyInfo(Variant::BOOL, "seek")));
+
+ ADD_SIGNAL(MethodInfo("removed_from_graph"));
+ BIND_ENUM_CONSTANT(FILTER_IGNORE);
+ BIND_ENUM_CONSTANT(FILTER_PASS);
+ BIND_ENUM_CONSTANT(FILTER_STOP);
+ BIND_ENUM_CONSTANT(FILTER_BLEND);
+}
+
+AnimationNode::AnimationNode() {
+
+ state = NULL;
+ parent = NULL;
+ player = NULL;
+ set_local_to_scene(true);
+ filter_enabled = false;
+}
+
+////////////////////
+
+void AnimationGraphPlayer::set_graph_root(const Ref<AnimationNode> &p_root) {
+
+ if (root.is_valid()) {
+ root->set_graph_player(NULL);
+ }
+ if (p_root.is_valid()) {
+ ERR_EXPLAIN("root node already set to another player");
+ ERR_FAIL_COND(p_root->player);
+ }
+ root = p_root;
+
+ if (root.is_valid()) {
+ root->set_graph_player(this);
+ }
+
+ update_configuration_warning();
+}
+
+Ref<AnimationNode> AnimationGraphPlayer::get_graph_root() const {
+ return root;
+}
+
+void AnimationGraphPlayer::set_active(bool p_active) {
+
+ if (active == p_active)
+ return;
+
+ active = p_active;
+ started = active;
+
+ if (process_mode == ANIMATION_PROCESS_IDLE) {
+ set_process_internal(active);
+ } else {
+
+ set_physics_process_internal(active);
+ }
+
+ if (!active && is_inside_tree()) {
+ for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) {
+
+ if (ObjectDB::get_instance(E->get()->object_id)) {
+ E->get()->object->call("stop");
+ }
+ }
+
+ playing_caches.clear();
+ }
+}
+
+bool AnimationGraphPlayer::is_active() const {
+
+ return active;
+}
+
+void AnimationGraphPlayer::set_process_mode(AnimationProcessMode p_mode) {
+
+ if (process_mode == p_mode)
+ return;
+
+ bool was_active = is_active();
+ if (was_active) {
+ set_active(false);
+ }
+
+ process_mode = p_mode;
+
+ if (was_active) {
+ set_active(true);
+ }
+}
+
+AnimationGraphPlayer::AnimationProcessMode AnimationGraphPlayer::get_process_mode() const {
+ return process_mode;
+}
+
+void AnimationGraphPlayer::_node_removed(Node *p_node) {
+ cache_valid = false;
+}
+
+bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
+
+ setup_pass++;
+
+ if (!player->has_node(player->get_root())) {
+ ERR_PRINT("AnimationGraphPlayer: AnimationPlayer root is invalid.");
+ set_active(false);
+ return false;
+ }
+ Node *parent = player->get_node(player->get_root());
+
+ List<StringName> sname;
+ player->get_animation_list(&sname);
+
+ for (List<StringName>::Element *E = sname.front(); E; E = E->next()) {
+ Ref<Animation> anim = player->get_animation(E->get());
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ NodePath path = anim->track_get_path(i);
+ Animation::TrackType track_type = anim->track_get_type(i);
+
+ TrackCache *track = NULL;
+ if (track_cache.has(path)) {
+ track = track_cache.get(path);
+ }
+
+ //if not valid, delete track
+ if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == NULL)) {
+ playing_caches.erase(track);
+ memdelete(track);
+ track_cache.erase(path);
+ track = NULL;
+ }
+
+ if (!track) {
+
+ RES resource;
+ Vector<StringName> leftover_path;
+ Node *child = parent->get_node_and_resource(path, resource, leftover_path);
+
+ if (!child) {
+ ERR_PRINTS("AnimationGraphPlayer: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'");
+ continue;
+ }
+
+ if (!child->is_connected("tree_exited", this, "_node_removed")) {
+ child->connect("tree_exited", this, "_node_removed", varray(child));
+ }
+
+ switch (track_type) {
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *track_value = memnew(TrackCacheValue);
+
+ if (resource.is_valid()) {
+ track_value->object = resource.ptr();
+ } else {
+ track_value->object = child;
+ }
+
+ track_value->subpath = leftover_path;
+ track_value->object_id = track_value->object->get_instance_id();
+
+ track = track_value;
+
+ } break;
+ case Animation::TYPE_TRANSFORM: {
+
+ Spatial *spatial = Object::cast_to<Spatial>(child);
+
+ if (!spatial) {
+ ERR_PRINTS("AnimationGraphPlayer: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'");
+ continue;
+ }
+
+ TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
+
+ track_xform->spatial = spatial;
+ track_xform->skeleton = NULL;
+ track_xform->bone_idx = -1;
+
+ if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {
+
+ Skeleton *sk = Object::cast_to<Skeleton>(spatial);
+ int bone_idx = sk->find_bone(path.get_subname(0));
+ if (bone_idx != -1 && !sk->is_bone_ignore_animation(bone_idx)) {
+
+ track_xform->skeleton = sk;
+ track_xform->bone_idx = bone_idx;
+ }
+ }
+
+ track_xform->object = spatial;
+ track_xform->object_id = track_xform->object->get_instance_id();
+
+ track = track_xform;
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ TrackCacheMethod *track_method = memnew(TrackCacheMethod);
+
+ if (resource.is_valid()) {
+ track_method->object = resource.ptr();
+ } else {
+ track_method->object = child;
+ }
+
+ track_method->object_id = track_method->object->get_instance_id();
+
+ track = track_method;
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *track_bezier = memnew(TrackCacheBezier);
+
+ if (resource.is_valid()) {
+ track_bezier->object = resource.ptr();
+ } else {
+ track_bezier->object = child;
+ }
+
+ track_bezier->subpath = leftover_path;
+ track_bezier->object_id = track_bezier->object->get_instance_id();
+
+ track = track_bezier;
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
+
+ track_audio->object = child;
+ track_audio->object_id = track_audio->object->get_instance_id();
+
+ track = track_audio;
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation);
+
+ track_animation->object = child;
+ track_animation->object_id = track_animation->object->get_instance_id();
+
+ track = track_animation;
+
+ } break;
+ }
+
+ track_cache[path] = track;
+ }
+
+ track->setup_pass = setup_pass;
+ }
+ }
+
+ List<NodePath> to_delete;
+
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ TrackCache *tc = track_cache[*K];
+ if (tc->setup_pass != setup_pass) {
+ to_delete.push_back(*K);
+ }
+ }
+
+ while (to_delete.front()) {
+ NodePath np = to_delete.front()->get();
+ memdelete(track_cache[np]);
+ track_cache.erase(np);
+ to_delete.pop_front();
+ }
+
+ state.track_map.clear();
+
+ K = NULL;
+ int idx = 0;
+ while ((K = track_cache.next(K))) {
+ state.track_map[*K] = idx;
+ idx++;
+ }
+
+ state.track_count = idx;
+
+ cache_valid = true;
+
+ return true;
+}
+
+void AnimationGraphPlayer::_clear_caches() {
+
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ memdelete(track_cache[*K]);
+ }
+ playing_caches.clear();
+
+ track_cache.clear();
+ cache_valid = false;
+}
+
+void AnimationGraphPlayer::_process_graph(float p_delta) {
+
+ //check all tracks, see if they need modification
+
+ if (!root.is_valid()) {
+ ERR_PRINT("AnimationGraphPlayer: root AnimationNode is not set, disabling playback.");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ if (!has_node(animation_player)) {
+ ERR_PRINT("AnimationGraphPlayer: no valid AnimationPlayer path set, disabling playback");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player));
+
+ if (!player) {
+ ERR_PRINT("AnimationGraphPlayer: path points to a node not an AnimationPlayer, disabling playback");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ if (!cache_valid) {
+ if (!_update_caches(player)) {
+ return;
+ }
+ }
+
+ { //setup
+
+ process_pass++;
+
+ state.valid = true;
+ state.invalid_reasons = "";
+ state.animation_states.clear(); //will need to be re-created
+ state.valid = true;
+ state.player = player;
+ state.last_pass = process_pass;
+
+ // root source blends
+
+ root->blends.resize(state.track_count);
+ float *src_blendsw = root->blends.ptrw();
+ for (int i = 0; i < state.track_count; i++) {
+ src_blendsw[i] = 1.0; //by default all go to 1 for the root input
+ }
+ }
+
+ //process
+
+ {
+
+ if (started) {
+ //if started, seek
+ root->_pre_process(&state, 0, true);
+ started = false;
+ }
+
+ root->_pre_process(&state, p_delta, false);
+ }
+
+ if (!state.valid) {
+ return; //state is not valid. do nothing.
+ }
+ //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks
+
+ {
+
+ bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint();
+
+ for (List<AnimationNode::AnimationState>::Element *E = state.animation_states.front(); E; E = E->next()) {
+
+ const AnimationNode::AnimationState &as = E->get();
+
+ Ref<Animation> a = as.animation;
+ float time = as.time;
+ float delta = as.delta;
+ bool seeked = as.seeked;
+
+ for (int i = 0; i < a->get_track_count(); i++) {
+
+ NodePath path = a->track_get_path(i);
+ TrackCache *track = track_cache[path];
+ if (track->type != a->track_get_type(i)) {
+ continue; //may happen should not
+ }
+
+ ERR_CONTINUE(!state.track_map.has(path));
+ int blend_idx = state.track_map[path];
+
+ ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
+
+ float blend = (*as.track_blends)[blend_idx];
+
+ if (blend < CMP_EPSILON)
+ continue; //nothing to blend
+
+ switch (track->type) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ Vector3 loc;
+ Quat rot;
+ Vector3 scale;
+
+ Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
+
+ if (err != OK)
+ continue;
+
+ if (t->process_pass != process_pass) {
+
+ t->process_pass = process_pass;
+ t->loc = Vector3();
+ t->rot = Quat();
+ t->scale = Vector3();
+ }
+
+ t->loc = t->loc.linear_interpolate(loc, blend);
+ t->rot = t->rot.slerp(rot, blend);
+ t->scale = t->scale.linear_interpolate(scale, blend);
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
+
+ Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
+
+ if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek
+
+ Variant value = a->value_track_interpolate(i, time);
+
+ if (value == Variant())
+ continue;
+
+ if (t->process_pass != process_pass) {
+ Variant::CallError ce;
+ t->value = Variant::construct(value.get_type(), NULL, 0, ce); //reset
+ t->process_pass = process_pass;
+ }
+
+ Variant::interpolate(t->value, value, blend, t->value);
+
+ } else if (delta != 0) {
+
+ List<int> indices;
+ a->value_track_get_key_indices(i, time, delta, &indices);
+
+ for (List<int>::Element *F = indices.front(); F; F = F->next()) {
+
+ Variant value = a->track_get_key_value(i, F->get());
+ t->object->set_indexed(t->subpath, value);
+ }
+ }
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ if (delta == 0) {
+ continue;
+ }
+ TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
+
+ List<int> indices;
+
+ a->method_track_get_key_indices(i, time, delta, &indices);
+
+ for (List<int>::Element *E = indices.front(); E; E = E->next()) {
+
+ StringName method = a->method_track_get_name(i, E->get());
+ Vector<Variant> params = a->method_track_get_params(i, E->get());
+
+ int s = params.size();
+
+ ERR_CONTINUE(s > VARIANT_ARG_MAX);
+ if (can_call) {
+ t->object->call_deferred(
+ method,
+ s >= 1 ? params[0] : Variant(),
+ s >= 2 ? params[1] : Variant(),
+ s >= 3 ? params[2] : Variant(),
+ s >= 4 ? params[3] : Variant(),
+ s >= 5 ? params[4] : Variant());
+ }
+ }
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
+
+ float bezier = a->bezier_track_interpolate(i, time);
+
+ if (t->process_pass != process_pass) {
+ t->value = 0;
+ t->process_pass = process_pass;
+ }
+
+ t->value = Math::lerp(t->value, bezier, blend);
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+
+ if (seeked) {
+ //find whathever should be playing
+ int idx = a->track_find_key(i, time);
+ if (idx < 0)
+ continue;
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ start_ofs += time - a->track_get_key_time(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ if (start_ofs > len - end_ofs) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ continue;
+ }
+
+ t->object->call("set_stream", stream);
+ t->object->call("play", start_ofs);
+
+ t->playing = true;
+ playing_caches.insert(t);
+ if (len && end_ofs > 0) { //force a end at a time
+ t->len = len - start_ofs - end_ofs;
+ } else {
+ t->len = 0;
+ }
+
+ t->start = time;
+ }
+
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ t->object->call("set_stream", stream);
+ t->object->call("play", start_ofs);
+
+ t->playing = true;
+ playing_caches.insert(t);
+ if (len && end_ofs > 0) { //force a end at a time
+ t->len = len - start_ofs - end_ofs;
+ } else {
+ t->len = 0;
+ }
+
+ t->start = time;
+ }
+ } else if (t->playing) {
+ if (t->start > time || (t->len > 0 && time - t->start < t->len)) {
+ //time to stop
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ }
+ }
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(t->object);
+
+ if (!player)
+ continue;
+
+ if (delta == 0 || seeked) {
+ //seek
+ int idx = a->track_find_key(i, time);
+ if (idx < 0)
+ continue;
+
+ float pos = a->track_get_key_time(i, idx);
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name))
+ continue;
+
+ Ref<Animation> anim = player->get_animation(anim_name);
+
+ float at_anim_pos;
+
+ if (anim->has_loop()) {
+ at_anim_pos = Math::fposmod(time - pos, anim->get_length()); //seek to loop
+ } else {
+ at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end
+ }
+
+ if (player->is_playing() || seeked) {
+ player->play(anim_name);
+ player->seek(at_anim_pos);
+ t->playing = true;
+ playing_caches.insert(t);
+ } else {
+ player->set_assigned_animation(anim_name);
+ player->seek(at_anim_pos, true);
+ }
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) {
+
+ if (playing_caches.has(t)) {
+ playing_caches.erase(t);
+ player->stop();
+ t->playing = false;
+ }
+ } else {
+ player->play(anim_name);
+ t->playing = true;
+ playing_caches.insert(t);
+ }
+ }
+ }
+
+ } break;
+ }
+ }
+ }
+ }
+
+ {
+ // finally, set the tracks
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ TrackCache *track = track_cache[*K];
+ if (track->process_pass != process_pass)
+ continue; //not processed, ignore
+
+ switch (track->type) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ Transform xform;
+ xform.origin = t->loc;
+
+ t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
+
+ xform.basis.set_quat_scale(t->rot, t->scale);
+
+ if (t->skeleton && t->bone_idx >= 0) {
+
+ t->skeleton->set_bone_pose(t->bone_idx, xform);
+
+ } else {
+
+ t->spatial->set_transform(xform);
+ }
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
+
+ t->object->set_indexed(t->subpath, t->value);
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
+
+ t->object->set_indexed(t->subpath, t->value);
+
+ } break;
+ default: {} //the rest dont matter
+ }
+ }
+ }
+}
+
+void AnimationGraphPlayer::_notification(int p_what) {
+
+ if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) {
+ _process_graph(get_physics_process_delta_time());
+ }
+
+ if (active && p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) {
+ _process_graph(get_process_delta_time());
+ }
+
+ if (p_what == NOTIFICATION_EXIT_TREE) {
+ _clear_caches();
+ }
+}
+
+void AnimationGraphPlayer::set_animation_player(const NodePath &p_player) {
+ animation_player = p_player;
+ update_configuration_warning();
+}
+
+NodePath AnimationGraphPlayer::get_animation_player() const {
+ return animation_player;
+}
+
+bool AnimationGraphPlayer::is_state_invalid() const {
+
+ return !state.valid;
+}
+String AnimationGraphPlayer::get_invalid_state_reason() const {
+
+ return state.invalid_reasons;
+}
+
+uint64_t AnimationGraphPlayer::get_last_process_pass() const {
+ return process_pass;
+}
+
+String AnimationGraphPlayer::get_configuration_warning() const {
+
+ String warning = Node::get_configuration_warning();
+
+ if (!root.is_valid()) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+ warning += TTR("A root AnimationNode for the graph is not set.");
+ }
+
+ if (!has_node(animation_player)) {
+
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("Path to an AnimationPlayer node containing animations is not set.");
+ return warning;
+ }
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player));
+
+ if (!player) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node.");
+ return warning;
+ }
+
+ if (!player->has_node(player->get_root())) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("AnimationPlayer root is not a valid node.");
+ return warning;
+ }
+
+ return warning;
+}
+
+void AnimationGraphPlayer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationGraphPlayer::set_active);
+ ClassDB::bind_method(D_METHOD("is_active"), &AnimationGraphPlayer::is_active);
+
+ ClassDB::bind_method(D_METHOD("set_graph_root", "root"), &AnimationGraphPlayer::set_graph_root);
+ ClassDB::bind_method(D_METHOD("get_graph_root"), &AnimationGraphPlayer::get_graph_root);
+
+ ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationGraphPlayer::set_process_mode);
+ ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationGraphPlayer::get_process_mode);
+
+ ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationGraphPlayer::set_animation_player);
+ ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationGraphPlayer::get_animation_player);
+
+ ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationGraphPlayer::_node_removed);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_graph_root", "get_graph_root");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player"), "set_animation_player", "get_animation_player");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode");
+}
+
+AnimationGraphPlayer::AnimationGraphPlayer() {
+
+ process_mode = ANIMATION_PROCESS_IDLE;
+ active = false;
+ cache_valid = false;
+ setup_pass = 1;
+ started = true;
+}
+
+AnimationGraphPlayer::~AnimationGraphPlayer() {
+ if (root.is_valid()) {
+ root->player = NULL;
+ }
+}
diff --git a/scene/animation/animation_graph_player.h b/scene/animation/animation_graph_player.h
new file mode 100644
index 0000000000..8952be75c9
--- /dev/null
+++ b/scene/animation/animation_graph_player.h
@@ -0,0 +1,267 @@
+#ifndef ANIMATION_GRAPH_PLAYER_H
+#define ANIMATION_GRAPH_PLAYER_H
+
+#include "animation_player.h"
+#include "scene/3d/skeleton.h"
+#include "scene/3d/spatial.h"
+#include "scene/resources/animation.h"
+
+class AnimationNodeBlendTree;
+class AnimationPlayer;
+class AnimationGraphPlayer;
+
+class AnimationNode : public Resource {
+ GDCLASS(AnimationNode, Resource)
+public:
+ enum FilterAction {
+ FILTER_IGNORE,
+ FILTER_PASS,
+ FILTER_STOP,
+ FILTER_BLEND
+ };
+
+ struct Input {
+
+ String name;
+ StringName connected_to;
+ float activity;
+ uint64_t last_pass;
+ };
+
+ Vector<Input> inputs;
+
+ float process_input(int p_input, float p_time, bool p_seek, float p_blend);
+
+ friend class AnimationGraphPlayer;
+
+ struct AnimationState {
+
+ Ref<Animation> animation;
+ float time;
+ float delta;
+ const Vector<float> *track_blends;
+ float blend;
+ bool seeked;
+ };
+
+ struct State {
+
+ int track_count;
+ HashMap<NodePath, int> track_map;
+ List<AnimationState> animation_states;
+ bool valid;
+ AnimationPlayer *player;
+ String invalid_reasons;
+ uint64_t last_pass;
+ };
+
+ Vector<float> blends;
+ State *state;
+ float _pre_process(State *p_state, float p_time, bool p_seek);
+ void _pre_update_animations(HashMap<NodePath, int> *track_map);
+ Vector2 position;
+
+ AnimationNode *parent;
+ AnimationGraphPlayer *player;
+
+ float _blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = NULL);
+
+ HashMap<NodePath, bool> filter;
+ bool filter_enabled;
+
+ Array _get_filters() const;
+ void _set_filters(const Array &p_filters);
+
+protected:
+ void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend);
+ float blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
+ float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
+ void make_invalid(const String &p_reason);
+
+ static void _bind_methods();
+
+ void _validate_property(PropertyInfo &property) const;
+
+public:
+ void set_parent(AnimationNode *p_parent);
+ Ref<AnimationNode> get_parent() const;
+ virtual void set_graph_player(AnimationGraphPlayer *p_player);
+ AnimationGraphPlayer *get_graph_player() const;
+ AnimationPlayer *get_player() const;
+
+ virtual float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
+
+ int get_input_count() const;
+ String get_input_name(int p_input);
+ StringName get_input_connection(int p_input);
+ void set_input_connection(int p_input, const StringName &p_connection);
+ float get_input_activity(int p_input) const;
+
+ void add_input(const String &p_name);
+ void set_input_name(int p_input, const String &p_name);
+ void remove_input(int p_index);
+
+ void set_filter_path(const NodePath &p_path, bool p_enable);
+ bool is_path_filtered(const NodePath &p_path) const;
+
+ void set_filter_enabled(bool p_enable);
+ bool is_filter_enabled() const;
+
+ virtual bool has_filter() const;
+
+ void set_position(const Vector2 &p_position);
+ Vector2 get_position() const;
+
+ AnimationNode();
+};
+
+VARIANT_ENUM_CAST(AnimationNode::FilterAction)
+
+//root node does not allow inputs
+class AnimationRootNode : public AnimationNode {
+ GDCLASS(AnimationRootNode, AnimationNode)
+public:
+ AnimationRootNode() {}
+};
+
+class AnimationGraphPlayer : public Node {
+ GDCLASS(AnimationGraphPlayer, Node)
+public:
+ enum AnimationProcessMode {
+ ANIMATION_PROCESS_PHYSICS,
+ ANIMATION_PROCESS_IDLE,
+ };
+
+private:
+ struct TrackCache {
+ uint64_t setup_pass;
+ uint64_t process_pass;
+ Animation::TrackType type;
+ Object *object;
+ ObjectID object_id;
+
+ TrackCache() {
+ setup_pass = 0;
+ process_pass = 0;
+ object = NULL;
+ object_id = 0;
+ }
+ virtual ~TrackCache() {}
+ };
+
+ struct TrackCacheTransform : public TrackCache {
+ Spatial *spatial;
+ Skeleton *skeleton;
+ int bone_idx;
+ Vector3 loc;
+ Quat rot;
+ Vector3 scale;
+
+ TrackCacheTransform() {
+ type = Animation::TYPE_TRANSFORM;
+ spatial = NULL;
+ bone_idx = -1;
+ skeleton = NULL;
+ }
+ };
+
+ struct TrackCacheValue : public TrackCache {
+
+ Variant value;
+ Vector<StringName> subpath;
+ TrackCacheValue() { type = Animation::TYPE_VALUE; }
+ };
+
+ struct TrackCacheMethod : public TrackCache {
+
+ TrackCacheMethod() { type = Animation::TYPE_METHOD; }
+ };
+
+ struct TrackCacheBezier : public TrackCache {
+
+ float value;
+ Vector<StringName> subpath;
+ TrackCacheBezier() {
+ type = Animation::TYPE_BEZIER;
+ value = 0;
+ }
+ };
+
+ struct TrackCacheAudio : public TrackCache {
+
+ bool playing;
+ float start;
+ float len;
+
+ TrackCacheAudio() {
+ type = Animation::TYPE_AUDIO;
+ playing = false;
+ start = 0;
+ len = 0;
+ }
+ };
+
+ struct TrackCacheAnimation : public TrackCache {
+
+ bool playing;
+
+ TrackCacheAnimation() {
+ type = Animation::TYPE_ANIMATION;
+ playing = false;
+ }
+ };
+
+ HashMap<NodePath, TrackCache *> track_cache;
+ Set<TrackCache *> playing_caches;
+
+ Ref<AnimationNode> root;
+
+ AnimationProcessMode process_mode;
+ bool active;
+ NodePath animation_player;
+
+ AnimationNode::State state;
+ bool cache_valid;
+ void _node_removed(Node *p_node);
+ void _caches_cleared();
+
+ void _clear_caches();
+ bool _update_caches(AnimationPlayer *player);
+ void _process_graph(float p_delta);
+
+ uint64_t setup_pass;
+ uint64_t process_pass;
+
+ bool started;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_graph_root(const Ref<AnimationNode> &p_root);
+ Ref<AnimationNode> get_graph_root() const;
+
+ void set_active(bool p_active);
+ bool is_active() const;
+
+ void set_process_mode(AnimationProcessMode p_mode);
+ AnimationProcessMode get_process_mode() const;
+
+ void set_animation_player(const NodePath &p_player);
+ NodePath get_animation_player() const;
+
+ virtual String get_configuration_warning() const;
+
+ bool is_state_invalid() const;
+ String get_invalid_state_reason() const;
+
+ uint64_t get_last_process_pass() const;
+ AnimationGraphPlayer();
+ ~AnimationGraphPlayer();
+};
+
+VARIANT_ENUM_CAST(AnimationGraphPlayer::AnimationProcessMode)
+
+#endif // ANIMATION_GRAPH_PLAYER_H
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index a0e0137863..06aaeddf18 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -33,7 +33,7 @@
#include "engine.h"
#include "message_queue.h"
#include "scene/scene_string_names.h"
-
+#include "servers/audio/audio_stream.h"
#ifdef TOOLS_ENABLED
void AnimatedValuesBackup::update_skeletons() {
@@ -325,10 +325,27 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
}
}
+
+ if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) {
+
+ if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
+
+ TrackNodeCache::BezierAnim ba;
+ String path = leftover_path[leftover_path.size() - 1];
+ Vector<String> index = path.split(".");
+ for (int j = 0; j < index.size(); j++) {
+ ba.bezier_property.push_back(index[j]);
+ }
+ ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
+ ba.owner = p_anim->node_cache[i];
+
+ p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
+ }
+ }
}
}
-void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
@@ -394,7 +411,39 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
TrackNodeCache::PropertyAnim *pa = &E->get();
- if (a->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (p_delta == 0 && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
+ Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
+
+ if (update_mode == Animation::UPDATE_CAPTURE) {
+
+ if (p_started) {
+ pa->capture = pa->object->get_indexed(pa->subpath);
+ }
+
+ if (a->track_get_key_count(i) == 0)
+ continue; //eeh not worth it
+
+ float first_key_time = a->track_get_key_time(i, 0);
+
+ if (p_time < first_key_time) {
+ float c = p_time / first_key_time;
+ Variant first_value = a->track_get_key_value(i, 0);
+ Variant interp_value;
+ Variant::interpolate(pa->capture, first_value, c, interp_value);
+
+ if (pa->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_prop[cache_update_prop_size++] = pa;
+ pa->value_accum = interp_value;
+ pa->accum_pass = accum_pass;
+ } else {
+ Variant::interpolate(pa->value_accum, interp_value, p_interp, pa->value_accum);
+ }
+
+ continue; //handled
+ }
+ }
+
+ if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
Variant value = a->value_track_interpolate(i, p_time);
@@ -415,7 +464,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum);
}
- } else if (p_allow_discrete && p_delta != 0) {
+ } else if (p_is_current && p_delta != 0) {
List<int> indices;
a->value_track_get_key_indices(i, p_time, p_delta, &indices);
@@ -470,9 +519,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
if (!nc->node)
continue;
- if (p_delta == 0)
+ if (p_delta == 0) {
continue;
- if (!p_allow_discrete)
+ }
+ if (!p_is_current)
break;
List<int> indices;
@@ -500,11 +550,180 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
}
} break;
+ case Animation::TYPE_BEZIER: {
+
+ if (!nc->node)
+ continue;
+
+ Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames());
+ ERR_CONTINUE(!E); //should it continue, or create a new one?
+
+ TrackNodeCache::BezierAnim *ba = &E->get();
+
+ float bezier = a->bezier_track_interpolate(i, p_time);
+ if (ba->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_bezier[cache_update_bezier_size++] = ba;
+ ba->bezier_accum = bezier;
+ ba->accum_pass = accum_pass;
+ } else {
+ ba->bezier_accum = Math::lerp(ba->bezier_accum, bezier, p_interp);
+ }
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (!nc->node)
+ continue;
+ if (p_delta == 0) {
+ continue;
+ }
+
+ if (p_seeked) {
+ //find whathever should be playing
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ start_ofs += p_time - a->track_get_key_time(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ if (start_ofs > len - end_ofs) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ continue;
+ }
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+ } else if (nc->audio_playing) {
+ if (nc->audio_start > p_time || (nc->audio_len > 0 && p_time - nc->audio_start < nc->audio_len)) {
+ //time to stop
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ }
+ }
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node);
+ if (!player)
+ continue;
+
+ if (p_delta == 0 || p_seeked) {
+ //seek
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ float pos = a->track_get_key_time(i, idx);
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name))
+ continue;
+
+ Ref<Animation> anim = player->get_animation(anim_name);
+
+ float at_anim_pos;
+
+ if (anim->has_loop()) {
+ at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop
+ } else {
+ at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end
+ }
+
+ if (player->is_playing() || p_seeked) {
+ player->play(anim_name);
+ player->seek(at_anim_pos);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ } else {
+ player->set_assigned_animation(anim_name);
+ player->seek(at_anim_pos, true);
+ }
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) {
+
+ if (playing_caches.has(nc)) {
+ playing_caches.erase(nc);
+ player->stop();
+ nc->animation_playing = false;
+ }
+ } else {
+ player->play(anim_name);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ }
+ }
+ }
+
+ } break;
}
}
}
-void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) {
+void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) {
float delta = p_delta * speed_scale * cd.speed_scale;
float next_pos = cd.pos + delta;
@@ -553,22 +772,25 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f
cd.pos = next_pos;
- _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current);
+ _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started);
}
-void AnimationPlayer::_animation_process2(float p_delta) {
+void AnimationPlayer::_animation_process2(float p_delta, bool p_started) {
Playback &c = playback;
accum_pass++;
- _animation_process_data(c.current, p_delta, 1.0f);
+ _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started);
+ if (p_delta != 0) {
+ c.seeked = false;
+ }
List<Blend>::Element *prev = NULL;
for (List<Blend>::Element *E = c.blend.back(); E; E = prev) {
Blend &b = E->get();
float blend = b.blend_left / b.blend_time;
- _animation_process_data(b.data, p_delta, blend);
+ _animation_process_data(b.data, p_delta, blend, false, false);
b.blend_left -= Math::absf(speed_scale * p_delta);
@@ -652,6 +874,16 @@ void AnimationPlayer::_animation_update_transforms() {
}
cache_update_prop_size = 0;
+
+ for (int i = 0; i < cache_update_bezier_size; i++) {
+
+ TrackNodeCache::BezierAnim *ba = cache_update_bezier[i];
+
+ ERR_CONTINUE(ba->accum_pass != accum_pass);
+ ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
+ }
+
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::_animation_process(float p_delta) {
@@ -660,7 +892,12 @@ void AnimationPlayer::_animation_process(float p_delta) {
end_reached = false;
end_notify = false;
- _animation_process2(p_delta);
+ _animation_process2(p_delta, playback.started);
+
+ if (playback.started) {
+ playback.started = false;
+ }
+
_animation_update_transforms();
if (end_reached) {
if (queued.size()) {
@@ -865,7 +1102,7 @@ void AnimationPlayer::queue(const StringName &p_name) {
void AnimationPlayer::clear_queue() {
queued.clear();
-};
+}
void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) {
@@ -930,10 +1167,14 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float
}
}
+ _stop_playing_caches();
+
c.current.from = &animation_set[name];
c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
c.current.speed_scale = p_custom_scale;
c.assigned = p_name;
+ c.seeked = false;
+ c.started = true;
if (!end_reached)
queued.clear();
@@ -1004,6 +1245,7 @@ String AnimationPlayer::get_assigned_animation() const {
void AnimationPlayer::stop(bool p_reset) {
+ _stop_playing_caches();
Playback &c = playback;
c.blend.clear();
if (p_reset) {
@@ -1042,6 +1284,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) {
}
playback.current.pos = p_time;
+ playback.seeked = true;
if (p_update) {
_animation_process(0);
}
@@ -1084,6 +1327,25 @@ float AnimationPlayer::get_current_animation_length() const {
void AnimationPlayer::_animation_changed() {
clear_caches();
+ emit_signal("caches_cleared");
+}
+
+void AnimationPlayer::_stop_playing_caches() {
+
+ for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) {
+
+ if (E->get()->node && E->get()->audio_playing) {
+ E->get()->node->call("stop");
+ }
+ if (E->get()->node && E->get()->animation_playing) {
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node);
+ if (!player)
+ continue;
+ player->stop();
+ }
+ }
+
+ playing_caches.clear();
}
void AnimationPlayer::_node_removed(Node *p_node) {
@@ -1093,6 +1355,8 @@ void AnimationPlayer::_node_removed(Node *p_node) {
void AnimationPlayer::clear_caches() {
+ _stop_playing_caches();
+
node_cache_map.clear();
for (Map<StringName, AnimationData>::Element *E = animation_set.front(); E; E = E->next()) {
@@ -1102,6 +1366,7 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::set_active(bool p_active) {
@@ -1358,6 +1623,7 @@ void AnimationPlayer::_bind_methods() {
ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING, "anim_name")));
ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING, "anim_name")));
+ ADD_SIGNAL(MethodInfo("caches_cleared"));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
@@ -1368,6 +1634,7 @@ AnimationPlayer::AnimationPlayer() {
accum_pass = 1;
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
speed_scale = 1;
end_reached = false;
end_notify = false;
@@ -1377,6 +1644,8 @@ AnimationPlayer::AnimationPlayer() {
root = SceneStringNames::get_singleton()->path_pp;
playing = false;
active = true;
+ playback.seeked = false;
+ playback.started = false;
}
AnimationPlayer::~AnimationPlayer() {
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index af2022ddac..49c73e54ad 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -98,6 +98,12 @@ private:
Vector3 scale_accum;
uint64_t accum_pass;
+ bool audio_playing;
+ float audio_start;
+ float audio_len;
+
+ bool animation_playing;
+
struct PropertyAnim {
TrackNodeCache *owner;
@@ -106,6 +112,7 @@ private:
Object *object;
Variant value_accum;
uint64_t accum_pass;
+ Variant capture;
PropertyAnim() {
accum_pass = 0;
object = NULL;
@@ -114,6 +121,22 @@ private:
Map<StringName, PropertyAnim> property_anim;
+ struct BezierAnim {
+
+ Vector<StringName> bezier_property;
+ TrackNodeCache *owner;
+ float bezier_accum;
+ Object *object;
+ uint64_t accum_pass;
+ BezierAnim() {
+ accum_pass = 0;
+ bezier_accum = 0;
+ object = NULL;
+ }
+ };
+
+ Map<StringName, BezierAnim> bezier_anim;
+
TrackNodeCache() {
skeleton = NULL;
spatial = NULL;
@@ -121,6 +144,8 @@ private:
accum_pass = 0;
bone_idx = -1;
node_2d = NULL;
+ audio_playing = false;
+ animation_playing = false;
}
};
@@ -146,6 +171,10 @@ private:
int cache_update_size;
TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX];
int cache_update_prop_size;
+ TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
+ int cache_update_bezier_size;
+ Set<TrackNodeCache *> playing_caches;
+
Map<Ref<Animation>, int> used_anims;
uint64_t accum_pass;
@@ -202,6 +231,8 @@ private:
List<Blend> blend;
PlaybackData current;
StringName assigned;
+ bool seeked;
+ bool started;
} playback;
List<StringName> queued;
@@ -216,15 +247,16 @@ private:
NodePath root;
- void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete = true);
+ void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false);
void _ensure_node_caches(AnimationData *p_anim);
- void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend);
- void _animation_process2(float p_delta);
+ void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started);
+ void _animation_process2(float p_delta, bool p_started);
void _animation_update_transforms();
void _animation_process(float p_delta);
void _node_removed(Node *p_node);
+ void _stop_playing_caches();
// bind helpers
PoolVector<String> _get_animation_list() const {
diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp
index ce5b372d72..143684bdf9 100644
--- a/scene/animation/animation_tree_player.cpp
+++ b/scene/animation/animation_tree_player.cpp
@@ -1216,6 +1216,12 @@ String AnimationTreePlayer::animation_node_get_master_animation(const StringName
return n->from;
}
+float AnimationTreePlayer::animation_node_get_position(const StringName &p_node) const {
+
+ GET_NODE_V(NODE_ANIMATION, AnimationNode, 0);
+ return n->time;
+}
+
bool AnimationTreePlayer::animation_node_is_path_filtered(const StringName &p_node, const NodePath &p_path) const {
GET_NODE_V(NODE_ANIMATION, AnimationNode, 0);
@@ -1724,6 +1730,7 @@ void AnimationTreePlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("animation_node_set_master_animation", "id", "source"), &AnimationTreePlayer::animation_node_set_master_animation);
ClassDB::bind_method(D_METHOD("animation_node_get_master_animation", "id"), &AnimationTreePlayer::animation_node_get_master_animation);
+ ClassDB::bind_method(D_METHOD("animation_node_get_position", "id"), &AnimationTreePlayer::animation_node_get_position);
ClassDB::bind_method(D_METHOD("animation_node_set_filter_path", "id", "path", "enable"), &AnimationTreePlayer::animation_node_set_filter_path);
ClassDB::bind_method(D_METHOD("oneshot_node_set_fadein_time", "id", "time_sec"), &AnimationTreePlayer::oneshot_node_set_fadein_time);
diff --git a/scene/animation/animation_tree_player.h b/scene/animation/animation_tree_player.h
index 09d6f6fcb4..d2d7b1c9ec 100644
--- a/scene/animation/animation_tree_player.h
+++ b/scene/animation/animation_tree_player.h
@@ -348,6 +348,7 @@ public:
Ref<Animation> animation_node_get_animation(const StringName &p_node) const;
void animation_node_set_master_animation(const StringName &p_node, const String &p_master_animation);
String animation_node_get_master_animation(const StringName &p_node) const;
+ float animation_node_get_position(const StringName &p_node) const;
void animation_node_set_filter_path(const StringName &p_node, const NodePath &p_track_path, bool p_filter);
void animation_node_set_get_filtered_paths(const StringName &p_node, List<NodePath> *r_paths) const;
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 49013b160a..9f7503577b 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -201,6 +201,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all);
ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Tween::stop, DEFVAL(""));
ClassDB::bind_method(D_METHOD("stop_all"), &Tween::stop_all);
+ ClassDB::bind_method(D_METHOD("is_stopped"), &Tween::is_stopped);
ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL(""));
ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all);
ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL(""));
@@ -743,6 +744,10 @@ bool Tween::stop(Object *p_object, StringName p_key) {
return true;
}
+bool Tween::is_stopped() const {
+ return tell() >= get_runtime();
+}
+
bool Tween::stop_all() {
set_active(false);
@@ -886,6 +891,10 @@ real_t Tween::tell() const {
real_t Tween::get_runtime() const {
+ if (speed_scale == 0) {
+ return INFINITY;
+ }
+
pending_update++;
real_t runtime = 0;
for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -896,7 +905,8 @@ real_t Tween::get_runtime() const {
runtime = t;
}
pending_update--;
- return runtime;
+
+ return runtime / speed_scale;
}
bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) {
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index 757d80e90a..36094bf294 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -162,6 +162,7 @@ public:
bool reset_all();
bool stop(Object *p_object, StringName p_key);
bool stop_all();
+ bool is_stopped() const;
bool resume(Object *p_object, StringName p_key);
bool resume_all();
bool remove(Object *p_object, StringName p_key);
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index 177582c87c..7df03bf7c6 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -51,6 +51,8 @@ void Container::add_child_notify(Node *p_child) {
control->connect("size_flags_changed", this, "queue_sort");
control->connect("minimum_size_changed", this, "_child_minsize_changed");
control->connect("visibility_changed", this, "_child_minsize_changed");
+
+ minimum_size_changed();
queue_sort();
}
@@ -61,6 +63,7 @@ void Container::move_child_notify(Node *p_child) {
if (!Object::cast_to<Control>(p_child))
return;
+ minimum_size_changed();
queue_sort();
}
@@ -75,6 +78,8 @@ void Container::remove_child_notify(Node *p_child) {
control->disconnect("size_flags_changed", this, "queue_sort");
control->disconnect("minimum_size_changed", this, "_child_minsize_changed");
control->disconnect("visibility_changed", this, "_child_minsize_changed");
+
+ minimum_size_changed();
queue_sort();
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 3097ecaf16..a738687a70 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -162,6 +162,7 @@ void Control::_update_minimum_size_cache() {
minsize.y = MAX(minsize.y, data.custom_minimum_size.y);
data.minimum_size_cache = minsize;
data.minimum_size_valid = true;
+ minimum_size_changed();
}
Size2 Control::get_combined_minimum_size() const {
@@ -277,6 +278,7 @@ void Control::_update_minimum_size() {
data.updating_last_minimum_size = false;
if (minsize != data.last_minimum_size) {
+ data.last_minimum_size = minsize;
emit_signal(SceneStringNames::get_singleton()->minimum_size_changed);
}
}
@@ -451,10 +453,8 @@ void Control::_notification(int p_notification) {
} break;
case NOTIFICATION_POST_ENTER_TREE: {
- if (is_visible_in_tree()) {
- data.minimum_size_valid = false;
- _size_changed();
- }
+ data.minimum_size_valid = false;
+ _size_changed();
} break;
case NOTIFICATION_EXIT_TREE: {
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 4bd92d888d..25cb74a494 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -777,6 +777,7 @@ void FileDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mode", "mode"), &FileDialog::set_mode);
ClassDB::bind_method(D_METHOD("get_mode"), &FileDialog::get_mode);
ClassDB::bind_method(D_METHOD("get_vbox"), &FileDialog::get_vbox);
+ ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit);
ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access);
ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access);
ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files);
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 9fc8e98a7f..b5622604e2 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -147,6 +147,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
grabbed = _get_point_from_pos(x);
//grab or select
if (grabbed != -1) {
+ grabbed = false;
return;
}
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 38ce91a4df..e2c730a56e 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -58,6 +58,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S
c.from_port = p_from_port;
c.to = p_to;
c.to_port = p_to_port;
+ c.activity = 0;
connections.push_back(c);
top_layer->update();
update();
@@ -624,6 +625,7 @@ void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const
void GraphEdit::_connections_layer_draw() {
+ Color activity_color = get_color("activity");
//draw connections
List<List<Connection>::Element *> to_erase;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
@@ -661,6 +663,11 @@ void GraphEdit::_connections_layer_draw() {
Color color = gfrom->get_connection_output_color(E->get().from_port);
Vector2 topos = gto->get_connection_input_position(E->get().to_port) + gto->get_offset() * zoom;
Color tocolor = gto->get_connection_input_color(E->get().to_port);
+
+ if (E->get().activity > 0) {
+ color = color.linear_interpolate(activity_color, E->get().activity);
+ tocolor = tocolor.linear_interpolate(activity_color, E->get().activity);
+ }
_draw_cos_line(connections_layer, frompos, topos, color, tocolor);
}
@@ -980,6 +987,23 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
}
}
+void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) {
+
+ for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
+
+ if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) {
+
+ if (ABS(E->get().activity != p_activity)) {
+ //update only if changed
+ top_layer->update();
+ connections_layer->update();
+ }
+ E->get().activity = p_activity;
+ return;
+ }
+ }
+}
+
void GraphEdit::clear_connections() {
connections.clear();
@@ -1141,11 +1165,16 @@ void GraphEdit::_snap_value_changed(double) {
update();
}
+HBoxContainer *GraphEdit::get_zoom_hbox() {
+ return zoom_hb;
+}
+
void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node);
ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected);
ClassDB::bind_method(D_METHOD("disconnect_node", "from", "from_port", "to", "to_port"), &GraphEdit::disconnect_node);
+ ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity);
ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list);
ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections);
ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs);
@@ -1187,6 +1216,8 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset);
ClassDB::bind_method(D_METHOD("_connections_layer_draw"), &GraphEdit::_connections_layer_draw);
+ ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox);
+
ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
@@ -1253,7 +1284,7 @@ GraphEdit::GraphEdit() {
zoom = 1;
- HBoxContainer *zoom_hb = memnew(HBoxContainer);
+ zoom_hb = memnew(HBoxContainer);
top_layer->add_child(zoom_hb);
zoom_hb->set_position(Vector2(10, 10));
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 3bfde44854..14789001e4 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -31,6 +31,7 @@
#ifndef GRAPH_EDIT_H
#define GRAPH_EDIT_H
+#include "scene/gui/box_container.h"
#include "scene/gui/graph_node.h"
#include "scene/gui/scroll_bar.h"
#include "scene/gui/slider.h"
@@ -62,6 +63,7 @@ public:
StringName to;
int from_port;
int to_port;
+ float activity;
};
private:
@@ -157,6 +159,8 @@ private:
Set<int> valid_left_disconnect_types;
Set<int> valid_right_disconnect_types;
+ HBoxContainer *zoom_hb;
+
friend class GraphEditFilter;
bool _filter_input(const Point2 &p_point);
void _snap_toggled();
@@ -175,6 +179,8 @@ public:
void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void clear_connections();
+ void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity);
+
void add_valid_connection_type(int p_type, int p_with_type);
void remove_valid_connection_type(int p_type, int p_with_type);
bool is_valid_connection_type(int p_type, int p_with_type) const;
@@ -206,6 +212,8 @@ public:
int get_snap() const;
void set_snap(int p_snap);
+ HBoxContainer *get_zoom_hbox();
+
GraphEdit();
};
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index e57af0a4c0..0cd5219f8f 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -215,12 +215,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
case (KEY_A): { //Select All
select();
} break;
+#ifdef APPLE_STYLE_KEYS
case (KEY_LEFT): { // Go to start of text - like HOME key
set_cursor_position(0);
} break;
case (KEY_RIGHT): { // Go to end of text - like END key
set_cursor_position(text.length());
} break;
+#endif
default: { handled = false; }
}
@@ -563,6 +565,9 @@ void LineEdit::_notification(int p_what) {
#endif
case NOTIFICATION_RESIZED: {
+ if (expand_to_text_length) {
+ window_pos = 0; //force scroll back since it's expanding to text length
+ }
set_cursor_position(get_cursor_position());
} break;
@@ -608,7 +613,8 @@ void LineEdit::_notification(int p_what) {
}
int x_ofs = 0;
- int cached_text_width = text.empty() ? cached_placeholder_width : cached_width;
+ bool using_placeholder = text.empty();
+ int cached_text_width = using_placeholder ? cached_placeholder_width : cached_width;
switch (align) {
@@ -643,9 +649,9 @@ void LineEdit::_notification(int p_what) {
Color font_color_selected = get_color("font_color_selected");
Color cursor_color = get_color("cursor_color");
- const String &t = text.empty() ? placeholder : text;
+ const String &t = using_placeholder ? placeholder : text;
// draw placeholder color
- if (text.empty())
+ if (using_placeholder)
font_color.a *= placeholder_alpha;
font_color.a *= disabled_alpha;
@@ -754,7 +760,8 @@ void LineEdit::_notification(int p_what) {
if (has_focus()) {
- OS::get_singleton()->set_ime_position(get_global_position() + Point2(x_ofs, y_ofs + caret_height));
+ OS::get_singleton()->set_ime_active(true);
+ OS::get_singleton()->set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + caret_height));
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
}
} break;
@@ -764,6 +771,7 @@ void LineEdit::_notification(int p_what) {
draw_caret = true;
}
+ OS::get_singleton()->set_ime_active(true);
Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height;
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos);
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
@@ -776,6 +784,7 @@ void LineEdit::_notification(int p_what) {
OS::get_singleton()->set_ime_position(Point2());
OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL);
+ OS::get_singleton()->set_ime_active(false);
ime_text = "";
ime_selection = Point2();
@@ -1005,7 +1014,6 @@ void LineEdit::set_text(String p_text) {
update();
cursor_pos = 0;
window_pos = 0;
- _text_changed();
}
void LineEdit::clear() {
@@ -1093,11 +1101,12 @@ void LineEdit::set_cursor_position(int p_pos) {
for (int i = cursor_pos; i >= window_pos; i--) {
if (i >= text.length()) {
- accum_width = font->get_char_size(' ').width; //anything should do
+ //do not do this, because if the cursor is at the end, its just fine that it takes no space
+ //accum_width = font->get_char_size(' ').width; //anything should do
} else {
accum_width += font->get_char_size(text[i], i + 1 < text.length() ? text[i + 1] : 0).width; //anything should do
}
- if (accum_width >= window_width)
+ if (accum_width > window_width)
break;
wp = i;
@@ -1164,7 +1173,7 @@ Size2 LineEdit::get_minimum_size() const {
int mstext = get_constant("minimum_spaces") * space_size;
if (expand_to_text_length) {
- mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact
+ mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact, and because cursor needs a bit more when at the end
}
min.width += mstext;
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index d18a3a3f2f..26d01ecc09 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -113,37 +113,9 @@ void Popup::set_as_minsize() {
void Popup::popup_centered_minsize(const Size2 &p_minsize) {
- Size2 total_minsize = p_minsize;
-
- for (int i = 0; i < get_child_count(); i++) {
-
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c)
- continue;
- if (!c->is_visible())
- continue;
-
- Size2 minsize = c->get_combined_minimum_size();
-
- for (int j = 0; j < 2; j++) {
-
- Margin m_beg = Margin(0 + j);
- Margin m_end = Margin(2 + j);
-
- float margin_begin = c->get_margin(m_beg);
- float margin_end = c->get_margin(m_end);
- float anchor_begin = c->get_anchor(m_beg);
- float anchor_end = c->get_anchor(m_end);
-
- minsize[j] += margin_begin * (ANCHOR_END - anchor_begin) + margin_end * anchor_end;
- }
-
- total_minsize.width = MAX(total_minsize.width, minsize.width);
- total_minsize.height = MAX(total_minsize.height, minsize.height);
- }
-
- popup_centered(total_minsize);
- popped_up = true;
+ set_custom_minimum_size(p_minsize);
+ _fix_size();
+ popup_centered();
}
void Popup::popup_centered(const Size2 &p_size) {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index fd2466407e..18e609c798 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -440,6 +440,8 @@ void PopupMenu::_notification(int p_what) {
float h;
Size2 icon_size;
+ Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1);
+
item_ofs.x += items[i].h_ofs;
if (!items[i].icon.is_null()) {
@@ -463,18 +465,18 @@ void PopupMenu::_notification(int p_what) {
if (items[i].checkable_type) {
Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr();
- icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)));
+ icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
item_ofs.x += icon->get_width() + hseparation;
}
if (!items[i].icon.is_null()) {
- items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)));
+ items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
item_ofs.x += items[i].icon->get_width();
item_ofs.x += hseparation;
}
if (items[i].submenu != "") {
- submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2));
+ submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
}
item_ofs.y += font->get_ascent();
@@ -533,6 +535,7 @@ void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label,
item.ID = p_ID;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) {
@@ -543,6 +546,7 @@ void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) {
item.ID = p_ID;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_ID) {
@@ -554,6 +558,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.submenu = p_submenu;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
@@ -567,6 +572,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
@@ -579,6 +585,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
@@ -586,6 +593,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p
add_check_item(p_label, p_ID, p_accel);
items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
update();
+ minimum_size_changed();
}
void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
@@ -593,6 +601,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const Stri
add_icon_check_item(p_icon, p_label, p_ID, p_accel);
items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
update();
+ minimum_size_changed();
}
void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
@@ -608,6 +617,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut
item.shortcut_is_global = p_global;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
@@ -622,6 +632,7 @@ void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_g
item.shortcut_is_global = p_global;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
@@ -638,6 +649,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh
item.shortcut_is_global = p_global;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
@@ -653,6 +665,7 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bo
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
@@ -660,6 +673,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_
add_check_shortcut(p_shortcut, p_ID, p_global);
items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
update();
+ minimum_size_changed();
}
void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID, uint32_t p_accel) {
@@ -673,6 +687,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.state = p_default_state;
items.push_back(item);
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_text(int p_idx, const String &p_text) {
@@ -682,6 +697,7 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
items[p_idx].xl_text = tr(p_text);
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) {
@@ -689,6 +705,7 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) {
items[p_idx].icon = p_icon;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
@@ -697,6 +714,7 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
items[p_idx].checked = p_checked;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_id(int p_idx, int p_ID) {
@@ -704,6 +722,7 @@ void PopupMenu::set_item_id(int p_idx, int p_ID) {
items[p_idx].ID = p_ID;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
@@ -712,6 +731,7 @@ void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
items[p_idx].accel = p_accel;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
@@ -719,6 +739,7 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
ERR_FAIL_INDEX(p_idx, items.size());
items[p_idx].metadata = p_meta;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
@@ -726,6 +747,7 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
ERR_FAIL_INDEX(p_idx, items.size());
items[p_idx].disabled = p_disabled;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
@@ -733,6 +755,7 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
ERR_FAIL_INDEX(p_idx, items.size());
items[p_idx].submenu = p_submenu;
update();
+ minimum_size_changed();
}
void PopupMenu::toggle_item_checked(int p_idx) {
@@ -740,6 +763,7 @@ void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items[p_idx].checked = !items[p_idx].checked;
update();
+ minimum_size_changed();
}
String PopupMenu::get_item_text(int p_idx) const {
@@ -881,6 +905,7 @@ void PopupMenu::set_item_h_offset(int p_idx, int p_offset) {
ERR_FAIL_INDEX(p_idx, items.size());
items[p_idx].h_ofs = p_offset;
update();
+ minimum_size_changed();
}
void PopupMenu::set_item_multistate(int p_idx, int p_state) {
@@ -890,6 +915,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
update();
}
+void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
+
+ ERR_FAIL_INDEX(p_idx, items.size());
+ items[p_idx].shortcut_is_disabled = p_disabled;
+ update();
+}
+
void PopupMenu::toggle_item_multistate(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
@@ -914,6 +946,12 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const {
return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
}
+bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx, items.size(), false);
+ return items[p_idx].shortcut_is_disabled;
+}
+
int PopupMenu::get_item_count() const {
return items.size();
@@ -940,7 +978,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
int il = items.size();
for (int i = 0; i < il; i++) {
- if (is_item_disabled(i))
+ if (is_item_disabled(i) || items[i].shortcut_is_disabled)
continue;
if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) {
@@ -1045,6 +1083,7 @@ void PopupMenu::clear() {
items.clear();
mouse_over = -1;
update();
+ minimum_size_changed();
}
Array PopupMenu::_get_items() const {
@@ -1224,6 +1263,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate);
+ ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked);
ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate);
@@ -1240,6 +1280,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable);
+ ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index fde91bd845..d3ee9be1c0 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -64,6 +64,7 @@ class PopupMenu : public Popup {
int h_ofs;
Ref<ShortCut> shortcut;
bool shortcut_is_global;
+ bool shortcut_is_disabled;
Item() {
checked = false;
@@ -76,6 +77,7 @@ class PopupMenu : public Popup {
_ofs_cache = 0;
h_ofs = 0;
shortcut_is_global = false;
+ shortcut_is_disabled = false;
}
};
@@ -149,6 +151,7 @@ public:
void set_item_h_offset(int p_idx, int p_offset);
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
+ void set_item_shortcut_disabled(int p_idx, bool p_disabled);
void toggle_item_checked(int p_idx);
@@ -165,6 +168,7 @@ public:
bool is_item_separator(int p_idx) const;
bool is_item_checkable(int p_idx) const;
bool is_item_radio_checkable(int p_idx) const;
+ bool is_item_shortcut_disabled(int p_idx) const;
String get_item_tooltip(int p_idx) const;
Ref<ShortCut> get_item_shortcut(int p_idx) const;
int get_item_state(int p_idx) const;
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index 37e519e375..fc5d56237a 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -39,9 +39,9 @@ Size2 ProgressBar::get_minimum_size() const {
Size2 minimum_size = bg->get_minimum_size();
minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height);
minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width);
- if (percent_visible) {
- minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height());
- }
+ //if (percent_visible) { this is needed, else the progressbar will collapse
+ minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height());
+ //}
return minimum_size;
}
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 2dd5c64378..495d618930 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -241,10 +241,10 @@ void ScrollContainer::_notification(int p_what) {
size -= sb->get_minimum_size();
ofs += sb->get_offset();
- if (h_scroll->is_visible_in_tree())
+ if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) //scrolls may have been moved out for reasons
size.y -= h_scroll->get_minimum_size().y;
- if (v_scroll->is_visible_in_tree())
+ if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) //scrolls may have been moved out for reasons
size.x -= h_scroll->get_minimum_size().x;
for (int i = 0; i < get_child_count(); i++) {
@@ -482,6 +482,16 @@ String ScrollContainer::get_configuration_warning() const {
return "";
}
+HScrollBar *ScrollContainer::get_h_scrollbar() {
+
+ return h_scroll;
+}
+
+VScrollBar *ScrollContainer::get_v_scrollbar() {
+
+ return v_scroll;
+}
+
void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("_scroll_moved"), &ScrollContainer::_scroll_moved);
@@ -498,6 +508,9 @@ void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone);
ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone);
+ ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar);
+ ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar);
+
ADD_SIGNAL(MethodInfo("scroll_started"));
ADD_SIGNAL(MethodInfo("scroll_ended"));
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index 3fe1ed447a..abef80294a 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -92,6 +92,9 @@ public:
int get_deadzone() const;
void set_deadzone(int p_deadzone);
+ HScrollBar *get_h_scrollbar();
+ VScrollBar *get_v_scrollbar();
+
virtual bool clips_input() const;
virtual String get_configuration_warning() const;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 0363dd44c2..4f72b5c6ed 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -143,6 +143,42 @@ void TabContainer::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_RESIZED: {
+
+ Vector<Control *> tabs = _get_tabs();
+ int side_margin = get_constant("side_margin");
+ Ref<Texture> menu = get_icon("menu");
+ Ref<Texture> increment = get_icon("increment");
+ Ref<Texture> decrement = get_icon("decrement");
+ int header_width = get_size().width - side_margin * 2;
+
+ // Find the width of the header area.
+ if (popup)
+ header_width -= menu->get_width();
+ if (buttons_visible_cache)
+ header_width -= increment->get_width() + decrement->get_width();
+ if (popup || buttons_visible_cache)
+ header_width += side_margin;
+
+ // Find the width of all tabs after first_tab_cache.
+ int all_tabs_width = 0;
+ for (int i = first_tab_cache; i < tabs.size(); i++) {
+ int tab_width = _get_tab_width(i);
+ all_tabs_width += tab_width;
+ }
+
+ // Check if tabs before first_tab_cache would fit into the header area.
+ for (int i = first_tab_cache - 1; i >= 0; i--) {
+ int tab_width = _get_tab_width(i);
+
+ if (all_tabs_width + tab_width > header_width)
+ break;
+
+ all_tabs_width += tab_width;
+ first_tab_cache--;
+ }
+ } break;
+
case NOTIFICATION_DRAW: {
RID canvas = get_canvas_item();
@@ -197,6 +233,10 @@ void TabContainer::_notification(int p_what) {
header_width += side_margin;
}
+ if (!buttons_visible_cache) {
+ first_tab_cache = 0;
+ }
+
// Go through the visible tabs to find the width they occupy.
all_tabs_width = 0;
Vector<int> tab_widths;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index c6ff8489c0..215ba0271f 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -447,7 +447,7 @@ void TextEdit::_click_selection_held() {
}
void TextEdit::_update_selection_mode_pointer() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -462,7 +462,7 @@ void TextEdit::_update_selection_mode_pointer() {
}
void TextEdit::_update_selection_mode_word() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -515,7 +515,7 @@ void TextEdit::_update_selection_mode_word() {
}
void TextEdit::_update_selection_mode_line() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -1388,6 +1388,7 @@ void TextEdit::_notification(int p_what) {
}
if (has_focus()) {
+ OS::get_singleton()->set_ime_active(true);
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()));
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
}
@@ -1399,6 +1400,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
+ OS::get_singleton()->set_ime_active(true);
Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height();
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos);
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
@@ -1413,6 +1415,7 @@ void TextEdit::_notification(int p_what) {
OS::get_singleton()->set_ime_position(Point2());
OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL);
+ OS::get_singleton()->set_ime_active(false);
ime_text = "";
ime_selection = Point2();
@@ -2950,13 +2953,13 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
case KEY_A: {
#ifndef APPLE_STYLE_KEYS
- if (!k->get_command() || k->get_shift() || k->get_alt()) {
+ if (!k->get_control() || k->get_shift() || k->get_alt()) {
scancode_handled = false;
break;
}
select_all();
#else
- if (k->get_alt() || (!k->get_shift() && !k->get_command() && !k->get_control())) {
+ if ((!k->get_command() && !k->get_control())) {
scancode_handled = false;
break;
}
@@ -4110,7 +4113,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
void TextEdit::set_text(String p_text) {
setting_text = true;
- clear();
+ _clear();
_insert_text_at_cursor(p_text);
clear_undo_history();
cursor.column = 0;
@@ -4123,7 +4126,7 @@ void TextEdit::set_text(String p_text) {
cursor_set_column(0);
update();
setting_text = false;
- _text_changed_emit();
+
//get_range()->set(0);
};
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 3643aedb85..4dc7b03685 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -40,7 +40,6 @@
#include "viewport.h"
VARIANT_ENUM_CAST(Node::PauseMode);
-VARIANT_ENUM_CAST(Node::RPCMode);
void Node::_notification(int p_notification) {
@@ -485,18 +484,18 @@ bool Node::is_network_master() const {
/***** RPC CONFIG ********/
-void Node::rpc_config(const StringName &p_method, RPCMode p_mode) {
+void Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode) {
- if (p_mode == RPC_MODE_DISABLED) {
+ if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) {
data.rpc_methods.erase(p_method);
} else {
data.rpc_methods[p_method] = p_mode;
};
}
-void Node::rset_config(const StringName &p_property, RPCMode p_mode) {
+void Node::rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode) {
- if (p_mode == RPC_MODE_DISABLED) {
+ if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) {
data.rpc_properties.erase(p_property);
} else {
data.rpc_properties[p_property] = p_mode;
@@ -718,121 +717,14 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {
multiplayer = p_multiplayer;
}
-const Map<StringName, Node::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) {
+const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) {
return data.rpc_methods.find(p_method);
}
-const Map<StringName, Node::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) {
+const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) {
return data.rpc_properties.find(p_property);
}
-bool Node::can_call_rpc(const StringName &p_method, int p_from) const {
-
- const Map<StringName, RPCMode>::Element *E = data.rpc_methods.find(p_method);
- if (E) {
-
- switch (E->get()) {
-
- case RPC_MODE_DISABLED: {
- return false;
- } break;
- case RPC_MODE_REMOTE: {
- return true;
- } break;
- case RPC_MODE_SYNC: {
- return true;
- } break;
- case RPC_MODE_MASTER: {
- return is_network_master();
- } break;
- case RPC_MODE_SLAVE: {
- return !is_network_master() && p_from == get_network_master();
- } break;
- }
- }
-
- if (get_script_instance()) {
- //attempt with script
- ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rpc_mode(p_method);
-
- switch (rpc_mode) {
-
- case ScriptInstance::RPC_MODE_DISABLED: {
- return false;
- } break;
- case ScriptInstance::RPC_MODE_REMOTE: {
- return true;
- } break;
- case ScriptInstance::RPC_MODE_SYNC: {
- return true;
- } break;
- case ScriptInstance::RPC_MODE_MASTER: {
- return is_network_master();
- } break;
- case ScriptInstance::RPC_MODE_SLAVE: {
- return !is_network_master() && p_from == get_network_master();
- } break;
- }
- }
-
- ERR_PRINTS("RPC from " + itos(p_from) + " on unauthorized method attempted: " + String(p_method) + " on base: " + String(Variant(this)));
- return false;
-}
-
-bool Node::can_call_rset(const StringName &p_property, int p_from) const {
-
- const Map<StringName, RPCMode>::Element *E = data.rpc_properties.find(p_property);
- if (E) {
-
- switch (E->get()) {
-
- case RPC_MODE_DISABLED: {
- return false;
- } break;
- case RPC_MODE_REMOTE: {
- return true;
- } break;
- case RPC_MODE_SYNC: {
- return true;
- } break;
- case RPC_MODE_MASTER: {
- return is_network_master();
- } break;
- case RPC_MODE_SLAVE: {
- return !is_network_master() && p_from == get_network_master();
- } break;
- }
- }
-
- if (get_script_instance()) {
- //attempt with script
- ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rset_mode(p_property);
-
- switch (rpc_mode) {
-
- case ScriptInstance::RPC_MODE_DISABLED: {
- return false;
- } break;
- case ScriptInstance::RPC_MODE_REMOTE: {
- return true;
- } break;
- case ScriptInstance::RPC_MODE_SYNC: {
- return true;
- } break;
- case ScriptInstance::RPC_MODE_MASTER: {
- return is_network_master();
- } break;
- case ScriptInstance::RPC_MODE_SLAVE: {
- return !is_network_master() && p_from == get_network_master();
- } break;
- }
- }
-
- ERR_PRINTS("RSET from " + itos(p_from) + " on unauthorized property attempted: " + String(p_property) + " on base: " + String(Variant(this)));
-
- return false;
-}
-
bool Node::can_process() const {
ERR_FAIL_COND_V(!is_inside_tree(), false);
@@ -1203,6 +1095,10 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) {
}
void Node::add_child_below_node(Node *p_node, Node *p_child, bool p_legible_unique_name) {
+
+ ERR_FAIL_NULL(p_node);
+ ERR_FAIL_NULL(p_child);
+
add_child(p_child, p_legible_unique_name);
if (is_a_parent_of(p_node)) {
@@ -2802,12 +2698,6 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
- BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
- BIND_ENUM_CONSTANT(RPC_MODE_REMOTE);
- BIND_ENUM_CONSTANT(RPC_MODE_SYNC);
- BIND_ENUM_CONSTANT(RPC_MODE_MASTER);
- BIND_ENUM_CONSTANT(RPC_MODE_SLAVE);
-
BIND_ENUM_CONSTANT(PAUSE_MODE_INHERIT);
BIND_ENUM_CONSTANT(PAUSE_MODE_STOP);
BIND_ENUM_CONSTANT(PAUSE_MODE_PROCESS);
diff --git a/scene/main/node.h b/scene/main/node.h
index 540f34cba7..341349de79 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -65,15 +65,6 @@ public:
#endif
};
- enum RPCMode {
-
- RPC_MODE_DISABLED, //no rpc for this method, calls to this will be blocked (default)
- RPC_MODE_REMOTE, // using rpc() on it will call method / set property in all other peers
- RPC_MODE_SYNC, // using rpc() on it will call method / set property in all other peers and locally
- RPC_MODE_MASTER, // usinc rpc() on it will call method on wherever the master is, be it local or remote
- RPC_MODE_SLAVE, // usinc rpc() on it will call method for all slaves, be it local or remote
- };
-
struct Comparator {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
@@ -120,8 +111,8 @@ private:
Node *pause_owner;
int network_master;
- Map<StringName, RPCMode> rpc_methods;
- Map<StringName, RPCMode> rpc_properties;
+ Map<StringName, MultiplayerAPI::RPCMode> rpc_methods;
+ Map<StringName, MultiplayerAPI::RPCMode> rpc_properties;
// variables used to properly sort the node when processing, ignored otherwise
//should move all the stuff below to bits
@@ -404,8 +395,8 @@ public:
int get_network_master() const;
bool is_network_master() const;
- void rpc_config(const StringName &p_method, RPCMode p_mode); // config a local method for RPC
- void rset_config(const StringName &p_property, RPCMode p_mode); // config a local property for RPC
+ void rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode); // config a local method for RPC
+ void rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode); // config a local property for RPC
void rpc(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode
void rpc_unreliable(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode
@@ -423,11 +414,8 @@ public:
Ref<MultiplayerAPI> get_multiplayer() const;
Ref<MultiplayerAPI> get_custom_multiplayer() const;
void set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer);
- const Map<StringName, RPCMode>::Element *get_node_rpc_mode(const StringName &p_method);
- const Map<StringName, RPCMode>::Element *get_node_rset_mode(const StringName &p_property);
-
- bool can_call_rpc(const StringName &p_method, int p_from) const;
- bool can_call_rset(const StringName &p_property, int p_from) const;
+ const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rpc_mode(const StringName &p_method);
+ const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rset_mode(const StringName &p_property);
Node();
~Node();
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 607dbebf6c..8d6e57b335 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -485,7 +485,9 @@ bool SceneTree::idle(float p_time) {
idle_process_time = p_time;
- multiplayer->poll();
+ if (multiplayer_poll) {
+ multiplayer->poll();
+ }
emit_signal("idle_frame");
@@ -1672,6 +1674,14 @@ Ref<MultiplayerAPI> SceneTree::get_multiplayer() const {
return multiplayer;
}
+void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) {
+ multiplayer_poll = p_enabled;
+}
+
+bool SceneTree::is_multiplayer_poll_enabled() const {
+ return multiplayer_poll;
+}
+
void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {
ERR_FAIL_COND(!p_multiplayer.is_valid());
@@ -1802,6 +1812,8 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer"), &SceneTree::set_multiplayer);
ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer);
+ ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled);
+ ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled);
ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &SceneTree::set_network_peer);
ClassDB::bind_method(D_METHOD("get_network_peer"), &SceneTree::get_network_peer);
ClassDB::bind_method(D_METHOD("is_network_server"), &SceneTree::is_network_server);
@@ -1830,6 +1842,7 @@ void SceneTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "NetworkedMultiplayerPeer", 0), "set_network_peer", "get_network_peer");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_multiplayer", "get_multiplayer");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node")));
@@ -1934,6 +1947,7 @@ SceneTree::SceneTree() {
root->set_world(Ref<World>(memnew(World)));
// Initialize network state
+ multiplayer_poll = true;
set_multiplayer(Ref<MultiplayerAPI>(memnew(MultiplayerAPI)));
//root->set_world_2d( Ref<World2D>( memnew( World2D )));
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 6e0156546e..aa8d78b1e1 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -186,6 +186,7 @@ private:
///network///
Ref<MultiplayerAPI> multiplayer;
+ bool multiplayer_poll;
void _network_peer_connected(int p_id);
void _network_peer_disconnected(int p_id);
@@ -411,6 +412,8 @@ public:
//network API
Ref<MultiplayerAPI> get_multiplayer() const;
+ void set_multiplayer_poll_enabled(bool p_enabled);
+ bool is_multiplayer_poll_enabled() const;
void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer);
void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer);
Ref<NetworkedMultiplayerPeer> get_network_peer() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index f631fd6f3a..1236aea2dd 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1308,13 +1308,37 @@ void Viewport::_gui_cancel_tooltip() {
}
}
+String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) {
+
+ Vector2 pos = p_pos;
+ String tooltip;
+
+ while (p_control) {
+
+ tooltip = p_control->get_tooltip(pos);
+
+ if (tooltip != String())
+ break;
+ pos = p_control->get_transform().xform(pos);
+
+ if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP)
+ break;
+ if (p_control->is_set_as_toplevel())
+ break;
+
+ p_control = p_control->get_parent_control();
+ }
+
+ return tooltip;
+}
+
void Viewport::_gui_show_tooltip() {
if (!gui.tooltip) {
return;
}
- String tooltip = gui.tooltip->get_tooltip(gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos));
+ String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos));
if (tooltip.length() == 0)
return; // bye
@@ -1388,12 +1412,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu
Control *control = Object::cast_to<Control>(ci);
if (control) {
- control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev);
+
+ control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it)
if (gui.key_event_accepted)
break;
if (!control->is_inside_tree())
break;
- control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev);
+ control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev);
+
if (!control->is_inside_tree() || control->is_set_as_toplevel())
break;
if (gui.key_event_accepted)
@@ -1562,7 +1588,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (mb->is_pressed()) {
Size2 pos = mpos;
- if (gui.mouse_focus && mb->get_button_index() != gui.mouse_focus_button && mb->get_button_index() == BUTTON_LEFT) {
+ if (gui.mouse_focus && mb->get_button_index() != gui.mouse_focus_button) {
//do not steal mouse focus and stuff
@@ -1864,7 +1890,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (gui.tooltip_popup) {
if (can_tooltip) {
- String tooltip = over->get_tooltip(gui.tooltip->get_global_transform().xform_inv(mpos));
+ String tooltip = _gui_get_tooltip(over, gui.tooltip->get_global_transform().xform_inv(mpos));
if (tooltip.length() == 0)
_gui_cancel_tooltip();
@@ -1886,7 +1912,23 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
mm->set_position(pos);
- Control::CursorShape cursor_shape = over->get_cursor_shape(pos);
+ Control::CursorShape cursor_shape = Control::CURSOR_ARROW;
+ {
+ Control *c = over;
+ Vector2 cpos = pos;
+ while (c) {
+ cursor_shape = c->get_cursor_shape(cpos);
+ cpos = c->get_transform().xform(cpos);
+ if (cursor_shape != Control::CURSOR_ARROW)
+ break;
+ if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP)
+ break;
+ if (c->is_set_as_toplevel())
+ break;
+ c = c->get_parent_control();
+ }
+ }
+
OS::get_singleton()->set_cursor_shape((OS::CursorShape)cursor_shape);
if (over->can_process()) {
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index c1ef58de69..3000398540 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -312,6 +312,7 @@ private:
void _gui_remove_root_control(List<Control *>::Element *RI);
void _gui_remove_subwindow_control(List<Control *>::Element *SI);
+ String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos);
void _gui_cancel_tooltip();
void _gui_show_tooltip();
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 7533fa5f6c..244d95d9c7 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -63,6 +63,9 @@
#include "scene/2d/tile_map.h"
#include "scene/2d/visibility_notifier_2d.h"
#include "scene/2d/y_sort.h"
+#include "scene/animation/animation_blend_space.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_graph_player.h"
#include "scene/animation/animation_player.h"
#include "scene/animation/animation_tree_player.h"
#include "scene/animation/tween.h"
@@ -382,6 +385,21 @@ void register_scene_types() {
ClassDB::register_class<NavigationMesh>();
ClassDB::register_class<Navigation>();
+ ClassDB::register_class<AnimationGraphPlayer>();
+ ClassDB::register_class<AnimationNode>();
+ ClassDB::register_class<AnimationRootNode>();
+ ClassDB::register_class<AnimationNodeBlendTree>();
+ ClassDB::register_class<AnimationNodeBlendSpace>();
+ ClassDB::register_class<AnimationNodeOutput>();
+ ClassDB::register_class<AnimationNodeOneShot>();
+ ClassDB::register_class<AnimationNodeAnimation>();
+ ClassDB::register_class<AnimationNodeAdd>();
+ ClassDB::register_class<AnimationNodeBlend2>();
+ ClassDB::register_class<AnimationNodeBlend3>();
+ ClassDB::register_class<AnimationNodeTimeScale>();
+ ClassDB::register_class<AnimationNodeTimeSeek>();
+ ClassDB::register_class<AnimationNodeTransition>();
+
OS::get_singleton()->yield(); //may take time to init
ClassDB::register_virtual_class<CollisionObject>();
@@ -404,6 +422,7 @@ void register_scene_types() {
ClassDB::register_class<Curve3D>();
ClassDB::register_class<Path>();
ClassDB::register_class<PathFollow>();
+ ClassDB::register_class<OrientedPathFollow>();
ClassDB::register_class<VisibilityNotifier>();
ClassDB::register_class<VisibilityEnabler>();
ClassDB::register_class<WorldEnvironment>();
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 7a1fffaa26..fe4d687c23 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -32,6 +32,8 @@
#include "geometry.h"
+#define ANIM_MIN_LENGTH 0.001
+
bool Animation::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
@@ -54,6 +56,15 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
} else if (type == "method") {
add_track(TYPE_METHOD);
+ } else if (type == "bezier") {
+
+ add_track(TYPE_BEZIER);
+ } else if (type == "audio") {
+
+ add_track(TYPE_AUDIO);
+ } else if (type == "animation") {
+
+ add_track(TYPE_ANIMATION);
} else {
return false;
@@ -163,7 +174,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
while (track_get_key_count(track))
track_remove_key(track, 0); //well shouldn't be set anyway
@@ -201,6 +212,114 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
}
}
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("points"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolRealArray values = d["points"];
+
+ ERR_FAIL_COND_V(times.size() * 5 != values.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<float>::Read rv = values.read();
+
+ bt->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ bt->values[i].time = rt[i];
+ bt->values[i].transition = 0; //unused in bezier
+ bt->values[i].value.value = rv[i * 5 + 0];
+ bt->values[i].value.in_handle.x = rv[i * 5 + 1];
+ bt->values[i].value.in_handle.y = rv[i * 5 + 2];
+ bt->values[i].value.out_handle.x = rv[i * 5 + 3];
+ bt->values[i].value.out_handle.y = rv[i * 5 + 4];
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ Array clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+
+ ad->values.clear();
+
+ for (int i = 0; i < valcount; i++) {
+
+ Dictionary d = clips[i];
+ if (!d.has("start_offset"))
+ continue;
+ if (!d.has("end_offset"))
+ continue;
+ if (!d.has("stream"))
+ continue;
+
+ TKey<AudioKey> ak;
+ ak.time = rt[i];
+ ak.value.start_offset = d["start_offset"];
+ ak.value.end_offset = d["end_offset"];
+ ak.value.stream = d["stream"];
+
+ ad->values.push_back(ak);
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolVector<String> clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<String>::Read rc = clips.read();
+
+ an->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ TKey<StringName> ak;
+ ak.time = rt[i];
+ ak.value = rc[i];
+ an->values[i] = ak;
+ }
+ }
+
+ return true;
+ } else {
+ return false;
}
} else
return false;
@@ -232,6 +351,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
case TYPE_TRANSFORM: r_ret = "transform"; break;
case TYPE_VALUE: r_ret = "value"; break;
case TYPE_METHOD: r_ret = "method"; break;
+ case TYPE_BEZIER: r_ret = "bezier"; break;
+ case TYPE_AUDIO: r_ret = "audio"; break;
+ case TYPE_ANIMATION: r_ret = "animation"; break;
}
return true;
@@ -329,7 +451,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
Dictionary d;
@@ -368,6 +490,119 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = d;
return true;
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ const BezierTrack *bt = static_cast<const BezierTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<float> key_points;
+
+ int kk = bt->values.size();
+
+ key_times.resize(kk);
+ key_points.resize(kk * 5);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<float>::Write wpo = key_points.write();
+
+ int idx = 0;
+
+ const TKey<BezierKey> *vls = bt->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ wpo[idx * 5 + 0] = vls[i].value.value;
+ wpo[idx * 5 + 1] = vls[i].value.in_handle.x;
+ wpo[idx * 5 + 2] = vls[i].value.in_handle.y;
+ wpo[idx * 5 + 3] = vls[i].value.out_handle.x;
+ wpo[idx * 5 + 4] = vls[i].value.out_handle.y;
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+ wpo = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["points"] = key_points;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ Array clips;
+
+ int kk = ad->values.size();
+
+ key_times.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+
+ int idx = 0;
+
+ const TKey<AudioKey> *vls = ad->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ Dictionary clip;
+ clip["start_offset"] = vls[i].value.start_offset;
+ clip["end_offset"] = vls[i].value.end_offset;
+ clip["stream"] = vls[i].value.stream;
+ clips.push_back(clip);
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<String> clips;
+
+ int kk = an->values.size();
+
+ key_times.resize(kk);
+ clips.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<String>::Write wcl = clips.write();
+
+ const TKey<StringName> *vls = an->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[i] = vls[i].time;
+ wcl[i] = vls[i].value;
+ }
+
+ wti = PoolVector<float>::Write();
+ wcl = PoolVector<String>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
}
} else
return false;
@@ -412,6 +647,21 @@ int Animation::add_track(TrackType p_type, int p_at_pos) {
tracks.insert(p_at_pos, memnew(MethodTrack));
} break;
+ case TYPE_BEZIER: {
+
+ tracks.insert(p_at_pos, memnew(BezierTrack));
+
+ } break;
+ case TYPE_AUDIO: {
+
+ tracks.insert(p_at_pos, memnew(AudioTrack));
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ tracks.insert(p_at_pos, memnew(AnimationTrack));
+
+ } break;
default: {
ERR_PRINT("Unknown track type");
@@ -446,6 +696,24 @@ void Animation::remove_track(int p_track) {
_clear(mt->methods);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ _clear(bz->values);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ _clear(ad->values);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ _clear(an->values);
+
+ } break;
}
memdelete(t);
@@ -642,6 +910,27 @@ void Animation::track_remove_key(int p_track, int p_idx) {
mt->methods.remove(p_idx);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, bz->values.size());
+ bz->values.remove(p_idx);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, ad->values.size());
+ ad->values.remove(p_idx);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, an->values.size());
+ an->values.remove(p_idx);
+
+ } break;
}
emit_changed();
@@ -686,6 +975,39 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const {
return k;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ int k = _find(bt->values, p_time);
+ if (k < 0 || k >= bt->values.size())
+ return -1;
+ if (bt->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
}
return -1;
@@ -748,6 +1070,51 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key
_insert(p_time, mt->methods, k);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ Array arr = p_key;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = arr[0];
+ k.value.in_handle.x = arr[1];
+ k.value.in_handle.y = arr[2];
+ k.value.out_handle.x = arr[3];
+ k.value.out_handle.y = arr[4];
+ _insert(p_time, bt->values, k);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_key;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ TKey<AudioKey> ak;
+ ak.time = p_time;
+ ak.value.start_offset = k["start_offset"];
+ ak.value.end_offset = k["end_offset"];
+ ak.value.stream = k["stream"];
+ _insert(p_time, at->values, ak);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> ak;
+ ak.time = p_time;
+ ak.value = p_key;
+
+ _insert(p_time, at->values, ak);
+
+ } break;
}
emit_changed();
@@ -776,6 +1143,21 @@ int Animation::track_get_key_count(int p_track) const {
MethodTrack *mt = static_cast<MethodTrack *>(t);
return mt->methods.size();
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ return bt->values.size();
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ return at->values.size();
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ return at->values.size();
+ } break;
}
ERR_FAIL_V(-1);
@@ -817,6 +1199,41 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
return d;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant());
+
+ Array arr;
+ arr.resize(5);
+ arr[0] = bt->values[p_key_idx].value.value;
+ arr[1] = bt->values[p_key_idx].value.in_handle.x;
+ arr[2] = bt->values[p_key_idx].value.in_handle.y;
+ arr[3] = bt->values[p_key_idx].value.out_handle.x;
+ arr[4] = bt->values[p_key_idx].value.out_handle.y;
+ return arr;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ Dictionary k;
+ k["start_offset"] = at->values[p_key_idx].value.start_offset;
+ k["end_offset"] = at->values[p_key_idx].value.end_offset;
+ k["stream"] = at->values[p_key_idx].value.stream;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ return at->values[p_key_idx].value;
+
+ } break;
}
ERR_FAIL_V(Variant());
@@ -849,6 +1266,27 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].time;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), -1);
+ return bt->values[p_key_idx].time;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
}
ERR_FAIL_V(-1);
@@ -881,6 +1319,18 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].transition;
} break;
+ case TYPE_BEZIER: {
+
+ return 1; //bezier does not really use transitions
+ } break;
+ case TYPE_AUDIO: {
+
+ return 1; //audio does not really use transitions
+ } break;
+ case TYPE_ANIMATION: {
+
+ return 1; //animation does not really use transitions
+ } break;
}
ERR_FAIL_V(0);
@@ -923,6 +1373,42 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
if (d.has("args"))
mt->methods[p_key_idx].params = d["args"];
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, bt->values.size());
+
+ Array arr = p_value;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ bt->values[p_key_idx].value.value = arr[0];
+ bt->values[p_key_idx].value.in_handle.x = arr[1];
+ bt->values[p_key_idx].value.in_handle.y = arr[2];
+ bt->values[p_key_idx].value.out_handle.x = arr[3];
+ bt->values[p_key_idx].value.out_handle.y = arr[4];
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_value;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ at->values[p_key_idx].value.start_offset = k["start_offset"];
+ at->values[p_key_idx].value.end_offset = k["end_offset"];
+ at->values[p_key_idx].value.stream = k["stream"];
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ at->values[p_key_idx].value = p_value;
+
+ } break;
}
}
@@ -953,6 +1439,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra
mt->methods[p_key_idx].transition = p_transition;
} break;
+ case TYPE_BEZIER:
+ case TYPE_AUDIO:
+ case TYPE_ANIMATION: {
+ // they dont use transition
+ } break;
}
}
@@ -1410,7 +1901,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_VALUE);
- ERR_FAIL_INDEX(p_mode, 3);
+ ERR_FAIL_INDEX(p_mode, 4);
ValueTrack *vt = static_cast<ValueTrack *>(t);
vt->update_mode = p_mode;
@@ -1426,6 +1917,161 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
return vt->update_mode;
}
+template <class T>
+void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const {
+
+ if (from_time != length && to_time == length)
+ to_time = length * 1.01; //include a little more if at the end
+
+ int to = _find(p_array, to_time);
+
+ // can't really send the events == time, will be sent in the next frame.
+ // if event>=len then it will probably never be requested by the anim player.
+
+ if (to >= 0 && p_array[to].time >= to_time)
+ to--;
+
+ if (to < 0)
+ return; // not bother
+
+ int from = _find(p_array, from_time);
+
+ // position in the right first event.+
+ if (from < 0 || p_array[from].time < from_time)
+ from++;
+
+ int max = p_array.size();
+
+ for (int i = from; i <= to; i++) {
+
+ ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen
+ p_indices->push_back(i);
+ }
+}
+
+void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ const Track *t = tracks[p_track];
+
+ float from_time = p_time - p_delta;
+ float to_time = p_time;
+
+ if (from_time > to_time)
+ SWAP(from_time, to_time);
+
+ if (loop) {
+
+ if (from_time > length || from_time < 0)
+ from_time = Math::fposmod(from_time, length);
+
+ if (to_time > length || to_time < 0)
+ to_time = Math::fposmod(to_time, length);
+
+ if (from_time > to_time) {
+ // handle loop by splitting
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+
+ } break;
+ }
+ return;
+ }
+ } else {
+
+ if (from_time < 0)
+ from_time = 0;
+ if (from_time > length)
+ from_time = length;
+
+ if (to_time < 0)
+ to_time = 0;
+ if (to_time > length)
+ to_time = length;
+ }
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
+
+ } break;
+ }
+}
+
void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const {
if (from_time != length && to_time == length)
@@ -1527,9 +2173,362 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
+int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = p_value;
+ k.value.in_handle = p_in_handle;
+ if (k.value.in_handle.x > 0) {
+ k.value.in_handle.x = 0;
+ }
+ k.value.out_handle = p_out_handle;
+ if (k.value.out_handle.x < 0) {
+ k.value.out_handle.x = 0;
+ }
+
+ int key = _insert(p_time, bt->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.value = p_value;
+ emit_changed();
+}
+
+void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.in_handle = p_handle;
+ if (bt->values[p_index].value.in_handle.x > 0) {
+ bt->values[p_index].value.in_handle.x = 0;
+ }
+ emit_changed();
+}
+void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.out_handle = p_handle;
+ if (bt->values[p_index].value.out_handle.x < 0) {
+ bt->values[p_index].value.out_handle.x = 0;
+ }
+ emit_changed();
+}
+float Animation::bezier_track_get_key_value(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0);
+
+ return bt->values[p_index].value.value;
+}
+Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.in_handle;
+}
+Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.out_handle;
+}
+
+static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = t * t;
+ real_t t3 = t2 * t;
+
+ return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+}
+
+float Animation::bezier_track_interpolate(int p_track, float p_time) const {
+ //this uses a different interpolation scheme
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *track = tracks[p_track];
+ ERR_FAIL_COND_V(track->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(track);
+
+ int len = _find(bt->values, length) + 1; // try to find last key (there may be more past the end)
+
+ if (len <= 0) {
+ // (-1 or -2 returned originally) (plus one above)
+ return 0;
+ } else if (len == 1) { // one key found (0+1), return it
+ return bt->values[0].value.value;
+ }
+
+ int idx = _find(bt->values, p_time);
+
+ ERR_FAIL_COND_V(idx == -2, 0);
+
+ //there really is no looping interpolation on bezier
+
+ if (idx < 0) {
+ return bt->values[0].value.value;
+ }
+
+ if (idx >= bt->values.size() - 1) {
+ return bt->values[bt->values.size() - 1].value.value;
+ }
+
+ float t = p_time - bt->values[idx].time;
+
+ int iterations = 10;
+
+ float low = 0;
+ float high = bt->values[idx + 1].time - bt->values[idx].time;
+ float middle = 0;
+
+ Vector2 start(0, bt->values[idx].value.value);
+ Vector2 start_out = start + bt->values[idx].value.out_handle;
+ Vector2 end(high, bt->values[idx + 1].value.value);
+ Vector2 end_in = end + bt->values[idx + 1].value.in_handle;
+
+ //narrow high and low as much as possible
+ for (int i = 0; i < iterations; i++) {
+
+ middle = (low + high) / 2;
+
+ Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end);
+
+ if (interp.x < t) {
+ low = middle;
+ } else {
+ high = middle;
+ }
+ }
+
+ //interpolate the result:
+ Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end);
+ Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end);
+
+ float c = (t - low_pos.x) / (high_pos.x - low_pos.x);
+
+ return low_pos.linear_interpolate(high_pos, c).y;
+}
+
+int Animation::audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) {
+
+ print_line("really insert key? ");
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ TKey<AudioKey> k;
+ k.time = p_time;
+ k.value.stream = p_stream;
+ k.value.start_offset = p_start_offset;
+ if (k.value.start_offset < 0)
+ k.value.start_offset = 0;
+ k.value.end_offset = p_end_offset;
+ if (k.value.end_offset < 0)
+ k.value.end_offset = 0;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value.stream = p_stream;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.start_offset = p_offset;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.end_offset = p_offset;
+
+ emit_changed();
+}
+
+RES Animation::audio_track_get_key_stream(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), RES());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES());
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), RES());
+
+ return at->values[p_key].value.stream;
+}
+float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.start_offset;
+}
+float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.end_offset;
+}
+
+//
+
+int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> k;
+ k.time = p_time;
+ k.value = p_animation;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_ANIMATION);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value = p_animation;
+
+ emit_changed();
+}
+
+StringName Animation::animation_track_get_key_animation(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), StringName());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, StringName());
+
+ const AnimationTrack *at = static_cast<const AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), StringName());
+
+ return at->values[p_key].value;
+}
+
void Animation::set_length(float p_length) {
- ERR_FAIL_COND(length < 0);
+ if (p_length < ANIM_MIN_LENGTH) {
+ p_length = ANIM_MIN_LENGTH;
+ }
length = p_length;
emit_changed();
}
@@ -1592,6 +2591,16 @@ void Animation::track_move_down(int p_track) {
emit_changed();
}
+void Animation::track_swap(int p_track, int p_with_track) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ ERR_FAIL_INDEX(p_with_track, tracks.size());
+ if (p_track == p_with_track)
+ return;
+ SWAP(tracks[p_track], tracks[p_with_track]);
+ emit_changed();
+}
+
void Animation::set_step(float p_step) {
step = p_step;
@@ -1631,6 +2640,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_move_up", "idx"), &Animation::track_move_up);
ClassDB::bind_method(D_METHOD("track_move_down", "idx"), &Animation::track_move_down);
+ ClassDB::bind_method(D_METHOD("track_swap", "idx", "with_idx"), &Animation::track_swap);
ClassDB::bind_method(D_METHOD("track_set_imported", "idx", "imported"), &Animation::track_set_imported);
ClassDB::bind_method(D_METHOD("track_is_imported", "idx"), &Animation::track_is_imported);
@@ -1667,6 +2677,30 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("method_track_get_name", "idx", "key_idx"), &Animation::method_track_get_name);
ClassDB::bind_method(D_METHOD("method_track_get_params", "idx", "key_idx"), &Animation::method_track_get_params);
+ ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()));
+
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "idx", "key_idx", "value"), &Animation::bezier_track_set_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "idx", "key_idx"), &Animation::bezier_track_get_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_out_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_interpolate", "track", "time"), &Animation::bezier_track_interpolate);
+
+ ClassDB::bind_method(D_METHOD("audio_track_insert_key", "track", "time", "stream", "start_offset", "end_offset"), &Animation::audio_track_insert_key, DEFVAL(0), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_stream", "idx", "key_idx", "stream"), &Animation::audio_track_set_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_start_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_end_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_end_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "idx", "key_idx"), &Animation::audio_track_get_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+
+ ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track", "time", "animation"), &Animation::animation_track_insert_key);
+ ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
+ ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "idx", "key_idx"), &Animation::animation_track_get_key_animation);
+
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
@@ -1686,6 +2720,9 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_VALUE);
BIND_ENUM_CONSTANT(TYPE_TRANSFORM);
BIND_ENUM_CONSTANT(TYPE_METHOD);
+ BIND_ENUM_CONSTANT(TYPE_BEZIER);
+ BIND_ENUM_CONSTANT(TYPE_AUDIO);
+ BIND_ENUM_CONSTANT(TYPE_ANIMATION);
BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST);
BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR);
@@ -1694,6 +2731,7 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS);
BIND_ENUM_CONSTANT(UPDATE_DISCRETE);
BIND_ENUM_CONSTANT(UPDATE_TRIGGER);
+ BIND_ENUM_CONSTANT(UPDATE_CAPTURE);
}
void Animation::clear() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 73691a69f2..a41e6ea5d7 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,6 +45,9 @@ public:
TYPE_VALUE, ///< Set a value in a property, can be interpolated.
TYPE_TRANSFORM, ///< Transform a node or a bone.
TYPE_METHOD, ///< Call any method on a specific node.
+ TYPE_BEZIER, ///< Bezier curve
+ TYPE_AUDIO,
+ TYPE_ANIMATION,
};
enum InterpolationType {
@@ -57,6 +60,7 @@ public:
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_TRIGGER,
+ UPDATE_CAPTURE,
};
@@ -137,6 +141,55 @@ private:
MethodTrack() { type = TYPE_METHOD; }
};
+ /* BEZIER TRACK */
+
+ struct BezierKey {
+ Vector2 in_handle; //relative (x always <0)
+ Vector2 out_handle; //relative (x always >0)
+ float value;
+ };
+
+ struct BezierTrack : public Track {
+
+ Vector<TKey<BezierKey> > values;
+
+ BezierTrack() {
+ type = TYPE_BEZIER;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AudioKey {
+ RES stream;
+ float start_offset; //offset from start
+ float end_offset; //offset from end, if 0 then full length or infinite
+ AudioKey() {
+ start_offset = 0;
+ end_offset = 0;
+ }
+ };
+
+ struct AudioTrack : public Track {
+
+ Vector<TKey<AudioKey> > values;
+
+ AudioTrack() {
+ type = TYPE_AUDIO;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AnimationTrack : public Track {
+
+ Vector<TKey<StringName> > values;
+
+ AnimationTrack() {
+ type = TYPE_ANIMATION;
+ }
+ };
+
Vector<Track *> tracks;
/*
@@ -168,6 +221,9 @@ private:
template <class T>
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T> > &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const;
+ template <class T>
+ _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const;
+
_FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const;
_FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const;
@@ -238,6 +294,7 @@ public:
void track_move_up(int p_track);
void track_move_down(int p_track);
+ void track_swap(int p_track, int p_with_track);
void track_set_imported(int p_track, bool p_imported);
bool track_is_imported(int p_track) const;
@@ -245,7 +302,6 @@ public:
void track_set_enabled(int p_track, bool p_enabled);
bool track_is_enabled(int p_track) const;
- int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
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);
@@ -257,10 +313,33 @@ public:
float track_get_key_time(int p_track, int p_key_idx) const;
float track_get_key_transition(int p_track, int p_key_idx) const;
+ int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
+ int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
+ void bezier_track_set_key_value(int p_track, int p_index, float p_value);
+ void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle);
+ void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle);
+ float bezier_track_get_key_value(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const;
+
+ float bezier_track_interpolate(int p_track, float p_time) const;
+
+ int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0);
+ void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream);
+ void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset);
+ void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset);
+ RES audio_track_get_key_stream(int p_track, int p_key) const;
+ float audio_track_get_key_start_offset(int p_track, int p_key) const;
+ float audio_track_get_key_end_offset(int p_track, int p_key) const;
+
+ int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation);
+ void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
+ StringName animation_track_get_key_animation(int p_track, int p_key) const;
+
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
bool track_get_interpolation_loop_wrap(int p_track) const;
@@ -277,6 +356,8 @@ public:
void copy_track(int p_track, Ref<Animation> p_to_animation);
+ void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const;
+
void set_length(float p_length);
float get_length() const;
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index b77143cd9d..02a9e4d69b 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -524,6 +524,9 @@ String AudioStreamSample::get_stream_name() const {
void AudioStreamSample::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamSample::set_data);
+ ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamSample::get_data);
+
ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioStreamSample::set_format);
ClassDB::bind_method(D_METHOD("get_format"), &AudioStreamSample::get_format);
@@ -542,16 +545,13 @@ void AudioStreamSample::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo);
ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo);
- ClassDB::bind_method(D_METHOD("_set_data", "data"), &AudioStreamSample::set_data);
- ClassDB::bind_method(D_METHOD("_get_data"), &AudioStreamSample::get_data);
-
+ ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_rate"), "set_mix_rate", "get_mix_rate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stereo"), "set_stereo", "is_stereo");
- ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
BIND_ENUM_CONSTANT(FORMAT_8_BITS);
BIND_ENUM_CONSTANT(FORMAT_16_BITS);
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 4ec1e8973d..7f902fc982 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -1169,6 +1169,7 @@ void Curve3D::_bake() const {
if (points.size() == 0) {
baked_point_cache.resize(0);
baked_tilt_cache.resize(0);
+ baked_up_vector_cache.resize(0);
return;
}
@@ -1178,6 +1179,14 @@ void Curve3D::_bake() const {
baked_point_cache.set(0, points[0].pos);
baked_tilt_cache.resize(1);
baked_tilt_cache.set(0, points[0].tilt);
+
+ if (up_vector_enabled) {
+
+ baked_up_vector_cache.resize(1);
+ baked_up_vector_cache.set(0, Vector3(0, 1, 0));
+ } else
+ baked_up_vector_cache.resize(0);
+
return;
}
@@ -1247,10 +1256,51 @@ void Curve3D::_bake() const {
baked_tilt_cache.resize(pointlist.size());
PoolRealArray::Write wt = baked_tilt_cache.write();
+ baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0);
+ PoolVector3Array::Write up_write = baked_up_vector_cache.write();
+
+ Vector3 sideways;
+ Vector3 up;
+ Vector3 forward;
+
+ Vector3 prev_sideways = Vector3(1, 0, 0);
+ Vector3 prev_up = Vector3(0, 1, 0);
+ Vector3 prev_forward = Vector3(0, 0, 1);
+
for (List<Plane>::Element *E = pointlist.front(); E; E = E->next()) {
w[idx] = E->get().normal;
wt[idx] = E->get().d;
+
+ if (!up_vector_enabled) {
+ idx++;
+ continue;
+ }
+
+ forward = idx > 0 ? (w[idx] - w[idx - 1]).normalized() : prev_forward;
+
+ float y_dot = prev_up.dot(forward);
+
+ if (y_dot > (1.0f - CMP_EPSILON)) {
+ sideways = prev_sideways;
+ up = -prev_forward;
+ } else if (y_dot < -(1.0f - CMP_EPSILON)) {
+ sideways = prev_sideways;
+ up = prev_forward;
+ } else {
+ sideways = prev_up.cross(forward).normalized();
+ up = forward.cross(sideways).normalized();
+ }
+
+ if (idx == 1)
+ up_write[0] = up;
+
+ up_write[idx] = up;
+
+ prev_sideways = sideways;
+ prev_up = up;
+ prev_forward = forward;
+
idx++;
}
}
@@ -1343,6 +1393,53 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const {
return Math::lerp(r[idx], r[idx + 1], frac);
}
+Vector3 Curve3D::interpolate_baked_up_vector(float p_offset, bool p_apply_tilt) const {
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ // curve may not have baked up vectors
+ int count = baked_up_vector_cache.size();
+ if (count == 0) {
+ ERR_EXPLAIN("No up vectors in Curve3D");
+ ERR_FAIL_COND_V(count == 0, Vector3(0, 1, 0));
+ }
+
+ if (count == 1)
+ return baked_up_vector_cache.get(0);
+
+ PoolVector3Array::Read r = baked_up_vector_cache.read();
+ PoolVector3Array::Read rp = baked_point_cache.read();
+ PoolRealArray::Read rt = baked_tilt_cache.read();
+
+ float offset = CLAMP(p_offset, 0.0f, baked_max_ofs);
+
+ int idx = Math::floor((double)offset / (double)bake_interval);
+ float frac = Math::fmod(offset, bake_interval) / bake_interval;
+
+ if (idx == count - 1)
+ return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx];
+
+ Vector3 forward = (rp[idx + 1] - rp[idx]).normalized();
+ Vector3 up = r[idx];
+ Vector3 up1 = r[idx + 1];
+
+ if (p_apply_tilt) {
+ up.rotate(forward, rt[idx]);
+ up1.rotate(idx + 2 >= count ? forward : (rp[idx + 2] - rp[idx + 1]).normalized(), rt[idx + 1]);
+ }
+
+ Vector3 axis = up.cross(up1);
+
+ if (axis.length_squared() < CMP_EPSILON2)
+ axis = forward;
+ else
+ axis.normalize();
+
+ return up.rotated(axis, up.angle_to(up1) * frac);
+}
+
PoolVector3Array Curve3D::get_baked_points() const {
if (baked_cache_dirty)
@@ -1359,6 +1456,14 @@ PoolRealArray Curve3D::get_baked_tilts() const {
return baked_tilt_cache;
}
+PoolVector3Array Curve3D::get_baked_up_vectors() const {
+
+ if (baked_cache_dirty)
+ _bake();
+
+ return baked_up_vector_cache;
+}
+
Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const {
// Brute force method
@@ -1452,6 +1557,18 @@ float Curve3D::get_bake_interval() const {
return bake_interval;
}
+void Curve3D::set_up_vector_enabled(bool p_enable) {
+
+ up_vector_enabled = p_enable;
+ baked_cache_dirty = true;
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+bool Curve3D::is_up_vector_enabled() const {
+
+ return up_vector_enabled;
+}
+
Dictionary Curve3D::_get_data() const {
Dictionary dc;
@@ -1563,11 +1680,15 @@ void Curve3D::_bind_methods() {
//ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10));
ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval);
ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval);
+ ClassDB::bind_method(D_METHOD("set_up_vector_enabled", "enable"), &Curve3D::set_up_vector_enabled);
+ ClassDB::bind_method(D_METHOD("is_up_vector_enabled"), &Curve3D::is_up_vector_enabled);
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length);
ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve3D::interpolate_baked, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("interpolate_baked_up_vector", "offset", "apply_tilt"), &Curve3D::interpolate_baked_up_vector, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points);
ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts);
+ ClassDB::bind_method(D_METHOD("get_baked_up_vectors"), &Curve3D::get_baked_up_vectors);
ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve3D::get_closest_point);
ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve3D::get_closest_offset);
ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve3D::tessellate, DEFVAL(5), DEFVAL(4));
@@ -1577,6 +1698,9 @@ void Curve3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+
+ ADD_GROUP("Up Vector", "up_vector_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled");
}
Curve3D::Curve3D() {
@@ -1586,4 +1710,5 @@ Curve3D::Curve3D() {
add_point(Vector3(0,2,0));
add_point(Vector3(0,3,5));*/
bake_interval = 0.2;
+ up_vector_enabled = true;
}
diff --git a/scene/resources/curve.h b/scene/resources/curve.h
index 492eb05d1e..9cb12a4345 100644
--- a/scene/resources/curve.h
+++ b/scene/resources/curve.h
@@ -232,11 +232,13 @@ class Curve3D : public Resource {
mutable bool baked_cache_dirty;
mutable PoolVector3Array baked_point_cache;
mutable PoolRealArray baked_tilt_cache;
+ mutable PoolVector3Array baked_up_vector_cache;
mutable float baked_max_ofs;
void _bake() const;
float bake_interval;
+ bool up_vector_enabled;
void _bake_segment3d(Map<float, Vector3> &r_bake, float p_begin, float p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, float p_tol) const;
Dictionary _get_data() const;
@@ -264,12 +266,16 @@ public:
void set_bake_interval(float p_tolerance);
float get_bake_interval() const;
+ void set_up_vector_enabled(bool p_enable);
+ bool is_up_vector_enabled() const;
float get_baked_length() const;
Vector3 interpolate_baked(float p_offset, bool p_cubic = false) const;
float interpolate_baked_tilt(float p_offset) const;
+ Vector3 interpolate_baked_up_vector(float p_offset, bool p_apply_tilt = false) const;
PoolVector3Array get_baked_points() const; //useful for going through
PoolRealArray get_baked_tilts() const; //useful for going through
+ PoolVector3Array get_baked_up_vectors() const;
Vector3 get_closest_point(const Vector3 &p_to_point) const;
float get_closest_offset(const Vector3 &p_to_point) const;
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 3ea856541e..d64e6970bf 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -874,6 +874,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5));
theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05));
theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2));
+ theme->set_color("activity", "GraphEdit", Color(1, 1, 1));
theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale);
theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale);
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index e0562d9e4a..28aa6f1aa7 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -419,10 +419,10 @@ void CapsuleMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings);
ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings);
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_mid_height", "get_mid_height");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_mid_height", "get_mid_height");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
}
void CapsuleMesh::set_radius(const float p_radius) {
@@ -676,10 +676,10 @@ void CubeMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &CubeMesh::set_subdivide_depth);
ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &CubeMesh::get_subdivide_depth);
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth");
}
void CubeMesh::set_size(const Vector3 &p_size) {
@@ -881,11 +881,11 @@ void CylinderMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings);
ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings);
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_top_radius", "get_top_radius");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_bottom_radius", "get_bottom_radius");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
}
void CylinderMesh::set_top_radius(const float p_radius) {
@@ -1017,8 +1017,8 @@ void PlaneMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth");
}
void PlaneMesh::set_size(const Size2 &p_size) {
@@ -1283,9 +1283,9 @@ void PrismMesh::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth");
}
void PrismMesh::set_left_to_right(const float p_left_to_right) {
@@ -1352,10 +1352,10 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const {
PoolVector<float> tangents;
PoolVector<Vector2> uvs;
- faces.resize(4);
- normals.resize(4);
- tangents.resize(4 * 4);
- uvs.resize(4);
+ faces.resize(6);
+ normals.resize(6);
+ tangents.resize(6 * 4);
+ uvs.resize(6);
Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f);
@@ -1366,9 +1366,15 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const {
Vector3(_size.x, -_size.y, 0),
};
- for (int i = 0; i < 4; i++) {
+ static const int indices[6] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+
+ for (int i = 0; i < 6; i++) {
- faces.set(i, quad_faces[i]);
+ int j = indices[i];
+ faces.set(i, quad_faces[j]);
normals.set(i, Vector3(0, 0, 1));
tangents.set(i * 4 + 0, 1.0);
tangents.set(i * 4 + 1, 0.0);
@@ -1382,14 +1388,14 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const {
Vector2(1, 1),
};
- uvs.set(i, quad_uv[i]);
+ uvs.set(i, quad_uv[j]);
}
p_arr[VS::ARRAY_VERTEX] = faces;
p_arr[VS::ARRAY_NORMAL] = normals;
p_arr[VS::ARRAY_TANGENT] = tangents;
p_arr[VS::ARRAY_TEX_UV] = uvs;
-};
+}
void QuadMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadMesh::set_size);
@@ -1398,7 +1404,7 @@ void QuadMesh::_bind_methods() {
}
QuadMesh::QuadMesh() {
- primitive_type = PRIMITIVE_TRIANGLE_FAN;
+ primitive_type = PRIMITIVE_TRIANGLES;
size = Size2(1.0, 1.0);
}
@@ -1499,10 +1505,10 @@ void SphereMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere);
ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere);
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius");
- ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere");
}
diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp
index 7da65ac984..ebad00b068 100644
--- a/scene/resources/style_box.cpp
+++ b/scene/resources/style_box.cpp
@@ -136,8 +136,17 @@ Ref<Texture> StyleBoxTexture::get_normal_map() const {
void StyleBoxTexture::set_margin_size(Margin p_margin, float p_size) {
+ ERR_FAIL_INDEX(p_margin, 4);
+
margin[p_margin] = p_size;
emit_changed();
+ static const char *margin_prop[4] = {
+ "content_margin_left",
+ "content_margin_top",
+ "content_margin_right",
+ "content_margin_bottom",
+ };
+ _change_notify(margin_prop[p_margin]);
}
float StyleBoxTexture::get_margin_size(Margin p_margin) const {
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 56a2e7afba..54f5aea160 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -76,7 +76,9 @@ void Texture::_bind_methods() {
ClassDB::bind_method(D_METHOD("draw_rect_region", "canvas_item", "rect", "src_rect", "modulate", "transpose", "normal_map", "clip_uv"), &Texture::draw_rect_region, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(Variant()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("get_data"), &Texture::get_data);
+ ADD_GROUP("Flags", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter,Anisotropic Linear,Convert to Linear,Mirrored Repeat,Video Surface"), "set_flags", "get_flags");
+ ADD_GROUP("", "");
BIND_ENUM_CONSTANT(FLAGS_DEFAULT);
BIND_ENUM_CONSTANT(FLAG_MIPMAPS);
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index 42d64376f5..58057cda0c 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -264,7 +264,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region"));
p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE"));
if (tile_get_tile_mode(id) == AUTO_TILE) {
- p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/bitmask_flags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
@@ -960,6 +960,7 @@ void TileSet::_bind_methods() {
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_ENUM_CONSTANT(BITMASK_2X2);
+ BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL);
BIND_ENUM_CONSTANT(BITMASK_3X3);
BIND_ENUM_CONSTANT(BIND_TOPLEFT);
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index d5704ac9a0..ec635ee5cc 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -56,6 +56,7 @@ public:
enum BitmaskMode {
BITMASK_2X2,
+ BITMASK_3X3_MINIMAL,
BITMASK_3X3
};
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 2dc32b893d..bf765385d0 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -187,6 +187,8 @@ SceneStringNames::SceneStringNames() {
node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed");
+ output = StaticCString::create("output");
+
path_pp = NodePath("..");
_default = StaticCString::create("default");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 2e6da26d68..b88cf7d8d7 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -199,6 +199,8 @@ public:
StringName node_configuration_warning_changed;
+ StringName output;
+
enum {
MAX_MATERIALS = 32
};