diff --git a/AUTHORS.md b/AUTHORS.md index deb1f9ac..d6640065 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -23,3 +23,4 @@ name is available. 98teg Andrii Doroshenko (Xrayez) Daw11 + Mariano Suligoy (MarianoGnu) diff --git a/doc/GradientTexture2D.xml b/doc/GradientTexture2D.xml new file mode 100644 index 00000000..8c6b273e --- /dev/null +++ b/doc/GradientTexture2D.xml @@ -0,0 +1,53 @@ + + + + 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. + + + 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_2d.svg b/editor/icons/icon_gradient_texture_2d.svg new file mode 100644 index 00000000..ec4c4546 --- /dev/null +++ b/editor/icons/icon_gradient_texture_2d.svg @@ -0,0 +1 @@ + diff --git a/goost.py b/goost.py index c41f5c3b..a09bac84 100644 --- a/goost.py +++ b/goost.py @@ -49,6 +49,7 @@ def get_child_components(parent): classes = [ "GoostGeometry2D", "GoostImage", + "GradientTexture2D", "ImageBlender", "ImageIndexed", "LinkedList", diff --git a/scene/SCsub b/scene/SCsub index e326c427..1dd02aa4 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -10,4 +10,5 @@ if not env["disable_3d"]: if env["goost_physics_enabled"]: SConscript("physics/SCsub", exports="env_goost") +env_goost.add_source_files(env.modules_sources, "resources/*.cpp") env_goost.add_source_files(env.modules_sources, "*.cpp") diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index c6a059f6..2df35c06 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -6,9 +6,10 @@ #include "2d/editor/poly_node_2d_editor_plugin.h" #include "2d/editor/visual_shape_2d_editor_plugin.h" #include "2d/poly_collision_shape_2d.h" -#include "2d/poly_shape_2d.h" #include "2d/poly_generators_2d.h" +#include "2d/poly_shape_2d.h" #include "2d/visual_shape_2d.h" +#include "resources/gradient_texture_2d.h" namespace goost { @@ -21,6 +22,7 @@ void register_scene_types() { ClassDB::register_class(); #endif ClassDB::register_class(); + ClassDB::register_class(); #if defined(TOOLS_ENABLED) && defined(GOOST_EDITOR_ENABLED) #ifdef GOOST_CORE_ENABLED diff --git a/scene/resources/gradient_texture_2d.cpp b/scene/resources/gradient_texture_2d.cpp new file mode 100644 index 00000000..316da146 --- /dev/null +++ b/scene/resources/gradient_texture_2d.cpp @@ -0,0 +1,186 @@ +#include "gradient_texture_2d.h" + +#include "core/core_string_names.h" +#include "core/math/geometry.h" + +GradientTexture2D::GradientTexture2D() { + texture = VS::get_singleton()->texture_create(); + _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(); +} + +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; + } + PoolVector data; + data.resize(width * height * 4); + { + PoolVector::Write wd8 = data.write(); + 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); + Color color = g.get_color_at_offset(ofs); + + wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + } + } + } + Ref image = memnew(Image(width, height, false, Image::FORMAT_RGBA8, data)); + + VS::get_singleton()->texture_allocate(texture, width, height, 0, Image::FORMAT_RGBA8, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER); + VS::get_singleton()->texture_set_data(texture, 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(); +} + +void GradientTexture2D::set_height(int p_height) { + height = p_height; + _queue_update(); +} +void GradientTexture2D::set_fill_from(Vector2 p_fill_from) { + fill_from = p_fill_from; + _queue_update(); +} + +void GradientTexture2D::set_fill_to(Vector2 p_fill_to) { + fill_to = p_fill_to; + _queue_update(); +} + +void GradientTexture2D::set_fill(Fill p_fill) { + fill = p_fill; + _queue_update(); +} + +void GradientTexture2D::set_repeat(Repeat p_repeat) { + repeat = p_repeat; + _queue_update(); +} + +Ref GradientTexture2D::get_data() 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_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_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); +} diff --git a/scene/resources/gradient_texture_2d.h b/scene/resources/gradient_texture_2d.h new file mode 100644 index 00000000..ae47f06f --- /dev/null +++ b/scene/resources/gradient_texture_2d.h @@ -0,0 +1,75 @@ +#ifndef GOOST_GRADIENT_TEXTURE_2D +#define GOOST_GRADIENT_TEXTURE_2D + +#include "scene/resources/texture.h" + +class GradientTexture2D : public Texture { + GDCLASS(GradientTexture2D, Texture); + +public: + enum Fill { + FILL_LINEAR, + FILL_RADIAL, + }; + enum Repeat { + REPEAT_NONE, + REPEAT, + REPEAT_MIRROR, + }; + +private: + Ref gradient; + RID texture; + + int width = 64; + int height = 64; + + 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 { return gradient; } + + void set_width(int p_width); + virtual int get_width() const override { return width; } + void set_height(int p_height); + virtual int get_height() const override { return height; }; + + void set_fill(Fill p_fill); + Fill get_fill() const { return fill; } + void set_fill_from(Vector2 p_fill_from); + Vector2 get_fill_from() const { return fill_from; } + void set_fill_to(Vector2 p_fill_to); + Vector2 get_fill_to() const { return fill_to; } + + void set_repeat(Repeat p_repeat); + Repeat get_repeat() const { return repeat; } + + virtual void set_flags(uint32_t p_flags) {} + virtual uint32_t get_flags() const { return FLAG_FILTER; } + + virtual RID get_rid() const override { return texture; } + virtual bool has_alpha() const override { return true; } + virtual Ref get_data() const override; + + GradientTexture2D(); + virtual ~GradientTexture2D(); +}; + +VARIANT_ENUM_CAST(GradientTexture2D::Fill); +VARIANT_ENUM_CAST(GradientTexture2D::Repeat); + +#endif diff --git a/tests/project/goost/scene/resources/test_gradient_texture_2d.gd b/tests/project/goost/scene/resources/test_gradient_texture_2d.gd new file mode 100644 index 00000000..7c32e507 --- /dev/null +++ b/tests/project/goost/scene/resources/test_gradient_texture_2d.gd @@ -0,0 +1,35 @@ +extends "res://addons/gut/test.gd" + +func test_linear(): + var texture = GradientTexture2D.new() + var gradient = Gradient.new() + texture.gradient = gradient + + yield(texture, "changed") + + var image = texture.get_data() + assert_not_null(image) + + image.lock() + assert_eq(image.get_pixel(0, 0), Color.black) + assert_eq(image.get_pixel(texture.get_width() - 1, 0), Color.white) + image.unlock() + + +func test_radial(): + var texture = GradientTexture2D.new() + var gradient = Gradient.new() + texture.gradient = gradient + texture.fill = GradientTexture2D.FILL_RADIAL + texture.fill_from = Vector2(0.5, 0.5) + texture.fill_to = Vector2(0.5, 0) + + yield(texture, "changed") + + var image = texture.get_data() + assert_not_null(image) + + image.lock() + assert_eq(image.get_pixel(0, 0), Color.white) + assert_ne(image.get_pixel(texture.get_width() - 1, 0), Color.black) + image.unlock()