diff options
author | Marc Gilleron <marc.gilleron@gmail.com> | 2017-04-30 16:27:10 +0200 |
---|---|---|
committer | Marc Gilleron <marc.gilleron@gmail.com> | 2017-06-24 01:01:36 +0200 |
commit | 659897cfb8bda0377d798a6f73505d537e521cf9 (patch) | |
tree | db94c76d34d51e9ec97213a08c9654825707fad4 /editor/plugins | |
parent | 00e5ba314393ce2cc4df883bc1742306007ed117 (diff) |
Added Curve resource
- New resource for curves in y(x) form
- CurveTexture now has a Curve
- Curve and CurveTexture share the same editor
Diffstat (limited to 'editor/plugins')
-rw-r--r-- | editor/plugins/curve_editor_plugin.cpp | 935 | ||||
-rw-r--r-- | editor/plugins/curve_editor_plugin.h | 128 | ||||
-rw-r--r-- | editor/plugins/texture_editor_plugin.cpp | 12 |
3 files changed, 652 insertions, 423 deletions
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/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"); |