1/**************************************************************************/
2/* editor_preview_plugins.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 "editor_preview_plugins.h"
32
33#include "core/config/project_settings.h"
34#include "core/io/file_access_memory.h"
35#include "core/io/resource_loader.h"
36#include "core/object/script_language.h"
37#include "core/os/os.h"
38#include "editor/editor_paths.h"
39#include "editor/editor_scale.h"
40#include "editor/editor_settings.h"
41#include "scene/resources/atlas_texture.h"
42#include "scene/resources/bit_map.h"
43#include "scene/resources/font.h"
44#include "scene/resources/gradient_texture.h"
45#include "scene/resources/image_texture.h"
46#include "scene/resources/material.h"
47#include "scene/resources/mesh.h"
48#include "servers/audio/audio_stream.h"
49
50void post_process_preview(Ref<Image> p_image) {
51 if (p_image->get_format() != Image::FORMAT_RGBA8) {
52 p_image->convert(Image::FORMAT_RGBA8);
53 }
54
55 const int w = p_image->get_width();
56 const int h = p_image->get_height();
57
58 const int r = MIN(w, h) / 32;
59 const int r2 = r * r;
60 Color transparent = Color(0, 0, 0, 0);
61
62 for (int i = 0; i < r; i++) {
63 for (int j = 0; j < r; j++) {
64 int dx = i - r;
65 int dy = j - r;
66 if (dx * dx + dy * dy > r2) {
67 p_image->set_pixel(i, j, transparent);
68 p_image->set_pixel(w - 1 - i, j, transparent);
69 p_image->set_pixel(w - 1 - i, h - 1 - j, transparent);
70 p_image->set_pixel(i, h - 1 - j, transparent);
71 } else {
72 break;
73 }
74 }
75 }
76}
77
78bool EditorTexturePreviewPlugin::handles(const String &p_type) const {
79 return ClassDB::is_parent_class(p_type, "Texture2D");
80}
81
82bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
83 return true;
84}
85
86Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
87 Ref<Image> img;
88 Ref<AtlasTexture> atex = p_from;
89 if (atex.is_valid()) {
90 Ref<Texture2D> tex = atex->get_atlas();
91 if (!tex.is_valid()) {
92 return Ref<Texture2D>();
93 }
94
95 Ref<Image> atlas = tex->get_image();
96 if (!atlas.is_valid()) {
97 return Ref<Texture2D>();
98 }
99
100 img = atlas->get_region(atex->get_region());
101 } else {
102 Ref<Texture2D> tex = p_from;
103 if (tex.is_valid()) {
104 img = tex->get_image();
105 if (img.is_valid()) {
106 img = img->duplicate();
107 }
108 }
109 }
110
111 if (img.is_null() || img->is_empty()) {
112 return Ref<Texture2D>();
113 }
114 p_metadata["dimensions"] = img->get_size();
115
116 img->clear_mipmaps();
117
118 if (img->is_compressed()) {
119 if (img->decompress() != OK) {
120 return Ref<Texture2D>();
121 }
122 } else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
123 img->convert(Image::FORMAT_RGBA8);
124 }
125
126 Vector2 new_size = img->get_size();
127 if (new_size.x > p_size.x) {
128 new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
129 }
130 if (new_size.y > p_size.y) {
131 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
132 }
133 Vector2i new_size_i(MAX(1, (int)new_size.x), MAX(1, (int)new_size.y));
134 img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC);
135 post_process_preview(img);
136
137 return ImageTexture::create_from_image(img);
138}
139
140EditorTexturePreviewPlugin::EditorTexturePreviewPlugin() {
141}
142
143////////////////////////////////////////////////////////////////////////////
144
145bool EditorImagePreviewPlugin::handles(const String &p_type) const {
146 return p_type == "Image";
147}
148
149Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
150 Ref<Image> img = p_from;
151
152 if (img.is_null() || img->is_empty()) {
153 return Ref<Image>();
154 }
155
156 img = img->duplicate();
157 img->clear_mipmaps();
158
159 if (img->is_compressed()) {
160 if (img->decompress() != OK) {
161 return Ref<Image>();
162 }
163 } else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
164 img->convert(Image::FORMAT_RGBA8);
165 }
166
167 Vector2 new_size = img->get_size();
168 if (new_size.x > p_size.x) {
169 new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
170 }
171 if (new_size.y > p_size.y) {
172 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
173 }
174 img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
175 post_process_preview(img);
176
177 return ImageTexture::create_from_image(img);
178}
179
180EditorImagePreviewPlugin::EditorImagePreviewPlugin() {
181}
182
183bool EditorImagePreviewPlugin::generate_small_preview_automatically() const {
184 return true;
185}
186
187////////////////////////////////////////////////////////////////////////////
188
189bool EditorBitmapPreviewPlugin::handles(const String &p_type) const {
190 return ClassDB::is_parent_class(p_type, "BitMap");
191}
192
193Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
194 Ref<BitMap> bm = p_from;
195
196 if (bm->get_size() == Size2()) {
197 return Ref<Texture2D>();
198 }
199
200 Vector<uint8_t> data;
201
202 data.resize(bm->get_size().width * bm->get_size().height);
203
204 {
205 uint8_t *w = data.ptrw();
206
207 for (int i = 0; i < bm->get_size().width; i++) {
208 for (int j = 0; j < bm->get_size().height; j++) {
209 if (bm->get_bit(i, j)) {
210 w[j * (int)bm->get_size().width + i] = 255;
211 } else {
212 w[j * (int)bm->get_size().width + i] = 0;
213 }
214 }
215 }
216 }
217
218 Ref<Image> img = Image::create_from_data(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data);
219
220 if (img->is_compressed()) {
221 if (img->decompress() != OK) {
222 return Ref<Texture2D>();
223 }
224 } else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
225 img->convert(Image::FORMAT_RGBA8);
226 }
227
228 Vector2 new_size = img->get_size();
229 if (new_size.x > p_size.x) {
230 new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
231 }
232 if (new_size.y > p_size.y) {
233 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
234 }
235 img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
236 post_process_preview(img);
237
238 return ImageTexture::create_from_image(img);
239}
240
241bool EditorBitmapPreviewPlugin::generate_small_preview_automatically() const {
242 return true;
243}
244
245EditorBitmapPreviewPlugin::EditorBitmapPreviewPlugin() {
246}
247
248///////////////////////////////////////////////////////////////////////////
249
250bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const {
251 return ClassDB::is_parent_class(p_type, "PackedScene");
252}
253
254Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
255 return generate_from_path(p_from->get_path(), p_size, p_metadata);
256}
257
258Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
259 String temp_path = EditorPaths::get_singleton()->get_cache_dir();
260 String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
261 cache_base = temp_path.path_join("resthumb-" + cache_base);
262
263 //does not have it, try to load a cached thumbnail
264
265 String path = cache_base + ".png";
266
267 if (!FileAccess::exists(path)) {
268 return Ref<Texture2D>();
269 }
270
271 Ref<Image> img;
272 img.instantiate();
273 Error err = img->load(path);
274 if (err == OK) {
275 post_process_preview(img);
276 return ImageTexture::create_from_image(img);
277
278 } else {
279 return Ref<Texture2D>();
280 }
281}
282
283EditorPackedScenePreviewPlugin::EditorPackedScenePreviewPlugin() {
284}
285
286//////////////////////////////////////////////////////////////////
287
288void EditorMaterialPreviewPlugin::_generate_frame_started() {
289 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture
290
291 RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_preview_done));
292}
293
294void EditorMaterialPreviewPlugin::_preview_done() {
295 preview_done.post();
296}
297
298bool EditorMaterialPreviewPlugin::handles(const String &p_type) const {
299 return ClassDB::is_parent_class(p_type, "Material"); // Any material.
300}
301
302bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const {
303 return true;
304}
305
306Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
307 Ref<Material> material = p_from;
308 ERR_FAIL_COND_V(material.is_null(), Ref<Texture2D>());
309
310 if (material->get_shader_mode() == Shader::MODE_SPATIAL) {
311 RS::get_singleton()->mesh_surface_set_material(sphere, 0, material->get_rid());
312
313 RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT);
314
315 preview_done.wait();
316
317 Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
318 RS::get_singleton()->mesh_surface_set_material(sphere, 0, RID());
319
320 ERR_FAIL_COND_V(!img.is_valid(), Ref<ImageTexture>());
321
322 img->convert(Image::FORMAT_RGBA8);
323 int thumbnail_size = MAX(p_size.x, p_size.y);
324 img->resize(thumbnail_size, thumbnail_size, Image::INTERPOLATE_CUBIC);
325 post_process_preview(img);
326 return ImageTexture::create_from_image(img);
327 }
328
329 return Ref<Texture2D>();
330}
331
332EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() {
333 scenario = RS::get_singleton()->scenario_create();
334
335 viewport = RS::get_singleton()->viewport_create();
336 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
337 RS::get_singleton()->viewport_set_scenario(viewport, scenario);
338 RS::get_singleton()->viewport_set_size(viewport, 128, 128);
339 RS::get_singleton()->viewport_set_transparent_background(viewport, true);
340 RS::get_singleton()->viewport_set_active(viewport, true);
341 viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
342
343 camera = RS::get_singleton()->camera_create();
344 RS::get_singleton()->viewport_attach_camera(viewport, camera);
345 RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
346 RS::get_singleton()->camera_set_perspective(camera, 45, 0.1, 10);
347
348 if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
349 camera_attributes = RS::get_singleton()->camera_attributes_create();
350 RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds.
351 RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes);
352 }
353
354 light = RS::get_singleton()->directional_light_create();
355 light_instance = RS::get_singleton()->instance_create2(light, scenario);
356 RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
357
358 light2 = RS::get_singleton()->directional_light_create();
359 RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
360 //RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
361
362 light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
363
364 RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
365
366 sphere = RS::get_singleton()->mesh_create();
367 sphere_instance = RS::get_singleton()->instance_create2(sphere, scenario);
368
369 int lats = 32;
370 int lons = 32;
371 const double lat_step = Math_TAU / lats;
372 const double lon_step = Math_TAU / lons;
373 real_t radius = 1.0;
374
375 Vector<Vector3> vertices;
376 Vector<Vector3> normals;
377 Vector<Vector2> uvs;
378 Vector<real_t> tangents;
379 Basis tt = Basis(Vector3(0, 1, 0), Math_PI * 0.5);
380
381 for (int i = 1; i <= lats; i++) {
382 double lat0 = lat_step * (i - 1) - Math_TAU / 4;
383 double z0 = Math::sin(lat0);
384 double zr0 = Math::cos(lat0);
385
386 double lat1 = lat_step * i - Math_TAU / 4;
387 double z1 = Math::sin(lat1);
388 double zr1 = Math::cos(lat1);
389
390 for (int j = lons; j >= 1; j--) {
391 double lng0 = lon_step * (j - 1);
392 double x0 = Math::cos(lng0);
393 double y0 = Math::sin(lng0);
394
395 double lng1 = lon_step * j;
396 double x1 = Math::cos(lng1);
397 double y1 = Math::sin(lng1);
398
399 Vector3 v[4] = {
400 Vector3(x1 * zr0, z0, y1 * zr0),
401 Vector3(x1 * zr1, z1, y1 * zr1),
402 Vector3(x0 * zr1, z1, y0 * zr1),
403 Vector3(x0 * zr0, z0, y0 * zr0)
404 };
405
406#define ADD_POINT(m_idx) \
407 normals.push_back(v[m_idx]); \
408 vertices.push_back(v[m_idx] * radius); \
409 { \
410 Vector2 uv(Math::atan2(v[m_idx].x, v[m_idx].z), Math::atan2(-v[m_idx].y, v[m_idx].z)); \
411 uv /= Math_PI; \
412 uv *= 4.0; \
413 uv = uv * 0.5 + Vector2(0.5, 0.5); \
414 uvs.push_back(uv); \
415 } \
416 { \
417 Vector3 t = tt.xform(v[m_idx]); \
418 tangents.push_back(t.x); \
419 tangents.push_back(t.y); \
420 tangents.push_back(t.z); \
421 tangents.push_back(1.0); \
422 }
423
424 ADD_POINT(0);
425 ADD_POINT(1);
426 ADD_POINT(2);
427
428 ADD_POINT(2);
429 ADD_POINT(3);
430 ADD_POINT(0);
431 }
432 }
433
434 Array arr;
435 arr.resize(RS::ARRAY_MAX);
436 arr[RS::ARRAY_VERTEX] = vertices;
437 arr[RS::ARRAY_NORMAL] = normals;
438 arr[RS::ARRAY_TANGENT] = tangents;
439 arr[RS::ARRAY_TEX_UV] = uvs;
440 RS::get_singleton()->mesh_add_surface_from_arrays(sphere, RS::PRIMITIVE_TRIANGLES, arr);
441}
442
443EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() {
444 ERR_FAIL_NULL(RenderingServer::get_singleton());
445 RS::get_singleton()->free(sphere);
446 RS::get_singleton()->free(sphere_instance);
447 RS::get_singleton()->free(viewport);
448 RS::get_singleton()->free(light);
449 RS::get_singleton()->free(light_instance);
450 RS::get_singleton()->free(light2);
451 RS::get_singleton()->free(light_instance2);
452 RS::get_singleton()->free(camera);
453 RS::get_singleton()->free(camera_attributes);
454 RS::get_singleton()->free(scenario);
455}
456
457///////////////////////////////////////////////////////////////////////////
458
459bool EditorScriptPreviewPlugin::handles(const String &p_type) const {
460 return ClassDB::is_parent_class(p_type, "Script");
461}
462
463Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
464 Ref<Script> scr = p_from;
465 if (scr.is_null()) {
466 return Ref<Texture2D>();
467 }
468
469 String code = scr->get_source_code().strip_edges();
470 if (code.is_empty()) {
471 return Ref<Texture2D>();
472 }
473
474 List<String> kwors;
475 scr->get_language()->get_reserved_words(&kwors);
476
477 HashSet<String> control_flow_keywords;
478 HashSet<String> keywords;
479
480 for (const String &E : kwors) {
481 if (scr->get_language()->is_control_flow_keyword(E)) {
482 control_flow_keywords.insert(E);
483 } else {
484 keywords.insert(E);
485 }
486 }
487
488 int line = 0;
489 int col = 0;
490 int thumbnail_size = MAX(p_size.x, p_size.y);
491 Ref<Image> img = Image::create_empty(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8);
492
493 Color bg_color = EDITOR_GET("text_editor/theme/highlighting/background_color");
494 Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
495 Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
496 Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
497 Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
498 Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
499
500 if (bg_color.a == 0) {
501 bg_color = Color(0, 0, 0, 0);
502 }
503 bg_color.a = MAX(bg_color.a, 0.2); // some background
504
505 img->fill(bg_color);
506
507 const int x0 = thumbnail_size / 8;
508 const int y0 = thumbnail_size / 8;
509 const int available_height = thumbnail_size - 2 * y0;
510 col = x0;
511
512 bool prev_is_text = false;
513 bool in_control_flow_keyword = false;
514 bool in_keyword = false;
515 bool in_comment = false;
516 for (int i = 0; i < code.length(); i++) {
517 char32_t c = code[i];
518 if (c > 32) {
519 if (col < thumbnail_size) {
520 Color color = text_color;
521
522 if (c == '#') {
523 in_comment = true;
524 }
525
526 if (in_comment) {
527 color = comment_color;
528 } else {
529 if (is_symbol(c)) {
530 //make symbol a little visible
531 color = symbol_color;
532 in_control_flow_keyword = false;
533 in_keyword = false;
534 } else if (!prev_is_text && is_ascii_identifier_char(c)) {
535 int pos = i;
536
537 while (is_ascii_identifier_char(code[pos])) {
538 pos++;
539 }
540 String word = code.substr(i, pos - i);
541 if (control_flow_keywords.has(word)) {
542 in_control_flow_keyword = true;
543 } else if (keywords.has(word)) {
544 in_keyword = true;
545 }
546
547 } else if (!is_ascii_identifier_char(c)) {
548 in_keyword = false;
549 }
550
551 if (in_control_flow_keyword) {
552 color = control_flow_keyword_color;
553 } else if (in_keyword) {
554 color = keyword_color;
555 }
556 }
557 Color ul = color;
558 ul.a *= 0.5;
559 img->set_pixel(col, y0 + line * 2, bg_color.blend(ul));
560 img->set_pixel(col, y0 + line * 2 + 1, color);
561
562 prev_is_text = is_ascii_identifier_char(c);
563 }
564 col++;
565 } else {
566 prev_is_text = false;
567 in_control_flow_keyword = false;
568 in_keyword = false;
569
570 if (c == '\n') {
571 in_comment = false;
572
573 col = x0;
574 line++;
575 if (line >= available_height / 2) {
576 break;
577 }
578 } else if (c == '\t') {
579 col += 3;
580 } else {
581 col++;
582 }
583 }
584 }
585 post_process_preview(img);
586 return ImageTexture::create_from_image(img);
587}
588
589EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() {
590}
591
592///////////////////////////////////////////////////////////////////
593
594bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const {
595 return ClassDB::is_parent_class(p_type, "AudioStream");
596}
597
598Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
599 Ref<AudioStream> stream = p_from;
600 ERR_FAIL_COND_V(stream.is_null(), Ref<Texture2D>());
601
602 Vector<uint8_t> img;
603
604 int w = p_size.x;
605 int h = p_size.y;
606 img.resize(w * h * 3);
607
608 uint8_t *imgdata = img.ptrw();
609 uint8_t *imgw = imgdata;
610
611 Ref<AudioStreamPlayback> playback = stream->instantiate_playback();
612 ERR_FAIL_COND_V(playback.is_null(), Ref<Texture2D>());
613
614 real_t len_s = stream->get_length();
615 if (len_s == 0) {
616 len_s = 60; //one minute audio if no length specified
617 }
618 int frame_length = AudioServer::get_singleton()->get_mix_rate() * len_s;
619
620 Vector<AudioFrame> frames;
621 frames.resize(frame_length);
622
623 playback->start();
624 playback->mix(frames.ptrw(), 1, frames.size());
625 playback->stop();
626
627 for (int i = 0; i < w; i++) {
628 real_t max = -1000;
629 real_t min = 1000;
630 int from = uint64_t(i) * frame_length / w;
631 int to = (uint64_t(i) + 1) * frame_length / w;
632 to = MIN(to, frame_length);
633 from = MIN(from, frame_length - 1);
634 if (to == from) {
635 to = from + 1;
636 }
637
638 for (int j = from; j < to; j++) {
639 max = MAX(max, frames[j].l);
640 max = MAX(max, frames[j].r);
641
642 min = MIN(min, frames[j].l);
643 min = MIN(min, frames[j].r);
644 }
645
646 int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
647 int pto = CLAMP((max * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
648
649 for (int j = 0; j < h; j++) {
650 uint8_t *p = &imgw[(j * w + i) * 3];
651 if (j < pfrom || j > pto) {
652 p[0] = 100;
653 p[1] = 100;
654 p[2] = 100;
655 } else {
656 p[0] = 180;
657 p[1] = 180;
658 p[2] = 180;
659 }
660 }
661 }
662
663 //post_process_preview(img);
664
665 Ref<Image> image = Image::create_from_data(w, h, false, Image::FORMAT_RGB8, img);
666 return ImageTexture::create_from_image(image);
667}
668
669EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() {
670}
671
672///////////////////////////////////////////////////////////////////////////
673
674void EditorMeshPreviewPlugin::_generate_frame_started() {
675 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture
676
677 RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_preview_done));
678}
679
680void EditorMeshPreviewPlugin::_preview_done() {
681 preview_done.post();
682}
683
684bool EditorMeshPreviewPlugin::handles(const String &p_type) const {
685 return ClassDB::is_parent_class(p_type, "Mesh"); // Any mesh.
686}
687
688Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
689 Ref<Mesh> mesh = p_from;
690 ERR_FAIL_COND_V(mesh.is_null(), Ref<Texture2D>());
691
692 RS::get_singleton()->instance_set_base(mesh_instance, mesh->get_rid());
693
694 AABB aabb = mesh->get_aabb();
695 Vector3 ofs = aabb.get_center();
696 aabb.position -= ofs;
697 Transform3D xform;
698 xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI * 0.125);
699 xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI * 0.125) * xform.basis;
700 AABB rot_aabb = xform.xform(aabb);
701 real_t m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5;
702 if (m == 0) {
703 return Ref<Texture2D>();
704 }
705 m = 1.0 / m;
706 m *= 0.5;
707 xform.basis.scale(Vector3(m, m, m));
708 xform.origin = -xform.basis.xform(ofs); //-ofs*m;
709 xform.origin.z -= rot_aabb.size.z * 2;
710 RS::get_singleton()->instance_set_transform(mesh_instance, xform);
711
712 RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT);
713
714 preview_done.wait();
715
716 Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
717 ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
718
719 RS::get_singleton()->instance_set_base(mesh_instance, RID());
720
721 img->convert(Image::FORMAT_RGBA8);
722
723 Vector2 new_size = img->get_size();
724 if (new_size.x > p_size.x) {
725 new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
726 }
727 if (new_size.y > p_size.y) {
728 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
729 }
730 img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
731 post_process_preview(img);
732
733 return ImageTexture::create_from_image(img);
734}
735
736EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() {
737 scenario = RS::get_singleton()->scenario_create();
738
739 viewport = RS::get_singleton()->viewport_create();
740 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
741 RS::get_singleton()->viewport_set_scenario(viewport, scenario);
742 RS::get_singleton()->viewport_set_size(viewport, 128, 128);
743 RS::get_singleton()->viewport_set_transparent_background(viewport, true);
744 RS::get_singleton()->viewport_set_active(viewport, true);
745 viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
746
747 camera = RS::get_singleton()->camera_create();
748 RS::get_singleton()->viewport_attach_camera(viewport, camera);
749 RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
750 //RS::get_singleton()->camera_set_perspective(camera,45,0.1,10);
751 RS::get_singleton()->camera_set_orthogonal(camera, 1.0, 0.01, 1000.0);
752
753 if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
754 camera_attributes = RS::get_singleton()->camera_attributes_create();
755 RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds.
756 RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes);
757 }
758
759 light = RS::get_singleton()->directional_light_create();
760 light_instance = RS::get_singleton()->instance_create2(light, scenario);
761 RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
762
763 light2 = RS::get_singleton()->directional_light_create();
764 RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
765 //RS::get_singleton()->light_set_color(light2, RS::LIGHT_COLOR_SPECULAR, Color(0.0, 0.0, 0.0));
766 light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
767
768 RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
769
770 //sphere = RS::get_singleton()->mesh_create();
771 mesh_instance = RS::get_singleton()->instance_create();
772 RS::get_singleton()->instance_set_scenario(mesh_instance, scenario);
773}
774
775EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() {
776 ERR_FAIL_NULL(RenderingServer::get_singleton());
777 //RS::get_singleton()->free(sphere);
778 RS::get_singleton()->free(mesh_instance);
779 RS::get_singleton()->free(viewport);
780 RS::get_singleton()->free(light);
781 RS::get_singleton()->free(light_instance);
782 RS::get_singleton()->free(light2);
783 RS::get_singleton()->free(light_instance2);
784 RS::get_singleton()->free(camera);
785 RS::get_singleton()->free(camera_attributes);
786 RS::get_singleton()->free(scenario);
787}
788
789///////////////////////////////////////////////////////////////////////////
790
791void EditorFontPreviewPlugin::_generate_frame_started() {
792 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture
793
794 RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_preview_done));
795}
796
797void EditorFontPreviewPlugin::_preview_done() {
798 preview_done.post();
799}
800
801bool EditorFontPreviewPlugin::handles(const String &p_type) const {
802 return ClassDB::is_parent_class(p_type, "Font");
803}
804
805Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
806 Ref<Font> sampled_font = ResourceLoader::load(p_path);
807 ERR_FAIL_COND_V(sampled_font.is_null(), Ref<Texture2D>());
808
809 String sample;
810 static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
811 for (int i = 0; i < sample_base.length(); i++) {
812 if (sampled_font->has_char(sample_base[i])) {
813 sample += sample_base[i];
814 }
815 }
816 if (sample.is_empty()) {
817 sample = sampled_font->get_supported_chars().substr(0, 6);
818 }
819 Vector2 size = sampled_font->get_string_size(sample, HORIZONTAL_ALIGNMENT_LEFT, -1, 50);
820
821 Vector2 pos;
822
823 pos.x = 64 - size.x / 2;
824 pos.y = 80;
825
826 const Color c = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
827 const float fg = c.get_luminance() < 0.5 ? 1.0 : 0.0;
828 sampled_font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(fg, fg, fg));
829
830 RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT);
831
832 preview_done.wait();
833
834 RS::get_singleton()->canvas_item_clear(canvas_item);
835
836 Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
837 ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
838
839 img->convert(Image::FORMAT_RGBA8);
840
841 Vector2 new_size = img->get_size();
842 if (new_size.x > p_size.x) {
843 new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
844 }
845 if (new_size.y > p_size.y) {
846 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
847 }
848 img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
849 post_process_preview(img);
850
851 return ImageTexture::create_from_image(img);
852}
853
854Ref<Texture2D> EditorFontPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
855 String path = p_from->get_path();
856 if (!FileAccess::exists(path)) {
857 return Ref<Texture2D>();
858 }
859 return generate_from_path(path, p_size, p_metadata);
860}
861
862EditorFontPreviewPlugin::EditorFontPreviewPlugin() {
863 viewport = RS::get_singleton()->viewport_create();
864 RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
865 RS::get_singleton()->viewport_set_size(viewport, 128, 128);
866 RS::get_singleton()->viewport_set_active(viewport, true);
867 viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
868
869 canvas = RS::get_singleton()->canvas_create();
870 canvas_item = RS::get_singleton()->canvas_item_create();
871
872 RS::get_singleton()->viewport_attach_canvas(viewport, canvas);
873 RS::get_singleton()->canvas_item_set_parent(canvas_item, canvas);
874}
875
876EditorFontPreviewPlugin::~EditorFontPreviewPlugin() {
877 ERR_FAIL_NULL(RenderingServer::get_singleton());
878 RS::get_singleton()->free(canvas_item);
879 RS::get_singleton()->free(canvas);
880 RS::get_singleton()->free(viewport);
881}
882
883////////////////////////////////////////////////////////////////////////////
884
885static const real_t GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR = 4.0;
886
887bool EditorGradientPreviewPlugin::handles(const String &p_type) const {
888 return ClassDB::is_parent_class(p_type, "Gradient");
889}
890
891bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const {
892 return true;
893}
894
895Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
896 Ref<Gradient> gradient = p_from;
897 if (gradient.is_valid()) {
898 Ref<GradientTexture1D> ptex;
899 ptex.instantiate();
900 ptex->set_width(p_size.width * GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR * EDSCALE);
901 ptex->set_gradient(gradient);
902 return ImageTexture::create_from_image(ptex->get_image());
903 }
904 return Ref<Texture2D>();
905}
906
907EditorGradientPreviewPlugin::EditorGradientPreviewPlugin() {
908}
909