Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport GradientTexture2D to 3.x #54824

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions doc/classes/GradientTexture2D.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GradientTexture2D" inherits="Texture" version="3.5">
<brief_description>
Gradient-filled 2D texture.
</brief_description>
<description>
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]).
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="fill" type="int" setter="set_fill" getter="get_fill" enum="GradientTexture2D.Fill" default="0">
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.
</member>
<member name="fill_from" type="Vector2" setter="set_fill_from" getter="get_fill_from" default="Vector2( 0, 0 )">
The initial offset used to fill the texture specified in UV coordinates.
</member>
<member name="fill_to" type="Vector2" setter="set_fill_to" getter="get_fill_to" default="Vector2( 1, 0 )">
The final offset used to fill the texture specified in UV coordinates.
</member>
<member name="flags" type="int" setter="set_flags" getter="get_flags" override="true" default="7" />
<member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient">
The [Gradient] used to fill the texture.
</member>
<member name="height" type="int" setter="set_height" getter="get_height" default="64">
The number of vertical color samples that will be obtained from the [Gradient], which also represents the texture's height.
</member>
<member name="repeat" type="int" setter="set_repeat" getter="get_repeat" enum="GradientTexture2D.Repeat" default="0">
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.
</member>
<member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="false">
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).
</member>
<member name="width" type="int" setter="set_width" getter="get_width" default="64">
The number of horizontal color samples that will be obtained from the [Gradient], which also represents the texture's width.
</member>
</members>
<constants>
<constant name="FILL_LINEAR" value="0" enum="Fill">
The colors are linearly interpolated in a straight line.
</constant>
<constant name="FILL_RADIAL" value="1" enum="Fill">
The colors are linearly interpolated in a circular pattern.
</constant>
<constant name="REPEAT_NONE" value="0" enum="Repeat">
The gradient fill is restricted to the range defined by [member fill_from] to [member fill_to] offsets.
</constant>
<constant name="REPEAT" value="1" enum="Repeat">
The texture is filled starting from [member fill_from] to [member fill_to] offsets, repeating the same pattern in both directions.
</constant>
<constant name="REPEAT_MIRROR" value="2" enum="Repeat">
The texture is filled starting from [member fill_from] to [member fill_to] offsets, mirroring the pattern in both directions.
</constant>
</constants>
</class>
1 change: 1 addition & 0 deletions editor/icons/icon_gradient_texture_2_d.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions scene/register_scene_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ void register_scene_types() {
ClassDB::register_class<LargeTexture>();
ClassDB::register_class<CurveTexture>();
ClassDB::register_class<GradientTexture>();
ClassDB::register_class<GradientTexture2D>();
ClassDB::register_class<ProxyTexture>();
ClassDB::register_class<AnimatedTexture>();
ClassDB::register_class<CameraTexture>();
Expand Down
282 changes: 280 additions & 2 deletions scene/resources/texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<Image> &p_image, uint32_t p_flags) {
ERR_FAIL_COND_MSG(p_image.is_null() || p_image->empty(), "Invalid image");
flags = p_flags;
Expand Down Expand Up @@ -281,13 +283,15 @@ 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<Texture> &p_normal_map) const {
if ((w | h) == 0) {
return;
}
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<Texture> &p_normal_map, bool p_clip_uv) const {
if ((w | h) == 0) {
return;
Expand Down Expand Up @@ -1803,6 +1807,280 @@ Ref<Image> 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<Gradient> 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<Gradient> 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;
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();
AaronRecord marked this conversation as resolved.
Show resolved Hide resolved
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<uint8_t> 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);
AaronRecord marked this conversation as resolved.
Show resolved Hide resolved
} 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<float>(x) / (width - 1);
}
if (height > 1) {
pos.y = static_cast<float>(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");
AaronRecord marked this conversation as resolved.
Show resolved Hide resolved
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<Image> GradientTexture2D::get_image() const {
if (!texture.is_valid()) {
return Ref<Image>();
}
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() {
Expand Down Expand Up @@ -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);

Expand Down
Loading