diff --git a/scene/3d/portal.cpp b/scene/3d/portal.cpp deleted file mode 100644 index 3124f66..0000000 --- a/scene/3d/portal.cpp +++ /dev/null @@ -1,707 +0,0 @@ -/*************************************************************************/ -/* portal.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "portal.h" - -#include "core/config/engine.h" -#include "mesh_instance.h" -#include "room.h" -#include "room_group.h" -#include "room_manager.h" -#include "scene/main/viewport.h" -#include "scene/resources/mesh/mesh.h" -#include "scene/resources/world_3d.h" -#include "servers/rendering_server.h" - -bool Portal::_portal_plane_convention = false; -bool Portal::_settings_gizmo_show_margins = true; - -Portal::Portal() { - clear(); - - _settings_active = true; - _settings_two_way = true; - _internal = false; - _linkedroom_ID[0] = -1; - _linkedroom_ID[1] = -1; - _pts_world.clear(); - _pts_local.clear(); - _pts_local_raw.resize(0); - _pt_center_world = Vector3(); - _plane = Plane(); - _margin = 1.0; - _use_default_margin = true; - - // the visual server portal lifetime is linked to the lifetime of this object - _portal_rid = RID_PRIME(RenderingServer::get_singleton()->portal_create()); - -#ifdef TOOLS_ENABLED - _room_manager_pandemonium_ID = 0; -#endif - - // portals are defined COUNTER clockwise, - // because they point OUTWARD from the room in the direction - // of the normal - PoolVector points; - points.resize(4); - points.set(0, Vector2(1, -1)); - points.set(1, Vector2(1, 1)); - points.set(2, Vector2(-1, 1)); - points.set(3, Vector2(-1, -1)); - - set_points(points); // default shape -} - -Portal::~Portal() { - if (_portal_rid != RID()) { - RenderingServer::get_singleton()->free(_portal_rid); - } -} - -String Portal::get_configuration_warning() const { - String warning = Spatial::get_configuration_warning(); - - auto lambda = [](const Node *p_node) { - return static_cast((Object::cast_to(p_node) || Object::cast_to(p_node) || Object::cast_to(p_node))); - }; - - if (Room::detect_nodes_using_lambda(this, lambda)) { - if (Room::detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("The RoomManager should not be a child or grandchild of a Portal."); - } - if (Room::detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("A Room should not be a child or grandchild of a Portal."); - } - if (Room::detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("A RoomGroup should not be a child or grandchild of a Portal."); - } - } - - return warning; -} - -void Portal::set_point(int p_idx, const Vector2 &p_point) { - ERR_FAIL_INDEX_MSG(p_idx, _pts_local_raw.size(), "Index out of bounds. Call set_points() to set the size of the array."); - - _pts_local_raw.set(p_idx, p_point); - _sanitize_points(); - update_gizmos(); -} - -void Portal::set_points(const PoolVector &p_points) { - _pts_local_raw = p_points; - _sanitize_points(); - - if (is_inside_tree()) { - portal_update(); - update_gizmos(); - } -} - -PoolVector Portal::get_points() const { - return _pts_local_raw; -} - -// extra editor links to the room manager to allow unloading -// on change, or re-converting -void Portal::_changed() { -#ifdef TOOLS_ENABLED - RoomManager *rm = RoomManager::active_room_manager; - if (!rm) { - return; - } - - rm->_rooms_changed("changed Portal " + get_name()); -#endif -} - -void Portal::clear() { - _internal = false; - _linkedroom_ID[0] = -1; - _linkedroom_ID[1] = -1; - _importing_portal = false; -} - -void Portal::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_WORLD: { - ERR_FAIL_COND(get_world_3d().is_null()); - - // defer full creation of the visual server portal to when the editor portal is in the scene tree - RenderingServer::get_singleton()->portal_set_scenario(_portal_rid, get_world_3d()->get_scenario()); - - // we can't calculate world points until we have entered the tree - portal_update(); - update_gizmos(); - - } break; - case NOTIFICATION_EXIT_WORLD: { - // partially destroy the visual server portal when the editor portal exits the scene tree - RenderingServer::get_singleton()->portal_set_scenario(_portal_rid, RID()); - } break; - case NOTIFICATION_TRANSFORM_CHANGED: { - // keep the world points and the visual server up to date - portal_update(); - - // In theory we shouldn't need to update the gizmo when the transform - // changes .. HOWEVER, the portal margin is displayed in world space units, - // back transformed to model space. - // If the Z scale is changed by the user, the portal margin length can become incorrect - // and needs 'resyncing' to the global scale of the portal node. - // We really only need to do this when Z scale is changed, but it is easier codewise - // to always change it, unless we have evidence this is a performance problem. - update_gizmos(); - } break; - } -} - -void Portal::set_portal_active(bool p_active) { - _settings_active = p_active; - RenderingServer::get_singleton()->portal_set_active(_portal_rid, p_active); -} - -bool Portal::get_portal_active() const { - return _settings_active; -} - -void Portal::set_use_default_margin(bool p_use) { - _use_default_margin = p_use; - update_gizmos(); -} - -bool Portal::get_use_default_margin() const { - return _use_default_margin; -} - -void Portal::set_portal_margin(real_t p_margin) { - _margin = p_margin; - - if (!_use_default_margin) { - // give visual feedback in the editor for the portal margin zone - update_gizmos(); - } -} - -real_t Portal::get_portal_margin() const { - return _margin; -} - -void Portal::resolve_links(const LocalVector &p_rooms, const RID &p_from_room_rid) { - Room *linkedroom = nullptr; - if (has_node(_settings_path_linkedroom)) { - linkedroom = Object::cast_to(get_node(_settings_path_linkedroom)); - - // only allow linking to rooms that are part of the roomlist - // (already recognised). - // If we don't check this, it will start trying to link to Room nodes that are invalid, - // and crash. - if (linkedroom && (p_rooms.find(linkedroom) == -1)) { - // invalid room - WARN_PRINT("Portal attempting to link to Room outside the roomlist : " + linkedroom->get_name()); - linkedroom = nullptr; - } - - // this should not happen, but just in case - if (linkedroom && (linkedroom->_room_ID >= p_rooms.size())) { - WARN_PRINT("Portal attempting to link to invalid Room : " + linkedroom->get_name()); - linkedroom = nullptr; - } - } - - if (linkedroom) { - _linkedroom_ID[1] = linkedroom->_room_ID; - - // send to visual server - RenderingServer::get_singleton()->portal_link(_portal_rid, p_from_room_rid, linkedroom->_room_rid, _settings_two_way); - } else { - _linkedroom_ID[1] = -1; - } -} - -void Portal::set_linked_room_internal(const NodePath &link_path) { - _settings_path_linkedroom = link_path; -} - -bool Portal::try_set_unique_name(const String &p_name) { - SceneTree *scene_tree = get_tree(); - if (!scene_tree) { - // should not happen in the editor - return false; - } - - Viewport *root = scene_tree->get_root(); - if (!root) { - return false; - } - - Node *found = root->find_node(p_name, true, false); - - // if the name does not already exist in the scene tree, we can use it - if (!found) { - set_name(p_name); - return true; - } - - // we are trying to set the same name this node already has... - if (found == this) { - // noop - return true; - } - - return false; -} - -void Portal::set_linked_room(const NodePath &link_path) { - _settings_path_linkedroom = link_path; - - // see if the link looks legit - Room *linkedroom = nullptr; - if (has_node(link_path)) { - linkedroom = Object::cast_to(get_node(link_path)); - - if (linkedroom) { - if (linkedroom != get_parent()) { - // was ok - } else { - WARN_PRINT("Linked room cannot be the parent room of a portal."); - } - } else { - WARN_PRINT("Linked room path is not a room."); - } - } - - _changed(); -} - -NodePath Portal::get_linked_room() const { - return _settings_path_linkedroom; -} - -void Portal::flip() { - // flip portal - Transform tr = get_transform(); - Basis flip_basis = Basis(Vector3(0, Math_PI, 0)); - tr.basis *= flip_basis; - set_transform(tr); - - _pts_local.clear(); - _pts_world.clear(); - - // flip the raw verts - Vector raw; - raw.resize(_pts_local_raw.size()); - for (int n = 0; n < _pts_local_raw.size(); n++) { - const Vector2 &pt = _pts_local_raw[n]; - raw.set(n, Vector2(-pt.x, pt.y)); - } - - // standardize raw verts winding - Geometry::sort_polygon_winding(raw, false); - - for (int n = 0; n < raw.size(); n++) { - _pts_local_raw.set(n, raw[n]); - } - - _sanitize_points(); - portal_update(); - - update_gizmos(); -} - -bool Portal::create_from_mesh_instance(const MeshInstance *p_mi) { - ERR_FAIL_COND_V(!p_mi, false); - - _pts_local.clear(); - _pts_world.clear(); - - Ref rmesh = p_mi->get_mesh(); - ERR_FAIL_COND_V(!rmesh.is_valid(), false); - - if (rmesh->get_surface_count() == 0) { - WARN_PRINT(vformat("Portal '%s' has no surfaces, ignoring", get_name())); - return false; - } - - Array arrays = rmesh->surface_get_arrays(0); - PoolVector vertices = arrays[RS::ARRAY_VERTEX]; - PoolVector indices = arrays[RS::ARRAY_INDEX]; - - // get the model space verts and find center - int num_source_points = vertices.size(); - ERR_FAIL_COND_V(num_source_points < 3, false); - - const Transform &tr_source = p_mi->get_global_transform(); - - Vector pts_world; - - for (int n = 0; n < num_source_points; n++) { - Vector3 pt = tr_source.xform(vertices[n]); - - // test for duplicates. - // Some geometry may contain duplicate verts in portals - // which will muck up the winding etc... - bool duplicate = false; - - for (int m = 0; m < pts_world.size(); m++) { - Vector3 diff = pt - pts_world[m]; - // hopefully this epsilon will do in nearly all cases - if (diff.length() < 0.001) { - duplicate = true; - break; - } - } - - if (!duplicate) { - pts_world.push_back(pt); - } - } - - ERR_FAIL_COND_V(pts_world.size() < 3, false); - - // create the normal from 3 vertices .. either indexed, or use the first 3 - Vector3 three_pts[3]; - if (indices.size() >= 3) { - for (int n = 0; n < 3; n++) { - ERR_FAIL_COND_V(indices[n] >= num_source_points, false); - three_pts[n] = tr_source.xform(vertices[indices[n]]); - } - } else { - for (int n = 0; n < 3; n++) { - three_pts[n] = pts_world[n]; - } - } - Vector3 normal = Plane(three_pts[0], three_pts[1], three_pts[2]).normal; - if (_portal_plane_convention) { - normal = -normal; - } - - // get the verts sorted with winding, assume that the triangle initial winding - // tells us the normal and hence which way the world space portal should be facing - _sort_verts_clockwise(normal, pts_world); - - // back calculate the plane from *all* the portal points, this will give us a nice average plane - // (in case of wonky portals where artwork isn't bang on) - _plane = _plane_from_points_newell(pts_world); - - // change the portal transform to match our plane and the center of the portal - Transform tr_global; - - // prevent warnings when poly normal matches the up vector - Vector3 up(0, 1, 0); - if (Math::abs(_plane.normal.dot(up)) > 0.9) { - up = Vector3(1, 0, 0); - } - - tr_global.set_look_at(Vector3(0, 0, 0), _plane.normal, up); - tr_global.origin = _pt_center_world; - - // We can't directly set this global transform on the portal, because the parent node may already - // have a transform applied, so we need to account for this and give a corrected local transform - // for the portal, such that the end result global transform will be correct. - - // find the difference between this new global transform and the transform of the parent - // then use this for the new local transform of the portal - Spatial *parent = Object::cast_to(get_parent()); - ERR_FAIL_COND_V(!parent, false); - - Transform tr_inverse_parent = parent->get_global_transform().affine_inverse(); - Transform new_local_transform = tr_inverse_parent * tr_global; - set_transform(new_local_transform); - - // now back calculate the local space coords of the portal from the world space coords. - // The local space will be used in future for editing and as a 'master' store of the verts. - _pts_local_raw.resize(pts_world.size()); - - // back transform from global space to local space - Transform tr = tr_global.affine_inverse(); - - for (int n = 0; n < pts_world.size(); n++) { - // pt3 is now in local space - Vector3 pt3 = tr.xform(pts_world[n]); - - // only the x and y required - _pts_local_raw.set(n, Vector2(pt3.x, pt3.y)); - - // The z coordinate should be approx zero - // DEV_ASSERT(Math::abs(pt3.z) < 0.1); - } - - _sanitize_points(); - portal_update(); - - return true; -} - -void Portal::_update_aabb() { - _aabb_local = AABB(); - - if (_pts_local.size()) { - Vector3 begin = _vec2to3(_pts_local[0]); - Vector3 end = begin; - - for (int n = 1; n < _pts_local.size(); n++) { - Vector3 pt = _vec2to3(_pts_local[n]); - - if (pt.x < begin.x) { - begin.x = pt.x; - } - if (pt.y < begin.y) { - begin.y = pt.y; - } - if (pt.z < begin.z) { - begin.z = pt.z; - } - - if (pt.x > end.x) { - end.x = pt.x; - } - if (pt.y > end.y) { - end.y = pt.y; - } - if (pt.z > end.z) { - end.z = pt.z; - } - } - - _aabb_local.position = begin; - _aabb_local.size = end - begin; - } -} - -void Portal::portal_update() { - // first calculate the plane from the transform - // (portals are standardized outward from source room once sanitized, - // irrespective of the user portal plane convention) - const Transform &tr = get_global_transform(); - _plane = Plane(0.0, 0.0, -1.0, 0.0); - _plane = tr.xform(_plane); - - // after becoming a portal, the centre world IS the transform origin - _pt_center_world = tr.origin; - - // recalculates world points from the local space - int num_points = _pts_local.size(); - if (_pts_world.size() != num_points) { - _pts_world.resize(num_points); - } - - for (int n = 0; n < num_points; n++) { - _pts_world.set(n, tr.xform(_vec2to3(_pts_local[n]))); - } - - // no need to check winding order, the points are pre-sanitized only when they change - - // extension margin to prevent objects too easily sprawling - real_t margin = get_active_portal_margin(); - RenderingServer::get_singleton()->portal_set_geometry(_portal_rid, _pts_world, margin); -} - -real_t Portal::get_active_portal_margin() const { - if (_use_default_margin) { - return RoomManager::_get_default_portal_margin(); - } - return _margin; -} - -void Portal::_sanitize_points() { - // remove duplicates? NYI maybe not necessary - Vector raw; - raw.resize(_pts_local_raw.size()); - for (int n = 0; n < _pts_local_raw.size(); n++) { - raw.set(n, _pts_local_raw[n]); - } - - // this function may get rid of some concave points due to user editing .. - // may not be necessary, no idea how fast it is - _pts_local = Geometry::convex_hull_2d(raw); - - // some peculiarity of convex_hull_2d function, it duplicates the last point for some reason - if (_pts_local.size() > 1) { - _pts_local.resize(_pts_local.size() - 1); - } - - // sort winding, the system expects counter clockwise polys - Geometry::sort_polygon_winding(_pts_local, false); - - // a bit of a bodge, but a small epsilon pulling in the portal edges towards the center - // can hide walls in the opposite room that abutt the portal (due to floating point error) - // find 2d center - Vector2 center; - for (int n = 0; n < _pts_local.size(); n++) { - center += _pts_local[n]; - } - center /= _pts_local.size(); - - const real_t pull_in = 0.0001; - - for (int n = 0; n < _pts_local.size(); n++) { - Vector2 offset = _pts_local[n] - center; - real_t l = offset.length(); - - // don't apply the pull in for tiny holes - if (l > (pull_in * 2.0)) { - real_t fract = (l - pull_in) / l; - offset *= fract; - _pts_local.set(n, center + offset); - } - } - - _update_aabb(); -} - -void Portal::_sort_verts_clockwise(const Vector3 &p_portal_normal, Vector &r_verts) { - // cannot sort less than 3 verts - if (r_verts.size() < 3) { - return; - } - - // find centroid - int num_points = r_verts.size(); - _pt_center_world = Vector3(0, 0, 0); - - for (int n = 0; n < num_points; n++) { - _pt_center_world += r_verts[n]; - } - _pt_center_world /= num_points; - ///////////////////////////////////////// - - // now algorithm - for (int n = 0; n < num_points - 2; n++) { - Vector3 a = r_verts[n] - _pt_center_world; - a.normalize(); - - Plane p = Plane(r_verts[n], _pt_center_world, _pt_center_world + p_portal_normal); - - double smallest_angle = -1; - int smallest = -1; - - for (int m = n + 1; m < num_points; m++) { - if (p.distance_to(r_verts[m]) > 0.0) { - Vector3 b = r_verts[m] - _pt_center_world; - b.normalize(); - - double angle = a.dot(b); - - if (angle > smallest_angle) { - smallest_angle = angle; - smallest = m; - } - } // which side - - } // for m - - // swap smallest and n+1 vert - if (smallest != -1) { - Vector3 temp = r_verts[smallest]; - r_verts.set(smallest, r_verts[n + 1]); - r_verts.set(n + 1, temp); - } - } // for n - - // the vertices are now sorted, but may be in the opposite order to that wanted. - // we detect this by calculating the normal of the poly, then flipping the order if the normal is pointing - // the wrong way. - Plane plane = Plane(r_verts[0], r_verts[1], r_verts[2]); - - if (p_portal_normal.dot(plane.normal) < 0.0) { - // reverse winding order of verts - r_verts.invert(); - } -} - -Plane Portal::_plane_from_points_newell(const Vector &p_pts) { - int num_points = p_pts.size(); - - if (num_points < 3) { - return Plane(); - } - - Vector3 normal; - Vector3 center; - - for (int i = 0; i < num_points; i++) { - int j = (i + 1) % num_points; - - const Vector3 &pi = p_pts[i]; - const Vector3 &pj = p_pts[j]; - - center += pi; - - normal.x += (((pi.z) + (pj.z)) * ((pj.y) - (pi.y))); - normal.y += (((pi.x) + (pj.x)) * ((pj.z) - (pi.z))); - normal.z += (((pi.y) + (pj.y)) * ((pj.x) - (pi.x))); - } - - normal.normalize(); - center /= num_points; - - _pt_center_world = center; - - // point and normal - return Plane(center, normal); -} - -void Portal::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_portal_active", "p_active"), &Portal::set_portal_active); - ClassDB::bind_method(D_METHOD("get_portal_active"), &Portal::get_portal_active); - - ClassDB::bind_method(D_METHOD("set_two_way", "p_two_way"), &Portal::set_two_way); - ClassDB::bind_method(D_METHOD("is_two_way"), &Portal::is_two_way); - - ClassDB::bind_method(D_METHOD("set_use_default_margin", "p_use"), &Portal::set_use_default_margin); - ClassDB::bind_method(D_METHOD("get_use_default_margin"), &Portal::get_use_default_margin); - - ClassDB::bind_method(D_METHOD("set_portal_margin", "p_margin"), &Portal::set_portal_margin); - ClassDB::bind_method(D_METHOD("get_portal_margin"), &Portal::get_portal_margin); - - ClassDB::bind_method(D_METHOD("set_linked_room", "p_room"), &Portal::set_linked_room); - ClassDB::bind_method(D_METHOD("get_linked_room"), &Portal::get_linked_room); - - ClassDB::bind_method(D_METHOD("set_points", "points"), &Portal::set_points); - ClassDB::bind_method(D_METHOD("get_points"), &Portal::get_points); - - ClassDB::bind_method(D_METHOD("set_point", "index", "position"), &Portal::set_point); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "portal_active"), "set_portal_active", "get_portal_active"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "two_way"), "set_two_way", "is_two_way"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "linked_room", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Room"), "set_linked_room", "get_linked_room"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_margin"), "set_use_default_margin", "get_use_default_margin"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "portal_margin", PROPERTY_HINT_RANGE, "0.0,10.0,0.01"), "set_portal_margin", "get_portal_margin"); - ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), "set_points", "get_points"); -} diff --git a/scene/3d/portal.h b/scene/3d/portal.h deleted file mode 100644 index ac16d2d..0000000 --- a/scene/3d/portal.h +++ /dev/null @@ -1,190 +0,0 @@ -#ifndef PORTAL_H -#define PORTAL_H -/*************************************************************************/ -/* portal.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/containers/local_vector.h" -#include "core/containers/rid.h" -#include "scene/main/spatial.h" - -class RoomManager; -class MeshInstance; -class Room; - -class Portal : public Spatial { - GDCLASS(Portal, Spatial); - - RID _portal_rid; - - friend class RoomManager; - friend class PortalGizmoPlugin; - friend class PortalEditorPlugin; - friend class PortalSpatialGizmo; - -public: - // ui interface .. will have no effect after room conversion - void set_linked_room(const NodePath &link_path); - NodePath get_linked_room() const; - - // open and close doors - void set_portal_active(bool p_active); - bool get_portal_active() const; - - // whether the portal can be seen through in both directions or not - void set_two_way(bool p_two_way) { - _settings_two_way = p_two_way; - _changed(); - } - bool is_two_way() const { return _settings_two_way; } - - // call during each conversion - void clear(); - - // whether to use the room manager default - void set_use_default_margin(bool p_use); - bool get_use_default_margin() const; - - // custom portal margin (per portal) .. only valid if use_default_margin is off - void set_portal_margin(real_t p_margin); - real_t get_portal_margin() const; - - // either the default margin or the custom portal margin, depending on the setting - real_t get_active_portal_margin() const; - - // the raw points are used for the IDE Inspector, and also to allow the user - // to edit the geometry of the portal at runtime (they can also just change the portal node transform) - void set_points(const PoolVector &p_points); - PoolVector get_points() const; - - // primarily for the gizmo - void set_point(int p_idx, const Vector2 &p_point); - - String get_configuration_warning() const; - - Portal(); - ~Portal(); - - // whether the convention is that the normal of the portal points outward (false) or inward (true) - // normally I'd recommend portal normal faces outward. But you may make a booboo, so this can work - // with either convention. - static bool _portal_plane_convention; - -private: - // updates world coords when the transform changes, and updates the visual server - void portal_update(); - - void set_linked_room_internal(const NodePath &link_path); - bool try_set_unique_name(const String &p_name); - bool is_portal_internal(int p_room_outer) const { return _internal && (_linkedroom_ID[0] != p_room_outer); } - - bool create_from_mesh_instance(const MeshInstance *p_mi); - void flip(); - void _sanitize_points(); - void _update_aabb(); - static Vector3 _vec2to3(const Vector2 &p_pt) { return Vector3(p_pt.x, p_pt.y, 0.0); } - void _sort_verts_clockwise(const Vector3 &p_portal_normal, Vector &r_verts); - Plane _plane_from_points_newell(const Vector &p_pts); - void resolve_links(const LocalVector &p_rooms, const RID &p_from_room_rid); - void _changed(); - - // nodepath to the room this outgoing portal leads to - NodePath _settings_path_linkedroom; - - // portal can be turned on and off at runtime, for e.g. - // opening and closing a door - bool _settings_active; - - // user can choose not to include the portal in the convex hull of the room - // during conversion - bool _settings_include_in_bound; - - // portals can be seen through one way or two way - bool _settings_two_way; - - // room from and to, ID in the room manager - int _linkedroom_ID[2]; - - // whether the portal is from a room within a room - bool _internal; - - // normal determined by winding order - Vector _pts_world; - - // points in local space of the plane, - // not necessary in correct winding order - // (as they can be edited by the user) - // Note: these are saved by the IDE - PoolVector _pts_local_raw; - - // sanitized - Vector _pts_local; - AABB _aabb_local; - - // center of the world points - Vector3 _pt_center_world; - - // portal plane in world space, always pointing OUTWARD from the source room - Plane _plane; - - // extension margin - real_t _margin; - bool _use_default_margin; - - // during conversion, we need to know - // whether this portal is being imported from a mesh - // and is using an explicitly named link room with prefix. - // If this is not the case, and it is already a Pandemonium Portal node, - // we will either use the assigned nodepath, or autolink. - bool _importing_portal = false; - - // for editing -#ifdef TOOLS_ENABLED - ObjectID _room_manager_pandemonium_ID; - - // warnings - bool _warning_outside_room_aabb = false; - bool _warning_facing_wrong_way = false; - bool _warning_autolink_failed = false; -#endif - - // this is read from the gizmo - static bool _settings_gizmo_show_margins; - -public: - // makes sure portals are not converted more than once per - // call to rooms_convert - int _conversion_tick = -1; - -protected: - static void _bind_methods(); - void _notification(int p_what); -}; - -#endif diff --git a/scene/3d/room.cpp b/scene/3d/room.cpp deleted file mode 100644 index 0c98836..0000000 --- a/scene/3d/room.cpp +++ /dev/null @@ -1,294 +0,0 @@ -/*************************************************************************/ -/* room.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "room.h" - -#include "portal.h" -#include "room_group.h" -#include "room_manager.h" -#include "scene/resources/world_3d.h" -#include "servers/rendering_server.h" - -void Room::SimplifyInfo::set_simplify(real_t p_value, real_t p_room_size) { - _plane_simplify = CLAMP(p_value, 0.0, 1.0); - - // just for reference in case we later want to use degrees... - // _plane_simplify_dot = Math::cos(Math::deg2rad(_plane_simplify_degrees)); - - // _plane_simplify_dot = _plane_simplify; - // _plane_simplify_dot *= _plane_simplify_dot; - // _plane_simplify_dot = 1.0 - _plane_simplify_dot; - - // distance based on size of room - // _plane_simplify_dist = p_room_size * 0.1 * _plane_simplify; - // _plane_simplify_dist = MAX(_plane_simplify_dist, 0.08); - - // test fix - _plane_simplify_dot = 0.99; - _plane_simplify_dist = 0.08; - - // print_verbose("plane simplify dot : " + String(Variant(_plane_simplify_dot))); - // print_verbose("plane simplify dist : " + String(Variant(_plane_simplify_dist))); -} - -bool Room::SimplifyInfo::add_plane_if_unique(LocalVector &r_planes, const Plane &p) const { - for (int n = 0; n < r_planes.size(); n++) { - const Plane &o = r_planes[n]; - - // this is a fudge factor for how close planes can be to be considered the same ... - // to prevent ridiculous amounts of planes - const real_t d = _plane_simplify_dist; // 0.08f - - if (Math::abs(p.d - o.d) > d) { - continue; - } - - real_t dot = p.normal.dot(o.normal); - if (dot < _plane_simplify_dot) // 0.98f - { - continue; - } - - // match! - return false; - } - - r_planes.push_back(p); - return true; -} - -void Room::clear() { - _room_ID = -1; - _planes.clear(); - _preliminary_planes.clear(); - _roomgroups.clear(); - _portals.clear(); - _bound_mesh_data.edges.clear(); - _bound_mesh_data.faces.clear(); - _bound_mesh_data.vertices.clear(); - _aabb = AABB(); -#ifdef TOOLS_ENABLED - _gizmo_overlap_zones.clear(); -#endif -} - -Room::Room() { - _room_rid = RID_PRIME(RenderingServer::get_singleton()->room_create()); -} - -Room::~Room() { - if (_room_rid != RID()) { - RenderingServer::get_singleton()->free(_room_rid); - } -} - -bool Room::contains_point(const Vector3 &p_pt) const { - if (!_aabb.has_point(p_pt)) { - return false; - } - - for (int n = 0; n < _planes.size(); n++) { - if (_planes[n].is_point_over(p_pt)) { - return false; - } - } - - return true; -} - -void Room::set_room_simplify(real_t p_value) { - _simplify_info.set_simplify(p_value, _aabb.get_longest_axis_size()); -} - -void Room::set_use_default_simplify(bool p_use) { - _use_default_simplify = p_use; -} - -void Room::set_point(int p_idx, const Vector3 &p_point) { - ERR_FAIL_INDEX_MSG(p_idx, _bound_pts.size(), "Index out of bounds. Call set_points() to set the size of the array."); - - _bound_pts.set(p_idx, p_point); - -#ifdef TOOLS_ENABLED - _changed(true); -#endif -} - -void Room::set_points(const PoolVector &p_points) { - _bound_pts = p_points; - -#ifdef TOOLS_ENABLED - if (p_points.size()) { - _changed(true); - } -#endif -} - -PoolVector Room::get_points() const { - return _bound_pts; -} - -PoolVector Room::generate_points() { - PoolVector pts_returned; -#ifdef TOOLS_ENABLED - // do a rooms convert to make sure the planes are up to date - RoomManager *rm = RoomManager::active_room_manager; - if (rm) { - rm->rooms_convert(); - } - - if (!_planes.size()) { - return pts_returned; - } - - // scale an epsilon using 10.0 for a normal sized room - real_t scaled_epsilon = _aabb.get_longest_axis_size() / 10.0; - scaled_epsilon = MAX(scaled_epsilon * 0.01, 0.001); - - LocalVector pts; - pts = Geometry::compute_convex_mesh_points(&_planes[0], _planes.size(), scaled_epsilon); - - // eliminate duplicates - for (int n = 0; n < pts.size(); n++) { - const Vector3 &a = pts[n]; - - for (int m = n + 1; m < pts.size(); m++) { - const Vector3 &b = pts[m]; - if (a.is_equal_approx(b, scaled_epsilon)) { - // remove b - pts.remove_unordered(m); - m--; // repeat m as the new m is the old last - } - } - } - - // convert vector to poolvector - pts_returned.resize(pts.size()); - - Transform tr = get_global_transform(); - tr.affine_invert(); - - for (int n = 0; n < pts.size(); n++) { - // the points should be saved in LOCAL space, - // so that if we move the room afterwards, the bound points - // will also move in relation to the room. - pts_returned.set(n, tr.xform(pts[n])); - } - -#endif - return pts_returned; -} - -String Room::get_configuration_warning() const { - String warning = Spatial::get_configuration_warning(); - - auto lambda = [](const Node *p_node) { - return static_cast((Object::cast_to(p_node) || Object::cast_to(p_node) || Object::cast_to(p_node))); - }; - - if (detect_nodes_using_lambda(this, lambda)) { - if (detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("A Room cannot have another Room as a child or grandchild."); - } - - if (detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("The RoomManager should not be placed inside a Room."); - } - - if (detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("A RoomGroup should not be placed inside a Room."); - } - } - - if (_planes.size() > 80) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("Room convex hull contains a large number of planes.\nConsider simplifying the room bound in order to increase performance."); - } - - return warning; -} - -// extra editor links to the room manager to allow unloading -// on change, or re-converting -void Room::_changed(bool p_regenerate_bounds) { -#ifdef TOOLS_ENABLED - RoomManager *rm = RoomManager::active_room_manager; - if (!rm) { - return; - } - - if (p_regenerate_bounds) { - rm->_room_regenerate_bound(this); - } - rm->_rooms_changed("changed Room " + get_name()); -#endif -} - -void Room::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_WORLD: { - ERR_FAIL_COND(get_world_3d().is_null()); - RenderingServer::get_singleton()->room_set_scenario(_room_rid, get_world_3d()->get_scenario()); - } break; - case NOTIFICATION_EXIT_WORLD: { - RenderingServer::get_singleton()->room_set_scenario(_room_rid, RID()); - } break; - } -} - -void Room::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_use_default_simplify", "p_use"), &Room::set_use_default_simplify); - ClassDB::bind_method(D_METHOD("get_use_default_simplify"), &Room::get_use_default_simplify); - - ClassDB::bind_method(D_METHOD("set_room_simplify", "p_value"), &Room::set_room_simplify); - ClassDB::bind_method(D_METHOD("get_room_simplify"), &Room::get_room_simplify); - - ClassDB::bind_method(D_METHOD("set_points", "points"), &Room::set_points); - ClassDB::bind_method(D_METHOD("get_points"), &Room::get_points); - - ClassDB::bind_method(D_METHOD("set_point", "index", "position"), &Room::set_point); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_simplify"), "set_use_default_simplify", "get_use_default_simplify"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "room_simplify", PROPERTY_HINT_RANGE, "0.0,1.0,0.005"), "set_room_simplify", "get_room_simplify"); - - ADD_GROUP("Bound", ""); - ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "points"), "set_points", "get_points"); -} diff --git a/scene/3d/room.h b/scene/3d/room.h deleted file mode 100644 index c2c1f3c..0000000 --- a/scene/3d/room.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef ROOM_H -#define ROOM_H -/*************************************************************************/ -/* room.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/containers/local_vector.h" -#include "core/math/geometry.h" -#include "core/containers/rid.h" -#include "scene/main/spatial.h" - -class Portal; - -class Room : public Spatial { - GDCLASS(Room, Spatial); - - friend class RoomManager; - friend class RoomGroup; - friend class Portal; - friend class RoomGizmoPlugin; - friend class RoomEditorPlugin; - friend class RoomSpatialGizmo; - - RID _room_rid; - -public: - struct SimplifyInfo { - SimplifyInfo() { set_simplify(0.5); } - void set_simplify(real_t p_value, real_t p_room_size = 0.0); - bool add_plane_if_unique(LocalVector &r_planes, const Plane &p) const; - real_t _plane_simplify = 0.5; - real_t _plane_simplify_dot = 0.98; - real_t _plane_simplify_dist = 0.08; - }; - - Room(); - ~Room(); - - void set_room_simplify(real_t p_value); - real_t get_room_simplify() const { return _simplify_info._plane_simplify; } - - // whether to use the room manager default - void set_use_default_simplify(bool p_use); - bool get_use_default_simplify() const { return _use_default_simplify; } - - void set_points(const PoolVector &p_points); - PoolVector get_points() const; - - // primarily for the gizmo - void set_point(int p_idx, const Vector3 &p_point); - - // editor only - PoolVector generate_points(); - - String get_configuration_warning() const; - -private: - // call during each conversion - void clear(); - - void _changed(bool p_regenerate_bounds = false); - template - static bool detect_nodes_of_type(const Node *p_node, bool p_ignore_first_node = true); - template - static bool detect_nodes_using_lambda(const Node *p_node, T p_lambda, bool p_ignore_first_node = true); - - // note this is client side, and does not use the final planes stored in the PortalRenderer - bool contains_point(const Vector3 &p_pt) const; - - // planes forming convex hull of room - LocalVector _planes; - - // preliminary planes are created during the first conversion pass, - // they do not include the portals, and are used for identifying auto - // linkage of rooms by portals - LocalVector _preliminary_planes; - - Geometry::MeshData _bound_mesh_data; - AABB _aabb; - - // editable points making up the bound - PoolVector _bound_pts; - -#ifdef TOOLS_ENABLED - // to help with editing, when converting, we can generate overlap zones - // that occur between rooms. Ideally these should not occur, as rooms - // should be convex and non-overlapping. But if they do occur, they should - // be minimized. - Vector _gizmo_overlap_zones; -#endif - - // makes sure lrooms are not converted more than once per - // call to rooms_convert - int _conversion_tick = -1; - - // room ID during conversion, used for matching portals links to rooms - int _room_ID; - - // room priority allows rooms to be placed inside other rooms, - // such as a house on a landscape room. - // If the camera is inside more than one room, the higher priority room - // will *win* (e.g. house, rather than landscape) - int _room_priority = 0; - - // a room may be in one or several roomgroups - LocalVector _roomgroups; - - // list of portal ids from or to this room, just used in conversion to determine room bound - LocalVector _portals; - - // each room now stores simplification data - SimplifyInfo _simplify_info; - bool _use_default_simplify = true; - -protected: - static void _bind_methods(); - void _notification(int p_what); -}; - -template -bool Room::detect_nodes_of_type(const Node *p_node, bool p_ignore_first_node) { - if (Object::cast_to(p_node) && (!p_ignore_first_node)) { - return true; - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - if (detect_nodes_of_type(p_node->get_child(n), false)) { - return true; - } - } - - return false; -} - -template -bool Room::detect_nodes_using_lambda(const Node *p_node, T p_lambda, bool p_ignore_first_node) { - if (p_lambda(p_node) && !p_ignore_first_node) { - return true; - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - if (detect_nodes_using_lambda(p_node->get_child(n), p_lambda, false)) { - return true; - } - } - return false; -} - -#endif diff --git a/scene/3d/room_group.cpp b/scene/3d/room_group.cpp deleted file mode 100644 index ba9a647..0000000 --- a/scene/3d/room_group.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/*************************************************************************/ -/* room_group.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "room_group.h" - -#include "core/math/geometry.h" -#include "room.h" -#include "room_manager.h" -#include "scene/resources/mesh/mesh.h" -#include "scene/resources/world_3d.h" -#include "servers/rendering_server.h" - -void RoomGroup::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_roomgroup_priority", "p_priority"), &RoomGroup::set_roomgroup_priority); - ClassDB::bind_method(D_METHOD("get_roomgroup_priority"), &RoomGroup::get_roomgroup_priority); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "roomgroup_priority", PROPERTY_HINT_RANGE, "-16,16,1", PROPERTY_USAGE_DEFAULT), "set_roomgroup_priority", "get_roomgroup_priority"); -} - -RoomGroup::RoomGroup() { - _room_group_rid = RID_PRIME(RenderingServer::get_singleton()->roomgroup_create()); -} - -RoomGroup::~RoomGroup() { - if (_room_group_rid != RID()) { - RenderingServer::get_singleton()->free(_room_group_rid); - } -} - -String RoomGroup::get_configuration_warning() const { - String warning = Spatial::get_configuration_warning(); - - if (Room::detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("The RoomManager should not be placed inside a RoomGroup."); - } - - return warning; -} - -void RoomGroup::clear() { - _roomgroup_ID = -1; -} - -void RoomGroup::add_room(Room *p_room) { - RenderingServer::get_singleton()->roomgroup_add_room(_room_group_rid, p_room->_room_rid); -} - -// extra editor links to the room manager to allow unloading -// on change, or re-converting -void RoomGroup::_changed() { -#ifdef TOOLS_ENABLED - RoomManager *rm = RoomManager::active_room_manager; - if (!rm) { - return; - } - - rm->_rooms_changed("changed RoomGroup " + get_name()); -#endif -} - -void RoomGroup::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_WORLD: { - ERR_FAIL_COND(get_world_3d().is_null()); - RenderingServer::get_singleton()->roomgroup_set_scenario(_room_group_rid, get_world_3d()->get_scenario()); - } break; - case NOTIFICATION_EXIT_WORLD: { - RenderingServer::get_singleton()->roomgroup_set_scenario(_room_group_rid, RID()); - } break; - } -} diff --git a/scene/3d/room_group.h b/scene/3d/room_group.h deleted file mode 100644 index 14b5b3a..0000000 --- a/scene/3d/room_group.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef ROOM_GROUP_H -#define ROOM_GROUP_H -/*************************************************************************/ -/* room_group.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/containers/rid.h" -#include "scene/main/spatial.h" - -class Room; - -class RoomGroup : public Spatial { - GDCLASS(RoomGroup, Spatial); - - friend class RoomManager; - - RID _room_group_rid; - -public: - RoomGroup(); - ~RoomGroup(); - - void add_room(Room *p_room); - - void set_roomgroup_priority(int p_priority) { - _settings_priority = p_priority; - _changed(); - } - int get_roomgroup_priority() const { return _settings_priority; } - - String get_configuration_warning() const; - -private: - void clear(); - void _changed(); - - // roomgroup ID during conversion - int _roomgroup_ID; - - // the roomgroup can be used to set a number of rooms to a different priority - // to allow a group of rooms WITHIN another room / rooms. - // This is for e.g. buildings on landscape. - int _settings_priority = 0; - - // makes sure lrooms are not converted more than once per - // call to rooms_convert - int _conversion_tick = -1; - -protected: - static void _bind_methods(); - void _notification(int p_what); -}; - -#endif diff --git a/scene/3d/room_manager.cpp b/scene/3d/room_manager.cpp deleted file mode 100644 index e697933..0000000 --- a/scene/3d/room_manager.cpp +++ /dev/null @@ -1,2322 +0,0 @@ -/*************************************************************************/ -/* room_manager.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "room_manager.h" - -#include "core/config/engine.h" -#include "core/config/project_settings.h" -#include "core/containers/bitfield_dynamic.h" -#include "core/math/geometry.h" -#include "core/math/quick_hull.h" -#include "core/os/os.h" -#include "mesh_instance.h" -#include "multimesh_instance.h" -#include "portal.h" -#include "room_group.h" -#include "scene/3d/camera.h" -#include "scene/3d/light.h" -#include "scene/3d/sprite_3d.h" -#include "scene/resources/mesh/multimesh.h" -#include "scene/resources/world_3d.h" -#include "visibility_notifier.h" - -#ifdef TOOLS_ENABLED -#include "editor/plugins/spatial_editor_plugin.h" -#include "editor/editor_node.h" -#endif - -#include "modules/modules_enabled.gen.h" // For csg. - -#ifdef MODULE_CSG_ENABLED -#include "modules/csg/csg_shape.h" -#endif - -// #define PANDEMONIUM_PORTALS_USE_BULLET_CONVEX_HULL - -#ifdef PANDEMONIUM_PORTALS_USE_BULLET_CONVEX_HULL -#include "core/math/convex_hull.h" -#endif - -// This needs to be static because it cannot easily be propagated to portals -// during load (as the RoomManager may be loaded before Portals enter the scene tree) -real_t RoomManager::_default_portal_margin = 1.0; - -#ifdef TOOLS_ENABLED -RoomManager *RoomManager::active_room_manager = nullptr; - -// static versions of functions for use from editor toolbars -void RoomManager::static_rooms_set_active(bool p_active) { - if (active_room_manager) { - active_room_manager->rooms_set_active(p_active); - active_room_manager->property_list_changed_notify(); - } -} - -bool RoomManager::static_rooms_get_active() { - if (active_room_manager) { - return active_room_manager->rooms_get_active(); - } - - return false; -} - -bool RoomManager::static_rooms_get_active_and_loaded() { - if (active_room_manager) { - if (active_room_manager->rooms_get_active()) { - Ref world = active_room_manager->get_world_3d(); - RID scenario = world->get_scenario(); - return active_room_manager->rooms_get_active() && RenderingServer::get_singleton()->rooms_is_loaded(scenario); - } - } - - return false; -} - -void RoomManager::static_rooms_convert() { - if (active_room_manager) { - return active_room_manager->rooms_convert(); - } -} -#endif - -RoomManager::RoomManager() { - // some high value, we want room manager to be processed after other - // nodes because the camera should be moved first - set_process_priority(10000); -} - -RoomManager::~RoomManager() { -} - -String RoomManager::get_configuration_warning() const { - String warning = Spatial::get_configuration_warning(); - - if (_settings_path_roomlist == NodePath()) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("The RoomList has not been assigned."); - } else { - Spatial *roomlist = _resolve_path(_settings_path_roomlist); - if (!roomlist) { - // possibly also check (roomlist->get_class_name() != StringName("Spatial")) - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("The RoomList node should be a Spatial (or derived from Spatial)."); - } - } - - if (_settings_portal_depth_limit == 0) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("Portal Depth Limit is set to Zero.\nOnly the Room that the Camera is in will render."); - } - - if (Room::detect_nodes_of_type(this)) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("There should only be one RoomManager in the SceneTree."); - } - - return warning; -} - -void RoomManager::_preview_camera_update() { - Ref world = get_world_3d(); - RID scenario = world->get_scenario(); - - if (_pandemonium_preview_camera_ID != (ObjectID)-1) { - Camera *cam = Object::cast_to(ObjectDB::get_instance(_pandemonium_preview_camera_ID)); - if (!cam) { - _pandemonium_preview_camera_ID = (ObjectID)-1; - } else { - // get camera position and direction - Vector3 camera_pos = cam->get_global_transform().origin; - Vector planes = cam->get_frustum(); - - // only update the visual server when there is a change.. as it will request a screen redraw - // this is kinda silly, but the other way would be keeping track of the override camera in visual server - // and tracking the camera deletes, which might be more error prone for a debug feature... - bool changed = false; - if (camera_pos != _pandemonium_camera_pos) { - changed = true; - } - // check planes - if (!changed) { - if (planes.size() != _pandemonium_camera_planes.size()) { - changed = true; - } - } - - if (!changed) { - // num of planes must be identical - for (int n = 0; n < planes.size(); n++) { - if (planes[n] != _pandemonium_camera_planes[n]) { - changed = true; - break; - } - } - } - - if (changed) { - _pandemonium_camera_pos = camera_pos; - _pandemonium_camera_planes = planes; - RenderingServer::get_singleton()->rooms_override_camera(scenario, true, camera_pos, &planes); - } - } - } -} - -void RoomManager::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (Engine::get_singleton()->is_editor_hint()) { - set_process_internal(_pandemonium_preview_camera_ID != (ObjectID)-1); -#ifdef TOOLS_ENABLED - // note this mechanism may fail to work correctly if the user creates two room managers, - // but should not create major problems as it is just used to auto update when portals etc - // are changed in the editor, and there is a check for nullptr. - active_room_manager = this; - SpatialEditor *spatial_editor = SpatialEditor::get_singleton(); - if (spatial_editor) { - spatial_editor->update_portal_tools(); - } -#endif - } else { - if (_settings_gameplay_monitor_enabled) { - set_process_internal(true); - } - } - } break; - case NOTIFICATION_EXIT_TREE: { -#ifdef TOOLS_ENABLED - active_room_manager = nullptr; - if (Engine::get_singleton()->is_editor_hint()) { - SpatialEditor *spatial_editor = SpatialEditor::get_singleton(); - if (spatial_editor) { - spatial_editor->update_portal_tools(); - } - } -#endif - } break; - case NOTIFICATION_INTERNAL_PROCESS: { - // can't call visual server if not inside world - if (!is_inside_world()) { - return; - } - - if (Engine::get_singleton()->is_editor_hint()) { - _preview_camera_update(); - return; - } - - if (_settings_gameplay_monitor_enabled) { - Ref world = get_world_3d(); - RID scenario = world->get_scenario(); - - List cameras; - world->get_camera_list(&cameras); - - Vector positions; - - for (int n = 0; n < cameras.size(); n++) { - positions.push_back(cameras[n]->get_global_transform().origin); - } - - RenderingServer::get_singleton()->rooms_update_gameplay_monitor(scenario, positions); - } - - } break; - } -} - -void RoomManager::_bind_methods() { - BIND_ENUM_CONSTANT(PVS_MODE_DISABLED); - BIND_ENUM_CONSTANT(PVS_MODE_PARTIAL); - BIND_ENUM_CONSTANT(PVS_MODE_FULL); - - // main functions - ClassDB::bind_method(D_METHOD("rooms_convert"), &RoomManager::rooms_convert); - ClassDB::bind_method(D_METHOD("rooms_clear"), &RoomManager::rooms_clear); - - ClassDB::bind_method(D_METHOD("set_pvs_mode", "pvs_mode"), &RoomManager::set_pvs_mode); - ClassDB::bind_method(D_METHOD("get_pvs_mode"), &RoomManager::get_pvs_mode); - - ClassDB::bind_method(D_METHOD("set_roomlist_path", "p_path"), &RoomManager::set_roomlist_path); - ClassDB::bind_method(D_METHOD("get_roomlist_path"), &RoomManager::get_roomlist_path); - - // These are commented out for now, but available in case we want to cache PVS to disk, the functionality exists - // ClassDB::bind_method(D_METHOD("set_pvs_filename", "pvs_filename"), &RoomManager::set_pvs_filename); - // ClassDB::bind_method(D_METHOD("get_pvs_filename"), &RoomManager::get_pvs_filename); - - // just some macros to make setting inspector values easier -#define LPORTAL_STRINGIFY(x) #x -#define LPORTAL_TOSTRING(x) LPORTAL_STRINGIFY(x) - -#define LIMPL_PROPERTY(P_TYPE, P_NAME, P_SET, P_GET) \ - ClassDB::bind_method(D_METHOD(LPORTAL_TOSTRING(P_SET), LPORTAL_TOSTRING(P_NAME)), &RoomManager::P_SET); \ - ClassDB::bind_method(D_METHOD(LPORTAL_TOSTRING(P_GET)), &RoomManager::P_GET); \ - ADD_PROPERTY(PropertyInfo(P_TYPE, LPORTAL_TOSTRING(P_NAME)), LPORTAL_TOSTRING(P_SET), LPORTAL_TOSTRING(P_GET)); - -#define LIMPL_PROPERTY_RANGE(P_TYPE, P_NAME, P_SET, P_GET, P_RANGE_STRING) \ - ClassDB::bind_method(D_METHOD(LPORTAL_TOSTRING(P_SET), LPORTAL_TOSTRING(P_NAME)), &RoomManager::P_SET); \ - ClassDB::bind_method(D_METHOD(LPORTAL_TOSTRING(P_GET)), &RoomManager::P_GET); \ - ADD_PROPERTY(PropertyInfo(P_TYPE, LPORTAL_TOSTRING(P_NAME), PROPERTY_HINT_RANGE, P_RANGE_STRING), LPORTAL_TOSTRING(P_SET), LPORTAL_TOSTRING(P_GET)); - - ADD_GROUP("Main", ""); - LIMPL_PROPERTY(Variant::BOOL, active, rooms_set_active, rooms_get_active); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "roomlist", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial"), "set_roomlist_path", "get_roomlist_path"); - - ADD_GROUP("PVS", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "pvs_mode", PROPERTY_HINT_ENUM, "Disabled,Partial,Full"), "set_pvs_mode", "get_pvs_mode"); - // ADD_PROPERTY(PropertyInfo(Variant::STRING, "pvs_filename", PROPERTY_HINT_FILE, "*.pvs"), "set_pvs_filename", "get_pvs_filename"); - - ADD_GROUP("Gameplay", ""); - LIMPL_PROPERTY(Variant::BOOL, gameplay_monitor, set_gameplay_monitor_enabled, get_gameplay_monitor_enabled); - LIMPL_PROPERTY(Variant::BOOL, use_secondary_pvs, set_use_secondary_pvs, get_use_secondary_pvs); - - ADD_GROUP("Optimize", ""); - LIMPL_PROPERTY(Variant::BOOL, merge_meshes, set_merge_meshes, get_merge_meshes); - - ADD_GROUP("Debug", ""); - LIMPL_PROPERTY(Variant::BOOL, show_margins, set_show_margins, get_show_margins); - LIMPL_PROPERTY(Variant::BOOL, debug_sprawl, set_debug_sprawl, get_debug_sprawl); - LIMPL_PROPERTY_RANGE(Variant::INT, overlap_warning_threshold, set_overlap_warning_threshold, get_overlap_warning_threshold, "1,1000,1"); - LIMPL_PROPERTY(Variant::NODE_PATH, preview_camera, set_preview_camera_path, get_preview_camera_path); - - ADD_GROUP("Advanced", ""); - LIMPL_PROPERTY_RANGE(Variant::INT, portal_depth_limit, set_portal_depth_limit, get_portal_depth_limit, "0,255,1"); - LIMPL_PROPERTY_RANGE(Variant::REAL, room_simplify, set_room_simplify, get_room_simplify, "0.0,1.0,0.005"); - LIMPL_PROPERTY_RANGE(Variant::REAL, default_portal_margin, set_default_portal_margin, get_default_portal_margin, "0.0, 10.0, 0.01"); - LIMPL_PROPERTY_RANGE(Variant::REAL, roaming_expansion_margin, set_roaming_expansion_margin, get_roaming_expansion_margin, "0.0, 3.0, 0.01"); - -#undef LIMPL_PROPERTY -#undef LIMPL_PROPERTY_RANGE -#undef LPORTAL_STRINGIFY -#undef LPORTAL_TOSTRING -} - -void RoomManager::_refresh_from_project_settings() { - _settings_use_simple_pvs = GLOBAL_GET("rendering/portals/pvs/use_simple_pvs"); - _settings_log_pvs_generation = GLOBAL_GET("rendering/portals/pvs/pvs_logging"); - _settings_use_signals = GLOBAL_GET("rendering/portals/gameplay/use_signals"); - _settings_remove_danglers = GLOBAL_GET("rendering/portals/optimize/remove_danglers"); - _show_debug = GLOBAL_GET("rendering/portals/debug/logging"); - Portal::_portal_plane_convention = GLOBAL_GET("rendering/portals/advanced/flip_imported_portals"); - - // force not to show logs when not in editor - if (!Engine::get_singleton()->is_editor_hint()) { - _show_debug = false; - _settings_log_pvs_generation = false; - } -} - -void RoomManager::set_roomlist_path(const NodePath &p_path) { - _settings_path_roomlist = p_path; - update_configuration_warning(); -} - -void RoomManager::set_preview_camera_path(const NodePath &p_path) { - _settings_path_preview_camera = p_path; - - resolve_preview_camera_path(); - - bool camera_on = _pandemonium_preview_camera_ID != (ObjectID)-1; - - // make sure the cached camera planes are invalid, this will - // force an update to the visual server on the next internal_process - _pandemonium_camera_planes.clear(); - - // if in the editor, turn processing on or off - // according to whether the camera is overridden - if (Engine::get_singleton()->is_editor_hint()) { - if (is_inside_tree()) { - set_process_internal(camera_on); - } - } - - // if we are turning camera override off, must inform visual server - if (!camera_on && is_inside_world() && get_world_3d().is_valid() && get_world_3d()->get_scenario().is_valid()) { - RenderingServer::get_singleton()->rooms_override_camera(get_world_3d()->get_scenario(), false, Vector3(), nullptr); - } - - // we couldn't resolve the path, let's set it to null - if (!camera_on) { - _settings_path_preview_camera = NodePath(); - } -} - -void RoomManager::set_room_simplify(real_t p_value) { - _room_simplify_info.set_simplify(p_value); -} - -real_t RoomManager::get_room_simplify() const { - return _room_simplify_info._plane_simplify; -} - -void RoomManager::set_portal_depth_limit(int p_limit) { - _settings_portal_depth_limit = p_limit; - - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_set_params(get_world_3d()->get_scenario(), p_limit, _settings_roaming_expansion_margin); - } -} - -void RoomManager::set_roaming_expansion_margin(real_t p_dist) { - _settings_roaming_expansion_margin = p_dist; - - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_set_params(get_world_3d()->get_scenario(), _settings_portal_depth_limit, _settings_roaming_expansion_margin); - } -} - -void RoomManager::set_default_portal_margin(real_t p_dist) { - _default_portal_margin = p_dist; - - // send to portals - Spatial *roomlist = _resolve_path(_settings_path_roomlist); - if (!roomlist) { - return; - } - - _update_portal_gizmos(roomlist); -} - -void RoomManager::_update_portal_gizmos(Spatial *p_node) { - Portal *portal = Object::cast_to(p_node); - - if (portal) { - portal->update_gizmos(); - } - - // recurse - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - - if (child) { - _update_portal_gizmos(child); - } - } -} - -real_t RoomManager::get_default_portal_margin() const { - return _default_portal_margin; -} - -void RoomManager::set_show_margins(bool p_show) { - Portal::_settings_gizmo_show_margins = p_show; - - Spatial *roomlist = _resolve_path(_settings_path_roomlist); - if (!roomlist) { - return; - } - - _update_gizmos_recursive(roomlist); -} - -bool RoomManager::get_show_margins() const { - return Portal::_settings_gizmo_show_margins; -} - -void RoomManager::set_debug_sprawl(bool p_enable) { - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_set_debug_feature(get_world_3d()->get_scenario(), RenderingServer::ROOMS_DEBUG_SPRAWL, p_enable); - _debug_sprawl = p_enable; - } -} - -bool RoomManager::get_debug_sprawl() const { - return _debug_sprawl; -} - -void RoomManager::set_merge_meshes(bool p_enable) { - _settings_merge_meshes = p_enable; -} - -bool RoomManager::get_merge_meshes() const { - return _settings_merge_meshes; -} - -void RoomManager::show_warning(const String &p_string, bool p_skippable, bool p_alert) { - if (p_skippable && !Engine::get_singleton()->is_editor_hint() && !_show_debug) { - return; - } - - WARN_PRINT(p_string); - // OS::get_singleton()->alert(p_string, p_title); -#ifdef TOOLS_ENABLED - if (p_alert && Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->show_warning(TTRGET(p_string)); - } -#endif -} - -void RoomManager::debug_print_line(String p_string, int p_priority) { - if (_show_debug) { - if (!p_priority) { - print_verbose(p_string); - } else { - print_line(p_string); - } - } -} - -void RoomManager::rooms_set_active(bool p_active) { - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_set_active(get_world_3d()->get_scenario(), p_active); - _active = p_active; - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - SpatialEditor *spatial_editor = SpatialEditor::get_singleton(); - if (spatial_editor) { - spatial_editor->update_portal_tools(); - } - } -#endif - } -} - -bool RoomManager::rooms_get_active() const { - return _active; -} - -void RoomManager::set_pvs_mode(PVSMode p_mode) { - _pvs_mode = p_mode; -} - -RoomManager::PVSMode RoomManager::get_pvs_mode() const { - return _pvs_mode; -} - -void RoomManager::set_pvs_filename(String p_filename) { - _pvs_filename = p_filename; -} - -String RoomManager::get_pvs_filename() const { - return _pvs_filename; -} - -void RoomManager::_rooms_changed(String p_reason) { - _rooms.clear(); - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_unload(get_world_3d()->get_scenario(), p_reason); - } -} - -void RoomManager::rooms_clear() { - _rooms.clear(); - if (is_inside_world() && get_world_3d().is_valid()) { - RenderingServer::get_singleton()->rooms_and_portals_clear(get_world_3d()->get_scenario()); - } -} - -void RoomManager::rooms_flip_portals() { - // this is a helper emergency function to deal with situations where the user has ended up with Portal nodes - // pointing in the wrong direction (by doing initial conversion with flip_portal_meshes set incorrectly). - _roomlist = _resolve_path(_settings_path_roomlist); - if (!_roomlist) { - WARN_PRINT("Cannot resolve nodepath"); - show_warning(TTR("RoomList path is invalid.\nPlease check the RoomList branch has been assigned in the RoomManager.")); - return; - } - - _flip_portals_recursive(_roomlist); - _rooms_changed("flipped Portals"); -} - -void RoomManager::rooms_convert() { - // set all error conditions to false - _warning_misnamed_nodes_detected = false; - _warning_portal_link_room_not_found = false; - _warning_portal_autolink_failed = false; - _warning_room_overlap_detected = false; - - _refresh_from_project_settings(); - - _roomlist = _resolve_path(_settings_path_roomlist); - if (!_roomlist) { - WARN_PRINT("Cannot resolve nodepath"); - show_warning(TTR("RoomList path is invalid.\nPlease check the RoomList branch has been assigned in the RoomManager.")); - return; - } - - ERR_FAIL_COND(!is_inside_world() || !get_world_3d().is_valid()); - - // every time we run convert we increment this, - // to prevent individual rooms / portals being converted - // more than once in one run - _conversion_tick++; - - rooms_clear(); - - // first check that the roomlist is valid, and the user hasn't made - // a silly scene tree - if (!_check_roomlist_validity(_roomlist)) { - return; - } - - LocalVector portals; - LocalVector roomgroups; - - // find the rooms and portals - _convert_rooms_recursive(_roomlist, portals, roomgroups); - - if (!_rooms.size()) { - rooms_clear(); - show_warning(TTR("RoomList contains no Rooms, aborting.")); - return; - } - - // add portal links - _second_pass_portals(_roomlist, portals); - - // create the statics - _second_pass_rooms(roomgroups, portals); - - // third pass - - // autolink portals that are not already manually linked - // and finalize the portals - _autolink_portals(_roomlist, portals); - - // Find the statics AGAIN and only this time add them to the PortalRenderer. - // We need to do this twice because the room points determine the room bound... - // but the bound is needed for autolinking, - // and the autolinking needs to be done BEFORE adding to the PortalRenderer so that - // the static objects will correctly sprawl. It is a chicken and egg situation. - // Also finalize the room hulls. - _third_pass_rooms(portals); - - // now we run autoplace to place any statics that have not been explicitly placed in rooms. - // These will by definition not affect the room bounds, but is convenient for users to edit - // levels in a more freeform manner - _autoplace_recursive(_roomlist); - - bool generate_pvs = false; - bool pvs_cull = false; - switch (_pvs_mode) { - default: { - } break; - case PVS_MODE_PARTIAL: { - generate_pvs = true; - } break; - case PVS_MODE_FULL: { - generate_pvs = true; - pvs_cull = true; - } break; - } - - RenderingServer::get_singleton()->rooms_finalize(get_world_3d()->get_scenario(), generate_pvs, pvs_cull, _settings_use_secondary_pvs, _settings_use_signals, _pvs_filename, _settings_use_simple_pvs, _settings_log_pvs_generation); - - // refresh portal depth limit - set_portal_depth_limit(get_portal_depth_limit()); - -#ifdef TOOLS_ENABLED - _generate_room_overlap_zones(); -#endif - - // just delete any intermediate data - _cleanup_after_conversion(); - - // display error dialogs - if (_warning_misnamed_nodes_detected) { - show_warning(TTR("Misnamed nodes detected, check output log for details. Aborting.")); - rooms_clear(); - } - - if (_warning_portal_link_room_not_found) { - show_warning(TTR("Portal link room not found, check output log for details."), true); - } - - if (_warning_portal_autolink_failed) { - show_warning(TTR("Portal autolink failed, check output log for details.\nCheck the portal is facing outwards from the source room."), true); - } - - if (_warning_room_overlap_detected) { - show_warning(TTR("Room overlap detected, cameras may work incorrectly in overlapping area.\nCheck output log for details."), true); - } -} - -void RoomManager::_second_pass_room(Room *p_room, const LocalVector &p_roomgroups, const LocalVector &p_portals) { - if (_settings_merge_meshes) { - _merge_meshes_in_room(p_room); - } - - // find statics and manual bound - bool manual_bound_found = false; - - // points making up the room geometry, in world space, to create the convex hull - Vector room_pts; - - for (int n = 0; n < p_room->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_room->get_child(n)); - - if (child) { - if (_node_is_type(child) || child->is_queued_for_deletion()) { - // the adding of portal points is done after this stage, because - // we need to take into account incoming as well as outgoing portals - } else if (_name_ends_with(child, "-bound")) { - manual_bound_found = _convert_manual_bound(p_room, child, p_portals); - } else { - // don't add the instances to the portal renderer on the first pass of _find_statics, - // just find the geometry points in order to make sure the bound is correct. - _find_statics_recursive(p_room, child, room_pts, false); - } - } - } - - // Has the bound been specified using points in the room? - // in that case, overwrite the room_pts - if (p_room->_bound_pts.size() && p_room->is_inside_tree()) { - Transform tr = p_room->get_global_transform(); - - room_pts.clear(); - room_pts.resize(p_room->_bound_pts.size()); - for (int n = 0; n < room_pts.size(); n++) { - room_pts.set(n, tr.xform(p_room->_bound_pts[n])); - } - - // we override and manual bound with the room points - manual_bound_found = false; - } - - if (!manual_bound_found) { - // rough aabb for checking portals for warning conditions - AABB aabb; - aabb.create_from_points(room_pts); - - for (int n = 0; n < p_room->_portals.size(); n++) { - int portal_id = p_room->_portals[n]; - Portal *portal = p_portals[portal_id]; - - // only checking portals out from source room - if (portal->_linkedroom_ID[0] != p_room->_room_ID) { - continue; - } - - // don't add portals to the world bound that are internal to this room! - if (portal->is_portal_internal(p_room->_room_ID)) { - continue; - } - - // check portal for suspect conditions, like a long way from the room AABB, - // or possibly flipped the wrong way - _check_portal_for_warnings(portal, aabb); - } - - // create convex hull - _convert_room_hull_preliminary(p_room, room_pts, p_portals); - } - - // add the room to roomgroups - for (int n = 0; n < p_room->_roomgroups.size(); n++) { - int roomgroup_id = p_room->_roomgroups[n]; - p_roomgroups[roomgroup_id]->add_room(p_room); - } -} - -void RoomManager::_second_pass_rooms(const LocalVector &p_roomgroups, const LocalVector &p_portals) { - for (int n = 0; n < _rooms.size(); n++) { - _second_pass_room(_rooms[n], p_roomgroups, p_portals); - } -} - -#ifdef TOOLS_ENABLED -void RoomManager::_generate_room_overlap_zones() { - for (int n = 0; n < _rooms.size(); n++) { - Room *room = _rooms[n]; - - // no planes .. no overlap - if (!room->_planes.size()) { - continue; - } - - for (int c = n + 1; c < _rooms.size(); c++) { - if (c == n) { - continue; - } - Room *other = _rooms[c]; - - // do a quick reject AABB - if (!room->_aabb.intersects(other->_aabb) || (!other->_planes.size())) { - continue; - } - - // if the room priorities are different (i.e. an internal room), they are allowed to overlap - if (room->_room_priority != other->_room_priority) { - continue; - } - - // get all the planes of both rooms in a contiguous list - LocalVector planes; - planes.resize(room->_planes.size() + other->_planes.size()); - Plane *dest = planes.ptr(); - memcpy(dest, &room->_planes[0], room->_planes.size() * sizeof(Plane)); - dest += room->_planes.size(); - - memcpy(dest, &other->_planes[0], other->_planes.size() * sizeof(Plane)); - - Vector overlap_pts = Geometry::compute_convex_mesh_points(planes.ptr(), planes.size()); - - if (overlap_pts.size() < 4) { - continue; - } - - // there is an overlap, create a mesh from the points - Geometry::MeshData md; - Error err = _build_convex_hull(overlap_pts, md); - - if (err != OK) { - WARN_PRINT("QuickHull failed building room overlap hull"); - continue; - } - - // only if the volume is more than some threshold - real_t volume = Geometry::calculate_convex_hull_volume(md); - if (volume > _overlap_warning_threshold) { - WARN_PRINT("Room overlap of " + String(Variant(volume)) + " detected between " + room->get_name() + " and " + other->get_name()); - room->_gizmo_overlap_zones.push_back(md); - _warning_room_overlap_detected = true; - } - } - } -} -#endif - -void RoomManager::_third_pass_rooms(const LocalVector &p_portals) { - bool found_errors = false; - - for (int n = 0; n < _rooms.size(); n++) { - Room *room = _rooms[n]; - - // no need to do all these string operations if we are not debugging and don't need logs - if (_show_debug) { - String room_short_name = _find_name_before(room, "-room", true); - convert_log("ROOM\t" + room_short_name); - - // log output the portals associated with this room - for (int p = 0; p < room->_portals.size(); p++) { - const Portal &portal = *p_portals[room->_portals[p]]; - - bool portal_links_out = portal._linkedroom_ID[0] == room->_room_ID; - - int linked_room_id = (portal_links_out) ? portal._linkedroom_ID[1] : portal._linkedroom_ID[0]; - - // this shouldn't be out of range, but just in case - if ((linked_room_id >= 0) && (linked_room_id < _rooms.size())) { - Room *linked_room = _rooms[linked_room_id]; - - String portal_link_room_name = _find_name_before(linked_room, "-room", true); - String in_or_out = (portal_links_out) ? "POUT" : "PIN "; - - // display the name of the room linked to - convert_log("\t\t" + in_or_out + "\t" + portal_link_room_name); - } else { - WARN_PRINT_ONCE("linked_room_id is out of range"); - } - } - - } // if _show_debug - - // do a second pass finding the statics, where they are - // finally added to the rooms in the portal_renderer. - Vector room_pts; - - // the true indicates we DO want to add to the portal renderer this second time - // we call _find_statics_recursive - _find_statics_recursive(room, room, room_pts, true); - - if (!_convert_room_hull_final(room, p_portals)) { - found_errors = true; - } - room->update_gizmos(); - room->update_configuration_warning(); - } - - if (found_errors) { - show_warning(TTR("Error calculating room bounds.\nEnsure all rooms contain geometry or manual bounds.")); - } -} - -void RoomManager::_second_pass_portals(Spatial *p_roomlist, LocalVector &r_portals) { - for (unsigned int n = 0; n < r_portals.size(); n++) { - Portal *portal = r_portals[n]; - - // we have a choice here. - // If we are importing, we will try linking using the naming convention method. - // We do this by setting the assigned nodepath if we find the link room, then - // the resolving links is done in the usual manner from the nodepath. - if (portal->_importing_portal) { - String string_link_room_shortname = _find_name_before(portal, "-portal"); - String string_link_room = string_link_room_shortname + "-room"; - - if (string_link_room_shortname != "") { - // try the room name plus the postfix first, this will be the most common case during import - Room *linked_room = Object::cast_to(p_roomlist->find_node(string_link_room, true, false)); - - // try the short name as a last ditch attempt - if (!linked_room) { - linked_room = Object::cast_to(p_roomlist->find_node(string_link_room_shortname, true, false)); - } - - if (linked_room) { - NodePath path = portal->get_path_to(linked_room); - portal->set_linked_room_internal(path); - } else { - WARN_PRINT("Portal link room : " + string_link_room + " not found."); - _warning_portal_link_room_not_found = true; - } - } - } - - // get the room we are linking from - int room_from_id = portal->_linkedroom_ID[0]; - if (room_from_id != -1) { - Room *room_from = _rooms[room_from_id]; - portal->resolve_links(_rooms, room_from->_room_rid); - - // add the portal id to the room from and the room to. - // These are used so we can later add the portal geometry to the room bounds. - room_from->_portals.push_back(n); - - int room_to_id = portal->_linkedroom_ID[1]; - if (room_to_id != -1) { - Room *room_to = _rooms[room_to_id]; - room_to->_portals.push_back(n); - - // make the portal internal if necessary - portal->_internal = room_from->_room_priority > room_to->_room_priority; - } - } - } -} - -void RoomManager::_autolink_portals(Spatial *p_roomlist, LocalVector &r_portals) { - for (unsigned int n = 0; n < r_portals.size(); n++) { - Portal *portal = r_portals[n]; - - // all portals should have a source room - DEV_ASSERT(portal->_linkedroom_ID[0] != -1); - const Room *source_room = _rooms[portal->_linkedroom_ID[0]]; - - if (portal->_linkedroom_ID[1] != -1) { - // already manually linked - continue; - } - - bool autolink_found = false; - - // try to autolink - // try points iteratively out from the portal center and find the first that is in a room that isn't the source room - for (int attempt = 0; attempt < 4; attempt++) { - // found - if (portal->_linkedroom_ID[1] != -1) { - break; - } - - // these numbers are arbitrary .. we could alternatively reuse the portal margins for this? - real_t dist = 0.01; - switch (attempt) { - default: { - dist = 0.01; - } break; - case 1: { - dist = 0.1; - } break; - case 2: { - dist = 1.0; - } break; - case 3: { - dist = 2.0; - } break; - } - - Vector3 test_pos = portal->_pt_center_world + (dist * portal->_plane.normal); - - int best_priority = -1000; - int best_room = -1; - - for (int r = 0; r < _rooms.size(); r++) { - Room *room = _rooms[r]; - if (room->_room_ID == portal->_linkedroom_ID[0]) { - // can't link back to the source room - continue; - } - - // first do a rough aabb check - if (!room->_aabb.has_point(test_pos)) { - continue; - } - - bool outside = false; - for (int p = 0; p < room->_preliminary_planes.size(); p++) { - const Plane &plane = room->_preliminary_planes[p]; - if (plane.distance_to(test_pos) > 0.0) { - outside = true; - break; - } - } // for through planes - - if (!outside) { - // we found a suitable room, but we want the highest priority in - // case there are internal rooms... - if (room->_room_priority > best_priority) { - best_priority = room->_room_priority; - best_room = r; - } - } - - } // for through rooms - - // found a suitable link room - if (best_room != -1) { - Room *room = _rooms[best_room]; - - // great, we found a linked room! - convert_log("\t\tAUTOLINK OK from " + source_room->get_name() + " to " + room->get_name(), 1); - portal->_linkedroom_ID[1] = best_room; - - // add the portal to the portals list for the receiving room - room->_portals.push_back(n); - - // send complete link to visual server so the portal will be active in the visual server room system - RenderingServer::get_singleton()->portal_link(portal->_portal_rid, source_room->_room_rid, room->_room_rid, portal->_settings_two_way); - - // make the portal internal if necessary - // (this prevents the portal plane clipping the room bound) - portal->_internal = source_room->_room_priority > room->_room_priority; - - autolink_found = true; - break; - } - - } // for attempt - - // error condition - if (!autolink_found) { - if (_show_debug) { - WARN_PRINT("Portal AUTOLINK failed for " + portal->get_name() + " from " + source_room->get_name()); - } - _warning_portal_autolink_failed = true; - -#ifdef TOOLS_ENABLED - portal->_warning_autolink_failed = true; - portal->update_gizmos(); -#endif - } - } // for portal -} - -// to prevent users creating mistakes for themselves, we limit what can be put into the room list branch. -// returns invalid node, or NULL -bool RoomManager::_check_roomlist_validity(Node *p_node) { - // restrictions lifted here, but we can add more if required - return true; -} - -void RoomManager::_convert_rooms_recursive(Spatial *p_node, LocalVector &r_portals, LocalVector &r_roomgroups, int p_roomgroup) { - // is this a room? - if (_node_is_type(p_node) || _name_ends_with(p_node, "-room")) { - _convert_room(p_node, r_portals, r_roomgroups, p_roomgroup); - } - - // is this a roomgroup? - if (_node_is_type(p_node) || _name_ends_with(p_node, "-roomgroup")) { - p_roomgroup = _convert_roomgroup(p_node, r_roomgroups); - } - - // recurse through children - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - - if (child) { - _convert_rooms_recursive(child, r_portals, r_roomgroups, p_roomgroup); - } - } -} - -int RoomManager::_convert_roomgroup(Spatial *p_node, LocalVector &r_roomgroups) { - String string_full_name = p_node->get_name(); - - // is it already a roomgroup? - RoomGroup *roomgroup = Object::cast_to(p_node); - - // if not already a RoomGroup, convert the node and move all children - if (!roomgroup) { - // create a RoomGroup - roomgroup = _change_node_type(p_node, "G"); - } else { - // already hit this tick? - if (roomgroup->_conversion_tick == _conversion_tick) { - return roomgroup->_roomgroup_ID; - } - } - - convert_log("convert_roomgroup : " + string_full_name, 1); - - // make sure the roomgroup is blank, especially if already created - roomgroup->clear(); - - // make sure the object ID is sent to the visual server - RenderingServer::get_singleton()->roomgroup_prepare(roomgroup->_room_group_rid, roomgroup->get_instance_id()); - - // mark so as only to convert once - roomgroup->_conversion_tick = _conversion_tick; - - roomgroup->_roomgroup_ID = r_roomgroups.size(); - - r_roomgroups.push_back(roomgroup); - - return r_roomgroups.size() - 1; -} - -void RoomManager::_convert_room(Spatial *p_node, LocalVector &r_portals, const LocalVector &p_roomgroups, int p_roomgroup) { - String string_full_name = p_node->get_name(); - - // is it already an lroom? - Room *room = Object::cast_to(p_node); - - // if not already a Room, convert the node and move all children - if (!room) { - // create a Room - room = _change_node_type(p_node, "G"); - } else { - // already hit this tick? - if (room->_conversion_tick == _conversion_tick) { - return; - } - } - - // make sure the room is blank, especially if already created - room->clear(); - - // mark so as only to convert once - room->_conversion_tick = _conversion_tick; - - // set roomgroup - if (p_roomgroup != -1) { - room->_roomgroups.push_back(p_roomgroup); - room->_room_priority = p_roomgroups[p_roomgroup]->_settings_priority; - - RenderingServer::get_singleton()->room_prepare(room->_room_rid, room->_room_priority); - } - - // add to the list of rooms - room->_room_ID = _rooms.size(); - _rooms.push_back(room); - - _find_portals_recursive(room, room, r_portals); -} - -void RoomManager::_find_portals_recursive(Spatial *p_node, Room *p_room, LocalVector &r_portals) { - MeshInstance *mi = Object::cast_to(p_node); - if (_node_is_type(p_node) || (mi && _name_ends_with(mi, "-portal"))) { - _convert_portal(p_room, p_node, r_portals); - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - - if (child) { - _find_portals_recursive(child, p_room, r_portals); - } - } -} - -void RoomManager::_check_portal_for_warnings(Portal *p_portal, const AABB &p_room_aabb_without_portals) { -#ifdef TOOLS_ENABLED - AABB bb = p_room_aabb_without_portals; - bb = bb.grow(bb.get_longest_axis_size() * 0.5); - - bool changed = false; - - // far outside the room? - const Vector3 &pos = p_portal->get_global_transform().origin; - - bool old_outside = p_portal->_warning_outside_room_aabb; - p_portal->_warning_outside_room_aabb = !bb.has_point(pos); - - if (p_portal->_warning_outside_room_aabb != old_outside) { - changed = true; - } - - if (p_portal->_warning_outside_room_aabb) { - WARN_PRINT(String(p_portal->get_name()) + " possibly in the wrong room."); - } - - // facing wrong way? - Vector3 offset = pos - bb.get_center(); - real_t dot = offset.dot(p_portal->_plane.normal); - - bool old_facing = p_portal->_warning_facing_wrong_way; - p_portal->_warning_facing_wrong_way = dot < 0.0; - - if (p_portal->_warning_facing_wrong_way != old_facing) { - changed = true; - } - - if (p_portal->_warning_facing_wrong_way) { - WARN_PRINT(String(p_portal->get_name()) + " possibly facing the wrong way."); - } - - // handled later - p_portal->_warning_autolink_failed = false; - - if (changed) { - p_portal->update_gizmos(); - } -#endif -} - -bool RoomManager::_autoplace_object(VisualInstance *p_vi) { - // note we could alternatively use the portal_renderer to do this more efficiently - // (as it has a BSP) but at a cost of returning result from the visual server - AABB bb = p_vi->get_transformed_aabb(); - Vector3 centre = bb.get_center(); - - // in order to deal with internal rooms, we can't just stop at the first - // room the point is within, as there could be later rooms with a higher priority - int best_priority = -INT32_MAX; - Room *best_room = nullptr; - - // if not set to zero (no preference) this can override a preference - // for a certain RoomGroup priority to ensure the instance gets placed in the correct - // RoomGroup (e.g. outside, for building exteriors) - int preferred_priority = p_vi->get_portal_autoplace_priority(); - - for (int n = 0; n < _rooms.size(); n++) { - Room *room = _rooms[n]; - - if (room->contains_point(centre)) { - // the standard routine autoplaces in the highest priority room - if (room->_room_priority > best_priority) { - best_priority = room->_room_priority; - best_room = room; - } - - // if we override the preferred priority we always choose this - if (preferred_priority && (room->_room_priority == preferred_priority)) { - best_room = room; - break; - } - } - } - - if (best_room) { - // just dummies, we won't use these this time - Vector room_pts; - - // we can reuse this function - _process_static(best_room, p_vi, room_pts, true); - return true; - } - - return false; -} - -void RoomManager::_autoplace_recursive(Spatial *p_node) { - if (p_node->is_queued_for_deletion()) { - return; - } - - // as soon as we hit a room, quit the recursion as the objects - // will already have been added inside rooms - if (Object::cast_to(p_node)) { - return; - } - - VisualInstance *vi = Object::cast_to(p_node); - - // we are only interested in VIs with static or dynamic mode - if (vi) { - switch (vi->get_portal_mode()) { - default: { - } break; - case CullInstance::PORTAL_MODE_DYNAMIC: - case CullInstance::PORTAL_MODE_STATIC: { - _autoplace_object(vi); - } break; - } - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - - if (child) { - _autoplace_recursive(child); - } - } -} - -void RoomManager::_process_static(Room *p_room, Spatial *p_node, Vector &r_room_pts, bool p_add_to_portal_renderer) { - bool ignore = false; - VisualInstance *vi = Object::cast_to(p_node); - - bool is_dynamic = false; - - // we are only interested in VIs with static or dynamic mode - if (vi) { - switch (vi->get_portal_mode()) { - default: { - ignore = true; - } break; - case CullInstance::PORTAL_MODE_DYNAMIC: { - is_dynamic = true; - } break; - case CullInstance::PORTAL_MODE_STATIC: - break; - } - } - - if (!ignore) { - // We'll have a done flag. This isn't strictly speaking necessary - // because the types should be mutually exclusive, but this would - // break if something changes the inheritance hierarchy of the nodes - // at a later date, so having a done flag makes it more robust. - bool done = false; - - Light *light = Object::cast_to(p_node); - - if (!done && light) { - done = true; - - // lights (don't affect bound, so aren't added in first pass) - if (p_add_to_portal_renderer) { - Vector dummy_pts; - RenderingServer::get_singleton()->room_add_instance(p_room->_room_rid, light->get_instance(), light->get_transformed_aabb(), dummy_pts); - convert_log("\t\t\tLIGT\t" + light->get_name()); - } - } - - GeometryInstance *gi = Object::cast_to(p_node); - - if (!done && gi) { - done = true; - - // MeshInstance is the most interesting type for portalling, so we handle this explicitly - MeshInstance *mi = Object::cast_to(p_node); - if (mi) { - bool added = false; - - Vector object_pts; - AABB aabb; - // get the object points and don't immediately add to the room - // points, as we want to use these points for sprawling algorithm in - // the visual server. - if (_bound_findpoints_mesh_instance(mi, object_pts, aabb)) { - // need to keep track of room bound - // NOTE the is_visible check MAY cause problems if conversion run on nodes that - // aren't properly in the tree. It can optionally be removed. Certainly calling is_visible_in_tree - // DID cause problems. - if (!is_dynamic && mi->get_include_in_bound() && mi->is_visible()) { - r_room_pts.append_array(object_pts); - } - - if (p_add_to_portal_renderer) { - // We are sending the VisualInstance AABB rather than the manually calced AABB, maybe we don't need to calc the AABB. - // If this works okay we can maybe later remove the manual AABB calculation in _bound_findpoints_mesh_instance(). - RenderingServer::get_singleton()->room_add_instance(p_room->_room_rid, mi->get_instance(), mi->get_transformed_aabb().grow(mi->get_extra_cull_margin()), object_pts); - added = true; - } - } // if bound found points - - if (p_add_to_portal_renderer) { - String msg = "\t\t\tMESH\t" + mi->get_name(); - if (!added) { - msg += "\t(unrecognized)"; - } - convert_log(msg); - } - } else { - // geometry instance but not a mesh instance .. - Vector object_pts; - AABB aabb; - - bool added = false; - - // attempt to recognise this GeometryInstance and read back the geometry - // Note: never attempt to add dynamics to the room aabb - if (is_dynamic || _bound_findpoints_geom_instance(gi, object_pts, aabb)) { - // need to keep track of room bound - // NOTE the is_visible check MAY cause problems if conversion run on nodes that - // aren't properly in the tree. It can optionally be removed. Certainly calling is_visible_in_tree - // DID cause problems. - if (!is_dynamic && gi->get_include_in_bound() && gi->is_visible()) { - r_room_pts.append_array(object_pts); - } - - if (p_add_to_portal_renderer) { - // if dynamic, we won't have properly calculated the aabb yet - if (is_dynamic) { - aabb = gi->get_transformed_aabb(); - } - - aabb.grow_by(gi->get_extra_cull_margin()); - RenderingServer::get_singleton()->room_add_instance(p_room->_room_rid, gi->get_instance(), aabb, object_pts); - added = true; - } - } // if bound found points - - if (p_add_to_portal_renderer) { - String msg = "\t\t\tGEOM\t" + gi->get_name(); - if (!added) { - msg += "\t(unrecognized)"; - } - convert_log(msg); - } - } - } // if gi - - VisibilityNotifier *vn = Object::cast_to(p_node); - if (!done && vn && ((vn->get_portal_mode() == CullInstance::PORTAL_MODE_DYNAMIC) || (vn->get_portal_mode() == CullInstance::PORTAL_MODE_STATIC))) { - done = true; - - if (p_add_to_portal_renderer) { - AABB world_aabb = vn->get_global_transform().xform(vn->get_aabb()); - RenderingServer::get_singleton()->room_add_ghost(p_room->_room_rid, vn->get_instance_id(), world_aabb); - convert_log("\t\t\tVIS \t" + vn->get_name()); - } - } - - } // if not ignore -} - -void RoomManager::_find_statics_recursive(Room *p_room, Spatial *p_node, Vector &r_room_pts, bool p_add_to_portal_renderer) { - // don't process portal MeshInstances that are being deleted - // (and replaced by proper Portal nodes) - if (p_node->is_queued_for_deletion()) { - return; - } - - _process_static(p_room, p_node, r_room_pts, p_add_to_portal_renderer); - - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - - if (child) { - _find_statics_recursive(p_room, child, r_room_pts, p_add_to_portal_renderer); - } - } -} - -bool RoomManager::_convert_manual_bound(Room *p_room, Spatial *p_node, const LocalVector &p_portals) { - MeshInstance *mi = Object::cast_to(p_node); - if (!mi) { - return false; - } - - Vector points; - AABB aabb; - if (!_bound_findpoints_mesh_instance(mi, points, aabb)) { - return false; - } - - mi->set_portal_mode(CullInstance::PORTAL_MODE_IGNORE); - - // hide bounds after conversion - // set to portal mode ignore? - mi->hide(); - - return _convert_room_hull_preliminary(p_room, points, p_portals); -} - -bool RoomManager::_convert_room_hull_preliminary(Room *p_room, const Vector &p_room_pts, const LocalVector &p_portals) { - if (p_room_pts.size() <= 3) { - return false; - } - - Geometry::MeshData md; - - Error err = OK; - - // if there are too many room points, quickhull will fail or freeze etc, so we will revert - // to a bounding rect and send an error message - if (p_room_pts.size() > 100000) { - WARN_PRINT(String(p_room->get_name()) + " contains too many vertices to find convex hull, use a manual bound instead."); - - AABB aabb; - aabb.create_from_points(p_room_pts); - - LocalVector pts; - Vector3 mins = aabb.position; - Vector3 maxs = mins + aabb.size; - - pts.push_back(Vector3(mins.x, mins.y, mins.z)); - pts.push_back(Vector3(mins.x, maxs.y, mins.z)); - pts.push_back(Vector3(maxs.x, maxs.y, mins.z)); - pts.push_back(Vector3(maxs.x, mins.y, mins.z)); - pts.push_back(Vector3(mins.x, mins.y, maxs.z)); - pts.push_back(Vector3(mins.x, maxs.y, maxs.z)); - pts.push_back(Vector3(maxs.x, maxs.y, maxs.z)); - pts.push_back(Vector3(maxs.x, mins.y, maxs.z)); - - err = _build_convex_hull(pts, md); - } else { - err = _build_room_convex_hull(p_room, p_room_pts, md); - } - - if (err != OK) { - return false; - } - - // add any existing portals planes first, as these will trump any other existing planes further out - for (int n = 0; n < p_room->_portals.size(); n++) { - int portal_id = p_room->_portals[n]; - Portal *portal = p_portals[portal_id]; - - // don't add portals to the hull that are internal to this room! - if (portal->is_portal_internal(p_room->_room_ID)) { - continue; - } - - Plane plane = portal->_plane; - - // does it need to be reversed? (i.e. is the portal incoming rather than outgoing) - if (portal->_linkedroom_ID[1] == p_room->_room_ID) { - plane = -plane; - } - - _add_plane_if_unique(p_room, p_room->_preliminary_planes, plane); - } - - // add the planes from the geometry or manual bound - for (int n = 0; n < md.faces.size(); n++) { - const Plane &p = md.faces[n].plane; - _add_plane_if_unique(p_room, p_room->_preliminary_planes, p); - } - - // temporary copy of mesh data for the boundary points - // to form a new hull in _convert_room_hull_final - p_room->_bound_mesh_data = md; - - // aabb (should later include portals too, these are added in _convert_room_hull_final) - p_room->_aabb.create_from_points(md.vertices); - - return true; -} - -bool RoomManager::_convert_room_hull_final(Room *p_room, const LocalVector &p_portals) { - Vector vertices_including_portals = p_room->_bound_mesh_data.vertices; - - // add the portals planes first, as these will trump any other existing planes further out - int num_portals_added = 0; - - for (int n = 0; n < p_room->_portals.size(); n++) { - int portal_id = p_room->_portals[n]; - Portal *portal = p_portals[portal_id]; - - // don't add portals to the world bound that are internal to this room! - if (portal->is_portal_internal(p_room->_room_ID)) { - continue; - } - - Plane plane = portal->_plane; - - // does it need to be reversed? (i.e. is the portal incoming rather than outgoing) - if (portal->_linkedroom_ID[1] == p_room->_room_ID) { - plane = -plane; - } - - if (_add_plane_if_unique(p_room, p_room->_planes, plane)) { - num_portals_added++; - } - - // add any new portals to the aabb of the room - for (int p = 0; p < portal->_pts_world.size(); p++) { - const Vector3 &pt = portal->_pts_world[p]; - vertices_including_portals.push_back(pt); - p_room->_aabb.expand_to(pt); - } - } - - // create new convex hull - Geometry::MeshData md; - Error err = _build_room_convex_hull(p_room, vertices_including_portals, md); - - if (err != OK) { - return false; - } - - // add the planes from the new hull - for (int n = 0; n < md.faces.size(); n++) { - const Plane &p = md.faces[n].plane; - _add_plane_if_unique(p_room, p_room->_planes, p); - } - - // recreate the points within the new simplified bound, and then recreate the convex hull - // by running quickhull a second time... (this enables the gizmo to accurately show the simplified hull) - int num_planes_before_simplification = p_room->_planes.size(); - Geometry::MeshData md_simplified; - _build_simplified_bound(p_room, md_simplified, p_room->_planes, num_portals_added); - - if (num_planes_before_simplification != p_room->_planes.size()) { - convert_log("\t\t\tcontained " + itos(num_planes_before_simplification) + " planes before simplification, " + itos(p_room->_planes.size()) + " planes after."); - } - - // make a copy of the mesh data for debugging - // note this could be avoided in release builds? NYI - p_room->_bound_mesh_data = md_simplified; - - // send bound to visual server - RenderingServer::get_singleton()->room_set_bound(p_room->_room_rid, p_room->get_instance_id(), p_room->_planes, p_room->_aabb, md_simplified.vertices); - - return true; -} - -#ifdef TOOLS_ENABLED -bool RoomManager::_room_regenerate_bound(Room *p_room) { - // for a preview, we allow the editor to change the bound - ERR_FAIL_COND_V(!p_room, false); - - if (!p_room->_bound_pts.size()) { - return false; - } - - // can't do yet if not in the tree - if (!p_room->is_inside_tree()) { - return false; - } - - Transform tr = p_room->get_global_transform(); - - Vector pts; - pts.resize(p_room->_bound_pts.size()); - for (int n = 0; n < pts.size(); n++) { - pts.set(n, tr.xform(p_room->_bound_pts[n])); - } - - Geometry::MeshData md; - Error err = _build_room_convex_hull(p_room, pts, md); - - if (err != OK) { - return false; - } - - p_room->_bound_mesh_data = md; - p_room->update_gizmos(); - - return true; -} -#endif - -void RoomManager::_build_simplified_bound(const Room *p_room, Geometry::MeshData &r_md, LocalVector &r_planes, int p_num_portal_planes) { - if (!r_planes.size()) { - return; - } - - Vector pts = Geometry::compute_convex_mesh_points(&r_planes[0], r_planes.size(), 0.001); - Error err = _build_room_convex_hull(p_room, pts, r_md); - - if (err != OK) { - WARN_PRINT("QuickHull failed building simplified bound"); - return; - } - - // if the number of faces is less than the number of planes, we can use this simplified version to reduce the number of planes - if (r_md.faces.size() < r_planes.size()) { - // always include the portal planes - r_planes.resize(p_num_portal_planes); - - for (int n = 0; n < r_md.faces.size(); n++) { - _add_plane_if_unique(p_room, r_planes, r_md.faces[n].plane); - } - } -} - -Error RoomManager::_build_room_convex_hull(const Room *p_room, const Vector &p_points, Geometry::MeshData &r_mesh) { - // calculate an epsilon based on the simplify value, and use this to build the hull - real_t s = 0.0; - - DEV_ASSERT(p_room); - if (p_room->_use_default_simplify) { - s = _room_simplify_info._plane_simplify; - } else { - s = p_room->_simplify_info._plane_simplify; - } - - // value between 0.3 (accurate) and 10.0 (very rough) - // * UNIT_EPSILON - s *= s; - s *= 40.0; - s += 0.3; // minimum - s *= UNIT_EPSILON; - return _build_convex_hull(p_points, r_mesh, s); -} - -bool RoomManager::_add_plane_if_unique(const Room *p_room, LocalVector &r_planes, const Plane &p) { - DEV_ASSERT(p_room); - if (p_room->_use_default_simplify) { - return _room_simplify_info.add_plane_if_unique(r_planes, p); - } - - return p_room->_simplify_info.add_plane_if_unique(r_planes, p); -} - -void RoomManager::_convert_portal(Room *p_room, Spatial *p_node, LocalVector &portals) { - Portal *portal = Object::cast_to(p_node); - - bool importing = false; - - // if not a gportal already, convert the node type - if (!portal) { - importing = true; - portal = _change_node_type(p_node, "G", false); - portal->create_from_mesh_instance(Object::cast_to(p_node)); - - p_node->queue_delete(); - - } else { - // only allow converting once - if (portal->_conversion_tick == _conversion_tick) { - return; - } - } - - // make sure to start with fresh internal data each time (for linked rooms etc) - portal->clear(); - - // mark the portal if we are importing, because we will need to use the naming - // prefix system to look for linked rooms in that case - portal->_importing_portal = importing; - - // mark so as only to convert once - portal->_conversion_tick = _conversion_tick; - - // link rooms - portal->portal_update(); - - // keep a list of portals for second pass - portals.push_back(portal); - - // the portal is linking from this first room it is added to - portal->_linkedroom_ID[0] = p_room->_room_ID; -} - -bool RoomManager::_bound_findpoints_geom_instance(GeometryInstance *p_gi, Vector &r_room_pts, AABB &r_aabb) { - // max opposite extents .. note AABB storing size is rubbish in this aspect - // it can fail once mesh min is larger than FLT_MAX / 2. - r_aabb.position = Vector3(FLT_MAX / 2, FLT_MAX / 2, FLT_MAX / 2); - r_aabb.size = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX); - -#ifdef MODULE_CSG_ENABLED - CSGShape *shape = Object::cast_to(p_gi); - if (shape) { - // Shapes will not be up to date on the first frame due to a quirk - // of CSG - it defers updates to the next frame. So we need to explicitly - // force an update to make sure the CSG is correct on level load. - shape->force_update_shape(); - - Array arr = shape->get_meshes(); - if (!arr.size()) { - return false; - } - - Ref arr_mesh = arr[1]; - if (!arr_mesh.is_valid()) { - return false; - } - - if (arr_mesh->get_surface_count() == 0) { - return false; - } - - // for converting meshes to world space - Transform trans = p_gi->get_global_transform(); - - for (int surf = 0; surf < arr_mesh->get_surface_count(); surf++) { - Array arrays = arr_mesh->surface_get_arrays(surf); - - if (!arrays.size()) { - continue; - } - - PoolVector vertices = arrays[RS::ARRAY_VERTEX]; - - // convert to world space - for (int n = 0; n < vertices.size(); n++) { - Vector3 pt_world = trans.xform(vertices[n]); - r_room_pts.push_back(pt_world); - - // keep the bound up to date - r_aabb.expand_to(pt_world); - } - - } // for through the surfaces - - return true; - } // if csg shape -#endif - - // multimesh - MultiMeshInstance *mmi = Object::cast_to(p_gi); - if (mmi) { - Ref rmm = mmi->get_multimesh(); - if (!rmm.is_valid()) { - return false; - } - - // first get the mesh verts in local space - LocalVector local_verts; - Ref rmesh = rmm->get_mesh(); - - if (rmesh->get_surface_count() == 0) { - String string; - string = "MultiMeshInstance '" + mmi->get_name() + "' has no surfaces, ignoring"; - WARN_PRINT(string); - return false; - } - - for (int surf = 0; surf < rmesh->get_surface_count(); surf++) { - Array arrays = rmesh->surface_get_arrays(surf); - - if (!arrays.size()) { - WARN_PRINT_ONCE("MultiMesh mesh surface with no mesh, ignoring"); - continue; - } - - const PoolVector &vertices = arrays[RS::ARRAY_VERTEX]; - - int count = local_verts.size(); - local_verts.resize(local_verts.size() + vertices.size()); - - for (int n = 0; n < vertices.size(); n++) { - local_verts[count++] = vertices[n]; - } - } - - if (!local_verts.size()) { - return false; - } - - // now we have the local space verts, add a bunch for each instance, and find the AABB - for (int i = 0; i < rmm->get_instance_count(); i++) { - Transform trans = rmm->get_instance_transform(i); - trans = mmi->get_global_transform() * trans; - - for (int n = 0; n < local_verts.size(); n++) { - Vector3 pt_world = trans.xform(local_verts[n]); - r_room_pts.push_back(pt_world); - - // keep the bound up to date - r_aabb.expand_to(pt_world); - } - } - return true; - } - - // Sprite3D - SpriteBase3D *sprite = Object::cast_to(p_gi); - if (sprite) { - Ref tmesh = sprite->generate_triangle_mesh(); - PoolVector vertices = tmesh->get_vertices(); - - // for converting meshes to world space - Transform trans = p_gi->get_global_transform(); - - // convert to world space - for (int n = 0; n < vertices.size(); n++) { - Vector3 pt_world = trans.xform(vertices[n]); - r_room_pts.push_back(pt_world); - - // keep the bound up to date - r_aabb.expand_to(pt_world); - } - - return true; - } - - // Particles have a "visibility aabb" we can use for this - /* - Particles *particles = Object::cast_to(p_gi); - if (particles) { - r_aabb = particles->get_global_transform().xform(particles->get_visibility_aabb()); - return true; - } - */ - - // Fallback path for geometry that is not recognised - // (including CPUParticles, which will need to rely on an expansion margin) - r_aabb = p_gi->get_transformed_aabb(); - return true; -} - -bool RoomManager::_bound_findpoints_mesh_instance(MeshInstance *p_mi, Vector &r_room_pts, AABB &r_aabb) { - // max opposite extents .. note AABB storing size is rubbish in this aspect - // it can fail once mesh min is larger than FLT_MAX / 2. - r_aabb.position = Vector3(FLT_MAX / 2, FLT_MAX / 2, FLT_MAX / 2); - r_aabb.size = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX); - - // some pandemonium jiggery pokery to get the mesh verts in local space - Ref rmesh = p_mi->get_mesh(); - - ERR_FAIL_COND_V(!rmesh.is_valid(), false); - - if (rmesh->get_surface_count() == 0) { - String string; - string = "MeshInstance '" + p_mi->get_name() + "' has no surfaces, ignoring"; - WARN_PRINT(string); - return false; - } - - bool success = false; - - // for converting meshes to world space - Transform trans = p_mi->get_global_transform(); - - for (int surf = 0; surf < rmesh->get_surface_count(); surf++) { - Array arrays = rmesh->surface_get_arrays(surf); - - // possible to have a meshinstance with no geometry .. don't want to crash - if (!arrays.size()) { - WARN_PRINT_ONCE("MeshInstance surface with no mesh, ignoring"); - continue; - } - - success = true; - - PoolVector vertices = arrays[RS::ARRAY_VERTEX]; - - // convert to world space - for (int n = 0; n < vertices.size(); n++) { - Vector3 ptWorld = trans.xform(vertices[n]); - r_room_pts.push_back(ptWorld); - - // keep the bound up to date - r_aabb.expand_to(ptWorld); - } - - } // for through the surfaces - - return success; -} - -void RoomManager::_cleanup_after_conversion() { - for (int n = 0; n < _rooms.size(); n++) { - Room *room = _rooms[n]; - room->_portals.reset(); - room->_preliminary_planes.reset(); - - // outside the editor, there's no need to keep the data for the convex hull - // drawing, as it is only used for gizmos. - if (!Engine::get_singleton()->is_editor_hint()) { - room->_bound_mesh_data = Geometry::MeshData(); - } - } -} - -bool RoomManager::resolve_preview_camera_path() { - Camera *camera = _resolve_path(_settings_path_preview_camera); - - if (camera) { - _pandemonium_preview_camera_ID = camera->get_instance_id(); - return true; - } - _pandemonium_preview_camera_ID = -1; - return false; -} - -template -NODE_TYPE *RoomManager::_resolve_path(NodePath p_path) const { - if (has_node(p_path)) { - NODE_TYPE *node = Object::cast_to(get_node(p_path)); - if (node) { - return node; - } else { - WARN_PRINT("node is incorrect type"); - } - } - - return nullptr; -} - -template -bool RoomManager::_node_is_type(Node *p_node) const { - NODE_TYPE *node = Object::cast_to(p_node); - return node != nullptr; -} - -template -T *RoomManager::_change_node_type(Spatial *p_node, String p_prefix, bool p_delete) { - String string_full_name = p_node->get_name(); - - Node *parent = p_node->get_parent(); - if (!parent) { - return nullptr; - } - - // owner should normally be root - Node *owner = p_node->get_owner(); - - // change the name of the node to be deleted - p_node->set_name(p_prefix + string_full_name); - - // create the new class T object - T *pNew = memnew(T); - pNew->set_name(string_full_name); - - // add the child at the same position as the old node - // (this is more convenient for users) - parent->add_child_below_node(p_node, pNew); - - // new lroom should have same transform - pNew->set_transform(p_node->get_transform()); - - // move each child - while (p_node->get_child_count()) { - Node *child = p_node->get_child(0); - p_node->remove_child(child); - - // needs to set owner to appear in IDE - pNew->add_child(child); - } - - // needs to set owner to appear in IDE - _set_owner_recursive(pNew, owner); - - // delete old node - if (p_delete) { - p_node->queue_delete(); - } - - return pNew; -} - -void RoomManager::_update_gizmos_recursive(Node *p_node) { - Portal *portal = Object::cast_to(p_node); - - if (portal) { - portal->update_gizmos(); - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - _update_gizmos_recursive(p_node->get_child(n)); - } -} - -Error RoomManager::_build_convex_hull(const Vector &p_points, Geometry::MeshData &r_mesh, real_t p_epsilon) { -#ifdef PANDEMONIUM_PORTALS_USE_BULLET_CONVEX_HULL - return ConvexHullComputer::convex_hull(p_points, r_mesh); -#if 0 - // test comparison of methods - QuickHull::build(p_points, r_mesh, p_epsilon); - - int qh_faces = r_mesh.faces.size(); - int qh_verts = r_mesh.vertices.size(); - - r_mesh.vertices.clear(); - r_mesh.faces.clear(); - r_mesh.edges.clear(); - Error err = ConvexHullComputer::convex_hull(p_points, r_mesh); - - int bh_faces = r_mesh.faces.size(); - int bh_verts = r_mesh.vertices.size(); - - if (qh_faces != bh_faces) { - print_line("qh_faces : " + itos(qh_faces) + ", bh_faces : " + itos(bh_faces)); - } - if (qh_verts != bh_verts) { - print_line("qh_verts : " + itos(qh_verts) + ", bh_verts : " + itos(bh_verts)); - } - - return err; -#endif - -#else - QuickHull::_flag_warnings = false; - Error err = QuickHull::build(p_points, r_mesh, p_epsilon); - QuickHull::_flag_warnings = true; - return err; -#endif -} - -void RoomManager::_flip_portals_recursive(Spatial *p_node) { - Portal *portal = Object::cast_to(p_node); - - if (portal) { - portal->flip(); - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - if (child) { - _flip_portals_recursive(child); - } - } -} - -void RoomManager::_set_owner_recursive(Node *p_node, Node *p_owner) { - if (!p_node->get_owner() && (p_node != p_owner)) { - p_node->set_owner(p_owner); - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - _set_owner_recursive(p_node->get_child(n), p_owner); - } -} - -bool RoomManager::_name_ends_with(const Node *p_node, String p_postfix) const { - ERR_FAIL_NULL_V(p_node, false); - String name = p_node->get_name(); - - int pf_l = p_postfix.length(); - int l = name.length(); - - if (pf_l > l) { - return false; - } - - // allow capitalization errors - if (name.substr(l - pf_l, pf_l).to_lower() == p_postfix) { - return true; - } - - return false; -} - -String RoomManager::_find_name_before(Node *p_node, String p_postfix, bool p_allow_no_postfix) { - ERR_FAIL_NULL_V(p_node, String()); - String name = p_node->get_name(); - - int pf_l = p_postfix.length(); - int l = name.length(); - - if (pf_l > l) { - if (!p_allow_no_postfix) { - return String(); - } - } else { - if (name.substr(l - pf_l, pf_l) == p_postfix) { - name = name.substr(0, l - pf_l); - } else { - if (!p_allow_no_postfix) { - return String(); - } - } - } - - // because pandemonium doesn't support multiple nodes with the same name, we will strip e.g. a number - // after an * on the end of the name... - // e.g. kitchen*2-portal - for (int c = 0; c < name.length(); c++) { - if (name[c] == PANDEMONIUM_PORTAL_WILDCARD) { - // remove everything after and including this character - name = name.substr(0, c); - break; - } - } - - return name; -} - -void RoomManager::_merge_meshes_in_room(Room *p_room) { - // only do in running game so as not to lose data - if (Engine::get_singleton()->is_editor_hint()) { - return; - } - - _merge_log("merging room " + p_room->get_name()); - - // list of meshes suitable - LocalVector source_meshes; - _list_mergeable_mesh_instances(p_room, source_meshes); - - // none suitable - if (!source_meshes.size()) { - return; - } - - _merge_log("\t" + itos(source_meshes.size()) + " source meshes"); - - BitFieldDynamic bf; - bf.create(source_meshes.size(), true); - - for (int n = 0; n < source_meshes.size(); n++) { - LocalVector merge_list; - - // find similar meshes - MeshInstance *a = source_meshes[n]; - merge_list.push_back(a); - - // may not be necessary - bf.set_bit(n, true); - - for (int c = n + 1; c < source_meshes.size(); c++) { - // if not merged already - if (!bf.get_bit(c)) { - MeshInstance *b = source_meshes[c]; - - if (a->is_mergeable_with(b)) { - merge_list.push_back(b); - bf.set_bit(c, true); - } - } // if not merged already - } // for c through secondary mesh - - // only merge if more than 1 - if (merge_list.size() > 1) { - // we can merge! - // create a new holder mesh - - MeshInstance *merged = memnew(MeshInstance); - merged->set_name("MergedMesh"); - - _merge_log("\t\t" + merged->get_name()); - - // merge function takes a vector of variants - Vector variant_merge_list; - variant_merge_list.resize(merge_list.size()); - for (int i = 0; i < merge_list.size(); i++) { - variant_merge_list.set(i, merge_list[i]); - } - - if (merged->merge_meshes(variant_merge_list, true, false)) { - // set all the source meshes to portal mode ignore so not shown - for (int i = 0; i < merge_list.size(); i++) { - merge_list[i]->set_portal_mode(CullInstance::PORTAL_MODE_IGNORE); - } - - // and set the new merged mesh to static - merged->set_portal_mode(CullInstance::PORTAL_MODE_STATIC); - - // attach to scene tree - p_room->add_child(merged); - merged->set_owner(p_room->get_owner()); - - // compensate for room transform, as the verts are now in world space - Transform tr = p_room->get_global_transform(); - tr.affine_invert(); - merged->set_transform(tr); - - // delete originals? - // note this isn't perfect, it may still end up with dangling spatials, but they can be - // deleted later. - for (int i = 0; i < merge_list.size(); i++) { - MeshInstance *mi = merge_list[i]; - if (!mi->get_child_count()) { - mi->queue_delete(); - } else { - Node *parent = mi->get_parent(); - if (parent) { - // if there are children, we don't want to delete it, but we do want to - // remove the mesh drawing, e.g. by replacing it with a spatial - String name = mi->get_name(); - mi->set_name("DeleteMe"); // can be anything, just to avoid name conflict with replacement node - Spatial *replacement = memnew(Spatial); - replacement->set_name(name); - - parent->add_child(replacement); - - // make the transform and owner match - replacement->set_owner(mi->get_owner()); - replacement->set_transform(mi->get_transform()); - - // move all children from the mesh instance to the replacement - while (mi->get_child_count()) { - Node *child = mi->get_child(0); - mi->remove_child(child); - replacement->add_child(child); - } - - } // if the mesh instance has a parent (should hopefully be always the case?) - } - } - - } else { - // no success - memdelete(merged); - } - } - - } // for n through primary mesh - - if (_settings_remove_danglers) { - _remove_redundant_dangling_nodes(p_room); - } -} - -bool RoomManager::_remove_redundant_dangling_nodes(Spatial *p_node) { - int non_queue_delete_children = 0; - - // do the children first - for (int n = 0; n < p_node->get_child_count(); n++) { - Node *node_child = p_node->get_child(n); - - Spatial *child = Object::cast_to(node_child); - if (child) { - _remove_redundant_dangling_nodes(child); - } - - if (node_child && !node_child->is_queued_for_deletion()) { - non_queue_delete_children++; - } - } - - if (!non_queue_delete_children) { - // only remove true spatials, not derived classes - if (p_node->get_class_name() == "Spatial") { - p_node->queue_delete(); - return true; - } - } - - return false; -} - -void RoomManager::_list_mergeable_mesh_instances(Spatial *p_node, LocalVector &r_list) { - MeshInstance *mi = Object::cast_to(p_node); - - if (mi) { - // only interested in static portal mode meshes - VisualInstance *vi = Object::cast_to(mi); - - // we are only interested in VIs with static or dynamic mode - if (vi && vi->get_portal_mode() == CullInstance::PORTAL_MODE_STATIC) { - // disallow for portals or bounds - // mesh instance portals should be queued for deletion by this point, we don't want to merge portals! - if (!_node_is_type(mi) && !_name_ends_with(mi, "-bound") && !mi->is_queued_for_deletion()) { - // only merge if visible - if (mi->is_inside_tree() && mi->is_visible()) { - r_list.push_back(mi); - } - } - } - } - - for (int n = 0; n < p_node->get_child_count(); n++) { - Spatial *child = Object::cast_to(p_node->get_child(n)); - if (child) { - _list_mergeable_mesh_instances(child, r_list); - } - } -} diff --git a/scene/3d/room_manager.h b/scene/3d/room_manager.h deleted file mode 100644 index 8c69d44..0000000 --- a/scene/3d/room_manager.h +++ /dev/null @@ -1,279 +0,0 @@ -#ifndef ROOM_MANAGER_H -#define ROOM_MANAGER_H -/*************************************************************************/ -/* room_manager.h */ -/*************************************************************************/ -/* This file is part of: */ -/* PANDEMONIUM ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/containers/local_vector.h" -#include "room.h" -#include "scene/main/spatial.h" - -class Portal; -class RoomGroup; -class MeshInstance; -class GeometryInstance; -class VisualInstance; - -#define PANDEMONIUM_PORTAL_WILDCARD ('*') - -class RoomManager : public Spatial { - GDCLASS(RoomManager, Spatial); - -public: - enum PVSMode { - PVS_MODE_DISABLED, - PVS_MODE_PARTIAL, - PVS_MODE_FULL, - }; - - void set_roomlist_path(const NodePath &p_path); - NodePath get_roomlist_path() const { - return _settings_path_roomlist; - } - - void set_preview_camera_path(const NodePath &p_path); - - NodePath get_preview_camera_path() const { - return _settings_path_preview_camera; - } - - void rooms_set_active(bool p_active); - bool rooms_get_active() const; - - void set_show_margins(bool p_show); - bool get_show_margins() const; - - void set_debug_sprawl(bool p_enable); - bool get_debug_sprawl() const; - - void set_merge_meshes(bool p_enable); - bool get_merge_meshes() const; - - void set_room_simplify(real_t p_value); - real_t get_room_simplify() const; - - void set_default_portal_margin(real_t p_dist); - real_t get_default_portal_margin() const; - - void set_overlap_warning_threshold(int p_value) { _overlap_warning_threshold = p_value; } - int get_overlap_warning_threshold() const { return (int)_overlap_warning_threshold; } - - void set_portal_depth_limit(int p_limit); - int get_portal_depth_limit() const { return _settings_portal_depth_limit; } - - void set_roaming_expansion_margin(real_t p_dist); - real_t get_roaming_expansion_margin() const { return _settings_roaming_expansion_margin; } - - void set_pvs_mode(PVSMode p_mode); - PVSMode get_pvs_mode() const; - - void set_pvs_filename(String p_filename); - String get_pvs_filename() const; - - void set_use_secondary_pvs(bool p_enable) { _settings_use_secondary_pvs = p_enable; } - bool get_use_secondary_pvs() const { return _settings_use_secondary_pvs; } - - void set_gameplay_monitor_enabled(bool p_enable) { _settings_gameplay_monitor_enabled = p_enable; } - bool get_gameplay_monitor_enabled() const { return _settings_gameplay_monitor_enabled; } - - void rooms_convert(); - void rooms_clear(); - void rooms_flip_portals(); - - String get_configuration_warning() const; - - // for internal use in the editor.. - // either we can clear the rooms and unload, - // or reconvert. - void _rooms_changed(String p_reason); - -#ifdef TOOLS_ENABLED - // for a preview, we allow the editor to change the bound - bool _room_regenerate_bound(Room *p_room); -#endif - - RoomManager(); - ~RoomManager(); - - // an easy way of grabbing the active room manager for tools purposes -#ifdef TOOLS_ENABLED - static RoomManager *active_room_manager; - - // static versions of functions for use from editor toolbars - static void static_rooms_set_active(bool p_active); - static bool static_rooms_get_active(); - static bool static_rooms_get_active_and_loaded(); - static void static_rooms_convert(); -#endif - -private: - // funcs - bool resolve_preview_camera_path(); - void _preview_camera_update(); - - // conversion - // FIRST PASS - void _convert_rooms_recursive(Spatial *p_node, LocalVector &r_portals, LocalVector &r_roomgroups, int p_roomgroup = -1); - void _convert_room(Spatial *p_node, LocalVector &r_portals, const LocalVector &p_roomgroups, int p_roomgroup); - int _convert_roomgroup(Spatial *p_node, LocalVector &r_roomgroups); - - void _find_portals_recursive(Spatial *p_node, Room *p_room, LocalVector &r_portals); - void _convert_portal(Room *p_room, Spatial *p_node, LocalVector &portals); - - // SECOND PASS - void _second_pass_portals(Spatial *p_roomlist, LocalVector &r_portals); - void _second_pass_rooms(const LocalVector &p_roomgroups, const LocalVector &p_portals); - void _second_pass_room(Room *p_room, const LocalVector &p_roomgroups, const LocalVector &p_portals); - - bool _convert_manual_bound(Room *p_room, Spatial *p_node, const LocalVector &p_portals); - void _check_portal_for_warnings(Portal *p_portal, const AABB &p_room_aabb_without_portals); - void _process_static(Room *p_room, Spatial *p_node, Vector &r_room_pts, bool p_add_to_portal_renderer); - void _find_statics_recursive(Room *p_room, Spatial *p_node, Vector &r_room_pts, bool p_add_to_portal_renderer); - bool _convert_room_hull_preliminary(Room *p_room, const Vector &p_room_pts, const LocalVector &p_portals); - - bool _bound_findpoints_mesh_instance(MeshInstance *p_mi, Vector &r_room_pts, AABB &r_aabb); - bool _bound_findpoints_geom_instance(GeometryInstance *p_gi, Vector &r_room_pts, AABB &r_aabb); - - // THIRD PASS - void _autolink_portals(Spatial *p_roomlist, LocalVector &r_portals); - void _third_pass_rooms(const LocalVector &p_portals); - - bool _convert_room_hull_final(Room *p_room, const LocalVector &p_portals); - void _build_simplified_bound(const Room *p_room, Geometry::MeshData &r_md, LocalVector &r_planes, int p_num_portal_planes); - - // AUTOPLACE - automatically place STATIC and DYNAMICs that are not within a room - // into the most appropriate room, and sprawl - void _autoplace_recursive(Spatial *p_node); - bool _autoplace_object(VisualInstance *p_vi); - - // misc - bool _add_plane_if_unique(const Room *p_room, LocalVector &r_planes, const Plane &p); - void _update_portal_gizmos(Spatial *p_node); - bool _check_roomlist_validity(Node *p_node); - void _cleanup_after_conversion(); - Error _build_room_convex_hull(const Room *p_room, const Vector &p_points, Geometry::MeshData &r_mesh); -#ifdef TOOLS_ENABLED - void _generate_room_overlap_zones(); -#endif - - // merging - void _merge_meshes_in_room(Room *p_room); - void _list_mergeable_mesh_instances(Spatial *p_node, LocalVector &r_list); - void _merge_log(String p_string) { debug_print_line(p_string); } - bool _remove_redundant_dangling_nodes(Spatial *p_node); - - // helper funcs - bool _name_ends_with(const Node *p_node, String p_postfix) const; - template - NODE_TYPE *_resolve_path(NodePath p_path) const; - template - bool _node_is_type(Node *p_node) const; - template - T *_change_node_type(Spatial *p_node, String p_prefix, bool p_delete = true); - void _update_gizmos_recursive(Node *p_node); - void _set_owner_recursive(Node *p_node, Node *p_owner); - void _flip_portals_recursive(Spatial *p_node); - Error _build_convex_hull(const Vector &p_points, Geometry::MeshData &r_mesh, real_t p_epsilon = 3.0 * UNIT_EPSILON); - - // output strings during conversion process - void convert_log(String p_string, int p_priority = 0) { debug_print_line(p_string, 1); } - - // only prints when user has set 'debug' in the room manager inspector - // also does not show in non editor builds - void debug_print_line(String p_string, int p_priority = 0); - void show_warning(const String &p_string, bool p_skippable = false, bool p_alert = true); - -public: - static String _find_name_before(Node *p_node, String p_postfix, bool p_allow_no_postfix = false); - static real_t _get_default_portal_margin() { return _default_portal_margin; } - -private: - // accessible from UI - NodePath _settings_path_roomlist; - NodePath _settings_path_preview_camera; - - // resolved node - Spatial *_roomlist = nullptr; - bool _warning_misnamed_nodes_detected = false; - bool _warning_portal_link_room_not_found = false; - bool _warning_portal_autolink_failed = false; - bool _warning_room_overlap_detected = false; - - // merge suitable meshes in rooms? - bool _settings_merge_meshes = false; - - // remove redundant childless spatials after merging - bool _settings_remove_danglers = true; - - bool _active = true; - - // portals, room hulls etc - bool _show_debug = true; - bool _debug_sprawl = false; - - // pvs - PVSMode _pvs_mode = PVS_MODE_PARTIAL; - String _pvs_filename; - bool _settings_use_secondary_pvs = false; - bool _settings_use_simple_pvs = false; - bool _settings_log_pvs_generation = false; - - bool _settings_use_signals = true; - bool _settings_gameplay_monitor_enabled = false; - - int _conversion_tick = 0; - - // just used during conversion, could be invalidated - // later by user deleting rooms etc. - LocalVector _rooms; - - // advanced params - static real_t _default_portal_margin; - real_t _overlap_warning_threshold = 1.0; - Room::SimplifyInfo _room_simplify_info; - int _settings_portal_depth_limit = 16; - real_t _settings_roaming_expansion_margin = 1.0; - - // debug override camera - ObjectID _pandemonium_preview_camera_ID = -1; - // local version of the pandemonium camera frustum, - // to prevent updating the visual server (and causing - // a screen refresh) where not necessary. - Vector3 _pandemonium_camera_pos; - Vector _pandemonium_camera_planes; - -protected: - static void _bind_methods(); - void _notification(int p_what); - void _refresh_from_project_settings(); -}; - -VARIANT_ENUM_CAST(RoomManager::PVSMode); - -#endif diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index eb2d761..f868b85 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -190,14 +190,10 @@ #include "scene/3d/multimesh_instance.h" #include "scene/3d/occluder.h" #include "scene/3d/path.h" -#include "scene/3d/portal.h" #include "scene/3d/position_3d.h" #include "scene/3d/proximity_group.h" #include "scene/3d/reflection_probe.h" #include "scene/3d/remote_transform.h" -#include "scene/3d/room.h" -#include "scene/3d/room_group.h" -#include "scene/3d/room_manager.h" #include "scene/3d/spatial_velocity_tracker.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/visibility_notifier.h" @@ -429,11 +425,7 @@ void register_scene_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); ClassDB::register_class(); ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor