diff options
Diffstat (limited to 'scene')
213 files changed, 9482 insertions, 491 deletions
diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index 54194ff543..e3d1592be0 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -59,15 +59,36 @@ bool AnimatedSprite::_edit_use_pivot() const { } Rect2 AnimatedSprite::_edit_get_rect() const { + return _get_rect(); +} + +bool AnimatedSprite::_edit_use_rect() const { if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { - return Node2D::_edit_get_rect(); + return false; + } + Ref<Texture> t; + if (animation) + t = frames->get_frame(animation, frame); + if (t.is_null()) + return false; + + return true; +} + +Rect2 AnimatedSprite::get_anchorable_rect() const { + return _get_rect(); +} + +Rect2 AnimatedSprite::_get_rect() const { + if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { + return Rect2(); } Ref<Texture> t; if (animation) t = frames->get_frame(animation, frame); if (t.is_null()) - return Node2D::_edit_get_rect(); + return Rect2(); Size2 s = t->get_size(); Point2 ofs = offset; @@ -80,10 +101,6 @@ Rect2 AnimatedSprite::_edit_get_rect() const { return Rect2(ofs, s); } -bool AnimatedSprite::_edit_use_rect() const { - return true; -} - void SpriteFrames::add_frame(const StringName &p_anim, const Ref<Texture> &p_frame, int p_at_pos) { Map<StringName, Anim>::Element *E = animations.find(p_anim); diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index be5b1ef6d6..806052a696 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -145,6 +145,7 @@ class AnimatedSprite : public Node2D { void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; + Rect2 _get_rect() const; protected: static void _bind_methods(); @@ -161,6 +162,8 @@ public: virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const; + virtual Rect2 get_anchorable_rect() const; + void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 54541293fd..4dbcd6c609 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -36,11 +36,8 @@ void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid()) { - return; - } - - if (!active) { + if (!stream_playback.is_valid() || !active || + (stream_paused && !stream_paused_fade_out)) { return; } @@ -53,7 +50,11 @@ void AudioStreamPlayer2D::_mix_audio() { AudioFrame *buffer = mix_buffer.ptrw(); int buffer_size = mix_buffer.size(); - //mix + if (stream_paused_fade_out) { + // Short fadeout ramp + buffer_size = MIN(buffer_size, 128); + } + stream_playback->mix(buffer, pitch_scale, buffer_size); //write all outputs @@ -83,8 +84,10 @@ void AudioStreamPlayer2D::_mix_audio() { } //mix! - AudioFrame vol_inc = (current.vol - prev_outputs[i].vol) / float(buffer_size); - AudioFrame vol = current.vol; + AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol; + AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol; + AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); + AudioFrame vol = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : current.vol; int cc = AudioServer::get_singleton()->get_channel_count(); @@ -125,6 +128,8 @@ void AudioStreamPlayer2D::_mix_audio() { } output_ready = false; + stream_paused_fade_in = false; + stream_paused_fade_out = false; } void AudioStreamPlayer2D::_notification(int p_what) { @@ -142,6 +147,17 @@ void AudioStreamPlayer2D::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course @@ -242,7 +258,6 @@ void AudioStreamPlayer2D::_notification(int p_what) { void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - ERR_FAIL_COND(!p_stream.is_valid()); AudioServer::get_singleton()->lock(); mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); @@ -254,14 +269,15 @@ void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { setseek = -1; } - stream = p_stream; - stream_playback = p_stream->instance_playback(); + if (p_stream.is_valid()) { + stream = p_stream; + stream_playback = p_stream->instance_playback(); + } AudioServer::get_singleton()->unlock(); - if (stream_playback.is_null()) { + if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); - ERR_FAIL_COND(stream_playback.is_null()); } } @@ -418,6 +434,20 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { return area_mask; } +void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade_in = p_pause ? false : true; + stream_paused_fade_out = p_pause ? true : false; + } +} + +bool AudioStreamPlayer2D::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); @@ -454,6 +484,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_area_mask", "mask"), &AudioStreamPlayer2D::set_area_mask); ClassDB::bind_method(D_METHOD("get_area_mask"), &AudioStreamPlayer2D::get_area_mask); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer2D::_bus_layout_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -461,6 +494,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "1,4096,1,or_greater"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); @@ -483,6 +517,9 @@ AudioStreamPlayer2D::AudioStreamPlayer2D() { setplay = -1; output_ready = false; area_mask = 1; + stream_paused = false; + stream_paused_fade_in = false; + stream_paused_fade_out = false; AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); } diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 9ae8e3a518..e68e6eeca5 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -72,6 +72,9 @@ private: float volume_db; float pitch_scale; bool autoplay; + bool stream_paused; + bool stream_paused_fade_in; + bool stream_paused_fade_out; StringName bus; void _mix_audio(); @@ -123,6 +126,9 @@ public: void set_area_mask(uint32_t p_mask); uint32_t get_area_mask() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer2D(); ~AudioStreamPlayer2D(); }; diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp index caa1adebdb..e06c30ec6b 100644 --- a/scene/2d/back_buffer_copy.cpp +++ b/scene/2d/back_buffer_copy.cpp @@ -59,6 +59,11 @@ bool BackBufferCopy::_edit_use_rect() const { return true; } +Rect2 BackBufferCopy::get_anchorable_rect() const { + + return rect; +} + void BackBufferCopy::set_rect(const Rect2 &p_rect) { rect = p_rect; diff --git a/scene/2d/back_buffer_copy.h b/scene/2d/back_buffer_copy.h index 752d56de2b..b1ee12544b 100644 --- a/scene/2d/back_buffer_copy.h +++ b/scene/2d/back_buffer_copy.h @@ -58,6 +58,7 @@ public: void set_rect(const Rect2 &p_rect); Rect2 get_rect() const; + Rect2 get_anchorable_rect() const; void set_copy_mode(CopyMode p_mode); CopyMode get_copy_mode() const; diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index f1c09594da..47326b9be2 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -321,11 +321,6 @@ void CanvasItem::hide() { _change_notify("visible"); } -Size2 CanvasItem::_edit_get_minimum_size() const { - - return Size2(-1, -1); //no limit -} - void CanvasItem::_update_callback() { if (!is_inside_tree()) { @@ -994,7 +989,6 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("_edit_set_rect", "rect"), &CanvasItem::_edit_set_rect); ClassDB::bind_method(D_METHOD("_edit_get_rect"), &CanvasItem::_edit_get_rect); ClassDB::bind_method(D_METHOD("_edit_use_rect"), &CanvasItem::_edit_use_rect); - ClassDB::bind_method(D_METHOD("_edit_get_item_and_children_rect"), &CanvasItem::_edit_get_item_and_children_rect); ClassDB::bind_method(D_METHOD("_edit_set_rotation", "degrees"), &CanvasItem::_edit_set_rotation); ClassDB::bind_method(D_METHOD("_edit_get_rotation"), &CanvasItem::_edit_get_rotation); ClassDB::bind_method(D_METHOD("_edit_use_rotation"), &CanvasItem::_edit_use_rotation); @@ -1175,21 +1169,6 @@ int CanvasItem::get_canvas_layer() const { return 0; } -Rect2 CanvasItem::_edit_get_item_and_children_rect() const { - - Rect2 rect = _edit_get_rect(); - - for (int i = 0; i < get_child_count(); i++) { - CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i)); - if (c) { - Rect2 sir = c->get_transform().xform(c->_edit_get_item_and_children_rect()); - rect = rect.merge(sir); - } - } - - return rect; -} - CanvasItem::CanvasItem() : xform_change(this) { diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 10d5082dfc..1e6a251c9c 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -222,6 +222,9 @@ public: /* EDITOR */ + // Select the node + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + // Save and restore a CanvasItem state virtual void _edit_set_state(const Dictionary &p_state){}; virtual Dictionary _edit_get_state() const { return Dictionary(); }; @@ -234,36 +237,21 @@ public: virtual void _edit_set_scale(const Size2 &p_scale) = 0; virtual Size2 _edit_get_scale() const = 0; + // Used to rotate the node + virtual bool _edit_use_rotation() const { return false; }; + virtual void _edit_set_rotation(float p_rotation){}; + virtual float _edit_get_rotation() const { return 0.0; }; + // Used to resize/move the node + virtual bool _edit_use_rect() const { return false; }; // MAYBE REPLACE BY A _edit_get_editmode() virtual void _edit_set_rect(const Rect2 &p_rect){}; virtual Rect2 _edit_get_rect() const { return Rect2(0, 0, 0, 0); }; - virtual bool _edit_use_rect() const { return false; }; - - Rect2 _edit_get_item_and_children_rect() const; - - // used to select the node - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; - - // Used to rotate the node - virtual void - _edit_set_rotation(float p_rotation){}; - virtual float _edit_get_rotation() const { - return 0.0; - }; - virtual bool _edit_use_rotation() const { - return false; - }; + virtual Size2 _edit_get_minimum_size() const { return Size2(-1, -1); }; // LOOKS WEIRD // Used to set a pivot + virtual bool _edit_use_pivot() const { return false; }; virtual void _edit_set_pivot(const Point2 &p_pivot){}; - virtual Point2 _edit_get_pivot() const { - return Point2(); - }; - virtual bool _edit_use_pivot() const { - return false; - }; - - virtual Size2 _edit_get_minimum_size() const; + virtual Point2 _edit_get_pivot() const { return Point2(); }; /* VISIBILITY */ @@ -358,6 +346,9 @@ public: void set_notify_transform(bool p_enable); bool is_transform_notification_enabled() const; + // Used by control nodes to retreive the parent's anchorable area + virtual Rect2 get_anchorable_rect() const { return Rect2(0, 0, 0, 0); }; + int get_canvas_layer() const; CanvasItem(); diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index 329382c034..7d5360c0e4 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -158,8 +158,8 @@ void Joint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 9a44eb31bb..f93c7d1f79 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -59,14 +59,22 @@ bool Light2D::_edit_use_pivot() const { Rect2 Light2D::_edit_get_rect() const { if (texture.is_null()) - return Node2D::_edit_get_rect(); + return Rect2(); Size2 s = texture->get_size() * _scale; return Rect2(texture_offset - s / 2.0, s); } bool Light2D::_edit_use_rect() const { - return true; + return !texture.is_null(); +} + +Rect2 Light2D::get_anchorable_rect() const { + if (texture.is_null()) + return Rect2(); + + Size2 s = texture->get_size() * _scale; + return Rect2(texture_offset - s / 2.0, s); } void Light2D::_update_light_visibility() { diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 543805e329..40469cfbc8 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -94,6 +94,8 @@ public: virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const; + virtual Rect2 get_anchorable_rect() const; + void set_enabled(bool p_enabled); bool is_enabled() const; diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 4d6ebc81c3..81ed3c63c3 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -649,7 +649,7 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation_degrees", PROPERTY_HINT_RANGE, "-1440,1440,0.1"), "set_texture_rotation_degrees", "get_texture_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation", PROPERTY_HINT_NONE, "", 0), "set_texture_rotation", "get_texture_rotation"); ADD_GROUP("Skeleton", ""); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton"), "set_skeleton", "get_skeleton"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton"); ADD_GROUP("Invert", "invert_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enable"), "set_invert", "get_invert"); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index da764e032b..63c3d78dfd 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -201,7 +201,7 @@ void RemoteTransform2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform2D::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform2D::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/2d/screen_button.cpp b/scene/2d/screen_button.cpp index 9480f18176..45f63fd5bf 100644 --- a/scene/2d/screen_button.cpp +++ b/scene/2d/screen_button.cpp @@ -324,19 +324,21 @@ void TouchScreenButton::_release(bool p_exiting_tree) { } Rect2 TouchScreenButton::_edit_get_rect() const { - - if (texture.is_null()) - return Rect2(0, 0, 1, 1); - /* if (texture.is_null()) return CanvasItem::_edit_get_rect(); - */ return Rect2(Size2(), texture->get_size()); } bool TouchScreenButton::_edit_use_rect() const { - return true; + return !texture.is_null(); +} + +Rect2 TouchScreenButton::get_anchorable_rect() const { + if (texture.is_null()) + return CanvasItem::get_anchorable_rect(); + + return Rect2(Size2(), texture->get_size()); } void TouchScreenButton::set_visibility_mode(VisibilityMode p_mode) { diff --git a/scene/2d/screen_button.h b/scene/2d/screen_button.h index b2fafcc93d..3e8adc2e5e 100644 --- a/scene/2d/screen_button.h +++ b/scene/2d/screen_button.h @@ -103,8 +103,9 @@ public: bool is_pressed() const; - Rect2 _edit_get_rect() const; + virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const; + virtual Rect2 get_anchorable_rect() const; TouchScreenButton(); }; diff --git a/scene/2d/sprite.cpp b/scene/2d/sprite.cpp index 64d0771fab..ebe0e81f6e 100644 --- a/scene/2d/sprite.cpp +++ b/scene/2d/sprite.cpp @@ -63,9 +63,16 @@ Rect2 Sprite::_edit_get_rect() const { } bool Sprite::_edit_use_rect() const { + if (texture.is_null()) + return false; + return true; } +Rect2 Sprite::get_anchorable_rect() const { + return get_rect(); +} + void Sprite::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const { Rect2 base_rect; @@ -367,10 +374,6 @@ Rect2 Sprite::get_rect() const { if (texture.is_null()) return Rect2(0, 0, 1, 1); - /* - if (texture.is_null()) - return CanvasItem::_edit_get_rect(); - */ Size2i s; diff --git a/scene/2d/sprite.h b/scene/2d/sprite.h index 609ad8bb34..0a5ff002cd 100644 --- a/scene/2d/sprite.h +++ b/scene/2d/sprite.h @@ -115,6 +115,7 @@ public: int get_hframes() const; Rect2 get_rect() const; + virtual Rect2 get_anchorable_rect() const; Sprite(); ~Sprite(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index d88e148b2c..9a343ca0f0 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); @@ -706,7 +708,7 @@ void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { rect_cache_dirty = true; } -void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { +void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) { Quadrant &q = Q->get(); if (!q.dirty_list.in_list()) @@ -717,7 +719,10 @@ void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { pending_update = true; if (!is_inside_tree()) return; - _update_dirty_quadrants(); + + if (update) { + _update_dirty_quadrants(); + } } void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { @@ -725,6 +730,11 @@ void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_ set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); } +void TileMap::set_celld(const Vector2 &p_pos, const Dictionary &p_data) { + + set_cell(p_pos.x, p_pos.y, p_data["id"], p_data["flip_h"], p_data["flip_y"], p_data["transpose"], p_data["auto_coord"]); +} + void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) { PosKey pk(p_x, p_y); @@ -977,6 +987,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 { @@ -1006,8 +1024,9 @@ void TileMap::_recreate_quadrants() { } Q->get().cells.insert(E->key()); - _make_quadrant_dirty(Q); + _make_quadrant_dirty(Q, false); } + _update_dirty_quadrants(); } void TileMap::_clear_quadrants() { @@ -1133,16 +1152,6 @@ PoolVector<int> TileMap::_get_tile_data() const { return data; } -Rect2 TileMap::_edit_get_rect() const { - - const_cast<TileMap *>(this)->_update_dirty_quadrants(); - return rect_cache; -} - -bool TileMap::_edit_use_rect() const { - return true; -} - void TileMap::set_collision_layer(uint32_t p_layer) { collision_layer = p_layer; @@ -1602,6 +1611,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_celld", "data"), &TileMap::set_celld); ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell); ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv); ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 07947004b3..79d79ca59f 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -188,7 +188,7 @@ private: Map<PosKey, Quadrant>::Element *_create_quadrant(const PosKey &p_qk); void _erase_quadrant(Map<PosKey, Quadrant>::Element *Q); - void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q); + void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true); void _recreate_quadrants(); void _clear_quadrants(); void _update_dirty_quadrants(); @@ -241,12 +241,10 @@ public: void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord); Vector2 get_cell_autotile_coord(int p_x, int p_y) const; + void set_celld(const Vector2 &p_pos, const Dictionary &p_data); void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false); int get_cellv(const Vector2 &p_pos) const; - Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; - void make_bitmask_area_dirty(const Vector2 &p_pos); void update_bitmask_area(const Vector2 &p_pos); void update_bitmask_region(const Vector2 &p_start = Vector2(), const Vector2 &p_end = Vector2()); diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index 001c58ea76..4bff26a200 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -73,7 +73,10 @@ Vector3 ARVRCamera::project_local_ray_normal(const Point2 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_local_ray_normal(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -98,7 +101,10 @@ Point2 ARVRCamera::unproject_position(const Vector3 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector2()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector2()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::unproject_position(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -127,7 +133,10 @@ Vector3 ARVRCamera::project_position(const Point2 &p_point) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_position(p_point); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -157,7 +166,10 @@ Vector<Plane> ARVRCamera::get_frustum() const { ERR_FAIL_NULL_V(arvr_server, Vector<Plane>()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector<Plane>()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::get_frustum(); + } ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>()); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index e7b3645001..d46231a677 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -35,11 +35,8 @@ #include "scene/main/viewport.h" void AudioStreamPlayer3D::_mix_audio() { - if (!stream_playback.is_valid()) { - return; - } - - if (!active) { + if (!stream_playback.is_valid() || !active || + (stream_paused && !stream_paused_fade_out)) { return; } @@ -54,8 +51,13 @@ void AudioStreamPlayer3D::_mix_audio() { AudioFrame *buffer = mix_buffer.ptrw(); int buffer_size = mix_buffer.size(); - //mix - if (output_count > 0 || out_of_range_mode == OUT_OF_RANGE_MIX) { + if (stream_paused_fade_out) { + // Short fadeout ramp + buffer_size = MIN(buffer_size, 128); + } + + // Mix if we're not paused or we're fading out + if ((output_count > 0 || out_of_range_mode == OUT_OF_RANGE_MIX)) { float output_pitch_scale = 0.0; if (output_count) { @@ -105,8 +107,10 @@ void AudioStreamPlayer3D::_mix_audio() { int buffers = AudioServer::get_singleton()->get_channel_count(); for (int k = 0; k < buffers; k++) { - AudioFrame vol_inc = (current.vol[k] - prev_outputs[i].vol[k]) / float(buffer_size); - AudioFrame vol = current.vol[k]; + AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol[k]; + AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol[k]; + AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); + AudioFrame vol = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : current.vol[k]; AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); @@ -188,6 +192,8 @@ void AudioStreamPlayer3D::_mix_audio() { } output_ready = false; + stream_paused_fade_in = false; + stream_paused_fade_out = false; } float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { @@ -237,6 +243,18 @@ void AudioStreamPlayer3D::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { @@ -552,7 +570,6 @@ void AudioStreamPlayer3D::_notification(int p_what) { void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - ERR_FAIL_COND(!p_stream.is_valid()); AudioServer::get_singleton()->lock(); mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); @@ -564,14 +581,15 @@ void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { setseek = -1; } - stream = p_stream; - stream_playback = p_stream->instance_playback(); + if (p_stream.is_valid()) { + stream = p_stream; + stream_playback = p_stream->instance_playback(); + } AudioServer::get_singleton()->unlock(); - if (stream_playback.is_null()) { + if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); - ERR_FAIL_COND(stream_playback.is_null()); } } @@ -825,6 +843,20 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() return doppler_tracking; } +void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade_in = stream_paused ? false : true; + stream_paused_fade_out = stream_paused ? true : false; + } +} + +bool AudioStreamPlayer3D::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); @@ -888,6 +920,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &AudioStreamPlayer3D::set_doppler_tracking); ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &AudioStreamPlayer3D::get_doppler_tracking); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer3D::_bus_layout_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -898,6 +933,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "0,4096,1,or_greater"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); @@ -949,6 +985,9 @@ AudioStreamPlayer3D::AudioStreamPlayer3D() { attenuation_filter_db = -24; out_of_range_mode = OUT_OF_RANGE_MIX; doppler_tracking = DOPPLER_TRACKING_DISABLED; + stream_paused = false; + stream_paused_fade_in = false; + stream_paused_fade_out = false; velocity_tracker.instance(); AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 1fcb83cf21..14413d0702 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -108,6 +108,9 @@ private: float max_db; float pitch_scale; bool autoplay; + bool stream_paused; + bool stream_paused_fade_in; + bool stream_paused_fade_out; StringName bus; void _mix_audio(); @@ -199,6 +202,9 @@ public: void set_doppler_tracking(DopplerTracking p_tracking); DopplerTracking get_doppler_tracking() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer3D(); ~AudioStreamPlayer3D(); }; diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp new file mode 100644 index 0000000000..2e897c1c73 --- /dev/null +++ b/scene/3d/cpu_particles.cpp @@ -0,0 +1,1409 @@ +#include "cpu_particles.h" + +#include "particles.h" +#include "scene/3d/camera.h" +#include "scene/main/viewport.h" +#include "scene/resources/surface_tool.h" +#include "servers/visual_server.h" + +AABB CPUParticles::get_aabb() const { + + return AABB(); +} +PoolVector<Face3> CPUParticles::get_faces(uint32_t p_usage_flags) const { + + return PoolVector<Face3>(); +} + +void CPUParticles::set_emitting(bool p_emitting) { + + emitting = p_emitting; + if (!is_processing_internal()) { + set_process_internal(true); + if (is_inside_tree()) { +#ifndef NO_THREADS + update_mutex->lock(); +#endif + VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); +#ifndef NO_THREADS + update_mutex->unlock(); +#endif + } + } +} + +void CPUParticles::set_amount(int p_amount) { + + ERR_FAIL_COND(p_amount < 1); + + particles.resize(p_amount); + { + PoolVector<Particle>::Write w = particles.write(); + + for (int i = 0; i < p_amount; i++) { + w[i].active = false; + } + } + + particle_data.resize((12 + 4 + 1) * p_amount); + VS::get_singleton()->multimesh_allocate(multimesh, p_amount, VS::MULTIMESH_TRANSFORM_3D, VS::MULTIMESH_COLOR_8BIT, VS::MULTIMESH_CUSTOM_DATA_FLOAT); + + particle_order.resize(p_amount); +} +void CPUParticles::set_lifetime(float p_lifetime) { + + ERR_FAIL_COND(p_lifetime <= 0); + lifetime = p_lifetime; +} + +void CPUParticles::set_one_shot(bool p_one_shot) { + + one_shot = p_one_shot; +} + +void CPUParticles::set_pre_process_time(float p_time) { + + pre_process_time = p_time; +} +void CPUParticles::set_explosiveness_ratio(float p_ratio) { + + explosiveness_ratio = p_ratio; +} +void CPUParticles::set_randomness_ratio(float p_ratio) { + + randomness_ratio = p_ratio; +} +void CPUParticles::set_use_local_coordinates(bool p_enable) { + + local_coords = p_enable; +} +void CPUParticles::set_speed_scale(float p_scale) { + + speed_scale = p_scale; +} + +bool CPUParticles::is_emitting() const { + + return emitting; +} +int CPUParticles::get_amount() const { + + return particles.size(); +} +float CPUParticles::get_lifetime() const { + + return lifetime; +} +bool CPUParticles::get_one_shot() const { + + return one_shot; +} + +float CPUParticles::get_pre_process_time() const { + + return pre_process_time; +} +float CPUParticles::get_explosiveness_ratio() const { + + return explosiveness_ratio; +} +float CPUParticles::get_randomness_ratio() const { + + return randomness_ratio; +} + +bool CPUParticles::get_use_local_coordinates() const { + + return local_coords; +} + +float CPUParticles::get_speed_scale() const { + + return speed_scale; +} + +void CPUParticles::set_draw_order(DrawOrder p_order) { + + draw_order = p_order; +} + +CPUParticles::DrawOrder CPUParticles::get_draw_order() const { + + return draw_order; +} + +void CPUParticles::set_mesh(const Ref<Mesh> &p_mesh) { + + mesh = p_mesh; + if (mesh.is_valid()) { + VS::get_singleton()->multimesh_set_mesh(multimesh, mesh->get_rid()); + } else { + VS::get_singleton()->multimesh_set_mesh(multimesh, RID()); + } +} + +Ref<Mesh> CPUParticles::get_mesh() const { + + return mesh; +} + +void CPUParticles::set_fixed_fps(int p_count) { + fixed_fps = p_count; +} + +int CPUParticles::get_fixed_fps() const { + return fixed_fps; +} + +void CPUParticles::set_fractional_delta(bool p_enable) { + fractional_delta = p_enable; +} + +bool CPUParticles::get_fractional_delta() const { + return fractional_delta; +} + +String CPUParticles::get_configuration_warning() const { + + String warnings; + + return warnings; +} + +void CPUParticles::restart() { + + time = 0; + inactive_time = 0; + frame_remainder = 0; + cycle = 0; + + { + int pc = particles.size(); + PoolVector<Particle>::Write w = particles.write(); + + for (int i = 0; i < pc; i++) { + w[i].active = false; + } + } +} + +void CPUParticles::set_spread(float p_spread) { + + spread = p_spread; +} + +float CPUParticles::get_spread() const { + + return spread; +} + +void CPUParticles::set_flatness(float p_flatness) { + + flatness = p_flatness; +} +float CPUParticles::get_flatness() const { + + return flatness; +} + +void CPUParticles::set_param(Parameter p_param, float p_value) { + + ERR_FAIL_INDEX(p_param, PARAM_MAX); + + parameters[p_param] = p_value; +} +float CPUParticles::get_param(Parameter p_param) const { + + ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); + + return parameters[p_param]; +} + +void CPUParticles::set_param_randomness(Parameter p_param, float p_value) { + + ERR_FAIL_INDEX(p_param, PARAM_MAX); + + randomness[p_param] = p_value; +} +float CPUParticles::get_param_randomness(Parameter p_param) const { + + ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); + + return randomness[p_param]; +} + +static void _adjust_curve_range(const Ref<Curve> &p_curve, float p_min, float p_max) { + + Ref<Curve> curve = p_curve; + if (!curve.is_valid()) + return; + + curve->ensure_default_setup(p_min, p_max); +} + +void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) { + + ERR_FAIL_INDEX(p_param, PARAM_MAX); + + curve_parameters[p_param] = p_curve; + + switch (p_param) { + case PARAM_INITIAL_LINEAR_VELOCITY: { + //do none for this one + } break; + case PARAM_ANGULAR_VELOCITY: { + _adjust_curve_range(p_curve, -360, 360); + } break; + /*case PARAM_ORBIT_VELOCITY: { + _adjust_curve_range(p_curve, -500, 500); + } break;*/ + case PARAM_LINEAR_ACCEL: { + _adjust_curve_range(p_curve, -200, 200); + } break; + case PARAM_RADIAL_ACCEL: { + _adjust_curve_range(p_curve, -200, 200); + } break; + case PARAM_TANGENTIAL_ACCEL: { + _adjust_curve_range(p_curve, -200, 200); + } break; + case PARAM_DAMPING: { + _adjust_curve_range(p_curve, 0, 100); + } break; + case PARAM_ANGLE: { + _adjust_curve_range(p_curve, -360, 360); + } break; + case PARAM_SCALE: { + + } break; + case PARAM_HUE_VARIATION: { + _adjust_curve_range(p_curve, -1, 1); + } break; + case PARAM_ANIM_SPEED: { + _adjust_curve_range(p_curve, 0, 200); + } break; + case PARAM_ANIM_OFFSET: { + } break; + default: {} + } +} +Ref<Curve> CPUParticles::get_param_curve(Parameter p_param) const { + + ERR_FAIL_INDEX_V(p_param, PARAM_MAX, Ref<Curve>()); + + return curve_parameters[p_param]; +} + +void CPUParticles::set_color(const Color &p_color) { + + color = p_color; +} + +Color CPUParticles::get_color() const { + + return color; +} + +void CPUParticles::set_color_ramp(const Ref<Gradient> &p_ramp) { + + color_ramp = p_ramp; +} + +Ref<Gradient> CPUParticles::get_color_ramp() const { + + return color_ramp; +} + +void CPUParticles::set_particle_flag(Flags p_flag, bool p_enable) { + ERR_FAIL_INDEX(p_flag, FLAG_MAX); + flags[p_flag] = p_enable; + if (p_flag == FLAG_DISABLE_Z) { + _change_notify(); + } +} + +bool CPUParticles::get_particle_flag(Flags p_flag) const { + ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false); + return flags[p_flag]; +} + +void CPUParticles::set_emission_shape(EmissionShape p_shape) { + + emission_shape = p_shape; +} + +void CPUParticles::set_emission_sphere_radius(float p_radius) { + + emission_sphere_radius = p_radius; +} + +void CPUParticles::set_emission_box_extents(Vector3 p_extents) { + + emission_box_extents = p_extents; +} + +void CPUParticles::set_emission_points(const PoolVector<Vector3> &p_points) { + + emission_points = p_points; +} + +void CPUParticles::set_emission_normals(const PoolVector<Vector3> &p_normals) { + + emission_normals = p_normals; +} + +void CPUParticles::set_emission_colors(const PoolVector<Color> &p_colors) { + + emission_colors = p_colors; +} + +float CPUParticles::get_emission_sphere_radius() const { + + return emission_sphere_radius; +} +Vector3 CPUParticles::get_emission_box_extents() const { + + return emission_box_extents; +} +PoolVector<Vector3> CPUParticles::get_emission_points() const { + + return emission_points; +} +PoolVector<Vector3> CPUParticles::get_emission_normals() const { + + return emission_normals; +} + +PoolVector<Color> CPUParticles::get_emission_colors() const { + + return emission_colors; +} + +CPUParticles::EmissionShape CPUParticles::get_emission_shape() const { + return emission_shape; +} +void CPUParticles::set_gravity(const Vector3 &p_gravity) { + + gravity = p_gravity; +} + +Vector3 CPUParticles::get_gravity() const { + + return gravity; +} + +void CPUParticles::_validate_property(PropertyInfo &property) const { + + if (property.name == "color" && color_ramp.is_valid()) { + property.usage = 0; + } + + if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { + property.usage = 0; + } + + if (property.name == "emission_box_extents" && emission_shape != EMISSION_SHAPE_BOX) { + property.usage = 0; + } + + if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { + property.usage = 0; + } + + if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { + property.usage = 0; + } + /* + if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) { + property.usage = 0; + } + */ +} + +static uint32_t idhash(uint32_t x) { + + x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b); + x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b); + x = (x >> uint32_t(16)) ^ x; + return x; +} + +static float rand_from_seed(uint32_t &seed) { + int k; + int s = int(seed); + if (s == 0) + s = 305420679; + k = s / 127773; + s = 16807 * (s - k * 127773) - 2836 * k; + if (s < 0) + s += 2147483647; + seed = uint32_t(s); + return float(seed % uint32_t(65536)) / 65535.0; +} + +float rand_from_seed_m1_p1(uint32_t &seed) { + return rand_from_seed(seed) * 2.0 - 1.0; +} + +void CPUParticles::_particles_process(float p_delta) { + + int pcount = particles.size(); + PoolVector<Particle>::Write w = particles.write(); + + Particle *parray = w.ptr(); + + float prev_time = time; + time += p_delta; + if (time > lifetime) { + time = Math::fmod(time, lifetime); + cycle++; + if (one_shot && cycle > 0) { + emitting = false; + } + } + + Transform emission_xform; + Basis velocity_xform; + if (!local_coords) { + emission_xform = get_global_transform(); + velocity_xform = emission_xform.basis.inverse().transposed(); + } + + for (int i = 0; i < pcount; i++) { + + Particle &p = parray[i]; + + if (!emitting && !p.active) + continue; + + float restart_time = float(i) / float(pcount); + float local_delta = p_delta; + + if (randomness_ratio > 0.0) { + uint32_t seed = cycle; + if (restart_time >= time) { + seed -= uint32_t(1); + } + seed *= uint32_t(pcount); + seed += uint32_t(i); + float random = float(idhash(seed) % uint32_t(65536)) / 65536.0; + restart_time += randomness_ratio * random * 1.0 / float(pcount); + } + + restart_time *= (1.0 - explosiveness_ratio); + bool restart = false; + + if (time > prev_time) { + // restart_time >= prev_time is used so particles emit in the first frame they are processed + + if (restart_time >= prev_time && restart_time < time) { + restart = true; + if (fractional_delta) { + local_delta = (time - restart_time) * lifetime; + } + } + + } else if (local_delta > 0.0) { + if (restart_time >= prev_time) { + restart = true; + if (fractional_delta) { + local_delta = (1.0 - restart_time + time) * lifetime; + } + + } else if (restart_time < time) { + restart = true; + if (fractional_delta) { + local_delta = (time - restart_time) * lifetime; + } + } + } + + if (restart) { + + if (!emitting) { + p.active = false; + continue; + } + p.active = true; + + /*float tex_linear_velocity = 0; + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(0); + }*/ + + float tex_angle = 0.0; + if (curve_parameters[PARAM_ANGLE].is_valid()) { + tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(0); + } + + float tex_anim_offset = 0.0; + if (curve_parameters[PARAM_ANGLE].is_valid()) { + tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(0); + } + + p.seed = Math::rand(); + + p.angle_rand = Math::randf(); + p.scale_rand = Math::randf(); + p.hue_rot_rand = Math::randf(); + p.anim_offset_rand = Math::randf(); + + float angle1_rad; + float angle2_rad; + + if (flags[FLAG_DISABLE_Z]) { + + angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); + p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + + } else { + //initiate velocity spread in 3D + angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + angle2_rad = (Math::randf() * 2.0 - 1.0) * (1.0 - flatness) * Math_PI * spread / 180.0; + + Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad)); + Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad)); + direction_yz.z = direction_yz.z / Math::sqrt(direction_yz.z); //better uniform distribution + Vector3 direction = Vector3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z); + direction.normalize(); + p.velocity = direction * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + } + + float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); + p.custom[0] = Math::deg2rad(base_angle); //angle + p.custom[1] = 0.0; //phase + p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1) + p.transform = Transform(); + p.time = 0; + p.base_color = Color(1, 1, 1, 1); + + switch (emission_shape) { + case EMISSION_SHAPE_POINT: { + //do none + } break; + case EMISSION_SHAPE_SPHERE: { + p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0).normalized() * emission_sphere_radius; + } break; + case EMISSION_SHAPE_BOX: { + p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_box_extents; + } break; + case EMISSION_SHAPE_POINTS: + case EMISSION_SHAPE_DIRECTED_POINTS: { + + int pc = emission_points.size(); + if (pc == 0) + break; + + int random_idx = Math::rand() % pc; + + p.transform.origin = emission_points.get(random_idx); + + if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS && emission_normals.size() == pc) { + if (flags[FLAG_DISABLE_Z]) { + /* + mat2 rotm; + "; + rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy; + rotm[1] = rotm[0].yx * vec2(1.0, -1.0); + VELOCITY.xy = rotm * VELOCITY.xy; + */ + } else { + Vector3 normal = emission_normals.get(random_idx); + Vector3 v0 = Math::abs(normal.z) < 0.999 ? Vector3(0.0, 0.0, 1.0) : Vector3(0, 1.0, 0.0); + Vector3 tangent = v0.cross(normal).normalized(); + Vector3 bitangent = tangent.cross(normal).normalized(); + Basis m3; + m3.set_axis(0, tangent); + m3.set_axis(1, bitangent); + m3.set_axis(2, normal); + p.velocity = m3.xform(p.velocity); + } + } + + if (emission_colors.size() == pc) { + p.base_color = emission_colors.get(random_idx); + } + } break; + } + + if (!local_coords) { + p.velocity = velocity_xform.xform(p.velocity); + p.transform = emission_xform * p.transform; + } + + if (flags[FLAG_DISABLE_Z]) { + p.velocity.z = 0.0; + p.velocity.z = 0.0; + } + + } else if (!p.active) { + continue; + } else { + + uint32_t alt_seed = p.seed; + + p.time += local_delta; + p.custom[1] += p.time / lifetime; + + float tex_linear_velocity = 0.0; + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]); + } + /* + float tex_orbit_velocity = 0.0; + + if (flags[FLAG_DISABLE_Z]) { + + if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]); + } + } +*/ + float tex_angular_velocity = 0.0; + if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { + tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]); + } + + float tex_linear_accel = 0.0; + if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { + tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(p.custom[1]); + } + + float tex_tangential_accel = 0.0; + if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { + tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(p.custom[1]); + } + + float tex_radial_accel = 0.0; + if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { + tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(p.custom[1]); + } + + float tex_damping = 0.0; + if (curve_parameters[PARAM_DAMPING].is_valid()) { + tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(p.custom[1]); + } + + float tex_angle = 0.0; + if (curve_parameters[PARAM_ANGLE].is_valid()) { + tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(p.custom[1]); + } + float tex_anim_speed = 0.0; + if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { + tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(p.custom[1]); + } + + float tex_anim_offset = 0.0; + if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { + tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(p.custom[1]); + } + + Vector3 force = gravity; + Vector3 pos = p.transform.origin; + if (flags[FLAG_DISABLE_Z]) { + pos.z = 0.0; + } + //apply linear acceleration + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); + //apply radial acceleration + Vector3 org = emission_xform.origin; + Vector3 diff = pos - org; + force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); + //apply tangential acceleration; + if (flags[FLAG_DISABLE_Z]) { + + Vector3 yx = Vector3(diff.y, 0, diff.x); + force += yx.length() > 0.0 ? (yx * Vector3(-1.0, 0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + + } else { + Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); + force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + } + //apply attractor forces + p.velocity += force * local_delta; + //orbit velocity +#if 0 + if (flags[FLAG_DISABLE_Z]) { + + float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); + if (orbit_amount != 0.0) { + float ang = orbit_amount * DELTA * pi * 2.0; + mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); + TRANSFORM[3].xy -= diff.xy; + TRANSFORM[3].xy += rot * diff.xy; + } + } +#endif + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + p.velocity = p.velocity.normalized() * tex_linear_velocity; + } + if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + + float v = p.velocity.length(); + float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + v -= damp * local_delta; + if (v < 0.0) { + p.velocity = Vector3(); + } else { + p.velocity = p.velocity.normalized() * v; + } + } + float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); + base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + p.custom[0] = Math::deg2rad(base_angle); //angle + p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle + if (flags[FLAG_ANIM_LOOP]) { + p.custom[2] = Math::fmod(p.custom[2], 1.0f); //loop + + } else { + p.custom[2] = CLAMP(p.custom[2], 0.0f, 1.0); //0 to 1 only + } + } + //apply color + //apply hue rotation + + float tex_scale = 1.0; + if (curve_parameters[PARAM_SCALE].is_valid()) { + tex_scale = curve_parameters[PARAM_SCALE]->interpolate(p.custom[1]); + } + + float tex_hue_variation = 0.0; + if (curve_parameters[PARAM_HUE_VARIATION].is_valid()) { + tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(p.custom[1]); + } + + float hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_PI * 2.0 * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + float hue_rot_c = Math::cos(hue_rot_angle); + float hue_rot_s = Math::sin(hue_rot_angle); + + Basis hue_rot_mat; + { + Basis mat1(0.299, 0.587, 0.114, 0.299, 0.587, 0.114, 0.299, 0.587, 0.114); + Basis mat2(0.701, -0.587, -0.114, -0.299, 0.413, -0.114, -0.300, -0.588, 0.886); + Basis mat3(0.168, 0.330, -0.497, -0.328, 0.035, 0.292, 1.250, -1.050, -0.203); + + for (int j = 0; j < 3; j++) { + hue_rot_mat[j] = mat1[j] + mat2[j] * hue_rot_c + mat3[j] * hue_rot_s; + } + } + + if (color_ramp.is_valid()) { + p.color = color_ramp->get_color_at_offset(p.custom[1]) * color; + } else { + p.color = color; + } + + Vector3 color_rgb = hue_rot_mat.xform_inv(Vector3(p.color.r, p.color.g, p.color.b)); + p.color.r = color_rgb.x; + p.color.g = color_rgb.y; + p.color.b = color_rgb.z; + + p.color *= p.base_color; + + if (flags[FLAG_DISABLE_Z]) { + + if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) { + if (p.velocity.length() > 0.0) { + p.transform.basis.set_axis(1, p.velocity.normalized()); + } else { + p.transform.basis.set_axis(1, p.transform.basis.get_axis(1)); + } + p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); + p.transform.basis.set_axis(2, Vector3(0, 0, 1)); + + } else { + p.transform.basis.set_axis(0, Vector3(Math::cos(p.custom[0]), -Math::sin(p.custom[0]), 0.0)); + p.transform.basis.set_axis(1, Vector3(Math::sin(p.custom[0]), Math::cos(p.custom[0]), 0.0)); + p.transform.basis.set_axis(2, Vector3(0, 0, 1)); + } + + } else { + //orient particle Y towards velocity + if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) { + if (p.velocity.length() > 0.0) { + p.transform.basis.set_axis(1, p.velocity.normalized()); + } else { + p.transform.basis.set_axis(1, p.transform.basis.get_axis(1).normalized()); + } + if (p.transform.basis.get_axis(1) == p.transform.basis.get_axis(0)) { + p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); + p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized()); + } else { + p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized()); + p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized()); + } + } else { + p.transform.basis.orthonormalize(); + } + + //turn particle by rotation in Y + if (flags[FLAG_ROTATE_Y]) { + Basis rot_y(Vector3(0, 1, 0), p.custom[0]); + p.transform.basis = p.transform.basis * rot_y; + } + } + + //scale by scale + float base_scale = Math::lerp(parameters[PARAM_SCALE] * tex_scale, 1.0f, p.scale_rand * randomness[PARAM_SCALE]); + if (base_scale == 0.0) base_scale = 0.000001; + + p.transform.basis.scale(Vector3(1, 1, 1) * base_scale); + + if (flags[FLAG_DISABLE_Z]) { + p.velocity.z = 0.0; + p.transform.origin.z = 0.0; + } + + p.transform.origin += p.velocity * local_delta; + } +} + +void CPUParticles::_update_particle_data_buffer() { +#ifndef NO_THREADS + update_mutex->lock(); +#endif + + { + + int pc = particles.size(); + + PoolVector<int>::Write ow; + int *order = NULL; + + PoolVector<float>::Write w = particle_data.write(); + PoolVector<Particle>::Read r = particles.read(); + float *ptr = w.ptr(); + + Transform un_transform; + if (!local_coords) { + un_transform = get_global_transform().affine_inverse(); + } + + if (draw_order != DRAW_ORDER_INDEX) { + ow = particle_order.write(); + order = ow.ptr(); + + for (int i = 0; i < pc; i++) { + order[i] = i; + } + if (draw_order == DRAW_ORDER_LIFETIME) { + SortArray<int, SortLifetime> sorter; + sorter.compare.particles = r.ptr(); + sorter.sort(order, pc); + } else if (draw_order == DRAW_ORDER_VIEW_DEPTH) { + Camera *c = get_viewport()->get_camera(); + if (c) { + Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close + + if (local_coords) { + dir = un_transform.basis.xform(dir).normalized(); + } + + SortArray<int, SortAxis> sorter; + sorter.compare.particles = r.ptr(); + sorter.compare.axis = dir; + sorter.sort(order, pc); + } + } + } + + for (int i = 0; i < pc; i++) { + + int idx = order ? order[i] : i; + + Transform t = r[idx].transform; + + if (!local_coords) { + t = un_transform * t; + } + + // print_line(" particle " + itos(i) + ": " + String(r[idx].active ? "[x]" : "[ ]") + "\n\txform " + r[idx].transform + "\n\t" + r[idx].velocity + "\n\tcolor: " + r[idx].color); + + if (r[idx].active) { + ptr[0] = t.basis.elements[0][0]; + ptr[1] = t.basis.elements[0][1]; + ptr[2] = t.basis.elements[0][2]; + ptr[3] = t.origin.x; + ptr[4] = t.basis.elements[1][0]; + ptr[5] = t.basis.elements[1][1]; + ptr[6] = t.basis.elements[1][2]; + ptr[7] = t.origin.y; + ptr[8] = t.basis.elements[2][0]; + ptr[9] = t.basis.elements[2][1]; + ptr[10] = t.basis.elements[2][2]; + ptr[11] = t.origin.z; + } else { + zeromem(ptr, sizeof(float) * 12); + } + + Color c = r[idx].color; + uint8_t *data8 = (uint8_t *)&ptr[12]; + data8[0] = CLAMP(c.r * 255.0, 0, 255); + data8[1] = CLAMP(c.g * 255.0, 0, 255); + data8[2] = CLAMP(c.b * 255.0, 0, 255); + data8[3] = CLAMP(c.a * 255.0, 0, 255); + + ptr[13] = r[idx].custom[0]; + ptr[14] = r[idx].custom[1]; + ptr[15] = r[idx].custom[2]; + ptr[16] = r[idx].custom[3]; + + ptr += 17; + } + } + +#ifndef NO_THREADS + update_mutex->unlock(); +#endif +} + +void CPUParticles::_update_render_thread() { + +#ifndef NO_THREADS + update_mutex->lock(); +#endif + + VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); + +#ifndef NO_THREADS + update_mutex->unlock(); +#endif +} + +void CPUParticles::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + if (is_processing_internal()) { + +#ifndef NO_THREADS + update_mutex->lock(); +#endif + VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); +#ifndef NO_THREADS + update_mutex->unlock(); +#endif + } + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + if (is_processing_internal()) { + +#ifndef NO_THREADS + update_mutex->lock(); +#endif + VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); +#ifndef NO_THREADS + update_mutex->unlock(); +#endif + } + } + + if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) { + } + + if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + + if (particles.size() == 0) + return; + + float delta = get_process_delta_time(); + if (emitting) { + + inactive_time = 0; + } else { + inactive_time += delta; + if (inactive_time > lifetime * 1.2) { + set_process_internal(false); +#ifndef NO_THREADS + update_mutex->lock(); +#endif + VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); +#ifndef NO_THREADS + update_mutex->unlock(); +#endif + //reset variables + time = 0; + inactive_time = 0; + frame_remainder = 0; + cycle = 0; + return; + } + } + + if (time == 0 && pre_process_time > 0.0) { + + float frame_time; + if (fixed_fps > 0) + frame_time = 1.0 / fixed_fps; + else + frame_time = 1.0 / 30.0; + + float todo = pre_process_time; + + while (todo >= 0) { + _particles_process(frame_time); + todo -= frame_time; + } + } + + if (fixed_fps > 0) { + float frame_time = 1.0 / fixed_fps; + float decr = frame_time; + + float ldelta = delta; + if (ldelta > 0.1) { //avoid recursive stalls if fps goes below 10 + ldelta = 0.1; + } else if (ldelta <= 0.0) { //unlikely but.. + ldelta = 0.001; + } + float todo = frame_remainder + ldelta; + + while (todo >= frame_time) { + _particles_process(frame_time); + todo -= decr; + } + + frame_remainder = todo; + + } else { + _particles_process(delta); + } + + _update_particle_data_buffer(); + } +} + +void CPUParticles::convert_from_particles(Node *p_particles) { + + Particles *particles = Object::cast_to<Particles>(p_particles); + ERR_FAIL_COND(!particles); + + set_emitting(particles->is_emitting()); + set_amount(particles->get_amount()); + set_lifetime(particles->get_lifetime()); + set_one_shot(particles->get_one_shot()); + set_pre_process_time(particles->get_pre_process_time()); + set_explosiveness_ratio(particles->get_explosiveness_ratio()); + set_randomness_ratio(particles->get_randomness_ratio()); + set_use_local_coordinates(particles->get_use_local_coordinates()); + set_fixed_fps(particles->get_fixed_fps()); + set_fractional_delta(particles->get_fractional_delta()); + set_speed_scale(particles->get_speed_scale()); + set_draw_order(DrawOrder(particles->get_draw_order())); + set_mesh(particles->get_draw_pass_mesh(0)); + + Ref<ParticlesMaterial> material = particles->get_process_material(); + if (material.is_null()) + return; + + set_spread(material->get_spread()); + set_flatness(material->get_flatness()); + + set_color(material->get_color()); + + Ref<GradientTexture> gt = material->get_color_ramp(); + if (gt.is_valid()) { + set_color_ramp(gt->get_gradient()); + } + + set_particle_flag(FLAG_ALIGN_Y_TO_VELOCITY, material->get_flag(ParticlesMaterial::FLAG_ALIGN_Y_TO_VELOCITY)); + set_particle_flag(FLAG_ROTATE_Y, material->get_flag(ParticlesMaterial::FLAG_ROTATE_Y)); + set_particle_flag(FLAG_DISABLE_Z, material->get_flag(ParticlesMaterial::FLAG_DISABLE_Z)); + set_particle_flag(FLAG_ANIM_LOOP, material->get_flag(ParticlesMaterial::FLAG_ANIM_LOOP)); + + set_emission_shape(EmissionShape(material->get_emission_shape())); + set_emission_sphere_radius(material->get_emission_sphere_radius()); + set_emission_box_extents(material->get_emission_box_extents()); + + set_gravity(material->get_gravity()); + +#define CONVERT_PARAM(m_param) \ + set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + { \ + Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ + if (ctex.is_valid()) set_param_curve(m_param, ctex->get_curve()); \ + } \ + set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + + CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); + CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); + // CONVERT_PARAM(PARAM_ORBIT_VELOCITY); + CONVERT_PARAM(PARAM_LINEAR_ACCEL); + CONVERT_PARAM(PARAM_RADIAL_ACCEL); + CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL); + CONVERT_PARAM(PARAM_DAMPING); + CONVERT_PARAM(PARAM_ANGLE); + CONVERT_PARAM(PARAM_SCALE); + CONVERT_PARAM(PARAM_HUE_VARIATION); + CONVERT_PARAM(PARAM_ANIM_SPEED); + CONVERT_PARAM(PARAM_ANIM_OFFSET); + +#undef CONVERT_PARAM +} + +void CPUParticles::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &CPUParticles::set_emitting); + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &CPUParticles::set_amount); + ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &CPUParticles::set_lifetime); + ClassDB::bind_method(D_METHOD("set_one_shot", "enable"), &CPUParticles::set_one_shot); + ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles::set_pre_process_time); + ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles::set_explosiveness_ratio); + ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles::set_randomness_ratio); + ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles::set_use_local_coordinates); + ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles::set_fixed_fps); + ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &CPUParticles::set_fractional_delta); + ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &CPUParticles::set_speed_scale); + + ClassDB::bind_method(D_METHOD("is_emitting"), &CPUParticles::is_emitting); + ClassDB::bind_method(D_METHOD("get_amount"), &CPUParticles::get_amount); + ClassDB::bind_method(D_METHOD("get_lifetime"), &CPUParticles::get_lifetime); + ClassDB::bind_method(D_METHOD("get_one_shot"), &CPUParticles::get_one_shot); + ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles::get_pre_process_time); + ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles::get_explosiveness_ratio); + ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles::get_randomness_ratio); + ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles::get_use_local_coordinates); + ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles::get_fixed_fps); + ClassDB::bind_method(D_METHOD("get_fractional_delta"), &CPUParticles::get_fractional_delta); + ClassDB::bind_method(D_METHOD("get_speed_scale"), &CPUParticles::get_speed_scale); + + ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &CPUParticles::set_draw_order); + + ClassDB::bind_method(D_METHOD("get_draw_order"), &CPUParticles::get_draw_order); + + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CPUParticles::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles::get_mesh); + + ClassDB::bind_method(D_METHOD("restart"), &CPUParticles::restart); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); + ADD_GROUP("Time", ""); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_speed_scale", "get_speed_scale"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); + ADD_GROUP("Drawing", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,View Depth"), "set_draw_order", "get_draw_order"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + + BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); + BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); + BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH); + + //////////////////////////////// + + ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles::set_spread); + ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles::get_spread); + + ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &CPUParticles::set_flatness); + ClassDB::bind_method(D_METHOD("get_flatness"), &CPUParticles::get_flatness); + + ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles::set_param); + ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles::get_param); + + ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles::set_param_randomness); + ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles::get_param_randomness); + + ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles::set_param_curve); + ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles::get_param_curve); + + ClassDB::bind_method(D_METHOD("set_color", "color"), &CPUParticles::set_color); + ClassDB::bind_method(D_METHOD("get_color"), &CPUParticles::get_color); + + ClassDB::bind_method(D_METHOD("set_color_ramp", "ramp"), &CPUParticles::set_color_ramp); + ClassDB::bind_method(D_METHOD("get_color_ramp"), &CPUParticles::get_color_ramp); + + ClassDB::bind_method(D_METHOD("set_particle_flag", "flag", "enable"), &CPUParticles::set_particle_flag); + ClassDB::bind_method(D_METHOD("get_particle_flag", "flag"), &CPUParticles::get_particle_flag); + + ClassDB::bind_method(D_METHOD("set_emission_shape", "shape"), &CPUParticles::set_emission_shape); + ClassDB::bind_method(D_METHOD("get_emission_shape"), &CPUParticles::get_emission_shape); + + ClassDB::bind_method(D_METHOD("set_emission_sphere_radius", "radius"), &CPUParticles::set_emission_sphere_radius); + ClassDB::bind_method(D_METHOD("get_emission_sphere_radius"), &CPUParticles::get_emission_sphere_radius); + + ClassDB::bind_method(D_METHOD("set_emission_box_extents", "extents"), &CPUParticles::set_emission_box_extents); + ClassDB::bind_method(D_METHOD("get_emission_box_extents"), &CPUParticles::get_emission_box_extents); + + ClassDB::bind_method(D_METHOD("set_emission_points", "array"), &CPUParticles::set_emission_points); + ClassDB::bind_method(D_METHOD("get_emission_points"), &CPUParticles::get_emission_points); + + ClassDB::bind_method(D_METHOD("set_emission_normals", "array"), &CPUParticles::set_emission_normals); + ClassDB::bind_method(D_METHOD("get_emission_normals"), &CPUParticles::get_emission_normals); + + ClassDB::bind_method(D_METHOD("set_emission_colors", "array"), &CPUParticles::set_emission_colors); + ClassDB::bind_method(D_METHOD("get_emission_colors"), &CPUParticles::get_emission_colors); + + ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles::get_gravity); + ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles::set_gravity); + + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles::convert_from_particles); + + ClassDB::bind_method(D_METHOD("_update_render_thread"), &CPUParticles::_update_render_thread); + + 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::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "emission_points"), "set_emission_points", "get_emission_points"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); + ADD_GROUP("Flags", "flag_"); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_particle_flag", "get_particle_flag", FLAG_ALIGN_Y_TO_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_rotate_y"), "set_particle_flag", "get_particle_flag", FLAG_ROTATE_Y); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_disable_z"), "set_particle_flag", "get_particle_flag", FLAG_DISABLE_Z); + ADD_GROUP("Spread", ""); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness"); + ADD_GROUP("Gravity", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); + ADD_GROUP("Initial Velocity", "initial_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_GROUP("Angular Velocity", "angular_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-360,360,0.01"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); + /* + ADD_GROUP("Orbit Velocity", "orbit_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); +*/ + ADD_GROUP("Linear Accel", "linear_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); + ADD_GROUP("Radial Accel", "radial_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); + ADD_GROUP("Tangential Accel", "tangential_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); + 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, "Curve"), "set_param_curve", "get_param_curve", 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_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, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); + ADD_GROUP("Angle", ""); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); + ADD_GROUP("Scale", ""); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_GROUP("Color", ""); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp"); + + ADD_GROUP("Hue Variation", "hue_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.1"), "set_param", "get_param", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); + ADD_GROUP("Animation", "anim_"); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "anim_loop"), "set_particle_flag", "get_particle_flag", FLAG_ANIM_LOOP); + + BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); + BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY); + //BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); + BIND_ENUM_CONSTANT(PARAM_LINEAR_ACCEL); + BIND_ENUM_CONSTANT(PARAM_RADIAL_ACCEL); + BIND_ENUM_CONSTANT(PARAM_TANGENTIAL_ACCEL); + BIND_ENUM_CONSTANT(PARAM_DAMPING); + BIND_ENUM_CONSTANT(PARAM_ANGLE); + BIND_ENUM_CONSTANT(PARAM_SCALE); + BIND_ENUM_CONSTANT(PARAM_HUE_VARIATION); + BIND_ENUM_CONSTANT(PARAM_ANIM_SPEED); + BIND_ENUM_CONSTANT(PARAM_ANIM_OFFSET); + BIND_ENUM_CONSTANT(PARAM_MAX); + + BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); + BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); + BIND_ENUM_CONSTANT(FLAG_MAX); + + BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_SPHERE); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); +} + +CPUParticles::CPUParticles() { + + time = 0; + inactive_time = 0; + frame_remainder = 0; + cycle = 0; + + multimesh = VisualServer::get_singleton()->multimesh_create(); + set_base(multimesh); + + set_emitting(true); + set_one_shot(false); + set_amount(8); + set_lifetime(1); + set_fixed_fps(0); + set_fractional_delta(true); + set_pre_process_time(0); + set_explosiveness_ratio(0); + set_randomness_ratio(0); + set_use_local_coordinates(true); + + set_draw_order(DRAW_ORDER_INDEX); + set_speed_scale(1); + + set_spread(45); + set_flatness(0); + set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1); + //set_param(PARAM_ORBIT_VELOCITY, 0); + set_param(PARAM_LINEAR_ACCEL, 0); + set_param(PARAM_RADIAL_ACCEL, 0); + set_param(PARAM_TANGENTIAL_ACCEL, 0); + set_param(PARAM_DAMPING, 0); + set_param(PARAM_ANGLE, 0); + set_param(PARAM_SCALE, 1); + set_param(PARAM_HUE_VARIATION, 0); + set_param(PARAM_ANIM_SPEED, 0); + set_param(PARAM_ANIM_OFFSET, 0); + set_emission_shape(EMISSION_SHAPE_POINT); + set_emission_sphere_radius(1); + set_emission_box_extents(Vector3(1, 1, 1)); + + set_gravity(Vector3(0, -9.8, 0)); + + for (int i = 0; i < PARAM_MAX; i++) { + set_param_randomness(Parameter(i), 0); + } + + for (int i = 0; i < FLAG_MAX; i++) { + flags[i] = false; + } + + set_color(Color(1, 1, 1, 1)); + +#ifndef NO_THREADS + update_mutex = Mutex::create(); +#endif +} + +CPUParticles::~CPUParticles() { + VS::get_singleton()->free(multimesh); + +#ifndef NO_THREADS + memdelete(update_mutex); +#endif +} diff --git a/scene/3d/cpu_particles.h b/scene/3d/cpu_particles.h new file mode 100644 index 0000000000..1ee709719d --- /dev/null +++ b/scene/3d/cpu_particles.h @@ -0,0 +1,258 @@ +#ifndef CPU_PARTICLES_H +#define CPU_PARTICLES_H +#include "rid.h" +#include "scene/3d/visual_instance.h" +#include "scene/main/timer.h" +#include "scene/resources/material.h" + +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ + +class CPUParticles : public GeometryInstance { +private: + GDCLASS(CPUParticles, GeometryInstance); + +public: + enum DrawOrder { + DRAW_ORDER_INDEX, + DRAW_ORDER_LIFETIME, + DRAW_ORDER_VIEW_DEPTH, + }; + + enum Parameter { + + PARAM_INITIAL_LINEAR_VELOCITY, + PARAM_ANGULAR_VELOCITY, + //PARAM_ORBIT_VELOCITY, + PARAM_LINEAR_ACCEL, + PARAM_RADIAL_ACCEL, + PARAM_TANGENTIAL_ACCEL, + PARAM_DAMPING, + PARAM_ANGLE, + PARAM_SCALE, + PARAM_HUE_VARIATION, + PARAM_ANIM_SPEED, + PARAM_ANIM_OFFSET, + PARAM_MAX + }; + + enum Flags { + FLAG_ALIGN_Y_TO_VELOCITY, + FLAG_ROTATE_Y, + FLAG_DISABLE_Z, + FLAG_ANIM_LOOP, + FLAG_MAX + }; + + enum EmissionShape { + EMISSION_SHAPE_POINT, + EMISSION_SHAPE_SPHERE, + EMISSION_SHAPE_BOX, + EMISSION_SHAPE_POINTS, + EMISSION_SHAPE_DIRECTED_POINTS, + }; + +private: + bool emitting; + + struct Particle { + Transform transform; + Color color; + float custom[4]; + Vector3 velocity; + bool active; + float angle_rand; + float scale_rand; + float hue_rot_rand; + float anim_offset_rand; + float time; + Color base_color; + + uint32_t seed; + }; + + float time; + float inactive_time; + float frame_remainder; + int cycle; + + RID multimesh; + + PoolVector<Particle> particles; + PoolVector<float> particle_data; + PoolVector<int> particle_order; + + struct SortLifetime { + const Particle *particles; + + bool operator()(int p_a, int p_b) const { + return particles[p_a].time < particles[p_b].time; + } + }; + + struct SortAxis { + const Particle *particles; + Vector3 axis; + bool operator()(int p_a, int p_b) const { + + return axis.dot(particles[p_a].transform.origin) < axis.dot(particles[p_b].transform.origin); + } + }; + + // + + bool one_shot; + + float lifetime; + float pre_process_time; + float explosiveness_ratio; + float randomness_ratio; + float speed_scale; + bool local_coords; + int fixed_fps; + bool fractional_delta; + + DrawOrder draw_order; + + Ref<Mesh> mesh; + + //////// + + float spread; + float flatness; + + float parameters[PARAM_MAX]; + float randomness[PARAM_MAX]; + + Ref<Curve> curve_parameters[PARAM_MAX]; + Color color; + Ref<Gradient> color_ramp; + + bool flags[FLAG_MAX]; + + EmissionShape emission_shape; + float emission_sphere_radius; + Vector3 emission_box_extents; + PoolVector<Vector3> emission_points; + PoolVector<Vector3> emission_normals; + PoolVector<Color> emission_colors; + int emission_point_count; + + bool anim_loop; + Vector3 gravity; + + void _particles_process(float p_delta); + void _update_particle_data_buffer(); + + Mutex *update_mutex; + + void _update_render_thread(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + virtual void _validate_property(PropertyInfo &property) const; + +public: + AABB get_aabb() const; + PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + + void set_emitting(bool p_emitting); + void set_amount(int p_amount); + void set_lifetime(float p_lifetime); + void set_one_shot(bool p_one_shot); + void set_pre_process_time(float p_time); + void set_explosiveness_ratio(float p_ratio); + void set_randomness_ratio(float p_ratio); + void set_visibility_aabb(const AABB &p_aabb); + void set_use_local_coordinates(bool p_enable); + void set_speed_scale(float p_scale); + + bool is_emitting() const; + int get_amount() const; + float get_lifetime() const; + bool get_one_shot() const; + float get_pre_process_time() const; + float get_explosiveness_ratio() const; + float get_randomness_ratio() const; + AABB get_visibility_aabb() const; + bool get_use_local_coordinates() const; + float get_speed_scale() const; + + void set_fixed_fps(int p_count); + int get_fixed_fps() const; + + void set_fractional_delta(bool p_enable); + bool get_fractional_delta() const; + + void set_draw_order(DrawOrder p_order); + DrawOrder get_draw_order() const; + + void set_draw_passes(int p_count); + int get_draw_passes() const; + + void set_mesh(const Ref<Mesh> &p_mesh); + Ref<Mesh> get_mesh() const; + + /////////////////// + + void set_spread(float p_spread); + float get_spread() const; + + void set_flatness(float p_flatness); + float get_flatness() const; + + void set_param(Parameter p_param, float p_value); + float get_param(Parameter p_param) const; + + void set_param_randomness(Parameter p_param, float p_value); + float get_param_randomness(Parameter p_param) const; + + void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); + Ref<Curve> get_param_curve(Parameter p_param) const; + + void set_color(const Color &p_color); + Color get_color() const; + + void set_color_ramp(const Ref<Gradient> &p_texture); + Ref<Gradient> get_color_ramp() const; + + void set_particle_flag(Flags p_flag, bool p_enable); + bool get_particle_flag(Flags p_flag) const; + + void set_emission_shape(EmissionShape p_shape); + void set_emission_sphere_radius(float p_radius); + void set_emission_box_extents(Vector3 p_extents); + void set_emission_points(const PoolVector<Vector3> &p_points); + void set_emission_normals(const PoolVector<Vector3> &p_normals); + void set_emission_colors(const PoolVector<Color> &p_colors); + void set_emission_point_count(int p_count); + + EmissionShape get_emission_shape() const; + float get_emission_sphere_radius() const; + Vector3 get_emission_box_extents() const; + PoolVector<Vector3> get_emission_points() const; + PoolVector<Vector3> get_emission_normals() const; + PoolVector<Color> get_emission_colors() const; + int get_emission_point_count() const; + + void set_gravity(const Vector3 &p_gravity); + Vector3 get_gravity() const; + + virtual String get_configuration_warning() const; + + void restart(); + + void convert_from_particles(Node *p_particles); + + CPUParticles(); + ~CPUParticles(); +}; + +VARIANT_ENUM_CAST(CPUParticles::DrawOrder) +VARIANT_ENUM_CAST(CPUParticles::Parameter) +VARIANT_ENUM_CAST(CPUParticles::Flags) +VARIANT_ENUM_CAST(CPUParticles::EmissionShape) + +#endif // CPU_PARTICLES_H diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index 80bae911d4..e836a6154a 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -371,7 +371,7 @@ void MeshInstance::_bind_methods() { ClassDB::set_method_flags("MeshInstance", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton"), "set_skeleton_path", "get_skeleton_path"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton"), "set_skeleton_path", "get_skeleton_path"); } MeshInstance::MeshInstance() { 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/physics_body.cpp b/scene/3d/physics_body.cpp index 5056fb2fe4..e851c8d643 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -979,7 +979,7 @@ bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_in return colliding; } -Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, bool p_infinite_inertia, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle) { +Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle, bool p_infinite_inertia) { Vector3 lv = p_linear_velocity; @@ -1128,7 +1128,7 @@ Ref<KinematicCollision> KinematicBody::_get_slide_collision(int p_bounce) { void KinematicBody::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia"), &KinematicBody::_move, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "infinite_inertia", "slope_stop_min_velocity", "max_slides", "floor_max_angle"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(true), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45))); + ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "slope_stop_min_velocity", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody::test_move); diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h index 17d2769c79..0190dcbfc3 100644 --- a/scene/3d/physics_body.h +++ b/scene/3d/physics_body.h @@ -303,7 +303,7 @@ public: void set_safe_margin(float p_margin); float get_safe_margin() const; - Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), bool p_infinite_inertia = true, float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45)); + Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45), bool p_infinite_inertia = true); bool is_on_floor() const; bool is_on_wall() const; bool is_on_ceiling() const; diff --git a/scene/3d/physics_joint.cpp b/scene/3d/physics_joint.cpp index b2d10006f7..7988c43eab 100644 --- a/scene/3d/physics_joint.cpp +++ b/scene/3d/physics_joint.cpp @@ -154,8 +154,8 @@ void Joint::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::INT, "solver/priority", PROPERTY_HINT_RANGE, "1,8,1"), "set_solver_priority", "get_solver_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision/exclude_nodes"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); @@ -260,7 +260,7 @@ void HingeJoint::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_lower_limit", "lower_limit"), &HingeJoint::_set_lower_limit); ClassDB::bind_method(D_METHOD("_get_lower_limit"), &HingeJoint::_get_lower_limit); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.00,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit/enable"), "set_flag", "get_flag", FLAG_USE_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit/upper", PROPERTY_HINT_RANGE, "-180,180,0.1"), "_set_upper_limit", "_get_upper_limit"); @@ -270,7 +270,7 @@ void HingeJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_limit/relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_RELAXATION); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "motor/enable"), "set_flag", "get_flag", FLAG_ENABLE_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "-200,200,0.01,or_greater,or_lesser"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/max_impulse", PROPERTY_HINT_RANGE, "0.01,1024,0.01"), "set_param", "get_param", PARAM_MOTOR_MAX_IMPULSE); BIND_ENUM_CONSTANT(PARAM_BIAS); 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/remote_transform.cpp b/scene/3d/remote_transform.cpp index afb85f7314..2156e24cd0 100644 --- a/scene/3d/remote_transform.cpp +++ b/scene/3d/remote_transform.cpp @@ -194,7 +194,7 @@ void RemoteTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index 76d90dc6ff..8d91b6f09f 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -547,6 +547,8 @@ void Skeleton::localize_rests() { } } +#ifndef _3D_DISABLED + void Skeleton::bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone) { ERR_FAIL_INDEX(p_bone, bones.size()); ERR_FAIL_COND(bones[p_bone].physical_bone); @@ -691,6 +693,8 @@ void Skeleton::physical_bones_remove_collision_exception(RID p_exception) { _physical_bones_add_remove_collision_exception(false, this, p_exception); } +#endif // _3D_DISABLED + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -727,11 +731,15 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_transform", "bone_idx"), &Skeleton::get_bone_transform); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton::physical_bones_stop_simulation); ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton::physical_bones_start_simulation_on, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton::physical_bones_remove_collision_exception); +#endif // _3D_DISABLED + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index dad11960a5..9672acb57a 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -38,7 +38,10 @@ @author Juan Linietsky <reduzio@gmail.com> */ +#ifndef _3D_DISABLED class PhysicalBone; +#endif // _3D_DISABLED + class Skeleton : public Spatial { GDCLASS(Skeleton, Spatial); @@ -64,8 +67,10 @@ class Skeleton : public Spatial { Transform transform_final; +#ifndef _3D_DISABLED PhysicalBone *physical_bone; PhysicalBone *cache_parent_physical_bone; +#endif // _3D_DISABLED List<uint32_t> nodes_bound; @@ -75,8 +80,10 @@ class Skeleton : public Spatial { ignore_animation = false; custom_pose_enable = false; disable_rest = false; +#ifndef _3D_DISABLED physical_bone = NULL; cache_parent_physical_bone = NULL; +#endif // _3D_DISABLED } }; @@ -164,6 +171,7 @@ public: void localize_rests(); // used for loaders and tools +#ifndef _3D_DISABLED // Physical bone API void bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone); @@ -182,6 +190,7 @@ public: void physical_bones_start_simulation_on(const Array &p_bones); void physical_bones_add_collision_exception(RID p_exception); void physical_bones_remove_collision_exception(RID p_exception); +#endif // _3D_DISABLED public: Skeleton(); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index d833e6a8bb..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); @@ -901,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/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp new file mode 100644 index 0000000000..d3d2870c3f --- /dev/null +++ b/scene/animation/animation_blend_space_1d.cpp @@ -0,0 +1,294 @@ +#include "animation_blend_space_1d.h" + +void AnimationNodeBlendSpace1D::set_tree(AnimationTree *p_player) { + + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } + +} + +void AnimationNodeBlendSpace1D::_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 AnimationNodeBlendSpace1D::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace1D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace1D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace1D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace1D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace1D::set_blend_pos); + ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace1D::get_blend_pos); + + ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label); + ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point); + + for (int i = 0; i < MAX_BLEND_POINTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "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::REAL, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label"); +} + +void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float 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 - 1; i > p_at_index; i++) { + blend_points[i] = blend_points[i - 1]; + } + } + + 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_tree(get_tree()); + + blend_points_used++; +} + +void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].position = p_position; +} + +void AnimationNodeBlendSpace1D::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_tree(NULL); + } + + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} + +float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, 0); + return blend_points[p_point].position; +} + +Ref<AnimationRootNode> AnimationNodeBlendSpace1D::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 AnimationNodeBlendSpace1D::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_tree(NULL); + + for (int i = p_point; i < blend_points_used - 1; i++) { + blend_points[i] = blend_points[i + 1]; + } + + blend_points_used--; +} + +int AnimationNodeBlendSpace1D::get_blend_point_count() const { + + return blend_points_used; +} + +void AnimationNodeBlendSpace1D::set_min_space(float p_min) { + min_space = p_min; + + if (min_space >= max_space) { + min_space = max_space - 1; + } +} + +float AnimationNodeBlendSpace1D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace1D::set_max_space(float p_max) { + max_space = p_max; + + if (max_space <= min_space) { + max_space = min_space + 1; + } +} + +float AnimationNodeBlendSpace1D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace1D::set_snap(float p_snap) { + snap = p_snap; +} + +float AnimationNodeBlendSpace1D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace1D::set_blend_pos(float p_pos) { + blend_pos = p_pos; +} + +float AnimationNodeBlendSpace1D::get_blend_pos() const { + return blend_pos; +} + +void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) { + value_label = p_label; +} + +String AnimationNodeBlendSpace1D::get_value_label() const { + return value_label; +} + +void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { + if (p_index == blend_points_used) { + add_blend_point(p_node, 0); + } else { + set_blend_point_node(p_index, p_node); + } +} + +float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { + + if (blend_points_used == 0) { + return 0.0; + } + + if (blend_points_used == 1) { + // only one point available, just play that animation + return blend_node(blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); + } + + float weights[MAX_BLEND_POINTS] = {}; + + int point_lower = -1; + float pos_lower = 0.0; + int point_higher = -1; + float pos_higher = 0.0; + + // find the closest two points to blend between + for (int i = 0; i < blend_points_used; i++) { + + float pos = blend_points[i].position; + + if (pos <= blend_pos) { + if (point_lower == -1) { + point_lower = i; + pos_lower = pos; + } else if ((blend_pos - pos) < (blend_pos - pos_lower)) { + point_lower = i; + pos_lower = pos; + } + } else { + if (point_higher == -1) { + point_higher = i; + pos_higher = pos; + } else if ((pos - blend_pos) < (pos_higher - blend_pos)) { + point_higher = i; + pos_higher = pos; + } + } + } + + // fill in weights + + if (point_lower == -1) { + // we are on the left side, no other point to the left + // we just play the next point. + + weights[point_higher] = 1.0; + } else if (point_higher == -1) { + // we are on the right side, no other point to the right + // we just play the previous point + + weights[point_lower] = 1.0; + } else { + + // we are between two points. + // figure out weights, then blend the animations + + float distance_between_points = pos_higher - pos_lower; + + float current_pos_inbetween = blend_pos - pos_lower; + + float blend_percentage = current_pos_inbetween / distance_between_points; + + float blend_lower = 1.0 - blend_percentage; + float blend_higher = blend_percentage; + + weights[point_lower] = blend_lower; + weights[point_higher] = blend_higher; + } + + // actually blend the animations now + + float max_time_remaining = 0.0; + + for (int i = 0; i < blend_points_used; i++) { + float remaining = blend_node(blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); + + max_time_remaining = MAX(max_time_remaining, remaining); + } + + return max_time_remaining; +} + +String AnimationNodeBlendSpace1D::get_caption() const { + return "BlendSpace1D"; +} + +AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() { + + blend_points_used = 0; + max_space = 1; + min_space = -1; + + snap = 0.1; + value_label = "value"; +} + +AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h new file mode 100644 index 0000000000..774894ef4b --- /dev/null +++ b/scene/animation/animation_blend_space_1d.h @@ -0,0 +1,71 @@ +#ifndef ANIMATION_BLEND_SPACE_1D_H +#define ANIMATION_BLEND_SPACE_1D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace1D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode) + + enum { + MAX_BLEND_POINTS = 64 + }; + + struct BlendPoint { + Ref<AnimationRootNode> node; + float position; + }; + + BlendPoint blend_points[MAX_BLEND_POINTS]; + int blend_points_used; + + float blend_pos; + + float max_space; + float min_space; + + float snap; + + String value_label; + + void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + static void _bind_methods(); + +public: + + virtual void set_tree(AnimationTree *p_player); + + void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1); + void set_blend_point_position(int p_point, float p_position); + void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node); + + float 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; + + void set_min_space(float p_min); + float get_min_space() const; + + void set_max_space(float p_max); + float get_max_space() const; + + void set_snap(float p_snap); + float get_snap() const; + + void set_blend_pos(float p_pos); + float get_blend_pos() const; + + void set_value_label(const String &p_label); + String get_value_label() const; + + float process(float p_time, bool p_seek); + String get_caption() const; + + AnimationNodeBlendSpace1D(); + ~AnimationNodeBlendSpace1D(); +}; + +#endif // ANIMATION_BLEND_SPACE_1D_H diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp new file mode 100644 index 0000000000..82db647124 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.cpp @@ -0,0 +1,567 @@ +#include "animation_blend_space_2d.h" +#include "math/delaunay.h" + +void AnimationNodeBlendSpace2D::set_tree(AnimationTree *p_player) { + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } +} + + +void AnimationNodeBlendSpace2D::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 - 1; 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_tree(get_tree()); + blend_points_used++; + + if (auto_triangles) { + trianges_dirty = true; + } +} + +void AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::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_tree(NULL); + } + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} +Vector2 AnimationNodeBlendSpace2D::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> AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::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_tree(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 AnimationNodeBlendSpace2D::get_blend_point_count() const { + + return blend_points_used; +} + +bool AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) { + ERR_FAIL_INDEX(p_triangle, triangles.size()); + + triangles.remove(p_triangle); +} + +int AnimationNodeBlendSpace2D::get_triangle_count() const { + return triangles.size(); +} + +void AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace2D::set_snap(const Vector2 &p_snap) { + snap = p_snap; +} +Vector2 AnimationNodeBlendSpace2D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace2D::set_blend_position(const Vector2 &p_pos) { + blend_pos = p_pos; +} +Vector2 AnimationNodeBlendSpace2D::get_blend_position() const { + return blend_pos; +} + +void AnimationNodeBlendSpace2D::set_x_label(const String &p_label) { + x_label = p_label; +} +String AnimationNodeBlendSpace2D::get_x_label() const { + return x_label; +} + +void AnimationNodeBlendSpace2D::set_y_label(const String &p_label) { + y_label = p_label; +} +String AnimationNodeBlendSpace2D::get_y_label() const { + return y_label; +} + +void AnimationNodeBlendSpace2D::_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 AnimationNodeBlendSpace2D::_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> AnimationNodeBlendSpace2D::_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 AnimationNodeBlendSpace2D::_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 AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::_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 AnimationNodeBlendSpace2D::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 AnimationNodeBlendSpace2D::get_caption() const { + return "BlendSpace2D"; +} + +void AnimationNodeBlendSpace2D::_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 AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) { + auto_triangles = p_enable; + if (auto_triangles) { + trianges_dirty = true; + } +} + +bool AnimationNodeBlendSpace2D::get_auto_triangles() const { + return auto_triangles; +} + +void AnimationNodeBlendSpace2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point); + ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace2D::remove_triangle); + ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace2D::get_triangle_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace2D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace2D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace2D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace2D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace2D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace2D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_position", "pos"), &AnimationNodeBlendSpace2D::set_blend_position); + ClassDB::bind_method(D_METHOD("get_blend_position"), &AnimationNodeBlendSpace2D::get_blend_position); + + ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace2D::set_x_label); + ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace2D::get_x_label); + + ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label); + ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point); + + ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles); + ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles); + + ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace2D::set_auto_triangles); + ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace2D::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_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_position", "get_blend_position"); + 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"); +} + +AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() { + + 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; +} + +AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h new file mode 100644 index 0000000000..4778299df1 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.h @@ -0,0 +1,97 @@ +#ifndef ANIMATION_BLEND_SPACE_2D_H +#define ANIMATION_BLEND_SPACE_2D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace2D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace2D, 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: + + virtual void set_tree(AnimationTree *p_player); + + 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_position(const Vector2 &p_pos); + Vector2 get_blend_position() 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; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + Vector2 get_closest_point(const Vector2 &p_point); + + void set_auto_triangles(bool p_enable); + bool get_auto_triangles() const; + + AnimationNodeBlendSpace2D(); + ~AnimationNodeBlendSpace2D(); +}; + +#endif // ANIMATION_BLEND_SPACE_2D_H diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp new file mode 100644 index 0000000000..6dcd5ca8ea --- /dev/null +++ b/scene/animation/animation_blend_tree.cpp @@ -0,0 +1,1170 @@ +#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") { + AnimationTree *gp = get_tree(); + 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 AnimationNodeAdd2::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd2::get_amount() const { + return amount; +} + +String AnimationNodeAdd2::get_caption() const { + return "Add2"; +} +void AnimationNodeAdd2::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd2::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd2::has_filter() const { + + return true; +} + +float AnimationNodeAdd2::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 AnimationNodeAdd2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd2::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd2::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::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"); +} + +AnimationNodeAdd2::AnimationNodeAdd2() { + + add_input("in"); + add_input("add"); + amount = 0; + sync = false; +} + +//////////////////////////////////////////////// + +void AnimationNodeAdd3::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd3::get_amount() const { + return amount; +} + +String AnimationNodeAdd3::get_caption() const { + return "Add3"; +} +void AnimationNodeAdd3::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd3::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd3::has_filter() const { + + return true; +} + +float AnimationNodeAdd3::process(float p_time, bool p_seek) { + + blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync); + float rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_PASS, !sync); + + return rem0; +} + +void AnimationNodeAdd3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd3::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd3::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd3::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd3::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"); +} + +AnimationNodeAdd3::AnimationNodeAdd3() { + + add_input("-add"); + 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_tree() != 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_tree(get_tree()); + + 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_tree(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_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + node->set_tree(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_tree(NULL); + } +} diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h new file mode 100644 index 0000000000..e86cc2e823 --- /dev/null +++ b/scene/animation/animation_blend_tree.h @@ -0,0 +1,352 @@ +#ifndef ANIMATION_BLEND_TREE_H +#define ANIMATION_BLEND_TREE_H + +#include "scene/animation/animation_tree.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 AnimationNodeAdd2 : public AnimationNode { + GDCLASS(AnimationNodeAdd2, 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); + + AnimationNodeAdd2(); +}; + +class AnimationNodeAdd3 : public AnimationNode { + GDCLASS(AnimationNodeAdd3, 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); + + AnimationNodeAdd3(); +}; + +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_tree(AnimationTree *p_player); + AnimationNodeBlendTree(); + ~AnimationNodeBlendTree(); +}; + +VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError) + +#endif // ANIMATION_BLEND_TREE_H diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp new file mode 100644 index 0000000000..c5ad980806 --- /dev/null +++ b/scene/animation/animation_node_state_machine.cpp @@ -0,0 +1,790 @@ +#include "animation_node_state_machine.h" + +///////////////////////////////////////////////// + +void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) { + + switch_mode = p_mode; +} + +AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const { + + return switch_mode; +} + +void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) { + auto_advance = p_enable; +} + +bool AnimationNodeStateMachineTransition::has_auto_advance() const { + return auto_advance; +} + +void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { + + ERR_FAIL_COND(p_xfade < 0); + xfade = p_xfade; + emit_changed(); +} + +float AnimationNodeStateMachineTransition::get_xfade_time() const { + return xfade; +} + +void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) { + disabled = p_disabled; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_disabled() const { + return disabled; +} + +void AnimationNodeStateMachineTransition::set_priority(int p_priority) { + priority = p_priority; + emit_changed(); +} + +int AnimationNodeStateMachineTransition::get_priority() const { + return priority; +} + +void AnimationNodeStateMachineTransition::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode); + ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode); + + ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance); + ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance); + + ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time); + ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time); + + ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled); + ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); + + BIND_CONSTANT(SWITCH_MODE_IMMEDIATE); + BIND_CONSTANT(SWITCH_MODE_SYNC); + BIND_CONSTANT(SWITCH_MODE_AT_END); +} + +AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { + + switch_mode = SWITCH_MODE_IMMEDIATE; + auto_advance = false; + xfade = 0; + disabled = false; + priority = 1; +} + +/////////////////////////////////////////////////////// +void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node) { + + ERR_FAIL_COND(states.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_tree() != NULL); + ERR_FAIL_COND(String(p_name).find("/") != -1); + states[p_name] = p_node; + + p_node->set_parent(this); + p_node->set_tree(get_tree()); + + emit_changed(); +} + +Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const { + + ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>()); + + return states[p_name]; +} + +StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const { + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + if (E->get() == p_node) { + return E->key(); + } + } + + ERR_FAIL_V(StringName()); +} + +bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { + return states.has(p_name); +} +void AnimationNodeStateMachine::remove_node(const StringName &p_name) { + + ERR_FAIL_COND(!states.has(p_name)); + + { + //erase node connections + Ref<AnimationNode> node = states[p_name]; + for (int i = 0; i < node->get_input_count(); i++) { + node->set_input_connection(i, StringName()); + } + node->set_parent(NULL); + node->set_tree(NULL); + } + + states.erase(p_name); + path.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name || transitions[i].to == p_name) { + transitions.remove(i); + i--; + } + } + + if (start_node == p_name) { + start_node = StringName(); + } + + if (end_node == p_name) { + end_node = StringName(); + } + + if (playing && current == p_name) { + stop(); + } + emit_changed(); +} + +void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { + + ERR_FAIL_COND(!states.has(p_name)); + ERR_FAIL_COND(states.has(p_new_name)); + + states[p_new_name] = states[p_name]; + states.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name) { + transitions[i].from = p_new_name; + } + + if (transitions[i].to == p_name) { + transitions[i].to = p_new_name; + } + } + + if (start_node == p_name) { + start_node = p_new_name; + } + + if (end_node == p_name) { + end_node = p_new_name; + } + + if (playing && current == p_name) { + current = p_new_name; + } + + path.clear(); //clear path +} + +void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { + + List<StringName> nodes; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + nodes.push_back(E->key()); + } + nodes.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + r_nodes->push_back(E->get()); + } +} + +bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return true; + } + return false; +} + +int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return i; + } + return -1; +} + +void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) { + + ERR_FAIL_COND(p_from == p_to); + ERR_FAIL_COND(!states.has(p_from)); + ERR_FAIL_COND(!states.has(p_to)); + ERR_FAIL_COND(p_transition.is_null()); + + for (int i = 0; i < transitions.size(); i++) { + ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); + } + + Transition tr; + tr.from = p_from; + tr.to = p_to; + tr.transition = p_transition; + + transitions.push_back(tr); +} + +Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const { + ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>()); + return transitions[p_transition].transition; +} +StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].from; +} +StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].to; +} + +int AnimationNodeStateMachine::get_transition_count() const { + + return transitions.size(); +} +void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { + transitions.remove(i); + return; + } + } + + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) { + + transitions.remove(p_transition); + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::set_start_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + start_node = p_node; +} + +String AnimationNodeStateMachine::get_start_node() const { + + return start_node; +} + +void AnimationNodeStateMachine::set_end_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + end_node = p_node; +} + +String AnimationNodeStateMachine::get_end_node() const { + + return end_node; +} + +void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) { + graph_offset = p_offset; +} + +Vector2 AnimationNodeStateMachine::get_graph_offset() const { + return graph_offset; +} + +float AnimationNodeStateMachine::process(float p_time, bool p_seek) { + + //if not playing and it can restart, then restart + if (!playing) { + if (start_node) { + start(start_node); + } else { + return 0; + } + } + + bool do_start = (p_seek && p_time == 0) || play_start || current == StringName(); + + if (do_start) { + + if (start_node != StringName() && p_seek && p_time == 0) { + current = start_node; + } + + len_current = blend_node(states[current], 0, true, 1.0, FILTER_IGNORE, false); + pos_current = 0; + loops_current = 0; + play_start = false; + } + + float fade_blend = 1.0; + + if (fading_from != StringName()) { + + if (!p_seek) { + fading_pos += p_time; + } + fade_blend = MIN(1.0, fading_pos / fading_time); + if (fade_blend >= 1.0) { + fading_from = StringName(); + } + } + + float rem = blend_node(states[current], p_time, p_seek, fade_blend, FILTER_IGNORE, false); + + if (fading_from != StringName()) { + + blend_node(states[fading_from], p_time, p_seek, 1.0 - fade_blend, FILTER_IGNORE, false); + } + + //guess playback position + if (rem > len_current) { // weird but ok + len_current = rem; + } + + { //advance and loop check + + float next_pos = len_current - rem; + + if (next_pos < pos_current) { + loops_current++; + } + pos_current = next_pos; //looped + } + + //find next + StringName next; + float next_xfade = 0; + AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; + + if (path.size()) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].to == path[0]) { + next_xfade = transitions[i].transition->get_xfade_time(); + switch_mode = transitions[i].transition->get_switch_mode(); + next = path[0]; + } + } + } else { + float priority_best = 1e20; + int auto_advance_to = -1; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].transition->has_auto_advance()) { + + if (transitions[i].transition->get_priority() < priority_best) { + auto_advance_to = i; + } + } + } + + if (auto_advance_to != -1) { + next = transitions[auto_advance_to].to; + next_xfade = transitions[auto_advance_to].transition->get_xfade_time(); + switch_mode = transitions[auto_advance_to].transition->get_switch_mode(); + } + } + + //if next, see when to transition + if (next != StringName()) { + + bool goto_next = false; + + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) { + goto_next = fading_from == StringName(); + } else { + goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; + if (loops_current > 0) { + next_xfade = 0; + } + } + + if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped + + if (next_xfade) { + //time to fade, baby + fading_from = current; + fading_time = next_xfade; + fading_pos = 0; + } else { + fading_from = StringName(); + fading_pos = 0; + } + + if (path.size()) { //if it came from path, remove path + path.remove(0); + } + current = next; + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = MIN(pos_current, len_current); + blend_node(states[current], pos_current, true, 0, FILTER_IGNORE, false); + + } else { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = 0; + } + + rem = len_current; //so it does not show 0 on transition + loops_current = 0; + } + } + + //compute time left for transitions by using the end node + + if (end_node != StringName() && end_node != current) { + + rem = blend_node(states[end_node], 0, true, 0, FILTER_IGNORE, false); + } + + return rem; +} + +bool AnimationNodeStateMachine::travel(const StringName &p_state) { + ERR_FAIL_COND_V(!playing, false); + ERR_FAIL_COND_V(!states.has(p_state), false); + ERR_FAIL_COND_V(!states.has(current), false); + + path.clear(); //a new one will be needed + + if (current == p_state) + return true; //nothing to do + + loops_current = 0; // reset loops, so fade does not happen immediately + + Vector2 current_pos = states[current]->get_position(); + Vector2 target_pos = states[p_state]->get_position(); + + Map<StringName, AStarCost> cost_map; + + List<int> open_list; + + //build open list + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current) { + open_list.push_back(i); + float cost = states[transitions[i].to]->get_position().distance_to(current_pos); + cost *= transitions[i].transition->get_priority(); + AStarCost ap; + ap.prev = current; + ap.distance = cost; + cost_map[transitions[i].to] = ap; + + if (transitions[i].to == p_state) { //prematurely found it! :D + path.push_back(p_state); + return true; + } + } + } + + //begin astar + bool found_route = false; + while (!found_route) { + + if (open_list.size() == 0) { + return false; //no path found + } + + //find the last cost transition + List<int>::Element *least_cost_transition = NULL; + float least_cost = 1e20; + + for (List<int>::Element *E = open_list.front(); E; E = E->next()) { + + float cost = cost_map[transitions[E->get()].to].distance; + cost += states[transitions[E->get()].to]->get_position().distance_to(target_pos); + + if (cost < least_cost) { + least_cost_transition = E; + } + } + + StringName transition_prev = transitions[least_cost_transition->get()].from; + StringName transition = transitions[least_cost_transition->get()].to; + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from != transition || transitions[i].to == transition_prev) { + continue; //not interested on those + } + + float distance = states[transitions[i].from]->get_position().distance_to(states[transitions[i].to]->get_position()); + distance *= transitions[i].transition->get_priority(); + distance += cost_map[transitions[i].from].distance; + + if (cost_map.has(transitions[i].to)) { + //oh this was visited already, can we win the cost? + if (distance < cost_map[transitions[i].to].distance) { + cost_map[transitions[i].to].distance = distance; + cost_map[transitions[i].to].prev = transitions[i].from; + } + } else { + //add to open list + AStarCost ac; + ac.prev = transitions[i].from; + ac.distance = distance; + cost_map[transitions[i].to] = ac; + + open_list.push_back(i); + + if (transitions[i].to == p_state) { + found_route = true; + break; + } + } + } + + if (found_route) { + break; + } + + open_list.erase(least_cost_transition); + } + + //make path + StringName at = p_state; + while (at != current) { + path.push_back(at); + at = cost_map[at].prev; + } + + path.invert(); + + return true; +} + +void AnimationNodeStateMachine::start(const StringName &p_state) { + + ERR_FAIL_COND(!states.has(p_state)); + path.clear(); + current = p_state; + playing = true; + play_start = true; +} +void AnimationNodeStateMachine::stop() { + playing = false; + play_start = false; + current = StringName(); +} +bool AnimationNodeStateMachine::is_playing() const { + + return playing; +} +StringName AnimationNodeStateMachine::get_current_node() const { + if (!playing) { + return StringName(); + } + + return current; +} + +StringName AnimationNodeStateMachine::get_blend_from_node() const { + if (!playing) { + return StringName(); + } + + return fading_from; +} + +float AnimationNodeStateMachine::get_current_play_pos() const { + return pos_current; +} +float AnimationNodeStateMachine::get_current_length() const { + return len_current; +} + +Vector<StringName> AnimationNodeStateMachine::get_travel_path() const { + return path; +} +String AnimationNodeStateMachine::get_caption() const { + return "StateMachine"; +} + +void AnimationNodeStateMachine::_notification(int p_what) { +} + +void AnimationNodeStateMachine::set_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + Ref<AnimationRootNode> node = E->get(); + node->set_tree(p_player); + } +} + +bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) { + + String name = p_name; + if (name.begins_with("states/")) { + 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 (states.has(node_name)) { + states[node_name]->set_position(p_value); + } + return true; + } + } else if (name == "transitions") { + + Array trans = p_value; + ERR_FAIL_COND_V(trans.size() % 3 != 0, false); + + for (int i = 0; i < trans.size(); i += 3) { + add_transition(trans[i], trans[i + 1], trans[i + 2]); + } + return true; + } else if (name == "start_node") { + set_start_node(p_value); + return true; + } else if (name == "end_node") { + set_end_node(p_value); + return true; + } else if (name == "graph_offset") { + set_graph_offset(p_value); + return true; + } + + return false; +} + +bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const { + + String name = p_name; + if (name.begins_with("states/")) { + String node_name = name.get_slicec('/', 1); + String what = name.get_slicec('/', 2); + + if (what == "node") { + if (states.has(node_name)) { + r_ret = states[node_name]; + return true; + } + } + + if (what == "position") { + + if (states.has(node_name)) { + r_ret = states[node_name]->get_position(); + return true; + } + } + } else if (name == "transitions") { + Array trans; + trans.resize(transitions.size() * 3); + + for (int i = 0; i < transitions.size(); i++) { + trans[i * 3 + 0] = transitions[i].from; + trans[i * 3 + 1] = transitions[i].to; + trans[i * 3 + 2] = transitions[i].transition; + } + + r_ret = trans; + return true; + } else if (name == "start_node") { + r_ret = get_start_node(); + return true; + } else if (name == "end_node") { + r_ret = get_end_node(); + return true; + } else if (name == "graph_offset") { + r_ret = get_graph_offset(); + return true; + } + + return false; +} +void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> names; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.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(); + p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); +} + +void AnimationNodeStateMachine::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeStateMachine::add_node); + ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node); + ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node); + ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node); + ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node); + ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name); + + ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition); + ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from); + ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to); + ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count); + ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index); + ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition); + + ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node); + ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node); + + ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node); + ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); + + ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachine::travel); + ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachine::start); + ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachine::stop); + ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachine::is_playing); + ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachine::get_current_node); + ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachine::get_travel_path); +} + +AnimationNodeStateMachine::AnimationNodeStateMachine() { + + play_start = false; + + playing = false; + len_current = 0; + + fading_time = 0; +} diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h new file mode 100644 index 0000000000..e7357e09ea --- /dev/null +++ b/scene/animation/animation_node_state_machine.h @@ -0,0 +1,142 @@ +#ifndef ANIMATION_NODE_STATE_MACHINE_H +#define ANIMATION_NODE_STATE_MACHINE_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeStateMachineTransition : public Resource { + GDCLASS(AnimationNodeStateMachineTransition, Resource) +public: + enum SwitchMode { + SWITCH_MODE_IMMEDIATE, + SWITCH_MODE_SYNC, + SWITCH_MODE_AT_END, + }; + +private: + SwitchMode switch_mode; + bool auto_advance; + float xfade; + bool disabled; + int priority; + +protected: + static void _bind_methods(); + +public: + void set_switch_mode(SwitchMode p_mode); + SwitchMode get_switch_mode() const; + + void set_auto_advance(bool p_enable); + bool has_auto_advance() const; + + void set_xfade_time(float p_xfade); + float get_xfade_time() const; + + void set_disabled(bool p_disabled); + bool is_disabled() const; + + void set_priority(int p_priority); + int get_priority() const; + + AnimationNodeStateMachineTransition(); +}; + +VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode) + +class AnimationNodeStateMachine : public AnimationRootNode { + + GDCLASS(AnimationNodeStateMachine, AnimationRootNode); + +private: + Map<StringName, Ref<AnimationRootNode> > states; + + struct Transition { + + StringName from; + StringName to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + + struct AStarCost { + float distance; + StringName prev; + }; + + Vector<Transition> transitions; + + float len_total; + + float len_current; + float pos_current; + int loops_current; + + bool play_start; + StringName start_node; + StringName end_node; + + Vector2 graph_offset; + + StringName current; + + StringName fading_from; + float fading_time; + float fading_pos; + + Vector<StringName> path; + bool playing; + +protected: + void _notification(int p_what); + 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: + 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 get_node_list(List<StringName> *r_nodes) const; + + bool has_transition(const StringName &p_from, const StringName &p_to) const; + int find_transition(const StringName &p_from, const StringName &p_to) const; + void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition); + Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const; + StringName get_transition_from(int p_transition) const; + StringName get_transition_to(int p_transition) const; + int get_transition_count() const; + void remove_transition_by_index(int p_transition); + void remove_transition(const StringName &p_from, const StringName &p_to); + + void set_start_node(const StringName &p_node); + String get_start_node() const; + + void set_end_node(const StringName &p_node); + String get_end_node() const; + + void set_graph_offset(const Vector2 &p_offset); + Vector2 get_graph_offset() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + bool travel(const StringName &p_state); + void start(const StringName &p_state); + void stop(); + bool is_playing() const; + StringName get_current_node() const; + StringName get_blend_from_node() const; + Vector<StringName> get_travel_path() const; + float get_current_play_pos() const; + float get_current_length() const; + + virtual void set_tree(AnimationTree *p_player); + + AnimationNodeStateMachine(); +}; + +#endif // ANIMATION_NODE_STATE_MACHINE_H diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index a0e0137863..eac2c8d0c1 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,51 @@ 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); + } + + int key_count = a->track_get_key_count(i); + if (key_count == 0) + continue; //eeh not worth it + + float first_key_time = a->track_get_key_time(i, 0); + float transition = 1.0; + int first_key = 0; + + if (first_key_time == 0.0) { + //ignore, use for transition + if (key_count == 1) + continue; //with one key we cant do anything + transition = a->track_get_key_transition(i, 0); + first_key_time = a->track_get_key_time(i, 1); + first_key = 1; + } + + if (p_time < first_key_time) { + float c = Math::ease(p_time / first_key_time, transition); + Variant first_value = a->track_get_key_value(i, first_key); + 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 +476,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 +531,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 +562,195 @@ 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) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && p_time < nc->audio_start) { + stop = true; + } else if (nc->audio_len > 0) { + float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start; + + if (len > nc->audio_len) { + stop = true; + } + } + + if (stop) { + //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 +799,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 +901,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 +919,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 +1129,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 +1194,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 +1272,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 +1311,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 +1354,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 +1382,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 +1393,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 +1650,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 +1661,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 +1671,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.cpp b/scene/animation/animation_tree.cpp new file mode 100644 index 0000000000..83ec9f819b --- /dev/null +++ b/scene/animation/animation_tree.cpp @@ -0,0 +1,1338 @@ +#include "animation_tree.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_tree(), 0); //should not happen, but used to catch bugs + + Ref<AnimationNodeBlendTree> tree = get_parent(); + + if (!tree.is_valid() && get_tree()->get_tree_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_tree()) + return 0; + + if (get_tree()->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 { + + if (get_script_instance()) { + return get_script_instance()->call("get_caption"); + } + + 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(Object *p_parent) { + set_parent(Object::cast_to<AnimationNode>(p_parent)); +} + +void AnimationNode::set_parent(AnimationNode *p_parent) { + parent = p_parent; //do not use ref because parent contains children + if (get_script_instance()) { + get_script_instance()->call("_parent_set", p_parent); + } +} + +Ref<AnimationNode> AnimationNode::get_parent() const { + if (parent) { + return Ref<AnimationNode>(parent); + } + + return Ref<AnimationNode>(); +} + +AnimationTree *AnimationNode::get_tree() 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_tree(AnimationTree *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)); + + ClassDB::bind_method(D_METHOD("set_parent", "parent"), &AnimationNode::_set_parent); + ClassDB::bind_method(D_METHOD("get_parent"), &AnimationNode::get_parent); + ClassDB::bind_method(D_METHOD("get_tree"), &AnimationNode::get_tree); + + 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"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "get_caption")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "has_filter")); + BIND_VMETHOD(MethodInfo("_parent_set", PropertyInfo(Variant::OBJECT, "parent"))); + + 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 AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) { + + if (root.is_valid()) { + root->set_tree(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_tree(this); + } + + update_configuration_warning(); +} + +Ref<AnimationNode> AnimationTree::get_tree_root() const { + return root; +} + +void AnimationTree::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 AnimationTree::is_active() const { + + return active; +} + +void AnimationTree::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); + } +} + +AnimationTree::AnimationProcessMode AnimationTree::get_process_mode() const { + return process_mode; +} + +void AnimationTree::_node_removed(Node *p_node) { + cache_valid = false; +} + +bool AnimationTree::_update_caches(AnimationPlayer *player) { + + setup_pass++; + + if (!player->has_node(player->get_root())) { + ERR_PRINT("AnimationTree: 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("AnimationTree: '" + 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("AnimationTree: '" + 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 AnimationTree::_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 AnimationTree::_process_graph(float p_delta) { + + //check all tracks, see if they need modification + root_motion_transform = Transform(); + + if (!root.is_valid()) { + ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback."); + set_active(false); + cache_valid = false; + return; + } + + if (!has_node(animation_player)) { + ERR_PRINT("AnimationTree: 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("AnimationTree: 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 + } + + track->root_motion = root_motion_track == path; + + 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); + + if (t->process_pass != process_pass) { + + t->process_pass = process_pass; + t->loc = Vector3(); + t->rot = Quat(); + t->rot_blend_accum = 0; + t->scale = Vector3(); + } + + if (track->root_motion) { + + float prev_time = time - delta; + if (prev_time < 0) { + if (!a->has_loop()) { + prev_time = 0; + } else { + prev_time = a->get_length() + prev_time; + } + } + + Vector3 loc[2]; + Quat rot[2]; + Vector3 scale[2]; + + if (prev_time > time) { + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + } + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + + } else { + 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; + + t->loc = t->loc.linear_interpolate(loc, blend); + if (t->rot_blend_accum==0) { + t->rot = rot; + t->rot_blend_accum = blend; + } else { + float rot_total = t->rot_blend_accum + blend; + t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized(); + t->rot_blend_accum = rot_total; + } + 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) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && time < t->start) { + stop = true; + } else if (t->len > 0) { + float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + + if (len > t->len) { + stop=true; + } + } + + if (stop) { + //time to stop + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } + } + } + + float db = Math::linear2db(MAX(blend,0.00001)); + if (t->object->has_method("set_unit_db")) { + t->object->call("set_unit_db", db); + } else { + t->object->call("set_volume_db", db); + } + } 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 and root motion + + xform.basis.set_quat_scale(t->rot, t->scale); + + if (t->root_motion) { + + root_motion_transform = xform; + + if (t->skeleton && t->bone_idx >= 0) { + root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse(); + } + } else if (t->skeleton && t->bone_idx >= 0) { + + t->skeleton->set_bone_pose(t->bone_idx, xform); + + } 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 AnimationTree::_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 AnimationTree::set_animation_player(const NodePath &p_player) { + animation_player = p_player; + update_configuration_warning(); +} + +NodePath AnimationTree::get_animation_player() const { + return animation_player; +} + +bool AnimationTree::is_state_invalid() const { + + return !state.valid; +} +String AnimationTree::get_invalid_state_reason() const { + + return state.invalid_reasons; +} + +uint64_t AnimationTree::get_last_process_pass() const { + return process_pass; +} + +String AnimationTree::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 AnimationTree::set_root_motion_track(const NodePath &p_track) { + root_motion_track = p_track; +} + +NodePath AnimationTree::get_root_motion_track() const { + return root_motion_track; +} + +Transform AnimationTree::get_root_motion_transform() const { + return root_motion_transform; +} + +void AnimationTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active); + + ClassDB::bind_method(D_METHOD("set_tree_root", "root"), &AnimationTree::set_tree_root); + ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_tree_root); + + ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationTree::set_process_mode); + ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationTree::get_process_mode); + + ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); + + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); + ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); + + ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform); + + + + ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_tree_root", "get_tree_root"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player",PROPERTY_HINT_NODE_PATH_VALID_TYPES,"AnimationPlayer"), "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"); + ADD_GROUP("Root Motion", "root_motion_"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); +} + +AnimationTree::AnimationTree() { + + process_mode = ANIMATION_PROCESS_IDLE; + active = false; + cache_valid = false; + setup_pass = 1; + started = true; +} + +AnimationTree::~AnimationTree() { + if (root.is_valid()) { + root->player = NULL; + } +} diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h new file mode 100644 index 0000000000..540c36437a --- /dev/null +++ b/scene/animation/animation_tree.h @@ -0,0 +1,281 @@ +#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 AnimationTree; + +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 AnimationTree; + + 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; + AnimationTree *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; + + void _set_parent(Object *p_parent); + +public: + void set_parent(AnimationNode *p_parent); + Ref<AnimationNode> get_parent() const; + virtual void set_tree(AnimationTree *p_player); + AnimationTree *get_tree() 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 AnimationTree : public Node { + GDCLASS(AnimationTree, Node) +public: + enum AnimationProcessMode { + ANIMATION_PROCESS_PHYSICS, + ANIMATION_PROCESS_IDLE, + }; + +private: + struct TrackCache { + + bool root_motion; + uint64_t setup_pass; + uint64_t process_pass; + Animation::TrackType type; + Object *object; + ObjectID object_id; + + TrackCache() { + root_motion = false; + 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; + float rot_blend_accum; + 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; + + NodePath root_motion_track; + Transform root_motion_transform; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_tree_root(const Ref<AnimationNode> &p_root); + Ref<AnimationNode> get_tree_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; + + void set_root_motion_track(const NodePath &p_track); + NodePath get_root_motion_track() const; + + Transform get_root_motion_transform() const; + + uint64_t get_last_process_pass() const; + AnimationTree(); + ~AnimationTree(); +}; + +VARIANT_ENUM_CAST(AnimationTree::AnimationProcessMode) + +#endif // ANIMATION_GRAPH_PLAYER_H diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp index 143684bdf9..026215508b 100644 --- a/scene/animation/animation_tree_player.cpp +++ b/scene/animation/animation_tree_player.cpp @@ -1814,7 +1814,7 @@ void AnimationTreePlayer::_bind_methods() { ADD_GROUP("Playback", "playback_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_animation_process_mode", "get_animation_process_mode"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player"), "set_master_player", "get_master_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_master_player", "get_master_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "base_path"), "set_base_path", "get_base_path"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp new file mode 100644 index 0000000000..a28c63064a --- /dev/null +++ b/scene/animation/root_motion_view.cpp @@ -0,0 +1,178 @@ +#include "root_motion_view.h" +#include "scene/animation/animation_tree.h" +#include "scene/resources/material.h" +void RootMotionView::set_animation_path(const NodePath &p_path) { + path = p_path; + first = true; +} + +NodePath RootMotionView::get_animation_path() const { + return path; +} + +void RootMotionView::set_color(const Color &p_color) { + color = p_color; + first = true; +} + +Color RootMotionView::get_color() const { + return color; +} + +void RootMotionView::set_cell_size(float p_size) { + cell_size = p_size; + first = true; +} + +float RootMotionView::get_cell_size() const { + return cell_size; +} + +void RootMotionView::set_radius(float p_radius) { + radius = p_radius; + first = true; +} + +float RootMotionView::get_radius() const { + return radius; +} + +void RootMotionView::set_zero_y(bool p_zero_y) { + zero_y = p_zero_y; +} + +bool RootMotionView::get_zero_y() const { + return zero_y; +} + +void RootMotionView::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + VS::get_singleton()->immediate_set_material(immediate, SpatialMaterial::get_material_rid_for_2d(false, true, false, false, false)); + first = true; + } + + if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + Transform transform; + + if (has_node(path)) { + + Node *node = get_node(path); + + AnimationTree *tree = Object::cast_to<AnimationTree>(node); + if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) { + if (is_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_PHYSICS) { + set_process_internal(false); + set_physics_process_internal(true); + } + + if (is_physics_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_IDLE) { + set_process_internal(true); + set_physics_process_internal(false); + } + + transform = tree->get_root_motion_transform(); + } + } + + if (!first && transform == Transform()) { + return; + } + + first = false; + + transform.orthonormalize(); //dont want scale, too imprecise + transform.affine_invert(); + + accumulated = transform * accumulated; + accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); + if (zero_y) { + accumulated.origin.y = 0; + } + accumulated.origin.z = Math::fposmod(accumulated.origin.z, cell_size); + + VS::get_singleton()->immediate_clear(immediate); + + int cells_in_radius = int((radius / cell_size) + 1.0); + + VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_LINES); + for (int i = -cells_in_radius; i < cells_in_radius; i++) { + for (int j = -cells_in_radius; j < cells_in_radius; j++) { + + Vector3 from(i * cell_size, 0, j * cell_size); + Vector3 from_i((i + 1) * cell_size, 0, j * cell_size); + Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size); + from = accumulated.xform(from); + from_i = accumulated.xform(from_i); + from_j = accumulated.xform(from_j); + + Color c = color, c_i = color, c_j = color; + c.a *= MAX(0, 1.0 - from.length() / radius); + c_i.a *= MAX(0, 1.0 - from_i.length() / radius); + c_j.a *= MAX(0, 1.0 - from_j.length() / radius); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_i); + VS::get_singleton()->immediate_vertex(immediate, from_i); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_j); + VS::get_singleton()->immediate_vertex(immediate, from_j); + } + } + + VS::get_singleton()->immediate_end(immediate); + } +} + +AABB RootMotionView::get_aabb() const { + + return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2)); +} +PoolVector<Face3> RootMotionView::get_faces(uint32_t p_usage_flags) const { + return PoolVector<Face3>(); +} + +void RootMotionView::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path); + ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path); + + ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color); + ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color); + + ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RootMotionView::set_cell_size); + ClassDB::bind_method(D_METHOD("get_cell_size"), &RootMotionView::get_cell_size); + + ClassDB::bind_method(D_METHOD("set_radius", "size"), &RootMotionView::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &RootMotionView::get_radius); + + ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y); + ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y"); +} + +RootMotionView::RootMotionView() { + zero_y = true; + radius = 10; + cell_size = 1; + set_process_internal(true); + immediate = VisualServer::get_singleton()->immediate_create(); + set_base(immediate); + color = Color(0.5, 0.5, 1.0); +} + +RootMotionView::~RootMotionView() { + set_base(RID()); + VisualServer::get_singleton()->free(immediate); +} diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h new file mode 100644 index 0000000000..611183d364 --- /dev/null +++ b/scene/animation/root_motion_view.h @@ -0,0 +1,47 @@ +#ifndef ROOT_MOTION_VIEW_H +#define ROOT_MOTION_VIEW_H + +#include "scene/3d/visual_instance.h" + +class RootMotionView : public VisualInstance { + GDCLASS(RootMotionView, VisualInstance) +public: + RID immediate; + NodePath path; + float cell_size; + float radius; + bool use_in_game; + Color color; + bool first; + bool zero_y; + + Transform accumulated; + +private: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_animation_path(const NodePath &p_path); + NodePath get_animation_path() const; + + void set_color(const Color &p_path); + Color get_color() const; + + void set_cell_size(float p_size); + float get_cell_size() const; + + void set_radius(float p_radius); + float get_radius() const; + + void set_zero_y(bool p_zero_y); + bool get_zero_y() const; + + virtual AABB get_aabb() const; + virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + + RootMotionView(); + ~RootMotionView(); +}; + +#endif // ROOT_MOTION_VIEW_H diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 4eefcc9ced..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); 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/audio/audio_player.cpp b/scene/audio/audio_player.cpp index 408c00334a..863b278b62 100644 --- a/scene/audio/audio_player.cpp +++ b/scene/audio/audio_player.cpp @@ -41,10 +41,10 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { int buffer_size = mix_buffer.size(); if (p_fadeout) { - buffer_size = MIN(buffer_size, 16); //short fadeout ramp + // Short fadeout ramp + buffer_size = MIN(buffer_size, 128); } - //mix stream_playback->mix(buffer, pitch_scale, buffer_size); //multiply volume interpolating to avoid clicks if this changes @@ -56,6 +56,7 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { buffer[i] *= vol; vol += vol_inc; } + //set volume for next mix mix_volume_db = target_volume; @@ -90,11 +91,14 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { void AudioStreamPlayer::_mix_audio() { - if (!stream_playback.is_valid()) { + if (!stream_playback.is_valid() || !active) return; - } - if (!active) { + if (stream_paused) { + if (stream_paused_fade) { + _mix_internal(true); + stream_paused_fade = false; + } return; } @@ -135,6 +139,17 @@ void AudioStreamPlayer::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { @@ -159,7 +174,6 @@ void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { if (p_stream.is_valid() && stream_playback.is_null()) { stream.unref(); - ERR_FAIL_COND(stream_playback.is_null()); } } @@ -275,6 +289,19 @@ bool AudioStreamPlayer::_is_active() const { return active; } +void AudioStreamPlayer::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade = p_pause ? true : false; + } +} + +bool AudioStreamPlayer::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer::_validate_property(PropertyInfo &property) const { if (property.name == "bus") { @@ -328,11 +355,15 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer::_bus_layout_changed); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "volume_db", PROPERTY_HINT_RANGE, "-80,24"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); @@ -351,6 +382,8 @@ AudioStreamPlayer::AudioStreamPlayer() { autoplay = false; setseek = -1; active = false; + stream_paused = false; + stream_paused_fade = false; mix_target = MIX_TARGET_STEREO; AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); diff --git a/scene/audio/audio_player.h b/scene/audio/audio_player.h index 21189aea6d..591c00ed18 100644 --- a/scene/audio/audio_player.h +++ b/scene/audio/audio_player.h @@ -57,6 +57,8 @@ private: float pitch_scale; float volume_db; bool autoplay; + bool stream_paused; + bool stream_paused_fade; StringName bus; MixTarget mix_target; @@ -100,6 +102,9 @@ public: void set_mix_target(MixTarget p_target); MixTarget get_mix_target() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer(); ~AudioStreamPlayer(); }; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 34891832e2..f8c188d33d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -242,6 +242,14 @@ bool ColorPicker::is_raw_mode() const { return raw_mode_enabled; } +void ColorPicker::set_deferred_mode(bool p_enabled) { + deferred_mode_enabled = p_enabled; +} + +bool ColorPicker::is_deferred_mode() const { + return deferred_mode_enabled; +} + void ColorPicker::_update_text_value() { bool visible = true; if (text_is_constructor) { @@ -328,7 +336,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { emit_signal("color_changed", color); + changing_color = false; } else { changing_color = false; } @@ -347,7 +359,8 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -368,7 +381,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + else if (!bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) + emit_signal("color_changed", color); } Ref<InputEventMouseMotion> mev = p_event; @@ -383,7 +399,8 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -500,6 +517,8 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); ClassDB::bind_method(D_METHOD("set_raw_mode", "mode"), &ColorPicker::set_raw_mode); ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); + ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); + ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); @@ -522,6 +541,7 @@ void ColorPicker::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); } @@ -533,6 +553,7 @@ ColorPicker::ColorPicker() : edit_alpha = true; text_is_constructor = false; raw_mode_enabled = false; + deferred_mode_enabled = false; changing_color = false; screen = NULL; diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 6b63e5fe60..c8d8e1aa8a 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -67,6 +67,7 @@ private: Color color; bool raw_mode_enabled; + bool deferred_mode_enabled; bool updating; bool changing_color; float h, s, v; @@ -107,6 +108,9 @@ public: void set_raw_mode(bool p_enabled); bool is_raw_mode() const; + void set_deferred_mode(bool p_enabled); + bool is_deferred_mode() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d07b5a9f65..068af42260 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -160,8 +160,16 @@ void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); minsize.x = MAX(minsize.x, data.custom_minimum_size.x); minsize.y = MAX(minsize.y, data.custom_minimum_size.y); + + bool size_changed = false; + if (data.minimum_size_cache != minsize) + size_changed = true; + data.minimum_size_cache = minsize; data.minimum_size_valid = true; + + if (size_changed) + minimum_size_changed(); } Size2 Control::get_combined_minimum_size() const { @@ -452,10 +460,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: { @@ -1268,35 +1274,34 @@ bool Control::has_constant(const StringName &p_name, const StringName &p_type) c return Theme::get_default()->has_constant(p_name, type); } -Size2 Control::get_parent_area_size() const { - - ERR_FAIL_COND_V(!is_inside_tree(), Size2()); - - Size2 parent_size; +Rect2 Control::get_parent_anchorable_rect() const { + if (!is_inside_tree()) + return Rect2(); + Rect2 parent_rect; if (data.parent_canvas_item) { - - parent_size = data.parent_canvas_item->_edit_get_rect().size; + parent_rect = data.parent_canvas_item->get_anchorable_rect(); } else { - - parent_size = get_viewport()->get_visible_rect().size; + parent_rect = get_viewport()->get_visible_rect(); } - return parent_size; + return parent_rect; } -void Control::_size_changed() { +Size2 Control::get_parent_area_size() const { - if (!is_inside_tree()) - return; + return get_parent_anchorable_rect().size; +} + +void Control::_size_changed() { - Size2 parent_size = get_parent_area_size(); + Rect2 parent_rect = get_parent_anchorable_rect(); float margin_pos[4]; for (int i = 0; i < 4; i++) { - float area = parent_size[i & 1]; + float area = parent_rect.size[i & 1]; margin_pos[i] = data.margin[i] + (data.anchor[i] * area); } @@ -1326,9 +1331,9 @@ void Control::_size_changed() { } // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() - if (Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { - new_size_cache = new_size_cache.floor(); - new_pos_cache = new_pos_cache.floor(); + if (is_inside_tree() && Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { + new_size_cache = new_size_cache.round(); + new_pos_cache = new_pos_cache.round(); } bool pos_changed = new_pos_cache != data.pos_cache; bool size_changed = new_size_cache != data.size_cache; @@ -1336,57 +1341,25 @@ void Control::_size_changed() { data.pos_cache = new_pos_cache; data.size_cache = new_size_cache; - if (size_changed) { - notification(NOTIFICATION_RESIZED); - } - if (pos_changed || size_changed) { - item_rect_changed(size_changed); - _change_notify_margins(); - _notify_transform(); - } - - if (pos_changed && !size_changed) { - _update_canvas_item_transform(); //move because it won't be updated - } -} - -float Control::_get_parent_range(int p_idx) const { - - if (!is_inside_tree()) { - - return 0; - } - if (data.parent_canvas_item) { + if (is_inside_tree()) { + if (size_changed) { + notification(NOTIFICATION_RESIZED); + } + if (pos_changed || size_changed) { + item_rect_changed(size_changed); + _change_notify_margins(); + _notify_transform(); + } - return data.parent_canvas_item->_edit_get_rect().size[p_idx & 1]; - } else { - return get_viewport()->get_visible_rect().size[p_idx & 1]; + if (pos_changed && !size_changed) { + _update_canvas_item_transform(); //move because it won't be updated + } } - - return 0; -} - -float Control::_get_range(int p_idx) const { - - p_idx &= 1; - - float parent_range = _get_parent_range(p_idx); - float from = _a2s(data.margin[p_idx], data.anchor[p_idx], parent_range); - float to = _a2s(data.margin[p_idx + 2], data.anchor[p_idx + 2], parent_range); - - return to - from; -} - -float Control::_s2a(float p_val, float p_anchor, float p_range) const { - return p_val - (p_anchor * p_range); -} - -float Control::_a2s(float p_val, float p_anchor, float p_range) const { - return Math::floor(p_val + (p_anchor * p_range)); } void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bool p_push_opposite_anchor) { - float parent_range = _get_parent_range((p_margin == MARGIN_LEFT || p_margin == MARGIN_RIGHT) ? 0 : 1); + Rect2 parent_rect = get_parent_anchorable_rect(); + float parent_range = (p_margin == MARGIN_LEFT || p_margin == MARGIN_RIGHT) ? parent_rect.size.x : parent_rect.size.y; float previous_margin_pos = data.margin[p_margin] + data.anchor[p_margin] * parent_range; float previous_opposite_margin_pos = data.margin[(p_margin + 2) % 4] + data.anchor[(p_margin + 2) % 4] * parent_range; @@ -1402,9 +1375,9 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo } if (!p_keep_margin) { - data.margin[p_margin] = _s2a(previous_margin_pos, data.anchor[p_margin], parent_range); + data.margin[p_margin] = previous_margin_pos - data.anchor[p_margin] * parent_range; if (p_push_opposite_anchor) { - data.margin[(p_margin + 2) % 4] = _s2a(previous_opposite_margin_pos, data.anchor[(p_margin + 2) % 4], parent_range); + data.margin[(p_margin + 2) % 4] = previous_opposite_margin_pos - data.anchor[(p_margin + 2) % 4] * parent_range; } } if (is_inside_tree()) { @@ -1412,7 +1385,7 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo } update(); - _change_notify(); + _change_notify("anchor"); } void Control::_set_anchor(Margin p_margin, float p_anchor) { @@ -1558,8 +1531,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz new_size.y = min_size.y; } - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); + Rect2 parent_rect = get_parent_anchorable_rect(); //Left switch (p_preset) { @@ -1571,21 +1543,21 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - data.margin[0] = pw * (0.0 - data.anchor[0]) + p_margin; + data.margin[0] = parent_rect.size.x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x; break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - data.margin[0] = pw * (0.5 - data.anchor[0]) - new_size.x / 2; + data.margin[0] = parent_rect.size.x * (0.5 - data.anchor[0]) - new_size.x / 2 + parent_rect.position.x; break; case PRESET_TOP_RIGHT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_RIGHT: case PRESET_RIGHT_WIDE: - data.margin[0] = pw * (1.0 - data.anchor[0]) - new_size.x - p_margin; + data.margin[0] = parent_rect.size.x * (1.0 - data.anchor[0]) - new_size.x - p_margin + parent_rect.position.x; break; } @@ -1599,21 +1571,21 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - data.margin[1] = ph * (0.0 - data.anchor[1]) + p_margin; + data.margin[1] = parent_rect.size.y * (0.0 - data.anchor[1]) + p_margin + parent_rect.position.y; break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - data.margin[1] = ph * (0.5 - data.anchor[1]) - new_size.y / 2; + data.margin[1] = parent_rect.size.y * (0.5 - data.anchor[1]) - new_size.y / 2 + parent_rect.position.y; break; case PRESET_BOTTOM_LEFT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_BOTTOM: case PRESET_BOTTOM_WIDE: - data.margin[1] = ph * (1.0 - data.anchor[1]) - new_size.y - p_margin; + data.margin[1] = parent_rect.size.y * (1.0 - data.anchor[1]) - new_size.y - p_margin + parent_rect.position.y; break; } @@ -1623,14 +1595,14 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_LEFT: case PRESET_CENTER_LEFT: case PRESET_LEFT_WIDE: - data.margin[2] = pw * (0.0 - data.anchor[2]) + new_size.x + p_margin; + data.margin[2] = parent_rect.size.x * (0.0 - data.anchor[2]) + new_size.x + p_margin + parent_rect.position.x; break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - data.margin[2] = pw * (0.5 - data.anchor[2]) + new_size.x / 2; + data.margin[2] = parent_rect.size.x * (0.5 - data.anchor[2]) + new_size.x / 2 + parent_rect.position.x; break; case PRESET_TOP_RIGHT: @@ -1641,7 +1613,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - data.margin[2] = pw * (1.0 - data.anchor[2]) - p_margin; + data.margin[2] = parent_rect.size.x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x; break; } @@ -1651,14 +1623,14 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_TOP_RIGHT: case PRESET_CENTER_TOP: case PRESET_TOP_WIDE: - data.margin[3] = ph * (0.0 - data.anchor[3]) + new_size.y + p_margin; + data.margin[3] = parent_rect.size.y * (0.0 - data.anchor[3]) + new_size.y + p_margin + parent_rect.position.y; break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - data.margin[3] = ph * (0.5 - data.anchor[3]) + new_size.y / 2; + data.margin[3] = parent_rect.size.y * (0.5 - data.anchor[3]) + new_size.y / 2 + parent_rect.position.y; break; case PRESET_BOTTOM_LEFT: @@ -1669,7 +1641,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - data.margin[3] = ph * (1.0 - data.anchor[3]) - p_margin; + data.margin[3] = parent_rect.size.y * (1.0 - data.anchor[3]) - p_margin + parent_rect.position.y; break; } @@ -1748,31 +1720,29 @@ void Control::set_global_position(const Point2 &p_point) { set_position(inv.xform(p_point)); } -void Control::set_position(const Size2 &p_point) { - - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); +Rect2 Control::_compute_child_rect(const float p_anchors[4], const float p_margins[4]) const { - float x = _a2s(data.margin[0], data.anchor[0], pw); - float y = _a2s(data.margin[1], data.anchor[1], ph); - float x2 = _a2s(data.margin[2], data.anchor[2], pw); - float y2 = _a2s(data.margin[3], data.anchor[3], ph); + Rect2 anchorable = get_parent_anchorable_rect(); + Rect2 result = anchorable; + for (int i = 0; i < 4; i++) { + result.grow_margin((Margin)i, p_anchors[i] * anchorable.get_size()[i % 2] + p_margins[i]); + } - Size2 ret = Size2(x2 - x, y2 - y); - Size2 min = get_combined_minimum_size(); + return result; +} - Size2 size = Size2(MAX(min.width, ret.width), MAX(min.height, ret.height)); - float w = size.x; - float h = size.y; +void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]) { - x = p_point.x; - y = p_point.y; + Size2 parent_rect_size = get_parent_anchorable_rect().size; + r_margins[0] = Math::floor(p_rect.position.x - (p_anchors[0] * parent_rect_size.x)); + r_margins[1] = Math::floor(p_rect.position.y - (p_anchors[1] * parent_rect_size.y)); + r_margins[2] = Math::floor(p_rect.position.x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x)); + r_margins[3] = Math::floor(p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y)); +} - data.margin[0] = _s2a(x, data.anchor[0], pw); - data.margin[1] = _s2a(y, data.anchor[1], ph); - data.margin[2] = _s2a(x + w, data.anchor[2], pw); - data.margin[3] = _s2a(y + h, data.anchor[3], ph); +void Control::set_position(const Size2 &p_point) { + _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); _size_changed(); } @@ -1785,18 +1755,7 @@ void Control::set_size(const Size2 &p_size) { if (new_size.y < min.y) new_size.y = min.y; - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); - - float x = _a2s(data.margin[0], data.anchor[0], pw); - float y = _a2s(data.margin[1], data.anchor[1], ph); - - float w = new_size.width; - float h = new_size.height; - - data.margin[2] = _s2a(x + w, data.anchor[2], pw); - data.margin[3] = _s2a(y + h, data.anchor[3], ph); - + _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); _size_changed(); } @@ -1827,6 +1786,11 @@ Rect2 Control::get_rect() const { return Rect2(get_position(), get_size()); } +Rect2 Control::get_anchorable_rect() const { + + return Rect2(Point2(), get_size()); +} + void Control::add_icon_override(const StringName &p_name, const Ref<Texture> &p_icon) { ERR_FAIL_COND(p_icon.is_null()); @@ -2326,12 +2290,11 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { Point2 points[4]; Transform2D xform = get_global_transform(); - Rect2 rect = _edit_get_rect(); - points[0] = xform.xform(rect.position); - points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); - points[2] = xform.xform(rect.position + rect.size); - points[3] = xform.xform(rect.position + Point2(0, rect.size.y)); + points[0] = xform.xform(Point2()); + points[1] = xform.xform(Point2(get_size().x, 0)); + points[2] = xform.xform(get_size()); + points[3] = xform.xform(Point2(0, get_size().y)); const Vector2 dir[4] = { Vector2(-1, 0), @@ -2385,12 +2348,11 @@ void Control::_window_find_focus_neighbour(const Vector2 &p_dir, Node *p_at, con Point2 points[4]; Transform2D xform = c->get_global_transform(); - Rect2 rect = c->_edit_get_rect(); - points[0] = xform.xform(rect.position); - points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); - points[2] = xform.xform(rect.position + rect.size); - points[3] = xform.xform(rect.position + Point2(0, rect.size.y)); + points[0] = xform.xform(Point2()); + points[1] = xform.xform(Point2(get_size().x, 0)); + points[2] = xform.xform(get_size()); + points[3] = xform.xform(Point2(0, get_size().y)); float min = 1e7; @@ -2889,12 +2851,12 @@ void Control::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); ADD_GROUP("Focus", "focus_"); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next"), "set_focus_next", "get_focus_next"); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous"), "set_focus_previous", "get_focus_previous"); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next"); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Mouse", "mouse_"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 9124256624..fa5274d854 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -202,12 +202,12 @@ private: NodePath focus_next; NodePath focus_prev; - HashMap<StringName, Ref<Texture>, StringNameHasher> icon_override; - HashMap<StringName, Ref<Shader>, StringNameHasher> shader_override; - HashMap<StringName, Ref<StyleBox>, StringNameHasher> style_override; - HashMap<StringName, Ref<Font>, StringNameHasher> font_override; - HashMap<StringName, Color, StringNameHasher> color_override; - HashMap<StringName, int, StringNameHasher> constant_override; + HashMap<StringName, Ref<Texture> > icon_override; + HashMap<StringName, Ref<Shader> > shader_override; + HashMap<StringName, Ref<StyleBox> > style_override; + HashMap<StringName, Ref<Font> > font_override; + HashMap<StringName, Color> color_override; + HashMap<StringName, int> constant_override; Map<Ref<Font>, int> font_refcount; } data; @@ -220,10 +220,6 @@ private: void _set_anchor(Margin p_margin, float p_anchor); - float _get_parent_range(int p_idx) const; - float _get_range(int p_idx) const; - float _s2a(float p_val, float p_anchor, float p_range) const; - float _a2s(float p_val, float p_anchor, float p_range) const; void _propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool p_assign = true); void _theme_changed(); @@ -233,6 +229,9 @@ private: void _update_scroll(); void _resize(const Size2 &p_size); + Rect2 _compute_child_rect(const float p_anchors[4], const float p_margins[4]) const; + void _compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]); + void _size_changed(); String _get_tooltip() const; @@ -283,6 +282,7 @@ public: }; + /* EDITOR */ virtual Dictionary _edit_get_state() const; virtual void _edit_set_state(const Dictionary &p_state); @@ -358,6 +358,7 @@ public: Rect2 get_rect() const; Rect2 get_global_rect() const; Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the visual server + Rect2 get_anchorable_rect() const; void set_rotation(float p_radians); void set_rotation_degrees(float p_degrees); @@ -465,6 +466,7 @@ public: bool is_toplevel_control() const; Size2 get_parent_area_size() const; + Rect2 get_parent_anchorable_rect() const; void grab_click_focus(); 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/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/item_list.cpp b/scene/gui/item_list.cpp index 57b9a9a11b..72ed0e9b81 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -942,6 +942,7 @@ void ItemList::_notification(int p_what) { } } + minimum_size_changed(); shape_changed = false; } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index f1f1a66b47..b71a4dd133 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -565,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; @@ -610,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) { @@ -645,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; @@ -707,7 +711,8 @@ void LineEdit::_notification(int p_what) { if (selected) VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(char_width, caret_height)), selection_color); - drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color); + int yofs = y_ofs + (caret_height - font->get_height()) / 2; + drawer.draw_char(ci, Point2(x_ofs, yofs + font_ascent), cchar, next, selected ? font_color_selected : font_color); if (char_ofs == cursor_pos && draw_caret) { if (ime_text.length() == 0) { @@ -756,7 +761,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; @@ -766,6 +772,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); @@ -778,6 +785,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(); @@ -1094,11 +1102,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; @@ -1165,7 +1174,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 93865cebde..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(); @@ -913,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()); @@ -937,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(); @@ -963,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)) { @@ -1248,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); @@ -1264,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/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index f34559fc8d..ce2e3538da 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -324,7 +324,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & color = _find_color(text, p_base_color); font_color_shadow = _find_color(text, p_font_color_shadow); underline = _find_underline(text); - if (_find_meta(text, &meta)) { + if (_find_meta(text, &meta) && underline_meta) { underline = true; } 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/slider.cpp b/scene/gui/slider.cpp index 46215c9277..b820e2eafd 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -65,11 +65,12 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { } else { grab.active = false; } - } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { - - set_value(get_value() + get_step()); - } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { - set_value(get_value() - get_step()); + } else if (scrollable) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { + set_value(get_value() + get_step()); + } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + set_value(get_value() - get_step()); + } } } @@ -247,6 +248,16 @@ bool Slider::is_editable() const { return editable; } +void Slider::set_scrollable(bool p_scrollable) { + + scrollable = p_scrollable; +} + +bool Slider::is_scrollable() const { + + return scrollable; +} + void Slider::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &Slider::_gui_input); @@ -258,8 +269,11 @@ void Slider::_bind_methods() { ClassDB::bind_method(D_METHOD("set_editable", "editable"), &Slider::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &Slider::is_editable); + ClassDB::bind_method(D_METHOD("set_scrollable", "scrollable"), &Slider::set_scrollable); + ClassDB::bind_method(D_METHOD("is_scrollable"), &Slider::is_scrollable); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrollable"), "set_scrollable", "is_scrollable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tick_count", PROPERTY_HINT_RANGE, "0,4096,1"), "set_ticks", "get_ticks"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ticks_on_borders"), "set_ticks_on_borders", "get_ticks_on_borders"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); @@ -272,5 +286,6 @@ Slider::Slider(Orientation p_orientation) { ticks = 0; custom_step = -1; editable = true; + scrollable = true; set_focus_mode(FOCUS_ALL); } diff --git a/scene/gui/slider.h b/scene/gui/slider.h index e77a0b7423..4d02348159 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -48,6 +48,7 @@ class Slider : public Range { Orientation orientation; float custom_step; bool editable; + bool scrollable; protected: void _gui_input(Ref<InputEvent> p_event); @@ -70,6 +71,9 @@ public: void set_editable(bool p_editable); bool is_editable() const; + void set_scrollable(bool p_scrollable); + bool is_scrollable() const; + Slider(Orientation p_orientation = VERTICAL); }; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 0363dd44c2..c30fa96327 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; @@ -367,7 +407,7 @@ void TabContainer::_child_renamed_callback() { void TabContainer::add_child_notify(Node *p_child) { - Control::add_child_notify(p_child); + Container::add_child_notify(p_child); Control *c = Object::cast_to<Control>(p_child); if (!c) @@ -475,7 +515,7 @@ Control *TabContainer::get_current_tab_control() const { void TabContainer::remove_child_notify(Node *p_child) { - Control::remove_child_notify(p_child); + Container::remove_child_notify(p_child); call_deferred("_update_current_tab"); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 1afe5f7541..8a3c9d2bb2 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -31,11 +31,11 @@ #ifndef TAB_CONTAINER_H #define TAB_CONTAINER_H -#include "scene/gui/control.h" +#include "scene/gui/container.h" #include "scene/gui/popup.h" -class TabContainer : public Control { +class TabContainer : public Container { - GDCLASS(TabContainer, Control); + GDCLASS(TabContainer, Container); public: enum TabAlign { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 55a650ff12..218b5060a1 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -426,6 +426,9 @@ void TextEdit::_update_scrollbars() { void TextEdit::_click_selection_held() { + // Warning: is_mouse_button_pressed(BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. + // I'm unsure if there's an actual fix that doesn't have a ton of side effects. if (Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && selection.selecting_mode != Selection::MODE_NONE) { switch (selection.selecting_mode) { case Selection::MODE_POINTER: { @@ -447,7 +450,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); @@ -455,14 +458,14 @@ void TextEdit::_update_selection_mode_pointer() { select(selection.selecting_line, selection.selecting_column, row, col); cursor_set_line(row, false); - cursor_set_column(col, false); + cursor_set_column(col); update(); click_select_held->start(); } 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); @@ -496,26 +499,29 @@ void TextEdit::_update_selection_mode_word() { selection.selected_word_beg = beg; selection.selected_word_end = end; selection.selected_word_origin = beg; + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } else { if ((col <= selection.selected_word_origin && row == selection.selecting_line) || row < selection.selecting_line) { selection.selecting_column = selection.selected_word_end; select(row, beg, selection.selecting_line, selection.selected_word_end); + cursor_set_line(selection.from_line, false); cursor_set_column(selection.from_column); } else { selection.selecting_column = selection.selected_word_beg; select(selection.selecting_line, selection.selected_word_beg, row, end); + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } } - cursor_set_line(row, false); update(); + click_select_held->start(); } 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); @@ -531,7 +537,7 @@ void TextEdit::_update_selection_mode_line() { selection.selecting_column = 0; col = text[row].length(); } - cursor_set_column(0, false); + cursor_set_column(0); select(selection.selecting_line, selection.selecting_column, row, col); update(); @@ -960,12 +966,13 @@ void TextEdit::_notification(int p_what) { // draw line numbers if (cache.line_number_w) { + int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; String fc = String::num(line + 1); while (fc.length() < line_number_char_count) { fc = line_num_padding + fc; } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, cache.line_number_color); } } @@ -1088,12 +1095,13 @@ void TextEdit::_notification(int p_what) { } if (brace_matching_enabled) { + int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) || (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { if (brace_open_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); } if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || @@ -1101,7 +1109,7 @@ void TextEdit::_notification(int p_what) { if (brace_close_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); } } @@ -1161,9 +1169,10 @@ void TextEdit::_notification(int p_what) { } if (str[j] >= 32) { - int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); if (underlined) { - draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); + draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); } } else if (draw_tabs && str[j] == '\t') { int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; @@ -1388,6 +1397,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 +1409,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 +1424,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(); @@ -1525,8 +1537,11 @@ void TextEdit::backspace_at_cursor() { if (is_line_hidden(cursor.line)) set_line_as_hidden(prev_line, true); - if (is_line_set_as_breakpoint(cursor.line)) + if (is_line_set_as_breakpoint(cursor.line)) { + if (!text.is_breakpoint(prev_line)) + emit_signal("breakpoint_toggled", prev_line); set_line_as_breakpoint(prev_line, true); + } if (auto_brace_completion_enabled && cursor.column > 0 && @@ -1657,14 +1672,17 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co rows /= get_row_height(); rows += get_v_scroll_offset(); int first_vis_line = get_first_visible_line(); + int last_vis_line = get_last_visible_line(); int row = first_vis_line + Math::floor(rows); int wrap_index = 0; if (is_wrap_enabled() || is_hiding_enabled()) { - int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + 1, wrap_index) - 1; - row = first_vis_line + f_ofs; - row = CLAMP(row, 0, get_last_visible_line() + 1); + int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) + row = first_vis_line - f_ofs; + else + row = first_vis_line + f_ofs; } if (row < 0) @@ -3298,22 +3316,37 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_COND(p_char < 0); - /* STEP 1 add spaces if the char is greater than the end of the line */ + /* STEP 1 remove \r from source text and separate in substrings */ + + Vector<String> substrings = p_text.replace("\r", "").split("\n"); + + /* STEP 2 fire breakpoint_toggled signals */ + + // Is this just a new empty line? + bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; + + int i = p_line + !shift_first_line; + int lines = substrings.size() - 1; + for (; i < text.size(); i++) { + if (text.is_breakpoint(i)) { + if ((i - lines < p_line || !text.is_breakpoint(i - lines)) || (i - lines == p_line && !shift_first_line)) + emit_signal("breakpoint_toggled", i); + if (i + lines >= text.size() || !text.is_breakpoint(i + lines)) + emit_signal("breakpoint_toggled", i + lines); + } + } + + /* STEP 3 add spaces if the char is greater than the end of the line */ while (p_char > text[p_line].length()) { text.set(p_line, text[p_line] + String::chr(' ')); } - /* STEP 2 separate dest string in pre and post text */ + /* STEP 4 separate dest string in pre and post text */ String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - /* STEP 3 remove \r from source text and separate in substrings */ - - //buh bye \r and split - Vector<String> substrings = p_text.replace("\r", "").split("\n"); - for (int i = 0; i < substrings.size(); i++) { //insert the substrings @@ -3331,9 +3364,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } } - // if we are just making a new empty line, reset breakpoints and hidden status - if (p_char == 0 && p_text.replace("\r", "") == "\n") { - + if (shift_first_line) { text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line)); text.set_hidden(p_line + 1, text.is_hidden(p_line)); text.set_breakpoint(p_line, false); @@ -3389,11 +3420,20 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); - for (int i = p_from_line; i < p_to_line; i++) { + int lines = p_to_line - p_from_line; - text.remove(p_from_line + 1); + for (int i = p_from_line + 1; i < text.size(); i++) { + if (text.is_breakpoint(i)) { + if (i + lines >= text.size() || !text.is_breakpoint(i + lines)) + emit_signal("breakpoint_toggled", i); + if (i > p_to_line && (i - lines < 0 || !text.is_breakpoint(i - lines))) + emit_signal("breakpoint_toggled", i - lines); + } } + for (int i = p_from_line; i < p_to_line; i++) { + text.remove(p_from_line + 1); + } text.set(p_from_line, pre_text + post_text); text.set_line_wrap_amount(p_from_line, -1); @@ -4866,6 +4906,24 @@ void TextEdit::get_breakpoints(List<int> *p_breakpoints) const { } } +Array TextEdit::get_breakpoints_array() const { + + Array arr; + for (int i = 0; i < text.size(); i++) { + if (text.is_breakpoint(i)) + arr.append(i); + } + return arr; +} + +void TextEdit::remove_breakpoints() { + for (int i = 0; i < text.size(); i++) { + if (text.is_breakpoint(i)) + /* Should "breakpoint_toggled" be fired when breakpoints are removed this way? */ + text.set_breakpoint(i, false); + } +} + void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); @@ -5535,7 +5593,17 @@ void TextEdit::_confirm_completion() { cursor_set_column(cursor.column - completion_base.length(), false); insert_text_at_cursor(completion_current); - if (completion_current.ends_with("(") && auto_brace_completion_enabled) { + // When inserted into the middle of an existing string, don't add an unnecessary quote + String line = text[cursor.line]; + CharType next_char = line[cursor.column]; + CharType last_completion_char = completion_current[completion_current.length() - 1]; + + if ((last_completion_char == '"' || last_completion_char == '\'') && + last_completion_char == next_char) { + _base_remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); + } + + if (last_completion_char == '(' && auto_brace_completion_enabled) { insert_text_at_cursor(")"); cursor.column--; } @@ -5762,18 +5830,23 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { if (select_word(s, col, beg, end)) { bool inside_quotes = false; + char selected_quote = '\0'; int qbegin = 0, qend = 0; for (int i = 0; i < s.length(); i++) { - if (s[i] == '"') { - if (inside_quotes) { - qend = i; - inside_quotes = false; - if (col >= qbegin && col <= qend) { - return s.substr(qbegin, qend - qbegin); + if (s[i] == '"' || s[i] == '\'') { + if (i == 0 || s[i - 1] != '\\') { + if (inside_quotes && selected_quote == s[i]) { + qend = i; + inside_quotes = false; + selected_quote = '\0'; + if (col >= qbegin && col <= qend) { + return s.substr(qbegin, qend - qbegin); + } + } else if (!inside_quotes) { + qbegin = i + 1; + inside_quotes = true; + selected_quote = s[i]; } - } else { - qbegin = i + 1; - inside_quotes = true; } } } @@ -5854,12 +5927,12 @@ void TextEdit::set_line_length_guideline_column(int p_column) { update(); } -void TextEdit::set_draw_breakpoint_gutter(bool p_draw) { +void TextEdit::set_breakpoint_gutter_enabled(bool p_draw) { draw_breakpoint_gutter = p_draw; update(); } -bool TextEdit::is_drawing_breakpoint_gutter() const { +bool TextEdit::is_breakpoint_gutter_enabled() const { return draw_breakpoint_gutter; } @@ -6042,6 +6115,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers); ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled); + ClassDB::bind_method(D_METHOD("set_breakpoint_gutter_enabled", "enable"), &TextEdit::set_breakpoint_gutter_enabled); + ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled); ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); @@ -6080,11 +6155,15 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu); + ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array); + ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "syntax_highlighting"), "set_syntax_coloring", "is_syntax_coloring_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_line_numbers"), "set_show_line_numbers", "is_show_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "breakpoint_gutter"), "set_breakpoint_gutter_enabled", "is_breakpoint_gutter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 5c82d1ac20..586f4c8e93 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -473,6 +473,8 @@ public: void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; void get_breakpoints(List<int> *p_breakpoints) const; + Array get_breakpoints_array() const; + void remove_breakpoints(); void set_line_as_hidden(int p_line, bool p_hidden); bool is_line_hidden(int p_line) const; @@ -632,8 +634,8 @@ public: void set_show_line_length_guideline(bool p_show); void set_line_length_guideline_column(int p_column); - void set_draw_breakpoint_gutter(bool p_draw); - bool is_drawing_breakpoint_gutter() const; + void set_breakpoint_gutter_enabled(bool p_draw); + bool is_breakpoint_gutter_enabled() const; void set_breakpoint_gutter_width(int p_gutter_width); int get_breakpoint_gutter_width() const; diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index ae21775c55..4750e05633 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -30,8 +30,6 @@ #include "http_request.h" -#include "version.h" - void HTTPRequest::_redirect_request(const String &p_new_url) { } @@ -106,28 +104,10 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_h validate_ssl = p_ssl_validate_domain; - bool has_user_agent = false; - bool has_accept = false; headers = p_custom_headers; request_data = p_request_data; - for (int i = 0; i < headers.size(); i++) { - - if (headers[i].findn("user-agent:") == 0) - has_user_agent = true; - if (headers[i].findn("Accept:") == 0) - has_accept = true; - } - - if (!has_user_agent) { - headers.push_back("User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")"); - } - - if (!has_accept) { - headers.push_back("Accept: */*"); - } - requesting = true; if (use_threads) { diff --git a/scene/main/node.cpp b/scene/main/node.cpp index ffb8acc687..6d18cce21d 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1095,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)) { @@ -1891,7 +1895,7 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const // Skip nodes not really belonging to the instanced hierarchy; they'll be processed normally later // but remember non-instanced nodes that are hidden below instanced ones if (descendant->data.owner != this) { - if (descendant->get_parent() && descendant->get_parent() != this && descendant->get_parent()->data.owner == this) + if (descendant->get_parent() && descendant->get_parent() != this && descendant->get_parent()->data.owner == this && descendant->data.owner != descendant->get_parent()) hidden_roots.push_back(descendant); continue; } @@ -1930,8 +1934,9 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const if (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE) { Resource *res = Object::cast_to<Resource>(value); - if (res) // Duplicate only if it's a resource + if (res) { // Duplicate only if it's a resource current_node->set(name, res->duplicate()); + } } else { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 8d6e57b335..6438616cf2 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -498,7 +498,8 @@ bool SceneTree::idle(float p_time) { _notify_group_pause("idle_process_internal", Node::NOTIFICATION_INTERNAL_PROCESS); _notify_group_pause("idle_process", Node::NOTIFICATION_PROCESS); - Size2 win_size = Size2(OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height); + Size2 win_size = Size2(OS::get_singleton()->get_window_size().width, OS::get_singleton()->get_window_size().height); + if (win_size != last_screen_size) { last_screen_size = win_size; @@ -656,6 +657,11 @@ void SceneTree::_notification(int p_notification) { #endif } break; + case NOTIFICATION_CRASH: { + + get_root()->propagate_notification(p_notification); + } break; + default: break; }; @@ -1112,7 +1118,7 @@ void SceneTree::_update_root_rect() { } //actual screen video mode - Size2 video_mode = Size2(OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height); + Size2 video_mode = Size2(OS::get_singleton()->get_window_size().width, OS::get_singleton()->get_window_size().height); Size2 desired_res = stretch_min; Size2 viewport_size; @@ -2004,7 +2010,7 @@ SceneTree::SceneTree() { stretch_aspect = STRETCH_ASPECT_IGNORE; stretch_shrink = 1; - last_screen_size = Size2(OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height); + last_screen_size = Size2(OS::get_singleton()->get_window_size().width, OS::get_singleton()->get_window_size().height); _update_root_rect(); if (ScriptDebugger::get_singleton()) { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index ca9be9823a..01302d4214 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -144,7 +144,7 @@ void ViewportTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_viewport_path_in_scene", "path"), &ViewportTexture::set_viewport_path_in_scene); ClassDB::bind_method(D_METHOD("get_viewport_path_in_scene"), &ViewportTexture::get_viewport_path_in_scene); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Viewport"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); } ViewportTexture::ViewportTexture() { @@ -626,7 +626,7 @@ Rect2 Viewport::get_visible_rect() const { if (size == Size2()) { - r = Rect2(Point2(), Size2(OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height)); + r = Rect2(Point2(), Size2(OS::get_singleton()->get_window_size().width, OS::get_singleton()->get_window_size().height)); } else { r = Rect2(Point2(), size); @@ -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 @@ -1380,6 +1404,8 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu mb->get_button_index() == BUTTON_WHEEL_UP || mb->get_button_index() == BUTTON_WHEEL_LEFT || mb->get_button_index() == BUTTON_WHEEL_RIGHT)); + Ref<InputEventPanGesture> pn = p_input; + cant_stop_me_now = pn.is_valid() || cant_stop_me_now; bool ismouse = ev.is_valid() || Object::cast_to<InputEventMouseMotion>(*p_input) != NULL; @@ -1388,12 +1414,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) @@ -1864,7 +1892,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 +1914,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 2f3d4df329..f42c1e0982 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -51,6 +51,7 @@ #include "scene/2d/parallax_background.h" #include "scene/2d/parallax_layer.h" #include "scene/2d/particles_2d.h" + #include "scene/2d/path_2d.h" #include "scene/2d/physics_body_2d.h" #include "scene/2d/polygon_2d.h" @@ -63,8 +64,14 @@ #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_1d.h" +#include "scene/animation/animation_blend_space_2d.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_node_state_machine.h" #include "scene/animation/animation_player.h" +#include "scene/animation/animation_tree.h" #include "scene/animation/animation_tree_player.h" +#include "scene/animation/root_motion_view.h" #include "scene/animation/tween.h" #include "scene/audio/audio_player.h" #include "scene/gui/box_container.h" @@ -155,6 +162,7 @@ #include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" +#include "scene/3d/cpu_particles.h" #include "scene/3d/particles.h" #include "scene/3d/scenario_fx.h" #include "scene/3d/spatial.h" @@ -377,11 +385,34 @@ void register_scene_types() { ClassDB::register_class<BakedLightmapData>(); ClassDB::register_class<AnimationTreePlayer>(); ClassDB::register_class<Particles>(); + ClassDB::register_class<CPUParticles>(); ClassDB::register_class<Position3D>(); ClassDB::register_class<NavigationMeshInstance>(); ClassDB::register_class<NavigationMesh>(); ClassDB::register_class<Navigation>(); + ClassDB::register_class<RootMotionView>(); + ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor + + ClassDB::register_class<AnimationTree>(); + ClassDB::register_class<AnimationNode>(); + ClassDB::register_class<AnimationRootNode>(); + ClassDB::register_class<AnimationNodeBlendTree>(); + ClassDB::register_class<AnimationNodeBlendSpace1D>(); + ClassDB::register_class<AnimationNodeBlendSpace2D>(); + ClassDB::register_class<AnimationNodeStateMachine>(); + ClassDB::register_class<AnimationNodeStateMachineTransition>(); + ClassDB::register_class<AnimationNodeOutput>(); + ClassDB::register_class<AnimationNodeOneShot>(); + ClassDB::register_class<AnimationNodeAnimation>(); + ClassDB::register_class<AnimationNodeAdd2>(); + ClassDB::register_class<AnimationNodeAdd3>(); + 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>(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 7a1fffaa26..e57a2b68c8 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; @@ -123,8 +134,8 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { int um = d["update"]; if (um < 0) um = 0; - else if (um > 2) - um = 2; + else if (um > 3) + um = 3; vt->update_mode = UpdateMode(um); } @@ -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,361 @@ 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) { + + 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 +2590,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 +2639,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 +2676,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 +2719,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 +2730,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/curve.cpp b/scene/resources/curve.cpp index 7f902fc982..f2fd919f20 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -479,6 +479,16 @@ real_t Curve::interpolate_baked(real_t offset) { } } +void Curve::ensure_default_setup(float p_min, float p_max) { + if (_points.size() == 0 && _min_value == 0 && _max_value == 1) { + + add_point(Vector2(0, 1)); + add_point(Vector2(1, 1)); + set_min_value(p_min); + set_max_value(p_max); + } +} + void Curve::_bind_methods() { ClassDB::bind_method(D_METHOD("add_point", "position", "left_tangent", "right_tangent", "left_mode", "right_mode"), &Curve::add_point, DEFVAL(0), DEFVAL(0), DEFVAL(TANGENT_FREE), DEFVAL(TANGENT_FREE)); diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 9cb12a4345..058c4f1bc2 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -128,6 +128,8 @@ public: void set_bake_resolution(int p_resolution); real_t interpolate_baked(real_t offset); + void ensure_default_setup(float p_min, float p_max); + protected: static void _bind_methods(); diff --git a/scene/resources/default_theme/arrow_down.png b/scene/resources/default_theme/arrow_down.png Binary files differindex fc837d120a..bfb87a4761 100644 --- a/scene/resources/default_theme/arrow_down.png +++ b/scene/resources/default_theme/arrow_down.png diff --git a/scene/resources/default_theme/arrow_right.png b/scene/resources/default_theme/arrow_right.png Binary files differindex ebe6e26ace..1e4c8e5529 100644 --- a/scene/resources/default_theme/arrow_right.png +++ b/scene/resources/default_theme/arrow_right.png diff --git a/scene/resources/default_theme/background.png b/scene/resources/default_theme/background.png Binary files differindex 03cfab1de3..6c5f43e3ce 100644 --- a/scene/resources/default_theme/background.png +++ b/scene/resources/default_theme/background.png diff --git a/scene/resources/default_theme/base_green.png b/scene/resources/default_theme/base_green.png Binary files differindex d03d6f08d8..03a5b313d7 100644 --- a/scene/resources/default_theme/base_green.png +++ b/scene/resources/default_theme/base_green.png diff --git a/scene/resources/default_theme/button_disabled.png b/scene/resources/default_theme/button_disabled.png Binary files differindex d75e76989d..708748dfe9 100644 --- a/scene/resources/default_theme/button_disabled.png +++ b/scene/resources/default_theme/button_disabled.png diff --git a/scene/resources/default_theme/button_focus.png b/scene/resources/default_theme/button_focus.png Binary files differindex 44e354be95..70e16b953b 100644 --- a/scene/resources/default_theme/button_focus.png +++ b/scene/resources/default_theme/button_focus.png diff --git a/scene/resources/default_theme/button_hover.png b/scene/resources/default_theme/button_hover.png Binary files differindex 6e609f435f..ef226e3caf 100644 --- a/scene/resources/default_theme/button_hover.png +++ b/scene/resources/default_theme/button_hover.png diff --git a/scene/resources/default_theme/button_normal.png b/scene/resources/default_theme/button_normal.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/button_normal.png +++ b/scene/resources/default_theme/button_normal.png diff --git a/scene/resources/default_theme/checked.png b/scene/resources/default_theme/checked.png Binary files differindex 93e291a29e..bde031b6a2 100644 --- a/scene/resources/default_theme/checked.png +++ b/scene/resources/default_theme/checked.png diff --git a/scene/resources/default_theme/checker_bg.png b/scene/resources/default_theme/checker_bg.png Binary files differindex f58dfed29c..3eff2f0e08 100644 --- a/scene/resources/default_theme/checker_bg.png +++ b/scene/resources/default_theme/checker_bg.png diff --git a/scene/resources/default_theme/close.png b/scene/resources/default_theme/close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close.png +++ b/scene/resources/default_theme/close.png diff --git a/scene/resources/default_theme/close_hl.png b/scene/resources/default_theme/close_hl.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close_hl.png +++ b/scene/resources/default_theme/close_hl.png diff --git a/scene/resources/default_theme/color_picker_sample.png b/scene/resources/default_theme/color_picker_sample.png Binary files differindex b145a3e384..e6ec28d307 100644 --- a/scene/resources/default_theme/color_picker_sample.png +++ b/scene/resources/default_theme/color_picker_sample.png diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 3ea856541e..702953fa40 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -844,7 +844,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("separation", "HBoxContainer", 4 * scale); theme->set_constant("separation", "VBoxContainer", 4 * scale); - theme->set_constant("margin_left", "MarginContainer", 8 * scale); + theme->set_constant("margin_left", "MarginContainer", 0 * scale); theme->set_constant("margin_top", "MarginContainer", 0 * scale); theme->set_constant("margin_right", "MarginContainer", 0 * scale); theme->set_constant("margin_bottom", "MarginContainer", 0 * scale); @@ -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/default_theme/dosfont.png b/scene/resources/default_theme/dosfont.png Binary files differindex 814d2e9060..e2739b94ea 100644 --- a/scene/resources/default_theme/dosfont.png +++ b/scene/resources/default_theme/dosfont.png diff --git a/scene/resources/default_theme/dropdown.png b/scene/resources/default_theme/dropdown.png Binary files differindex 3a6a2ed778..b5d9ffbbb4 100644 --- a/scene/resources/default_theme/dropdown.png +++ b/scene/resources/default_theme/dropdown.png diff --git a/scene/resources/default_theme/error_icon.png b/scene/resources/default_theme/error_icon.png Binary files differindex f291362350..7741d00749 100644 --- a/scene/resources/default_theme/error_icon.png +++ b/scene/resources/default_theme/error_icon.png diff --git a/scene/resources/default_theme/focus.png b/scene/resources/default_theme/focus.png Binary files differindex 5d37028f2d..f51ea89e8f 100644 --- a/scene/resources/default_theme/focus.png +++ b/scene/resources/default_theme/focus.png diff --git a/scene/resources/default_theme/frame_focus.png b/scene/resources/default_theme/frame_focus.png Binary files differindex 9170db38ed..1b24ba47d8 100644 --- a/scene/resources/default_theme/frame_focus.png +++ b/scene/resources/default_theme/frame_focus.png diff --git a/scene/resources/default_theme/full_panel_bg.png b/scene/resources/default_theme/full_panel_bg.png Binary files differindex 7f02dc7259..85f753cc13 100644 --- a/scene/resources/default_theme/full_panel_bg.png +++ b/scene/resources/default_theme/full_panel_bg.png diff --git a/scene/resources/default_theme/graph_node_breakpoint.png b/scene/resources/default_theme/graph_node_breakpoint.png Binary files differindex 0e36f31bd4..e18c6f42e1 100644 --- a/scene/resources/default_theme/graph_node_breakpoint.png +++ b/scene/resources/default_theme/graph_node_breakpoint.png diff --git a/scene/resources/default_theme/graph_node_close.png b/scene/resources/default_theme/graph_node_close.png Binary files differindex 144a8b9c4c..5c962ae1c6 100644 --- a/scene/resources/default_theme/graph_node_close.png +++ b/scene/resources/default_theme/graph_node_close.png diff --git a/scene/resources/default_theme/graph_node_comment.png b/scene/resources/default_theme/graph_node_comment.png Binary files differindex f2d6daa259..cdec1d1eac 100644 --- a/scene/resources/default_theme/graph_node_comment.png +++ b/scene/resources/default_theme/graph_node_comment.png diff --git a/scene/resources/default_theme/graph_node_comment_focus.png b/scene/resources/default_theme/graph_node_comment_focus.png Binary files differindex a4b7b5a618..472a6b6f53 100644 --- a/scene/resources/default_theme/graph_node_comment_focus.png +++ b/scene/resources/default_theme/graph_node_comment_focus.png diff --git a/scene/resources/default_theme/graph_node_default.png b/scene/resources/default_theme/graph_node_default.png Binary files differindex e3a220301f..359bbdc205 100644 --- a/scene/resources/default_theme/graph_node_default.png +++ b/scene/resources/default_theme/graph_node_default.png diff --git a/scene/resources/default_theme/graph_node_default_focus.png b/scene/resources/default_theme/graph_node_default_focus.png Binary files differindex 9972b07593..204dd16ac0 100644 --- a/scene/resources/default_theme/graph_node_default_focus.png +++ b/scene/resources/default_theme/graph_node_default_focus.png diff --git a/scene/resources/default_theme/graph_node_position.png b/scene/resources/default_theme/graph_node_position.png Binary files differindex 7ec15e2ff4..24c2759be6 100644 --- a/scene/resources/default_theme/graph_node_position.png +++ b/scene/resources/default_theme/graph_node_position.png diff --git a/scene/resources/default_theme/graph_node_selected.png b/scene/resources/default_theme/graph_node_selected.png Binary files differindex f76c9703dd..cc4eb7f753 100644 --- a/scene/resources/default_theme/graph_node_selected.png +++ b/scene/resources/default_theme/graph_node_selected.png diff --git a/scene/resources/default_theme/graph_port.png b/scene/resources/default_theme/graph_port.png Binary files differindex 9d5082cfdb..f33ae3baf3 100644 --- a/scene/resources/default_theme/graph_port.png +++ b/scene/resources/default_theme/graph_port.png diff --git a/scene/resources/default_theme/hseparator.png b/scene/resources/default_theme/hseparator.png Binary files differindex 99609ac118..d4fd71ace5 100644 --- a/scene/resources/default_theme/hseparator.png +++ b/scene/resources/default_theme/hseparator.png diff --git a/scene/resources/default_theme/hslider_bg.png b/scene/resources/default_theme/hslider_bg.png Binary files differindex 9c2a2df62a..b402bd370d 100644 --- a/scene/resources/default_theme/hslider_bg.png +++ b/scene/resources/default_theme/hslider_bg.png diff --git a/scene/resources/default_theme/hslider_grabber.png b/scene/resources/default_theme/hslider_grabber.png Binary files differindex 2acd33879a..d273b491ee 100644 --- a/scene/resources/default_theme/hslider_grabber.png +++ b/scene/resources/default_theme/hslider_grabber.png diff --git a/scene/resources/default_theme/hslider_grabber_disabled.png b/scene/resources/default_theme/hslider_grabber_disabled.png Binary files differindex 0d75182b8f..dddd1a468e 100644 --- a/scene/resources/default_theme/hslider_grabber_disabled.png +++ b/scene/resources/default_theme/hslider_grabber_disabled.png diff --git a/scene/resources/default_theme/hslider_grabber_hl.png b/scene/resources/default_theme/hslider_grabber_hl.png Binary files differindex f8a011e64b..e3defb3610 100644 --- a/scene/resources/default_theme/hslider_grabber_hl.png +++ b/scene/resources/default_theme/hslider_grabber_hl.png diff --git a/scene/resources/default_theme/hslider_tick.png b/scene/resources/default_theme/hslider_tick.png Binary files differindex f7afd78529..1ba19c37a1 100644 --- a/scene/resources/default_theme/hslider_tick.png +++ b/scene/resources/default_theme/hslider_tick.png diff --git a/scene/resources/default_theme/hsplit_bg.png b/scene/resources/default_theme/hsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/hsplit_bg.png +++ b/scene/resources/default_theme/hsplit_bg.png diff --git a/scene/resources/default_theme/hsplitter.png b/scene/resources/default_theme/hsplitter.png Binary files differindex 71a3914d7e..2287753c9d 100644 --- a/scene/resources/default_theme/hsplitter.png +++ b/scene/resources/default_theme/hsplitter.png diff --git a/scene/resources/default_theme/icon_add.png b/scene/resources/default_theme/icon_add.png Binary files differindex fa675045bc..eccb69b363 100644 --- a/scene/resources/default_theme/icon_add.png +++ b/scene/resources/default_theme/icon_add.png diff --git a/scene/resources/default_theme/icon_close.png b/scene/resources/default_theme/icon_close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/icon_close.png +++ b/scene/resources/default_theme/icon_close.png diff --git a/scene/resources/default_theme/icon_color_pick.png b/scene/resources/default_theme/icon_color_pick.png Binary files differindex 15679a9558..46953febb8 100644 --- a/scene/resources/default_theme/icon_color_pick.png +++ b/scene/resources/default_theme/icon_color_pick.png diff --git a/scene/resources/default_theme/icon_folder.png b/scene/resources/default_theme/icon_folder.png Binary files differindex cc05e98ebb..d1b308e88d 100644 --- a/scene/resources/default_theme/icon_folder.png +++ b/scene/resources/default_theme/icon_folder.png diff --git a/scene/resources/default_theme/icon_parent_folder.png b/scene/resources/default_theme/icon_parent_folder.png Binary files differindex 47fee1ad81..35d218722e 100644 --- a/scene/resources/default_theme/icon_parent_folder.png +++ b/scene/resources/default_theme/icon_parent_folder.png diff --git a/scene/resources/default_theme/icon_play.png b/scene/resources/default_theme/icon_play.png Binary files differindex 864e4e4fb9..b9ed6e6d5b 100644 --- a/scene/resources/default_theme/icon_play.png +++ b/scene/resources/default_theme/icon_play.png diff --git a/scene/resources/default_theme/icon_reload.png b/scene/resources/default_theme/icon_reload.png Binary files differindex 9303fabb9c..bec5f3f4f9 100644 --- a/scene/resources/default_theme/icon_reload.png +++ b/scene/resources/default_theme/icon_reload.png diff --git a/scene/resources/default_theme/icon_snap_grid.png b/scene/resources/default_theme/icon_snap_grid.png Binary files differindex 44db9bdfdc..0680317d86 100644 --- a/scene/resources/default_theme/icon_snap_grid.png +++ b/scene/resources/default_theme/icon_snap_grid.png diff --git a/scene/resources/default_theme/icon_stop.png b/scene/resources/default_theme/icon_stop.png Binary files differindex 1f194d0e14..0c1371ceb9 100644 --- a/scene/resources/default_theme/icon_stop.png +++ b/scene/resources/default_theme/icon_stop.png diff --git a/scene/resources/default_theme/icon_zoom_less.png b/scene/resources/default_theme/icon_zoom_less.png Binary files differindex 888ddc995d..03119c60ca 100644 --- a/scene/resources/default_theme/icon_zoom_less.png +++ b/scene/resources/default_theme/icon_zoom_less.png diff --git a/scene/resources/default_theme/icon_zoom_more.png b/scene/resources/default_theme/icon_zoom_more.png Binary files differindex fa675045bc..31467ec3de 100644 --- a/scene/resources/default_theme/icon_zoom_more.png +++ b/scene/resources/default_theme/icon_zoom_more.png diff --git a/scene/resources/default_theme/icon_zoom_reset.png b/scene/resources/default_theme/icon_zoom_reset.png Binary files differindex 953ae47d24..cac68c09fa 100644 --- a/scene/resources/default_theme/icon_zoom_reset.png +++ b/scene/resources/default_theme/icon_zoom_reset.png diff --git a/scene/resources/default_theme/line_edit.png b/scene/resources/default_theme/line_edit.png Binary files differindex bf2b91f1be..2b0c506f34 100644 --- a/scene/resources/default_theme/line_edit.png +++ b/scene/resources/default_theme/line_edit.png diff --git a/scene/resources/default_theme/line_edit_disabled.png b/scene/resources/default_theme/line_edit_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/line_edit_disabled.png +++ b/scene/resources/default_theme/line_edit_disabled.png diff --git a/scene/resources/default_theme/line_edit_focus.png b/scene/resources/default_theme/line_edit_focus.png Binary files differindex e66d7b60e3..1d74b74068 100644 --- a/scene/resources/default_theme/line_edit_focus.png +++ b/scene/resources/default_theme/line_edit_focus.png diff --git a/scene/resources/default_theme/logo.png b/scene/resources/default_theme/logo.png Binary files differindex 2161402438..d0ef9d8aa7 100644 --- a/scene/resources/default_theme/logo.png +++ b/scene/resources/default_theme/logo.png diff --git a/scene/resources/default_theme/mini_checkerboard.png b/scene/resources/default_theme/mini_checkerboard.png Binary files differindex 3e53183847..d8279bda80 100644 --- a/scene/resources/default_theme/mini_checkerboard.png +++ b/scene/resources/default_theme/mini_checkerboard.png diff --git a/scene/resources/default_theme/option_arrow.png b/scene/resources/default_theme/option_arrow.png Binary files differindex 007de16bfa..40590fd60a 100644 --- a/scene/resources/default_theme/option_arrow.png +++ b/scene/resources/default_theme/option_arrow.png diff --git a/scene/resources/default_theme/option_button_disabled.png b/scene/resources/default_theme/option_button_disabled.png Binary files differindex ce727d56e1..1961b673cd 100644 --- a/scene/resources/default_theme/option_button_disabled.png +++ b/scene/resources/default_theme/option_button_disabled.png diff --git a/scene/resources/default_theme/option_button_focus.png b/scene/resources/default_theme/option_button_focus.png Binary files differindex c76d91287e..402670f9a2 100644 --- a/scene/resources/default_theme/option_button_focus.png +++ b/scene/resources/default_theme/option_button_focus.png diff --git a/scene/resources/default_theme/option_button_hover.png b/scene/resources/default_theme/option_button_hover.png Binary files differindex fd1e987ceb..826fe1c9ca 100644 --- a/scene/resources/default_theme/option_button_hover.png +++ b/scene/resources/default_theme/option_button_hover.png diff --git a/scene/resources/default_theme/option_button_normal.png b/scene/resources/default_theme/option_button_normal.png Binary files differindex 9d7fb98d1c..2dadb40338 100644 --- a/scene/resources/default_theme/option_button_normal.png +++ b/scene/resources/default_theme/option_button_normal.png diff --git a/scene/resources/default_theme/option_button_pressed.png b/scene/resources/default_theme/option_button_pressed.png Binary files differindex 28b1d93468..68796f9d85 100644 --- a/scene/resources/default_theme/option_button_pressed.png +++ b/scene/resources/default_theme/option_button_pressed.png diff --git a/scene/resources/default_theme/panel_bg.png b/scene/resources/default_theme/panel_bg.png Binary files differindex 320819ad6d..b496e2177e 100644 --- a/scene/resources/default_theme/panel_bg.png +++ b/scene/resources/default_theme/panel_bg.png diff --git a/scene/resources/default_theme/popup_bg.png b/scene/resources/default_theme/popup_bg.png Binary files differindex 63f5994441..023029f936 100644 --- a/scene/resources/default_theme/popup_bg.png +++ b/scene/resources/default_theme/popup_bg.png diff --git a/scene/resources/default_theme/popup_bg_disabled.png b/scene/resources/default_theme/popup_bg_disabled.png Binary files differindex 611d949380..8eab5f27bc 100644 --- a/scene/resources/default_theme/popup_bg_disabled.png +++ b/scene/resources/default_theme/popup_bg_disabled.png diff --git a/scene/resources/default_theme/popup_checked.png b/scene/resources/default_theme/popup_checked.png Binary files differindex a24e0543c0..b7b05640e1 100644 --- a/scene/resources/default_theme/popup_checked.png +++ b/scene/resources/default_theme/popup_checked.png diff --git a/scene/resources/default_theme/popup_hover.png b/scene/resources/default_theme/popup_hover.png Binary files differindex 85d4e48475..bdb6ae8bd0 100644 --- a/scene/resources/default_theme/popup_hover.png +++ b/scene/resources/default_theme/popup_hover.png diff --git a/scene/resources/default_theme/popup_unchecked.png b/scene/resources/default_theme/popup_unchecked.png Binary files differindex c1137e6fbf..ff922335c3 100644 --- a/scene/resources/default_theme/popup_unchecked.png +++ b/scene/resources/default_theme/popup_unchecked.png diff --git a/scene/resources/default_theme/popup_window.png b/scene/resources/default_theme/popup_window.png Binary files differindex 59362a8ffd..174a29ef45 100644 --- a/scene/resources/default_theme/popup_window.png +++ b/scene/resources/default_theme/popup_window.png diff --git a/scene/resources/default_theme/progress_bar.png b/scene/resources/default_theme/progress_bar.png Binary files differindex bf81e3adea..057557e567 100644 --- a/scene/resources/default_theme/progress_bar.png +++ b/scene/resources/default_theme/progress_bar.png diff --git a/scene/resources/default_theme/progress_fill.png b/scene/resources/default_theme/progress_fill.png Binary files differindex 3a34dfdda6..e39bb2a021 100644 --- a/scene/resources/default_theme/progress_fill.png +++ b/scene/resources/default_theme/progress_fill.png diff --git a/scene/resources/default_theme/radio_checked.png b/scene/resources/default_theme/radio_checked.png Binary files differindex 95d472022f..0ce575c15f 100644 --- a/scene/resources/default_theme/radio_checked.png +++ b/scene/resources/default_theme/radio_checked.png diff --git a/scene/resources/default_theme/radio_unchecked.png b/scene/resources/default_theme/radio_unchecked.png Binary files differindex 7f0535c3a4..fe5bcf6ab1 100644 --- a/scene/resources/default_theme/radio_unchecked.png +++ b/scene/resources/default_theme/radio_unchecked.png diff --git a/scene/resources/default_theme/reference_border.png b/scene/resources/default_theme/reference_border.png Binary files differindex 96219676bf..6a680f393c 100644 --- a/scene/resources/default_theme/reference_border.png +++ b/scene/resources/default_theme/reference_border.png diff --git a/scene/resources/default_theme/scroll_bg.png b/scene/resources/default_theme/scroll_bg.png Binary files differindex cefadb2c08..fb151a48b1 100644 --- a/scene/resources/default_theme/scroll_bg.png +++ b/scene/resources/default_theme/scroll_bg.png diff --git a/scene/resources/default_theme/scroll_button_down.png b/scene/resources/default_theme/scroll_button_down.png Binary files differindex caeac9b286..1df4ef5b6b 100644 --- a/scene/resources/default_theme/scroll_button_down.png +++ b/scene/resources/default_theme/scroll_button_down.png diff --git a/scene/resources/default_theme/scroll_button_down_hl.png b/scene/resources/default_theme/scroll_button_down_hl.png Binary files differindex 48036e0297..ba79087393 100644 --- a/scene/resources/default_theme/scroll_button_down_hl.png +++ b/scene/resources/default_theme/scroll_button_down_hl.png diff --git a/scene/resources/default_theme/scroll_button_left.png b/scene/resources/default_theme/scroll_button_left.png Binary files differindex 3b50938d97..e430cb4673 100644 --- a/scene/resources/default_theme/scroll_button_left.png +++ b/scene/resources/default_theme/scroll_button_left.png diff --git a/scene/resources/default_theme/scroll_button_left_hl.png b/scene/resources/default_theme/scroll_button_left_hl.png Binary files differindex b3d348c24f..2a6ef17a34 100644 --- a/scene/resources/default_theme/scroll_button_left_hl.png +++ b/scene/resources/default_theme/scroll_button_left_hl.png diff --git a/scene/resources/default_theme/scroll_button_right.png b/scene/resources/default_theme/scroll_button_right.png Binary files differindex 1c622a41ad..4f61687aa4 100644 --- a/scene/resources/default_theme/scroll_button_right.png +++ b/scene/resources/default_theme/scroll_button_right.png diff --git a/scene/resources/default_theme/scroll_button_right_hl.png b/scene/resources/default_theme/scroll_button_right_hl.png Binary files differindex 108796ca02..10e2722509 100644 --- a/scene/resources/default_theme/scroll_button_right_hl.png +++ b/scene/resources/default_theme/scroll_button_right_hl.png diff --git a/scene/resources/default_theme/scroll_button_up.png b/scene/resources/default_theme/scroll_button_up.png Binary files differindex 2c8238ae4c..f425412f50 100644 --- a/scene/resources/default_theme/scroll_button_up.png +++ b/scene/resources/default_theme/scroll_button_up.png diff --git a/scene/resources/default_theme/scroll_button_up_hl.png b/scene/resources/default_theme/scroll_button_up_hl.png Binary files differindex 4283bd114a..615a236c52 100644 --- a/scene/resources/default_theme/scroll_button_up_hl.png +++ b/scene/resources/default_theme/scroll_button_up_hl.png diff --git a/scene/resources/default_theme/scroll_grabber.png b/scene/resources/default_theme/scroll_grabber.png Binary files differindex 1d625a9b7b..732725a28f 100644 --- a/scene/resources/default_theme/scroll_grabber.png +++ b/scene/resources/default_theme/scroll_grabber.png diff --git a/scene/resources/default_theme/scroll_grabber_hl.png b/scene/resources/default_theme/scroll_grabber_hl.png Binary files differindex 99eb24b7e7..006cfa3361 100644 --- a/scene/resources/default_theme/scroll_grabber_hl.png +++ b/scene/resources/default_theme/scroll_grabber_hl.png diff --git a/scene/resources/default_theme/scroll_grabber_pressed.png b/scene/resources/default_theme/scroll_grabber_pressed.png Binary files differindex a46d242ddd..f4886158fa 100644 --- a/scene/resources/default_theme/scroll_grabber_pressed.png +++ b/scene/resources/default_theme/scroll_grabber_pressed.png diff --git a/scene/resources/default_theme/selection.png b/scene/resources/default_theme/selection.png Binary files differindex 501877a8b4..7d1c985b35 100644 --- a/scene/resources/default_theme/selection.png +++ b/scene/resources/default_theme/selection.png diff --git a/scene/resources/default_theme/selection_oof.png b/scene/resources/default_theme/selection_oof.png Binary files differindex 9594fe0913..2da0538389 100644 --- a/scene/resources/default_theme/selection_oof.png +++ b/scene/resources/default_theme/selection_oof.png diff --git a/scene/resources/default_theme/spinbox_updown.png b/scene/resources/default_theme/spinbox_updown.png Binary files differindex b40b1e9fd2..74fab19f34 100644 --- a/scene/resources/default_theme/spinbox_updown.png +++ b/scene/resources/default_theme/spinbox_updown.png diff --git a/scene/resources/default_theme/submenu.png b/scene/resources/default_theme/submenu.png Binary files differindex ec727eecf1..8f7de446d4 100644 --- a/scene/resources/default_theme/submenu.png +++ b/scene/resources/default_theme/submenu.png diff --git a/scene/resources/default_theme/tab.png b/scene/resources/default_theme/tab.png Binary files differindex 3e4d792a48..895daa65e2 100644 --- a/scene/resources/default_theme/tab.png +++ b/scene/resources/default_theme/tab.png diff --git a/scene/resources/default_theme/tab_behind.png b/scene/resources/default_theme/tab_behind.png Binary files differindex 12f07c3a91..2803d9db65 100644 --- a/scene/resources/default_theme/tab_behind.png +++ b/scene/resources/default_theme/tab_behind.png diff --git a/scene/resources/default_theme/tab_close.png b/scene/resources/default_theme/tab_close.png Binary files differindex 20d9b5c810..af2775a132 100644 --- a/scene/resources/default_theme/tab_close.png +++ b/scene/resources/default_theme/tab_close.png diff --git a/scene/resources/default_theme/tab_container_bg.png b/scene/resources/default_theme/tab_container_bg.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/tab_container_bg.png +++ b/scene/resources/default_theme/tab_container_bg.png diff --git a/scene/resources/default_theme/tab_current.png b/scene/resources/default_theme/tab_current.png Binary files differindex 7289e032da..520d147217 100644 --- a/scene/resources/default_theme/tab_current.png +++ b/scene/resources/default_theme/tab_current.png diff --git a/scene/resources/default_theme/tab_menu.png b/scene/resources/default_theme/tab_menu.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu.png +++ b/scene/resources/default_theme/tab_menu.png diff --git a/scene/resources/default_theme/tab_menu_hl.png b/scene/resources/default_theme/tab_menu_hl.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu_hl.png +++ b/scene/resources/default_theme/tab_menu_hl.png diff --git a/scene/resources/default_theme/toggle_off.png b/scene/resources/default_theme/toggle_off.png Binary files differindex 71cd64b001..64b51c8c9d 100644 --- a/scene/resources/default_theme/toggle_off.png +++ b/scene/resources/default_theme/toggle_off.png diff --git a/scene/resources/default_theme/toggle_on.png b/scene/resources/default_theme/toggle_on.png Binary files differindex 6ea1b589c7..f0c699c181 100644 --- a/scene/resources/default_theme/toggle_on.png +++ b/scene/resources/default_theme/toggle_on.png diff --git a/scene/resources/default_theme/tool_button_pressed.png b/scene/resources/default_theme/tool_button_pressed.png Binary files differindex bcf70b486d..5494475792 100644 --- a/scene/resources/default_theme/tool_button_pressed.png +++ b/scene/resources/default_theme/tool_button_pressed.png diff --git a/scene/resources/default_theme/tooltip_bg.png b/scene/resources/default_theme/tooltip_bg.png Binary files differindex eca0675a98..07b7d942ca 100644 --- a/scene/resources/default_theme/tooltip_bg.png +++ b/scene/resources/default_theme/tooltip_bg.png diff --git a/scene/resources/default_theme/tree_bg.png b/scene/resources/default_theme/tree_bg.png Binary files differindex 839a6a272a..2b0c506f34 100644 --- a/scene/resources/default_theme/tree_bg.png +++ b/scene/resources/default_theme/tree_bg.png diff --git a/scene/resources/default_theme/tree_bg_disabled.png b/scene/resources/default_theme/tree_bg_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/tree_bg_disabled.png +++ b/scene/resources/default_theme/tree_bg_disabled.png diff --git a/scene/resources/default_theme/tree_bg_focus.png b/scene/resources/default_theme/tree_bg_focus.png Binary files differindex 692cf71926..aadc6b0db4 100644 --- a/scene/resources/default_theme/tree_bg_focus.png +++ b/scene/resources/default_theme/tree_bg_focus.png diff --git a/scene/resources/default_theme/tree_cursor.png b/scene/resources/default_theme/tree_cursor.png Binary files differindex 94d2a08818..2b8722d066 100644 --- a/scene/resources/default_theme/tree_cursor.png +++ b/scene/resources/default_theme/tree_cursor.png diff --git a/scene/resources/default_theme/tree_cursor_unfocus.png b/scene/resources/default_theme/tree_cursor_unfocus.png Binary files differindex 3f023bbabe..bfaebbea85 100644 --- a/scene/resources/default_theme/tree_cursor_unfocus.png +++ b/scene/resources/default_theme/tree_cursor_unfocus.png diff --git a/scene/resources/default_theme/tree_title.png b/scene/resources/default_theme/tree_title.png Binary files differindex b0ddcffbbe..e5f3f49695 100644 --- a/scene/resources/default_theme/tree_title.png +++ b/scene/resources/default_theme/tree_title.png diff --git a/scene/resources/default_theme/tree_title_pressed.png b/scene/resources/default_theme/tree_title_pressed.png Binary files differindex 746d10039e..35e2bb3008 100644 --- a/scene/resources/default_theme/tree_title_pressed.png +++ b/scene/resources/default_theme/tree_title_pressed.png diff --git a/scene/resources/default_theme/unchecked.png b/scene/resources/default_theme/unchecked.png Binary files differindex d6f790cbc2..8c818af755 100644 --- a/scene/resources/default_theme/unchecked.png +++ b/scene/resources/default_theme/unchecked.png diff --git a/scene/resources/default_theme/updown.png b/scene/resources/default_theme/updown.png Binary files differindex 916284a3cf..56f81921e8 100644 --- a/scene/resources/default_theme/updown.png +++ b/scene/resources/default_theme/updown.png diff --git a/scene/resources/default_theme/vseparator.png b/scene/resources/default_theme/vseparator.png Binary files differindex 498768c05b..51e79f3ac5 100644 --- a/scene/resources/default_theme/vseparator.png +++ b/scene/resources/default_theme/vseparator.png diff --git a/scene/resources/default_theme/vslider_bg.png b/scene/resources/default_theme/vslider_bg.png Binary files differindex 8d9ead3c5a..ba3244e3e5 100644 --- a/scene/resources/default_theme/vslider_bg.png +++ b/scene/resources/default_theme/vslider_bg.png diff --git a/scene/resources/default_theme/vslider_grabber.png b/scene/resources/default_theme/vslider_grabber.png Binary files differindex afc490be45..6c6bf93e68 100644 --- a/scene/resources/default_theme/vslider_grabber.png +++ b/scene/resources/default_theme/vslider_grabber.png diff --git a/scene/resources/default_theme/vslider_grabber_disabled.png b/scene/resources/default_theme/vslider_grabber_disabled.png Binary files differindex c830359f45..49cced5055 100644 --- a/scene/resources/default_theme/vslider_grabber_disabled.png +++ b/scene/resources/default_theme/vslider_grabber_disabled.png diff --git a/scene/resources/default_theme/vslider_grabber_hl.png b/scene/resources/default_theme/vslider_grabber_hl.png Binary files differindex 548972e115..28774fdbf8 100644 --- a/scene/resources/default_theme/vslider_grabber_hl.png +++ b/scene/resources/default_theme/vslider_grabber_hl.png diff --git a/scene/resources/default_theme/vslider_tick.png b/scene/resources/default_theme/vslider_tick.png Binary files differindex 873ebb00d8..bde788b563 100644 --- a/scene/resources/default_theme/vslider_tick.png +++ b/scene/resources/default_theme/vslider_tick.png diff --git a/scene/resources/default_theme/vsplit_bg.png b/scene/resources/default_theme/vsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/vsplit_bg.png +++ b/scene/resources/default_theme/vsplit_bg.png diff --git a/scene/resources/default_theme/vsplitter.png b/scene/resources/default_theme/vsplitter.png Binary files differindex ec5542bf69..dde1f390df 100644 --- a/scene/resources/default_theme/vsplitter.png +++ b/scene/resources/default_theme/vsplitter.png diff --git a/scene/resources/default_theme/window_resizer.png b/scene/resources/default_theme/window_resizer.png Binary files differindex ed51968c4e..b06e6f5366 100644 --- a/scene/resources/default_theme/window_resizer.png +++ b/scene/resources/default_theme/window_resizer.png diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 05493d5777..e5d463d391 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -625,7 +625,7 @@ void DynamicFontAtSize::_update_char(CharType p_char) { break; } - int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); if (error) { char_map[p_char] = character; return; diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 3fab4d3cfc..d3da842b79 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -378,7 +378,7 @@ bool Environment::is_ssr_rough() const { void Environment::set_ssao_enabled(bool p_enable) { ssao_enabled = p_enable; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); _change_notify(); } @@ -390,7 +390,7 @@ bool Environment::is_ssao_enabled() const { void Environment::set_ssao_radius(float p_radius) { ssao_radius = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius() const { @@ -400,7 +400,7 @@ float Environment::get_ssao_radius() const { void Environment::set_ssao_intensity(float p_intensity) { ssao_intensity = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity() const { @@ -411,7 +411,7 @@ float Environment::get_ssao_intensity() const { void Environment::set_ssao_radius2(float p_radius) { ssao_radius2 = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius2() const { @@ -421,7 +421,7 @@ float Environment::get_ssao_radius2() const { void Environment::set_ssao_intensity2(float p_intensity) { ssao_intensity2 = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity2() const { @@ -431,7 +431,7 @@ float Environment::get_ssao_intensity2() const { void Environment::set_ssao_bias(float p_bias) { ssao_bias = p_bias; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_bias() const { @@ -441,17 +441,27 @@ float Environment::get_ssao_bias() const { void Environment::set_ssao_direct_light_affect(float p_direct_light_affect) { ssao_direct_light_affect = p_direct_light_affect; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_direct_light_affect() const { return ssao_direct_light_affect; } +void Environment::set_ssao_ao_channel_affect(float p_ao_channel_affect) { + + ssao_ao_channel_affect = p_ao_channel_affect; + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); +} +float Environment::get_ssao_ao_channel_affect() const { + + return ssao_ao_channel_affect; +} + void Environment::set_ssao_color(const Color &p_color) { ssao_color = p_color; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Color Environment::get_ssao_color() const { @@ -462,7 +472,7 @@ Color Environment::get_ssao_color() const { void Environment::set_ssao_blur(SSAOBlur p_blur) { ssao_blur = p_blur; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOBlur Environment::get_ssao_blur() const { @@ -472,7 +482,7 @@ Environment::SSAOBlur Environment::get_ssao_blur() const { void Environment::set_ssao_quality(SSAOQuality p_quality) { ssao_quality = p_quality; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOQuality Environment::get_ssao_quality() const { @@ -483,7 +493,7 @@ Environment::SSAOQuality Environment::get_ssao_quality() const { void Environment::set_ssao_edge_sharpness(float p_edge_sharpness) { ssao_edge_sharpness = p_edge_sharpness; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_edge_sharpness() const { @@ -1008,6 +1018,9 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ssao_direct_light_affect", "amount"), &Environment::set_ssao_direct_light_affect); ClassDB::bind_method(D_METHOD("get_ssao_direct_light_affect"), &Environment::get_ssao_direct_light_affect); + ClassDB::bind_method(D_METHOD("set_ssao_ao_channel_affect", "amount"), &Environment::set_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("get_ssao_ao_channel_affect"), &Environment::get_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("set_ssao_color", "color"), &Environment::set_ssao_color); ClassDB::bind_method(D_METHOD("get_ssao_color"), &Environment::get_ssao_color); @@ -1028,6 +1041,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_intensity2", PROPERTY_HINT_RANGE, "0.0,128,0.1"), "set_ssao_intensity2", "get_ssao_intensity2"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_bias", PROPERTY_HINT_RANGE, "0.001,8,0.001"), "set_ssao_bias", "get_ssao_bias"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_light_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_direct_light_affect", "get_ssao_direct_light_affect"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_ao_channel_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_ao_channel_affect", "get_ssao_ao_channel_affect"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ssao_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ssao_color", "get_ssao_color"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_ssao_quality", "get_ssao_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_blur", PROPERTY_HINT_ENUM, "Disabled,1x1,2x2,3x3"), "set_ssao_blur", "get_ssao_blur"); @@ -1220,6 +1234,7 @@ Environment::Environment() { ssao_intensity2 = 1; ssao_bias = 0.01; ssao_direct_light_affect = 0.0; + ssao_ao_channel_affect = 0.0; ssao_blur = SSAO_BLUR_3x3; set_ssao_edge_sharpness(4); set_ssao_quality(SSAO_QUALITY_LOW); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index 27fd57aa09..7d66c7e60b 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -127,6 +127,7 @@ private: float ssao_intensity2; float ssao_bias; float ssao_direct_light_affect; + float ssao_ao_channel_affect; Color ssao_color; SSAOBlur ssao_blur; float ssao_edge_sharpness; @@ -274,6 +275,9 @@ public: void set_ssao_direct_light_affect(float p_direct_light_affect); float get_ssao_direct_light_affect() const; + void set_ssao_ao_channel_affect(float p_ao_channel_affect); + float get_ssao_ao_channel_affect() const; + void set_ssao_color(const Color &p_color); Color get_ssao_color() const; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 5e7569586a..654d7b884e 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -393,6 +393,9 @@ void SpatialMaterial::_update_shader() { if (flags[FLAG_DONT_RECEIVE_SHADOWS]) { code += ",shadows_disabled"; } + if (flags[FLAG_ENSURE_CORRECT_NORMALS]) { + code += ",ensure_correct_normals"; + } code += ";\n"; code += "uniform vec4 albedo : hint_color;\n"; @@ -1852,6 +1855,7 @@ void SpatialMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_SHADOWS); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_ensure_correct_normals"), "set_flag", "get_flag", FLAG_ENSURE_CORRECT_NORMALS); ADD_GROUP("Vertex Color", "vertex_color"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_use_as_albedo"), "set_flag", "get_flag", FLAG_ALBEDO_FROM_VERTEX_COLOR); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_is_srgb"), "set_flag", "get_flag", FLAG_SRGB_VERTEX_COLOR); @@ -2042,6 +2046,7 @@ void SpatialMaterial::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD); BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB); BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS); + BIND_ENUM_CONSTANT(FLAG_ENSURE_CORRECT_NORMALS); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(DIFFUSE_BURLEY); diff --git a/scene/resources/material.h b/scene/resources/material.h index ce733bfb8d..87594213bc 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -189,6 +189,7 @@ public: FLAG_USE_ALPHA_SCISSOR, FLAG_ALBEDO_TEXTURE_FORCE_SRGB, FLAG_DONT_RECEIVE_SHADOWS, + FLAG_ENSURE_CORRECT_NORMALS, FLAG_MAX }; @@ -237,7 +238,7 @@ private: uint64_t blend_mode : 2; uint64_t depth_draw_mode : 2; uint64_t cull_mode : 2; - uint64_t flags : 15; + uint64_t flags : 16; uint64_t detail_blend_mode : 2; uint64_t diffuse_mode : 3; uint64_t specular_mode : 2; diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index e8b7ecaf9a..a3fb068569 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -100,7 +100,7 @@ public: ARRAY_FLAG_USE_16_BIT_BONES = ARRAY_COMPRESS_INDEX << 2, ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_VERTEX | ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS + ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS }; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 5b600623b9..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) { @@ -677,9 +677,9 @@ void CubeMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &CubeMesh::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"), "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 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/theme.h b/scene/resources/theme.h index c23f237c75..e0d4038e7e 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -55,12 +55,12 @@ class Theme : public Resource { void _unref_font(Ref<Font> p_sc); void _emit_theme_changed(); - HashMap<StringName, HashMap<StringName, Ref<Texture>, StringNameHasher>, StringNameHasher> icon_map; - HashMap<StringName, HashMap<StringName, Ref<StyleBox>, StringNameHasher>, StringNameHasher> style_map; - HashMap<StringName, HashMap<StringName, Ref<Font>, StringNameHasher>, StringNameHasher> font_map; - HashMap<StringName, HashMap<StringName, Ref<Shader>, StringNameHasher>, StringNameHasher> shader_map; - HashMap<StringName, HashMap<StringName, Color, StringNameHasher>, StringNameHasher> color_map; - HashMap<StringName, HashMap<StringName, int, StringNameHasher>, StringNameHasher> constant_map; + HashMap<StringName, HashMap<StringName, Ref<Texture> > > icon_map; + HashMap<StringName, HashMap<StringName, Ref<StyleBox> > > style_map; + HashMap<StringName, HashMap<StringName, Ref<Font> > > font_map; + HashMap<StringName, HashMap<StringName, Ref<Shader> > > shader_map; + HashMap<StringName, HashMap<StringName, Color> > color_map; + HashMap<StringName, HashMap<StringName, int> > constant_map; protected: bool _set(const StringName &p_name, const Variant &p_value); 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 }; |