portal_rooms_bsp.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /**************************************************************************/
  2. /* portal_rooms_bsp.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_rooms_bsp.h"
  31. #include "core/math/geometry.h"
  32. #include "core/math/plane.h"
  33. #include "core/print_string.h"
  34. #include "core/variant.h"
  35. #include "portal_renderer.h"
  36. // #define GODOT_VERBOSE_PORTAL_ROOMS_BSP
  37. void PortalRoomsBSP::_log(String p_string) {
  38. #ifdef GODOT_VERBOSE_PORTAL_ROOMS_BSP
  39. print_line(p_string);
  40. #endif
  41. }
  42. // rooms which contain internal rooms cannot use the optimization where it terminates the search for
  43. // room within if inside the previous room. We can't use just use the rooms already marked as internal due
  44. // to a portal leading to them, because the internal room network may spread into another room (e.g. terrain)
  45. // which has internal room exit portal. So we need to detect manually all cases of overlap of internal rooms,
  46. // and set the flag.
  47. void PortalRoomsBSP::detect_internal_room_containment(PortalRenderer &r_portal_renderer) {
  48. int num_rooms = r_portal_renderer.get_num_rooms();
  49. for (int n = 0; n < num_rooms; n++) {
  50. VSRoom &room = r_portal_renderer.get_room(n);
  51. if (room._contains_internal_rooms) {
  52. // already established it contains internal rooms, no need to test
  53. continue;
  54. }
  55. // safety
  56. if (!room._planes.size()) {
  57. continue;
  58. }
  59. for (int i = 0; i < num_rooms; i++) {
  60. // don't test against ourself
  61. if (n == i) {
  62. continue;
  63. }
  64. // only interested in rooms with a higher priority, these are potential internal rooms
  65. const VSRoom &other = r_portal_renderer.get_room(i);
  66. if (other._priority <= room._priority) {
  67. continue;
  68. }
  69. // quick aabb check first
  70. if (!room._aabb.intersects(other._aabb)) {
  71. continue;
  72. }
  73. // safety
  74. if (!other._planes.size()) {
  75. continue;
  76. }
  77. if (Geometry::convex_hull_intersects_convex_hull(&room._planes[0], room._planes.size(), &other._planes[0], other._planes.size())) {
  78. // it intersects an internal room
  79. room._contains_internal_rooms = true;
  80. break;
  81. }
  82. }
  83. }
  84. }
  85. int PortalRoomsBSP::find_room_within(const PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_previous_room_id) const {
  86. real_t closest = FLT_MAX;
  87. int closest_room_id = -1;
  88. int closest_priority = -10000;
  89. // first try previous room
  90. if (p_previous_room_id != -1) {
  91. if (p_previous_room_id < p_portal_renderer.get_num_rooms()) {
  92. const VSRoom &prev_room = p_portal_renderer.get_room(p_previous_room_id);
  93. // we can only use this shortcut if the room doesn't include internal rooms.
  94. // otherwise the point may be inside more than one room, and we need to find the room of highest priority.
  95. if (!prev_room._contains_internal_rooms) {
  96. closest = prev_room.is_point_within(p_pos);
  97. closest_room_id = p_previous_room_id;
  98. if (closest < 0.0) {
  99. return p_previous_room_id;
  100. }
  101. } else {
  102. // don't mark it as checked later, as we haven't done it because it contains internal rooms
  103. p_previous_room_id = -1;
  104. }
  105. } else {
  106. // previous room was out of range (perhaps due to reconverting room system and the number of rooms decreasing)
  107. p_previous_room_id = -1;
  108. }
  109. }
  110. int num_bsp_rooms = 0;
  111. const int32_t *bsp_rooms = find_shortlist(p_pos, num_bsp_rooms);
  112. if (!num_bsp_rooms) {
  113. return -1;
  114. }
  115. // special case, only 1 room in the shortlist, no need to check further
  116. if (num_bsp_rooms == 1) {
  117. return bsp_rooms[0];
  118. }
  119. for (int n = 0; n < num_bsp_rooms; n++) {
  120. int room_id = bsp_rooms[n];
  121. // the previous room has already been done above, and will be in closest + closest_room_id
  122. if (room_id == p_previous_room_id) {
  123. continue;
  124. }
  125. const VSRoom &room = p_portal_renderer.get_room(room_id);
  126. real_t dist = room.is_point_within(p_pos);
  127. // if we are actually inside a room, unless we are dealing with internal rooms,
  128. // we can terminate early, no need to search more
  129. if (dist < 0.0) {
  130. if (!room._contains_internal_rooms) {
  131. // this will happen in most cases
  132. closest = dist;
  133. closest_room_id = room_id;
  134. break;
  135. } else {
  136. // if we are inside, and there are internal rooms involved we need to be a bit careful.
  137. // higher priority always wins (i.e. the internal room)
  138. // but with equal priority we just choose the regular best fit.
  139. if ((room._priority > closest_priority) || ((room._priority == closest_priority) && (dist < closest))) {
  140. closest = dist;
  141. closest_room_id = room_id;
  142. closest_priority = room._priority;
  143. continue;
  144. }
  145. }
  146. } else {
  147. // if we are outside we just pick the closest room, irrespective of priority
  148. if (dist < closest) {
  149. closest = dist;
  150. closest_room_id = room_id;
  151. // do NOT store the priority, we don't want an room that isn't a true hit
  152. // overriding a hit inside the room
  153. }
  154. }
  155. }
  156. return closest_room_id;
  157. }
  158. const int32_t *PortalRoomsBSP::find_shortlist(const Vector3 &p_pt, int &r_num_rooms) const {
  159. if (!_nodes.size()) {
  160. r_num_rooms = 0;
  161. return nullptr;
  162. }
  163. const Node *node = &_nodes[0];
  164. while (!node->leaf) {
  165. if (node->plane.is_point_over(p_pt)) {
  166. node = &_nodes[node->child[1]];
  167. } else {
  168. node = &_nodes[node->child[0]];
  169. }
  170. }
  171. r_num_rooms = node->num_ids;
  172. return &_room_ids[node->first_id];
  173. }
  174. void PortalRoomsBSP::create(PortalRenderer &r_portal_renderer) {
  175. clear();
  176. _portal_renderer = &r_portal_renderer;
  177. detect_internal_room_containment(r_portal_renderer);
  178. // noop
  179. int num_rooms = r_portal_renderer.get_num_rooms();
  180. if (!num_rooms) {
  181. return;
  182. }
  183. LocalVector<int32_t, int32_t> room_ids;
  184. room_ids.resize(num_rooms);
  185. for (int n = 0; n < num_rooms; n++) {
  186. room_ids[n] = n;
  187. }
  188. _nodes.push_back(Node());
  189. _nodes[0].clear();
  190. build(0, room_ids);
  191. #ifdef GODOT_VERBOSE_PORTAL_ROOMS_BSP
  192. debug_print_tree();
  193. #endif
  194. _log("PortalRoomsBSP " + itos(_nodes.size()) + " nodes.");
  195. }
  196. void PortalRoomsBSP::build(int p_start_node_id, LocalVector<int32_t, int32_t> p_orig_room_ids) {
  197. struct Element {
  198. void clear() { room_ids.clear(); }
  199. int node_id;
  200. LocalVector<int32_t, int32_t> room_ids;
  201. };
  202. Element first;
  203. first.node_id = p_start_node_id;
  204. first.room_ids = p_orig_room_ids;
  205. LocalVector<Element, int32_t> stack;
  206. stack.reserve(1024);
  207. stack.push_back(first);
  208. int stack_size = 1;
  209. while (stack_size) {
  210. stack_size--;
  211. Element curr = stack[stack_size];
  212. Node *node = &_nodes[curr.node_id];
  213. int best_fit = 0;
  214. int best_portal_id = -1;
  215. int best_room_a = -1;
  216. int best_room_b = -1;
  217. // find a splitting plane
  218. for (int n = 0; n < curr.room_ids.size(); n++) {
  219. // go through the portals in this room
  220. int rid = curr.room_ids[n];
  221. const VSRoom &room = _portal_renderer->get_room(rid);
  222. for (int p = 0; p < room._portal_ids.size(); p++) {
  223. int pid = room._portal_ids[p];
  224. // only outward portals
  225. const VSPortal &portal = _portal_renderer->get_portal(pid);
  226. if (portal._linkedroom_ID[1] == rid) {
  227. continue;
  228. }
  229. int fit = evaluate_portal(pid, curr.room_ids);
  230. if (fit > best_fit) {
  231. best_fit = fit;
  232. best_portal_id = pid;
  233. }
  234. }
  235. }
  236. bool split_found = false;
  237. Plane split_plane;
  238. // if a splitting portal was found, we are done
  239. if (best_portal_id != -1) {
  240. _log("found splitting portal : " + itos(best_portal_id));
  241. const VSPortal &portal = _portal_renderer->get_portal(best_portal_id);
  242. split_plane = portal._plane;
  243. split_found = true;
  244. } else {
  245. // let's try and find an arbitrary splitting plane
  246. for (int a = 0; a < curr.room_ids.size(); a++) {
  247. for (int b = a + 1; b < curr.room_ids.size(); b++) {
  248. Plane plane;
  249. // note the actual room ids are not the same as a and b!!
  250. int room_a_id = curr.room_ids[a];
  251. int room_b_id = curr.room_ids[b];
  252. int fit = evaluate_room_split_plane(room_a_id, room_b_id, curr.room_ids, plane);
  253. if (fit > best_fit) {
  254. best_fit = fit;
  255. // the room ids, NOT a and b
  256. best_room_a = room_a_id;
  257. best_room_b = room_b_id;
  258. split_plane = plane;
  259. }
  260. } // for b through rooms
  261. } // for a through rooms
  262. if (best_room_a != -1) {
  263. split_found = true;
  264. // print_line("found splitting plane between rooms : " + itos(best_room_a) + " and " + itos(best_room_b));
  265. }
  266. }
  267. // found either a portal plane or arbitrary
  268. if (split_found) {
  269. node->plane = split_plane;
  270. // add to stack
  271. stack_size += 2;
  272. if (stack_size > stack.size()) {
  273. stack.resize(stack_size);
  274. }
  275. stack[stack_size - 2].clear();
  276. stack[stack_size - 1].clear();
  277. LocalVector<int32_t, int32_t> &room_ids_back = stack[stack_size - 2].room_ids;
  278. LocalVector<int32_t, int32_t> &room_ids_front = stack[stack_size - 1].room_ids;
  279. if (best_portal_id != -1) {
  280. evaluate_portal(best_portal_id, curr.room_ids, &room_ids_back, &room_ids_front);
  281. } else {
  282. DEV_ASSERT(best_room_a != -1);
  283. evaluate_room_split_plane(best_room_a, best_room_b, curr.room_ids, split_plane, &room_ids_back, &room_ids_front);
  284. }
  285. DEV_ASSERT(room_ids_back.size() <= curr.room_ids.size());
  286. DEV_ASSERT(room_ids_front.size() <= curr.room_ids.size());
  287. _log("\tback contains : " + itos(room_ids_back.size()) + " rooms");
  288. _log("\tfront contains : " + itos(room_ids_front.size()) + " rooms");
  289. // create child nodes
  290. _nodes.push_back(Node());
  291. _nodes.push_back(Node());
  292. // need to reget the node pointer as we may have resized the vector
  293. node = &_nodes[curr.node_id];
  294. node->child[0] = _nodes.size() - 2;
  295. node->child[1] = _nodes.size() - 1;
  296. stack[stack_size - 2].node_id = node->child[0];
  297. stack[stack_size - 1].node_id = node->child[1];
  298. } else {
  299. // couldn't split any further, is leaf
  300. node->leaf = true;
  301. node->first_id = _room_ids.size();
  302. node->num_ids = curr.room_ids.size();
  303. _log("leaf contains : " + itos(curr.room_ids.size()) + " rooms");
  304. // add to the main list
  305. int start = _room_ids.size();
  306. _room_ids.resize(start + curr.room_ids.size());
  307. for (int n = 0; n < curr.room_ids.size(); n++) {
  308. _room_ids[start + n] = curr.room_ids[n];
  309. }
  310. }
  311. } // while stack not empty
  312. }
  313. void PortalRoomsBSP::debug_print_tree(int p_node_id, int p_depth) {
  314. String string = "";
  315. for (int n = 0; n < p_depth; n++) {
  316. string += "\t";
  317. }
  318. const Node &node = _nodes[p_node_id];
  319. if (node.leaf) {
  320. string += "L ";
  321. for (int n = 0; n < node.num_ids; n++) {
  322. int room_id = _room_ids[node.first_id + n];
  323. string += itos(room_id) + ", ";
  324. }
  325. } else {
  326. string += "N ";
  327. }
  328. print_line(string);
  329. // children
  330. if (!node.leaf) {
  331. debug_print_tree(node.child[0], p_depth + 1);
  332. debug_print_tree(node.child[1], p_depth + 1);
  333. }
  334. }
  335. bool PortalRoomsBSP::find_1d_split_point(real_t p_min_a, real_t p_max_a, real_t p_min_b, real_t p_max_b, real_t &r_split_point) const {
  336. if (p_max_a <= p_min_b) {
  337. r_split_point = p_max_a + ((p_min_b - p_max_a) * 0.5);
  338. return true;
  339. }
  340. if (p_max_b <= p_min_a) {
  341. r_split_point = p_max_b + ((p_min_a - p_max_b) * 0.5);
  342. return true;
  343. }
  344. return false;
  345. }
  346. bool PortalRoomsBSP::test_freeform_plane(const LocalVector<Vector3, int32_t> &p_verts_a, const LocalVector<Vector3, int32_t> &p_verts_b, const Plane &p_plane) const {
  347. // print_line("test_freeform_plane " + String(Variant(p_plane)));
  348. for (int n = 0; n < p_verts_a.size(); n++) {
  349. real_t dist = p_plane.distance_to(p_verts_a[n]);
  350. // print_line("\tdist_a " + String(Variant(dist)));
  351. if (dist > _plane_epsilon) {
  352. return false;
  353. }
  354. }
  355. for (int n = 0; n < p_verts_b.size(); n++) {
  356. real_t dist = p_plane.distance_to(p_verts_b[n]);
  357. // print_line("\tdist_b " + String(Variant(dist)));
  358. if (dist < -_plane_epsilon) {
  359. return false;
  360. }
  361. }
  362. return true;
  363. }
  364. // even if AABBs fail to have a splitting plane, there still may be another orientation that can split rooms (e.g. diagonal)
  365. bool PortalRoomsBSP::calculate_freeform_splitting_plane(const VSRoom &p_room_a, const VSRoom &p_room_b, Plane &r_plane) const {
  366. const LocalVector<Vector3, int32_t> &verts_a = p_room_a._verts;
  367. const LocalVector<Vector3, int32_t> &verts_b = p_room_b._verts;
  368. // test from room a to room b
  369. for (int i = 0; i < verts_a.size(); i++) {
  370. const Vector3 &pt_a = verts_a[i];
  371. for (int j = 0; j < verts_b.size(); j++) {
  372. const Vector3 &pt_b = verts_b[j];
  373. for (int k = j + 1; k < verts_b.size(); k++) {
  374. const Vector3 &pt_c = verts_b[k];
  375. // make a plane
  376. r_plane = Plane(pt_a, pt_b, pt_c);
  377. // test the plane
  378. if (test_freeform_plane(verts_a, verts_b, r_plane)) {
  379. return true;
  380. }
  381. }
  382. }
  383. }
  384. // test from room b to room a
  385. for (int i = 0; i < verts_b.size(); i++) {
  386. const Vector3 &pt_a = verts_b[i];
  387. for (int j = 0; j < verts_a.size(); j++) {
  388. const Vector3 &pt_b = verts_a[j];
  389. for (int k = j + 1; k < verts_a.size(); k++) {
  390. const Vector3 &pt_c = verts_a[k];
  391. // make a plane
  392. r_plane = Plane(pt_a, pt_b, pt_c);
  393. // test the plane
  394. if (test_freeform_plane(verts_b, verts_a, r_plane)) {
  395. return true;
  396. }
  397. }
  398. }
  399. }
  400. return false;
  401. }
  402. bool PortalRoomsBSP::calculate_aabb_splitting_plane(const AABB &p_a, const AABB &p_b, Plane &r_plane) const {
  403. real_t split_point = 0.0;
  404. const Vector3 &min_a = p_a.position;
  405. const Vector3 &min_b = p_b.position;
  406. Vector3 max_a = min_a + p_a.size;
  407. Vector3 max_b = min_b + p_b.size;
  408. if (find_1d_split_point(min_a.x, max_a.x, min_b.x, max_b.x, split_point)) {
  409. r_plane = Plane(Vector3(1, 0, 0), split_point);
  410. return true;
  411. }
  412. if (find_1d_split_point(min_a.y, max_a.y, min_b.y, max_b.y, split_point)) {
  413. r_plane = Plane(Vector3(0, 1, 0), split_point);
  414. return true;
  415. }
  416. if (find_1d_split_point(min_a.z, max_a.z, min_b.z, max_b.z, split_point)) {
  417. r_plane = Plane(Vector3(0, 0, 1), split_point);
  418. return true;
  419. }
  420. return false;
  421. }
  422. int PortalRoomsBSP::evaluate_room_split_plane(int p_room_a_id, int p_room_b_id, const LocalVector<int32_t, int32_t> &p_room_ids, Plane &r_plane, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
  423. // try and create a splitting plane between room a and b, then evaluate it.
  424. const VSRoom &room_a = _portal_renderer->get_room(p_room_a_id);
  425. const VSRoom &room_b = _portal_renderer->get_room(p_room_b_id);
  426. // easiest case, if the rooms don't overlap AABB, we can create an axis aligned plane between them
  427. if (calculate_aabb_splitting_plane(room_a._aabb, room_b._aabb, r_plane)) {
  428. return evaluate_plane(nullptr, r_plane, p_room_ids, r_room_ids_back, r_room_ids_front);
  429. }
  430. if (calculate_freeform_splitting_plane(room_a, room_b, r_plane)) {
  431. return evaluate_plane(nullptr, r_plane, p_room_ids, r_room_ids_back, r_room_ids_front);
  432. }
  433. return 0;
  434. }
  435. int PortalRoomsBSP::evaluate_plane(const VSPortal *p_portal, const Plane &p_plane, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
  436. int rooms_front = 0;
  437. int rooms_back = 0;
  438. if (r_room_ids_back) {
  439. DEV_ASSERT(!r_room_ids_back->size());
  440. }
  441. if (r_room_ids_front) {
  442. DEV_ASSERT(!r_room_ids_front->size());
  443. }
  444. #define GODOT_BSP_PUSH_FRONT \
  445. rooms_front++; \
  446. if (r_room_ids_front) { \
  447. r_room_ids_front->push_back(rid); \
  448. }
  449. #define GODOT_BSP_PUSH_BACK \
  450. rooms_back++; \
  451. if (r_room_ids_back) { \
  452. r_room_ids_back->push_back(rid); \
  453. }
  454. for (int n = 0; n < p_room_ids.size(); n++) {
  455. int rid = p_room_ids[n];
  456. const VSRoom &room = _portal_renderer->get_room(rid);
  457. // easy cases first
  458. real_t r_min, r_max;
  459. room._aabb.project_range_in_plane(p_plane, r_min, r_max);
  460. if ((r_min <= 0.0) && (r_max <= 0.0)) {
  461. GODOT_BSP_PUSH_BACK
  462. continue;
  463. }
  464. if ((r_min >= 0.0) && (r_max >= 0.0)) {
  465. GODOT_BSP_PUSH_FRONT
  466. continue;
  467. }
  468. // check if the room uses this portal
  469. // internal portals can link to a room that is both in front and behind,
  470. // so we can only deal with non internal portals here with this cheap test.
  471. if (p_portal && !p_portal->_internal) {
  472. if (p_portal->_linkedroom_ID[0] == rid) {
  473. GODOT_BSP_PUSH_BACK
  474. continue;
  475. }
  476. if (p_portal->_linkedroom_ID[1] == rid) {
  477. GODOT_BSP_PUSH_FRONT
  478. continue;
  479. }
  480. }
  481. // most expensive test, test the individual points of the room
  482. // This will catch some off axis rooms that aren't caught by the AABB alone
  483. int points_front = 0;
  484. int points_back = 0;
  485. for (int p = 0; p < room._verts.size(); p++) {
  486. const Vector3 &pt = room._verts[p];
  487. real_t dist = p_plane.distance_to(pt);
  488. // don't take account of points in the epsilon zone,
  489. // these are within the margin of error and could be in front OR behind the plane
  490. if (dist > _plane_epsilon) {
  491. points_front++;
  492. if (points_back) {
  493. break;
  494. }
  495. } else if (dist < -_plane_epsilon) {
  496. points_back++;
  497. if (points_front) {
  498. break;
  499. }
  500. }
  501. }
  502. // if all points are in front
  503. if (!points_back) {
  504. GODOT_BSP_PUSH_FRONT
  505. continue;
  506. }
  507. // if all points are behind
  508. if (!points_front) {
  509. GODOT_BSP_PUSH_BACK
  510. continue;
  511. }
  512. // if split, push to both children
  513. if (r_room_ids_front) {
  514. r_room_ids_front->push_back(rid);
  515. }
  516. if (r_room_ids_back) {
  517. r_room_ids_back->push_back(rid);
  518. }
  519. }
  520. #undef GODOT_BSP_PUSH_BACK
  521. #undef GODOT_BSP_PUSH_FRONT
  522. // we want the split that splits the most front and back rooms
  523. return rooms_front * rooms_back;
  524. }
  525. int PortalRoomsBSP::evaluate_portal(int p_portal_id, const LocalVector<int32_t, int32_t> &p_room_ids, LocalVector<int32_t, int32_t> *r_room_ids_back, LocalVector<int32_t, int32_t> *r_room_ids_front) {
  526. const VSPortal &portal = _portal_renderer->get_portal(p_portal_id);
  527. const Plane &plane = portal._plane;
  528. return evaluate_plane(&portal, plane, p_room_ids, r_room_ids_back, r_room_ids_front);
  529. }