浏览代码

Merge pull request #9404 from Zylann/curve_improvement

Curve improvement
Juan Linietsky 8 年之前
父节点
当前提交
51442988b1

+ 142 - 40
editor/plugins/curve_editor_plugin.cpp

@@ -43,7 +43,6 @@ CurveEditor::CurveEditor() {
 	_tangents_length = 40;
 	_tangents_length = 40;
 	_dragging = false;
 	_dragging = false;
 	_has_undo_data = false;
 	_has_undo_data = false;
-	_world_rect = Rect2(0, 0, 1, 1);
 
 
 	set_focus_mode(FOCUS_ALL);
 	set_focus_mode(FOCUS_ALL);
 	set_clip_contents(true);
 	set_clip_contents(true);
@@ -70,11 +69,15 @@ void CurveEditor::set_curve(Ref<Curve> curve) {
 		return;
 		return;
 
 
 	if (_curve_ref.is_valid()) {
 	if (_curve_ref.is_valid()) {
-		_curve_ref->disconnect("changed", this, "_curve_changed");
+		_curve_ref->disconnect(CoreStringNames::get_singleton()->changed, this, "_curve_changed");
+		_curve_ref->disconnect(Curve::SIGNAL_RANGE_CHANGED, this, "_curve_changed");
 	}
 	}
+
 	_curve_ref = curve;
 	_curve_ref = curve;
+
 	if (_curve_ref.is_valid()) {
 	if (_curve_ref.is_valid()) {
-		_curve_ref->connect("changed", this, "_curve_changed");
+		_curve_ref->connect(CoreStringNames::get_singleton()->changed, this, "_curve_changed");
+		_curve_ref->connect(Curve::SIGNAL_RANGE_CHANGED, this, "_curve_changed");
 	}
 	}
 
 
 	_selected_point = -1;
 	_selected_point = -1;
@@ -144,11 +147,13 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
 		Vector2 mpos = mm.get_position();
 		Vector2 mpos = mm.get_position();
 
 
 		if (_dragging && _curve_ref.is_valid()) {
 		if (_dragging && _curve_ref.is_valid()) {
+			Curve &curve = **_curve_ref;
+
 			if (_selected_point != -1) {
 			if (_selected_point != -1) {
 
 
 				if (!_has_undo_data) {
 				if (!_has_undo_data) {
 					// Save curve state before dragging points
 					// Save curve state before dragging points
-					_undo_data = _curve_ref->get_data();
+					_undo_data = curve.get_data();
 					_has_undo_data = true;
 					_has_undo_data = true;
 				}
 				}
 
 
@@ -157,26 +162,25 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
 
 
 					Vector2 point_pos = get_world_pos(mpos);
 					Vector2 point_pos = get_world_pos(mpos);
 
 
-					int i = _curve_ref->set_point_offset(_selected_point, point_pos.x);
+					int i = curve.set_point_offset(_selected_point, point_pos.x);
 					// The index may change if the point is dragged across another one
 					// The index may change if the point is dragged across another one
 					set_hover_point_index(i);
 					set_hover_point_index(i);
 					set_selected_point(i);
 					set_selected_point(i);
 
 
-					// 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.
 					// 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;
+					if (point_pos.y < curve.get_min_value())
+						point_pos.y = curve.get_min_value();
+					else if (point_pos.y > curve.get_max_value())
+						point_pos.y = curve.get_max_value();
 
 
-					_curve_ref->set_point_value(_selected_point, point_pos.y);
+					curve.set_point_value(_selected_point, point_pos.y);
 
 
 					//auto_calculate_tangents(i);
 					//auto_calculate_tangents(i);
 
 
 				} else {
 				} else {
 					// Drag tangent
 					// Drag tangent
 
 
-					Vector2 point_pos = _curve_ref->get_point_pos(_selected_point);
+					Vector2 point_pos = curve.get_point_pos(_selected_point);
 					Vector2 control_pos = get_world_pos(mpos);
 					Vector2 control_pos = get_world_pos(mpos);
 
 
 					Vector2 dir = (control_pos - point_pos).normalized();
 					Vector2 dir = (control_pos - point_pos).normalized();
@@ -190,13 +194,17 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
 					bool link = !Input::get_singleton()->is_key_pressed(KEY_SHIFT);
 					bool link = !Input::get_singleton()->is_key_pressed(KEY_SHIFT);
 
 
 					if (_selected_tangent == TANGENT_LEFT) {
 					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);
+						curve.set_point_left_tangent(_selected_point, tangent);
+
+						// Note: if a tangent is set to linear, it shouldn't be linked to the other
+						if (link && _selected_point != curve.get_point_count() - 1 && !curve.get_point_right_mode(_selected_point) != Curve::TANGENT_FREE)
+							curve.set_point_right_tangent(_selected_point, tangent);
+
 					} else {
 					} else {
-						_curve_ref->set_point_right_tangent(_selected_point, tangent);
-						if (link && _selected_point != 0)
-							_curve_ref->set_point_left_tangent(_selected_point, tangent);
+						curve.set_point_right_tangent(_selected_point, tangent);
+
+						if (link && _selected_point != 0 && !curve.get_point_left_mode(_selected_point) != Curve::TANGENT_FREE)
+							curve.set_point_left_tangent(_selected_point, tangent);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -230,25 +238,31 @@ void CurveEditor::on_preset_item_selected(int preset_id) {
 		case PRESET_FLAT0:
 		case PRESET_FLAT0:
 			curve.add_point(Vector2(0, 0));
 			curve.add_point(Vector2(0, 0));
 			curve.add_point(Vector2(1, 0));
 			curve.add_point(Vector2(1, 0));
+			curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
+			curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
 			break;
 			break;
 
 
 		case PRESET_FLAT1:
 		case PRESET_FLAT1:
 			curve.add_point(Vector2(0, 1));
 			curve.add_point(Vector2(0, 1));
 			curve.add_point(Vector2(1, 1));
 			curve.add_point(Vector2(1, 1));
+			curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
+			curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
 			break;
 			break;
 
 
 		case PRESET_LINEAR:
 		case PRESET_LINEAR:
-			curve.add_point(Vector2(0, 0), 0, 1);
-			curve.add_point(Vector2(1, 1), 1, 0);
+			curve.add_point(Vector2(0, 0));
+			curve.add_point(Vector2(1, 1));
+			curve.set_point_right_mode(0, Curve::TANGENT_LINEAR);
+			curve.set_point_left_mode(1, Curve::TANGENT_LINEAR);
 			break;
 			break;
 
 
 		case PRESET_EASE_IN:
 		case PRESET_EASE_IN:
 			curve.add_point(Vector2(0, 0));
 			curve.add_point(Vector2(0, 0));
-			curve.add_point(Vector2(1, 1), 1.4, 0);
+			curve.add_point(Vector2(1, 1), (curve.get_max_value() - curve.get_min_value()) * 1.4, 0);
 			break;
 			break;
 
 
 		case PRESET_EASE_OUT:
 		case PRESET_EASE_OUT:
-			curve.add_point(Vector2(0, 0), 0, 1.4);
+			curve.add_point(Vector2(0, 0), 0, (curve.get_max_value() - curve.get_min_value()) * 1.4);
 			curve.add_point(Vector2(1, 1));
 			curve.add_point(Vector2(1, 1));
 			break;
 			break;
 
 
@@ -281,6 +295,18 @@ void CurveEditor::on_context_menu_item_selected(int action_id) {
 		case CONTEXT_REMOVE_POINT:
 		case CONTEXT_REMOVE_POINT:
 			remove_point(_selected_point);
 			remove_point(_selected_point);
 			break;
 			break;
+
+		case CONTEXT_LINEAR:
+			toggle_linear();
+			break;
+
+		case CONTEXT_LEFT_LINEAR:
+			toggle_linear(TANGENT_LEFT);
+			break;
+
+		case CONTEXT_RIGHT_LINEAR:
+			toggle_linear(TANGENT_RIGHT);
+			break;
 	}
 	}
 }
 }
 
 
@@ -291,9 +317,37 @@ void CurveEditor::open_context_menu(Vector2 pos) {
 
 
 	if (_curve_ref.is_valid()) {
 	if (_curve_ref.is_valid()) {
 		_context_menu->add_item(TTR("Add point"), CONTEXT_ADD_POINT);
 		_context_menu->add_item(TTR("Add point"), CONTEXT_ADD_POINT);
+
 		if (_selected_point >= 0) {
 		if (_selected_point >= 0) {
 			_context_menu->add_item(TTR("Remove point"), CONTEXT_REMOVE_POINT);
 			_context_menu->add_item(TTR("Remove point"), CONTEXT_REMOVE_POINT);
+
+			if (_selected_tangent != TANGENT_NONE) {
+				_context_menu->add_separator();
+
+				_context_menu->add_check_item(TTR("Linear"), CONTEXT_LINEAR);
+
+				bool is_linear = _selected_tangent == TANGENT_LEFT ?
+										 _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR :
+										 _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR;
+
+				_context_menu->set_item_checked(CONTEXT_LINEAR, is_linear);
+
+			} else {
+				_context_menu->add_separator();
+
+				if (_selected_point > 0) {
+					_context_menu->add_check_item(TTR("Left linear"), CONTEXT_LEFT_LINEAR);
+					_context_menu->set_item_checked(CONTEXT_LEFT_LINEAR,
+							_curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR);
+				}
+				if (_selected_point + 1 < _curve_ref->get_point_count()) {
+					_context_menu->add_check_item(TTR("Right linear"), CONTEXT_RIGHT_LINEAR);
+					_context_menu->set_item_checked(CONTEXT_RIGHT_LINEAR,
+							_curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR);
+				}
+			}
 		}
 		}
+
 		_context_menu->add_separator();
 		_context_menu->add_separator();
 	}
 	}
 
 
@@ -319,7 +373,7 @@ int CurveEditor::get_point_at(Vector2 pos) const {
 	return -1;
 	return -1;
 }
 }
 
 
-int CurveEditor::get_tangent_at(Vector2 pos) const {
+CurveEditor::TangentIndex CurveEditor::get_tangent_at(Vector2 pos) const {
 	if (_curve_ref.is_null() || _selected_point < 0)
 	if (_curve_ref.is_null() || _selected_point < 0)
 		return TANGENT_NONE;
 		return TANGENT_NONE;
 
 
@@ -369,6 +423,25 @@ void CurveEditor::remove_point(int index) {
 	push_undo(prev_data);
 	push_undo(prev_data);
 }
 }
 
 
+void CurveEditor::toggle_linear(TangentIndex tangent) {
+	ERR_FAIL_COND(_curve_ref.is_null());
+
+	Array prev_data = _curve_ref->get_data();
+
+	if (tangent == TANGENT_NONE)
+		tangent = _selected_tangent;
+
+	if (tangent == TANGENT_LEFT) {
+		bool is_linear = _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR;
+		_curve_ref->set_point_left_mode(_selected_point, is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR);
+	} else {
+		bool is_linear = _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR;
+		_curve_ref->set_point_right_mode(_selected_point, is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR);
+	}
+
+	push_undo(prev_data);
+}
+
 void CurveEditor::set_selected_point(int index) {
 void CurveEditor::set_selected_point(int index) {
 	if (index != _selected_point) {
 	if (index != _selected_point) {
 		_selected_point = index;
 		_selected_point = index;
@@ -401,14 +474,23 @@ void CurveEditor::update_view_transform() {
 	Vector2 control_size = get_size();
 	Vector2 control_size = get_size();
 	const real_t margin = 24;
 	const real_t margin = 24;
 
 
-	_world_rect = Rect2(Curve::MIN_X, 0, Curve::MAX_X, 1);
+	float min_y = 0;
+	float max_y = 1;
+
+	if (_curve_ref.is_valid()) {
+		min_y = _curve_ref->get_min_value();
+		max_y = _curve_ref->get_max_value();
+	}
+
+	Rect2 world_rect = Rect2(Curve::MIN_X, min_y, Curve::MAX_X, max_y - min_y);
 	Vector2 wm = Vector2(margin, margin) / control_size;
 	Vector2 wm = Vector2(margin, margin) / control_size;
-	_world_rect.position -= wm;
-	_world_rect.size += 2.0 * wm;
+	wm.y *= (max_y - min_y);
+	world_rect.position -= wm;
+	world_rect.size += 2.0 * wm;
 
 
 	_world_to_view = Transform2D();
 	_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);
+	_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 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const {
 Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const {
@@ -509,17 +591,18 @@ void CurveEditor::_draw() {
 
 
 	const Color grid_color0(0, 0, 0, 0.5);
 	const Color grid_color0(0, 0, 0, 0.5);
 	const Color grid_color1(0, 0, 0, 0.15);
 	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(min_edge.x, curve.get_min_value()), Vector2(max_edge.x, curve.get_min_value()), grid_color0);
+	draw_line(Vector2(max_edge.x, curve.get_max_value()), Vector2(min_edge.x, curve.get_max_value()), grid_color0);
 	draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), 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(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);
 
 
-	const Vector2 grid_step(0.25, 0.5);
+	float curve_height = (curve.get_max_value() - curve.get_min_value());
+	const Vector2 grid_step(0.25, 0.5 * curve_height);
 
 
 	for (real_t x = 0; x < 1.0; x += grid_step.x) {
 	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);
 		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) {
+	for (real_t y = curve.get_min_value(); y < curve.get_max_value(); y += grid_step.y) {
 		draw_line(Vector2(min_edge.x, y), Vector2(max_edge.x, y), grid_color1);
 		draw_line(Vector2(min_edge.x, y), Vector2(max_edge.x, y), grid_color1);
 	}
 	}
 
 
@@ -528,17 +611,30 @@ void CurveEditor::_draw() {
 	draw_set_transform_matrix(Transform2D());
 	draw_set_transform_matrix(Transform2D());
 
 
 	Ref<Font> font = get_font("font", "Label");
 	Ref<Font> font = get_font("font", "Label");
+	float font_height = font->get_height();
 	const Color text_color(1, 1, 1, 0.3);
 	const Color text_color(1, 1, 1, 0.3);
 
 
-	draw_string(font, get_view_pos(Vector2(0, 0)), "0.0", text_color);
-
-	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);
+	{
+		// X axis
+		float y = curve.get_min_value();
+		Vector2 off(0, font_height - 1);
+		draw_string(font, get_view_pos(Vector2(0, y)) + off, "0.0", text_color);
+		draw_string(font, get_view_pos(Vector2(0.25, y)) + off, "0.25", text_color);
+		draw_string(font, get_view_pos(Vector2(0.5, y)) + off, "0.5", text_color);
+		draw_string(font, get_view_pos(Vector2(0.75, y)) + off, "0.75", text_color);
+		draw_string(font, get_view_pos(Vector2(1, y)) + off, "1.0", text_color);
+	}
 
 
-	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);
+	{
+		// Y axis
+		float m0 = curve.get_min_value();
+		float m1 = 0.5 * (curve.get_min_value() + curve.get_max_value());
+		float m2 = curve.get_max_value();
+		Vector2 off(1, -1);
+		draw_string(font, get_view_pos(Vector2(0, m0)) + off, String::num(m0, 2), text_color);
+		draw_string(font, get_view_pos(Vector2(0, m1)) + off, String::num(m1, 2), text_color);
+		draw_string(font, get_view_pos(Vector2(0, m2)) + off, String::num(m2, 3), text_color);
+	}
 
 
 	// Draw tangents for current point
 	// Draw tangents for current point
 
 
@@ -611,6 +707,12 @@ void CurveEditor::_draw() {
 		Vector2 pos = curve.get_point_pos(_hover_point);
 		Vector2 pos = curve.get_point_pos(_hover_point);
 		stroke_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(_hover_radius), hover_color);
 		stroke_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(_hover_radius), hover_color);
 	}
 	}
+
+	// Help text
+
+	if (_selected_point > 0 && _selected_point + 1 < curve.get_point_count()) {
+		draw_string(font, Vector2(50, font_height), TTR("Hold Shift to edit tangents individually"), text_color);
+	}
 }
 }
 
 
 // TODO That should be part of the drawing API...
 // TODO That should be part of the drawing API...

+ 7 - 4
editor/plugins/curve_editor_plugin.h

@@ -57,7 +57,10 @@ public:
 
 
 	enum ContextAction {
 	enum ContextAction {
 		CONTEXT_ADD_POINT = 0,
 		CONTEXT_ADD_POINT = 0,
-		CONTEXT_REMOVE_POINT
+		CONTEXT_REMOVE_POINT,
+		CONTEXT_LINEAR,
+		CONTEXT_LEFT_LINEAR,
+		CONTEXT_RIGHT_LINEAR
 	};
 	};
 
 
 	enum TangentIndex {
 	enum TangentIndex {
@@ -79,9 +82,10 @@ private:
 
 
 	void open_context_menu(Vector2 pos);
 	void open_context_menu(Vector2 pos);
 	int get_point_at(Vector2 pos) const;
 	int get_point_at(Vector2 pos) const;
-	int get_tangent_at(Vector2 pos) const;
+	TangentIndex get_tangent_at(Vector2 pos) const;
 	void add_point(Vector2 pos);
 	void add_point(Vector2 pos);
 	void remove_point(int index);
 	void remove_point(int index);
+	void toggle_linear(TangentIndex tangent = TANGENT_NONE);
 	void set_selected_point(int index);
 	void set_selected_point(int index);
 	void set_hover_point_index(int index);
 	void set_hover_point_index(int index);
 	void push_undo(Array previous_curve_data);
 	void push_undo(Array previous_curve_data);
@@ -96,7 +100,6 @@ private:
 	void stroke_rect(Rect2 rect, Color color);
 	void stroke_rect(Rect2 rect, Color color);
 
 
 private:
 private:
-	Rect2 _world_rect;
 	Transform2D _world_to_view;
 	Transform2D _world_to_view;
 
 
 	Ref<Curve> _curve_ref;
 	Ref<Curve> _curve_ref;
@@ -110,7 +113,7 @@ private:
 	Vector2 _context_click_pos;
 	Vector2 _context_click_pos;
 	int _selected_point;
 	int _selected_point;
 	int _hover_point;
 	int _hover_point;
-	int _selected_tangent;
+	TangentIndex _selected_tangent;
 	bool _dragging;
 	bool _dragging;
 
 
 	// Constant
 	// Constant

+ 3 - 8
scene/3d/particles.cpp

@@ -1052,16 +1052,11 @@ float ParticlesMaterial::get_param_randomness(Parameter p_param) const {
 
 
 static void _adjust_curve_range(const Ref<Texture> &p_texture, float p_min, float p_max) {
 static void _adjust_curve_range(const Ref<Texture> &p_texture, float p_min, float p_max) {
 
 
-	Ref<CurveTexture> curve = p_texture;
-	if (!curve.is_valid())
+	Ref<CurveTexture> curve_tex = p_texture;
+	if (!curve_tex.is_valid())
 		return;
 		return;
 
 
-	if (curve->get_max() == 1.0) {
-		curve->set_max(p_max);
-	}
-	if (curve->get_min() == 0.0) {
-		curve->set_min(p_min);
-	}
+	curve_tex->ensure_default_setup(p_min, p_max);
 }
 }
 
 
 void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture> &p_texture) {
 void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture> &p_texture) {

+ 118 - 0
scene/resources/curve.cpp

@@ -380,9 +380,13 @@ Curve2D::Curve2D()
 
 
 #endif
 #endif
 
 
+const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed";
+
 Curve::Curve() {
 Curve::Curve() {
 	_bake_resolution = 100;
 	_bake_resolution = 100;
 	_baked_cache_dirty = false;
 	_baked_cache_dirty = false;
+	_min_value = 0;
+	_max_value = 1;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	_disable_set_data = false;
 	_disable_set_data = false;
 #endif
 #endif
@@ -496,12 +500,38 @@ void Curve::clean_dupes() {
 void Curve::set_point_left_tangent(int i, real_t tangent) {
 void Curve::set_point_left_tangent(int i, real_t tangent) {
 	ERR_FAIL_INDEX(i, _points.size());
 	ERR_FAIL_INDEX(i, _points.size());
 	_points[i].left_tangent = tangent;
 	_points[i].left_tangent = tangent;
+	_points[i].left_mode = TANGENT_FREE;
 	mark_dirty();
 	mark_dirty();
 }
 }
 
 
 void Curve::set_point_right_tangent(int i, real_t tangent) {
 void Curve::set_point_right_tangent(int i, real_t tangent) {
 	ERR_FAIL_INDEX(i, _points.size());
 	ERR_FAIL_INDEX(i, _points.size());
 	_points[i].right_tangent = tangent;
 	_points[i].right_tangent = tangent;
+	_points[i].right_mode = TANGENT_FREE;
+	mark_dirty();
+}
+
+void Curve::set_point_left_mode(int i, TangentMode p_mode) {
+	ERR_FAIL_INDEX(i, _points.size());
+	_points[i].left_mode = p_mode;
+	if (i > 0) {
+		if (p_mode == TANGENT_LINEAR) {
+			Vector2 v = (_points[i - 1].pos - _points[i].pos).normalized();
+			_points[i].left_tangent = v.y / v.x;
+		}
+	}
+	mark_dirty();
+}
+
+void Curve::set_point_right_mode(int i, TangentMode p_mode) {
+	ERR_FAIL_INDEX(i, _points.size());
+	_points[i].right_mode = p_mode;
+	if (i + 1 < _points.size()) {
+		if (p_mode == TANGENT_LINEAR) {
+			Vector2 v = (_points[i + 1].pos - _points[i].pos).normalized();
+			_points[i].right_tangent = v.y / v.x;
+		}
+	}
 	mark_dirty();
 	mark_dirty();
 }
 }
 
 
@@ -515,6 +545,16 @@ real_t Curve::get_point_right_tangent(int i) const {
 	return _points[i].right_tangent;
 	return _points[i].right_tangent;
 }
 }
 
 
+Curve::TangentMode Curve::get_point_left_mode(int i) const {
+	ERR_FAIL_INDEX_V(i, _points.size(), TANGENT_FREE);
+	return _points[i].left_mode;
+}
+
+Curve::TangentMode Curve::get_point_right_mode(int i) const {
+	ERR_FAIL_INDEX_V(i, _points.size(), TANGENT_FREE);
+	return _points[i].right_mode;
+}
+
 void Curve::remove_point(int p_index) {
 void Curve::remove_point(int p_index) {
 	ERR_FAIL_INDEX(p_index, _points.size());
 	ERR_FAIL_INDEX(p_index, _points.size());
 	_points.remove(p_index);
 	_points.remove(p_index);
@@ -529,6 +569,7 @@ void Curve::clear_points() {
 void Curve::set_point_value(int p_index, real_t pos) {
 void Curve::set_point_value(int p_index, real_t pos) {
 	ERR_FAIL_INDEX(p_index, _points.size());
 	ERR_FAIL_INDEX(p_index, _points.size());
 	_points[p_index].pos.y = pos;
 	_points[p_index].pos.y = pos;
+	update_auto_tangents(p_index);
 	mark_dirty();
 	mark_dirty();
 }
 }
 
 
@@ -539,6 +580,11 @@ int Curve::set_point_offset(int p_index, float offset) {
 	int i = add_point(Vector2(offset, p.pos.y));
 	int i = add_point(Vector2(offset, p.pos.y));
 	_points[i].left_tangent = p.left_tangent;
 	_points[i].left_tangent = p.left_tangent;
 	_points[i].right_tangent = p.right_tangent;
 	_points[i].right_tangent = p.right_tangent;
+	_points[i].left_mode = p.left_mode;
+	_points[i].right_mode = p.right_mode;
+	if (p_index != i)
+		update_auto_tangents(p_index);
+	update_auto_tangents(i);
 	return i;
 	return i;
 }
 }
 
 
@@ -547,6 +593,53 @@ Vector2 Curve::get_point_pos(int p_index) const {
 	return _points[p_index].pos;
 	return _points[p_index].pos;
 }
 }
 
 
+void Curve::update_auto_tangents(int i) {
+
+	Point &p = _points[i];
+
+	if (i > 0) {
+		if (p.left_mode == TANGENT_LINEAR) {
+			Vector2 v = (_points[i - 1].pos - p.pos).normalized();
+			p.left_tangent = v.y / v.x;
+		}
+		if (_points[i - 1].right_mode == TANGENT_LINEAR) {
+			Vector2 v = (_points[i - 1].pos - p.pos).normalized();
+			_points[i - 1].right_tangent = v.y / v.x;
+		}
+	}
+
+	if (i + 1 < _points.size()) {
+		if (p.right_mode == TANGENT_LINEAR && i + 1 < _points.size()) {
+			Vector2 v = (_points[i + 1].pos - p.pos).normalized();
+			p.right_tangent = v.y / v.x;
+		}
+		if (_points[i + 1].left_mode == TANGENT_LINEAR) {
+			Vector2 v = (_points[i + 1].pos - p.pos).normalized();
+			_points[i + 1].left_tangent = v.y / v.x;
+		}
+	}
+}
+
+#define MIN_Y_RANGE 0.01
+
+void Curve::set_min_value(float p_min) {
+	if (p_min > _max_value - MIN_Y_RANGE)
+		_min_value = _max_value - MIN_Y_RANGE;
+	else
+		_min_value = p_min;
+	// Note: min and max are indicative values,
+	// it's still possible that existing points are out of range at this point.
+	emit_signal(SIGNAL_RANGE_CHANGED);
+}
+
+void Curve::set_max_value(float p_max) {
+	if (p_max < _min_value + MIN_Y_RANGE)
+		_max_value = _min_value + MIN_Y_RANGE;
+	else
+		_max_value = p_max;
+	emit_signal(SIGNAL_RANGE_CHANGED);
+}
+
 real_t Curve::interpolate(real_t offset) const {
 real_t Curve::interpolate(real_t offset) const {
 	if (_points.size() == 0)
 	if (_points.size() == 0)
 		return 0;
 		return 0;
@@ -636,6 +729,14 @@ void Curve::set_data(Array input) {
 		ERR_FAIL_COND(input[i].get_type() != Variant::VECTOR2);
 		ERR_FAIL_COND(input[i].get_type() != Variant::VECTOR2);
 		ERR_FAIL_COND(input[i + 1].get_type() != Variant::REAL);
 		ERR_FAIL_COND(input[i + 1].get_type() != Variant::REAL);
 		ERR_FAIL_COND(input[i + 2].get_type() != Variant::REAL);
 		ERR_FAIL_COND(input[i + 2].get_type() != Variant::REAL);
+
+		ERR_FAIL_COND(input[i + 3].get_type() != Variant::INT);
+		int left_mode = input[i + 3];
+		ERR_FAIL_COND(left_mode < 0 || left_mode >= TANGENT_MODE_COUNT);
+
+		ERR_FAIL_COND(input[i + 4].get_type() != Variant::INT);
+		int right_mode = input[i + 4];
+		ERR_FAIL_COND(right_mode < 0 || right_mode >= TANGENT_MODE_COUNT);
 	}
 	}
 
 
 	_points.resize(input.size() / 3);
 	_points.resize(input.size() / 3);
@@ -648,6 +749,11 @@ void Curve::set_data(Array input) {
 		p.pos = input[i];
 		p.pos = input[i];
 		p.left_tangent = input[i + 1];
 		p.left_tangent = input[i + 1];
 		p.right_tangent = input[i + 2];
 		p.right_tangent = input[i + 2];
+		// TODO For some reason the compiler won't convert from Variant to enum
+		int left_mode = input[i + 3];
+		int right_mode = input[i + 4];
+		p.left_mode = (TangentMode)left_mode;
+		p.right_mode = (TangentMode)right_mode;
 	}
 	}
 
 
 	mark_dirty();
 	mark_dirty();
@@ -726,8 +832,16 @@ void Curve::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("interpolate_baked", "offset"), &Curve::interpolate_baked);
 	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_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("get_point_right_tangent", "index"), &Curve::get_point_left_tangent);
+	ClassDB::bind_method(D_METHOD("get_point_left_mode", "index"), &Curve::get_point_left_mode);
+	ClassDB::bind_method(D_METHOD("get_point_right_mode", "index"), &Curve::get_point_left_mode);
 	ClassDB::bind_method(D_METHOD("set_point_left_tangent", "index", "tangent"), &Curve::set_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("set_point_right_tangent", "index", "tangent"), &Curve::set_point_left_tangent);
+	ClassDB::bind_method(D_METHOD("set_point_left_mode", "index", "mode"), &Curve::set_point_left_mode);
+	ClassDB::bind_method(D_METHOD("set_point_right_mode", "index", "mode"), &Curve::set_point_left_mode);
+	ClassDB::bind_method(D_METHOD("get_min_value"), &Curve::get_min_value);
+	ClassDB::bind_method(D_METHOD("set_min_value", "min"), &Curve::set_min_value);
+	ClassDB::bind_method(D_METHOD("get_max_value"), &Curve::get_max_value);
+	ClassDB::bind_method(D_METHOD("set_max_value", "max"), &Curve::set_max_value);
 	ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes);
 	ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes);
 	ClassDB::bind_method(D_METHOD("bake"), &Curve::bake);
 	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("get_bake_resolution"), &Curve::get_bake_resolution);
@@ -735,8 +849,12 @@ void Curve::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data);
 	ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data);
 	ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data);
 	ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data);
 
 
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_min_value", "get_min_value");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_max_value", "get_max_value");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution");
 	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");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data");
+
+	ADD_SIGNAL(MethodInfo(SIGNAL_RANGE_CHANGED));
 }
 }
 
 
 int Curve2D::get_point_count() const {
 int Curve2D::get_point_count() const {

+ 31 - 0
scene/resources/curve.h

@@ -89,24 +89,38 @@ public:
 	static const int MIN_X = 0.f;
 	static const int MIN_X = 0.f;
 	static const int MAX_X = 1.f;
 	static const int MAX_X = 1.f;
 
 
+	static const char *SIGNAL_RANGE_CHANGED;
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	bool _disable_set_data;
 	bool _disable_set_data;
 #endif
 #endif
 
 
+	enum TangentMode {
+		TANGENT_FREE = 0,
+		TANGENT_LINEAR,
+		TANGENT_MODE_COUNT
+	};
+
 	struct Point {
 	struct Point {
 		Vector2 pos;
 		Vector2 pos;
 		real_t left_tangent;
 		real_t left_tangent;
 		real_t right_tangent;
 		real_t right_tangent;
+		TangentMode left_mode;
+		TangentMode right_mode;
 
 
 		Point() {
 		Point() {
 			left_tangent = 0;
 			left_tangent = 0;
 			right_tangent = 0;
 			right_tangent = 0;
+			left_mode = TANGENT_FREE;
+			right_mode = TANGENT_FREE;
 		}
 		}
 
 
 		Point(Vector2 p, real_t left = 0, real_t right = 0) {
 		Point(Vector2 p, real_t left = 0, real_t right = 0) {
 			pos = p;
 			pos = p;
 			left_tangent = left;
 			left_tangent = left;
 			right_tangent = right;
 			right_tangent = right;
+			left_mode = TANGENT_FREE;
+			right_mode = TANGENT_FREE;
 		}
 		}
 	};
 	};
 
 
@@ -124,6 +138,12 @@ public:
 	int set_point_offset(int p_index, float offset);
 	int set_point_offset(int p_index, float offset);
 	Vector2 get_point_pos(int p_index) const;
 	Vector2 get_point_pos(int p_index) const;
 
 
+	float get_min_value() const { return _min_value; }
+	void set_min_value(float p_min);
+
+	float get_max_value() const { return _max_value; }
+	void set_max_value(float p_max);
+
 	real_t interpolate(real_t offset) const;
 	real_t interpolate(real_t offset) const;
 	real_t interpolate_local_nocheck(int index, real_t local_offset) const;
 	real_t interpolate_local_nocheck(int index, real_t local_offset) const;
 
 
@@ -131,8 +151,15 @@ public:
 
 
 	void set_point_left_tangent(int i, real_t tangent);
 	void set_point_left_tangent(int i, real_t tangent);
 	void set_point_right_tangent(int i, real_t tangent);
 	void set_point_right_tangent(int i, real_t tangent);
+	void set_point_left_mode(int i, TangentMode p_mode);
+	void set_point_right_mode(int i, TangentMode p_mode);
+
 	real_t get_point_left_tangent(int i) const;
 	real_t get_point_left_tangent(int i) const;
 	real_t get_point_right_tangent(int i) const;
 	real_t get_point_right_tangent(int i) const;
+	TangentMode get_point_left_mode(int i) const;
+	TangentMode get_point_right_mode(int i) const;
+
+	void update_auto_tangents(int i);
 
 
 	Array get_data() const;
 	Array get_data() const;
 	void set_data(Array input);
 	void set_data(Array input);
@@ -152,8 +179,12 @@ private:
 	bool _baked_cache_dirty;
 	bool _baked_cache_dirty;
 	Vector<real_t> _baked_cache;
 	Vector<real_t> _baked_cache;
 	int _bake_resolution;
 	int _bake_resolution;
+	float _min_value;
+	float _max_value;
 };
 };
 
 
+VARIANT_ENUM_CAST(Curve::TangentMode)
+
 class Curve2D : public Resource {
 class Curve2D : public Resource {
 
 
 	GDCLASS(Curve2D, Resource);
 	GDCLASS(Curve2D, Resource);

+ 6 - 38
scene/resources/texture.cpp

@@ -1370,12 +1370,6 @@ CubeMap::~CubeMap() {
 
 
 void CurveTexture::_bind_methods() {
 void CurveTexture::_bind_methods() {
 
 
-	ClassDB::bind_method(D_METHOD("set_max", "max"), &CurveTexture::set_max);
-	ClassDB::bind_method(D_METHOD("get_max"), &CurveTexture::get_max);
-
-	ClassDB::bind_method(D_METHOD("set_min", "min"), &CurveTexture::set_min);
-	ClassDB::bind_method(D_METHOD("get_min"), &CurveTexture::get_min);
-
 	ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width);
 	ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width);
 
 
 	ClassDB::bind_method(D_METHOD("set_curve", "curve:Curve"), &CurveTexture::set_curve);
 	ClassDB::bind_method(D_METHOD("set_curve", "curve:Curve"), &CurveTexture::set_curve);
@@ -1383,52 +1377,31 @@ void CurveTexture::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update);
 	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::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
 	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;
-	emit_changed();
-}
-float CurveTexture::get_max() const {
 
 
-	return _max;
-}
-
-void CurveTexture::set_min(float p_min) {
-
-	_min = p_min;
-	emit_changed();
-}
-float CurveTexture::get_min() const {
-
-	return _min;
-}
 void CurveTexture::set_width(int p_width) {
 void CurveTexture::set_width(int p_width) {
 
 
 	ERR_FAIL_COND(p_width < 32 || p_width > 4096);
 	ERR_FAIL_COND(p_width < 32 || p_width > 4096);
 	_width = p_width;
 	_width = p_width;
 	_update();
 	_update();
 }
 }
+
 int CurveTexture::get_width() const {
 int CurveTexture::get_width() const {
 
 
 	return _width;
 	return _width;
 }
 }
 
 
-void CurveTexture::ensure_default_setup() {
-
+void CurveTexture::ensure_default_setup(float p_min, float p_max) {
 	if (_curve.is_null()) {
 	if (_curve.is_null()) {
 		Ref<Curve> curve = Ref<Curve>(memnew(Curve));
 		Ref<Curve> curve = Ref<Curve>(memnew(Curve));
 		curve->add_point(Vector2(0, 1));
 		curve->add_point(Vector2(0, 1));
 		curve->add_point(Vector2(1, 1));
 		curve->add_point(Vector2(1, 1));
+		curve->set_min_value(p_min);
+		curve->set_max_value(p_max);
 		set_curve(curve);
 		set_curve(curve);
-	}
-
-	if (get_min() == 0 && get_max() == 1) {
-		set_max(32);
+		// Min and max is 0..1 by default
 	}
 	}
 }
 }
 
 
@@ -1457,11 +1430,9 @@ void CurveTexture::_update() {
 
 
 		if (_curve.is_valid()) {
 		if (_curve.is_valid()) {
 			Curve &curve = **_curve;
 			Curve &curve = **_curve;
-			float height = _max - _min;
 			for (int i = 0; i < _width; ++i) {
 			for (int i = 0; i < _width; ++i) {
 				float t = i / static_cast<float>(_width);
 				float t = i / static_cast<float>(_width);
-				float v = curve.interpolate_baked(t);
-				wd[i] = CLAMP(_min + v * height, _min, _max);
+				wd[i] = curve.interpolate_baked(t);
 			}
 			}
 
 
 		} else {
 		} else {
@@ -1490,9 +1461,6 @@ RID CurveTexture::get_rid() const {
 }
 }
 
 
 CurveTexture::CurveTexture() {
 CurveTexture::CurveTexture() {
-
-	_max = 1;
-	_min = 0;
 	_width = 2048;
 	_width = 2048;
 	_texture = VS::get_singleton()->texture_create();
 	_texture = VS::get_singleton()->texture_create();
 }
 }

+ 1 - 8
scene/resources/texture.h

@@ -403,7 +403,6 @@ class CurveTexture : public Texture {
 private:
 private:
 	RID _texture;
 	RID _texture;
 	Ref<Curve> _curve;
 	Ref<Curve> _curve;
-	float _min, _max;
 	int _width;
 	int _width;
 
 
 	void _update();
 	void _update();
@@ -412,16 +411,10 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
 public:
 public:
-	void set_max(float p_max);
-	float get_max() const;
-
-	void set_min(float p_min);
-	float get_min() const;
-
 	void set_width(int p_width);
 	void set_width(int p_width);
 	int get_width() const;
 	int get_width() const;
 
 
-	void ensure_default_setup();
+	void ensure_default_setup(float p_min=0, float p_max=1);
 
 
 	void set_curve(Ref<Curve> p_curve);
 	void set_curve(Ref<Curve> p_curve);
 	Ref<Curve> get_curve() const;
 	Ref<Curve> get_curve() const;