portal_tracer.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. /**************************************************************************/
  2. /* portal_tracer.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_tracer.h"
  31. #include "portal_renderer.h"
  32. #include "servers/visual/visual_server_globals.h"
  33. #include "servers/visual/visual_server_scene.h"
  34. PortalTracer::PlanesPool::PlanesPool() {
  35. reset();
  36. // preallocate the vectors to a reasonable size
  37. for (int n = 0; n < POOL_MAX; n++) {
  38. _planes[n].resize(32);
  39. }
  40. }
  41. void PortalTracer::PlanesPool::reset() {
  42. for (int n = 0; n < POOL_MAX; n++) {
  43. _freelist[n] = POOL_MAX - n - 1;
  44. }
  45. _num_free = POOL_MAX;
  46. }
  47. unsigned int PortalTracer::PlanesPool::request() {
  48. if (!_num_free) {
  49. return -1;
  50. }
  51. _num_free--;
  52. return _freelist[_num_free];
  53. }
  54. void PortalTracer::PlanesPool::free(unsigned int ui) {
  55. DEV_ASSERT(ui < POOL_MAX);
  56. DEV_ASSERT(_num_free < POOL_MAX);
  57. _freelist[_num_free] = ui;
  58. _num_free++;
  59. }
  60. void PortalTracer::trace_debug_sprawl(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, int p_start_room_id, TraceResult &r_result) {
  61. _portal_renderer = &p_portal_renderer;
  62. _trace_start_point = p_pos;
  63. _result = &r_result;
  64. // all the statics should be not hit to start with
  65. _result->clear();
  66. // new test, new tick, to prevent hitting objects more than once
  67. // on a test.
  68. _tick++;
  69. // if the camera is not in a room do nothing
  70. if (p_start_room_id == -1) {
  71. return;
  72. }
  73. trace_debug_sprawl_recursive(0, p_start_room_id);
  74. }
  75. void PortalTracer::trace(PortalRenderer &p_portal_renderer, const Vector3 &p_pos, const LocalVector<Plane> &p_planes, int p_start_room_id, TraceResult &r_result) {
  76. // store local versions to prevent passing around recursive functions
  77. _portal_renderer = &p_portal_renderer;
  78. _trace_start_point = p_pos;
  79. _result = &r_result;
  80. // The near and far clipping planes needs special treatment. The problem is, if it is
  81. // say a metre from the camera, it will clip out a portal immediately in front of the camera.
  82. // as a result we want to use the near clipping plane for objects, but construct a fake
  83. // near plane at exactly the position of the camera, to clip out portals that are behind us.
  84. _near_and_far_planes[0] = p_planes[0];
  85. _near_and_far_planes[1] = p_planes[1];
  86. // all the statics should be not hit to start with
  87. _result->clear();
  88. // new test, new tick, to prevent hitting objects more than once
  89. // on a test.
  90. _tick++;
  91. // if the camera is not in a room do nothing
  92. // (this will return no hits, but is unlikely because the find_rooms lookup will return the nearest
  93. // room even if not inside)
  94. if (p_start_room_id == -1) {
  95. return;
  96. }
  97. // start off the trace with the planes from the camera
  98. LocalVector<Plane> cam_planes;
  99. cam_planes = p_planes;
  100. if (p_portal_renderer.get_cull_using_pvs()) {
  101. trace_pvs(p_start_room_id, cam_planes);
  102. } else {
  103. // alternative : instead of copying straight, we create the first (near) clipping
  104. // plane manually, at 0 distance from the camera. This ensures that portals will not be
  105. // missed, while still culling portals and objects behind us. If we use the actual near clipping plane
  106. // then a portal in front of the camera may not be seen through, giving glitches
  107. cam_planes[0] = Plane(p_pos, cam_planes[0].normal);
  108. TraceParams params;
  109. params.use_pvs = p_portal_renderer.get_pvs().is_loaded();
  110. params.start_room_id = p_start_room_id;
  111. // create bitfield
  112. if (params.use_pvs) {
  113. const PVS &pvs = _portal_renderer->get_pvs();
  114. if (!pvs.get_pvs_size()) {
  115. params.use_pvs = false;
  116. } else {
  117. // decompress a simple to read roomlist bitfield (could use bits maybe but bytes ok for now)
  118. params.decompressed_room_pvs = nullptr;
  119. params.decompressed_room_pvs = (uint8_t *)alloca(sizeof(uint8_t) * pvs.get_pvs_size());
  120. memset(params.decompressed_room_pvs, 0, sizeof(uint8_t) * pvs.get_pvs_size());
  121. const VSRoom &source_room = _portal_renderer->get_room(p_start_room_id);
  122. for (int n = 0; n < source_room._pvs_size; n++) {
  123. int room_id = pvs.get_pvs_room_id(source_room._pvs_first + n);
  124. params.decompressed_room_pvs[room_id] = 255;
  125. }
  126. }
  127. }
  128. trace_recursive(params, 0, p_start_room_id, cam_planes);
  129. }
  130. }
  131. void PortalTracer::cull_roamers(const VSRoom &p_room, const LocalVector<Plane> &p_planes) {
  132. int num_roamers = p_room._roamer_pool_ids.size();
  133. for (int n = 0; n < num_roamers; n++) {
  134. uint32_t pool_id = p_room._roamer_pool_ids[n];
  135. PortalRenderer::Moving &moving = _portal_renderer->get_pool_moving(pool_id);
  136. // done already?
  137. if (moving.last_tick_hit == _tick) {
  138. continue;
  139. }
  140. if (test_cull_inside(moving.exact_aabb, p_planes)) {
  141. if (!_occlusion_culler.cull_aabb(moving.exact_aabb)) {
  142. // mark as done (and on visible list)
  143. moving.last_tick_hit = _tick;
  144. _result->visible_roamer_pool_ids.push_back(pool_id);
  145. }
  146. }
  147. }
  148. }
  149. void PortalTracer::cull_statics_debug_sprawl(const VSRoom &p_room) {
  150. int num_statics = p_room._static_ids.size();
  151. for (int n = 0; n < num_statics; n++) {
  152. uint32_t static_id = p_room._static_ids[n];
  153. // VSStatic &stat = _portal_renderer->get_static(static_id);
  154. // deal with dynamic stats
  155. // if (stat.dynamic) {
  156. // VSG::scene->_instance_get_transformed_aabb(stat.instance, stat.aabb);
  157. // }
  158. // set the visible bit if not set
  159. if (!_result->bf_visible_statics.check_and_set(static_id)) {
  160. _result->visible_static_ids.push_back(static_id);
  161. }
  162. }
  163. }
  164. void PortalTracer::cull_statics(const VSRoom &p_room, const LocalVector<Plane> &p_planes) {
  165. int num_statics = p_room._static_ids.size();
  166. for (int n = 0; n < num_statics; n++) {
  167. uint32_t static_id = p_room._static_ids[n];
  168. VSStatic &stat = _portal_renderer->get_static(static_id);
  169. // deal with dynamic stats
  170. if (stat.dynamic) {
  171. VSG::scene->_instance_get_transformed_aabb(stat.instance, stat.aabb);
  172. }
  173. // estimate the radius .. for now
  174. const AABB &bb = stat.aabb;
  175. // print("\t\t\tculling object " + pObj->get_name());
  176. if (test_cull_inside(bb, p_planes)) {
  177. if (_occlusion_culler.cull_aabb(bb)) {
  178. continue;
  179. }
  180. // bypass the bitfield for now and just show / hide
  181. //stat.show(bShow);
  182. // set the visible bit if not set
  183. if (_result->bf_visible_statics.check_and_set(static_id)) {
  184. // if wasn't previously set, add to the visible list
  185. _result->visible_static_ids.push_back(static_id);
  186. }
  187. }
  188. } // for n through statics
  189. }
  190. int PortalTracer::trace_globals(const LocalVector<Plane> &p_planes, VSInstance **p_result_array, int first_result, int p_result_max, uint32_t p_mask, bool p_override_camera) {
  191. uint32_t num_globals = _portal_renderer->get_num_moving_globals();
  192. int current_result = first_result;
  193. if (!p_override_camera) {
  194. for (uint32_t n = 0; n < num_globals; n++) {
  195. const PortalRenderer::Moving &moving = _portal_renderer->get_moving_global(n);
  196. #ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
  197. // debug check the instance is valid
  198. void *vss_instance = VSG::scene->_instance_get_from_rid(moving.instance_rid);
  199. if (vss_instance) {
  200. #endif
  201. if (test_cull_inside(moving.exact_aabb, p_planes, false)) {
  202. if (VSG::scene->_instance_cull_check(moving.instance, p_mask)) {
  203. p_result_array[current_result++] = moving.instance;
  204. // full up?
  205. if (current_result >= p_result_max) {
  206. return current_result;
  207. }
  208. }
  209. }
  210. #ifdef PORTAL_RENDERER_STORE_MOVING_RIDS
  211. } else {
  212. WARN_PRINT("vss instance is null " + PortalRenderer::_addr_to_string(moving.instance));
  213. }
  214. #endif
  215. }
  216. } // if not override camera
  217. else {
  218. // If we are overriding the camera there is a potential problem in the editor:
  219. // gizmos BEHIND the override camera will not be drawn.
  220. // As this should be editor only and performance is not critical, we will just disable
  221. // frustum culling for global objects when the camera is overridden.
  222. for (uint32_t n = 0; n < num_globals; n++) {
  223. const PortalRenderer::Moving &moving = _portal_renderer->get_moving_global(n);
  224. if (VSG::scene->_instance_cull_check(moving.instance, p_mask)) {
  225. p_result_array[current_result++] = moving.instance;
  226. // full up?
  227. if (current_result >= p_result_max) {
  228. return current_result;
  229. }
  230. }
  231. }
  232. } // if override camera
  233. return current_result;
  234. }
  235. void PortalTracer::trace_debug_sprawl_recursive(int p_depth, int p_room_id) {
  236. if (p_depth > 1) {
  237. return;
  238. }
  239. // prevent too much depth
  240. ERR_FAIL_COND_MSG(p_depth > 8, "Portal Depth Limit reached");
  241. // get the room
  242. const VSRoom &room = _portal_renderer->get_room(p_room_id);
  243. int num_portals = room._portal_ids.size();
  244. for (int p = 0; p < num_portals; p++) {
  245. const VSPortal &portal = _portal_renderer->get_portal(room._portal_ids[p]);
  246. if (!portal._active) {
  247. continue;
  248. }
  249. cull_statics_debug_sprawl(room);
  250. // everything depends on whether the portal is incoming or outgoing.
  251. int outgoing = 1;
  252. int room_a_id = portal._linkedroom_ID[0];
  253. if (room_a_id != p_room_id) {
  254. outgoing = 0;
  255. DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
  256. }
  257. // trace through this portal to the next room
  258. int linked_room_id = portal._linkedroom_ID[outgoing];
  259. if (linked_room_id != -1) {
  260. trace_debug_sprawl_recursive(p_depth + 1, linked_room_id);
  261. } // if a linked room exists
  262. } // for p through portals
  263. }
  264. void PortalTracer::trace_pvs(int p_source_room_id, const LocalVector<Plane> &p_planes) {
  265. const PVS &pvs = _portal_renderer->get_pvs();
  266. const VSRoom &source_room = _portal_renderer->get_room(p_source_room_id);
  267. for (int r = 0; r < source_room._pvs_size; r++) {
  268. int room_id = pvs.get_pvs_room_id(source_room._pvs_first + r);
  269. // get the room
  270. const VSRoom &room = _portal_renderer->get_room(room_id);
  271. cull_statics(room, p_planes);
  272. cull_roamers(room, p_planes);
  273. }
  274. }
  275. void PortalTracer::trace_recursive(const TraceParams &p_params, int p_depth, int p_room_id, const LocalVector<Plane> &p_planes, int p_from_external_room_id) {
  276. // Prevent too much depth.
  277. if (p_depth > _depth_limit) {
  278. WARN_PRINT_ONCE("Portal Depth Limit reached (seeing through too many portals)");
  279. return;
  280. }
  281. // Get the room.
  282. const VSRoom &room = _portal_renderer->get_room(p_room_id);
  283. // Set up the occlusion culler as a one off.
  284. _occlusion_culler.prepare(*_portal_renderer, room, _trace_start_point, p_planes, &_near_and_far_planes[0]);
  285. cull_statics(room, p_planes);
  286. cull_roamers(room, p_planes);
  287. int num_portals = room._portal_ids.size();
  288. for (int p = 0; p < num_portals; p++) {
  289. const VSPortal &portal = _portal_renderer->get_portal(room._portal_ids[p]);
  290. // Portals can be switched on and off at runtime, like opening and closing a door.
  291. if (!portal._active) {
  292. continue;
  293. }
  294. // Everything depends on whether the portal is incoming or outgoing.
  295. // If incoming we reverse the logic.
  296. int outgoing = 1;
  297. int room_a_id = portal._linkedroom_ID[0];
  298. if (room_a_id != p_room_id) {
  299. outgoing = 0;
  300. DEV_ASSERT(portal._linkedroom_ID[1] == p_room_id);
  301. }
  302. // Trace through this portal to the next room.
  303. int linked_room_id = portal._linkedroom_ID[outgoing];
  304. // Cull by PVS.
  305. if (p_params.use_pvs && (!p_params.decompressed_room_pvs[linked_room_id])) {
  306. continue;
  307. }
  308. // Cull by portal angle to camera.
  309. // Much better way of culling portals by direction to camera...
  310. // instead of using dot product with a varying view direction, we simply find which side of the portal
  311. // plane the camera is on! If it is behind, the portal can be seen through, if in front, it can't.
  312. // There is one exception to this, for the first source room. If we are in front of any portal in the first
  313. // source room, we will render EVERYTHING through it into the next room. This can happen due
  314. // to precision errors, or inaccuracy in setting up the portal planes relative to the room bounds -
  315. // in which case we can end up IN FRONT of a portal in the same room.
  316. bool start_room_in_front_portal_exception = false;
  317. /////////////////////////////////////////////
  318. real_t dist_cam = portal._plane.distance_to(_trace_start_point);
  319. if (!outgoing) {
  320. dist_cam = -dist_cam;
  321. }
  322. // If the camera is IN FRONT of the portal plane...
  323. if (dist_cam >= 0.0) {
  324. if ((p_room_id != p_params.start_room_id) || portal._internal) {
  325. continue;
  326. } else {
  327. start_room_in_front_portal_exception = true;
  328. }
  329. }
  330. /////////////////////////////////////////////
  331. // While clipping to the planes we maintain a list of partial planes, so we can add them to the
  332. // recursive next iteration of planes to check.
  333. static LocalVector<int> partial_planes;
  334. partial_planes.clear();
  335. // Is it culled by the planes?
  336. VSPortal::ClipResult overall_res = VSPortal::ClipResult::CLIP_INSIDE;
  337. if (!start_room_in_front_portal_exception) {
  338. // For portals, we want to ignore the near clipping plane, as we might be right on the edge of a doorway
  339. // and still want to look through the portal.
  340. // So earlier we have set it that the first plane (ASSUMING that plane zero is the near clipping plane)
  341. // starts from the camera position, and NOT the actual near clipping plane.
  342. // If we need quite a distant near plane, we may need a different strategy.
  343. for (uint32_t l = 0; l < p_planes.size(); l++) {
  344. VSPortal::ClipResult res = portal.clip_with_plane(p_planes[l]);
  345. switch (res) {
  346. case VSPortal::ClipResult::CLIP_OUTSIDE: {
  347. overall_res = res;
  348. } break;
  349. case VSPortal::ClipResult::CLIP_PARTIAL: {
  350. // If the portal intersects one of the planes, we should take this plane into account
  351. // in the next call of this recursive trace, because it can be used to cull out more objects.
  352. overall_res = res;
  353. partial_planes.push_back(l);
  354. } break;
  355. default: // Suppress warning.
  356. break;
  357. }
  358. // If the portal was totally outside the 'frustum' then we can ignore it.
  359. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE)
  360. break;
  361. }
  362. // This portal is culled.
  363. if (overall_res == VSPortal::ClipResult::CLIP_OUTSIDE) {
  364. continue;
  365. }
  366. }
  367. // Don't allow portals from internal to external room to be followed
  368. // if the external room has already been processed in this trace stack. This prevents
  369. // unneeded processing, and also prevents recursive feedback where you
  370. // see into internal room -> external room and back into the same internal room
  371. // via the same portal.
  372. if (portal._internal && (linked_room_id != -1)) {
  373. if (outgoing) {
  374. if (linked_room_id == p_from_external_room_id) {
  375. continue;
  376. }
  377. } else {
  378. // We are entering an internal portal from an external room.
  379. // set the external room id, so we can recognise this when we are
  380. // later exiting the internal rooms.
  381. // Note that as we can only store 1 previous external room, this system
  382. // won't work completely correctly when you have 2 levels of internal room
  383. // and you can see from roomgroup a -> b -> c. However this should just result
  384. // in a little slower culling for that particular view, and hopefully will not break
  385. // with recursive loop looking through the same portal multiple times. (don't think this
  386. // is possible in this scenario).
  387. p_from_external_room_id = p_room_id;
  388. }
  389. }
  390. // Occlusion culling of portals.
  391. if (_occlusion_culler.cull_sphere(portal._pt_center, portal._bounding_sphere_radius)) {
  392. continue;
  393. }
  394. // Hopefully the portal actually leads somewhere...
  395. if (linked_room_id != -1) {
  396. // We need some new planes.
  397. unsigned int pool_mem = _planes_pool.request();
  398. // If the planes pool is not empty, we got some planes, and can recurse.
  399. if (pool_mem != (unsigned int)-1) {
  400. // Get a new vector of planes from the pool.
  401. LocalVector<Plane> &new_planes = _planes_pool.get(pool_mem);
  402. // Makes sure there are none left over (as the pool may not clear them).
  403. new_planes.clear();
  404. if (!start_room_in_front_portal_exception) {
  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. // Note that this loses the far clipping plane .. but that shouldn't be important usually?
  408. // (maybe we might need to account for this in future .. look for issues)
  409. if (overall_res != VSPortal::ClipResult::CLIP_INSIDE) {
  410. // If it WASN'T totally inside the existing frustum, we also need to add any existing planes
  411. // that cut the portal.
  412. for (uint32_t n = 0; n < partial_planes.size(); n++) {
  413. new_planes.push_back(p_planes[partial_planes[n]]);
  414. }
  415. }
  416. // We will always add the portals planes. This could probably be optimized, as some
  417. // portal planes may be culled out by partial planes... NYI
  418. portal.add_planes(_trace_start_point, new_planes, outgoing != 0);
  419. // Always add the far plane. It is likely the portal is inside the far plane,
  420. // but it is still needed in future for culling portals and objects.
  421. // Note that there is a small possibility of far plane being added twice here
  422. // in some situations, but I don't think it should be a problem.
  423. // The fake near plane BTW is almost never added (otherwise it would prematurely
  424. // break traversal through the portals), so near clipping must be done
  425. // explicitly on objects.
  426. new_planes.push_back(_near_and_far_planes[1]);
  427. } else {
  428. // start_room_in_front_portal_exception
  429. // Copy the existing planes and reuse when tracing into the next room.
  430. new_planes = p_planes;
  431. }
  432. // Go and do the whole lot again in the next room...
  433. trace_recursive(p_params, p_depth + 1, linked_room_id, new_planes, p_from_external_room_id);
  434. // We no longer need these planes, return them to the pool.
  435. _planes_pool.free(pool_mem);
  436. } // pool mem allocated
  437. else {
  438. // Planes pool is empty!
  439. // This will happen if the view goes through shedloads of portals.
  440. // The solution is either to increase the plane pool size, or not build levels
  441. // with views through multiple portals. Looking through multiple portals is likely to be
  442. // slow anyway because of the number of planes to test.
  443. WARN_PRINT_ONCE("planes pool is empty");
  444. // Note we also have a depth check at the top of this function. Which will probably get hit
  445. // before the pool gets empty.
  446. }
  447. } // if a linked room exists
  448. } // for p through portals
  449. }
  450. int PortalTracer::occlusion_cull(PortalRenderer &p_portal_renderer, const Vector3 &p_point, const Vector3 &p_cam_dir, const CameraMatrix &p_cam_matrix, const Vector<Plane> &p_convex, VSInstance **p_result_array, int p_num_results) {
  451. _occlusion_culler.prepare_camera(p_cam_matrix, p_cam_dir);
  452. // silly conversion of vector to local vector
  453. // can this be avoided? NYI
  454. // pretty cheap anyway as it will just copy 6 planes, max a few times per frame...
  455. static LocalVector<Plane> local_planes;
  456. if ((int)local_planes.size() != p_convex.size()) {
  457. local_planes.resize(p_convex.size());
  458. }
  459. for (int n = 0; n < p_convex.size(); n++) {
  460. local_planes[n] = p_convex[n];
  461. }
  462. _occlusion_culler.prepare_generic(p_portal_renderer, p_portal_renderer.get_occluders_active_list(), p_point, local_planes);
  463. // cull each instance
  464. int count = p_num_results;
  465. AABB bb;
  466. for (int n = 0; n < count; n++) {
  467. VSInstance *instance = p_result_array[n];
  468. // this will return false for GLOBAL instances, so we don't occlusion cull gizmos
  469. if (VSG::scene->_instance_get_transformed_aabb_for_occlusion(instance, bb)) {
  470. if (_occlusion_culler.cull_aabb(bb)) {
  471. // remove from list with unordered swap from the end of list
  472. p_result_array[n] = p_result_array[count - 1];
  473. count--;
  474. n--; // repeat this element, as it will have changed
  475. }
  476. }
  477. }
  478. return count;
  479. }