sceneCullingState.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "scene/culling/sceneCullingState.h"
  24. #include "scene/sceneManager.h"
  25. #include "scene/sceneObject.h"
  26. #include "scene/zones/sceneZoneSpace.h"
  27. #include "math/mathUtils.h"
  28. #include "platform/profiler.h"
  29. #include "terrain/terrData.h"
  30. #include "util/tempAlloc.h"
  31. #include "gfx/sim/debugDraw.h"
  32. extern bool gEditingMission;
  33. bool SceneCullingState::smDisableTerrainOcclusion = true;
  34. bool SceneCullingState::smDisableZoneCulling = false;
  35. U32 SceneCullingState::smMaxOccludersPerZone = 4;
  36. F32 SceneCullingState::smOccluderMinWidthPercentage = 0.1f;
  37. F32 SceneCullingState::smOccluderMinHeightPercentage = 0.1f;
  38. //-----------------------------------------------------------------------------
  39. SceneCullingState::SceneCullingState( SceneManager* sceneManager, const SceneCameraState& viewState )
  40. : mSceneManager( sceneManager ),
  41. mCameraState( viewState ),
  42. mDisableTerrainOcclusion( smDisableTerrainOcclusion ),
  43. mDisableZoneCulling( smDisableZoneCulling )
  44. {
  45. AssertFatal( sceneManager->getZoneManager(), "SceneCullingState::SceneCullingState - SceneManager must have a zone manager!" );
  46. VECTOR_SET_ASSOCIATION( mZoneStates );
  47. VECTOR_SET_ASSOCIATION( mAddedOccluderObjects );
  48. // Allocate zone states.
  49. const U32 numZones = sceneManager->getZoneManager()->getNumZones();
  50. mZoneStates.setSize( numZones );
  51. dMemset( mZoneStates.address(), 0, sizeof( SceneZoneCullingState ) * numZones );
  52. // Allocate the zone visibility flags.
  53. mZoneVisibilityFlags.setSize( numZones );
  54. mZoneVisibilityFlags.clear();
  55. // Culling frustum
  56. mCullingFrustum = mCameraState.getFrustum();
  57. mCullingFrustum.bakeProjectionOffset();
  58. // Construct the root culling volume from
  59. // the culling frustum. Omit the frustum's
  60. // near and far plane so we don't test it repeatedly.
  61. PlaneF* planes = allocateData< PlaneF >( 4 );
  62. planes[ 0 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneLeft ];
  63. planes[ 1 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneRight ];
  64. planes[ 2 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneTop];
  65. planes[ 3 ] = mCullingFrustum.getPlanes()[ Frustum::PlaneBottom ];
  66. mRootVolume = SceneCullingVolume(
  67. SceneCullingVolume::Includer,
  68. PlaneSetF( planes, 4 )
  69. );
  70. clearExtraPlanesCull();
  71. }
  72. //-----------------------------------------------------------------------------
  73. bool SceneCullingState::isWithinVisibleZone( SceneObject* object ) const
  74. {
  75. SceneManager* mgr = object->getSceneManager();
  76. SceneZoneSpaceManager* zm = mgr->getZoneManager();
  77. U32 numZones = 0;
  78. U32* zones = NULL;
  79. SceneZoneSpaceManager::ObjectZoneValueIterator itr, itrEnd;
  80. zm->getObjectZoneValueIterators(object, itr, itrEnd);
  81. for (itr; itr != itrEnd; itr++)
  82. {
  83. if (mZoneVisibilityFlags.test(*itr))
  84. return true;
  85. }
  86. return false;
  87. }
  88. //-----------------------------------------------------------------------------
  89. void SceneCullingState::addOccluder( SceneObject* object )
  90. {
  91. PROFILE_SCOPE( SceneCullingState_addOccluder );
  92. // If the occluder is itself occluded, don't add it.
  93. //
  94. // NOTE: We do allow near plane intersections here. Silhouette extraction
  95. // should take that into account.
  96. if( cullObjects( &object, 1, DontCullRenderDisabled ) != 1 )
  97. return;
  98. // If the occluder has already been added, do nothing. Check this
  99. // after the culling check since the same occluder can be added by
  100. // two separate zones and not be visible in one yet be visible in the
  101. // other.
  102. if( mAddedOccluderObjects.contains( object ) )
  103. return;
  104. mAddedOccluderObjects.push_back( object );
  105. // Let the object build a silhouette. If it doesn't
  106. // return one, abort.
  107. Vector< Point3F > silhouette;
  108. object->buildSilhouette( getCameraState(), silhouette );
  109. if( silhouette.empty() || silhouette.size() < 3 )
  110. return;
  111. // Generate the culling volume.
  112. SceneCullingVolume volume;
  113. if( !createCullingVolume(
  114. silhouette.address(),
  115. silhouette.size(),
  116. SceneCullingVolume::Occluder,
  117. volume ) )
  118. return;
  119. // Add the frustum to all zones that the object is assigned to.
  120. U32 numZones = 0;
  121. U32* zones = NULL;
  122. SceneManager* sm = object->getSceneManager();
  123. SceneZoneSpaceManager* zm = sm->getZoneManager();
  124. SceneZoneSpaceManager::ObjectZoneValueIterator itr, itrEnd;
  125. zm->getObjectZoneValueIterators(object, itr, itrEnd);
  126. for (itr; itr != itrEnd; itr++)
  127. {
  128. addCullingVolumeToZone(*itr, volume);
  129. }
  130. }
  131. //-----------------------------------------------------------------------------
  132. bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, const SceneCullingVolume& volume )
  133. {
  134. PROFILE_SCOPE( SceneCullingState_addCullingVolumeToZone );
  135. AssertFatal( zoneId < mZoneStates.size(), "SceneCullingState::addCullingVolumeToZone - Zone ID out of range" );
  136. SceneZoneCullingState& zoneState = mZoneStates[ zoneId ];
  137. // [rene, 07-Apr-10] I previously used to attempt to merge things here and detect whether
  138. // the visibility state of the zone has changed at all. Since we allow polyhedra to be
  139. // degenerate here and since polyhedra cannot be merged easily like frustums, I have opted
  140. // to remove this for now. I'm also convinced that with the current traversal system it
  141. // adds little benefit.
  142. // Link the volume to the zone state.
  143. typedef SceneZoneCullingState::CullingVolumeLink LinkType;
  144. LinkType* link = reinterpret_cast< LinkType* >( allocateData( sizeof( LinkType ) ) );
  145. link->mVolume = volume;
  146. link->mNext = zoneState.mCullingVolumes;
  147. zoneState.mCullingVolumes = link;
  148. if( volume.isOccluder() )
  149. zoneState.mHaveOccluders = true;
  150. else
  151. zoneState.mHaveIncluders = true;
  152. // Mark sorting state as dirty.
  153. zoneState.mHaveSortedVolumes = false;
  154. // Set the visibility flag for the zone.
  155. if( volume.isIncluder() )
  156. mZoneVisibilityFlags.set( zoneId );
  157. return true;
  158. }
  159. //-----------------------------------------------------------------------------
  160. bool SceneCullingState::addCullingVolumeToZone( U32 zoneId, SceneCullingVolume::Type type, const AnyPolyhedron& polyhedron )
  161. {
  162. // Allocate space on our chunker.
  163. const U32 numPlanes = polyhedron.getNumPlanes();
  164. PlaneF* planes = allocateData< PlaneF >( numPlanes );
  165. // Copy the planes over.
  166. dMemcpy( planes, polyhedron.getPlanes(), numPlanes * sizeof( planes[ 0 ] ) );
  167. // Create a culling volume.
  168. SceneCullingVolume volume(
  169. type,
  170. PlaneSetF( planes, numPlanes )
  171. );
  172. // And add it.
  173. return addCullingVolumeToZone( zoneId, volume );
  174. }
  175. //-----------------------------------------------------------------------------
  176. bool SceneCullingState::createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume )
  177. {
  178. const Point3F& viewPos = getCameraState().getViewPosition();
  179. const Point3F& viewDir = getCameraState().getViewDirection();
  180. const bool isOrtho = getCullingFrustum().isOrtho();
  181. //TODO: check if we need to handle penetration of the near plane for occluders specially
  182. // Allocate space for the clipping planes we generate. Assume the worst case
  183. // of every edge generating a plane and, for includers, all edges meeting at
  184. // steep angles so we need to insert extra planes (the latter is not possible,
  185. // of course, but it makes things less complicated here). For occluders, add
  186. // an extra plane for the near cap.
  187. const U32 maxPlanes = ( type == SceneCullingVolume::Occluder ? numVertices + 1 : numVertices * 2 );
  188. PlaneF* planes = allocateData< PlaneF >( maxPlanes );
  189. // Keep track of the world-space bounds of the polygon. We use this later
  190. // to derive some metrics.
  191. Box3F wsPolyBounds;
  192. wsPolyBounds.minExtents = Point3F( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX );
  193. wsPolyBounds.maxExtents = Point3F( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN );
  194. // For occluders, also keep track of the nearest, and two farthest silhouette points. We use
  195. // this later to construct a near capping plane.
  196. F32 minVertexDistanceSquared = TypeTraits< F32 >::MAX;
  197. U32 leastDistantVert = 0;
  198. F32 maxVertexDistancesSquared[ 2 ] = { TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN };
  199. U32 mostDistantVertices[ 2 ] = { 0, 0 };
  200. // Generate the extrusion volume. For orthographic projections, extrude
  201. // parallel to the view direction whereas for parallel projections, extrude
  202. // from the viewpoint.
  203. U32 numPlanes = 0;
  204. U32 lastVertex = numVertices - 1;
  205. bool invert = false;
  206. for( U32 i = 0; i < numVertices; lastVertex = i, ++ i )
  207. {
  208. AssertFatal( numPlanes < maxPlanes, "SceneCullingState::createCullingVolume - Did not allocate enough planes!" );
  209. const Point3F& v1 = vertices[ i ];
  210. const Point3F& v2 = vertices[ lastVertex ];
  211. // Keep track of bounds.
  212. wsPolyBounds.minExtents.setMin( v1 );
  213. wsPolyBounds.maxExtents.setMax( v1 );
  214. // Skip the edge if it's length is really short.
  215. const Point3F edgeVector = v2 - v1;
  216. const F32 edgeVectorLenSquared = edgeVector.lenSquared();
  217. if( edgeVectorLenSquared < 0.025f )
  218. continue;
  219. //TODO: might need to do additional checks here for non-planar polygons used by occluders
  220. //TODO: test for colinearity of edge vector with view vector (occluders only)
  221. // Create a plane for the edge.
  222. if( isOrtho )
  223. {
  224. // Compute a plane through the two edge vertices and one
  225. // of the vertices extended along the view direction.
  226. if( !invert )
  227. planes[ numPlanes ] = PlaneF( v1, v1 + viewDir, v2 );
  228. else
  229. planes[ numPlanes ] = PlaneF( v2, v1 + viewDir, v1 );
  230. }
  231. else
  232. {
  233. // Compute a plane going through the viewpoint and the two
  234. // edge vertices.
  235. if( !invert )
  236. planes[ numPlanes ] = PlaneF( v1, viewPos, v2 );
  237. else
  238. planes[ numPlanes ] = PlaneF( v2, viewPos, v1 );
  239. }
  240. numPlanes ++;
  241. // If this is the first plane that we have created, find out whether
  242. // the vertex ordering is giving us the plane orientations that we want
  243. // (facing inside). If not, invert vertex order from now on.
  244. if( numPlanes == 1 )
  245. {
  246. Point3F center( 0, 0, 0 );
  247. for( U32 n = 0; n < numVertices; ++ n )
  248. center += vertices[n];
  249. center /= numVertices;
  250. if( planes[numPlanes - 1].whichSide( center ) == PlaneF::Back )
  251. {
  252. invert = true;
  253. planes[ numPlanes - 1 ].invert();
  254. }
  255. }
  256. // For occluders, keep tabs of the nearest, and two farthest vertices.
  257. if( type == SceneCullingVolume::Occluder )
  258. {
  259. const F32 distSquared = ( v1 - viewPos ).lenSquared();
  260. if( distSquared < minVertexDistanceSquared )
  261. {
  262. minVertexDistanceSquared = distSquared;
  263. leastDistantVert = i;
  264. }
  265. if( distSquared > maxVertexDistancesSquared[ 0 ] )
  266. {
  267. // Move 0 to 1.
  268. maxVertexDistancesSquared[ 1 ] = maxVertexDistancesSquared[ 0 ];
  269. mostDistantVertices[ 1 ] = mostDistantVertices[ 0 ];
  270. // Replace 0.
  271. maxVertexDistancesSquared[ 0 ] = distSquared;
  272. mostDistantVertices[ 0 ] = i;
  273. }
  274. else if( distSquared > maxVertexDistancesSquared[ 1 ] )
  275. {
  276. // Replace 1.
  277. maxVertexDistancesSquared[ 1 ] = distSquared;
  278. mostDistantVertices[ 1 ] = i;
  279. }
  280. }
  281. }
  282. // If the extrusion produced no useful result, abort.
  283. if( numPlanes < 3 )
  284. return false;
  285. // For includers, test the angle of the edges at the current vertex.
  286. // If too steep, add an extra plane to improve culling efficiency.
  287. if( false )//type == SceneCullingVolume::Includer )
  288. {
  289. const U32 numOriginalPlanes = numPlanes;
  290. U32 lastPlaneIndex = numPlanes - 1;
  291. for( U32 i = 0; i < numOriginalPlanes; lastPlaneIndex = i, ++ i )
  292. {
  293. const PlaneF& currentPlane = planes[ i ];
  294. const PlaneF& lastPlane = planes[ lastPlaneIndex ];
  295. // Compute the cosine of the angle between the two plane normals.
  296. const F32 cosAngle = mFabs( mDot( currentPlane, lastPlane ) );
  297. // The planes meet at increasingly steep angles the more they point
  298. // in opposite directions, i.e the closer the angle of their normals
  299. // is to 180 degrees. Skip any two planes that don't get near that.
  300. if( cosAngle > 0.1f )
  301. continue;
  302. Point3F newNormal = currentPlane + lastPlane;//addNormals - mDot( addNormals, crossNormals ) * crossNormals;
  303. //
  304. planes[ numPlanes ] = PlaneF( currentPlane.getPosition(), newNormal );
  305. numPlanes ++;
  306. }
  307. }
  308. // Compute the metrics of the culling volume in relation to the view frustum.
  309. //
  310. // For this, we are short-circuiting things slightly. The correct way (other than doing
  311. // full screen projections) would be to transform all the polygon points into camera
  312. // space, lay an AABB around those points, and then find the X and Z extents on the near plane.
  313. //
  314. // However, while not as accurate, a faster way is to just project the axial vectors
  315. // of the bounding box onto both the camera right and up vector. This gives us a rough
  316. // estimate of the camera-space size of the polygon we're looking at.
  317. const MatrixF& cameraTransform = getCameraState().getViewWorldMatrix();
  318. const Point3F cameraRight = cameraTransform.getRightVector();
  319. const Point3F cameraUp = cameraTransform.getUpVector();
  320. const Point3F wsPolyBoundsExtents = wsPolyBounds.getExtents();
  321. F32 widthEstimate =
  322. getMax( mFabs( wsPolyBoundsExtents.x * cameraRight.x ),
  323. getMax( mFabs( wsPolyBoundsExtents.y * cameraRight.y ),
  324. mFabs( wsPolyBoundsExtents.z * cameraRight.z ) ) );
  325. F32 heightEstimate =
  326. getMax( mFabs( wsPolyBoundsExtents.x * cameraUp.x ),
  327. getMax( mFabs( wsPolyBoundsExtents.y * cameraUp.y ),
  328. mFabs( wsPolyBoundsExtents.z * cameraUp.z ) ) );
  329. // If the current camera is a perspective one, divide the two estimates
  330. // by the distance of the nearest bounding box vertex to the camera
  331. // to account for perspective distortion.
  332. if( !isOrtho )
  333. {
  334. const Point3F nearestVertex = wsPolyBounds.computeVertex(
  335. Box3F::getPointIndexFromOctant( - viewDir )
  336. );
  337. const F32 distance = ( nearestVertex - viewPos ).len();
  338. widthEstimate /= distance;
  339. heightEstimate /= distance;
  340. }
  341. // If we are creating an occluder, check to see if the estimates fit
  342. // our minimum requirements.
  343. if( type == SceneCullingVolume::Occluder )
  344. {
  345. const F32 widthEstimatePercentage = widthEstimate / getCullingFrustum().getWidth();
  346. const F32 heightEstimatePercentage = heightEstimate / getCullingFrustum().getHeight();
  347. if( widthEstimatePercentage < smOccluderMinWidthPercentage ||
  348. heightEstimatePercentage < smOccluderMinHeightPercentage )
  349. return false; // Reject.
  350. }
  351. // Use the area estimate as the volume's sort point.
  352. const F32 sortPoint = widthEstimate * heightEstimate;
  353. // Finally, if it's an occluder, compute a near cap. The near cap prevents objects
  354. // in front of the occluder from testing positive. The same could be achieved by
  355. // manually comparing distances before testing objects but since that would amount
  356. // to the same checks the plane/AABB tests do, it's easier to just add another plane.
  357. // Additionally, it gives the benefit of being able to create more precise culling
  358. // results by angling the plane.
  359. //NOTE: Could consider adding a near cap for includers too when generating a volume
  360. // for the outdoor zone as that may prevent quite a bit of space from being included.
  361. // However, given that this space will most likely just be filled with interior
  362. // stuff anyway, it's probably not worth it.
  363. if( type == SceneCullingVolume::Occluder )
  364. {
  365. const U32 nearCapIndex = numPlanes;
  366. planes[ nearCapIndex ] = PlaneF(
  367. vertices[ mostDistantVertices[ 0 ] ],
  368. vertices[ mostDistantVertices[ 1 ] ],
  369. vertices[ leastDistantVert ] );
  370. // Invert the plane, if necessary.
  371. if( planes[ nearCapIndex ].whichSide( viewPos ) == PlaneF::Front )
  372. planes[ nearCapIndex ].invert();
  373. numPlanes ++;
  374. }
  375. // Create the volume from the planes.
  376. outVolume = SceneCullingVolume(
  377. type,
  378. PlaneSetF( planes, numPlanes )
  379. );
  380. outVolume.setSortPoint( sortPoint );
  381. // Done.
  382. return true;
  383. }
  384. //-----------------------------------------------------------------------------
  385. namespace {
  386. struct ZoneArrayIterator
  387. {
  388. U32 mCurrent;
  389. U32 mNumZones;
  390. const U32* mZones;
  391. ZoneArrayIterator( const U32* zones, U32 numZones )
  392. : mCurrent( 0 ),
  393. mNumZones( numZones ),
  394. mZones( zones ) {}
  395. bool isValid() const
  396. {
  397. return ( mCurrent < mNumZones );
  398. }
  399. ZoneArrayIterator& operator ++()
  400. {
  401. mCurrent ++;
  402. return *this;
  403. }
  404. U32 operator *() const
  405. {
  406. return mZones[ mCurrent ];
  407. }
  408. };
  409. }
  410. template< typename T, typename Iter >
  411. inline SceneZoneCullingState::CullingTestResult SceneCullingState::_testOccludersOnly( const T& bounds, Iter zoneIter ) const
  412. {
  413. // Test the culling states of all zones that the object
  414. // is assigned to.
  415. for( ; zoneIter.isValid(); ++ zoneIter )
  416. {
  417. const SceneZoneCullingState& zoneState = getZoneState( *zoneIter );
  418. // Skip zone if there are no occluders.
  419. if( !zoneState.hasOccluders() )
  420. continue;
  421. // If the object's world bounds overlaps any of the volumes
  422. // for this zone, it's rendered.
  423. if( zoneState.testVolumes( bounds, true ) == SceneZoneCullingState::CullingTestPositiveByOcclusion )
  424. return SceneZoneCullingState::CullingTestPositiveByOcclusion;
  425. }
  426. return SceneZoneCullingState::CullingTestNegative;
  427. }
  428. template< typename T, typename Iter >
  429. inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, Iter zoneIter,
  430. const PlaneF& nearPlane, const PlaneF& farPlane ) const
  431. {
  432. // Defer test of near and far plane until we've hit a zone
  433. // which actually has visible space. This prevents us from
  434. // doing near/far tests on objects that were included in the
  435. // potential render list but aren't actually in any visible
  436. // zone.
  437. bool haveTestedNearAndFar = false;
  438. // Test the culling states of all zones that the object
  439. // is assigned to.
  440. for( ; zoneIter.isValid(); ++ zoneIter )
  441. {
  442. const SceneZoneCullingState& zoneState = getZoneState( *zoneIter );
  443. // Skip zone if there are no positive culling volumes.
  444. if( !zoneState.hasIncluders() )
  445. continue;
  446. // If we haven't tested the near and far plane yet, do so
  447. // now.
  448. if( !haveTestedNearAndFar )
  449. {
  450. // Test near plane.
  451. PlaneF::Side nearSide = nearPlane.whichSide( bounds );
  452. if( nearSide == PlaneF::Back )
  453. return SceneZoneCullingState::CullingTestNegative;
  454. // Test far plane.
  455. PlaneF::Side farSide = farPlane.whichSide( bounds );
  456. if( farSide == PlaneF::Back )
  457. return SceneZoneCullingState::CullingTestNegative;
  458. haveTestedNearAndFar = true;
  459. }
  460. // If the object's world bounds overlaps any of the volumes
  461. // for this zone, it's rendered.
  462. SceneZoneCullingState::CullingTestResult result = zoneState.testVolumes( bounds );
  463. if( result == SceneZoneCullingState::CullingTestPositiveByInclusion )
  464. return result;
  465. else if( result == SceneZoneCullingState::CullingTestPositiveByOcclusion )
  466. return result;
  467. }
  468. return SceneZoneCullingState::CullingTestNegative;
  469. }
  470. //-----------------------------------------------------------------------------
  471. template< bool OCCLUDERS_ONLY, typename T >
  472. inline SceneZoneCullingState::CullingTestResult SceneCullingState::_test( const T& bounds, const U32* zones, U32 numZones ) const
  473. {
  474. // If zone culling is disabled, only test against
  475. // the root frustum.
  476. if( disableZoneCulling() )
  477. {
  478. if( !OCCLUDERS_ONLY && !getCullingFrustum().isCulled( bounds ) )
  479. return SceneZoneCullingState::CullingTestPositiveByInclusion;
  480. return SceneZoneCullingState::CullingTestNegative;
  481. }
  482. // Otherwise test each of the zones.
  483. if( OCCLUDERS_ONLY )
  484. {
  485. return _testOccludersOnly(
  486. bounds,
  487. ZoneArrayIterator( zones, numZones )
  488. );
  489. }
  490. else
  491. {
  492. const PlaneF* frustumPlanes = getCullingFrustum().getPlanes();
  493. return _test(
  494. bounds,
  495. ZoneArrayIterator( zones, numZones ),
  496. frustumPlanes[ Frustum::PlaneNear ],
  497. frustumPlanes[ Frustum::PlaneFar ]
  498. );
  499. }
  500. }
  501. //-----------------------------------------------------------------------------
  502. bool SceneCullingState::isCulled( const Box3F& aabb, const U32* zones, U32 numZones ) const
  503. {
  504. SceneZoneCullingState::CullingTestResult result = _test< false >( aabb, zones, numZones );
  505. return ( result == SceneZoneCullingState::CullingTestNegative ||
  506. result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  507. }
  508. //-----------------------------------------------------------------------------
  509. bool SceneCullingState::isCulled( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const
  510. {
  511. SceneZoneCullingState::CullingTestResult result = _test< false >( obb, zones, numZones );
  512. return ( result == SceneZoneCullingState::CullingTestNegative ||
  513. result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  514. }
  515. //-----------------------------------------------------------------------------
  516. bool SceneCullingState::isCulled( const SphereF& sphere, const U32* zones, U32 numZones ) const
  517. {
  518. SceneZoneCullingState::CullingTestResult result = _test< false >( sphere, zones, numZones );
  519. return ( result == SceneZoneCullingState::CullingTestNegative ||
  520. result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  521. }
  522. //-----------------------------------------------------------------------------
  523. bool SceneCullingState::isOccluded( SceneObject* object ) const
  524. {
  525. if( disableZoneCulling() )
  526. return false;
  527. CullingTestResult result = _testOccludersOnly(
  528. object->getWorldBox(),
  529. mSceneManager->getZoneManager()->makeObjectZoneValueIterator(object)
  530. );
  531. return ( result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  532. }
  533. //-----------------------------------------------------------------------------
  534. bool SceneCullingState::isOccluded( const Box3F& aabb, const U32* zones, U32 numZones ) const
  535. {
  536. return ( _test< true >( aabb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  537. }
  538. //-----------------------------------------------------------------------------
  539. bool SceneCullingState::isOccluded( const OrientedBox3F& obb, const U32* zones, U32 numZones ) const
  540. {
  541. return ( _test< true >( obb, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  542. }
  543. //-----------------------------------------------------------------------------
  544. bool SceneCullingState::isOccluded( const SphereF& sphere, const U32* zones, U32 numZones ) const
  545. {
  546. return ( _test< true >( sphere, zones, numZones ) == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  547. }
  548. //-----------------------------------------------------------------------------
  549. U32 SceneCullingState::cullObjects( SceneObject** objects, U32 numObjects, U32 cullOptions ) const
  550. {
  551. PROFILE_SCOPE( SceneCullingState_cullObjects );
  552. U32 numRemainingObjects = 0;
  553. // We test near and far planes separately in order to not do the tests
  554. // repeatedly, so fetch the planes now.
  555. const PlaneF& nearPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneNear ];
  556. const PlaneF& farPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneFar ];
  557. SceneZoneSpaceManager* zoneMgr = mSceneManager->getZoneManager();
  558. for( U32 i = 0; i < numObjects; ++ i )
  559. {
  560. SceneObject* object = objects[ i ];
  561. bool isCulled = true;
  562. // If we should respect editor overrides, test that now.
  563. if( !( cullOptions & CullEditorOverrides ) &&
  564. gEditingMission &&
  565. ( ( object->isCullingDisabledInEditor() && object->isRenderEnabled() ) || object->isSelected() ) )
  566. {
  567. isCulled = false;
  568. }
  569. // If the object is render-disabled, it gets culled. The only
  570. // way around this is the editor override above.
  571. else if( !( cullOptions & DontCullRenderDisabled ) &&
  572. !object->isRenderEnabled() )
  573. {
  574. isCulled = true;
  575. }
  576. // Global bounds objects are never culled. Note that this means
  577. // that if these objects are to respect zoning, they need to manually
  578. // trigger the respective culling checks for whatever they want to
  579. // batch.
  580. else if( object->isGlobalBounds() )
  581. isCulled = false;
  582. // If terrain occlusion checks are enabled, run them now.
  583. else if( !mDisableTerrainOcclusion &&
  584. object->getWorldBox().minExtents.x > -1e5 &&
  585. isOccludedByTerrain( object ) )
  586. {
  587. // Occluded by terrain.
  588. isCulled = true;
  589. }
  590. // If the object shouldn't be subjected to more fine-grained culling
  591. // or if zone culling is disabled, just test against the root frustum.
  592. else if( !( object->getTypeMask() & CULLING_INCLUDE_TYPEMASK ) ||
  593. ( object->getTypeMask() & CULLING_EXCLUDE_TYPEMASK ) ||
  594. disableZoneCulling() )
  595. {
  596. isCulled = getCullingFrustum().isCulled( object->getWorldBox() );
  597. }
  598. // Go through the zones that the object is assigned to and
  599. // test the object against the frustums of each of the zones.
  600. else
  601. {
  602. CullingTestResult result = _test(
  603. object->getWorldBox(),
  604. zoneMgr->makeObjectZoneValueIterator( object ),
  605. nearPlane,
  606. farPlane
  607. );
  608. isCulled = ( result == SceneZoneCullingState::CullingTestNegative ||
  609. result == SceneZoneCullingState::CullingTestPositiveByOcclusion );
  610. }
  611. if( !isCulled )
  612. isCulled = isOccludedWithExtraPlanesCull( object->getWorldBox() );
  613. if( !isCulled )
  614. objects[ numRemainingObjects ++ ] = object;
  615. }
  616. return numRemainingObjects;
  617. }
  618. //-----------------------------------------------------------------------------
  619. bool SceneCullingState::isOccludedByTerrain( SceneObject* object ) const
  620. {
  621. PROFILE_SCOPE( SceneCullingState_isOccludedByTerrain );
  622. // Don't try to occlude globally bounded objects.
  623. if( object->isGlobalBounds() )
  624. return false;
  625. const Vector< SceneObject* >& terrains = getSceneManager()->getContainer()->getTerrains();
  626. const U32 numTerrains = terrains.size();
  627. for( U32 terrainIdx = 0; terrainIdx < numTerrains; ++ terrainIdx )
  628. {
  629. TerrainBlock* terrain = dynamic_cast< TerrainBlock* >( terrains[ terrainIdx ] );
  630. if( !terrain )
  631. continue;
  632. MatrixF terrWorldTransform = terrain->getWorldTransform();
  633. Point3F localCamPos = getCameraState().getViewPosition();
  634. terrWorldTransform.mulP(localCamPos);
  635. F32 height;
  636. terrain->getHeight( Point2F( localCamPos.x, localCamPos.y ), &height );
  637. bool aboveTerrain = ( height <= localCamPos.z );
  638. // Don't occlude if we're below the terrain. This prevents problems when
  639. // looking out from underground bases...
  640. if( !aboveTerrain )
  641. continue;
  642. const Box3F& oBox = object->getObjBox();
  643. F32 minSide = getMin(oBox.len_x(), oBox.len_y());
  644. if (minSide > 85.0f)
  645. continue;
  646. const Box3F& rBox = object->getWorldBox();
  647. Point3F ul(rBox.minExtents.x, rBox.minExtents.y, rBox.maxExtents.z);
  648. Point3F ur(rBox.minExtents.x, rBox.maxExtents.y, rBox.maxExtents.z);
  649. Point3F ll(rBox.maxExtents.x, rBox.minExtents.y, rBox.maxExtents.z);
  650. Point3F lr(rBox.maxExtents.x, rBox.maxExtents.y, rBox.maxExtents.z);
  651. terrWorldTransform.mulP(ul);
  652. terrWorldTransform.mulP(ur);
  653. terrWorldTransform.mulP(ll);
  654. terrWorldTransform.mulP(lr);
  655. Point3F xBaseL0_s = ul - localCamPos;
  656. Point3F xBaseL0_e = lr - localCamPos;
  657. Point3F xBaseL1_s = ur - localCamPos;
  658. Point3F xBaseL1_e = ll - localCamPos;
  659. static F32 checkPoints[3] = {0.75, 0.5, 0.25};
  660. RayInfo rinfo;
  661. for( U32 i = 0; i < 3; i ++ )
  662. {
  663. Point3F start = (xBaseL0_s * checkPoints[i]) + localCamPos;
  664. Point3F end = (xBaseL0_e * checkPoints[i]) + localCamPos;
  665. if (terrain->castRay(start, end, &rinfo))
  666. continue;
  667. terrain->getHeight(Point2F(start.x, start.y), &height);
  668. if ((height <= start.z) == aboveTerrain)
  669. continue;
  670. start = (xBaseL1_s * checkPoints[i]) + localCamPos;
  671. end = (xBaseL1_e * checkPoints[i]) + localCamPos;
  672. if (terrain->castRay(start, end, &rinfo))
  673. continue;
  674. Point3F test = (start + end) * 0.5;
  675. if (terrain->castRay(localCamPos, test, &rinfo) == false)
  676. continue;
  677. return true;
  678. }
  679. }
  680. return false;
  681. }
  682. //-----------------------------------------------------------------------------
  683. void SceneCullingState::debugRenderCullingVolumes() const
  684. {
  685. const ColorI occluderColor( 255, 0, 0, 255 );
  686. const ColorI includerColor( 0, 255, 0, 255 );
  687. const PlaneF& nearPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneNear ];
  688. const PlaneF& farPlane = getCullingFrustum().getPlanes()[ Frustum::PlaneFar ];
  689. DebugDrawer* drawer = DebugDrawer::get();
  690. const SceneZoneSpaceManager* zoneManager = mSceneManager->getZoneManager();
  691. bool haveDebugZone = false;
  692. const U32 numZones = mZoneStates.size();
  693. for( S32 zoneId = numZones - 1; zoneId >= 0; -- zoneId )
  694. {
  695. if( !zoneManager->isValidZoneId( zoneId ) )
  696. continue;
  697. const SceneZoneCullingState& zoneState = mZoneStates[ zoneId ];
  698. if( !zoneManager->getZoneOwner( zoneId )->isSelected() && ( zoneId != SceneZoneSpaceManager::RootZoneId || haveDebugZone ) )
  699. continue;
  700. haveDebugZone = true;
  701. for( SceneZoneCullingState::CullingVolumeIterator iter( zoneState );
  702. iter.isValid(); ++ iter )
  703. {
  704. // Temporarily add near and far plane to culling volume so that
  705. // no matter how it is defined, it has a chance of being properly
  706. // capped.
  707. const U32 numPlanes = iter->getPlanes().getNumPlanes();
  708. const PlaneF* planes = iter->getPlanes().getPlanes();
  709. TempAlloc< PlaneF > tempPlanes( numPlanes + 2 );
  710. tempPlanes[ 0 ] = nearPlane;
  711. tempPlanes[ 1 ] = farPlane;
  712. dMemcpy( &tempPlanes[ 2 ], planes, numPlanes * sizeof( PlaneF ) );
  713. // Build a polyhedron from the plane set.
  714. Polyhedron polyhedron;
  715. polyhedron.buildFromPlanes(
  716. PlaneSetF( tempPlanes, numPlanes + 2 )
  717. );
  718. // If the polyhedron has any renderable data,
  719. // hand it over to the debug drawer.
  720. if( polyhedron.getNumEdges() )
  721. drawer->drawPolyhedron( polyhedron, iter->isOccluder() ? occluderColor : includerColor );
  722. }
  723. }
  724. }