Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate thumbnails on imported scenes. #96544

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions editor/editor_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "editor_interface.h"
#include "editor_interface.compat.inc"

#include "core/config/project_settings.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
#include "editor/editor_main_screen.h"
Expand All @@ -50,6 +51,10 @@
#include "editor/property_selector.h"
#include "editor/themes/editor_scale.h"
#include "main/main.h"
#include "plugins/editor_preview_plugins.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/world_environment.h"
#include "scene/gui/box_container.h"
#include "scene/gui/control.h"
#include "scene/main/window.h"
Expand Down Expand Up @@ -98,6 +103,27 @@ EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const {
return EditorUndoRedoManager::get_singleton();
}

AABB EditorInterface::_calculate_aabb_for_scene(Node *p_node, AABB &p_scene_aabb) {
MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(p_node);
if (mesh_node && mesh_node->get_mesh().is_valid()) {
Transform3D accum_xform;
Node3D *base = mesh_node;
while (base) {
accum_xform = base->get_transform() * accum_xform;
base = Object::cast_to<Node3D>(base->get_parent());
}

AABB aabb = accum_xform.xform(mesh_node->get_mesh()->get_aabb());
p_scene_aabb.merge_with(aabb);
}

for (int i = 0; i < p_node->get_child_count(); i++) {
p_scene_aabb = _calculate_aabb_for_scene(p_node->get_child(i), p_scene_aabb);
}

return p_scene_aabb;
}

TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) {
Vector<Ref<Mesh>> meshes;

Expand Down Expand Up @@ -203,6 +229,136 @@ Vector<Ref<Texture2D>> EditorInterface::make_mesh_previews(const Vector<Ref<Mesh
return textures;
}

void EditorInterface::make_scene_preview(const String &p_path, Node *p_scene, int p_preview_size) {
ERR_FAIL_NULL_MSG(p_scene, "The provided scene is null.");
ERR_FAIL_COND_MSG(p_scene->is_inside_tree(), "The scene must not be inside the tree.");
ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be called from the editor.");

SubViewport *sub_viewport_node = memnew(SubViewport);
AABB scene_aabb;
scene_aabb = _calculate_aabb_for_scene(p_scene, scene_aabb);

sub_viewport_node->set_update_mode(SubViewport::UPDATE_ALWAYS);
sub_viewport_node->set_size(Vector2i(p_preview_size, p_preview_size));
sub_viewport_node->set_transparent_background(false);
Ref<World3D> world;
world.instantiate();
sub_viewport_node->set_world_3d(world);

EditorNode::get_singleton()->add_child(sub_viewport_node);
Ref<Environment> env;
env.instantiate();
env->set_background(Environment::BG_CLEAR_COLOR);

Ref<CameraAttributesPractical> camera_attributes;
camera_attributes.instantiate();

Node3D *root = memnew(Node3D);
root->set_name("Root");
sub_viewport_node->add_child(root);

Camera3D *camera = memnew(Camera3D);
camera->set_environment(env);
camera->set_attributes(camera_attributes);
camera->set_name("Camera3D");
root->add_child(camera);
camera->set_current(true);

camera->set_position(Vector3(0.0, 0.0, 3.0));

DirectionalLight3D *light = memnew(DirectionalLight3D);
light->set_name("Light");
DirectionalLight3D *light2 = memnew(DirectionalLight3D);
light2->set_name("Light2");
light2->set_color(Color(0.7, 0.7, 0.7, 1.0));

root->add_child(light);
root->add_child(light2);

sub_viewport_node->add_child(p_scene);

// Calculate the camera and lighting position based on the size of the scene.
Vector3 center = scene_aabb.get_center();
float camera_size = scene_aabb.get_longest_axis_size();

const float cam_rot_x = -Math_PI / 4;
const float cam_rot_y = -Math_PI / 4;

camera->set_orthogonal(camera_size * 2.0, 0.0001, camera_size * 2.0);

Transform3D xf;
xf.basis = Basis(Vector3(0, 1, 0), cam_rot_y) * Basis(Vector3(1, 0, 0), cam_rot_x);
xf.origin = center;
xf.translate_local(0, 0, camera_size);

camera->set_transform(xf);

Transform3D xform;
xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI / 6);
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI / 6) * xform.basis;

light->set_transform(xform * Transform3D().looking_at(Vector3(-2, -1, -1), Vector3(0, 1, 0)));
light2->set_transform(xform * Transform3D().looking_at(Vector3(+1, -1, -2), Vector3(0, 1, 0)));

// Update the renderer to get the screenshot.
DisplayServer::get_singleton()->process_events();
Main::iteration();
Main::iteration();

// Get the texture.
Ref<Texture2D> texture = sub_viewport_node->get_texture();
ERR_FAIL_COND_MSG(texture.is_null(), "Failed to get texture from sub_viewport_node.");

// Remove the initial scene node.
sub_viewport_node->remove_child(p_scene);

// Cleanup the viewport.
if (sub_viewport_node) {
if (sub_viewport_node->get_parent()) {
sub_viewport_node->get_parent()->remove_child(sub_viewport_node);
}
sub_viewport_node->queue_free();
sub_viewport_node = nullptr;
}

// Now generate the cache image.
Ref<Image> img = texture->get_image();
if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) {
img = img->duplicate();

int preview_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
preview_size *= EDSCALE;

int vp_size = MIN(img->get_width(), img->get_height());
int x = (img->get_width() - vp_size) / 2;
int y = (img->get_height() - vp_size) / 2;

if (vp_size < preview_size) {
img->crop_from_point(x, y, vp_size, vp_size);
} else {
int ratio = vp_size / preview_size;
int size = preview_size * MAX(1, ratio / 2);

x = (img->get_width() - size) / 2;
y = (img->get_height() - size) / 2;

img->crop_from_point(x, y, size, size);
img->resize(preview_size, preview_size, Image::INTERPOLATE_LANCZOS);
}
img->convert(Image::FORMAT_RGB8);

String temp_path = EditorPaths::get_singleton()->get_cache_dir();
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
cache_base = temp_path.path_join("resthumb-" + cache_base);

post_process_preview(img);
img->save_png(cache_base + ".png");
}

EditorResourcePreview::get_singleton()->check_for_invalidation(p_path);
EditorFileSystem::get_singleton()->emit_signal(SNAME("filesystem_changed"));
}

void EditorInterface::set_plugin_enabled(const String &p_plugin, bool p_enabled) {
EditorNode::get_singleton()->set_addon_plugin_enabled(p_plugin, p_enabled, true);
}
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class EditorInterface : public Object {
// Editor tools.

TypedArray<Texture2D> _make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size);
AABB _calculate_aabb_for_scene(Node *p_node, AABB &p_scene_aabb);

protected:
static void _bind_methods();
Expand Down Expand Up @@ -107,6 +108,7 @@ class EditorInterface : public Object {
EditorUndoRedoManager *get_editor_undo_redo() const;

Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
void make_scene_preview(const String &p_path, Node *p_scene, int p_preview_size);

void set_plugin_enabled(const String &p_plugin, bool p_enabled);
bool is_plugin_enabled(const String &p_plugin) const;
Expand Down
23 changes: 22 additions & 1 deletion editor/editor_resource_preview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, con

if (!p_path.begins_with("ID:")) {
modified_time = FileAccess::get_modified_time(p_path);
String import_path = p_path + ".import";
if (FileAccess::exists(import_path)) {
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
}
}

Item item;
Expand Down Expand Up @@ -237,7 +241,14 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<
}
Ref<FileAccess> f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE);
ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + cache_base + ".txt'. Check user write permissions.");
_write_preview_cache(f, thumbnail_size, has_small_texture, FileAccess::get_modified_time(p_item.path), FileAccess::get_md5(p_item.path), p_metadata);

uint64_t modtime = FileAccess::get_modified_time(p_item.path);
String import_path = p_item.path + ".import";
if (FileAccess::exists(import_path)) {
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
}

_write_preview_cache(f, thumbnail_size, has_small_texture, modtime, FileAccess::get_md5(p_item.path), p_metadata);
}
}

Expand Down Expand Up @@ -298,6 +309,11 @@ void EditorResourcePreview::_iterate() {
_generate_preview(texture, small_texture, item, cache_base, preview_metadata);
} else {
uint64_t modtime = FileAccess::get_modified_time(item.path);
String import_path = item.path + ".import";
if (FileAccess::exists(import_path)) {
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
}

int tsize;
bool has_small_texture;
uint64_t last_modtime;
Expand Down Expand Up @@ -513,6 +529,11 @@ void EditorResourcePreview::check_for_invalidation(const String &p_path) {

if (cache.has(p_path)) {
uint64_t modified_time = FileAccess::get_modified_time(p_path);
String import_path = p_path + ".import";
if (FileAccess::exists(import_path)) {
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
}

if (modified_time != cache[p_path].modified_time) {
cache.erase(p_path);
call_invalidated = true;
Expand Down
11 changes: 11 additions & 0 deletions editor/import/3d/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "core/io/dir_access.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/import/3d/scene_import_settings.h"
Expand Down Expand Up @@ -2814,6 +2815,15 @@ void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, Ani
}
}

void ResourceImporterScene::_generate_editor_preview_for_scene(const String &p_path, Node *p_scene) {
if (!Engine::get_singleton()->is_editor_hint()) {
return;
}
ERR_FAIL_COND_MSG(p_path.is_empty(), "Path is empty, cannot generate preview.");
ERR_FAIL_NULL_MSG(p_scene, "Scene is null, cannot generate preview.");
EditorInterface::get_singleton()->make_scene_preview(p_path, p_scene, 1024);
}

Node *ResourceImporterScene::pre_import(const String &p_source_file, const HashMap<StringName, Variant> &p_options) {
Ref<EditorSceneFormatImporter> importer;
String ext = p_source_file.get_extension().to_lower();
Expand Down Expand Up @@ -3164,6 +3174,7 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p
print_verbose("Saving scene to: " + p_save_path + ".scn");
err = ResourceSaver::save(packer, p_save_path + ".scn", flags); //do not take over, let the changed files reload themselves
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + p_save_path + ".scn'.");
_generate_editor_preview_for_scene(p_source_file, scene);
} else {
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown scene import type: " + _scene_import_type);
}
Expand Down
1 change: 1 addition & 0 deletions editor/import/3d/resource_importer_scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class ResourceImporterScene : public ResourceImporter {
};

void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions);
void _generate_editor_preview_for_scene(const String &p_path, Node *p_scene);

String _scene_import_type = "PackedScene";

Expand Down