|
|
|
|
@ -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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
|
|