portal_pvs_builder.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /**************************************************************************/
  2. /* portal_pvs_builder.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "portal_pvs_builder.h"
  31. #include "core/os/file_access.h"
  32. #include "core/os/os.h"
  33. #include "core/print_string.h"
  34. #include "portal_renderer.h"
  35. bool PVSBuilder::_log_active = true;
  36. void PVSBuilder::find_neighbors(LocalVector<Neighbours> &r_neighbors) {
  37. // first find the neighbors
  38. int num_rooms = _portal_renderer->get_num_rooms();
  39. for (int n = 0; n < num_rooms; n++) {
  40. const VSRoom &room = _portal_renderer->get_room(n);
  41. // go through each portal
  42. int num_portals = room._portal_ids.size();
  43. for (int p = 0; p < num_portals; p++) {
  44. int portal_id = room._portal_ids[p];
  45. const VSPortal &portal = _portal_renderer->get_portal(portal_id);
  46. // everything depends on whether the portal is incoming or outgoing.
  47. // if incoming we reverse the logic.
  48. int outgoing = 1;
  49. int room_a_id = portal._linkedroom_ID[0];
  50. if (room_a_id != n) {
  51. outgoing = 0;
  52. DEV_ASSERT(portal._linkedroom_ID[1] == n);
  53. }
  54. // trace through this portal to the next room
  55. int linked_room_id = portal._linkedroom_ID[outgoing];
  56. // not relevant, portal doesn't go anywhere
  57. if (linked_room_id == -1)
  58. continue;
  59. r_neighbors[n].room_ids.push_back(linked_room_id);
  60. } // for p through portals
  61. } // for n through rooms
  62. // the secondary PVS is the primary PVS plus the neighbors
  63. }
  64. void PVSBuilder::create_secondary_pvs(int p_room_id, const LocalVector<Neighbours> &p_neighbors, BitFieldDynamic &r_bitfield_rooms) {
  65. VSRoom &room = _portal_renderer->get_room(p_room_id);
  66. room._secondary_pvs_first = _pvs->get_secondary_pvs_size();
  67. // go through each primary PVS room, and add the neighbors in the secondary pvs
  68. for (int r = 0; r < room._pvs_size; r++) {
  69. int pvs_entry = room._pvs_first + r;
  70. int pvs_room_id = _pvs->get_pvs_room_id(pvs_entry);
  71. // add the visible rooms first
  72. _pvs->add_to_secondary_pvs(pvs_room_id);
  73. room._secondary_pvs_size += 1;
  74. // now any neighbors of this that are not already added
  75. const Neighbours &neigh = p_neighbors[pvs_room_id];
  76. for (int n = 0; n < neigh.room_ids.size(); n++) {
  77. int neigh_room_id = neigh.room_ids[n];
  78. //log("\tconsidering neigh " + itos(neigh_room_id));
  79. if (r_bitfield_rooms.check_and_set(neigh_room_id)) {
  80. // add to the secondary pvs for this room
  81. _pvs->add_to_secondary_pvs(neigh_room_id);
  82. room._secondary_pvs_size += 1;
  83. } // neighbor room has not been added yet
  84. } // go through the neighbors
  85. } // go through each room in the primary pvs
  86. }
  87. #ifdef GODOT_PVS_SUPPORT_SAVE_FILE
  88. bool PVSBuilder::load_pvs(String p_filename) {
  89. if (p_filename == "") {
  90. return false;
  91. }
  92. Error err;
  93. FileAccess *file = FileAccess::open(p_filename, FileAccess::READ, &err);
  94. if (err || !file) {
  95. if (file) {
  96. memdelete(file);
  97. }
  98. return false;
  99. }
  100. // goto needs vars declaring ahead of time
  101. int32_t num_rooms;
  102. int32_t pvs_size;
  103. if (!((file->get_8() == 'p') &&
  104. (file->get_8() == 'v') &&
  105. (file->get_8() == 's') &&
  106. (file->get_8() == ' '))) {
  107. goto failed;
  108. }
  109. num_rooms = file->get_32();
  110. if (num_rooms != _portal_renderer->get_num_rooms()) {
  111. goto failed;
  112. }
  113. for (int n = 0; n < num_rooms; n++) {
  114. if (file->eof_reached())
  115. goto failed;
  116. VSRoom &room = _portal_renderer->get_room(n);
  117. room._pvs_first = file->get_32();
  118. room._pvs_size = file->get_32();
  119. room._secondary_pvs_first = file->get_32();
  120. room._secondary_pvs_size = file->get_32();
  121. }
  122. pvs_size = file->get_32();
  123. for (int n = 0; n < pvs_size; n++) {
  124. _pvs->add_to_pvs(file->get_16());
  125. }
  126. // secondary pvs
  127. pvs_size = file->get_32();
  128. for (int n = 0; n < pvs_size; n++) {
  129. _pvs->add_to_secondary_pvs(file->get_16());
  130. }
  131. if (file) {
  132. memdelete(file);
  133. }
  134. return true;
  135. failed:
  136. if (file) {
  137. memdelete(file);
  138. }
  139. return false;
  140. }
  141. void PVSBuilder::save_pvs(String p_filename) {
  142. if (p_filename == "") {
  143. p_filename = "res://test.pvs";
  144. }
  145. Error err;
  146. FileAccess *file = FileAccess::open(p_filename, FileAccess::WRITE, &err);
  147. if (err || !file) {
  148. if (file) {
  149. memdelete(file);
  150. }
  151. return;
  152. }
  153. file->store_8('p');
  154. file->store_8('v');
  155. file->store_8('s');
  156. file->store_8(' ');
  157. // hash? NYI
  158. // first save the room indices into the pvs
  159. int num_rooms = _portal_renderer->get_num_rooms();
  160. file->store_32(num_rooms);
  161. for (int n = 0; n < num_rooms; n++) {
  162. VSRoom &room = _portal_renderer->get_room(n);
  163. file->store_32(room._pvs_first);
  164. file->store_32(room._pvs_size);
  165. file->store_32(room._secondary_pvs_first);
  166. file->store_32(room._secondary_pvs_size);
  167. }
  168. int32_t pvs_size = _pvs->get_pvs_size();
  169. file->store_32(pvs_size);
  170. for (int n = 0; n < pvs_size; n++) {
  171. int16_t room_id = _pvs->get_pvs_room_id(n);
  172. file->store_16(room_id);
  173. }
  174. pvs_size = _pvs->get_secondary_pvs_size();
  175. file->store_32(pvs_size);
  176. for (int n = 0; n < pvs_size; n++) {
  177. int16_t room_id = _pvs->get_secondary_pvs_room_id(n);
  178. file->store_16(room_id);
  179. }
  180. if (file) {
  181. memdelete(file);
  182. }
  183. }
  184. #endif
  185. void PVSBuilder::calculate_pvs(PortalRenderer &p_portal_renderer, String p_filename, int p_depth_limit, bool p_use_simple_pvs, bool p_log_pvs_generation) {
  186. _portal_renderer = &p_portal_renderer;
  187. _pvs = &p_portal_renderer.get_pvs();
  188. _depth_limit = p_depth_limit;
  189. _log_active = p_log_pvs_generation;
  190. // attempt to load from file rather than create each time
  191. #ifdef GODOT_PVS_SUPPORT_SAVE_FILE
  192. if (load_pvs(p_filename)) {
  193. print_line("loaded pvs successfully from file " + p_filename);
  194. _pvs->set_loaded(true);
  195. return;
  196. }
  197. #endif
  198. uint32_t time_before = OS::get_singleton()->get_ticks_msec();
  199. int num_rooms = _portal_renderer->get_num_rooms();
  200. BitFieldDynamic bf;
  201. bf.create(num_rooms);
  202. LocalVector<Neighbours> neighbors;
  203. neighbors.resize(num_rooms);
  204. // find the immediate neighbors of each room -
  205. // this is needed to create the secondary pvs
  206. find_neighbors(neighbors);
  207. for (int n = 0; n < num_rooms; n++) {
  208. bf.blank();
  209. //_visible_rooms.clear();
  210. LocalVector<Plane, int32_t> dummy_planes;
  211. VSRoom &room = _portal_renderer->get_room(n);
  212. room._pvs_first = _pvs->get_pvs_size();
  213. log("pvs from room : " + itos(n));
  214. if (p_use_simple_pvs) {
  215. trace_rooms_recursive_simple(0, n, n, -1, false, -1, dummy_planes, bf);
  216. } else {
  217. trace_rooms_recursive(0, n, n, -1, false, -1, dummy_planes, bf);
  218. }
  219. create_secondary_pvs(n, neighbors, bf);
  220. if (_log_active) {
  221. String string = "";
  222. for (int i = 0; i < room._pvs_size; i++) {
  223. int visible_room = _pvs->get_pvs_room_id(room._pvs_first + i);
  224. string += itos(visible_room);
  225. string += ", ";
  226. }
  227. log("\t" + string);
  228. string = "secondary : ";
  229. for (int i = 0; i < room._secondary_pvs_size; i++) {
  230. int visible_room = _pvs->get_secondary_pvs_room_id(room._secondary_pvs_first + i);
  231. string += itos(visible_room);
  232. string += ", ";
  233. }
  234. log("\t" + string);
  235. }
  236. }
  237. _pvs->set_loaded(true);
  238. uint32_t time_after = OS::get_singleton()->get_ticks_msec();
  239. print_verbose("calculated PVS in " + itos(time_after - time_before) + " ms.");
  240. #ifdef GODOT_PVS_SUPPORT_SAVE_FILE
  241. save_pvs(p_filename);
  242. #endif
  243. }
  244. void PVSBuilder::logd(int p_depth, String p_string) {
  245. if (!_log_active) {
  246. return;
  247. }
  248. String string_long;
  249. for (int n = 0; n < p_depth; n++) {
  250. string_long += "\t";
  251. }
  252. string_long += p_string;
  253. log(string_long);
  254. }
  255. void PVSBuilder::log(String p_string) {
  256. if (_log_active) {
  257. print_line(p_string);
  258. }
  259. }
  260. // The full routine deals with re-entrant rooms. I.e. more than one portal path can lead into a room.
  261. // This makes the logic more complex, because we cannot terminate on the second entry to a room,
  262. // and have to account for internal rooms, and the possibility of portal paths going back on themselves.
  263. 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<Plane, int32_t> &p_planes, BitFieldDynamic &r_bitfield_rooms, int p_from_external_room_id) {
  264. // prevent too much depth
  265. if (p_depth > _depth_limit) {
  266. WARN_PRINT_ONCE("PVS Depth Limit reached (seeing through too many portals)");
  267. return;
  268. }
  269. // is this room hit first time?
  270. if (r_bitfield_rooms.check_and_set(p_room_id)) {
  271. // only add to the room PVS of the source room once
  272. VSRoom &source_room = _portal_renderer->get_room(p_source_room_id);
  273. _pvs->add_to_pvs(p_room_id);
  274. source_room._pvs_size += 1;
  275. }
  276. logd(p_depth, "trace_rooms_recursive room " + itos(p_room_id));
  277. // get the room
  278. const VSRoom &room = _portal_renderer->get_room(p_room_id);
  279. // go through each portal
  280. int num_portals = room._portal_ids.size();
  281. for (int p = 0; p < num_portals; p++) {
  282. int portal_id = room._portal_ids[p];
  283. const VSPortal &portal = _portal_renderer->get_portal(portal_id);
  284. // everything depends on whether the portal is incoming or outgoing.
  285. // if incoming we reverse the logic.
  286. int outgoing = 1;
  287. int room_a_id = portal._linkedroom_ID[0];
  288. if (room_a_id != p_room_id) {
  289. outgoing = 0;
  290. DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
  291. }
  292. // trace through this portal to the next room
  293. int linked_room_id = portal._linkedroom_ID[outgoing];
  294. // not relevant, portal doesn't go anywhere
  295. if (linked_room_id == -1)
  296. continue;
  297. // For pvs there is no real start point, but we will use the centre of the first portal.
  298. // This is used for checking portals are pointing outward from start point.
  299. if (p_source_room_id == p_room_id) {
  300. _trace_start_point = portal._pt_center;
  301. // We will use a small epsilon because we don't want to trace out
  302. // to coplanar portals for the first to second portals, before planes
  303. // have been added. So we will place the trace start point slightly
  304. // behind the first portal plane (e.g. slightly in the source room).
  305. // The epsilon must balance being enough in not to cause numerical error
  306. // at large distances from the origin, but too large and this will also
  307. // prevent the PVS entering portals that are very closely aligned
  308. // to the portal in.
  309. // Closely aligned portals should not happen in normal level design,
  310. // and will usually be a design error.
  311. // Watch for bugs here though, caused by closely aligned portals.
  312. // The epsilon should be BEHIND the way we are going through the portal,
  313. // so depends whether it is outgoing or not
  314. if (outgoing) {
  315. _trace_start_point -= portal._plane.normal * 0.1;
  316. } else {
  317. _trace_start_point += portal._plane.normal * 0.1;
  318. }
  319. } else {
  320. // much better way of culling portals by direction to camera...
  321. // instead of using dot product with a varying view direction, we simply find which side of the portal
  322. // plane the camera is on! If it is behind, the portal can be seen through, if in front, it can't
  323. real_t dist_cam = portal._plane.distance_to(_trace_start_point);
  324. if (!outgoing) {
  325. dist_cam = -dist_cam;
  326. }
  327. if (dist_cam >= 0.0) {
  328. // logd(p_depth + 2, "portal WRONG DIRECTION");
  329. continue;
  330. }
  331. }
  332. logd(p_depth + 1, "portal to room " + itos(linked_room_id));
  333. // is it culled by the planes?
  334. VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE;
  335. // while clipping to the planes we maintain a list of partial planes, so we can add them to the
  336. // recursive next iteration of planes to check
  337. static LocalVector<int> partial_planes;
  338. partial_planes.clear();
  339. for (int32_t l = 0; l < p_planes.size(); l++) {
  340. VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]);
  341. switch (res) {
  342. case VSPortal::ClipResult::CLIP_OUTSIDE: {
  343. overall_res = res;
  344. } break;
  345. case VSPortal::ClipResult::CLIP_PARTIAL: {
  346. // if the portal intersects one of the planes, we should take this plane into account
  347. // in the next call of this recursive trace, because it can be used to cull out more objects
  348. overall_res = res;
  349. partial_planes.push_back(l);
  350. } break;
  351. default: // suppress warning
  352. break;
  353. }
  354. // if the portal was totally outside the 'frustum' then we can ignore it
  355. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE)
  356. break;
  357. }
  358. // this portal is culled
  359. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) {
  360. logd(p_depth + 2, "portal CLIP_OUTSIDE");
  361. continue;
  362. }
  363. // Don't allow portals from internal to external room to be followed
  364. // if the external room has already been processed in this trace stack. This prevents
  365. // unneeded processing, and also prevents recursive feedback where you
  366. // see into internal room -> external room and back into the same internal room
  367. // via the same portal.
  368. if (portal._internal && (linked_room_id != -1)) {
  369. if (outgoing) {
  370. if (linked_room_id == p_from_external_room_id) {
  371. continue;
  372. }
  373. } else {
  374. // We are entering an internal portal from an external room.
  375. // set the external room id, so we can recognise this when we are
  376. // later exiting the internal rooms.
  377. // Note that as we can only store 1 previous external room, this system
  378. // won't work completely correctly when you have 2 levels of internal room
  379. // and you can see from roomgroup a -> b -> c. However this should just result
  380. // in a little slower culling for that particular view, and hopefully will not break
  381. // with recursive loop looking through the same portal multiple times. (don't think this
  382. // is possible in this scenario).
  383. p_from_external_room_id = p_room_id;
  384. }
  385. }
  386. // construct new planes
  387. LocalVector<Plane, int32_t> planes;
  388. if (p_first_portal_id != -1) {
  389. // add new planes
  390. const VSPortal &first_portal = _portal_renderer->get_portal(p_first_portal_id);
  391. portal.add_pvs_planes(first_portal, p_first_portal_outgoing, planes, outgoing != 0);
  392. //#define GODOT_PVS_EXTRA_REJECT_TEST
  393. #ifdef GODOT_PVS_EXTRA_REJECT_TEST
  394. // extra reject test for pvs - was the previous portal points outside the planes formed by the new portal?
  395. // not fully tested and not yet found a situation where needed, but will leave in in case testers find
  396. // such a situation.
  397. if (p_previous_portal_id != -1) {
  398. const VSPortal &prev_portal = _portal_renderer->get_portal(p_previous_portal_id);
  399. if (prev_portal._pvs_is_outside_planes(planes)) {
  400. continue;
  401. }
  402. }
  403. #endif
  404. }
  405. // if portal is totally inside the planes, don't copy the old planes ..
  406. // i.e. we can now cull using the portal and forget about the rest of the frustum (yay)
  407. if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) {
  408. // if it WASN'T totally inside the existing frustum, we also need to add any existing planes
  409. // that cut the portal.
  410. for (uint32_t n = 0; n < partial_planes.size(); n++)
  411. planes.push_back(p_planes[partial_planes[n]]);
  412. }
  413. // hopefully the portal actually leads somewhere...
  414. if (linked_room_id != -1) {
  415. // we either pass on the first portal id, or we start
  416. // it here, because we are looking through the first portal
  417. int first_portal_id = p_first_portal_id;
  418. if (first_portal_id == -1) {
  419. first_portal_id = portal_id;
  420. p_first_portal_outgoing = outgoing != 0;
  421. }
  422. 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, p_from_external_room_id);
  423. } // linked room is valid
  424. }
  425. }
  426. // This simpler routine was the first used. It is reliable and no epsilons, and fast.
  427. // But it will not create the correct result where there are multiple portal paths
  428. // through a room when building the PVS.
  429. void PVSBuilder::trace_rooms_recursive_simple(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<Plane, int32_t> &p_planes, BitFieldDynamic &r_bitfield_rooms) {
  430. // has this room been done already?
  431. if (!r_bitfield_rooms.check_and_set(p_room_id)) {
  432. return;
  433. }
  434. // prevent too much depth
  435. if (p_depth > _depth_limit) {
  436. WARN_PRINT_ONCE("Portal Depth Limit reached (seeing through too many portals)");
  437. return;
  438. }
  439. logd(p_depth, "trace_rooms_recursive room " + itos(p_room_id));
  440. // get the room
  441. const VSRoom &room = _portal_renderer->get_room(p_room_id);
  442. // add to the room PVS of the source room
  443. VSRoom &source_room = _portal_renderer->get_room(p_source_room_id);
  444. _pvs->add_to_pvs(p_room_id);
  445. source_room._pvs_size += 1;
  446. // go through each portal
  447. int num_portals = room._portal_ids.size();
  448. for (int p = 0; p < num_portals; p++) {
  449. int portal_id = room._portal_ids[p];
  450. const VSPortal &portal = _portal_renderer->get_portal(portal_id);
  451. // everything depends on whether the portal is incoming or outgoing.
  452. // if incoming we reverse the logic.
  453. int outgoing = 1;
  454. int room_a_id = portal._linkedroom_ID[0];
  455. if (room_a_id != p_room_id) {
  456. outgoing = 0;
  457. DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
  458. }
  459. // trace through this portal to the next room
  460. int linked_room_id = portal._linkedroom_ID[outgoing];
  461. logd(p_depth + 1, "portal to room " + itos(linked_room_id));
  462. // not relevant, portal doesn't go anywhere
  463. if (linked_room_id == -1)
  464. continue;
  465. // linked room done already?
  466. if (r_bitfield_rooms.get_bit(linked_room_id))
  467. continue;
  468. // is it culled by the planes?
  469. VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE;
  470. // while clipping to the planes we maintain a list of partial planes, so we can add them to the
  471. // recursive next iteration of planes to check
  472. static LocalVector<int> partial_planes;
  473. partial_planes.clear();
  474. for (int32_t l = 0; l < p_planes.size(); l++) {
  475. VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]);
  476. switch (res) {
  477. case VSPortal::ClipResult::CLIP_OUTSIDE: {
  478. overall_res = res;
  479. } break;
  480. case VSPortal::ClipResult::CLIP_PARTIAL: {
  481. // if the portal intersects one of the planes, we should take this plane into account
  482. // in the next call of this recursive trace, because it can be used to cull out more objects
  483. overall_res = res;
  484. partial_planes.push_back(l);
  485. } break;
  486. default: // suppress warning
  487. break;
  488. }
  489. // if the portal was totally outside the 'frustum' then we can ignore it
  490. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE)
  491. break;
  492. }
  493. // this portal is culled
  494. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) {
  495. logd(p_depth + 2, "portal CLIP_OUTSIDE");
  496. continue;
  497. }
  498. // construct new planes
  499. LocalVector<Plane, int32_t> planes;
  500. if (p_first_portal_id != -1) {
  501. // add new planes
  502. const VSPortal &first_portal = _portal_renderer->get_portal(p_first_portal_id);
  503. portal.add_pvs_planes(first_portal, p_first_portal_outgoing, planes, outgoing != 0);
  504. #ifdef GODOT_PVS_EXTRA_REJECT_TEST
  505. // extra reject test for pvs - was the previous portal points outside the planes formed by the new portal?
  506. // not fully tested and not yet found a situation where needed, but will leave in in case testers find
  507. // such a situation.
  508. if (p_previous_portal_id != -1) {
  509. const VSPortal &prev_portal = _portal_renderer->get_portal(p_previous_portal_id);
  510. if (prev_portal._pvs_is_outside_planes(planes)) {
  511. continue;
  512. }
  513. }
  514. #endif
  515. }
  516. // if portal is totally inside the planes, don't copy the old planes ..
  517. // i.e. we can now cull using the portal and forget about the rest of the frustum (yay)
  518. if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) {
  519. // if it WASN'T totally inside the existing frustum, we also need to add any existing planes
  520. // that cut the portal.
  521. for (uint32_t n = 0; n < partial_planes.size(); n++)
  522. planes.push_back(p_planes[partial_planes[n]]);
  523. }
  524. // hopefully the portal actually leads somewhere...
  525. if (linked_room_id != -1) {
  526. // we either pass on the first portal id, or we start
  527. // it here, because we are looking through the first portal
  528. int first_portal_id = p_first_portal_id;
  529. if (first_portal_id == -1) {
  530. first_portal_id = portal_id;
  531. p_first_portal_outgoing = outgoing != 0;
  532. }
  533. 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);
  534. } // linked room is valid
  535. }
  536. }