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 | |
50 | void 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 | |
78 | bool EditorTexturePreviewPlugin::handles(const String &p_type) const { |
79 | return ClassDB::is_parent_class(p_type, "Texture2D" ); |
80 | } |
81 | |
82 | bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { |
83 | return true; |
84 | } |
85 | |
86 | Ref<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 | |
140 | EditorTexturePreviewPlugin::EditorTexturePreviewPlugin() { |
141 | } |
142 | |
143 | //////////////////////////////////////////////////////////////////////////// |
144 | |
145 | bool EditorImagePreviewPlugin::handles(const String &p_type) const { |
146 | return p_type == "Image" ; |
147 | } |
148 | |
149 | Ref<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 | |
180 | EditorImagePreviewPlugin::EditorImagePreviewPlugin() { |
181 | } |
182 | |
183 | bool EditorImagePreviewPlugin::generate_small_preview_automatically() const { |
184 | return true; |
185 | } |
186 | |
187 | //////////////////////////////////////////////////////////////////////////// |
188 | |
189 | bool EditorBitmapPreviewPlugin::handles(const String &p_type) const { |
190 | return ClassDB::is_parent_class(p_type, "BitMap" ); |
191 | } |
192 | |
193 | Ref<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 | |
241 | bool EditorBitmapPreviewPlugin::generate_small_preview_automatically() const { |
242 | return true; |
243 | } |
244 | |
245 | EditorBitmapPreviewPlugin::EditorBitmapPreviewPlugin() { |
246 | } |
247 | |
248 | /////////////////////////////////////////////////////////////////////////// |
249 | |
250 | bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const { |
251 | return ClassDB::is_parent_class(p_type, "PackedScene" ); |
252 | } |
253 | |
254 | Ref<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 | |
258 | Ref<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 | |
283 | EditorPackedScenePreviewPlugin::EditorPackedScenePreviewPlugin() { |
284 | } |
285 | |
286 | ////////////////////////////////////////////////////////////////// |
287 | |
288 | void 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 | |
294 | void EditorMaterialPreviewPlugin::_preview_done() { |
295 | preview_done.post(); |
296 | } |
297 | |
298 | bool EditorMaterialPreviewPlugin::handles(const String &p_type) const { |
299 | return ClassDB::is_parent_class(p_type, "Material" ); // Any material. |
300 | } |
301 | |
302 | bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const { |
303 | return true; |
304 | } |
305 | |
306 | Ref<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 | |
332 | EditorMaterialPreviewPlugin::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 | |
443 | EditorMaterialPreviewPlugin::~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 | |
459 | bool EditorScriptPreviewPlugin::handles(const String &p_type) const { |
460 | return ClassDB::is_parent_class(p_type, "Script" ); |
461 | } |
462 | |
463 | Ref<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 = 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 = 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 | |
589 | EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() { |
590 | } |
591 | |
592 | /////////////////////////////////////////////////////////////////// |
593 | |
594 | bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const { |
595 | return ClassDB::is_parent_class(p_type, "AudioStream" ); |
596 | } |
597 | |
598 | Ref<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 | |
669 | EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() { |
670 | } |
671 | |
672 | /////////////////////////////////////////////////////////////////////////// |
673 | |
674 | void 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 | |
680 | void EditorMeshPreviewPlugin::_preview_done() { |
681 | preview_done.post(); |
682 | } |
683 | |
684 | bool EditorMeshPreviewPlugin::handles(const String &p_type) const { |
685 | return ClassDB::is_parent_class(p_type, "Mesh" ); // Any mesh. |
686 | } |
687 | |
688 | Ref<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 | |
736 | EditorMeshPreviewPlugin::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 | |
775 | EditorMeshPreviewPlugin::~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 | |
791 | void 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 | |
797 | void EditorFontPreviewPlugin::_preview_done() { |
798 | preview_done.post(); |
799 | } |
800 | |
801 | bool EditorFontPreviewPlugin::handles(const String &p_type) const { |
802 | return ClassDB::is_parent_class(p_type, "Font" ); |
803 | } |
804 | |
805 | Ref<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 | |
854 | Ref<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 | |
862 | EditorFontPreviewPlugin::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 | |
876 | EditorFontPreviewPlugin::~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 | |
885 | static const real_t GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR = 4.0; |
886 | |
887 | bool EditorGradientPreviewPlugin::handles(const String &p_type) const { |
888 | return ClassDB::is_parent_class(p_type, "Gradient" ); |
889 | } |
890 | |
891 | bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const { |
892 | return true; |
893 | } |
894 | |
895 | Ref<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 | |
907 | EditorGradientPreviewPlugin::EditorGradientPreviewPlugin() { |
908 | } |
909 | |