1
0
Fork 0
This commit is contained in:
daniel080400 2025-02-28 13:33:48 +00:00 committed by GitHub
commit f5ad43db2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 710 additions and 42 deletions

View File

@ -1729,7 +1729,9 @@ void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) {
}
}
// **Deprecated** now generates preview at EditorPackedScenePreviewPlugin
void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
return; // Disable
save_scene_progress = memnew(EditorProgress("save", TTR("Saving Scene"), 4));
if (editor_data.get_edited_scene_root() != nullptr) {
@ -1934,13 +1936,18 @@ void EditorNode::_save_scene(String p_file, int idx) {
Node *scene = editor_data.get_edited_scene_root(idx);
save_scene_progress = memnew(EditorProgress("save", TTR("Saving Scene"), 3));
save_scene_progress->step(TTR("Analyzing"), 0);
if (!scene) {
show_accept(TTR("This operation can't be done without a tree root."), TTR("OK"));
_close_save_scene_progress();
return;
}
if (!scene->get_scene_file_path().is_empty() && _validate_scene_recursive(scene->get_scene_file_path(), scene)) {
show_accept(TTR("This scene can't be saved because there is a cyclic instance inclusion.\nPlease resolve it and then attempt to save again."), TTR("OK"));
_close_save_scene_progress();
return;
}
@ -1952,6 +1959,8 @@ void EditorNode::_save_scene(String p_file, int idx) {
_reset_animation_mixers(scene, &anim_backups);
_save_editor_states(p_file, idx);
save_scene_progress->step(TTR("Packing Scene"), 1);
Ref<PackedScene> sdata;
if (ResourceCache::has(p_file)) {
@ -1972,9 +1981,12 @@ void EditorNode::_save_scene(String p_file, int idx) {
if (err != OK) {
show_accept(TTR("Couldn't save scene. Likely dependencies (instances or inheritance) couldn't be satisfied."), TTR("OK"));
_close_save_scene_progress();
return;
}
save_scene_progress->step(TTR("Saving scene"), 2);
int flg = 0;
if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
flg |= ResourceSaver::FLAG_COMPRESS;
@ -1987,6 +1999,8 @@ void EditorNode::_save_scene(String p_file, int idx) {
emit_signal(SNAME("scene_saved"), p_file);
editor_data.notify_scene_saved(p_file);
save_scene_progress->step(TTR("Saving external resources"), 3);
_save_external_resources();
saving_scene = p_file; // Some editors may save scenes of built-in resources as external data, so avoid saving this scene again.
editor_data.save_editor_external_data();
@ -2010,6 +2024,7 @@ void EditorNode::_save_scene(String p_file, int idx) {
}
scene->propagate_notification(NOTIFICATION_EDITOR_POST_SAVE);
_close_save_scene_progress();
}
void EditorNode::save_all_scenes() {
@ -2049,7 +2064,7 @@ void EditorNode::try_autosave() {
Node *scene = editor_data.get_edited_scene_root();
if (scene && !scene->get_scene_file_path().is_empty()) { // Only autosave if there is a scene and if it has a path.
_save_scene_with_preview(scene->get_scene_file_path());
_save_scene(scene->get_scene_file_path());
}
}
_menu_option(FILE_SAVE_ALL_SCENES);
@ -2069,7 +2084,7 @@ void EditorNode::_save_all_scenes() {
if (i != editor_data.get_edited_scene()) {
_save_scene(scene->get_scene_file_path(), i);
} else {
_save_scene_with_preview(scene->get_scene_file_path());
_save_scene(scene->get_scene_file_path());
}
} else if (!scene->get_scene_file_path().is_empty()) {
all_saved = false;
@ -2142,7 +2157,7 @@ void EditorNode::_dialog_action(String p_file) {
}
save_default_environment();
_save_scene_with_preview(p_file, scene_idx);
_save_scene(p_file, scene_idx);
_add_to_recent_scenes(p_file);
save_editor_layout_delayed();
@ -2159,7 +2174,7 @@ void EditorNode::_dialog_action(String p_file) {
case FILE_SAVE_AND_RUN: {
if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
save_default_environment();
_save_scene_with_preview(p_file);
_save_scene(p_file);
project_run_bar->play_custom_scene(p_file);
}
} break;
@ -2170,7 +2185,7 @@ void EditorNode::_dialog_action(String p_file) {
if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
save_default_environment();
_save_scene_with_preview(p_file);
_save_scene(p_file);
project_run_bar->play_main_scene((bool)pick_main_scene->get_meta("from_native", false));
}
} break;
@ -2285,7 +2300,7 @@ void EditorNode::_dialog_action(String p_file) {
default: {
// Save scene?
if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
_save_scene_with_preview(p_file);
_save_scene(p_file);
}
} break;
@ -2794,9 +2809,9 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
if (scene && !scene->get_scene_file_path().is_empty()) {
if (DirAccess::exists(scene->get_scene_file_path().get_base_dir())) {
if (scene_idx != editor_data.get_edited_scene()) {
_save_scene_with_preview(scene->get_scene_file_path(), scene_idx);
_save_scene(scene->get_scene_file_path(), scene_idx);
} else {
_save_scene_with_preview(scene->get_scene_file_path());
_save_scene(scene->get_scene_file_path());
}
if (scene_idx != -1) {

View File

@ -130,10 +130,14 @@ Variant EditorResourcePreviewGenerator::DrawRequester::_post_semaphore() {
return Variant(); // Needed because of how the callback is used.
}
bool EditorResourcePreview::is_threaded() const {
bool EditorResourcePreview::can_run_on_thread() const {
return RSG::rasterizer->can_create_resources_async();
}
bool EditorResourcePreview::is_threaded() const {
return thread.is_started();
}
void EditorResourcePreview::_thread_func(void *ud) {
EditorResourcePreview *erp = (EditorResourcePreview *)ud;
erp->_thread();
@ -330,7 +334,8 @@ void EditorResourcePreview::_iterate() {
cache_valid = false;
f.unref();
} else if (last_modtime != modtime) {
String last_md5 = f->get_line();
// String last_md5 = f->get_line(); // Why? this should be an image file
String last_md5 = hash;
String md5 = FileAccess::get_md5(item.path);
f.unref();
@ -559,7 +564,7 @@ void EditorResourcePreview::start() {
return;
}
if (is_threaded()) {
if (can_run_on_thread()) {
ERR_FAIL_COND_MSG(thread.is_started(), "Thread already started.");
thread.start(_thread_func, this);
} else {
@ -571,23 +576,21 @@ void EditorResourcePreview::start() {
void EditorResourcePreview::stop() {
if (is_threaded()) {
if (thread.is_started()) {
exiting.set();
preview_sem.post();
exiting.set();
preview_sem.post();
for (int i = 0; i < preview_generators.size(); i++) {
preview_generators.write[i]->abort();
}
while (!exited.is_set()) {
// Sync pending work.
OS::get_singleton()->delay_usec(10000);
RenderingServer::get_singleton()->sync();
MessageQueue::get_singleton()->flush();
}
thread.wait_to_finish();
for (int i = 0; i < preview_generators.size(); i++) {
preview_generators.write[i]->abort();
}
while (!exited.is_set()) {
// Sync pending work.
OS::get_singleton()->delay_usec(10000);
RenderingServer::get_singleton()->sync();
MessageQueue::get_singleton()->flush();
}
thread.wait_to_finish();
}
}

View File

@ -148,6 +148,7 @@ public:
void start();
void stop();
bool can_run_on_thread() const;
bool is_threaded() const;
EditorResourcePreview();

View File

@ -3182,7 +3182,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);
//_generate_editor_preview_for_scene(p_source_file, scene);
} else {
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown scene import type: " + _scene_import_type);
}

View File

@ -34,9 +34,25 @@
#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "main/main.h"
#include "modules/gridmap/grid_map.h"
#include "scene/2d/animated_sprite_2d.h"
#include "scene/2d/camera_2d.h"
#include "scene/2d/line_2d.h"
#include "scene/2d/mesh_instance_2d.h"
#include "scene/2d/multimesh_instance_2d.h"
#include "scene/2d/polygon_2d.h"
#include "scene/2d/sprite_2d.h"
#include "scene/2d/tile_map_layer.h"
#include "scene/2d/touch_screen_button.h"
#include "scene/3d/cpu_particles_3d.h"
#include "scene/3d/gpu_particles_3d.h"
#include "scene/3d/light_3d.h"
#include "scene/main/viewport.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/bit_map.h"
#include "scene/resources/font.h"
@ -44,6 +60,7 @@
#include "scene/resources/image_texture.h"
#include "scene/resources/material.h"
#include "scene/resources/mesh.h"
#include "scene/resources/world_2d.h"
#include "servers/audio/audio_stream.h"
void post_process_preview(Ref<Image> p_image) {
@ -292,6 +309,11 @@ EditorBitmapPreviewPlugin::EditorBitmapPreviewPlugin() {
///////////////////////////////////////////////////////////////////////////
void EditorPackedScenePreviewPlugin::abort() {
draw_requester.abort();
aborted = true;
}
bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "PackedScene");
}
@ -301,31 +323,638 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_f
}
Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
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);
// Safe checks, since this function interacts with EditorNode to render previews
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_editor_hint(), Ref<Texture2D>(), "This function can only be called from the editor.");
ERR_FAIL_COND_V_MSG(EditorNode::get_singleton() == nullptr, Ref<Texture2D>(), "EditorNode doesn't exist.");
//does not have it, try to load a cached thumbnail
// Lower abort flag
aborted = false;
String path = cache_base + ".png";
if (!FileAccess::exists(path)) {
Error load_error;
Ref<PackedScene> pack = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE, &load_error); // no more cache issues?
if (load_error != OK) {
print_error(vformat("Failed to generate scene thumbnail for %s : Loaded with error code %d", p_path, int(load_error)));
return Ref<Texture2D>();
}
if (!pack.is_valid()) {
print_error(vformat("Failed to generate scene thumbnail for %s : Invalid scene file", p_path));
return Ref<Texture2D>();
}
Ref<Image> img;
img.instantiate();
Error err = img->load(path);
if (err == OK) {
post_process_preview(img);
return ImageTexture::create_from_image(img);
} else {
bool rm_script_success = _remove_scripts_from_packed_scene(pack); // We don't want tool scripts to fire off when generating previews
if (!rm_script_success) {
print_error(vformat("Failed to generate scene thumbnail for %s : error in removing scripts from preview scene, thus not safe to create thumbnail image", p_path));
return Ref<Texture2D>();
}
Node *p_scene = pack->instantiate(); // The instantiated preview scene
int count_2d = 0;
int count_3d = 0;
int count_light_3d = 0;
_count_node_types(p_scene, count_2d, count_3d, count_light_3d);
if (count_3d > 0) { // Is 3d scene
RS::get_singleton()->viewport_set_size(viewport, Math::round(p_size.x), Math::round(p_size.y));
RS::get_singleton()->viewport_set_transparent_background(viewport, false);
RS::get_singleton()->viewport_set_disable_2d(viewport, true);
RS::get_singleton()->viewport_set_disable_3d(viewport, false);
if (p_size.x < 2048 && p_size.y < 2048) { // Universal baseline for textures in Godot 4 is 4K
RS::get_singleton()->viewport_set_scaling_3d_scale(viewport, 2.0); // Supersampling
}
RS::get_singleton()->viewport_set_msaa_3d(viewport, RS::ViewportMSAA::VIEWPORT_MSAA_8X);
RID environment = RS::get_singleton()->environment_create();
Color default_clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
RS::get_singleton()->environment_set_background(environment, RenderingServer::EnvironmentBG::ENV_BG_CLEAR_COLOR);
RS::get_singleton()->environment_set_bg_color(environment, default_clear_color);
RS::get_singleton()->scenario_set_environment(scenario, environment);
RID camera_3d = RS::get_singleton()->camera_create();
RID camera_attributes = RS::get_singleton()->camera_attributes_create();
RS::get_singleton()->viewport_attach_camera(viewport, camera_3d);
RS::get_singleton()->camera_set_perspective(camera_3d, preview_3d_fov, 0.05, 10000.0);
RS::get_singleton()->camera_set_camera_attributes(camera_3d, camera_attributes);
// Add scene to viewport
_construct_scene_3d(p_scene);
// Preview light
RID light_1 = RS::get_singleton()->directional_light_create();
RID light_2 = RS::get_singleton()->directional_light_create();
RID light_inst_1 = RS::get_singleton()->instance_create();
RID light_inst_2 = RS::get_singleton()->instance_create();
if (count_light_3d == 0) {
RS::get_singleton()->instance_set_scenario(light_inst_1, scenario);
RS::get_singleton()->instance_set_scenario(light_inst_2, scenario);
RS::get_singleton()->instance_set_base(light_inst_1, light_1);
RS::get_singleton()->instance_set_base(light_inst_2, light_2);
RS::get_singleton()->light_set_color(light_1, Color(1.0, 1.0, 1.0, 1.0));
RS::get_singleton()->light_set_color(light_2, Color(0.7, 0.7, 0.7, 1.0));
RS::get_singleton()->instance_set_transform(light_inst_1, Transform3D(Basis().rotated(Vector3(0, 1, 0), -Math_PI / 6), Vector3(0.0, 0.0, 0.0)));
RS::get_singleton()->instance_set_transform(light_inst_2, Transform3D(Basis().rotated(Vector3(1, 0, 0), -Math_PI / 6), Vector3(0.0, 0.0, 0.0)));
}
// Move camera to fit scene
AABB scene_aabb;
_calculate_scene_aabb(p_scene, scene_aabb);
float bound_sphere_radius = (scene_aabb.get_end() - scene_aabb.get_position()).length() / 2.0f;
if (bound_sphere_radius <= 0.0f) {
// The scene has zero volume, so just it give a literal
bound_sphere_radius = 1.0f;
}
float cam_distance = bound_sphere_radius / Math::tan(Math::deg_to_rad(preview_3d_fov) / 2.0f);
Transform3D thumbnail_cam_trans_3d;
thumbnail_cam_trans_3d.set_origin(scene_aabb.get_center() + Vector3(1.0f, 0.25f, 1.0f).normalized() * cam_distance);
thumbnail_cam_trans_3d.set_look_at(thumbnail_cam_trans_3d.origin, scene_aabb.get_center());
RS::get_singleton()->camera_set_transform(camera_3d, thumbnail_cam_trans_3d);
// Wait for scene render
draw_requester.request_and_wait(viewport);
// HACK - Render again, this makes GPUParticles to render correctly to thumbnail.
// Also prevents thumbnail image to be incorrectly assigned to next the asset, don't know why.
RS::get_singleton()->viewport_set_update_mode(viewport, RS::ViewportUpdateMode::VIEWPORT_UPDATE_ONCE);
_wait_frames(1);
// Retrieve thumbnail image (if not aborted)
Ref<ImageTexture> thumbnail = Ref<Texture2D>();
if (!aborted) {
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
thumbnail = ImageTexture::create_from_image(img);
}
// Clean up
RS::get_singleton()->free(light_1);
RS::get_singleton()->free(light_inst_1);
RS::get_singleton()->free(light_2);
RS::get_singleton()->free(light_inst_2);
RS::get_singleton()->free(camera_attributes);
RS::get_singleton()->free(camera_3d);
RS::get_singleton()->free(environment);
p_scene->queue_free();
return thumbnail;
}
if (count_2d > 0) { // Is 2d scene
// NOTE -
// At the time of writing, CanvasItem nodes can't be rendered outside of the tree (see CanvasItem::queue_redraw() and RenderingServer::draw())
// So we hack this by creating SubViewports under the EditorNode.
SubViewport *sub_viewport = memnew(SubViewport);
sub_viewport->set_update_mode(SubViewport::UpdateMode::UPDATE_DISABLED);
sub_viewport->set_disable_3d(true);
sub_viewport->set_transparent_background(false);
sub_viewport->set_msaa_2d(Viewport::MSAA::MSAA_8X);
Ref<World2D> world;
world.instantiate();
sub_viewport->set_world_2d(world);
Node *preview_root = memnew(Node); // Nodes only used in preview is attached to this
sub_viewport->add_child(p_scene);
sub_viewport->add_child(preview_root);
// Hide gui
_hide_gui_in_scene(p_scene);
// Preview camera
Camera2D *camera = memnew(Camera2D);
camera->set_name("ThumbnailCamera2D");
preview_root->add_child(camera);
// Attach subviewport deferred (thread safe)
EditorNode::get_singleton()->call_deferred("add_child", sub_viewport);
_wait_frames(1);
if (aborted) {
sub_viewport->call_deferred("queue_free");
return Ref<Texture2D>();
}
// Make 2D camera current
camera->make_current();
// Calculate scene rect
Rect2 scene_rect;
_calculate_scene_rect(p_scene, scene_rect);
Vector2 scene_true_center = scene_rect.get_center();
// Place camera 2D
camera->set_position(Point2(scene_true_center));
// Render viewport
uint16_t scene_rect_long = MAX(scene_rect.get_size().x, scene_rect.get_size().y);
sub_viewport->set_size(p_size);
camera->set_zoom(Vector2(p_size.x / float(scene_rect_long), p_size.y / float(scene_rect_long)));
sub_viewport->set_update_mode(SubViewport::UpdateMode::UPDATE_ONCE);
_wait_frames(1);
if (aborted) {
sub_viewport->call_deferred("queue_free");
return Ref<Texture2D>();
}
// Retrieve thumbnail of 2D (No GUI)
Ref<ImageTexture> capture_2d = ImageTexture::create_from_image(sub_viewport->get_texture()->get_image());
if (capture_2d->get_image()->get_size() != p_size) {
capture_2d->get_image()->resize(p_size.x, p_size.y);
}
capture_2d->get_image()->convert(Image::Format::FORMAT_RGBA8); // ALPHA channel is needed for it to blend with other image, don't know why.
// Prepare for gui render
sub_viewport->call_deferred("remove_child", p_scene);
p_scene->queue_free();
p_scene = pack->instantiate();
_hide_node_2d_in_scene(p_scene);
SubViewport *sub_viewport_gui = memnew(SubViewport);
sub_viewport_gui->set_size(Size2i(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")));
sub_viewport_gui->set_update_mode(SubViewport::UpdateMode::UPDATE_DISABLED);
sub_viewport_gui->set_transparent_background(true);
sub_viewport_gui->set_msaa_2d(Viewport::MSAA::MSAA_8X);
sub_viewport_gui->set_disable_3d(true);
sub_viewport_gui->add_child(p_scene);
// Render GUI
EditorNode::get_singleton()->call_deferred("add_child", sub_viewport_gui);
sub_viewport_gui->set_update_mode(SubViewport::UpdateMode::UPDATE_ONCE);
_wait_frames(1);
if (aborted) {
sub_viewport->call_deferred("queue_free");
sub_viewport_gui->call_deferred("queue_free");
return Ref<Texture2D>();
}
// Retrieve thumbnail of gui
Ref<ImageTexture> capture_gui = ImageTexture::create_from_image(sub_viewport_gui->get_texture()->get_image());
if (capture_gui->get_image()->get_size() != p_size) {
capture_gui->get_image()->resize(p_size.x, p_size.y);
}
// Mix 2D, GUI thumbnail images into one
Ref<ImageTexture> thumbnail = memnew(ImageTexture);
Ref<Image> thumbnail_image = Image::create_empty(p_size.x, p_size.y, false, Image::Format::FORMAT_RGBA8); // blend_rect needs ALPHA channel to work
thumbnail_image->blend_rect(capture_2d->get_image(), capture_2d->get_image()->get_used_rect(), Point2i(0, 0));
thumbnail_image->blend_rect(capture_gui->get_image(), capture_gui->get_image()->get_used_rect(), Point2i(0, 0));
thumbnail->set_image(thumbnail_image);
// Clean up
EditorNode::get_singleton()->call_deferred("remove_child", sub_viewport);
EditorNode::get_singleton()->call_deferred("remove_child", sub_viewport_gui);
sub_viewport->call_deferred("queue_free");
sub_viewport_gui->call_deferred("queue_free");
p_scene->queue_free();
return thumbnail;
}
// Is scene without any visuals (No Node2D, Node3D, Control found)
return Ref<Texture2D>();
}
void EditorPackedScenePreviewPlugin::_construct_scene_3d(Node *p_node) const {
// Create visual instance into scenario
if (p_node->is_class("VisualInstance3D")) {
VisualInstance3D *v3d = Object::cast_to<VisualInstance3D>(p_node);
RS::get_singleton()->instance_set_scenario(v3d->get_instance(), scenario);
RS::get_singleton()->instance_set_transform(v3d->get_instance(), _get_global_transform_3d(v3d));
}
if (p_node->is_class("GridMap")) {
GridMap *gm = Object::cast_to<GridMap>(p_node);
Transform3D gm_t3d = _get_global_transform_3d(gm);
Array meshes = gm->get_meshes(); // get_bake_meshes() will return [Transform3D, Ref<Mesh>, Transform3D...], to get filled cell count do (array.size() / 2)
for (int i = 1; i <= meshes.size() / 2; i++) {
Ref<Mesh> mesh = meshes[i * 2 - 1];
Transform3D mesh_t3d = meshes[(i - 1) * 2]; // Is in local space
RID mesh_inst = RS::get_singleton()->instance_create();
RS::get_singleton()->instance_set_base(mesh_inst, mesh->get_rid());
RS::get_singleton()->instance_set_scenario(mesh_inst, scenario);
RS::get_singleton()->instance_set_transform(mesh_inst, gm_t3d * mesh_t3d);
}
}
// Specific class settings
if (p_node->is_class("CPUParticles3D")) {
CPUParticles3D *particles_node = Object::cast_to<CPUParticles3D>(p_node);
particles_node->set_pre_process_time(particles_node->get_lifetime() * 0.5); // Fast forward the particle emission to make it render something
particles_node->set_use_local_coordinates(true); // HACK - Now constructs scene outside of tree, using global coords will cause error, this may introduce visual bugs, but is the best solution now
particles_node->restart(true); // Keep seed to make simulation persistent
}
if (p_node->is_class("GPUParticles3D")) {
// The same as CPUParticles
GPUParticles3D *particles_node = Object::cast_to<GPUParticles3D>(p_node);
particles_node->set_pre_process_time(particles_node->get_lifetime() * 0.5);
particles_node->set_use_local_coordinates(true);
particles_node->restart(true);
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_construct_scene_3d(p_node->get_child(i));
}
}
void EditorPackedScenePreviewPlugin::_count_node_types(Node *p_node, int &c2d, int &c3d, int &clight3d) const {
if (p_node->is_class("Control") || p_node->is_class("Node2D")) {
c2d++;
}
if (p_node->is_class("Node3D")) {
c3d++;
}
if (p_node->is_class("Light3D")) {
clight3d++;
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_count_node_types(p_node->get_child(i), c2d, c3d, clight3d);
}
}
void EditorPackedScenePreviewPlugin::_calculate_scene_rect(Node *p_node, Rect2 &scene_rect) const {
// NOTE: There's no universal way to get the exact global rect as a Node2D, so we dig into subclasses one by one
// NOTE:
// 1. Sprite2D::position by default is at the **center** of the sprite. (with offset == (0,0) AND centered == true)
// 2. Rect2::position is at the **up-left** of the rect
// 3. AABB::position is at the **bottom-left-forward** of the bounding box
//
// calculation below is done with these in mind.
Rect2 n2d_rect = Rect2(); // The rect of the current iterating node2d
if (p_node->is_class("Sprite2D")) {
Sprite2D *sprite = Object::cast_to<Sprite2D>(p_node);
n2d_rect.size = sprite->get_global_scale() * sprite->get_rect().size;
n2d_rect.position = sprite->get_global_position() + sprite->get_offset() * sprite->get_global_scale();
if (sprite->is_centered()) {
n2d_rect.position -= n2d_rect.size / 2.0f;
}
}
if (p_node->is_class("AnimatedSprite2D")) {
AnimatedSprite2D *anim_sprite = Object::cast_to<AnimatedSprite2D>(p_node);
if (anim_sprite->get_sprite_frames().is_valid()) {
Ref<Texture2D> current_frame_tex = anim_sprite->get_sprite_frames()->get_frame_texture(anim_sprite->get_animation(), anim_sprite->get_frame());
if (current_frame_tex.is_valid()) {
n2d_rect.size = current_frame_tex->get_size() * anim_sprite->get_global_scale();
n2d_rect.position = anim_sprite->get_global_position() + anim_sprite->get_offset() * anim_sprite->get_global_scale();
if (anim_sprite->is_centered()) {
n2d_rect.position -= n2d_rect.size / 2.0f;
}
}
}
}
if (p_node->is_class("MeshInstance2D")) {
// NOTE: Conversion is 1m = 1px (before 2d scale)
MeshInstance2D *mesh2d = Object::cast_to<MeshInstance2D>(p_node);
Ref<Mesh> mesh = mesh2d->get_mesh();
if (mesh.is_valid()) {
// Discard z axis (depth) and only get length of mesh in x,y axis
n2d_rect.size.x = (mesh->get_aabb().get_end() - mesh->get_aabb().position).x;
n2d_rect.size.y = (mesh->get_aabb().get_end() - mesh->get_aabb().position).y;
n2d_rect.size *= mesh2d->get_global_scale();
// Account for mesh offset in 3d space when calculating rect2
n2d_rect.position.x = mesh2d->get_global_position().x + mesh->get_aabb().position.x * mesh2d->get_global_scale().x; // AABB::position is bottom-left
n2d_rect.position.y = mesh2d->get_global_position().y + mesh->get_aabb().position.y * mesh2d->get_global_scale().y;
}
}
if (p_node->is_class("MultiMeshInstance2D")) {
// Basically the same procedure as MeshInstance2D.
MultiMeshInstance2D *mmesh2d = Object::cast_to<MultiMeshInstance2D>(p_node);
Ref<MultiMesh> mmesh = mmesh2d->get_multimesh();
if (mmesh.is_valid()) {
n2d_rect.size.x = (mmesh->get_aabb().get_end() - mmesh->get_aabb().position).x;
n2d_rect.size.y = (mmesh->get_aabb().get_end() - mmesh->get_aabb().position).y;
n2d_rect.size *= mmesh2d->get_global_scale();
n2d_rect.position.x = mmesh2d->get_global_position().x + mmesh->get_aabb().position.x * mmesh2d->get_global_scale().x;
n2d_rect.position.y = mmesh2d->get_global_position().y + mmesh->get_aabb().position.y * mmesh2d->get_global_scale().y;
}
}
if (p_node->is_class("TileMapLayer")) {
// NOTE: TileMapLayer::get_used_rect() only count cells, not their actual pixel size
TileMapLayer *tile_map = Object::cast_to<TileMapLayer>(p_node);
if (tile_map->get_tile_set().is_valid()) {
Size2 tile_size = Size2(tile_map->get_tile_set()->get_tile_size()); // tile map cell pixel size (x,y)
Rect2 tile_rect = Rect2(tile_map->get_used_rect()); // unit is in cells, not pixels!
n2d_rect.position = tile_map->get_global_position() + tile_rect.position * tile_size * tile_map->get_global_scale(); // accounts tilemap offset
n2d_rect.size = tile_rect.size * tile_size * tile_map->get_global_scale();
}
}
if (p_node->is_class("Polygon2D")) {
Polygon2D *poly2d = Object::cast_to<Polygon2D>(p_node);
PackedVector2Array polygon = poly2d->get_polygon();
if (polygon.size() > 2) { // Abort if there's no surface (min = 3 verts)
// Calculate bounds
float max_x = polygon[0].x;
float min_x = polygon[0].x;
float max_y = polygon[0].y;
float min_y = polygon[0].y;
for (int i = 0; i < polygon.size(); i++) {
if (polygon[i].x > max_x) {
max_x = polygon[i].x;
}
if (polygon[i].x < min_x) {
min_x = polygon[i].x;
}
if (polygon[i].y > max_y) {
max_y = polygon[i].y;
}
if (polygon[i].y < min_y) {
min_y = polygon[i].y;
}
}
Rect2 poly_rect = Rect2(Point2(min_x, min_y), Size2(max_x - min_x, max_y - min_y));
n2d_rect.position = poly2d->get_global_position() + poly2d->get_offset() * poly2d->get_global_scale();
n2d_rect.position += poly_rect.position * poly2d->get_global_scale();
n2d_rect.size = poly_rect.size * poly2d->get_global_scale();
}
}
if (p_node->is_class("Line2D")) {
// The same procedure as Polygon2D
Line2D *line2d = Object::cast_to<Line2D>(p_node);
PackedVector2Array points = line2d->get_points();
if (line2d->get_point_count() > 1) { // Abort if there's no line drawn
// Calculate bounds
float max_x = points[0].x;
float min_x = points[0].x;
float max_y = points[0].y;
float min_y = points[0].y;
for (int i = 0; i < points.size(); i++) {
if (points[i].x > max_x) {
max_x = points[i].x;
}
if (points[i].x < min_x) {
min_x = points[i].x;
}
if (points[i].y > max_y) {
max_y = points[i].y;
}
if (points[i].y < min_y) {
min_y = points[i].y;
}
}
Rect2 line2d_rect = Rect2(Point2(min_x, min_y), Size2(max_x - min_x, max_y - min_y));
n2d_rect.position = line2d->get_global_position();
n2d_rect.position += line2d_rect.position * line2d->get_global_scale();
n2d_rect.size = line2d_rect.size * line2d->get_global_scale();
n2d_rect.size += Size2(line2d->get_width(), line2d->get_width()) / 2.0f; // account for line width
}
}
if (p_node->is_class("TouchScreenButton")) {
TouchScreenButton *btn = Object::cast_to<TouchScreenButton>(p_node);
Ref<Texture2D> btn_tex = btn->get_texture_normal();
if (btn_tex.is_valid()) { // Abort if there's no normal texture for this button (won't display anything)
n2d_rect.position = btn->get_global_position(); // It's not possible to offset image in this node
n2d_rect.size = btn_tex->get_size() * btn->get_global_scale();
}
}
// Merge the calculated node 2d rect
if (scene_rect.get_size().length() == 0.0f) { // Avoid accounting scene origin (0,0) into scene rect
scene_rect = n2d_rect.abs();
} else {
scene_rect = scene_rect.merge(n2d_rect.abs());
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_calculate_scene_rect(p_node->get_child(i), scene_rect);
}
}
void EditorPackedScenePreviewPlugin::_hide_node_2d_in_scene(Node *p_node) const {
// NOTE: Irreversible (cannot unhide nodes after this)
// We cannot simple hide() since it will affect all its children (may contain Control nodes)
if (p_node->is_class("Node2D")) {
Node2D *n2d = Object::cast_to<Node2D>(p_node);
n2d->set_self_modulate(Color(0.0f, 0.0f, 0.0f, 0.0f));
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_hide_node_2d_in_scene(p_node->get_child(i));
}
}
void EditorPackedScenePreviewPlugin::_hide_gui_in_scene(Node *p_node) const {
// NOTE: Irreversible (cannot unhide nodes after this)
// We cannot simply hide() since it will affect all its children (may contain Node2D nodes)
if (p_node->is_class("Control")) {
Control *ctrl = Object::cast_to<Control>(p_node);
ctrl->set_self_modulate(Color(0.0f, 0.0f, 0.0f, 0.0f));
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_hide_gui_in_scene(p_node->get_child(i));
}
}
void EditorPackedScenePreviewPlugin::_wait_frames(const uint64_t &n) const {
if (n <= 0) {
return;
}
const uint64_t prev_frame = Engine::get_singleton()->get_frames_drawn();
while (Engine::get_singleton()->get_frames_drawn() - prev_frame < n + 1) { // Wait for n frames == (n+1) frames has rendered
if (!EditorResourcePreview::get_singleton()->is_threaded()) {
// Is running this on main thread, iterate main loop (or will get stuck here forever)
Main::iteration();
}
if (aborted) {
break;
}
continue;
}
}
void EditorPackedScenePreviewPlugin::_calculate_scene_aabb(Node *p_node, AABB &aabb) const {
if (p_node->is_class("GeometryInstance3D")) { // Use this because VisualInstance3D may have derived classes that are non-graphical (probes, volumes)
GeometryInstance3D *g3d = Object::cast_to<GeometryInstance3D>(p_node);
AABB node_aabb = _get_global_transform_3d(g3d).xform(g3d->get_aabb());
aabb.merge_with(node_aabb);
}
if (p_node->is_class("CPUParticles3D")) { // CPUParticles3D does not calculate particle bounds, so do it here
CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_node);
// Account the furthest position where particles can go
Vector3 particle_destination = _get_global_transform_3d(particles).origin;
particle_destination += particles->get_direction() * particles->get_param_max(CPUParticles3D::PARAM_INITIAL_LINEAR_VELOCITY);
aabb.expand_to(particle_destination * 0.5);
aabb.expand_to(particle_destination * -0.5);
}
if (p_node->is_class("GPUParticles3D")) {
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_node);
aabb.merge_with(_get_global_transform_3d(particles).xform(particles->get_visibility_aabb()));
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_calculate_scene_aabb(p_node->get_child(i), aabb);
}
}
Transform3D EditorPackedScenePreviewPlugin::_get_global_transform_3d(Node *p_n3d) const {
// Designed to work even if node is outside the tree (is_inside_tree() != true)
Transform3D global_transform = Transform3D();
Array parents = Array();
if (!p_n3d->is_class("Node3D")) {
ERR_PRINT("Expected a Node3D node as argument");
return global_transform;
}
Node *p_loop_node = p_n3d;
while (p_loop_node != nullptr) {
if (p_loop_node->is_class("Node3D")) {
parents.append(p_loop_node);
}
p_loop_node = p_loop_node->get_parent();
}
parents.reverse();
for (int i = 0; i < parents.size(); i++) {
Node3D *p_parent = Object::cast_to<Node3D>(parents[i]);
if (i == 0) {
global_transform = p_parent->get_transform();
continue;
}
global_transform *= p_parent->get_transform();
}
return global_transform;
}
bool EditorPackedScenePreviewPlugin::_remove_scripts_from_packed_scene(Ref<PackedScene> pack) const {
// Refer to SceneState in packed_scene.cpp to see how PackedScene is managed underhood.
// Sanitize
Dictionary bundle = pack->get_state()->get_bundled_scene();
ERR_FAIL_COND_V(!bundle.has("names"), false);
ERR_FAIL_COND_V(!bundle.has("variants"), false);
ERR_FAIL_COND_V(!bundle.has("node_count"), false);
ERR_FAIL_COND_V(!bundle.has("nodes"), false);
ERR_FAIL_COND_V(!bundle.has("conn_count"), false);
ERR_FAIL_COND_V(!bundle.has("conns"), false);
const uint8_t supported_version = 3;
uint8_t current_version = 1;
if (bundle.has("version")) {
current_version = bundle["version"];
}
if (current_version > supported_version) {
WARN_PRINT_ONCE(vformat("Scene thumbnail creation was built upon PackedScene with version %d, but the version has changed to %d now.", supported_version, current_version));
// And assume it's safe to continue, there should have no reason to change the main structure of PackedScene
}
// Find and remove all scripts in scene
Ref<Script> const dummy = 0;
Array edited_variants = bundle["variants"];
if (edited_variants.size() == 0) {
return true; // Scene has no resources at all
}
for (int i = 0; i < edited_variants.size(); i++) {
if (edited_variants[i].get_type() != Variant::OBJECT) {
continue;
}
if (Object::cast_to<Script>(edited_variants[i])) {
edited_variants[i] = dummy; // Clear the script
}
}
// Create a new scene state
bundle["variants"] = edited_variants;
Ref<SceneState> new_state = memnew(SceneState);
new_state->set_bundled_scene(bundle);
pack->replace_state(new_state);
return true;
}
EditorPackedScenePreviewPlugin::EditorPackedScenePreviewPlugin() {
// Viewport
viewport = RS::get_singleton()->viewport_create();
RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
RS::get_singleton()->viewport_set_active(viewport, true);
viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
scenario = RS::get_singleton()->scenario_create();
RS::get_singleton()->viewport_set_scenario(viewport, scenario);
}
EditorPackedScenePreviewPlugin::~EditorPackedScenePreviewPlugin() {
RS::get_singleton()->free(viewport);
RS::get_singleton()->free(viewport_texture);
RS::get_singleton()->free(scenario);
}
//////////////////////////////////////////////////////////////////

View File

@ -73,12 +73,32 @@ public:
class EditorPackedScenePreviewPlugin : public EditorResourcePreviewGenerator {
GDCLASS(EditorPackedScenePreviewPlugin, EditorResourcePreviewGenerator);
RID viewport;
RID viewport_texture;
RID scenario;
float preview_3d_fov = 30.0;
mutable bool aborted = false;
mutable DrawRequester draw_requester;
protected:
void _construct_scene_3d(Node *p_node) const;
void _count_node_types(Node *p_node, int &c2d, int &c3d, int &clight3d) const;
void _calculate_scene_rect(Node *p_node, Rect2 &rect) const;
void _calculate_scene_aabb(Node *p_node, AABB &aabb) const;
Transform3D _get_global_transform_3d(Node *p_n3d) const;
void _hide_node_2d_in_scene(Node *p_node) const;
void _hide_gui_in_scene(Node *p_node) const;
bool _remove_scripts_from_packed_scene(Ref<PackedScene> pack) const;
void _wait_frames(const uint64_t &n) const;
public:
virtual void abort() override;
virtual bool handles(const String &p_type) const override;
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override;
EditorPackedScenePreviewPlugin();
~EditorPackedScenePreviewPlugin();
};
class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator {