浏览代码

Merge pull request #8591 from Zylann/curve_1d

Added Curve resource
Juan Linietsky 8 年之前
父节点
当前提交
693eb0fa06

+ 1 - 1
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) ) );

+ 551 - 384
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);
 }

+ 89 - 39
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

+ 12 - 0
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");

+ 4 - 18
scene/3d/particles.cpp

@@ -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;

+ 1 - 0
scene/register_scene_types.cpp

@@ -580,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>();

+ 359 - 0
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();

+ 72 - 0
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);

+ 58 - 184
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);
 }
 //////////////////
 

+ 16 - 11
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;