Skip to content

Commit 6f59ff1

Browse files
committed
Rework scene preview thumbnails - take 2
1 parent dec5a37 commit 6f59ff1

13 files changed

+1234
-134
lines changed

doc/classes/EditorInterface.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@
472472
<param index="1" name="with_preview" type="bool" default="true" />
473473
<description>
474474
Saves the currently active scene as a file at [param path].
475+
[b]Note:[/b] The [param with_preview] parameter has no effect.
475476
</description>
476477
</method>
477478
<method name="select_file">

doc/classes/EditorSettings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@
287287
<member name="docks/filesystem/textfile_extensions" type="String" setter="" getter="">
288288
A comma separated list of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files), e.g. [code]"txt,md,cfg,ini,log,json,yml,yaml,toml,xml"[/code].
289289
</member>
290+
<member name="docks/filesystem/thumbnail_file_size_threshold" type="int" setter="" getter="">
291+
If the total file size of all resources used by any given scene exceeds this value (in megabytes), the editor will not generate a thumbnail for that scene. Generally, a higher value will allow bigger scenes to generate thumbnails in the background, at the cost of more noticeable stutters.
292+
</member>
290293
<member name="docks/filesystem/thumbnail_size" type="int" setter="" getter="">
291294
The thumbnail size to use in the FileSystem dock (in pixels). See also [member filesystem/file_dialog/thumbnail_size].
292295
</member>

editor/docks/filesystem_dock.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,10 @@ void FileSystemDock::_tree_thumbnail_done(const String &p_path, const Ref<Textur
850850
if (item && tree_update_id == p_update_id && p_small_preview.is_valid()) {
851851
item->set_icon(0, p_small_preview);
852852
}
853+
if (file_list_vb->is_visible() && file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS) {
854+
// So we can see thumbnail creation in real-time, it'll eventually use the cached thumbnail, no worries.
855+
_update_file_list(true);
856+
}
853857
}
854858

855859
void FileSystemDock::_toggle_file_display() {

editor/editor_interface.cpp

Lines changed: 55 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "main/main.h"
5757
#include "scene/3d/light_3d.h"
5858
#include "scene/3d/mesh_instance_3d.h"
59+
#include "scene/3d/world_environment.h"
5960
#include "scene/gui/box_container.h"
6061
#include "scene/gui/control.h"
6162
#include "scene/main/window.h"
@@ -108,15 +109,8 @@ EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const {
108109
AABB EditorInterface::_calculate_aabb_for_scene(Node *p_node, AABB &p_scene_aabb) {
109110
MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(p_node);
110111
if (mesh_node && mesh_node->get_mesh().is_valid()) {
111-
Transform3D accum_xform;
112-
Node3D *base = mesh_node;
113-
while (base) {
114-
accum_xform = base->get_transform() * accum_xform;
115-
base = Object::cast_to<Node3D>(base->get_parent());
116-
}
117-
118-
AABB aabb = accum_xform.xform(mesh_node->get_mesh()->get_aabb());
119-
p_scene_aabb.merge_with(aabb);
112+
AABB mesh_aabb = mesh_node->get_transform().xform(mesh_node->get_aabb()); // Should not be necessary, as imported transform already be reset, but just in case.
113+
p_scene_aabb.merge_with(mesh_aabb);
120114
}
121115

122116
for (int i = 0; i < p_node->get_child_count(); i++) {
@@ -236,75 +230,79 @@ void EditorInterface::make_scene_preview(const String &p_path, Node *p_scene, in
236230
return;
237231
}
238232
ERR_FAIL_COND_MSG(p_path.is_empty(), "Path is empty, cannot generate preview.");
239-
ERR_FAIL_NULL_MSG(p_scene, "The provided scene is null, cannot generate preview.");
233+
ERR_FAIL_NULL_MSG(p_scene, "The provided scene is null.");
240234
ERR_FAIL_COND_MSG(p_scene->is_inside_tree(), "The scene must not be inside the tree.");
241235
ERR_FAIL_NULL_MSG(EditorNode::get_singleton(), "EditorNode doesn't exist.");
236+
ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be called from the editor.");
242237

243238
SubViewport *sub_viewport_node = memnew(SubViewport);
239+
EditorNode::get_singleton()->add_child(sub_viewport_node);
244240
AABB scene_aabb;
245241
scene_aabb = _calculate_aabb_for_scene(p_scene, scene_aabb);
246242

247-
sub_viewport_node->set_update_mode(SubViewport::UPDATE_ALWAYS);
243+
sub_viewport_node->set_update_mode(SubViewport::UPDATE_ONCE);
248244
sub_viewport_node->set_size(Vector2i(p_preview_size, p_preview_size));
249245
sub_viewport_node->set_transparent_background(false);
246+
247+
// Doing this so it won't popup in editor viewport
250248
Ref<World3D> world;
251249
world.instantiate();
252250
sub_viewport_node->set_world_3d(world);
253251

254-
EditorNode::get_singleton()->add_child(sub_viewport_node);
252+
// Supersampling x2
253+
if (p_preview_size < 2048) { // Universal baseline for textures in Godot 4 is 4K
254+
sub_viewport_node->set_scaling_3d_scale(2.0);
255+
}
256+
257+
// MSAA if using forward renderer
258+
if (RS::get_singleton()->get_current_rendering_method() != "gl_compatibility") {
259+
sub_viewport_node->set_msaa_3d(Viewport::MSAA::MSAA_8X);
260+
}
261+
255262
Ref<Environment> env;
256263
env.instantiate();
264+
Color default_clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
257265
env->set_background(Environment::BG_CLEAR_COLOR);
258-
259-
Ref<CameraAttributesPractical> camera_attributes;
260-
camera_attributes.instantiate();
266+
env->set_bg_color(default_clear_color);
261267

262268
Node3D *root = memnew(Node3D);
263-
root->set_name("Root");
264269
sub_viewport_node->add_child(root);
270+
root->add_child(p_scene);
265271

266272
Camera3D *camera = memnew(Camera3D);
267-
camera->set_environment(env);
268-
camera->set_attributes(camera_attributes);
269-
camera->set_name("Camera3D");
270273
root->add_child(camera);
274+
camera->set_environment(env);
271275
camera->set_current(true);
272276

273-
camera->set_position(Vector3(0.0, 0.0, 3.0));
274-
275-
DirectionalLight3D *light = memnew(DirectionalLight3D);
276-
light->set_name("Light");
277-
DirectionalLight3D *light2 = memnew(DirectionalLight3D);
278-
light2->set_name("Light2");
279-
light2->set_color(Color(0.7, 0.7, 0.7, 1.0));
280-
281-
root->add_child(light);
282-
root->add_child(light2);
283-
284-
sub_viewport_node->add_child(p_scene);
277+
DirectionalLight3D *light_1 = memnew(DirectionalLight3D);
278+
DirectionalLight3D *light_2 = memnew(DirectionalLight3D);
279+
root->add_child(light_1);
280+
root->add_child(light_2);
285281

286-
// Calculate the camera and lighting position based on the size of the scene.
287-
Vector3 center = scene_aabb.get_center();
288-
float camera_size = scene_aabb.get_longest_axis_size();
289-
290-
const float cam_rot_x = -Math::PI / 4;
291-
const float cam_rot_y = -Math::PI / 4;
292-
293-
camera->set_orthogonal(camera_size * 2.0, 0.0001, camera_size * 2.0);
282+
// Setup preview camera
283+
float bound_sphere_radius = (scene_aabb.get_end() - scene_aabb.get_position()).length() / 2.0;
284+
if (bound_sphere_radius <= 0.0) {
285+
// The scene has zero volume, so just it give a literal
286+
bound_sphere_radius = 1.0;
287+
}
294288

295-
Transform3D xf;
296-
xf.basis = Basis(Vector3(0, 1, 0), cam_rot_y) * Basis(Vector3(1, 0, 0), cam_rot_x);
297-
xf.origin = center;
298-
xf.translate_local(0, 0, camera_size);
289+
const float cam_fov = 30.0;
290+
const float cam_distance = bound_sphere_radius / Math::tan(Math::deg_to_rad(cam_fov) / 2.0);
291+
const float cam_near = cam_distance * 0.01;
292+
const float cam_far = cam_distance * 2.0;
299293

300-
camera->set_transform(xf);
294+
camera->set_perspective(cam_fov, cam_near, cam_far);
301295

302-
Transform3D xform;
303-
xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math::PI / 6);
304-
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math::PI / 6) * xform.basis;
296+
Transform3D cam_t3d;
297+
cam_t3d.set_origin(scene_aabb.get_center() + Vector3(1.0f, 0.25f, 1.0f).normalized() * cam_distance);
298+
cam_t3d.set_look_at(cam_t3d.origin, scene_aabb.get_center());
299+
camera->set_transform(cam_t3d);
305300

306-
light->set_transform(xform * Transform3D().looking_at(Vector3(-2, -1, -1), Vector3(0, 1, 0)));
307-
light2->set_transform(xform * Transform3D().looking_at(Vector3(+1, -1, -2), Vector3(0, 1, 0)));
301+
// Setup preview lighting
302+
light_1->set_color(Color(1.0, 1.0, 1.0, 1.0));
303+
light_2->set_color(Color(0.7, 0.7, 0.7, 1.0));
304+
light_1->set_transform(Transform3D(Basis().rotated(Vector3(0, 1, 0), -Math::PI / 6), Vector3(0.0, 0.0, 0.0)));
305+
light_2->set_transform(Transform3D(Basis().rotated(Vector3(1, 0, 0), -Math::PI / 6), Vector3(0.0, 0.0, 0.0)));
308306

309307
// Update the renderer to get the screenshot.
310308
DisplayServer::get_singleton()->process_events();
@@ -313,45 +311,18 @@ void EditorInterface::make_scene_preview(const String &p_path, Node *p_scene, in
313311

314312
// Get the texture.
315313
Ref<Texture2D> texture = sub_viewport_node->get_texture();
316-
ERR_FAIL_COND_MSG(texture.is_null(), "Failed to get texture from sub_viewport_node.");
317-
318-
// Remove the initial scene node.
319-
sub_viewport_node->remove_child(p_scene);
320314

321-
// Cleanup the viewport.
322-
if (sub_viewport_node) {
323-
if (sub_viewport_node->get_parent()) {
324-
sub_viewport_node->get_parent()->remove_child(sub_viewport_node);
325-
}
315+
if (texture.is_null()) {
316+
ERR_PRINT(vformat(R"(Failed to create preview for "%s", preview SubViewport texture is invalid.)", p_path));
326317
sub_viewport_node->queue_free();
327-
sub_viewport_node = nullptr;
318+
return;
328319
}
329320

330-
// Now generate the cache image.
321+
// Retrieve and save preview image
331322
Ref<Image> img = texture->get_image();
332323
if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) {
333324
img = img->duplicate();
334-
335-
int preview_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
336-
preview_size *= EDSCALE;
337-
338-
int vp_size = MIN(img->get_width(), img->get_height());
339-
int x = (img->get_width() - vp_size) / 2;
340-
int y = (img->get_height() - vp_size) / 2;
341-
342-
if (vp_size < preview_size) {
343-
img->crop_from_point(x, y, vp_size, vp_size);
344-
} else {
345-
int ratio = vp_size / preview_size;
346-
int size = preview_size * MAX(1, ratio / 2);
347-
348-
x = (img->get_width() - size) / 2;
349-
y = (img->get_height() - size) / 2;
350-
351-
img->crop_from_point(x, y, size, size);
352-
img->resize(preview_size, preview_size, Image::INTERPOLATE_LANCZOS);
353-
}
354-
img->convert(Image::FORMAT_RGB8);
325+
img->convert(Image::FORMAT_RGBA8);
355326

356327
String temp_path = EditorPaths::get_singleton()->get_cache_dir();
357328
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
@@ -361,6 +332,9 @@ void EditorInterface::make_scene_preview(const String &p_path, Node *p_scene, in
361332
img->save_png(cache_base + ".png");
362333
}
363334

335+
// Cleanup the viewport.
336+
sub_viewport_node->queue_free();
337+
364338
EditorResourcePreview::get_singleton()->check_for_invalidation(p_path);
365339
EditorFileSystem::get_singleton()->emit_signal(SNAME("filesystem_changed"));
366340
}

0 commit comments

Comments
 (0)