Torque3D Documentation / _generateds / sceneCullingState.cpp

sceneCullingState.cpp

Engine/source/scene/culling/sceneCullingState.cpp

More...

Public Variables

bool

For frame signal.

Detailed Description

Public Variables

bool gEditingMission 

For frame signal.

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