/*************************************************************************/ /* portal_pvs_builder.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2021 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_pvs_builder.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "core/print_string.h" #include "portal_renderer.h" bool PVSBuilder::_log_active = false; void PVSBuilder::find_neighbors(LocalVector &r_neighbors) { // first find the neighbors int num_rooms = _portal_renderer->get_num_rooms(); for (int n = 0; n < num_rooms; n++) { const VSRoom &room = _portal_renderer->get_room(n); // go through each portal int num_portals = room._portal_ids.size(); for (int p = 0; p < num_portals; p++) { int portal_id = room._portal_ids[p]; const VSPortal &portal = _portal_renderer->get_portal(portal_id); // everything depends on whether the portal is incoming or outgoing. // if incoming we reverse the logic. int outgoing = 1; int room_a_id = portal._linkedroom_ID[0]; if (room_a_id != n) { outgoing = 0; DEV_ASSERT(portal._linkedroom_ID[1] == n); } // trace through this portal to the next room int linked_room_id = portal._linkedroom_ID[outgoing]; // not relevant, portal doesn't go anywhere if (linked_room_id == -1) continue; r_neighbors[n].room_ids.push_back(linked_room_id); } // for p through portals } // for n through rooms // the secondary PVS is the primary PVS plus the neighbors } void PVSBuilder::create_secondary_pvs(int p_room_id, const LocalVector &p_neighbors, BitFieldDynamic &r_bitfield_rooms) { VSRoom &room = _portal_renderer->get_room(p_room_id); room._secondary_pvs_first = _pvs->get_secondary_pvs_size(); // go through each primary PVS room, and add the neighbors in the secondary pvs for (int r = 0; r < room._pvs_size; r++) { int pvs_entry = room._pvs_first + r; int pvs_room_id = _pvs->get_pvs_room_id(pvs_entry); // add the visible rooms first _pvs->add_to_secondary_pvs(pvs_room_id); room._secondary_pvs_size += 1; // now any neighbors of this that are not already added const Neighbours &neigh = p_neighbors[pvs_room_id]; for (int n = 0; n < neigh.room_ids.size(); n++) { int neigh_room_id = neigh.room_ids[n]; //log("\tconsidering neigh " + itos(neigh_room_id)); if (r_bitfield_rooms.check_and_set(neigh_room_id)) { // add to the secondary pvs for this room _pvs->add_to_secondary_pvs(neigh_room_id); room._secondary_pvs_size += 1; } // neighbor room has not been added yet } // go through the neighbors } // go through each room in the primary pvs } #ifdef GODOT_PVS_SUPPORT_SAVE_FILE bool PVSBuilder::load_pvs(String p_filename) { if (p_filename == "") { return false; } Error err; FileAccess *file = FileAccess::open(p_filename, FileAccess::READ, &err); if (err || !file) { if (file) { memdelete(file); } return false; } // goto needs vars declaring ahead of time int32_t num_rooms; int32_t pvs_size; if (!((file->get_8() == 'p') && (file->get_8() == 'v') && (file->get_8() == 's') && (file->get_8() == ' '))) { goto failed; } num_rooms = file->get_32(); if (num_rooms != _portal_renderer->get_num_rooms()) { goto failed; } for (int n = 0; n < num_rooms; n++) { if (file->eof_reached()) goto failed; VSRoom &room = _portal_renderer->get_room(n); room._pvs_first = file->get_32(); room._pvs_size = file->get_32(); room._secondary_pvs_first = file->get_32(); room._secondary_pvs_size = file->get_32(); } pvs_size = file->get_32(); for (int n = 0; n < pvs_size; n++) { _pvs->add_to_pvs(file->get_16()); } // secondary pvs pvs_size = file->get_32(); for (int n = 0; n < pvs_size; n++) { _pvs->add_to_secondary_pvs(file->get_16()); } if (file) { memdelete(file); } return true; failed: if (file) { memdelete(file); } return false; } void PVSBuilder::save_pvs(String p_filename) { if (p_filename == "") { p_filename = "res://test.pvs"; } Error err; FileAccess *file = FileAccess::open(p_filename, FileAccess::WRITE, &err); if (err || !file) { if (file) { memdelete(file); } return; } file->store_8('p'); file->store_8('v'); file->store_8('s'); file->store_8(' '); // hash? NYI // first save the room indices into the pvs int num_rooms = _portal_renderer->get_num_rooms(); file->store_32(num_rooms); for (int n = 0; n < num_rooms; n++) { VSRoom &room = _portal_renderer->get_room(n); file->store_32(room._pvs_first); file->store_32(room._pvs_size); file->store_32(room._secondary_pvs_first); file->store_32(room._secondary_pvs_size); } int32_t pvs_size = _pvs->get_pvs_size(); file->store_32(pvs_size); for (int n = 0; n < pvs_size; n++) { int16_t room_id = _pvs->get_pvs_room_id(n); file->store_16(room_id); } pvs_size = _pvs->get_secondary_pvs_size(); file->store_32(pvs_size); for (int n = 0; n < pvs_size; n++) { int16_t room_id = _pvs->get_secondary_pvs_room_id(n); file->store_16(room_id); } if (file) { memdelete(file); } } #endif void PVSBuilder::calculate_pvs(PortalRenderer &p_portal_renderer, String p_filename) { _portal_renderer = &p_portal_renderer; _pvs = &p_portal_renderer.get_pvs(); // attempt to load from file rather than create each time #ifdef GODOT_PVS_SUPPORT_SAVE_FILE if (load_pvs(p_filename)) { print_line("loaded pvs successfully from file " + p_filename); _pvs->set_loaded(true); return; } #endif uint32_t time_before = OS::get_singleton()->get_ticks_msec(); int num_rooms = _portal_renderer->get_num_rooms(); BitFieldDynamic bf; bf.create(num_rooms); LocalVector neighbors; neighbors.resize(num_rooms); // find the immediate neighbors of each room - // this is needed to create the secondary pvs find_neighbors(neighbors); for (int n = 0; n < num_rooms; n++) { bf.blank(); //_visible_rooms.clear(); LocalVector dummy_planes; VSRoom &room = _portal_renderer->get_room(n); room._pvs_first = _pvs->get_pvs_size(); log("pvs from room : " + itos(n)); trace_rooms_recursive(0, n, n, -1, false, -1, dummy_planes, bf); create_secondary_pvs(n, neighbors, bf); if (_log_active) { String string = ""; for (int i = 0; i < room._pvs_size; i++) { int visible_room = _pvs->get_pvs_room_id(room._pvs_first + i); string += itos(visible_room); string += ", "; } log("\t" + string); string = "secondary : "; for (int i = 0; i < room._secondary_pvs_size; i++) { int visible_room = _pvs->get_secondary_pvs_room_id(room._secondary_pvs_first + i); string += itos(visible_room); string += ", "; } log("\t" + string); } } _pvs->set_loaded(true); uint32_t time_after = OS::get_singleton()->get_ticks_msec(); print_verbose("calculated PVS in " + itos(time_after - time_before) + " ms."); #ifdef GODOT_PVS_SUPPORT_SAVE_FILE save_pvs(p_filename); #endif } void PVSBuilder::logd(int p_depth, String p_string) { return; String string_long; for (int n = 0; n < p_depth; n++) { string_long += "\t"; } string_long += p_string; log(string_long); } void PVSBuilder::log(String p_string) { if (_log_active) { print_line(p_string); } } void PVSBuilder::trace_rooms_recursive(int p_depth, int p_source_room_id, int p_room_id, int p_first_portal_id, bool p_first_portal_outgoing, int p_previous_portal_id, const LocalVector &p_planes, BitFieldDynamic &r_bitfield_rooms) { // has this room been done already? if (!r_bitfield_rooms.check_and_set(p_room_id)) { return; } logd(p_depth, "trace_rooms_recursive room " + itos(p_room_id)); // get the room const VSRoom &room = _portal_renderer->get_room(p_room_id); // add to the room PVS of the source room VSRoom &source_room = _portal_renderer->get_room(p_source_room_id); _pvs->add_to_pvs(p_room_id); source_room._pvs_size += 1; // go through each portal int num_portals = room._portal_ids.size(); for (int p = 0; p < num_portals; p++) { int portal_id = room._portal_ids[p]; const VSPortal &portal = _portal_renderer->get_portal(portal_id); // everything depends on whether the portal is incoming or outgoing. // if incoming we reverse the logic. int outgoing = 1; int room_a_id = portal._linkedroom_ID[0]; if (room_a_id != p_room_id) { outgoing = 0; DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id); } // trace through this portal to the next room int linked_room_id = portal._linkedroom_ID[outgoing]; logd(p_depth + 1, "portal to room " + itos(linked_room_id)); // not relevant, portal doesn't go anywhere if (linked_room_id == -1) continue; // linked room done already? if (r_bitfield_rooms.get_bit(linked_room_id)) continue; // is it culled by the planes? VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE; // while clipping to the planes we maintain a list of partial planes, so we can add them to the // recursive next iteration of planes to check static LocalVector partial_planes; partial_planes.clear(); for (int32_t l = 0; l < p_planes.size(); l++) { VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]); switch (res) { case VSPortal::ClipResult::CLIP_OUTSIDE: { overall_res = res; } break; case VSPortal::ClipResult::CLIP_PARTIAL: { // if the portal intersects one of the planes, we should take this plane into account // in the next call of this recursive trace, because it can be used to cull out more objects overall_res = res; partial_planes.push_back(l); } break; default: // suppress warning break; } // if the portal was totally outside the 'frustum' then we can ignore it if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) break; } // this portal is culled if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) { logd(p_depth + 2, "portal CLIP_OUTSIDE"); continue; } // construct new planes LocalVector planes; if (p_first_portal_id != -1) { // add new planes const VSPortal &first_portal = _portal_renderer->get_portal(p_first_portal_id); portal.add_pvs_planes(first_portal, p_first_portal_outgoing, planes, outgoing != 0); //#define GODOT_PVS_EXTRA_REJECT_TEST #ifdef GODOT_PVS_EXTRA_REJECT_TEST // extra reject test for pvs - was the previous portal points outside the planes formed by the new portal? // not fully tested and not yet found a situation where needed, but will leave in in case testers find // such a situation. if (p_previous_portal_id != -1) { const VSPortal &prev_portal = _portal_renderer->get_portal(p_previous_portal_id); if (prev_portal._pvs_is_outside_planes(planes)) { continue; } } #endif } // if portal is totally inside the planes, don't copy the old planes .. // i.e. we can now cull using the portal and forget about the rest of the frustum (yay) if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) { // if it WASNT totally inside the existing frustum, we also need to add any existing planes // that cut the portal. for (uint32_t n = 0; n < partial_planes.size(); n++) planes.push_back(p_planes[partial_planes[n]]); } // hopefully the portal actually leads somewhere... if (linked_room_id != -1) { // we either pass on the first portal id, or we start // it here, because we are looking through the first portal int first_portal_id = p_first_portal_id; if (first_portal_id == -1) { first_portal_id = portal_id; p_first_portal_outgoing = outgoing != 0; } trace_rooms_recursive(p_depth + 1, p_source_room_id, linked_room_id, first_portal_id, p_first_portal_outgoing, portal_id, planes, r_bitfield_rooms); } // linked room is valid } }