summaryrefslogtreecommitdiff
path: root/editor
diff options
context:
space:
mode:
authorMarc Gilleron <marc.gilleron@gmail.com>2017-04-30 16:27:10 +0200
committerMarc Gilleron <marc.gilleron@gmail.com>2017-06-24 01:01:36 +0200
commit659897cfb8bda0377d798a6f73505d537e521cf9 (patch)
treedb94c76d34d51e9ec97213a08c9654825707fad4 /editor
parent00e5ba314393ce2cc4df883bc1742306007ed117 (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')
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/plugins/curve_editor_plugin.cpp935
-rw-r--r--editor/plugins/curve_editor_plugin.h128
-rw-r--r--editor/plugins/texture_editor_plugin.cpp12
4 files changed, 653 insertions, 424 deletions
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index b700072dbe..2b29e4b08a 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -6114,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/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");