1 | /**************************************************************************/ |
2 | /* gradient_texture.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 "gradient_texture.h" |
32 | |
33 | #include "core/core_string_names.h" |
34 | #include "core/math/geometry_2d.h" |
35 | |
36 | GradientTexture1D::GradientTexture1D() { |
37 | _queue_update(); |
38 | } |
39 | |
40 | GradientTexture1D::~GradientTexture1D() { |
41 | if (texture.is_valid()) { |
42 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
43 | RS::get_singleton()->free(texture); |
44 | } |
45 | } |
46 | |
47 | void GradientTexture1D::_bind_methods() { |
48 | ClassDB::bind_method(D_METHOD("set_gradient" , "gradient" ), &GradientTexture1D::set_gradient); |
49 | ClassDB::bind_method(D_METHOD("get_gradient" ), &GradientTexture1D::get_gradient); |
50 | |
51 | ClassDB::bind_method(D_METHOD("set_width" , "width" ), &GradientTexture1D::set_width); |
52 | // The `get_width()` method is already exposed by the parent class Texture2D. |
53 | |
54 | ClassDB::bind_method(D_METHOD("set_use_hdr" , "enabled" ), &GradientTexture1D::set_use_hdr); |
55 | ClassDB::bind_method(D_METHOD("is_using_hdr" ), &GradientTexture1D::is_using_hdr); |
56 | |
57 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient" , PROPERTY_HINT_RESOURCE_TYPE, "Gradient" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient" , "get_gradient" ); |
58 | ADD_PROPERTY(PropertyInfo(Variant::INT, "width" , PROPERTY_HINT_RANGE, "1,16384,suffix:px" ), "set_width" , "get_width" ); |
59 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr" ), "set_use_hdr" , "is_using_hdr" ); |
60 | } |
61 | |
62 | void GradientTexture1D::set_gradient(Ref<Gradient> p_gradient) { |
63 | if (p_gradient == gradient) { |
64 | return; |
65 | } |
66 | if (gradient.is_valid()) { |
67 | gradient->disconnect_changed(callable_mp(this, &GradientTexture1D::_queue_update)); |
68 | } |
69 | gradient = p_gradient; |
70 | if (gradient.is_valid()) { |
71 | gradient->connect_changed(callable_mp(this, &GradientTexture1D::_queue_update)); |
72 | } |
73 | _queue_update(); |
74 | emit_changed(); |
75 | } |
76 | |
77 | Ref<Gradient> GradientTexture1D::get_gradient() const { |
78 | return gradient; |
79 | } |
80 | |
81 | void GradientTexture1D::_queue_update() { |
82 | if (update_pending) { |
83 | return; |
84 | } |
85 | update_pending = true; |
86 | callable_mp(this, &GradientTexture1D::update_now).call_deferred(); |
87 | } |
88 | |
89 | void GradientTexture1D::_update() { |
90 | update_pending = false; |
91 | |
92 | if (gradient.is_null()) { |
93 | return; |
94 | } |
95 | |
96 | if (use_hdr) { |
97 | // High dynamic range. |
98 | Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBAF)); |
99 | Gradient &g = **gradient; |
100 | // `create()` isn't available for non-uint8_t data, so fill in the data manually. |
101 | for (int i = 0; i < width; i++) { |
102 | float ofs = float(i) / (width - 1); |
103 | image->set_pixel(i, 0, g.get_color_at_offset(ofs)); |
104 | } |
105 | |
106 | if (texture.is_valid()) { |
107 | RID new_texture = RS::get_singleton()->texture_2d_create(image); |
108 | RS::get_singleton()->texture_replace(texture, new_texture); |
109 | } else { |
110 | texture = RS::get_singleton()->texture_2d_create(image); |
111 | } |
112 | } else { |
113 | // Low dynamic range. "Overbright" colors will be clamped. |
114 | Vector<uint8_t> data; |
115 | data.resize(width * 4); |
116 | { |
117 | uint8_t *wd8 = data.ptrw(); |
118 | Gradient &g = **gradient; |
119 | |
120 | for (int i = 0; i < width; i++) { |
121 | float ofs = float(i) / (width - 1); |
122 | Color color = g.get_color_at_offset(ofs); |
123 | |
124 | wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); |
125 | wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); |
126 | wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); |
127 | wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); |
128 | } |
129 | } |
130 | |
131 | Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); |
132 | |
133 | if (texture.is_valid()) { |
134 | RID new_texture = RS::get_singleton()->texture_2d_create(image); |
135 | RS::get_singleton()->texture_replace(texture, new_texture); |
136 | } else { |
137 | texture = RS::get_singleton()->texture_2d_create(image); |
138 | } |
139 | } |
140 | } |
141 | |
142 | void GradientTexture1D::set_width(int p_width) { |
143 | ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range." ); |
144 | width = p_width; |
145 | _queue_update(); |
146 | emit_changed(); |
147 | } |
148 | |
149 | int GradientTexture1D::get_width() const { |
150 | return width; |
151 | } |
152 | |
153 | void GradientTexture1D::set_use_hdr(bool p_enabled) { |
154 | if (p_enabled == use_hdr) { |
155 | return; |
156 | } |
157 | |
158 | use_hdr = p_enabled; |
159 | _queue_update(); |
160 | emit_changed(); |
161 | } |
162 | |
163 | bool GradientTexture1D::is_using_hdr() const { |
164 | return use_hdr; |
165 | } |
166 | |
167 | RID GradientTexture1D::get_rid() const { |
168 | if (!texture.is_valid()) { |
169 | texture = RS::get_singleton()->texture_2d_placeholder_create(); |
170 | } |
171 | return texture; |
172 | } |
173 | |
174 | Ref<Image> GradientTexture1D::get_image() const { |
175 | const_cast<GradientTexture1D *>(this)->update_now(); |
176 | if (!texture.is_valid()) { |
177 | return Ref<Image>(); |
178 | } |
179 | return RenderingServer::get_singleton()->texture_2d_get(texture); |
180 | } |
181 | |
182 | void GradientTexture1D::update_now() { |
183 | if (update_pending) { |
184 | _update(); |
185 | } |
186 | } |
187 | |
188 | ////////////////// |
189 | |
190 | GradientTexture2D::GradientTexture2D() { |
191 | _queue_update(); |
192 | } |
193 | |
194 | GradientTexture2D::~GradientTexture2D() { |
195 | if (texture.is_valid()) { |
196 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
197 | RS::get_singleton()->free(texture); |
198 | } |
199 | } |
200 | |
201 | void GradientTexture2D::set_gradient(Ref<Gradient> p_gradient) { |
202 | if (gradient == p_gradient) { |
203 | return; |
204 | } |
205 | if (gradient.is_valid()) { |
206 | gradient->disconnect_changed(callable_mp(this, &GradientTexture2D::_queue_update)); |
207 | } |
208 | gradient = p_gradient; |
209 | if (gradient.is_valid()) { |
210 | gradient->connect_changed(callable_mp(this, &GradientTexture2D::_queue_update)); |
211 | } |
212 | _queue_update(); |
213 | emit_changed(); |
214 | } |
215 | |
216 | Ref<Gradient> GradientTexture2D::get_gradient() const { |
217 | return gradient; |
218 | } |
219 | |
220 | void GradientTexture2D::_queue_update() { |
221 | if (update_pending) { |
222 | return; |
223 | } |
224 | update_pending = true; |
225 | callable_mp(this, &GradientTexture2D::update_now).call_deferred(); |
226 | } |
227 | |
228 | void GradientTexture2D::_update() { |
229 | update_pending = false; |
230 | |
231 | if (gradient.is_null()) { |
232 | return; |
233 | } |
234 | Ref<Image> image; |
235 | image.instantiate(); |
236 | |
237 | if (gradient->get_point_count() <= 1) { // No need to interpolate. |
238 | image->initialize_data(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); |
239 | image->fill((gradient->get_point_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1)); |
240 | } else { |
241 | if (use_hdr) { |
242 | image->initialize_data(width, height, false, Image::FORMAT_RGBAF); |
243 | Gradient &g = **gradient; |
244 | // `create()` isn't available for non-uint8_t data, so fill in the data manually. |
245 | for (int y = 0; y < height; y++) { |
246 | for (int x = 0; x < width; x++) { |
247 | float ofs = _get_gradient_offset_at(x, y); |
248 | image->set_pixel(x, y, g.get_color_at_offset(ofs)); |
249 | } |
250 | } |
251 | } else { |
252 | Vector<uint8_t> data; |
253 | data.resize(width * height * 4); |
254 | { |
255 | uint8_t *wd8 = data.ptrw(); |
256 | Gradient &g = **gradient; |
257 | for (int y = 0; y < height; y++) { |
258 | for (int x = 0; x < width; x++) { |
259 | float ofs = _get_gradient_offset_at(x, y); |
260 | const Color &c = g.get_color_at_offset(ofs); |
261 | |
262 | wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255)); |
263 | wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255)); |
264 | wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255)); |
265 | wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255)); |
266 | } |
267 | } |
268 | } |
269 | image->set_data(width, height, false, Image::FORMAT_RGBA8, data); |
270 | } |
271 | } |
272 | |
273 | if (texture.is_valid()) { |
274 | RID new_texture = RS::get_singleton()->texture_2d_create(image); |
275 | RS::get_singleton()->texture_replace(texture, new_texture); |
276 | } else { |
277 | texture = RS::get_singleton()->texture_2d_create(image); |
278 | } |
279 | } |
280 | |
281 | float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { |
282 | if (fill_to == fill_from) { |
283 | return 0; |
284 | } |
285 | float ofs = 0; |
286 | Vector2 pos; |
287 | if (width > 1) { |
288 | pos.x = static_cast<float>(x) / (width - 1); |
289 | } |
290 | if (height > 1) { |
291 | pos.y = static_cast<float>(y) / (height - 1); |
292 | } |
293 | if (fill == Fill::FILL_LINEAR) { |
294 | Vector2 segment[2]; |
295 | segment[0] = fill_from; |
296 | segment[1] = fill_to; |
297 | Vector2 closest = Geometry2D::get_closest_point_to_segment_uncapped(pos, &segment[0]); |
298 | ofs = (closest - fill_from).length() / (fill_to - fill_from).length(); |
299 | if ((closest - fill_from).dot(fill_to - fill_from) < 0) { |
300 | ofs *= -1; |
301 | } |
302 | } else if (fill == Fill::FILL_RADIAL) { |
303 | ofs = (pos - fill_from).length() / (fill_to - fill_from).length(); |
304 | } else if (fill == Fill::FILL_SQUARE) { |
305 | ofs = MAX(Math::abs(pos.x - fill_from.x), Math::abs(pos.y - fill_from.y)) / MAX(Math::abs(fill_to.x - fill_from.x), Math::abs(fill_to.y - fill_from.y)); |
306 | } |
307 | if (repeat == Repeat::REPEAT_NONE) { |
308 | ofs = CLAMP(ofs, 0.0, 1.0); |
309 | } else if (repeat == Repeat::REPEAT) { |
310 | ofs = Math::fmod(ofs, 1.0f); |
311 | if (ofs < 0) { |
312 | ofs = 1 + ofs; |
313 | } |
314 | } else if (repeat == Repeat::REPEAT_MIRROR) { |
315 | ofs = Math::abs(ofs); |
316 | ofs = Math::fmod(ofs, 2.0f); |
317 | if (ofs > 1.0) { |
318 | ofs = 2.0 - ofs; |
319 | } |
320 | } |
321 | return ofs; |
322 | } |
323 | |
324 | void GradientTexture2D::set_width(int p_width) { |
325 | ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range." ); |
326 | width = p_width; |
327 | _queue_update(); |
328 | emit_changed(); |
329 | } |
330 | |
331 | int GradientTexture2D::get_width() const { |
332 | return width; |
333 | } |
334 | |
335 | void GradientTexture2D::set_height(int p_height) { |
336 | ERR_FAIL_COND_MSG(p_height <= 0 || p_height > 16384, "Texture dimensions have to be within 1 to 16384 range." ); |
337 | height = p_height; |
338 | _queue_update(); |
339 | emit_changed(); |
340 | } |
341 | int GradientTexture2D::get_height() const { |
342 | return height; |
343 | } |
344 | |
345 | void GradientTexture2D::set_use_hdr(bool p_enabled) { |
346 | if (p_enabled == use_hdr) { |
347 | return; |
348 | } |
349 | |
350 | use_hdr = p_enabled; |
351 | _queue_update(); |
352 | emit_changed(); |
353 | } |
354 | |
355 | bool GradientTexture2D::is_using_hdr() const { |
356 | return use_hdr; |
357 | } |
358 | |
359 | void GradientTexture2D::set_fill_from(Vector2 p_fill_from) { |
360 | fill_from = p_fill_from; |
361 | _queue_update(); |
362 | emit_changed(); |
363 | } |
364 | |
365 | Vector2 GradientTexture2D::get_fill_from() const { |
366 | return fill_from; |
367 | } |
368 | |
369 | void GradientTexture2D::set_fill_to(Vector2 p_fill_to) { |
370 | fill_to = p_fill_to; |
371 | _queue_update(); |
372 | emit_changed(); |
373 | } |
374 | |
375 | Vector2 GradientTexture2D::get_fill_to() const { |
376 | return fill_to; |
377 | } |
378 | |
379 | void GradientTexture2D::set_fill(Fill p_fill) { |
380 | fill = p_fill; |
381 | _queue_update(); |
382 | emit_changed(); |
383 | } |
384 | |
385 | GradientTexture2D::Fill GradientTexture2D::get_fill() const { |
386 | return fill; |
387 | } |
388 | |
389 | void GradientTexture2D::set_repeat(Repeat p_repeat) { |
390 | repeat = p_repeat; |
391 | _queue_update(); |
392 | emit_changed(); |
393 | } |
394 | |
395 | GradientTexture2D::Repeat GradientTexture2D::get_repeat() const { |
396 | return repeat; |
397 | } |
398 | |
399 | RID GradientTexture2D::get_rid() const { |
400 | if (!texture.is_valid()) { |
401 | texture = RS::get_singleton()->texture_2d_placeholder_create(); |
402 | } |
403 | return texture; |
404 | } |
405 | |
406 | Ref<Image> GradientTexture2D::get_image() const { |
407 | const_cast<GradientTexture2D *>(this)->update_now(); |
408 | if (!texture.is_valid()) { |
409 | return Ref<Image>(); |
410 | } |
411 | return RenderingServer::get_singleton()->texture_2d_get(texture); |
412 | } |
413 | |
414 | void GradientTexture2D::update_now() { |
415 | if (update_pending) { |
416 | _update(); |
417 | } |
418 | } |
419 | |
420 | void GradientTexture2D::_bind_methods() { |
421 | ClassDB::bind_method(D_METHOD("set_gradient" , "gradient" ), &GradientTexture2D::set_gradient); |
422 | ClassDB::bind_method(D_METHOD("get_gradient" ), &GradientTexture2D::get_gradient); |
423 | |
424 | ClassDB::bind_method(D_METHOD("set_width" , "width" ), &GradientTexture2D::set_width); |
425 | ClassDB::bind_method(D_METHOD("set_height" , "height" ), &GradientTexture2D::set_height); |
426 | |
427 | ClassDB::bind_method(D_METHOD("set_use_hdr" , "enabled" ), &GradientTexture2D::set_use_hdr); |
428 | ClassDB::bind_method(D_METHOD("is_using_hdr" ), &GradientTexture2D::is_using_hdr); |
429 | |
430 | ClassDB::bind_method(D_METHOD("set_fill" , "fill" ), &GradientTexture2D::set_fill); |
431 | ClassDB::bind_method(D_METHOD("get_fill" ), &GradientTexture2D::get_fill); |
432 | ClassDB::bind_method(D_METHOD("set_fill_from" , "fill_from" ), &GradientTexture2D::set_fill_from); |
433 | ClassDB::bind_method(D_METHOD("get_fill_from" ), &GradientTexture2D::get_fill_from); |
434 | ClassDB::bind_method(D_METHOD("set_fill_to" , "fill_to" ), &GradientTexture2D::set_fill_to); |
435 | ClassDB::bind_method(D_METHOD("get_fill_to" ), &GradientTexture2D::get_fill_to); |
436 | |
437 | ClassDB::bind_method(D_METHOD("set_repeat" , "repeat" ), &GradientTexture2D::set_repeat); |
438 | ClassDB::bind_method(D_METHOD("get_repeat" ), &GradientTexture2D::get_repeat); |
439 | |
440 | ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient" , PROPERTY_HINT_RESOURCE_TYPE, "Gradient" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient" , "get_gradient" ); |
441 | ADD_PROPERTY(PropertyInfo(Variant::INT, "width" , PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px" ), "set_width" , "get_width" ); |
442 | ADD_PROPERTY(PropertyInfo(Variant::INT, "height" , PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px" ), "set_height" , "get_height" ); |
443 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr" ), "set_use_hdr" , "is_using_hdr" ); |
444 | |
445 | ADD_GROUP("Fill" , "fill_" ); |
446 | ADD_PROPERTY(PropertyInfo(Variant::INT, "fill" , PROPERTY_HINT_ENUM, "Linear,Radial,Square" ), "set_fill" , "get_fill" ); |
447 | ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_from" ), "set_fill_from" , "get_fill_from" ); |
448 | ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_to" ), "set_fill_to" , "get_fill_to" ); |
449 | |
450 | ADD_GROUP("Repeat" , "repeat_" ); |
451 | ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat" , PROPERTY_HINT_ENUM, "No Repeat,Repeat,Mirror Repeat" ), "set_repeat" , "get_repeat" ); |
452 | |
453 | BIND_ENUM_CONSTANT(FILL_LINEAR); |
454 | BIND_ENUM_CONSTANT(FILL_RADIAL); |
455 | BIND_ENUM_CONSTANT(FILL_SQUARE); |
456 | |
457 | BIND_ENUM_CONSTANT(REPEAT_NONE); |
458 | BIND_ENUM_CONSTANT(REPEAT); |
459 | BIND_ENUM_CONSTANT(REPEAT_MIRROR); |
460 | } |
461 | |