diff options
35 files changed, 1631 insertions, 701 deletions
diff --git a/core/class_db.cpp b/core/class_db.cpp index 1fe02c8cd9..6b8c290a99 100644 --- a/core/class_db.cpp +++ b/core/class_db.cpp @@ -497,7 +497,7 @@ void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherit } } -void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) { +void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance, bool p_exclude_from_properties) { OBJTYPE_RLOCK; @@ -528,6 +528,9 @@ void ClassDB::get_method_list(StringName p_class, List<MethodInfo> *p_methods, b minfo.name = E->get(); minfo.id = method->get_method_id(); + if (p_exclude_from_properties && type->methods_in_properties.has(minfo.name)) + continue; + for (int i = 0; i < method->get_argument_count(); i++) { //Variant::Type t=method->get_argument_type(i); @@ -802,7 +805,14 @@ void ClassDB::add_property(StringName p_class, const PropertyInfo &p_pinfo, cons OBJTYPE_WLOCK type->property_list.push_back(p_pinfo); - +#ifdef DEBUG_METHODS_ENABLED + if (mb_get) { + type->methods_in_properties.insert(p_getter); + } + if (mb_set) { + type->methods_in_properties.insert(p_setter); + } +#endif PropertySetGet psg; psg.setter = p_setter; psg.getter = p_getter; diff --git a/core/class_db.h b/core/class_db.h index 547068da5f..4f00a16e91 100644 --- a/core/class_db.h +++ b/core/class_db.h @@ -139,6 +139,7 @@ public: #ifdef DEBUG_METHODS_ENABLED List<StringName> constant_order; List<StringName> method_order; + Set<StringName> methods_in_properties; List<MethodInfo> virtual_methods; StringName category; #endif @@ -486,7 +487,7 @@ public: static bool has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false); static void set_method_flags(StringName p_class, StringName p_method, int p_flags); - static void get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false); + static void get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static MethodBind *get_method(StringName p_class, StringName p_name); static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true); diff --git a/core/image.cpp b/core/image.cpp index ec21260b19..e0cf82d920 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -2167,7 +2167,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mipmap_offset", "mipmap"), &Image::get_mipmap_offset); - ClassDB::bind_method(D_METHOD("resize_to_po2", "square"), &Image::resize_to_po2, DEFVAL("false")); + ClassDB::bind_method(D_METHOD("resize_to_po2", "square"), &Image::resize_to_po2, DEFVAL(false)); ClassDB::bind_method(D_METHOD("resize", "width", "height", "interpolation"), &Image::resize, DEFVAL(INTERPOLATE_BILINEAR)); ClassDB::bind_method(D_METHOD("shrink_x2"), &Image::shrink_x2); ClassDB::bind_method(D_METHOD("expand_x2_hq2x"), &Image::expand_x2_hq2x); diff --git a/core/os/input_event.cpp b/core/os/input_event.cpp index dbdf9628e3..e60f588be3 100644 --- a/core/os/input_event.cpp +++ b/core/os/input_event.cpp @@ -89,6 +89,11 @@ bool InputEvent::action_match(const Ref<InputEvent> &p_event) const { return false; } +bool InputEvent::shortcut_match(const Ref<InputEvent> &p_event) const { + + return false; +} + bool InputEvent::is_action_type() const { return false; @@ -130,6 +135,7 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text); ClassDB::bind_method(D_METHOD("action_match", "event:InputEvent"), &InputEvent::action_match); + ClassDB::bind_method(D_METHOD("shortcut_match", "event:InputEvent"), &InputEvent::shortcut_match); ClassDB::bind_method(D_METHOD("is_action_type"), &InputEvent::is_action_type); @@ -276,6 +282,27 @@ uint32_t InputEventKey::get_scancode_with_modifiers() const { return sc; } +String InputEventKey::as_text() const { + + String kc = keycode_get_string(scancode); + if (kc == String()) + return kc; + + if (get_metakey()) { + kc = "Meta+" + kc; + } + if (get_alt()) { + kc = "Alt+" + kc; + } + if (get_shift()) { + kc = "Shift+" + kc; + } + if (get_control()) { + kc = "Ctrl+" + kc; + } + return kc; +} + bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const { Ref<InputEventKey> key = p_event; @@ -288,6 +315,18 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const { return get_scancode() == key->get_scancode() && (!key->is_pressed() || (code & event_code) == code); } +bool InputEventKey::shortcut_match(const Ref<InputEvent> &p_event) const { + + Ref<InputEventKey> key = p_event; + if (key.is_null()) + return false; + + uint32_t code = get_scancode_with_modifiers(); + uint32_t event_code = key->get_scancode_with_modifiers(); + + return code == event_code; +} + void InputEventKey::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventKey::set_pressed); diff --git a/core/os/input_event.h b/core/os/input_event.h index 6a694df345..b120d4b840 100644 --- a/core/os/input_event.h +++ b/core/os/input_event.h @@ -165,6 +165,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; virtual bool action_match(const Ref<InputEvent> &p_event) const; + virtual bool shortcut_match(const Ref<InputEvent> &p_event) const; virtual bool is_action_type() const; InputEvent(); @@ -243,9 +244,12 @@ public: uint32_t get_scancode_with_modifiers() const; virtual bool action_match(const Ref<InputEvent> &p_event) const; + virtual bool shortcut_match(const Ref<InputEvent> &p_event) const; virtual bool is_action_type() const { return true; } + virtual String as_text() const; + InputEventKey(); }; diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 81baf542c0..f7e1fdee9d 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -5288,6 +5288,7 @@ void RasterizerStorageGLES3::_particles_process(Particles *particles, float p_de if (particles->clear) { particles->cycle_number = 0; + particles->random_seed = Math::rand(); } else if (new_phase < particles->phase) { particles->cycle_number++; } @@ -5298,6 +5299,8 @@ void RasterizerStorageGLES3::_particles_process(Particles *particles, float p_de shaders.particles.set_uniform(ParticlesShaderGLES3::DELTA, p_delta * particles->speed_scale); shaders.particles.set_uniform(ParticlesShaderGLES3::CLEAR, particles->clear); + glUniform1ui(shaders.particles.get_uniform_location(ParticlesShaderGLES3::RANDOM_SEED), particles->random_seed); + if (particles->use_local_coords) shaders.particles.set_uniform(ParticlesShaderGLES3::EMISSION_TRANSFORM, Transform()); else @@ -5353,6 +5356,33 @@ void RasterizerStorageGLES3::update_particles() { Particles *particles = particle_update_list.first()->self(); + if (particles->inactive && !particles->emitting) { + + particle_update_list.remove(particle_update_list.first()); + continue; + } + + if (particles->emitting) { + if (particles->inactive) { + //restart system from scratch + particles->prev_ticks = 0; + particles->phase = 0; + particles->prev_phase = 0; + particles->clear = true; + particles->particle_valid_histories[0] = false; + particles->particle_valid_histories[1] = false; + } + particles->inactive = false; + particles->inactive_time = 0; + } else { + particles->inactive_time += particles->speed_scale * frame.delta; + if (particles->inactive_time > particles->lifetime * 1.2) { + particles->inactive = true; + particle_update_list.remove(particle_update_list.first()); + continue; + } + } + Material *material = material_owner.getornull(particles->process_material); if (!material || !material->shader || material->shader->mode != VS::SHADER_PARTICLES) { diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index ca0194bd5e..65026a16ec 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -1033,6 +1033,8 @@ public: struct Particles : public GeometryOwner { + bool inactive; + float inactive_time; bool emitting; int amount; float lifetime; @@ -1060,6 +1062,7 @@ public: float phase; float prev_phase; uint64_t prev_ticks; + uint32_t random_seed; uint32_t cycle_number; @@ -1088,6 +1091,7 @@ public: frame_remainder = 0; histories_enabled = false; speed_scale = 1.0; + random_seed = 0; custom_aabb = Rect3(Vector3(-4, -4, -4), Vector3(8, 8, 8)); @@ -1098,6 +1102,8 @@ public: prev_ticks = 0; clear = true; + inactive = true; + inactive_time = false; glGenBuffers(2, particle_buffers); glGenVertexArrays(2, particle_vaos); diff --git a/drivers/gles3/shader_compiler_gles3.cpp b/drivers/gles3/shader_compiler_gles3.cpp index 3376f99112..41421a3e2f 100644 --- a/drivers/gles3/shader_compiler_gles3.cpp +++ b/drivers/gles3/shader_compiler_gles3.cpp @@ -795,6 +795,7 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() { actions[VS::SHADER_PARTICLES].renames["INDEX"] = "index"; actions[VS::SHADER_PARTICLES].renames["GRAVITY"] = "current_gravity"; actions[VS::SHADER_PARTICLES].renames["EMISSION_TRANSFORM"] = "emission_transform"; + actions[VS::SHADER_PARTICLES].renames["RANDOM_SEED"] = "random_seed"; actions[VS::SHADER_SPATIAL].render_mode_defines["disable_force"] = "#define DISABLE_FORCE\n"; actions[VS::SHADER_SPATIAL].render_mode_defines["disable_velocity"] = "#define DISABLE_VELOCITY\n"; diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl index 7e7b083f73..ec2577538c 100644 --- a/drivers/gles3/shaders/particles.glsl +++ b/drivers/gles3/shaders/particles.glsl @@ -37,6 +37,7 @@ uniform bool clear; uniform uint cycle; uniform float lifetime; uniform mat4 emission_transform; +uniform uint random_seed; out highp vec4 out_color; //tfb: @@ -104,7 +105,9 @@ void main() { bool shader_active = velocity_active.a > 0.5; if (system_phase > prev_system_phase) { - if (prev_system_phase < restart_phase && system_phase >= restart_phase) { + // restart_phase >= prev_system_phase is used so particles emit in the first frame they are processed + + if (restart_phase >= prev_system_phase && restart_phase < system_phase ) { restart=true; #ifdef USE_FRACTIONAL_DELTA local_delta = (system_phase - restart_phase) * lifetime; @@ -112,12 +115,12 @@ void main() { } } else { - if (prev_system_phase < restart_phase) { + if (restart_phase >= prev_system_phase) { restart=true; #ifdef USE_FRACTIONAL_DELTA local_delta = (1.0 - restart_phase + system_phase) * lifetime; #endif - } else if (system_phase >= restart_phase) { + } else if (restart_phase < system_phase ) { restart=true; #ifdef USE_FRACTIONAL_DELTA local_delta = (system_phase - restart_phase) * lifetime; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 56b62cdf6e..2b29e4b08a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5311,36 +5311,6 @@ EditorNode::EditorNode() { top_region->add_child(left_menu_hb); menu_hb->add_child(top_region); - PopupMenu *p; - - project_menu = memnew(MenuButton); - project_menu->set_tooltip(TTR("Miscellaneous project or scene-wide tools.")); - project_menu->set_text(TTR("Project")); - project_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles")); - left_menu_hb->add_child(project_menu); - - p = project_menu->get_popup(); - p->connect("id_pressed", this, "_menu_option"); - p->add_item(TTR("Run Script"), FILE_RUN_SCRIPT, KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_R); - p->add_item(TTR("Export"), FILE_EXPORT_PROJECT); - - PopupMenu *tool_menu = memnew(PopupMenu); - tool_menu->set_name("Tools"); - tool_menu->connect("id_pressed", this, "_menu_option"); - p->add_child(tool_menu); - p->add_submenu_item(TTR("Tools"), "Tools"); - tool_menu->add_item(TTR("Orphan Resource Explorer"), TOOLS_ORPHAN_RESOURCES); - p->add_separator(); - p->add_item(TTR("Project Settings"), RUN_SETTINGS); - p->add_separator(); - -#ifdef OSX_ENABLED - p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_Q); -#else - p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_CTRL + KEY_Q); -#endif - p->add_item(TTR("Quit"), FILE_QUIT, KEY_MASK_CMD + KEY_Q); - file_menu = memnew(MenuButton); file_menu->set_text(TTR("Scene")); //file_menu->set_icon(gui_base->get_icon("Save","EditorIcons")); @@ -5360,6 +5330,7 @@ EditorNode::EditorNode() { ED_SHORTCUT("editor/next_tab", TTR("Next tab"), KEY_MASK_CMD + KEY_TAB); ED_SHORTCUT("editor/prev_tab", TTR("Previous tab"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_TAB); ED_SHORTCUT("editor/filter_files", TTR("Filter Files.."), KEY_MASK_ALT + KEY_MASK_CMD + KEY_P); + PopupMenu *p; file_menu->set_tooltip(TTR("Operations with scene files.")); p = file_menu->get_popup(); @@ -5379,7 +5350,6 @@ EditorNode::EditorNode() { p->add_shortcut(ED_SHORTCUT("editor/quick_open_scene", TTR("Quick Open Scene.."), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_O), FILE_QUICK_OPEN_SCENE); p->add_shortcut(ED_SHORTCUT("editor/quick_open_script", TTR("Quick Open Script.."), KEY_MASK_ALT + KEY_MASK_CMD + KEY_O), FILE_QUICK_OPEN_SCRIPT); p->add_separator(); - PopupMenu *pm_export = memnew(PopupMenu); pm_export->set_name("Export"); p->add_child(pm_export); @@ -5405,6 +5375,35 @@ EditorNode::EditorNode() { sp->set_custom_minimum_size(Size2(30, 0) * EDSCALE); menu_hb->add_child(sp); } + p->add_separator(); + p->add_item(TTR("Quit"), FILE_QUIT, KEY_MASK_CMD + KEY_Q); + + project_menu = memnew(MenuButton); + project_menu->set_tooltip(TTR("Miscellaneous project or scene-wide tools.")); + project_menu->set_text(TTR("Project")); + project_menu->add_style_override("hover", gui_base->get_stylebox("MenuHover", "EditorStyles")); + left_menu_hb->add_child(project_menu); + + p = project_menu->get_popup(); + p->add_item(TTR("Project Settings"), RUN_SETTINGS); + p->add_separator(); + p->connect("id_pressed", this, "_menu_option"); + p->add_item(TTR("Run Script"), FILE_RUN_SCRIPT, KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_R); + p->add_item(TTR("Export"), FILE_EXPORT_PROJECT); + + PopupMenu *tool_menu = memnew(PopupMenu); + tool_menu->set_name("Tools"); + tool_menu->connect("id_pressed", this, "_menu_option"); + p->add_child(tool_menu); + p->add_submenu_item(TTR("Tools"), "Tools"); + tool_menu->add_item(TTR("Orphan Resource Explorer"), TOOLS_ORPHAN_RESOURCES); + p->add_separator(); + +#ifdef OSX_ENABLED + p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_Q); +#else + p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_CTRL + KEY_Q); +#endif PanelContainer *editor_region = memnew(PanelContainer); main_editor_button_vb = memnew(HBoxContainer); @@ -6115,7 +6114,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(GradientEditorPlugin(this))); add_editor_plugin(memnew(GradientTextureEditorPlugin(this))); add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this))); - add_editor_plugin(memnew(CurveTextureEditorPlugin(this))); + add_editor_plugin(memnew(CurveEditorPlugin(this))); add_editor_plugin(memnew(TextureEditorPlugin(this))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); //add_editor_plugin( memnew( MaterialEditorPlugin(this) ) ); diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index d869d703f1..50a625ddc1 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -27,528 +27,695 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ + #include "curve_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core_string_names.h" +#include "os/input.h" #include "os/keyboard.h" -#include "spatial_editor_plugin.h" -void CurveTextureEdit::_gui_input(const Ref<InputEvent> &p_event) { +CurveEditor::CurveEditor() { + _selected_point = -1; + _hover_point = -1; + _selected_tangent = TANGENT_NONE; + _hover_radius = 6; + _tangents_length = 40; + _dragging = false; + _has_undo_data = false; + _world_rect = Rect2(0, 0, 1, 1); - Ref<InputEventKey> k = p_event; - if (k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && grabbed != -1) { + set_focus_mode(FOCUS_ALL); + set_clip_contents(true); + + _context_menu = memnew(PopupMenu); + _context_menu->connect("id_pressed", this, "_on_context_menu_item_selected"); + add_child(_context_menu); + + _presets_menu = memnew(PopupMenu); + _presets_menu->set_name("_presets_menu"); + _presets_menu->add_item("Flat0", PRESET_FLAT0); + _presets_menu->add_item("Flat1", PRESET_FLAT1); + _presets_menu->add_item("Linear", PRESET_LINEAR); + _presets_menu->add_item("Ease in", PRESET_EASE_IN); + _presets_menu->add_item("Ease out", PRESET_EASE_OUT); + _presets_menu->add_item("Smoothstep", PRESET_SMOOTHSTEP); + _presets_menu->connect("id_pressed", this, "_on_preset_item_selected"); + _context_menu->add_child(_presets_menu); +} - points.remove(grabbed); - grabbed = -1; - update(); - emit_signal("curve_changed"); - accept_event(); +void CurveEditor::set_curve(Ref<Curve> curve) { + + if (curve == _curve_ref) + return; + + if (_curve_ref.is_valid()) { + _curve_ref->disconnect("changed", this, "_curve_changed"); + } + _curve_ref = curve; + if (_curve_ref.is_valid()) { + _curve_ref->connect("changed", this, "_curve_changed"); } - Ref<InputEventMouseButton> mb = p_event; + _selected_point = -1; + _hover_point = -1; + _selected_tangent = TANGENT_NONE; - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + update(); - update(); - Ref<Font> font = get_font("font", "Label"); + // Note: if you edit a curve, then set another, and try to undo, + // it will normally apply on the previous curve, but you won't see it +} + +Size2 CurveEditor::get_minimum_size() const { + return Vector2(64, 64); +} + +void CurveEditor::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) + _draw(); +} + +void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb_ref = p_event; + if (mb_ref.is_valid()) { + + const InputEventMouseButton &mb = **mb_ref; - int font_h = font->get_height(); + if (mb.is_pressed() && !_dragging) { - Vector2 size = get_size(); - size.y -= font_h; + Vector2 mpos = mb.get_position(); - Point2 p = Vector2(mb->get_position().x, mb->get_position().y) / size; - p.y = CLAMP(1.0 - p.y, 0, 1) * (max - min) + min; - grabbed = -1; - grabbing = true; + _selected_tangent = get_tangent_at(mpos); + if (_selected_tangent == TANGENT_NONE) + set_selected_point(get_point_at(mpos)); - for (int i = 0; i < points.size(); i++) { + switch (mb.get_button_index()) { + case BUTTON_RIGHT: + _context_click_pos = mpos; + open_context_menu(get_global_transform().xform(mpos)); + break; - Vector2 ps = p * get_size(); - Vector2 pt = Vector2(points[i].offset, points[i].height) * get_size(); - if (ps.distance_to(pt) < 4) { - grabbed = i; + case BUTTON_MIDDLE: + remove_point(_hover_point); + break; + + case BUTTON_LEFT: + _dragging = true; + break; } } - //grab or select - if (grabbed != -1) { - return; - } - //insert - - Point np; - np.offset = p.x; - np.height = p.y; - - points.push_back(np); - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == p.x && points[i].height == p.y) { - grabbed = i; - break; + if (!mb.is_pressed() && _dragging && mb.get_button_index() == BUTTON_LEFT) { + _dragging = false; + if (_has_undo_data) { + push_undo(_undo_data); + _has_undo_data = false; } } - - emit_signal("curve_changed"); } - if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { + Ref<InputEventMouseMotion> mm_ref = p_event; + if (mm_ref.is_valid()) { - if (grabbing) { - grabbing = false; - emit_signal("curve_changed"); - } - update(); - } + const InputEventMouseMotion &mm = **mm_ref; - Ref<InputEventMouseMotion> mm = p_event; + Vector2 mpos = mm.get_position(); - if (mm.is_valid() && grabbing && grabbed != -1) { + if (_dragging && _curve_ref.is_valid()) { + if (_selected_point != -1) { - Ref<Font> font = get_font("font", "Label"); - int font_h = font->get_height(); - Vector2 size = get_size(); - size.y -= font_h; + if (!_has_undo_data) { + // Save curve state before dragging points + _undo_data = _curve_ref->get_data(); + _has_undo_data = true; + } - Point2 p = mm->get_position() / size; - p.y = CLAMP(1.0 - p.y, 0, 1) * (max - min) + min; - p.x = CLAMP(p.x, 0.0, 1.0); + if (_selected_tangent == TANGENT_NONE) { + // Drag point - bool valid = true; + Vector2 point_pos = get_world_pos(mpos); - for (int i = 0; i < points.size(); i++) { + int i = _curve_ref->set_point_offset(_selected_point, point_pos.x); + // The index may change if the point is dragged across another one + set_hover_point_index(i); + set_selected_point(i); - if (points[i].offset == p.x && points[i].height == p.y && i != grabbed) { - valid = false; - } - } + // TODO Get rid of this clamp if zoom is implemented in this editor. + // This is to prevent the user from loosing a point out of view. + if (point_pos.y < 0.0) + point_pos.y = 0.0; + else if (point_pos.y > 1.0) + point_pos.y = 1.0; + + _curve_ref->set_point_value(_selected_point, point_pos.y); - if (!valid) - return; + //auto_calculate_tangents(i); - points[grabbed].offset = p.x; - points[grabbed].height = p.y; + } else { + // Drag tangent - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == p.x && points[i].height == p.y) { - grabbed = i; - break; + Vector2 point_pos = _curve_ref->get_point_pos(_selected_point); + Vector2 control_pos = get_world_pos(mpos); + + Vector2 dir = (control_pos - point_pos).normalized(); + + real_t tangent; + if (Math::abs(dir.x) > CMP_EPSILON) + tangent = dir.y / dir.x; + else + tangent = 9999 * (dir.y >= 0 ? 1 : -1); + + bool link = !Input::get_singleton()->is_key_pressed(KEY_SHIFT); + + if (_selected_tangent == TANGENT_LEFT) { + _curve_ref->set_point_left_tangent(_selected_point, tangent); + if (link && _selected_point != _curve_ref->get_point_count() - 1) + _curve_ref->set_point_right_tangent(_selected_point, tangent); + } else { + _curve_ref->set_point_right_tangent(_selected_point, tangent); + if (link && _selected_point != 0) + _curve_ref->set_point_left_tangent(_selected_point, tangent); + } + } } + + } else { + set_hover_point_index(get_point_at(mpos)); } + } - emit_signal("curve_changed"); + Ref<InputEventKey> key_ref = p_event; + if (key_ref.is_valid()) { + const InputEventKey &key = **key_ref; - update(); + if (key.is_pressed() && _selected_point != -1) { + if (key.get_scancode() == KEY_DELETE) + remove_point(_selected_point); + } } } -void CurveTextureEdit::_plot_curve(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_c, const Vector2 &p_d) { +void CurveEditor::on_preset_item_selected(int preset_id) { + ERR_FAIL_COND(preset_id < 0 || preset_id >= PRESET_COUNT); + ERR_FAIL_COND(_curve_ref.is_null()); - Ref<Font> font = get_font("font", "Label"); - - int font_h = font->get_height(); + Curve &curve = **_curve_ref; + Array previous_data = curve.get_data(); - float geometry[4][4]; - float tmp1[4][4]; - float tmp2[4][4]; - float deltas[4][4]; - double x, dx, dx2, dx3; - double y, dy, dy2, dy3; - double d, d2, d3; - int lastx, lasty; - int newx, newy; - int ntimes; - int i, j; + curve.clear_points(); - int xmax = get_size().x; - int ymax = get_size().y - font_h; + switch (preset_id) { + case PRESET_FLAT0: + curve.add_point(Vector2(0, 0)); + curve.add_point(Vector2(1, 0)); + break; - int vsplits = 4; + case PRESET_FLAT1: + curve.add_point(Vector2(0, 1)); + curve.add_point(Vector2(1, 1)); + break; - int zero_ofs = (1.0 - (0.0 - min) / (max - min)) * ymax; + case PRESET_LINEAR: + curve.add_point(Vector2(0, 0), 0, 1); + curve.add_point(Vector2(1, 1), 1, 0); + break; - draw_line(Vector2(0, zero_ofs), Vector2(xmax, zero_ofs), Color(0.8, 0.8, 0.8, 0.15), 2.0); + case PRESET_EASE_IN: + curve.add_point(Vector2(0, 0)); + curve.add_point(Vector2(1, 1), 1.4, 0); + break; - for (int i = 0; i <= vsplits; i++) { - float fofs = float(i) / vsplits; - int yofs = fofs * ymax; - draw_line(Vector2(xmax, yofs), Vector2(xmax - 4, yofs), Color(0.8, 0.8, 0.8, 0.8), 2.0); + case PRESET_EASE_OUT: + curve.add_point(Vector2(0, 0), 0, 1.4); + curve.add_point(Vector2(1, 1)); + break; - String text = rtos((1.0 - fofs) * (max - min) + min); - int ppos = text.find("."); - if (ppos != -1) { - if (text.length() > ppos + 2) - text = text.substr(0, ppos + 2); - } + case PRESET_SMOOTHSTEP: + curve.add_point(Vector2(0, 0)); + curve.add_point(Vector2(1, 1)); + break; - int size = font->get_string_size(text).x; - int xofs = xmax - size - 4; - yofs -= font_h / 2; + default: + break; + } - if (yofs < 2) { - yofs = 2; - } else if (yofs + font_h > ymax - 2) { - yofs = ymax - font_h - 2; - } + push_undo(previous_data); +} - draw_string(font, Vector2(xofs, yofs + font->get_ascent()), text, Color(0.8, 0.8, 0.8, 1)); +void CurveEditor::_curve_changed() { + update(); + // Point count can change in case of undo + if (_selected_point >= _curve_ref->get_point_count()) { + set_selected_point(-1); } +} - /* construct the geometry matrix from the segment */ - for (i = 0; i < 4; i++) { - geometry[i][2] = 0; - geometry[i][3] = 0; - } +void CurveEditor::on_context_menu_item_selected(int action_id) { + switch (action_id) { + case CONTEXT_ADD_POINT: + add_point(_context_click_pos); + break; - geometry[0][0] = (p_a[0] * xmax); - geometry[1][0] = (p_b[0] * xmax); - geometry[2][0] = (p_c[0] * xmax); - geometry[3][0] = (p_d[0] * xmax); - - geometry[0][1] = ((p_a[1] - min) / (max - min) * ymax); - geometry[1][1] = ((p_b[1] - min) / (max - min) * ymax); - geometry[2][1] = ((p_c[1] - min) / (max - min) * ymax); - geometry[3][1] = ((p_d[1] - min) / (max - min) * ymax); - - /* subdivide the curve ntimes (1000) times */ - ntimes = 4 * xmax; - /* ntimes can be adjusted to give a finer or coarser curve */ - d = 1.0 / ntimes; - d2 = d * d; - d3 = d * d * d; - - /* construct a temporary matrix for determining the forward differencing deltas */ - tmp2[0][0] = 0; - tmp2[0][1] = 0; - tmp2[0][2] = 0; - tmp2[0][3] = 1; - tmp2[1][0] = d3; - tmp2[1][1] = d2; - tmp2[1][2] = d; - tmp2[1][3] = 0; - tmp2[2][0] = 6 * d3; - tmp2[2][1] = 2 * d2; - tmp2[2][2] = 0; - tmp2[2][3] = 0; - tmp2[3][0] = 6 * d3; - tmp2[3][1] = 0; - tmp2[3][2] = 0; - tmp2[3][3] = 0; - - /* compose the basis and geometry matrices */ - - static const float CR_basis[4][4] = { - { -0.5, 1.5, -1.5, 0.5 }, - { 1.0, -2.5, 2.0, -0.5 }, - { -0.5, 0.0, 0.5, 0.0 }, - { 0.0, 1.0, 0.0, 0.0 }, - }; - - for (i = 0; i < 4; i++) { - for (j = 0; j < 4; j++) { - tmp1[i][j] = (CR_basis[i][0] * geometry[0][j] + - CR_basis[i][1] * geometry[1][j] + - CR_basis[i][2] * geometry[2][j] + - CR_basis[i][3] * geometry[3][j]); - } + case CONTEXT_REMOVE_POINT: + remove_point(_selected_point); + break; } - /* compose the above results to get the deltas matrix */ - - for (i = 0; i < 4; i++) { - for (j = 0; j < 4; j++) { - deltas[i][j] = (tmp2[i][0] * tmp1[0][j] + - tmp2[i][1] * tmp1[1][j] + - tmp2[i][2] * tmp1[2][j] + - tmp2[i][3] * tmp1[3][j]); +} + +void CurveEditor::open_context_menu(Vector2 pos) { + _context_menu->set_position(pos); + + _context_menu->clear(); + + if (_curve_ref.is_valid()) { + _context_menu->add_item(TTR("Add point"), CONTEXT_ADD_POINT); + if (_selected_point >= 0) { + _context_menu->add_item(TTR("Remove point"), CONTEXT_REMOVE_POINT); } + _context_menu->add_separator(); } - /* extract the x deltas */ - x = deltas[0][0]; - dx = deltas[1][0]; - dx2 = deltas[2][0]; - dx3 = deltas[3][0]; + _context_menu->add_submenu_item(TTR("Load preset"), _presets_menu->get_name()); - /* extract the y deltas */ - y = deltas[0][1]; - dy = deltas[1][1]; - dy2 = deltas[2][1]; - dy3 = deltas[3][1]; + _context_menu->popup(); +} - lastx = CLAMP(x, 0, xmax); - lasty = CLAMP(y, 0, ymax); +int CurveEditor::get_point_at(Vector2 pos) const { + if (_curve_ref.is_null()) + return -1; + const Curve &curve = **_curve_ref; - /* if (fix255) - { - cd->curve[cd->outline][lastx] = lasty; - } - else - { - cd->curve_ptr[cd->outline][lastx] = lasty; - if(gb_debug) printf("bender_plot_curve xmax:%d ymax:%d\n", (int)xmax, (int)ymax); + const float r = _hover_radius * _hover_radius; + + for (int i = 0; i < curve.get_point_count(); ++i) { + Vector2 p = get_view_pos(curve.get_point_pos(i)); + if (p.distance_squared_to(pos) <= r) { + return i; } -*/ - /* loop over the curve */ - for (i = 0; i < ntimes; i++) { - /* increment the x values */ - x += dx; - dx += dx2; - dx2 += dx3; - - /* increment the y values */ - y += dy; - dy += dy2; - dy2 += dy3; - - newx = CLAMP((Math::round(x)), 0, xmax); - newy = CLAMP((Math::round(y)), 0, ymax); - - /* if this point is different than the last one...then draw it */ - if ((lastx != newx) || (lasty != newy)) { -#if 0 - if(fix255) - { - /* use fixed array size (for the curve graph) */ - cd->curve[cd->outline][newx] = newy; - } - else - { - /* use dynamic allocated curve_ptr (for the real curve) */ - cd->curve_ptr[cd->outline][newx] = newy; + } - if(gb_debug) printf("outline: %d cX: %d cY: %d\n", (int)cd->outline, (int)newx, (int)newy); - } -#endif - draw_line(Vector2(lastx, ymax - lasty), Vector2(newx, ymax - newy), Color(0.8, 0.8, 0.8, 0.8), 2.0); + return -1; +} + +int CurveEditor::get_tangent_at(Vector2 pos) const { + if (_curve_ref.is_null() || _selected_point < 0) + return TANGENT_NONE; + + if (_selected_point != 0) { + Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_LEFT); + if (control_pos.distance_to(pos) < _hover_radius) { + return TANGENT_LEFT; } + } - lastx = newx; - lasty = newy; + if (_selected_point != _curve_ref->get_point_count() - 1) { + Vector2 control_pos = get_tangent_view_pos(_selected_point, TANGENT_RIGHT); + if (control_pos.distance_to(pos) < _hover_radius) { + return TANGENT_RIGHT; + } } - int splits = 8; + return TANGENT_NONE; +} - draw_line(Vector2(0, ymax - 1), Vector2(xmax, ymax - 1), Color(0.8, 0.8, 0.8, 0.3), 2.0); +void CurveEditor::add_point(Vector2 pos) { + ERR_FAIL_COND(_curve_ref.is_null()); - for (int i = 0; i <= splits; i++) { - float fofs = float(i) / splits; - draw_line(Vector2(fofs * xmax, ymax), Vector2(fofs * xmax, ymax - 2), Color(0.8, 0.8, 0.8, 0.8), 2.0); + Array prev_data = _curve_ref->get_data(); - String text = rtos(fofs); - int size = font->get_string_size(text).x; - int ofs = fofs * xmax - size * 0.5; - if (ofs < 2) { - ofs = 2; - } else if (ofs + size > xmax - 2) { - ofs = xmax - size - 2; - } + Vector2 point_pos = get_world_pos(pos); + if (point_pos.y < 0.0) + point_pos.y = 0.0; + else if (point_pos.y > 1.0) + point_pos.y = 1.0; - draw_string(font, Vector2(ofs, ymax + font->get_ascent()), text, Color(0.8, 0.8, 0.8, 1)); - } + _curve_ref->add_point(point_pos); + + push_undo(prev_data); } -void CurveTextureEdit::_notification(int p_what) { +void CurveEditor::remove_point(int index) { + ERR_FAIL_COND(_curve_ref.is_null()); - if (p_what == NOTIFICATION_DRAW) { + Array prev_data = _curve_ref->get_data(); - Ref<Font> font = get_font("font", "Label"); + _curve_ref->remove_point(index); - int font_h = font->get_height(); + if (index == _selected_point) + set_selected_point(-1); - draw_style_box(get_stylebox("bg", "Tree"), Rect2(Point2(), get_size())); + push_undo(prev_data); +} - int w = get_size().x; - int h = get_size().y; +void CurveEditor::set_selected_point(int index) { + if (index != _selected_point) { + _selected_point = index; + update(); + } +} - Vector2 prev = Vector2(0, 0); - Vector2 prev2 = Vector2(0, 0); +void CurveEditor::set_hover_point_index(int index) { + if (index != _hover_point) { + _hover_point = index; + update(); + } +} - for (int i = -1; i < points.size(); i++) { +void CurveEditor::push_undo(Array previous_curve_data) { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - Vector2 next; - Vector2 next2; - if (i + 1 >= points.size()) { - next = Vector2(1, 0); - } else { - next = Vector2(points[i + 1].offset, points[i + 1].height); - } + ur->create_action(TTR("Modify Curve")); + ur->add_do_method(*_curve_ref, "_set_data", _curve_ref->get_data()); + ur->add_undo_method(*_curve_ref, "_set_data", previous_curve_data); - if (i + 2 >= points.size()) { - next2 = Vector2(1, 0); - } else { - next2 = Vector2(points[i + 2].offset, points[i + 2].height); - } + // This boolean is to prevent commit_action from executing the do method, + // because at this point it's already done, there is no point in doing it twice + _curve_ref->_disable_set_data = true; + ur->commit_action(); + _curve_ref->_disable_set_data = false; +} - /*if (i==-1 && prev.offset==next.offset) { - prev=next; - continue; - }*/ +void CurveEditor::update_view_transform() { + Vector2 control_size = get_size(); + const real_t margin = 24; - _plot_curve(prev2, prev, next, next2); + _world_rect = Rect2(Curve::MIN_X, 0, Curve::MAX_X, 1); + Vector2 wm = Vector2(margin, margin) / control_size; + _world_rect.position -= wm; + _world_rect.size += 2.0 * wm; - prev2 = prev; - prev = next; - } + _world_to_view = Transform2D(); + _world_to_view.translate(-_world_rect.position - Vector2(0, _world_rect.size.y)); + _world_to_view.scale(Vector2(control_size.x, -control_size.y) / _world_rect.size); +} - Vector2 size = get_size(); - size.y -= font_h; - for (int i = 0; i < points.size(); i++) { +Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const { - Color col = i == grabbed ? Color(1, 0.0, 0.0, 0.9) : Color(1, 1, 1, 0.8); + Vector2 dir; + if (tangent == TANGENT_LEFT) + dir = -Vector2(1, _curve_ref->get_point_left_tangent(i)); + else + dir = Vector2(1, _curve_ref->get_point_right_tangent(i)); - float h = (points[i].height - min) / (max - min); - draw_rect(Rect2(Vector2(points[i].offset, 1.0 - h) * size - Vector2(2, 2), Vector2(5, 5)), col); - } + Vector2 point_pos = get_view_pos(_curve_ref->get_point_pos(i)); + Vector2 control_pos = get_view_pos(_curve_ref->get_point_pos(i) + dir); - /* if (grabbed!=-1) { + return point_pos + _tangents_length * (control_pos - point_pos).normalized(); +} - draw_rect(Rect2(total_w+3,0,h,h),points[grabbed].color); - } -*/ - if (has_focus()) { +Vector2 CurveEditor::get_view_pos(Vector2 world_pos) const { + return _world_to_view.xform(world_pos); +} + +Vector2 CurveEditor::get_world_pos(Vector2 view_pos) const { + return _world_to_view.affine_inverse().xform(view_pos); +} - draw_line(Vector2(-1, -1), Vector2(w + 1, -1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(w + 1, -1), Vector2(w + 1, h + 1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(w + 1, h + 1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(-1, -1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6)); +// Uses non-baked points, but takes advantage of ordered iteration to be faster +template <typename T> +static void plot_curve_accurate(const Curve &curve, float step, T plot_func) { + + if (curve.get_point_count() <= 1) { + // Not enough points to make a curve, so it's just a straight line + float y = curve.interpolate(0); + plot_func(Vector2(0, y), Vector2(1.f, y), true); + + } else { + Vector2 first_point = curve.get_point_pos(0); + Vector2 last_point = curve.get_point_pos(curve.get_point_count() - 1); + + // Edge lines + plot_func(Vector2(0, first_point.y), first_point, false); + plot_func(Vector2(Curve::MAX_X, last_point.y), last_point, false); + + // Draw section by section, so that we get maximum precision near points. + // It's an accurate representation, but slower than using the baked one. + for (int i = 1; i < curve.get_point_count(); ++i) { + Vector2 a = curve.get_point_pos(i - 1); + Vector2 b = curve.get_point_pos(i); + + Vector2 pos = a; + Vector2 prev_pos = a; + + float len = b.x - a.x; + //float step = 4.f / view_size.x; + + for (float x = step; x < len; x += step) { + pos.x = a.x + x; + pos.y = curve.interpolate_local_nocheck(i - 1, x); + plot_func(prev_pos, pos, true); + prev_pos = pos; + } + + plot_func(prev_pos, b, true); } } } -Size2 CurveTextureEdit::get_minimum_size() const { +struct CanvasItemPlotCurve { - return Vector2(64, 64); -} + CanvasItem &ci; + Color color1; + Color color2; -void CurveTextureEdit::set_range(float p_min, float p_max) { - max = p_max; - min = p_min; - update(); -} + CanvasItemPlotCurve(CanvasItem &p_ci, Color p_color1, Color p_color2) + : ci(p_ci), color1(p_color1), color2(p_color2) {} -void CurveTextureEdit::set_points(const Vector<Vector2> &p_points) { + void operator()(Vector2 pos0, Vector2 pos1, bool in_definition) { + ci.draw_line(pos0, pos1, in_definition ? color1 : color2); + } +}; + +void CurveEditor::_draw() { + if (_curve_ref.is_null()) + return; + Curve &curve = **_curve_ref; + + update_view_transform(); + + // Background + + Vector2 view_size = get_rect().size; + draw_style_box(get_stylebox("bg", "Tree"), Rect2(Point2(), view_size)); + + // Grid + + draw_set_transform_matrix(_world_to_view); + + Vector2 min_edge = get_world_pos(Vector2(0, view_size.y)); + Vector2 max_edge = get_world_pos(Vector2(view_size.x, 0)); + + const Color grid_color0(0, 0, 0, 0.5); + const Color grid_color1(0, 0, 0, 0.15); + draw_line(Vector2(min_edge.x, 0), Vector2(max_edge.x, 0), grid_color0); + draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), grid_color0); + draw_line(Vector2(1, max_edge.y), Vector2(1, min_edge.y), grid_color0); + draw_line(Vector2(max_edge.x, 1), Vector2(min_edge.x, 1), grid_color0); - points.clear(); - for (int i = 0; i < p_points.size(); i++) { - Point p; - p.offset = p_points[i].x; - p.height = p_points[i].y; - points.push_back(p); + const Vector2 grid_step(0.25, 0.5); + + for (real_t x = 0; x < 1.0; x += grid_step.x) { + draw_line(Vector2(x, min_edge.y), Vector2(x, max_edge.y), grid_color1); + } + for (real_t y = 0; y < 1.0; y += grid_step.y) { + draw_line(Vector2(min_edge.x, y), Vector2(max_edge.x, y), grid_color1); } - points.sort(); - update(); -} + // Markings -Vector<Vector2> CurveTextureEdit::get_points() const { - Vector<Vector2> ret; - for (int i = 0; i < points.size(); i++) - ret.push_back(Vector2(points[i].offset, points[i].height)); - return ret; -} + draw_set_transform_matrix(Transform2D()); -void CurveTextureEdit::_bind_methods() { + Ref<Font> font = get_font("font", "Label"); + const Color text_color(1, 1, 1, 0.3); - ClassDB::bind_method(D_METHOD("_gui_input"), &CurveTextureEdit::_gui_input); + draw_string(font, get_view_pos(Vector2(0, 0)), "0.0", text_color); - ADD_SIGNAL(MethodInfo("curve_changed")); -} + draw_string(font, get_view_pos(Vector2(0.25, 0)), "0.25", text_color); + draw_string(font, get_view_pos(Vector2(0.5, 0)), "0.5", text_color); + draw_string(font, get_view_pos(Vector2(0.75, 0)), "0.75", text_color); + draw_string(font, get_view_pos(Vector2(1, 0)), "1.0", text_color); -CurveTextureEdit::CurveTextureEdit() { + draw_string(font, get_view_pos(Vector2(0, 0.5)), "0.5", text_color); + draw_string(font, get_view_pos(Vector2(0, 1)), "1.0", text_color); - grabbed = -1; - grabbing = false; - max = 1; - min = 0; - set_focus_mode(FOCUS_ALL); -} + // Draw tangents for current point -void CurveTextureEditorPlugin::_curve_settings_changed() { + if (_selected_point >= 0) { - if (!curve_texture_ref.is_valid()) - return; - curve_editor->set_points(Variant(curve_texture_ref->get_points())); - curve_editor->set_range(curve_texture_ref->get_min(), curve_texture_ref->get_max()); -} + const Color tangent_color(0.5, 0.5, 1, 1); + + int i = _selected_point; + Vector2 pos = curve.get_point_pos(i); + + if (i != 0) { + Vector2 control_pos = get_tangent_view_pos(i, TANGENT_LEFT); + draw_line(get_view_pos(pos), control_pos, tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), tangent_color); + } -CurveTextureEditorPlugin::CurveTextureEditorPlugin(EditorNode *p_node) { + if (i != curve.get_point_count() - 1) { + Vector2 control_pos = get_tangent_view_pos(i, TANGENT_RIGHT); + draw_line(get_view_pos(pos), control_pos, tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), tangent_color); + } + } - editor = p_node; - curve_editor = memnew(CurveTextureEdit); + // Draw lines - curve_button = editor->add_bottom_panel_item("CurveTexture", curve_editor); + draw_set_transform_matrix(_world_to_view); - curve_button->hide(); - curve_editor->set_custom_minimum_size(Size2(100, 128 * EDSCALE)); - curve_editor->hide(); - curve_editor->connect("curve_changed", this, "curve_changed"); -} + const Color line_color(1, 1, 1, 0.85); + const Color edge_line_color(1, 1, 1, 0.4); + + CanvasItemPlotCurve plot_func(*this, line_color, edge_line_color); + plot_curve_accurate(curve, 4.f / view_size.x, plot_func); + + /*// TEST draw baked curve + { + Vector2 pos = Vector2(0, curve.interpolate_baked(0)); + Vector2 prev_pos = pos; -void CurveTextureEditorPlugin::edit(Object *p_object) { + float len = 1.0; + float step = 4.f / view_size.x; - if (curve_texture_ref.is_valid()) { - curve_texture_ref->disconnect("changed", this, "_curve_settings_changed"); + for(float x = step; x < len; x += step) { + pos.x = x; + pos.y = curve.interpolate_baked(x); + draw_line(get_point_view_pos(prev_pos), get_point_view_pos(pos), Color(0,1,0)); + prev_pos = pos; + } + + draw_line(get_point_view_pos(prev_pos), get_point_view_pos(Vector2(1, curve.interpolate_baked(1))), Color(0,1,0)); + }//*/ + + // Draw points + + draw_set_transform_matrix(Transform2D()); + + const Color point_color(1, 1, 1); + const Color selected_point_color(1, 0.5, 0.5); + + for (int i = 0; i < curve.get_point_count(); ++i) { + Vector2 pos = curve.get_point_pos(i); + draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(3), i == _selected_point ? selected_point_color : point_color); + // TODO Circles are prettier. Needs a fix! Or a texture + //draw_circle(pos, 2, point_color); } - CurveTexture *curve_texture = p_object->cast_to<CurveTexture>(); - if (!curve_texture) - return; - curve_texture_ref = Ref<CurveTexture>(curve_texture); - curve_editor->set_points(Variant(curve_texture_ref->get_points())); - curve_editor->set_range(curve_texture_ref->get_min(), curve_texture_ref->get_max()); - if (!curve_texture_ref->is_connected("changed", this, "_curve_settings_changed")) { - curve_texture_ref->connect("changed", this, "_curve_settings_changed"); + + // Hover + + if (_hover_point != -1) { + const Color hover_color = line_color; + Vector2 pos = curve.get_point_pos(_hover_point); + stroke_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(_hover_radius), hover_color); } } -bool CurveTextureEditorPlugin::handles(Object *p_object) const { +// TODO That should be part of the drawing API... +void CurveEditor::stroke_rect(Rect2 rect, Color color) { + + // a---b + // | | + // c---d + Vector2 a(rect.position); + Vector2 b(rect.position.x + rect.size.x, rect.position.y); + Vector2 c(rect.position.x, rect.position.y + rect.size.y); + Vector2 d(rect.position + rect.size); + + draw_line(a, b, color); + draw_line(b, d, color); + draw_line(d, c, color); + draw_line(c, a, color); +} - return p_object->is_class("CurveTexture"); +void CurveEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_gui_input"), &CurveEditor::on_gui_input); + ClassDB::bind_method(D_METHOD("_on_preset_item_selected"), &CurveEditor::on_preset_item_selected); + ClassDB::bind_method(D_METHOD("_curve_changed"), &CurveEditor::_curve_changed); + ClassDB::bind_method(D_METHOD("_on_context_menu_item_selected"), &CurveEditor::on_context_menu_item_selected); } -void CurveTextureEditorPlugin::make_visible(bool p_visible) { +//--------------- - if (p_visible) { - curve_button->show(); - editor->make_bottom_panel_item_visible(curve_editor); +CurveEditorPlugin::CurveEditorPlugin(EditorNode *p_node) { + _editor_node = p_node; - } else { + _view = memnew(CurveEditor); + _view->set_custom_minimum_size(Size2(100, 128 * EDSCALE)); + _view->hide(); - curve_button->hide(); - if (curve_editor->is_visible_in_tree()) - editor->hide_bottom_panel(); - } + _toggle_button = _editor_node->add_bottom_panel_item(get_name(), _view); + _toggle_button->hide(); } -void CurveTextureEditorPlugin::_curve_changed() { +CurveEditorPlugin::~CurveEditorPlugin() { +} - if (curve_texture_ref.is_valid()) { +void CurveEditorPlugin::edit(Object *p_object) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<Curve> curve_ref; - Vector<Vector2> points = curve_editor->get_points(); - PoolVector<Vector2> ppoints = Variant(points); + if (_current_ref.is_valid()) { + CurveTexture *ct = _current_ref->cast_to<CurveTexture>(); + if (ct) + ct->disconnect(CoreStringNames::get_singleton()->changed, this, "_curve_texture_changed"); + } + + if (p_object) { + Resource *res = p_object->cast_to<Resource>(); + ERR_FAIL_COND(res == NULL); + ERR_FAIL_COND(!handles(p_object)); + + _current_ref = Ref<Resource>(p_object->cast_to<Resource>()); + + if (_current_ref.is_valid()) { + Curve *curve = _current_ref->cast_to<Curve>(); + if (curve) + curve_ref = Ref<Curve>(curve); + else { + CurveTexture *ct = _current_ref->cast_to<CurveTexture>(); + if (ct) { + ct->connect(CoreStringNames::get_singleton()->changed, this, "_curve_texture_changed"); + curve_ref = ct->get_curve(); + } + } + } - ur->create_action(TTR("Modify Curve"), UndoRedo::MERGE_ENDS); - ur->add_do_method(this, "undo_redo_curve_texture", ppoints); - ur->add_undo_method(this, "undo_redo_curve_texture", curve_texture_ref->get_points()); - ur->commit_action(); + } else { + _current_ref = Ref<Resource>(); } + + _view->set_curve(curve_ref); } -void CurveTextureEditorPlugin::_undo_redo_curve_texture(const PoolVector<Vector2> &points) { +bool CurveEditorPlugin::handles(Object *p_object) const { + // Both handled so that we can keep the curve editor open + return p_object->cast_to<Curve>() || p_object->cast_to<CurveTexture>(); +} - curve_texture_ref->set_points(points); - curve_editor->set_points(Variant(curve_texture_ref->get_points())); - curve_editor->update(); +void CurveEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + _toggle_button->show(); + _editor_node->make_bottom_panel_item_visible(_view); + } else { + _toggle_button->hide(); + if (_view->is_visible_in_tree()) + _editor_node->hide_bottom_panel(); + } } -CurveTextureEditorPlugin::~CurveTextureEditorPlugin() { +void CurveEditorPlugin::_curve_texture_changed() { + // If the curve is shown indirectly as a CurveTexture is edited, + // we need to monitor when the curve property gets assigned + CurveTexture *ct = _current_ref->cast_to<CurveTexture>(); + if (ct) { + _view->set_curve(ct->get_curve()); + } } -void CurveTextureEditorPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("curve_changed"), &CurveTextureEditorPlugin::_curve_changed); - ClassDB::bind_method(D_METHOD("_curve_settings_changed"), &CurveTextureEditorPlugin::_curve_settings_changed); - ClassDB::bind_method(D_METHOD("undo_redo_curve_texture", "points"), &CurveTextureEditorPlugin::_undo_redo_curve_texture); +void CurveEditorPlugin::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_curve_texture_changed"), &CurveEditorPlugin::_curve_texture_changed); } diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index 4e75ba407c..0ed4ee3517 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -27,69 +27,119 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ + #ifndef CURVE_EDITOR_PLUGIN_H #define CURVE_EDITOR_PLUGIN_H #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "scene/resources/curve.h" -class CurveTextureEdit : public Control { +// Edits a y(x) curve +class CurveEditor : public Control { + GDCLASS(CurveEditor, Control) +public: + CurveEditor(); - GDCLASS(CurveTextureEdit, Control); + Size2 get_minimum_size() const; - struct Point { + void set_curve(Ref<Curve> curve); - float offset; - float height; - bool operator<(const Point &p_ponit) const { - return offset < p_ponit.offset; - } + enum PresetID { + PRESET_FLAT0 = 0, + PRESET_FLAT1, + PRESET_LINEAR, + PRESET_EASE_IN, + PRESET_EASE_OUT, + PRESET_SMOOTHSTEP, + PRESET_COUNT }; - bool grabbing; - int grabbed; - Vector<Point> points; - float max, min; + enum ContextAction { + CONTEXT_ADD_POINT = 0, + CONTEXT_REMOVE_POINT + }; - void _plot_curve(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_c, const Vector2 &p_d); + enum TangentIndex { + TANGENT_NONE = -1, + TANGENT_LEFT = 0, + TANGENT_RIGHT = 1 + }; protected: - void _gui_input(const Ref<InputEvent> &p_event); void _notification(int p_what); + static void _bind_methods(); -public: - void set_range(float p_min, float p_max); - void set_points(const Vector<Vector2> &p_points); - Vector<Vector2> get_points() const; - virtual Size2 get_minimum_size() const; - CurveTextureEdit(); +private: + void on_gui_input(const Ref<InputEvent> &p_event); + void on_preset_item_selected(int preset_id); + void _curve_changed(); + void on_context_menu_item_selected(int action_id); + + void open_context_menu(Vector2 pos); + int get_point_at(Vector2 pos) const; + int get_tangent_at(Vector2 pos) const; + void add_point(Vector2 pos); + void remove_point(int index); + void set_selected_point(int index); + void set_hover_point_index(int index); + void push_undo(Array previous_curve_data); + void update_view_transform(); + + Vector2 get_tangent_view_pos(int i, TangentIndex tangent) const; + Vector2 get_view_pos(Vector2 world_pos) const; + Vector2 get_world_pos(Vector2 view_pos) const; + + void _draw(); + + void stroke_rect(Rect2 rect, Color color); + +private: + Rect2 _world_rect; + Transform2D _world_to_view; + + Ref<Curve> _curve_ref; + PopupMenu *_context_menu; + PopupMenu *_presets_menu; + + Array _undo_data; + bool _has_undo_data; + bool _undo_no_commit; + + Vector2 _context_click_pos; + int _selected_point; + int _hover_point; + int _selected_tangent; + bool _dragging; + + // Constant + float _hover_radius; + float _tangents_length; }; -class CurveTextureEditorPlugin : public EditorPlugin { - - GDCLASS(CurveTextureEditorPlugin, EditorPlugin); +class CurveEditorPlugin : public EditorPlugin { + GDCLASS(CurveEditorPlugin, EditorPlugin) +public: + CurveEditorPlugin(EditorNode *p_node); + ~CurveEditorPlugin(); - CurveTextureEdit *curve_editor; - Ref<CurveTexture> curve_texture_ref; - EditorNode *editor; - ToolButton *curve_button; + String get_name() const { return "Curve"; } + bool has_main_screen() const { return false; } + void edit(Object *p_object); + bool handles(Object *p_object) const; + void make_visible(bool p_visible); -protected: +private: static void _bind_methods(); - void _curve_changed(); - void _undo_redo_curve_texture(const PoolVector<Vector2> &points); - void _curve_settings_changed(); -public: - virtual String get_name() const { return "CurveTexture"; } - bool has_main_screen() const { return false; } - virtual void edit(Object *p_node); - virtual bool handles(Object *p_node) const; - virtual void make_visible(bool p_visible); + void _curve_texture_changed(); - CurveTextureEditorPlugin(EditorNode *p_node); - ~CurveTextureEditorPlugin(); +private: + CurveEditor *_view; + Ref<Resource> _current_ref; + EditorNode *_editor_node; + ToolButton *_toggle_button; }; #endif // CURVE_EDITOR_PLUGIN_H diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 7c8ee97f22..bad88979ac 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -188,7 +188,7 @@ void ShaderTextEditor::_validate_script() { if (err != OK) { String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); set_error(error_text); - get_text_edit()->set_line_as_marked(sl.get_error_line(), true); + get_text_edit()->set_line_as_marked(sl.get_error_line() - 1, true); } else { for (int i = 0; i < get_text_edit()->get_line_count(); i++) diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 676e50d61c..c4fe15e61c 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -61,9 +61,21 @@ void TextureEditor::_notification(int p_what) { tex_height = texture->get_height() * tex_width / texture->get_width(); } + // Prevent the texture from being unpreviewable after the rescale, so that we can still see something + if (tex_height <= 0) + tex_height = 1; + if (tex_width <= 0) + tex_width = 1; + int ofs_x = (size.width - tex_width) / 2; int ofs_y = (size.height - tex_height) / 2; + if (texture->cast_to<CurveTexture>()) { + // In the case of CurveTextures we know they are 1 in height, so fill the preview to see the gradient + ofs_y = 0; + tex_height = size.height; + } + draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height)); Ref<Font> font = get_font("font", "Label"); diff --git a/main/input_default.cpp b/main/input_default.cpp index e488438059..bde1e84926 100644 --- a/main/input_default.cpp +++ b/main/input_default.cpp @@ -874,6 +874,8 @@ void InputDefault::joy_axis(int p_device, int p_axis, const JoyAxis &p_value) { _THREAD_SAFE_METHOD_; + ERR_FAIL_INDEX(p_axis, JOY_AXIS_MAX); + Joypad &joy = joy_names[p_device]; if (joy.last_axis[p_axis] == p_value.value) { diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp index 5e3ce31dd6..adf3c8edc4 100644 --- a/modules/gdscript/gd_editor.cpp +++ b/modules/gdscript/gd_editor.cpp @@ -1276,7 +1276,7 @@ static void _find_identifiers_in_class(GDCompletionContext &context, bool p_stat } } List<MethodInfo> methods; - ClassDB::get_method_list(type, &methods); + ClassDB::get_method_list(type, &methods, false, true); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { if (E->get().name.begins_with("_")) continue; @@ -1643,7 +1643,7 @@ static void _find_type_arguments(GDCompletionContext &context, const GDParser::N } else { //regular method - if (p_method.operator String() == "connect") { + if (p_method.operator String() == "connect" || (p_method.operator String() == "emit_signal" && p_argidx == 0)) { if (p_argidx == 0) { List<MethodInfo> sigs; @@ -2251,7 +2251,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base } List<MethodInfo> mi; - ClassDB::get_method_list(t.obj_type, &mi); + ClassDB::get_method_list(t.obj_type, &mi, false, true); for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) { if (E->get().name.begins_with("_")) diff --git a/platform/windows/godot.ico b/platform/windows/godot.ico Binary files differindex fd5c28944f..dd611e07da 100644 --- a/platform/windows/godot.ico +++ b/platform/windows/godot.ico diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 3d9e64ae79..cef473dcdf 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -61,7 +61,7 @@ void AudioStreamPlayer2D::_mix_audio() { for (int j = 0; j < buffer_size; j++) { - target[j] = buffer[j] * vol; + target[j] += buffer[j] * vol; vol += vol_inc; } @@ -76,8 +76,8 @@ void AudioStreamPlayer2D::_mix_audio() { for (int j = 0; j < buffer_size; j++) { AudioFrame frame = buffer[j] * vol; - targets[0][j] = frame; - targets[1][j] = frame; + targets[0][j] += frame; + targets[1][j] += frame; vol += vol_inc; } @@ -93,9 +93,9 @@ void AudioStreamPlayer2D::_mix_audio() { for (int j = 0; j < buffer_size; j++) { AudioFrame frame = buffer[j] * vol; - targets[0][j] = frame; - targets[1][j] = frame; - targets[2][j] = frame; + targets[0][j] += frame; + targets[1][j] += frame; + targets[2][j] += frame; vol += vol_inc; } diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index 89b89a50d8..189dd66a26 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -39,6 +39,196 @@ #include "scene/scene_string_names.h" #include "servers/visual_server.h" +Mutex *CanvasItemMaterial::material_mutex = NULL; +SelfList<CanvasItemMaterial>::List CanvasItemMaterial::dirty_materials; +Map<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData> CanvasItemMaterial::shader_map; + +void CanvasItemMaterial::init_shaders() { + +#ifndef NO_THREADS + material_mutex = Mutex::create(); +#endif +} + +void CanvasItemMaterial::finish_shaders() { + +#ifndef NO_THREADS + memdelete(material_mutex); +#endif +} + +void CanvasItemMaterial::_update_shader() { + + dirty_materials.remove(&element); + + MaterialKey mk = _compute_key(); + if (mk.key == current_key.key) + return; //no update required in the end + + if (shader_map.has(current_key)) { + shader_map[current_key].users--; + if (shader_map[current_key].users == 0) { + //deallocate shader, as it's no longer in use + VS::get_singleton()->free(shader_map[current_key].shader); + shader_map.erase(current_key); + } + } + + current_key = mk; + + if (shader_map.has(mk)) { + + VS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader); + shader_map[mk].users++; + return; + } + + //must create a shader! + + String code = "shader_type canvas_item;\nrender_mode "; + switch (blend_mode) { + case BLEND_MODE_MIX: code += "blend_mix"; break; + case BLEND_MODE_ADD: code += "blend_add"; break; + case BLEND_MODE_SUB: code += "blend_sub"; break; + case BLEND_MODE_MUL: code += "blend_mul"; break; + case BLEND_MODE_PREMULT_ALPHA: code += "blend_premul_alpha"; break; + } + + switch (light_mode) { + case LIGHT_MODE_NORMAL: break; + case LIGHT_MODE_UNSHADED: code += "unshaded"; break; + case LIGHT_MODE_LIGHT_ONLY: code += "light_only"; break; + } + code += ";\n"; //thats it. + + ShaderData shader_data; + shader_data.shader = VS::get_singleton()->shader_create(); + shader_data.users = 1; + + VS::get_singleton()->shader_set_code(shader_data.shader, code); + + shader_map[mk] = shader_data; + + VS::get_singleton()->material_set_shader(_get_material(), shader_data.shader); +} + +void CanvasItemMaterial::flush_changes() { + + if (material_mutex) + material_mutex->lock(); + + while (dirty_materials.first()) { + + dirty_materials.first()->self()->_update_shader(); + } + + if (material_mutex) + material_mutex->unlock(); +} + +void CanvasItemMaterial::_queue_shader_change() { + + if (material_mutex) + material_mutex->lock(); + + if (!element.in_list()) { + dirty_materials.add(&element); + } + + if (material_mutex) + material_mutex->unlock(); +} + +bool CanvasItemMaterial::_is_shader_dirty() const { + + bool dirty = false; + + if (material_mutex) + material_mutex->lock(); + + dirty = element.in_list(); + + if (material_mutex) + material_mutex->unlock(); + + return dirty; +} +void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) { + + blend_mode = p_blend_mode; + _queue_shader_change(); +} + +CanvasItemMaterial::BlendMode CanvasItemMaterial::get_blend_mode() const { + return blend_mode; +} + +void CanvasItemMaterial::set_light_mode(LightMode p_light_mode) { + + light_mode = p_light_mode; + _queue_shader_change(); +} + +CanvasItemMaterial::LightMode CanvasItemMaterial::get_light_mode() const { + + return light_mode; +} + +void CanvasItemMaterial::_validate_property(PropertyInfo &property) const { +} + +void CanvasItemMaterial::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_blend_mode", "blend_mode"), &CanvasItemMaterial::set_blend_mode); + ClassDB::bind_method(D_METHOD("get_blend_mode"), &CanvasItemMaterial::get_blend_mode); + + ClassDB::bind_method(D_METHOD("set_light_mode", "light_mode"), &CanvasItemMaterial::set_light_mode); + ClassDB::bind_method(D_METHOD("get_light_mode"), &CanvasItemMaterial::get_light_mode); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Sub,Mul,Premult Alpha"), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mode", PROPERTY_HINT_ENUM, "Normal,Unshaded,Light Only"), "set_light_mode", "get_light_mode"); + + BIND_CONSTANT(BLEND_MODE_MIX); + BIND_CONSTANT(BLEND_MODE_ADD); + BIND_CONSTANT(BLEND_MODE_SUB); + BIND_CONSTANT(BLEND_MODE_MUL); + BIND_CONSTANT(BLEND_MODE_PREMULT_ALPHA); + BIND_CONSTANT(LIGHT_MODE_NORMAL); + BIND_CONSTANT(LIGHT_MODE_UNSHADED); + BIND_CONSTANT(LIGHT_MODE_LIGHT_ONLY); +} + +CanvasItemMaterial::CanvasItemMaterial() + : element(this) { + + blend_mode = BLEND_MODE_MIX; + light_mode = LIGHT_MODE_NORMAL; + + current_key.key = 0; + current_key.invalid_key = 1; + _queue_shader_change(); +} + +CanvasItemMaterial::~CanvasItemMaterial() { + + if (material_mutex) + material_mutex->lock(); + + if (shader_map.has(current_key)) { + shader_map[current_key].users--; + if (shader_map[current_key].users == 0) { + //deallocate shader, as it's no longer in use + VS::get_singleton()->free(shader_map[current_key].shader); + shader_map.erase(current_key); + } + + VS::get_singleton()->material_set_shader(_get_material(), RID()); + } + + if (material_mutex) + material_mutex->unlock(); +} + /////////////////////////////////////////////////////////////////// bool CanvasItem::is_visible_in_tree() const { @@ -665,7 +855,7 @@ bool CanvasItem::is_draw_behind_parent_enabled() const { return behind; } -void CanvasItem::set_material(const Ref<ShaderMaterial> &p_material) { +void CanvasItem::set_material(const Ref<Material> &p_material) { material = p_material; RID rid; @@ -686,7 +876,7 @@ bool CanvasItem::get_use_parent_material() const { return use_parent_material; } -Ref<ShaderMaterial> CanvasItem::get_material() const { +Ref<Material> CanvasItem::get_material() const { return material; } @@ -788,8 +978,8 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_world_2d"), &CanvasItem::get_world_2d); //ClassDB::bind_method(D_METHOD("get_viewport"),&CanvasItem::get_viewport); - ClassDB::bind_method(D_METHOD("set_material", "material:ShaderMaterial"), &CanvasItem::set_material); - ClassDB::bind_method(D_METHOD("get_material:ShaderMaterial"), &CanvasItem::get_material); + ClassDB::bind_method(D_METHOD("set_material", "material:Material"), &CanvasItem::set_material); + ClassDB::bind_method(D_METHOD("get_material:Material"), &CanvasItem::get_material); ClassDB::bind_method(D_METHOD("set_use_parent_material", "enable"), &CanvasItem::set_use_parent_material); ClassDB::bind_method(D_METHOD("get_use_parent_material"), &CanvasItem::get_use_parent_material); @@ -815,7 +1005,7 @@ void CanvasItem::_bind_methods() { ADD_PROPERTYNO(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask"); ADD_GROUP("Material", ""); - ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"), "set_material", "get_material"); + ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial"), "set_material", "get_material"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "use_parent_material"), "set_use_parent_material", "get_use_parent_material"); //exporting these two things doesn't really make much sense i think //ADD_PROPERTY( PropertyInfo(Variant::BOOL,"transform/toplevel"), "set_as_toplevel","is_set_as_toplevel") ; diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 906a08d219..bffc171fc1 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -42,6 +42,92 @@ class Font; class StyleBox; +class CanvasItemMaterial : public Material { + + GDCLASS(CanvasItemMaterial, Material) + +public: + enum BlendMode { + BLEND_MODE_MIX, + BLEND_MODE_ADD, + BLEND_MODE_SUB, + BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA + }; + + enum LightMode { + LIGHT_MODE_NORMAL, + LIGHT_MODE_UNSHADED, + LIGHT_MODE_LIGHT_ONLY + }; + +private: + union MaterialKey { + + struct { + uint32_t blend_mode : 4; + uint32_t light_mode : 4; + uint32_t invalid_key : 1; + }; + + uint32_t key; + + bool operator<(const MaterialKey &p_key) const { + return key < p_key.key; + } + }; + + struct ShaderData { + RID shader; + int users; + }; + + static Map<MaterialKey, ShaderData> shader_map; + + MaterialKey current_key; + + _FORCE_INLINE_ MaterialKey _compute_key() const { + + MaterialKey mk; + mk.key = 0; + mk.blend_mode = blend_mode; + mk.light_mode = light_mode; + return mk; + } + + static Mutex *material_mutex; + static SelfList<CanvasItemMaterial>::List dirty_materials; + SelfList<CanvasItemMaterial> element; + + void _update_shader(); + _FORCE_INLINE_ void _queue_shader_change(); + _FORCE_INLINE_ bool _is_shader_dirty() const; + + BlendMode blend_mode; + LightMode light_mode; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + void set_blend_mode(BlendMode p_blend_mode); + BlendMode get_blend_mode() const; + + void set_light_mode(LightMode p_light_mode); + LightMode get_light_mode() const; + + static void init_shaders(); + static void finish_shaders(); + static void flush_changes(); + + CanvasItemMaterial(); + virtual ~CanvasItemMaterial(); +}; + +VARIANT_ENUM_CAST(CanvasItemMaterial::BlendMode) +VARIANT_ENUM_CAST(CanvasItemMaterial::LightMode) + class CanvasItem : public Node { GDCLASS(CanvasItem, Node); @@ -83,7 +169,7 @@ private: bool notify_local_transform; bool notify_transform; - Ref<ShaderMaterial> material; + Ref<Material> material; mutable Transform2D global_transform; mutable bool global_invalid; @@ -203,8 +289,8 @@ public: RID get_canvas() const; Ref<World2D> get_world_2d() const; - void set_material(const Ref<ShaderMaterial> &p_material); - Ref<ShaderMaterial> get_material() const; + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; void set_use_parent_material(bool p_use_parent_material); bool get_use_parent_material() const; diff --git a/scene/2d/particles_2d.cpp b/scene/2d/particles_2d.cpp index cbecf00d52..beff247264 100644 --- a/scene/2d/particles_2d.cpp +++ b/scene/2d/particles_2d.cpp @@ -349,7 +349,7 @@ void Particles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order"); ADD_GROUP("Process Material", "process_"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticlesMaterial,ShaderMaterial"), "set_process_material", "get_process_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticlesMaterial"), "set_process_material", "get_process_material"); ADD_GROUP("Textures", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_map", "get_normal_map"); diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index 5b5bce342d..e755b1480b 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -98,7 +98,7 @@ void MeshInstance::_get_property_list(List<PropertyInfo> *p_list) const { if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "Material")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "material/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial")); } } } diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index 3394c1e204..722b698b75 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -303,7 +303,7 @@ void Particles::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,View Depth"), "set_draw_order", "get_draw_order"); ADD_GROUP("Process Material", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticlesMaterial,ShaderMaterial"), "set_process_material", "get_process_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticlesMaterial"), "set_process_material", "get_process_material"); ADD_GROUP("Draw Passes", "draw_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_passes", PROPERTY_HINT_RANGE, "0," + itos(MAX_DRAW_PASSES) + ",1"), "set_draw_passes", "get_draw_passes"); for (int i = 0; i < MAX_DRAW_PASSES; i++) { @@ -571,7 +571,7 @@ void ParticlesMaterial::_update_shader() { code += "\n"; code += " uint base_number=NUMBER/uint(trail_divisor);\n"; - code += " uint alt_seed=hash(base_number+uint(1));\n"; + code += " uint alt_seed=hash(base_number+uint(1)+RANDOM_SEED);\n"; code += " float angle_rand=rand_from_seed(alt_seed);\n"; code += " float scale_rand=rand_from_seed(alt_seed);\n"; code += " float hue_rot_rand=rand_from_seed(alt_seed);\n"; @@ -1082,16 +1082,9 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture> case PARAM_SCALE: { VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, p_texture); - Ref<CurveTexture> curve = p_texture; - if (curve.is_valid()) { - if (curve->get_min() == 0 && curve->get_max() == 1) { - - curve->set_max(32); - PoolVector<Vector2> points; - points.push_back(Vector2(0, 1)); - points.push_back(Vector2(1, 1)); - curve->set_points(points); - } + Ref<CurveTexture> curve_tex = p_texture; + if (curve_tex.is_valid()) { + curve_tex->ensure_default_setup(); } } break; @@ -1257,14 +1250,7 @@ void ParticlesMaterial::set_trail_size_modifier(const Ref<CurveTexture> &p_trail Ref<CurveTexture> curve = trail_size_modifier; if (curve.is_valid()) { - if (curve->get_min() == 0 && curve->get_max() == 1) { - - curve->set_max(32); - PoolVector<Vector2> points; - points.push_back(Vector2(0, 1)); - points.push_back(Vector2(1, 1)); - curve->set_points(points); - } + curve->ensure_default_setup(); } RID texture; diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index 104fa0f70f..6f8c38eddd 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -316,7 +316,7 @@ void GeometryInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("get_aabb"), &GeometryInstance::get_aabb); ADD_GROUP("Geometry", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_material_override", "get_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial"), "set_material_override", "get_material_override"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0"), "set_extra_cull_margin", "get_extra_cull_margin"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "visible_in_all_rooms"), "set_flag", "get_flag", FLAG_VISIBLE_IN_ALL_ROOMS); diff --git a/scene/gui/input_action.cpp b/scene/gui/input_action.cpp index 311cb4ab13..3f80c31c8b 100644 --- a/scene/gui/input_action.cpp +++ b/scene/gui/input_action.cpp @@ -43,7 +43,7 @@ Ref<InputEvent> ShortCut::get_shortcut() const { bool ShortCut::is_shortcut(const Ref<InputEvent> &p_event) const { - return shortcut.is_valid() && shortcut->action_match(p_event); + return shortcut.is_valid() && shortcut->shortcut_match(p_event); } String ShortCut::get_as_text() const { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index d4ca55346a..151bc80321 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -469,6 +469,9 @@ void register_scene_types() { ClassDB::register_class<Shader>(); ClassDB::register_class<ShaderMaterial>(); ClassDB::register_virtual_class<CanvasItem>(); + ClassDB::register_class<CanvasItemMaterial>(); + SceneTree::add_idle_callback(CanvasItemMaterial::flush_changes); + CanvasItemMaterial::init_shaders(); ClassDB::register_class<Node2D>(); ClassDB::register_class<Particles2D>(); //ClassDB::register_class<ParticleAttractor2D>(); @@ -577,6 +580,7 @@ void register_scene_types() { ClassDB::register_class<Animation>(); ClassDB::register_virtual_class<Font>(); ClassDB::register_class<BitmapFont>(); + ClassDB::register_class<Curve>(); ClassDB::register_class<DynamicFontData>(); ClassDB::register_class<DynamicFont>(); @@ -663,5 +667,6 @@ void unregister_scene_types() { SpatialMaterial::finish_shaders(); ParticlesMaterial::finish_shaders(); + CanvasItemMaterial::finish_shaders(); SceneStringNames::free(); } diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 10c12c9411..006e7de562 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -380,6 +380,365 @@ Curve2D::Curve2D() #endif +Curve::Curve() { + _bake_resolution = 100; + _baked_cache_dirty = false; +#ifdef TOOLS_ENABLED + _disable_set_data = false; +#endif +} + +int Curve::add_point(Vector2 p_pos, real_t left_tangent, real_t right_tangent) { + // Add a point and preserve order + + // Curve bounds is in 0..1 + if (p_pos.x > MAX_X) + p_pos.x = MAX_X; + else if (p_pos.x < MIN_X) + p_pos.x = MIN_X; + + int ret = -1; + + if (_points.size() == 0) { + _points.push_back(Point(p_pos, left_tangent, right_tangent)); + ret = 0; + + } else if (_points.size() == 1) { + // TODO Is the `else` able to handle this block already? + + real_t diff = p_pos.x - _points[0].pos.x; + + if (diff > 0) { + _points.push_back(Point(p_pos, left_tangent, right_tangent)); + ret = 1; + } else { + _points.insert(0, Point(p_pos, left_tangent, right_tangent)); + ret = 0; + } + + } else { + + int i = get_index(p_pos.x); + + int nearest_index = i; + if (i + 1 < _points.size()) { + real_t diff0 = p_pos.x - _points[i].pos.x; + real_t diff1 = _points[i + 1].pos.x - p_pos.x; + + if (diff1 < diff0) + nearest_index = i + 1; + } + + if (i == 0 && p_pos.x < _points[0].pos.x) { + // Insert before anything else + _points.insert(0, Point(p_pos, left_tangent, right_tangent)); + ret = 0; + } else { + // Insert between i and i+1 + ++i; + _points.insert(i, Point(p_pos, left_tangent, right_tangent)); + ret = i; + } + } + + mark_dirty(); + + return ret; +} + +int Curve::get_index(real_t offset) const { + + // Lower-bound float binary search + + int imin = 0; + int imax = _points.size() - 1; + + while (imax - imin > 1) { + int m = (imin + imax) / 2; + + real_t a = _points[m].pos.x; + real_t b = _points[m + 1].pos.x; + + if (a < offset && b < offset) { + imin = m; + + } else if (a > offset) { + imax = m; + + } else { + return m; + } + } + + // Will happen if the offset is out of bounds + if (offset > _points[imax].pos.x) + return imax; + return imin; +} + +void Curve::clean_dupes() { + + bool dirty = false; + + for (int i = 1; i < _points.size(); ++i) { + real_t diff = _points[i - 1].pos.x - _points[i].pos.x; + if (diff <= CMP_EPSILON) { + _points.remove(i); + --i; + dirty = true; + } + } + + if (dirty) + mark_dirty(); +} + +void Curve::set_point_left_tangent(int i, real_t tangent) { + ERR_FAIL_INDEX(i, _points.size()); + _points[i].left_tangent = tangent; + mark_dirty(); +} + +void Curve::set_point_right_tangent(int i, real_t tangent) { + ERR_FAIL_INDEX(i, _points.size()); + _points[i].right_tangent = tangent; + mark_dirty(); +} + +real_t Curve::get_point_left_tangent(int i) const { + ERR_FAIL_INDEX_V(i, _points.size(), 0); + return _points[i].left_tangent; +} + +real_t Curve::get_point_right_tangent(int i) const { + ERR_FAIL_INDEX_V(i, _points.size(), 0); + return _points[i].right_tangent; +} + +void Curve::remove_point(int p_index) { + ERR_FAIL_INDEX(p_index, _points.size()); + _points.remove(p_index); + mark_dirty(); +} + +void Curve::clear_points() { + _points.clear(); + mark_dirty(); +} + +void Curve::set_point_value(int p_index, real_t pos) { + ERR_FAIL_INDEX(p_index, _points.size()); + _points[p_index].pos.y = pos; + mark_dirty(); +} + +int Curve::set_point_offset(int p_index, float offset) { + ERR_FAIL_INDEX_V(p_index, _points.size(), -1); + Point p = _points[p_index]; + remove_point(p_index); + int i = add_point(Vector2(offset, p.pos.y)); + _points[i].left_tangent = p.left_tangent; + _points[i].right_tangent = p.right_tangent; + return i; +} + +Vector2 Curve::get_point_pos(int p_index) const { + ERR_FAIL_INDEX_V(p_index, _points.size(), Vector2(0, 0)); + return _points[p_index].pos; +} + +real_t Curve::interpolate(real_t offset) const { + if (_points.size() == 0) + return 0; + if (_points.size() == 1) + return _points[0].pos.y; + + int i = get_index(offset); + + if (i == _points.size() - 1) + return _points[i].pos.y; + + real_t local = offset - _points[i].pos.x; + + if (i == 0 && local <= 0) + return _points[0].pos.y; + + return interpolate_local_nocheck(i, local); +} + +real_t Curve::interpolate_local_nocheck(int index, real_t local_offset) const { + + const Point a = _points[index]; + const Point b = _points[index + 1]; + + // Cubic bezier + + // ac-----bc + // / \ + // / \ Here with a.right_tangent > 0 + // / \ and b.left_tangent < 0 + // / \ + // a b + // + // |-d1--|-d2--|-d3--| + // + // d1 == d2 == d3 == d / 3 + + // Control points are chosen at equal distances + real_t d = b.pos.x - a.pos.x; + if (Math::abs(d) <= CMP_EPSILON) + return b.pos.y; + local_offset /= d; + d /= 3.0; + real_t yac = a.pos.y + d * a.right_tangent; + real_t ybc = b.pos.y - d * b.left_tangent; + + real_t y = _bezier_interp(local_offset, a.pos.y, yac, ybc, b.pos.y); + + return y; +} + +void Curve::mark_dirty() { + _baked_cache_dirty = true; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +Array Curve::get_data() const { + + Array output; + output.resize(_points.size() * 3); + + for (int j = 0; j < _points.size(); ++j) { + + const Point p = _points[j]; + int i = j * 3; + + output[i] = p.pos; + output[i + 1] = p.left_tangent; + output[i + 2] = p.right_tangent; + } + + return output; +} + +void Curve::set_data(Array input) { + ERR_FAIL_COND(input.size() % 3 != 0); + +#ifdef TOOLS_ENABLED + if (_disable_set_data) + return; +#endif + + _points.clear(); + + // Validate input + for (int i = 0; i < input.size(); i += 3) { + ERR_FAIL_COND(input[i].get_type() != Variant::VECTOR2); + ERR_FAIL_COND(input[i + 1].get_type() != Variant::REAL); + ERR_FAIL_COND(input[i + 2].get_type() != Variant::REAL); + } + + _points.resize(input.size() / 3); + + for (int j = 0; j < _points.size(); ++j) { + + Point &p = _points[j]; + int i = j * 3; + + p.pos = input[i]; + p.left_tangent = input[i + 1]; + p.right_tangent = input[i + 2]; + } + + mark_dirty(); +} + +void Curve::bake() { + _baked_cache.clear(); + + _baked_cache.resize(_bake_resolution); + + for (int i = 1; i < _bake_resolution - 1; ++i) { + real_t x = i / static_cast<real_t>(_bake_resolution); + real_t y = interpolate(x); + _baked_cache[i] = y; + } + + if (_points.size() != 0) { + _baked_cache[0] = _points[0].pos.y; + _baked_cache[_baked_cache.size() - 1] = _points[_points.size() - 1].pos.y; + } + + _baked_cache_dirty = false; +} + +void Curve::set_bake_resolution(int p_resolution) { + ERR_FAIL_COND(p_resolution < 1); + ERR_FAIL_COND(p_resolution > 1000); + _bake_resolution = p_resolution; + _baked_cache_dirty = true; +} + +real_t Curve::interpolate_baked(real_t offset) { + if (_baked_cache_dirty) { + // Last-second bake if not done already + bake(); + } + + // Special cases if the cache is too small + if (_baked_cache.size() == 0) { + if (_points.size() == 0) + return 0; + return _points[0].pos.y; + } else if (_baked_cache.size() == 1) { + return _baked_cache[0]; + } + + // Get interpolation index + real_t fi = offset * _baked_cache.size(); + int i = Math::floor(fi); + if (i < 0) { + i = 0; + fi = 0; + } else if (i >= _baked_cache.size()) { + i = _baked_cache.size() - 1; + fi = 0; + } + + // Interpolate + if (i + 1 < _baked_cache.size()) { + real_t t = fi - i; + return Math::lerp(_baked_cache[i], _baked_cache[i + 1], t); + } else { + return _baked_cache[_baked_cache.size() - 1]; + } +} + +void Curve::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_point", "pos", "left_tangent", "right_tangent"), &Curve::add_point, DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("remove_point", "index"), &Curve::remove_point); + ClassDB::bind_method(D_METHOD("clear_points"), &Curve::clear_points); + ClassDB::bind_method(D_METHOD("get_point_pos", "index"), &Curve::get_point_pos); + ClassDB::bind_method(D_METHOD("set_point_value", "index, y"), &Curve::set_point_value); + ClassDB::bind_method(D_METHOD("set_point_offset", "index, offset"), &Curve::set_point_value); + ClassDB::bind_method(D_METHOD("interpolate", "offset"), &Curve::interpolate); + ClassDB::bind_method(D_METHOD("interpolate_baked", "offset"), &Curve::interpolate_baked); + ClassDB::bind_method(D_METHOD("get_point_left_tangent", "index"), &Curve::get_point_left_tangent); + ClassDB::bind_method(D_METHOD("get_point_right_tangent", "index"), &Curve::get_point_left_tangent); + ClassDB::bind_method(D_METHOD("set_point_left_tangent", "index", "tangent"), &Curve::set_point_left_tangent); + ClassDB::bind_method(D_METHOD("set_point_right_tangent", "index", "tangent"), &Curve::set_point_left_tangent); + ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes); + ClassDB::bind_method(D_METHOD("bake"), &Curve::bake); + ClassDB::bind_method(D_METHOD("get_bake_resolution"), &Curve::get_bake_resolution); + ClassDB::bind_method(D_METHOD("set_bake_resolution", "resolution"), &Curve::set_bake_resolution); + ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data"); +} + int Curve2D::get_point_count() const { return points.size(); diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 17c0ac9f5e..63b9a07f07 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -82,6 +82,78 @@ public: #endif +// y(x) curve +class Curve : public Resource { + GDCLASS(Curve, Resource) +public: + static const int MIN_X = 0.f; + static const int MAX_X = 1.f; + +#ifdef TOOLS_ENABLED + bool _disable_set_data; +#endif + + struct Point { + Vector2 pos; + real_t left_tangent; + real_t right_tangent; + + Point() { + left_tangent = 0; + right_tangent = 0; + } + + Point(Vector2 p, real_t left = 0, real_t right = 0) { + pos = p; + left_tangent = left; + right_tangent = right; + } + }; + + Curve(); + + int get_point_count() const { return _points.size(); } + + int add_point(Vector2 p_pos, real_t left_tangent = 0, real_t right_tangent = 0); + void remove_point(int p_index); + void clear_points(); + + int get_index(real_t offset) const; + + void set_point_value(int p_index, real_t pos); + int set_point_offset(int p_index, float offset); + Vector2 get_point_pos(int p_index) const; + + real_t interpolate(real_t offset) const; + real_t interpolate_local_nocheck(int index, real_t local_offset) const; + + void clean_dupes(); + + void set_point_left_tangent(int i, real_t tangent); + void set_point_right_tangent(int i, real_t tangent); + real_t get_point_left_tangent(int i) const; + real_t get_point_right_tangent(int i) const; + + Array get_data() const; + void set_data(Array input); + + void bake(); + int get_bake_resolution() const { return _bake_resolution; } + void set_bake_resolution(int p_interval); + real_t interpolate_baked(real_t offset); + +protected: + static void _bind_methods(); + +private: + void mark_dirty(); + + Vector<Point> _points; + bool _baked_cache_dirty; + Vector<real_t> _baked_cache; + int _bake_resolution; +}; + class Curve2D : public Resource { GDCLASS(Curve2D, Resource); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index a3180ee1df..ef7011b2af 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -668,7 +668,11 @@ void ArrayMesh::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::DICTIONARY, "surfaces/" + itos(i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); p_list->push_back(PropertyInfo(Variant::STRING, "surface_" + itos(i + 1) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "Material", PROPERTY_USAGE_EDITOR)); + if (surfaces[i].is_2d) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial", PROPERTY_USAGE_EDITOR)); + } else { + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial", PROPERTY_USAGE_EDITOR)); + } } p_list->push_back(PropertyInfo(Variant::RECT3, "custom_aabb/custom_aabb")); @@ -692,6 +696,7 @@ void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const Surface s; s.aabb = p_aabb; + s.is_2d = p_format & ARRAY_FLAG_USE_2D_VERTICES; surfaces.push_back(s); _recompute_aabb(); @@ -709,7 +714,8 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array & /* make aABB? */ { - PoolVector<Vector3> vertices = p_arrays[ARRAY_VERTEX]; + Variant arr = p_arrays[ARRAY_VERTEX]; + PoolVector<Vector3> vertices = arr; int len = vertices.size(); ERR_FAIL_COND(len == 0); PoolVector<Vector3>::Read r = vertices.read(); @@ -726,6 +732,7 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array & } surfaces[surfaces.size() - 1].aabb = aabb; + surfaces[surfaces.size() - 1].is_2d = arr.get_type() == Variant::POOL_VECTOR2_ARRAY; _recompute_aabb(); } diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 37fddf3aa6..f716b59fe9 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -147,6 +147,7 @@ private: String name; Rect3 aabb; Ref<Material> material; + bool is_2d; }; Vector<Surface> surfaces; RID mesh; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 2120b37497..7b393233f1 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -30,7 +30,9 @@ #include "texture.h" #include "core/method_bind_ext.inc" #include "core/os/os.h" +#include "core_string_names.h" #include "io/image_loader.h" + Size2 Texture::get_size() const { return Size2(get_width(), get_height()); @@ -1376,254 +1378,126 @@ void CurveTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); - ClassDB::bind_method(D_METHOD("set_points", "points"), &CurveTexture::set_points); - ClassDB::bind_method(D_METHOD("get_points"), &CurveTexture::get_points); + ClassDB::bind_method(D_METHOD("set_curve", "curve:Curve"), &CurveTexture::set_curve); + ClassDB::bind_method(D_METHOD("get_curve:Curve"), &CurveTexture::get_curve); + + ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); ADD_PROPERTY(PropertyInfo(Variant::REAL, "min", PROPERTY_HINT_RANGE, "-1024,1024"), "set_min", "get_min"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max", PROPERTY_HINT_RANGE, "-1024,1024"), "set_max", "get_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), "set_points", "get_points"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); } void CurveTexture::set_max(float p_max) { - max = p_max; + _max = p_max; emit_changed(); } float CurveTexture::get_max() const { - return max; + return _max; } void CurveTexture::set_min(float p_min) { - min = p_min; + _min = p_min; emit_changed(); } float CurveTexture::get_min() const { - return min; + return _min; } void CurveTexture::set_width(int p_width) { ERR_FAIL_COND(p_width < 32 || p_width > 4096); - width = p_width; - if (points.size()) - set_points(points); + _width = p_width; + _update(); } int CurveTexture::get_width() const { - return width; + return _width; } -static void _plot_curve(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_c, const Vector2 &p_d, float *p_heights, bool *p_useds, int p_width, float p_min, float p_max) { - - float geometry[4][4]; - float tmp1[4][4]; - float tmp2[4][4]; - float deltas[4][4]; - double x, dx, dx2, dx3; - double y, dy, dy2, dy3; - double d, d2, d3; - int lastx; - int newx; - float lasty; - float newy; - int ntimes; - int i, j; - - int xmax = p_width; - - /* construct the geometry matrix from the segment */ - for (i = 0; i < 4; i++) { - geometry[i][2] = 0; - geometry[i][3] = 0; - } +void CurveTexture::ensure_default_setup() { - geometry[0][0] = (p_a[0] * xmax); - geometry[1][0] = (p_b[0] * xmax); - geometry[2][0] = (p_c[0] * xmax); - geometry[3][0] = (p_d[0] * xmax); - - geometry[0][1] = (p_a[1]); - geometry[1][1] = (p_b[1]); - geometry[2][1] = (p_c[1]); - geometry[3][1] = (p_d[1]); - - /* subdivide the curve ntimes (1000) times */ - ntimes = 4 * xmax; - /* ntimes can be adjusted to give a finer or coarser curve */ - d = 1.0 / ntimes; - d2 = d * d; - d3 = d * d * d; - - /* construct a temporary matrix for determining the forward differencing deltas */ - tmp2[0][0] = 0; - tmp2[0][1] = 0; - tmp2[0][2] = 0; - tmp2[0][3] = 1; - tmp2[1][0] = d3; - tmp2[1][1] = d2; - tmp2[1][2] = d; - tmp2[1][3] = 0; - tmp2[2][0] = 6 * d3; - tmp2[2][1] = 2 * d2; - tmp2[2][2] = 0; - tmp2[2][3] = 0; - tmp2[3][0] = 6 * d3; - tmp2[3][1] = 0; - tmp2[3][2] = 0; - tmp2[3][3] = 0; - - /* compose the basis and geometry matrices */ - - static const float CR_basis[4][4] = { - { -0.5, 1.5, -1.5, 0.5 }, - { 1.0, -2.5, 2.0, -0.5 }, - { -0.5, 0.0, 0.5, 0.0 }, - { 0.0, 1.0, 0.0, 0.0 }, - }; - - for (i = 0; i < 4; i++) { - for (j = 0; j < 4; j++) { - tmp1[i][j] = (CR_basis[i][0] * geometry[0][j] + - CR_basis[i][1] * geometry[1][j] + - CR_basis[i][2] * geometry[2][j] + - CR_basis[i][3] * geometry[3][j]); - } + if (_curve.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + set_curve(curve); } - /* compose the above results to get the deltas matrix */ - - for (i = 0; i < 4; i++) { - for (j = 0; j < 4; j++) { - deltas[i][j] = (tmp2[i][0] * tmp1[0][j] + - tmp2[i][1] * tmp1[1][j] + - tmp2[i][2] * tmp1[2][j] + - tmp2[i][3] * tmp1[3][j]); - } + + if (get_min() == 0 && get_max() == 1) { + set_max(32); } +} - /* extract the x deltas */ - x = deltas[0][0]; - dx = deltas[1][0]; - dx2 = deltas[2][0]; - dx3 = deltas[3][0]; - - /* extract the y deltas */ - y = deltas[0][1]; - dy = deltas[1][1]; - dy2 = deltas[2][1]; - dy3 = deltas[3][1]; - - lastx = CLAMP(x, 0, xmax); - lasty = y; - - p_heights[lastx] = lasty; - p_useds[lastx] = true; - - /* loop over the curve */ - for (i = 0; i < ntimes; i++) { - /* increment the x values */ - x += dx; - dx += dx2; - dx2 += dx3; - - /* increment the y values */ - y += dy; - dy += dy2; - dy2 += dy3; - - newx = CLAMP((Math::round(x)), 0, xmax); - newy = CLAMP(y, p_min, p_max); - - /* if this point is different than the last one...then draw it */ - if ((lastx != newx) || (lasty != newy)) { - p_useds[newx] = true; - p_heights[newx] = newy; +void CurveTexture::set_curve(Ref<Curve> p_curve) { + if (_curve != p_curve) { + if (_curve.is_valid()) { + _curve->disconnect(CoreStringNames::get_singleton()->changed, this, "_update"); } - - lastx = newx; - lasty = newy; + _curve = p_curve; + if (_curve.is_valid()) { + _curve->connect(CoreStringNames::get_singleton()->changed, this, "_update"); + } + _update(); } } -void CurveTexture::set_points(const PoolVector<Vector2> &p_points) { - - points = p_points; +void CurveTexture::_update() { PoolVector<uint8_t> data; - PoolVector<bool> used; - data.resize(width * sizeof(float)); - used.resize(width); + data.resize(_width * sizeof(float)); + + // The array is locked in that scope { PoolVector<uint8_t>::Write wd8 = data.write(); float *wd = (float *)wd8.ptr(); - PoolVector<bool>::Write wu = used.write(); - int pc = p_points.size(); - PoolVector<Vector2>::Read pr = p_points.read(); - for (int i = 0; i < width; i++) { - wd[i] = 0.0; - wu[i] = false; - } - - Vector2 prev = Vector2(0, 0); - Vector2 prev2 = Vector2(0, 0); - - for (int i = -1; i < pc; i++) { - - Vector2 next; - Vector2 next2; - if (i + 1 >= pc) { - next = Vector2(1, 0); - } else { - next = Vector2(pr[i + 1].x, pr[i + 1].y); + if (_curve.is_valid()) { + Curve &curve = **_curve; + float height = _max - _min; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + float v = curve.interpolate_baked(t); + wd[i] = CLAMP(_min + v * height, _min, _max); } - if (i + 2 >= pc) { - next2 = Vector2(1, 0); - } else { - next2 = Vector2(pr[i + 2].x, pr[i + 2].y); + } else { + for (int i = 0; i < _width; ++i) { + wd[i] = 0; } - - /*if (i==-1 && prev.offset==next.offset) { - prev=next; - continue; - }*/ - - _plot_curve(prev2, prev, next, next2, wd, wu.ptr(), width, min, max); - - prev2 = prev; - prev = next; } } - Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RF, data)); + Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RF, data)); - VS::get_singleton()->texture_allocate(texture, width, 1, Image::FORMAT_RF, VS::TEXTURE_FLAG_FILTER); - VS::get_singleton()->texture_set_data(texture, image); + VS::get_singleton()->texture_allocate(_texture, _width, 1, Image::FORMAT_RF, VS::TEXTURE_FLAG_FILTER); + VS::get_singleton()->texture_set_data(_texture, image); emit_changed(); } -PoolVector<Vector2> CurveTexture::get_points() const { +Ref<Curve> CurveTexture::get_curve() const { - return points; + return _curve; } RID CurveTexture::get_rid() const { - return texture; + return _texture; } CurveTexture::CurveTexture() { - max = 1; - min = 0; - width = 2048; - texture = VS::get_singleton()->texture_create(); + _max = 1; + _min = 0; + _width = 2048; + _texture = VS::get_singleton()->texture_create(); } CurveTexture::~CurveTexture() { - VS::get_singleton()->free(texture); + VS::get_singleton()->free(_texture); } ////////////////// diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 2b82dbd21f..2c36cdef87 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -30,6 +30,7 @@ #ifndef TEXTURE_H #define TEXTURE_H +#include "curve.h" #include "io/resource_loader.h" #include "math_2d.h" #include "resource.h" @@ -389,20 +390,22 @@ public: ~CubeMap(); }; -VARIANT_ENUM_CAST(CubeMap::Flags); -VARIANT_ENUM_CAST(CubeMap::Side); -VARIANT_ENUM_CAST(CubeMap::Storage); +VARIANT_ENUM_CAST(CubeMap::Flags) +VARIANT_ENUM_CAST(CubeMap::Side) +VARIANT_ENUM_CAST(CubeMap::Storage) class CurveTexture : public Texture { - GDCLASS(CurveTexture, Texture); - RES_BASE_EXTENSION("curvetex"); + GDCLASS(CurveTexture, Texture) + RES_BASE_EXTENSION("curvetex") private: - RID texture; - PoolVector<Vector2> points; - float min, max; - int width; + RID _texture; + Ref<Curve> _curve; + float _min, _max; + int _width; + + void _update(); protected: static void _bind_methods(); @@ -417,8 +420,10 @@ public: void set_width(int p_width); int get_width() const; - void set_points(const PoolVector<Vector2> &p_points); - PoolVector<Vector2> get_points() const; + void ensure_default_setup(); + + void set_curve(Ref<Curve> p_curve); + Ref<Curve> get_curve() const; virtual RID get_rid() const; diff --git a/servers/visual/shader_types.cpp b/servers/visual/shader_types.cpp index 81139ecc1c..42f1a98826 100644 --- a/servers/visual/shader_types.cpp +++ b/servers/visual/shader_types.cpp @@ -212,6 +212,7 @@ ShaderTypes::ShaderTypes() { shader_modes[VS::SHADER_PARTICLES].functions["vertex"]["NUMBER"] = ShaderLanguage::TYPE_UINT; shader_modes[VS::SHADER_PARTICLES].functions["vertex"]["INDEX"] = ShaderLanguage::TYPE_INT; shader_modes[VS::SHADER_PARTICLES].functions["vertex"]["EMISSION_TRANSFORM"] = ShaderLanguage::TYPE_MAT4; + shader_modes[VS::SHADER_PARTICLES].functions["vertex"]["RANDOM_SEED"] = ShaderLanguage::TYPE_UINT; shader_modes[VS::SHADER_PARTICLES].modes.insert("billboard"); shader_modes[VS::SHADER_PARTICLES].modes.insert("disable_force"); diff --git a/servers/visual/visual_server_canvas.cpp b/servers/visual/visual_server_canvas.cpp index 616990b311..1227863b72 100644 --- a/servers/visual/visual_server_canvas.cpp +++ b/servers/visual/visual_server_canvas.cpp @@ -58,6 +58,12 @@ void VisualServerCanvas::_render_canvas_item(Item *p_canvas_item, const Transfor if (!ci->visible) return; + if (p_canvas_item->children_order_dirty) { + + p_canvas_item->child_items.sort_custom<ItemIndexSort>(); + p_canvas_item->children_order_dirty = false; + } + Rect2 rect = ci->get_rect(); Transform2D xform = p_transform * ci->xform; Rect2 global_rect = xform.xform(rect); @@ -171,6 +177,12 @@ void VisualServerCanvas::render_canvas(Canvas *p_canvas, const Transform2D &p_tr VSG::canvas_render->canvas_begin(); + if (p_canvas->children_order_dirty) { + + p_canvas->child_items.sort(); + p_canvas->children_order_dirty = false; + } + int l = p_canvas->child_items.size(); Canvas::ChildItem *ci = p_canvas->child_items.ptr(); diff --git a/servers/visual/visual_server_canvas.h b/servers/visual/visual_server_canvas.h index f78ef635f4..57c7515367 100644 --- a/servers/visual/visual_server_canvas.h +++ b/servers/visual/visual_server_canvas.h @@ -63,6 +63,14 @@ public: } }; + struct ItemIndexSort { + + _FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const { + + return p_left->index < p_right->index; + } + }; + struct ItemPtrSort { _FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const { |