diff --git a/doc/classes/Gradient.xml b/doc/classes/Gradient.xml index 1fe804213e90..7cdffd14d3b1 100644 --- a/doc/classes/Gradient.xml +++ b/doc/classes/Gradient.xml @@ -72,10 +72,22 @@ Gradient's colors returned as a [PoolColorArray]. + + Defines how the colors between points of the gradient are interpolated. See [enum InterpolationMode] for available modes. + Gradient's offsets returned as a [PoolRealArray]. + + Linear interpolation. + + + Constant interpolation, color changes abruptly at each point and stays uniform between. This might cause visible aliasing when used for a gradient texture in some cases. + + + Cubic interpolation. + diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 1f3ccb021628..6734c38be2fc 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -45,6 +45,8 @@ void GradientEditor::_gradient_changed() { editing = true; Vector points = gradient->get_points(); set_points(points); + set_interpolation_mode(gradient->get_interpolation_mode()); + update(); editing = false; } @@ -54,8 +56,10 @@ void GradientEditor::_ramp_changed() { undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); + undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); + undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); undo_redo->commit_action(); editing = false; } @@ -70,6 +74,7 @@ void GradientEditor::set_gradient(const Ref &p_gradient) { connect("ramp_changed", this, "_ramp_changed"); gradient->connect("changed", this, "_gradient_changed"); set_points(gradient->get_points()); + set_interpolation_mode(gradient->get_interpolation_mode()); } GradientEditor::GradientEditor() { diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 2d4971495f96..2cb9f4558bc0 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -52,6 +52,11 @@ GradientEdit::GradientEdit() { add_child(popup); + gradient_cache.instance(); + preview_texture.instance(); + + preview_texture->set_width(1024); + checker = Ref(memnew(ImageTexture)); Ref img = memnew(Image(checker_bg_png)); checker->create_from_image(img, ImageTexture::FLAG_REPEAT); @@ -315,46 +320,10 @@ void GradientEdit::_notification(int p_what) { _draw_checker(0, 0, total_w, h); //Draw color ramp - Gradient::Point prev; - prev.offset = 0; - if (points.size() == 0) { - prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - prev.color = points[0].color; //Extend color of first point to the beginning. - } - - for (int i = -1; i < points.size(); i++) { - Gradient::Point next; - //If there is no next point - if (i + 1 == points.size()) { - if (points.size() == 0) { - next.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - next.color = points[i].color; //Extend color of last point to the end. - } - next.offset = 1; - } else { - next = points[i + 1]; - } - - if (prev.offset == next.offset) { - prev = next; - continue; - } - - Vector points; - Vector colors; - points.push_back(Vector2(prev.offset * total_w, h)); - points.push_back(Vector2(prev.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, h)); - colors.push_back(prev.color); - colors.push_back(prev.color); - colors.push_back(next.color); - colors.push_back(next.color); - draw_primitive(points, colors, Vector()); - prev = next; - } + gradient_cache->set_points(points); + gradient_cache->set_interpolation_mode(interpolation_mode); + preview_texture->set_gradient(gradient_cache); + draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h)); //Draw point markers for (int i = 0; i < points.size(); i++) { @@ -482,6 +451,14 @@ Vector &GradientEdit::get_points() { return points; } +void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { + interpolation_mode = p_interp_mode; +} + +Gradient::InterpolationMode GradientEdit::get_interpolation_mode() { + return interpolation_mode; +} + void GradientEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &GradientEdit::_gui_input); ClassDB::bind_method(D_METHOD("_color_changed"), &GradientEdit::_color_changed); diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index 9b6a9c259177..00d1af807832 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -47,6 +47,10 @@ class GradientEdit : public Control { bool grabbing; int grabbed; Vector points; + Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; + + Ref gradient_cache; + Ref preview_texture; void _draw_checker(int x, int y, int w, int h); void _color_changed(const Color &p_color); @@ -64,6 +68,9 @@ class GradientEdit : public Control { Vector get_colors() const; void set_points(Vector &p_points); Vector &get_points(); + void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); + Gradient::InterpolationMode get_interpolation_mode(); + virtual Size2 get_minimum_size() const; GradientEdit(); diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp index ffeb5f1684aa..66c28b6e8df0 100644 --- a/scene/resources/gradient.cpp +++ b/scene/resources/gradient.cpp @@ -71,8 +71,18 @@ void Gradient::_bind_methods() { ClassDB::bind_method(D_METHOD(COLOR_RAMP_SET_COLORS, "colors"), &Gradient::set_colors); ClassDB::bind_method(D_METHOD(COLOR_RAMP_GET_COLORS), &Gradient::get_colors); + ClassDB::bind_method(D_METHOD("set_interpolation_mode", "interpolation_mode"), &Gradient::set_interpolation_mode); + ClassDB::bind_method(D_METHOD("get_interpolation_mode"), &Gradient::get_interpolation_mode); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode"); + + ADD_GROUP("Raw Data", ""); ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "offsets"), COLOR_RAMP_SET_OFFSETS, COLOR_RAMP_GET_OFFSETS); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "colors"), COLOR_RAMP_SET_COLORS, COLOR_RAMP_GET_COLORS); + + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR); + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT); + BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC); } Vector Gradient::get_offsets() const { @@ -93,6 +103,15 @@ Vector Gradient::get_colors() const { return colors; } +void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { + interpolation_mode = p_interp_mode; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +Gradient::InterpolationMode Gradient::get_interpolation_mode() { + return interpolation_mode; +} + void Gradient::set_offsets(const Vector &p_offsets) { points.resize(p_offsets.size()); for (int i = 0; i < points.size(); i++) { diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index 70a50e7e8be3..521540518881 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -38,6 +38,12 @@ class Gradient : public Resource { OBJ_SAVE_TYPE(Gradient); public: + enum InterpolationMode { + GRADIENT_INTERPOLATE_LINEAR, + GRADIENT_INTERPOLATE_CONSTANT, + GRADIENT_INTERPOLATE_CUBIC, + }; + struct Point { float offset; Color color; @@ -49,6 +55,8 @@ class Gradient : public Resource { private: Vector points; bool is_sorted; + InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR; + _FORCE_INLINE_ void _update_sorting() { if (!is_sorted) { points.sort(); @@ -81,6 +89,13 @@ class Gradient : public Resource { void set_colors(const Vector &p_colors); Vector get_colors() const; + void set_interpolation_mode(InterpolationMode p_interp_mode); + InterpolationMode get_interpolation_mode(); + + _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) { + return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0))); + } + _FORCE_INLINE_ Color get_color_at_offset(float p_offset) { if (points.empty()) { return Color(0, 0, 0, 1); @@ -124,10 +139,44 @@ class Gradient : public Resource { } const Point &pointFirst = points[first]; const Point &pointSecond = points[second]; - return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + + switch (interpolation_mode) { + case GRADIENT_INTERPOLATE_LINEAR: { + return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + } break; + case GRADIENT_INTERPOLATE_CONSTANT: { + return pointFirst.color; + } break; + case GRADIENT_INTERPOLATE_CUBIC: { + int p0 = first - 1; + int p3 = second + 1; + if (p3 >= points.size()) { + p3 = second; + } + if (p0 < 0) { + p0 = first; + } + const Point &pointP0 = points[p0]; + const Point &pointP3 = points[p3]; + + float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset); + float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x); + float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x); + float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x); + float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x); + + return Color(r, g, b, a); + } break; + default: { + // Fallback to linear interpolation. + return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + } + } } int get_points_count() const; }; +VARIANT_ENUM_CAST(Gradient::InterpolationMode); + #endif // GRADIENT_H