1/**************************************************************************/
2/* noise_texture_3d.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "noise_texture_3d.h"
32
33#include "noise.h"
34
35NoiseTexture3D::NoiseTexture3D() {
36 noise = Ref<Noise>();
37
38 _queue_update();
39}
40
41NoiseTexture3D::~NoiseTexture3D() {
42 ERR_FAIL_NULL(RenderingServer::get_singleton());
43 if (texture.is_valid()) {
44 RS::get_singleton()->free(texture);
45 }
46 if (noise_thread.is_started()) {
47 noise_thread.wait_to_finish();
48 }
49}
50
51void NoiseTexture3D::_bind_methods() {
52 ClassDB::bind_method(D_METHOD("_update_texture"), &NoiseTexture3D::_update_texture);
53 ClassDB::bind_method(D_METHOD("_generate_texture"), &NoiseTexture3D::_generate_texture);
54 ClassDB::bind_method(D_METHOD("_thread_done", "image"), &NoiseTexture3D::_thread_done);
55
56 ClassDB::bind_method(D_METHOD("set_width", "width"), &NoiseTexture3D::set_width);
57 ClassDB::bind_method(D_METHOD("set_height", "height"), &NoiseTexture3D::set_height);
58 ClassDB::bind_method(D_METHOD("set_depth", "depth"), &NoiseTexture3D::set_depth);
59
60 ClassDB::bind_method(D_METHOD("set_invert", "invert"), &NoiseTexture3D::set_invert);
61 ClassDB::bind_method(D_METHOD("get_invert"), &NoiseTexture3D::get_invert);
62
63 ClassDB::bind_method(D_METHOD("set_seamless", "seamless"), &NoiseTexture3D::set_seamless);
64 ClassDB::bind_method(D_METHOD("get_seamless"), &NoiseTexture3D::get_seamless);
65
66 ClassDB::bind_method(D_METHOD("set_seamless_blend_skirt", "seamless_blend_skirt"), &NoiseTexture3D::set_seamless_blend_skirt);
67 ClassDB::bind_method(D_METHOD("get_seamless_blend_skirt"), &NoiseTexture3D::get_seamless_blend_skirt);
68
69 ClassDB::bind_method(D_METHOD("set_normalize", "normalize"), &NoiseTexture3D::set_normalize);
70 ClassDB::bind_method(D_METHOD("is_normalized"), &NoiseTexture3D::is_normalized);
71
72 ClassDB::bind_method(D_METHOD("set_color_ramp", "gradient"), &NoiseTexture3D::set_color_ramp);
73 ClassDB::bind_method(D_METHOD("get_color_ramp"), &NoiseTexture3D::get_color_ramp);
74
75 ClassDB::bind_method(D_METHOD("set_noise", "noise"), &NoiseTexture3D::set_noise);
76 ClassDB::bind_method(D_METHOD("get_noise"), &NoiseTexture3D::get_noise);
77
78 ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,1,or_greater,suffix:px"), "set_width", "get_width");
79 ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,1,or_greater,suffix:px"), "set_height", "get_height");
80 ADD_PROPERTY(PropertyInfo(Variant::INT, "depth", PROPERTY_HINT_RANGE, "1,2048,1,or_greater,suffix:px"), "set_depth", "get_depth");
81 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert"), "set_invert", "get_invert");
82 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "seamless"), "set_seamless", "get_seamless");
83 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "seamless_blend_skirt", PROPERTY_HINT_RANGE, "0.05,1,0.001"), "set_seamless_blend_skirt", "get_seamless_blend_skirt");
84 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalize"), "set_normalize", "is_normalized");
85 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp");
86 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "Noise"), "set_noise", "get_noise");
87}
88
89void NoiseTexture3D::_validate_property(PropertyInfo &p_property) const {
90 if (p_property.name == "seamless_blend_skirt") {
91 if (!seamless) {
92 p_property.usage = PROPERTY_USAGE_NO_EDITOR;
93 }
94 }
95}
96
97void NoiseTexture3D::_set_texture_data(const TypedArray<Image> &p_data) {
98 if (!p_data.is_empty()) {
99 Vector<Ref<Image>> data;
100
101 data.resize(p_data.size());
102
103 for (int i = 0; i < data.size(); i++) {
104 data.write[i] = p_data[i];
105 }
106
107 if (texture.is_valid()) {
108 RID new_texture = RS::get_singleton()->texture_3d_create(data[0]->get_format(), data[0]->get_width(), data[0]->get_height(), data.size(), false, data);
109 RS::get_singleton()->texture_replace(texture, new_texture);
110 } else {
111 texture = RS::get_singleton()->texture_3d_create(data[0]->get_format(), data[0]->get_width(), data[0]->get_height(), data.size(), false, data);
112 }
113 format = data[0]->get_format();
114 }
115 emit_changed();
116}
117
118void NoiseTexture3D::_thread_done(const TypedArray<Image> &p_data) {
119 _set_texture_data(p_data);
120 noise_thread.wait_to_finish();
121 if (regen_queued) {
122 noise_thread.start(_thread_function, this);
123 regen_queued = false;
124 }
125}
126
127void NoiseTexture3D::_thread_function(void *p_ud) {
128 NoiseTexture3D *tex = static_cast<NoiseTexture3D *>(p_ud);
129 tex->call_deferred(SNAME("_thread_done"), tex->_generate_texture());
130}
131
132void NoiseTexture3D::_queue_update() {
133 if (update_queued) {
134 return;
135 }
136
137 update_queued = true;
138 call_deferred(SNAME("_update_texture"));
139}
140
141TypedArray<Image> NoiseTexture3D::_generate_texture() {
142 // Prevent memdelete due to unref() on other thread.
143 Ref<Noise> ref_noise = noise;
144
145 if (ref_noise.is_null()) {
146 return TypedArray<Image>();
147 }
148
149 Vector<Ref<Image>> images;
150
151 if (seamless) {
152 images = ref_noise->_get_seamless_image(width, height, depth, invert, true, seamless_blend_skirt, normalize);
153 } else {
154 images = ref_noise->_get_image(width, height, depth, invert, true, normalize);
155 }
156
157 if (color_ramp.is_valid()) {
158 for (int i = 0; i < images.size(); i++) {
159 images.write[i] = _modulate_with_gradient(images[i], color_ramp);
160 }
161 }
162
163 TypedArray<Image> new_data;
164 new_data.resize(images.size());
165
166 for (int i = 0; i < new_data.size(); i++) {
167 new_data[i] = images[i];
168 }
169
170 return new_data;
171}
172
173Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradient> p_gradient) {
174 int w = p_image->get_width();
175 int h = p_image->get_height();
176
177 Ref<Image> new_image = Image::create_empty(w, h, false, Image::FORMAT_RGBA8);
178
179 for (int row = 0; row < h; row++) {
180 for (int col = 0; col < w; col++) {
181 Color pixel_color = p_image->get_pixel(col, row);
182 Color ramp_color = p_gradient->get_color_at_offset(pixel_color.get_luminance());
183 new_image->set_pixel(col, row, ramp_color);
184 }
185 }
186
187 return new_image;
188}
189
190void NoiseTexture3D::_update_texture() {
191 bool use_thread = true;
192 if (first_time) {
193 use_thread = false;
194 first_time = false;
195 }
196 if (use_thread) {
197 if (!noise_thread.is_started()) {
198 noise_thread.start(_thread_function, this);
199 regen_queued = false;
200 } else {
201 regen_queued = true;
202 }
203
204 } else {
205 TypedArray<Image> new_data = _generate_texture();
206 _set_texture_data(new_data);
207 }
208 update_queued = false;
209}
210
211void NoiseTexture3D::set_noise(Ref<Noise> p_noise) {
212 if (p_noise == noise) {
213 return;
214 }
215 if (noise.is_valid()) {
216 noise->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update));
217 }
218 noise = p_noise;
219 if (noise.is_valid()) {
220 noise->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update));
221 }
222 _queue_update();
223}
224
225Ref<Noise> NoiseTexture3D::get_noise() {
226 return noise;
227}
228
229void NoiseTexture3D::set_width(int p_width) {
230 ERR_FAIL_COND(p_width <= 0);
231 if (p_width == width) {
232 return;
233 }
234 width = p_width;
235 _queue_update();
236}
237
238void NoiseTexture3D::set_height(int p_height) {
239 ERR_FAIL_COND(p_height <= 0);
240 if (p_height == height) {
241 return;
242 }
243 height = p_height;
244 _queue_update();
245}
246
247void NoiseTexture3D::set_depth(int p_depth) {
248 ERR_FAIL_COND(p_depth <= 0);
249 if (p_depth == depth) {
250 return;
251 }
252 depth = p_depth;
253 _queue_update();
254}
255
256void NoiseTexture3D::set_invert(bool p_invert) {
257 if (p_invert == invert) {
258 return;
259 }
260 invert = p_invert;
261 _queue_update();
262}
263
264bool NoiseTexture3D::get_invert() const {
265 return invert;
266}
267
268void NoiseTexture3D::set_seamless(bool p_seamless) {
269 if (p_seamless == seamless) {
270 return;
271 }
272 seamless = p_seamless;
273 _queue_update();
274 notify_property_list_changed();
275}
276
277bool NoiseTexture3D::get_seamless() {
278 return seamless;
279}
280
281void NoiseTexture3D::set_seamless_blend_skirt(real_t p_blend_skirt) {
282 ERR_FAIL_COND(p_blend_skirt < 0.05 || p_blend_skirt > 1);
283
284 if (p_blend_skirt == seamless_blend_skirt) {
285 return;
286 }
287 seamless_blend_skirt = p_blend_skirt;
288 _queue_update();
289}
290real_t NoiseTexture3D::get_seamless_blend_skirt() {
291 return seamless_blend_skirt;
292}
293
294void NoiseTexture3D::set_color_ramp(const Ref<Gradient> &p_gradient) {
295 if (p_gradient == color_ramp) {
296 return;
297 }
298 if (color_ramp.is_valid()) {
299 color_ramp->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update));
300 }
301 color_ramp = p_gradient;
302 if (color_ramp.is_valid()) {
303 color_ramp->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update));
304 }
305 _queue_update();
306}
307
308void NoiseTexture3D::set_normalize(bool p_normalize) {
309 if (normalize == p_normalize) {
310 return;
311 }
312 normalize = p_normalize;
313 _queue_update();
314}
315
316bool NoiseTexture3D::is_normalized() const {
317 return normalize;
318}
319
320Ref<Gradient> NoiseTexture3D::get_color_ramp() const {
321 return color_ramp;
322}
323
324int NoiseTexture3D::get_width() const {
325 return width;
326}
327
328int NoiseTexture3D::get_height() const {
329 return height;
330}
331
332int NoiseTexture3D::get_depth() const {
333 return depth;
334}
335
336RID NoiseTexture3D::get_rid() const {
337 if (!texture.is_valid()) {
338 texture = RS::get_singleton()->texture_3d_placeholder_create();
339 }
340
341 return texture;
342}
343
344Vector<Ref<Image>> NoiseTexture3D::get_data() const {
345 ERR_FAIL_COND_V(!texture.is_valid(), Vector<Ref<Image>>());
346 return RS::get_singleton()->texture_3d_get(texture);
347}
348
349Image::Format NoiseTexture3D::get_format() const {
350 return format;
351}
352