From 97074dcf95dcb91f88a8345859c21ae7ecc475a6 Mon Sep 17 00:00:00 2001 From: Hendrik Brucker Date: Tue, 12 Oct 2021 16:22:30 +0200 Subject: [PATCH] Add GradientTexture2D Co-authored-by: Mariano Javier Suligoy Co-authored-by: Andrii Doroshenko --- doc/classes/GradientTexture2D.xml | 57 +++++ editor/icons/icon_gradient_texture_2_d.svg | 1 + scene/register_scene_types.cpp | 1 + scene/resources/texture.cpp | 282 ++++++++++++++++++++- scene/resources/texture.h | 76 ++++++ 5 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 doc/classes/GradientTexture2D.xml create mode 100644 editor/icons/icon_gradient_texture_2_d.svg diff --git a/doc/classes/GradientTexture2D.xml b/doc/classes/GradientTexture2D.xml new file mode 100644 index 00000000000..79b66998c6d --- /dev/null +++ b/doc/classes/GradientTexture2D.xml @@ -0,0 +1,57 @@ + + + + Gradient-filled 2D texture. + + + The texture uses a [Gradient] to fill the texture data in 2D space. The gradient is filled according to the specified [member fill] and [member repeat] types using colors obtained from the gradient. The texture does not necessarily represent an exact copy of the gradient, but instead an interpolation of samples obtained from the gradient at fixed steps (see [member width] and [member height]). + + + + + + + + The gradient fill type, one of the [enum Fill] values. The texture is filled by interpolating colors starting from [member fill_from] to [member fill_to] offsets. + + + The initial offset used to fill the texture specified in UV coordinates. + + + The final offset used to fill the texture specified in UV coordinates. + + + + The [Gradient] used to fill the texture. + + + The number of vertical color samples that will be obtained from the [Gradient], which also represents the texture's height. + + + The gradient repeat type, one of the [enum Repeat] values. The texture is filled starting from [member fill_from] to [member fill_to] offsets by default, but the gradient fill can be repeated to cover the entire texture. + + + If [code]true[/code], the generated texture will support high dynamic range ([constant Image.FORMAT_RGBAF] format). This allows for glow effects to work if [member Environment.glow_enabled] is [code]true[/code]. If [code]false[/code], the generated texture will use low dynamic range; overbright colors will be clamped ([constant Image.FORMAT_RGBA8] format). + + + The number of horizontal color samples that will be obtained from the [Gradient], which also represents the texture's width. + + + + + The colors are linearly interpolated in a straight line. + + + The colors are linearly interpolated in a circular pattern. + + + The gradient fill is restricted to the range defined by [member fill_from] to [member fill_to] offsets. + + + The texture is filled starting from [member fill_from] to [member fill_to] offsets, repeating the same pattern in both directions. + + + The texture is filled starting from [member fill_from] to [member fill_to] offsets, mirroring the pattern in both directions. + + + diff --git a/editor/icons/icon_gradient_texture_2_d.svg b/editor/icons/icon_gradient_texture_2_d.svg new file mode 100644 index 00000000000..ef40323b8c7 --- /dev/null +++ b/editor/icons/icon_gradient_texture_2_d.svg @@ -0,0 +1 @@ + diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index f93f8f249f5..9430a4eb3c4 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -677,6 +677,7 @@ void register_scene_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 91a2355521a..78969c406ec 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -32,6 +32,7 @@ #include "core/core_string_names.h" #include "core/io/image_loader.h" +#include "core/math/geometry.h" #include "core/method_bind_ext.gen.inc" #include "core/os/os.h" #include "mesh.h" @@ -186,6 +187,7 @@ void ImageTexture::create(int p_width, int p_height, Image::Format p_format, uin _change_notify(); emit_changed(); } + void ImageTexture::create_from_image(const Ref &p_image, uint32_t p_flags) { ERR_FAIL_COND_MSG(p_image.is_null() || p_image->empty(), "Invalid image"); flags = p_flags; @@ -281,6 +283,7 @@ void ImageTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_m RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); VisualServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose, normal_rid); } + void ImageTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose, const Ref &p_normal_map) const { if ((w | h) == 0) { return; @@ -288,6 +291,7 @@ void ImageTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); VisualServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose, normal_rid); } + void ImageTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, const Ref &p_normal_map, bool p_clip_uv) const { if ((w | h) == 0) { return; @@ -1803,6 +1807,280 @@ Ref GradientTexture::get_data() const { return VisualServer::get_singleton()->texture_get_data(texture); } +GradientTexture2D::GradientTexture2D() { + _queue_update(); +} + +GradientTexture2D::~GradientTexture2D() { + if (texture.is_valid()) { + VS::get_singleton()->free(texture); + } +} + +void GradientTexture2D::set_gradient(Ref p_gradient) { + if (gradient == p_gradient) { + return; + } + if (gradient.is_valid()) { + gradient->disconnect(CoreStringNames::get_singleton()->changed, this, "_queue_update"); + } + gradient = p_gradient; + if (gradient.is_valid()) { + gradient->connect(CoreStringNames::get_singleton()->changed, this, "_queue_update"); + } + _queue_update(); +} + +Ref GradientTexture2D::get_gradient() const { + return gradient; +} + +void GradientTexture2D::_queue_update() { + if (update_pending) { + return; + } + update_pending = true; + call_deferred("_update"); +} + +void GradientTexture2D::_update() { + update_pending = false; + + if (gradient.is_null()) { + return; + } + Ref image; + image.instance(); + + if (gradient->get_points_count() <= 1) { // No need to interpolate. + image->create(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); + image->fill((gradient->get_points_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1)); + } else { + if (use_hdr) { + image->create(width, height, false, Image::FORMAT_RGBAF); + Gradient &g = **gradient; + // `create()` isn't available for non-uint8_t data, so fill in the data manually. + image->lock(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float ofs = _get_gradient_offset_at(x, y); + image->set_pixel(x, y, g.get_color_at_offset(ofs)); + } + } + image->unlock(); + } else { + PoolVector data; + data.resize(width * height * 4); + { + uint8_t *wd8 = data.write().ptr(); + Gradient &g = **gradient; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float ofs = _get_gradient_offset_at(x, y); + const Color &c = g.get_color_at_offset(ofs); + + wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255)); + } + } + } + image->create(width, height, false, Image::FORMAT_RGBA8, data); + } + } + + if (texture.is_valid()) { + VS::get_singleton()->free(texture); + texture = VS::get_singleton()->texture_create_from_image(image); + } else { + texture = VS::get_singleton()->texture_create_from_image(image); + } + + emit_changed(); +} + +float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { + if (fill_to == fill_from) { + return 0; + } + float ofs = 0; + Vector2 pos; + if (width > 1) { + pos.x = static_cast(x) / (width - 1); + } + if (height > 1) { + pos.y = static_cast(y) / (height - 1); + } + if (fill == Fill::FILL_LINEAR) { + Vector2 segment[2]; + segment[0] = fill_from; + segment[1] = fill_to; + Vector2 closest = Geometry::get_closest_point_to_segment_uncapped_2d(pos, &segment[0]); + ofs = (closest - fill_from).length() / (fill_to - fill_from).length(); + if ((closest - fill_from).dot(fill_to - fill_from) < 0) { + ofs *= -1; + } + } else if (fill == Fill::FILL_RADIAL) { + ofs = (pos - fill_from).length() / (fill_to - fill_from).length(); + } + if (repeat == Repeat::REPEAT_NONE) { + ofs = CLAMP(ofs, 0.0, 1.0); + } else if (repeat == Repeat::REPEAT) { + ofs = Math::fmod(ofs, 1.0f); + if (ofs < 0) { + ofs = 1 + ofs; + } + } else if (repeat == Repeat::REPEAT_MIRROR) { + ofs = Math::abs(ofs); + ofs = Math::fmod(ofs, 2.0f); + if (ofs > 1.0) { + ofs = 2.0 - ofs; + } + } + return ofs; +} + +void GradientTexture2D::set_width(int p_width) { + width = p_width; + _queue_update(); +} + +int GradientTexture2D::get_width() const { + return width; +} + +void GradientTexture2D::set_height(int p_height) { + height = p_height; + _queue_update(); +} + +int GradientTexture2D::get_height() const { + return height; +} + +void GradientTexture2D::set_flags(uint32_t p_flags) { + if (p_flags == flags) { + return; + } + + flags = p_flags; + VS::get_singleton()->texture_set_flags(texture, flags); + _change_notify("flags"); + emit_changed(); +} + +uint32_t GradientTexture2D::get_flags() const { + return flags; +} + +void GradientTexture2D::set_use_hdr(bool p_enabled) { + if (p_enabled == use_hdr) { + return; + } + + use_hdr = p_enabled; + _queue_update(); +} + +bool GradientTexture2D::is_using_hdr() const { + return use_hdr; +} + +void GradientTexture2D::set_fill_from(Vector2 p_fill_from) { + fill_from = p_fill_from; + _queue_update(); +} + +Vector2 GradientTexture2D::get_fill_from() const { + return fill_from; +} + +void GradientTexture2D::set_fill_to(Vector2 p_fill_to) { + fill_to = p_fill_to; + _queue_update(); +} + +Vector2 GradientTexture2D::get_fill_to() const { + return fill_to; +} + +void GradientTexture2D::set_fill(Fill p_fill) { + fill = p_fill; + _queue_update(); +} + +GradientTexture2D::Fill GradientTexture2D::get_fill() const { + return fill; +} + +void GradientTexture2D::set_repeat(Repeat p_repeat) { + repeat = p_repeat; + _queue_update(); +} + +GradientTexture2D::Repeat GradientTexture2D::get_repeat() const { + return repeat; +} + +RID GradientTexture2D::get_rid() const { + if (!texture.is_valid()) { + texture = RID(); + } + return texture; +} + +Ref GradientTexture2D::get_image() const { + if (!texture.is_valid()) { + return Ref(); + } + return VS::get_singleton()->texture_get_data(texture); +} + +void GradientTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture2D::set_gradient); + ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture2D::get_gradient); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture2D::set_width); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GradientTexture2D::set_height); + + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture2D::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture2D::is_using_hdr); + + ClassDB::bind_method(D_METHOD("set_fill", "fill"), &GradientTexture2D::set_fill); + ClassDB::bind_method(D_METHOD("get_fill"), &GradientTexture2D::get_fill); + ClassDB::bind_method(D_METHOD("set_fill_from", "fill_from"), &GradientTexture2D::set_fill_from); + ClassDB::bind_method(D_METHOD("get_fill_from"), &GradientTexture2D::get_fill_from); + ClassDB::bind_method(D_METHOD("set_fill_to", "fill_to"), &GradientTexture2D::set_fill_to); + ClassDB::bind_method(D_METHOD("get_fill_to"), &GradientTexture2D::get_fill_to); + + ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &GradientTexture2D::set_repeat); + ClassDB::bind_method(D_METHOD("get_repeat"), &GradientTexture2D::get_repeat); + + ClassDB::bind_method(D_METHOD("_update"), &GradientTexture2D::_update); + ClassDB::bind_method(D_METHOD("_queue_update"), &GradientTexture2D::_queue_update); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); + + ADD_GROUP("Fill", "fill_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill", PROPERTY_HINT_ENUM, "Linear,Radial"), "set_fill", "get_fill"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_from"), "set_fill_from", "get_fill_from"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_to"), "set_fill_to", "get_fill_to"); + + ADD_GROUP("Repeat", "repeat_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat", PROPERTY_HINT_ENUM, "No Repeat,Repeat,Mirror Repeat"), "set_repeat", "get_repeat"); + + BIND_ENUM_CONSTANT(FILL_LINEAR); + BIND_ENUM_CONSTANT(FILL_RADIAL); + + BIND_ENUM_CONSTANT(REPEAT_NONE); + BIND_ENUM_CONSTANT(REPEAT); + BIND_ENUM_CONSTANT(REPEAT_MIRROR); +} + ////////////////////////////////////// void ProxyTexture::_bind_methods() { @@ -2046,8 +2324,8 @@ bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const { return true; } -void AnimatedTexture::set_flags(uint32_t p_flags) { -} +void AnimatedTexture::set_flags(uint32_t p_flags) {} + uint32_t AnimatedTexture::get_flags() const { RWLockRead r(rw_lock); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index f7692c08ddb..95035e2823d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -649,6 +649,82 @@ public: virtual ~GradientTexture(); }; +class GradientTexture2D : public Texture { + GDCLASS(GradientTexture2D, Texture); + +public: + enum Fill { + FILL_LINEAR, + FILL_RADIAL, + }; + enum Repeat { + REPEAT_NONE, + REPEAT, + REPEAT_MIRROR, + }; + +private: + Ref gradient; + mutable RID texture; + + int width = 64; + int height = 64; + + uint32_t flags = FLAGS_DEFAULT; + + bool use_hdr = false; + + Vector2 fill_from; + Vector2 fill_to = Vector2(1, 0); + + Fill fill = FILL_LINEAR; + Repeat repeat = REPEAT_NONE; + + float _get_gradient_offset_at(int x, int y) const; + + bool update_pending = false; + void _queue_update(); + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_gradient(Ref p_gradient); + Ref get_gradient() const; + + void set_width(int p_width); + virtual int get_width() const; + void set_height(int p_height); + virtual int get_height() const; + + virtual void set_flags(uint32_t p_flags); + virtual uint32_t get_flags() const; + + void set_use_hdr(bool p_enabled); + bool is_using_hdr() const; + + void set_fill(Fill p_fill); + Fill get_fill() const; + void set_fill_from(Vector2 p_fill_from); + Vector2 get_fill_from() const; + void set_fill_to(Vector2 p_fill_to); + Vector2 get_fill_to() const; + + void set_repeat(Repeat p_repeat); + Repeat get_repeat() const; + + virtual RID get_rid() const; + virtual bool has_alpha() const { return true; } + virtual Ref get_image() const; + + GradientTexture2D(); + virtual ~GradientTexture2D(); +}; + +VARIANT_ENUM_CAST(GradientTexture2D::Fill); +VARIANT_ENUM_CAST(GradientTexture2D::Repeat); + class ProxyTexture : public Texture { GDCLASS(ProxyTexture, Texture);