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

Add GradientTexture2D #43

Merged
merged 1 commit into from
Dec 13, 2020
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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ name is available.
98teg
Andrii Doroshenko (Xrayez)
Daw11
Mariano Suligoy (MarianoGnu)
53 changes: 53 additions & 0 deletions doc/GradientTexture2D.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GradientTexture2D" inherits="Texture" version="3.2">
<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="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="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_2d.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 goost.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def get_child_components(parent):
classes = [
"GoostGeometry2D",
"GoostImage",
"GradientTexture2D",
"ImageBlender",
"ImageIndexed",
"LinkedList",
Expand Down
1 change: 1 addition & 0 deletions scene/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -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")
4 changes: 3 additions & 1 deletion scene/register_scene_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -21,6 +22,7 @@ void register_scene_types() {
ClassDB::register_class<PolyCollisionShape2D>();
#endif
ClassDB::register_class<VisualShape2D>();
ClassDB::register_class<GradientTexture2D>();

#if defined(TOOLS_ENABLED) && defined(GOOST_EDITOR_ENABLED)
#ifdef GOOST_CORE_ENABLED
Expand Down
186 changes: 186 additions & 0 deletions scene/resources/gradient_texture_2d.cpp
Original file line number Diff line number Diff line change
@@ -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<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();
}

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<uint8_t> data;
data.resize(width * height * 4);
{
PoolVector<uint8_t>::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> 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<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();
}

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<Image> GradientTexture2D::get_data() 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_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);
}
75 changes: 75 additions & 0 deletions scene/resources/gradient_texture_2d.h
Original file line number Diff line number Diff line change
@@ -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> 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<Gradient> p_gradient);
Ref<Gradient> 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<Image> get_data() const override;

GradientTexture2D();
virtual ~GradientTexture2D();
};

VARIANT_ENUM_CAST(GradientTexture2D::Fill);
VARIANT_ENUM_CAST(GradientTexture2D::Repeat);

#endif
35 changes: 35 additions & 0 deletions tests/project/goost/scene/resources/test_gradient_texture_2d.gd
Original file line number Diff line number Diff line change
@@ -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()