portal.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  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 "T3D/portal.h"
  24. #include "core/stream/bitStream.h"
  25. #include "console/consoleTypes.h"
  26. #include "console/engineAPI.h"
  27. #include "scene/sceneManager.h"
  28. #include "scene/sceneRenderState.h"
  29. #include "scene/zones/sceneRootZone.h"
  30. #include "scene/culling/sceneCullingState.h"
  31. #include "scene/zones/sceneTraversalState.h"
  32. #include "math/mPlaneSet.h"
  33. #include "gfx/gfxDrawUtil.h"
  34. #include "gfx/gfxTransformSaver.h"
  35. #include "scene/mixin/sceneAmbientSoundObject.impl.h"
  36. #include "scene/mixin/scenePolyhedralObject.impl.h"
  37. #include "math/mPolyhedron.impl.h"
  38. IMPLEMENT_CO_NETOBJECT_V1( Portal );
  39. ConsoleDocClass( Portal,
  40. "@brief An object that provides a \"window\" into a zone, allowing a viewer "
  41. "to see what's rendered in the zone.\n\n"
  42. "A portal is an object that connects zones such that the content of one zone becomes "
  43. "visible in the other when looking through the portal.\n\n"
  44. "Each portal is a full zone which is divided into two sides by the portal plane that "
  45. "intersects it. This intersection polygon is shown in red in the editor. Either of the "
  46. "sides of a portal can be connected to one or more zones.\n\n"
  47. "A connection from a specific portal side to a zone is made in either of two ways:\n\n"
  48. "<ol>\n"
  49. "<li>By moving a Zone object to intersect with the portal at the respective side. While usually it makes "
  50. "sense for this overlap to be small, the connection is established correctly as long as the center of the Zone "
  51. "object that should connect is on the correct side of the portal plane.</li>\n"
  52. "<li>By the respective side of the portal free of Zone objects that would connect to it. In this case, given "
  53. "that the other side is connected to one or more Zones, the portal will automatically connect itself to the "
  54. "outdoor \"zone\" which implicitly is present in any level.</li>\n"
  55. "</ol>\n\n"
  56. "From this, it follows that there are two types of portals:\n\n"
  57. "<dl>\n"
  58. "<dt>Exterior Portals</dt>"
  59. "<dd>An exterior portal is one that is connected to one or more Zone objects on one side and to the outdoor "
  60. "zone at the other side. This kind of portal is most useful for covering transitions from outdoor spaces to "
  61. "indoor spaces.</dd>"
  62. "<dt>Interior Portals</dt>"
  63. "<dd>An interior portal is one that is connected to one or more Zone objects on both sides. This kind of portal "
  64. "is most useful for covering transitions between indoor spaces./dd>"
  65. "</dl>\n\n"
  66. "Strictly speaking, there is a third type of portal called an \"invalid portal\". This is a portal that is not "
  67. "connected to a Zone object on either side in which case the portal serves no use.\n\n"
  68. "Portals in Torque are bidirectional meaning that they connect zones both ways and "
  69. "you can look through the portal's front side as well as through its back-side.\n\n"
  70. "Like Zones, Portals can either be box-shaped or use custom convex polyhedral shapes.\n\n"
  71. "Portals will usually be created in the editor but can, of course, also be created "
  72. "in script code as such:\n\n"
  73. "@tsexample\n"
  74. "// Example declaration of a Portal. This will create a box-shaped portal.\n"
  75. "new Portal( PortalToTestZone )\n"
  76. "{\n"
  77. " position = \"12.8467 -4.02246 14.8017\";\n"
  78. " rotation = \"0 0 -1 97.5085\";\n"
  79. " scale = \"1 0.25 1\";\n"
  80. " canSave = \"1\";\n"
  81. " canSaveDynamicFields = \"1\";\n"
  82. "};\n"
  83. "@endtsexample\n\n"
  84. "@note Keep in mind that zones and portals are more or less strictly a scene optimization mechanism meant to "
  85. "improve render times.\n\n"
  86. "@see Zone\n"
  87. "@ingroup enviroMisc\n"
  88. );
  89. // Notes:
  90. // - This class implements portal spaces as single zones. A different, interesting take
  91. // on this is to turn portal spaces into two zones with the portal plane acting as
  92. // the separator.
  93. // - One downside to our treatment of portals as full zones in their own right is that
  94. // in certain cases we end up including space in the traversal that is clearly not visible.
  95. // Take a situation where you are in the outside zone and you are looking straight into
  96. // the wall of a house. On the other side of that house is a portal leading into the house.
  97. // While the traversal will not step through that portal into the house since that would
  98. // be leading towards the camera rather than away from it, it will still add the frustum
  99. // to the visible space of the portal zone and thus make everything in the portal zone
  100. // visible. It has to do this since, while it can easily tell whether to step through the
  101. // portal or not, it cannot easily tell whether the whole portal zone is visible or not as
  102. // that depends on the occlusion situation in the outdoor zone.
  103. //-----------------------------------------------------------------------------
  104. Portal::Portal()
  105. : mClassification( InvalidPortal ),
  106. mIsGeometryDirty( true )
  107. {
  108. VECTOR_SET_ASSOCIATION( mPortalPolygonWS );
  109. mNetFlags.set( Ghostable | ScopeAlways );
  110. mTypeMask |= StaticObjectType;
  111. mPassableSides[ FrontSide ] = true;
  112. mPassableSides[ BackSide ] = true;
  113. mObjScale.set( 1.0f, 0.25f, 1.0f );
  114. // We're not closed off.
  115. mZoneFlags.clear( ZoneFlag_IsClosedOffSpace );
  116. }
  117. //-----------------------------------------------------------------------------
  118. void Portal::initPersistFields()
  119. {
  120. docsURL;
  121. addGroup( "Zoning" );
  122. addProtectedField( "frontSidePassable", TypeBool, Offset( mPassableSides[ FrontSide ], Portal ),
  123. &_setFrontSidePassable, &defaultProtectedGetFn,
  124. "Whether one can view through the front-side of the portal." );
  125. addProtectedField( "backSidePassable", TypeBool, Offset( mPassableSides[ BackSide ], Portal ),
  126. &_setBackSidePassable, &defaultProtectedGetFn,
  127. "Whether one can view through the back-side of the portal." );
  128. endGroup( "Zoning" );
  129. Parent::initPersistFields();
  130. }
  131. //-----------------------------------------------------------------------------
  132. void Portal::consoleInit()
  133. {
  134. // Disable rendering of portals by default.
  135. getStaticClassRep()->mIsRenderEnabled = false;
  136. }
  137. //-----------------------------------------------------------------------------
  138. String Portal::describeSelf() const
  139. {
  140. String str = Parent::describeSelf();
  141. switch( getClassification() )
  142. {
  143. case InvalidPortal: str += "|InvalidPortal"; break;
  144. case ExteriorPortal: str += "|ExteriorPortal"; break;
  145. case InteriorPortal: str += "|InteriorPortal"; break;
  146. }
  147. return str;
  148. }
  149. //-----------------------------------------------------------------------------
  150. bool Portal::writeField( StringTableEntry fieldName, const char* value )
  151. {
  152. static StringTableEntry sFrontSidePassable = StringTable->insert( "frontSidePassable" );
  153. static StringTableEntry sBackSidePassable = StringTable->insert( "backSidePassable" );
  154. // Don't write passable flags if at default.
  155. if( ( fieldName == sFrontSidePassable || fieldName == sBackSidePassable ) &&
  156. dAtob( value ) )
  157. return false;
  158. return Parent::writeField( fieldName, value );
  159. }
  160. //-----------------------------------------------------------------------------
  161. void Portal::onSceneRemove()
  162. {
  163. // Disconnect from root zone, if it's an exterior portal.
  164. if( mClassification == ExteriorPortal )
  165. {
  166. AssertFatal( getSceneManager()->getZoneManager(), "Portal::onSceneRemove - Portal classified as exterior without having a zone manager!" );
  167. getSceneManager()->getZoneManager()->getRootZone()->disconnectZoneSpace( this );
  168. }
  169. Parent::onSceneRemove();
  170. }
  171. //-----------------------------------------------------------------------------
  172. void Portal::setTransform( const MatrixF& mat )
  173. {
  174. // Portal geometry needs updating. Set this before calling
  175. // parent because the transform change will cause an immediate
  176. // update of the portal's zoning state.
  177. mIsGeometryDirty = true;
  178. Parent::setTransform( mat );
  179. }
  180. //-----------------------------------------------------------------------------
  181. void Portal::setSidePassable( Side side, bool value )
  182. {
  183. if( mPassableSides[ side ] == value )
  184. return;
  185. mPassableSides[ side ] = value;
  186. if( isServerObject() )
  187. setMaskBits( PassableMask );
  188. }
  189. //-----------------------------------------------------------------------------
  190. U32 Portal::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
  191. {
  192. U32 retMask = Parent::packUpdate( con, mask, stream );
  193. stream->writeFlag( mPassableSides[ FrontSide ] );
  194. stream->writeFlag( mPassableSides[ BackSide ] );
  195. return retMask;
  196. }
  197. //-----------------------------------------------------------------------------
  198. void Portal::unpackUpdate( NetConnection *con, BitStream *stream )
  199. {
  200. Parent::unpackUpdate( con, stream );
  201. mPassableSides[ FrontSide ] = stream->readFlag();
  202. mPassableSides[ BackSide ] = stream->readFlag();
  203. }
  204. //-----------------------------------------------------------------------------
  205. void Portal::_renderObject( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat )
  206. {
  207. if( overrideMat )
  208. return;
  209. // Update geometry if necessary.
  210. if( mIsGeometryDirty )
  211. _updateGeometry();
  212. // Render portal polygon.
  213. GFXStateBlockDesc desc;
  214. desc.setBlend( true );
  215. desc.setZReadWrite( true, false );
  216. desc.setCullMode( GFXCullNone );
  217. PlaneF::Side viewSide = mPortalPlane.whichSide( state->getCameraPosition() );
  218. ColorI color;
  219. switch( mClassification )
  220. {
  221. case InvalidPortal: color = ColorI( 255, 255, 255, 45 ); break;
  222. case ExteriorPortal: color = viewSide == PlaneF::Front ? ColorI( 0, 128, 128, 45 ) : ColorI( 0, 255, 255, 45 ); break;
  223. case InteriorPortal: color = viewSide == PlaneF::Front ? ColorI( 128, 128, 0, 45 ) : ColorI( 255, 255, 0, 45 ); break;
  224. }
  225. GFX->getDrawUtil()->drawPolygon( desc, mPortalPolygonWS.address(), mPortalPolygonWS.size(), color );
  226. desc.setFillModeWireframe();
  227. GFX->getDrawUtil()->drawPolygon( desc, mPortalPolygonWS.address(), mPortalPolygonWS.size(), ColorI::RED );
  228. // Render rest.
  229. Parent::_renderObject( ri, state, overrideMat );
  230. }
  231. //-----------------------------------------------------------------------------
  232. void Portal::traverseZones( SceneTraversalState* state, U32 startZoneId )
  233. {
  234. // Check whether the portal is occluded.
  235. if( state->getTraversalDepth() > 0 &&
  236. state->getCullingState()->isOccluded( this ) )
  237. return;
  238. Parent::traverseZones( state, startZoneId );
  239. }
  240. //-----------------------------------------------------------------------------
  241. void Portal::_traverseConnectedZoneSpaces( SceneTraversalState* state )
  242. {
  243. PROFILE_SCOPE( Portal_traverseConnectedZoneSpaces );
  244. // Don't traverse out from the portal if it is invalid.
  245. if( mClassification == InvalidPortal )
  246. return;
  247. AssertFatal( !mIsGeometryDirty, "Portal::_traverseConnectedZoneSpaces - Geometry not up-to-date!" );
  248. // When starting traversal within a portal zone, we cannot really use the portal
  249. // plane itself to direct our visibility queries. For example, the camera might
  250. // actually be located in front of the portal plane and thus cannot actually look
  251. // through the portal, though it will still see what lies on front of where the
  252. // portal leads.
  253. //
  254. // So if we're the start of the traversal chain, i.e. the traversal has started
  255. // out in the portal zone, then just put the traversal through to SceneZoneSpace
  256. // so it can hand it over to all connected zone managers.
  257. //
  258. // Otherwise, just do a normal traversal by stepping through the portal.
  259. if( state->getTraversalDepth() == 1 )
  260. {
  261. Parent::_traverseConnectedZoneSpaces( state );
  262. return;
  263. }
  264. SceneCullingState* cullingState = state->getCullingState();
  265. const SceneCameraState& cameraState = cullingState->getCameraState();
  266. // Get the data of the zone we're coming from. Note that at this point
  267. // the portal zone itself is already on top of the traversal stack, so
  268. // we skip over the bottom-most entry.
  269. const U32 sourceZoneId = state->getZoneIdFromStack( 1 );
  270. const SceneZoneSpace* sourceZoneSpace = state->getZoneFromStack( 1 );
  271. // Find out which side of the portal we are on given the
  272. // source zone.
  273. const Portal::Side currentZoneSide =
  274. sourceZoneId == SceneZoneSpaceManager::RootZoneId
  275. ? ( getInteriorSideOfExteriorPortal() == FrontSide ? BackSide : FrontSide )
  276. : getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );
  277. // Don't step through portal if the side we're interested in isn't passable.
  278. if( !isSidePassable( currentZoneSide ) )
  279. return;
  280. // If the viewpoint isn't on the same side of the portal as the source zone,
  281. // then stepping through the portal would mean we are stepping back towards
  282. // the viewpoint which doesn't make sense; so, skip the portal.
  283. const Point3F& viewPos = cameraState.getViewPosition();
  284. const F32 viewPosDistToPortal = mFabs( getPortalPlane().distToPlane( viewPos ) );
  285. if( !mIsZero( viewPosDistToPortal ) && getSideRelativeToPortalPlane( viewPos ) != currentZoneSide )
  286. return;
  287. // Before we go ahead and do the real work, try to find out whether
  288. // the portal is at a perpendicular or near-perpendicular angle to the view
  289. // direction. If so, there's no point in going further since we can't really
  290. // see much through the portal anyway. It also prevents us from stepping
  291. // over front/back side ambiguities.
  292. Point3F viewDirection = cameraState.getViewDirection();
  293. const F32 dotProduct = mDot( viewDirection, getPortalPlane() );
  294. if( mIsZero( dotProduct ) )
  295. return;
  296. // Finally, if we have come through a portal to the current zone, check if the target
  297. // portal we are trying to step through now completely lies on the "backside"--i.e.
  298. // the side of portal on which our current zone lies--of the source portal. If so,
  299. // we can be sure this portal leads us in the wrong direction. This prevents the
  300. // outdoor zone from having just arrived through a window on one side of a house just
  301. // to go round the house and re-enter it from the other side.
  302. Portal* sourcePortal = state->getTraversalDepth() > 2 ? dynamic_cast< Portal* >( state->getZoneFromStack( 2 ) ) : NULL;
  303. if( sourcePortal != NULL )
  304. {
  305. const Side sourcePortalFrontSide =
  306. sourceZoneId == SceneZoneSpaceManager::RootZoneId
  307. ? sourcePortal->getInteriorSideOfExteriorPortal()
  308. : sourcePortal->getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );
  309. const PlaneF::Side sourcePortalPlaneFrontSide =
  310. sourcePortalFrontSide == FrontSide ? PlaneF::Front : PlaneF::Back;
  311. bool allPortalVerticesOnBackside = true;
  312. const U32 numVertices = mPortalPolygonWS.size();
  313. for( U32 i = 0; i < numVertices; ++ i )
  314. {
  315. // Not using getSideRelativeToPortalPlane here since we want PlaneF::On to be
  316. // counted as backside here.
  317. if( sourcePortal->mPortalPlane.whichSide( mPortalPolygonWS[ i ] ) == sourcePortalPlaneFrontSide )
  318. {
  319. allPortalVerticesOnBackside = false;
  320. break;
  321. }
  322. }
  323. if( allPortalVerticesOnBackside )
  324. return;
  325. }
  326. // If we come from the outdoor zone, then we don't want to step through any portal
  327. // where the interior zones are actually on the same side as our camera since that
  328. // would mean we are stepping into an interior through the backside of a portal.
  329. if( sourceZoneId == SceneZoneSpaceManager::RootZoneId )
  330. {
  331. const Portal::Side cameraSide = getSideRelativeToPortalPlane( viewPos );
  332. if( cameraSide == getInteriorSideOfExteriorPortal() )
  333. return;
  334. }
  335. // Clip the current culling volume against the portal's polygon. If the polygon
  336. // lies completely outside the volume or for some other reason there's no good resulting
  337. // volume, _generateCullingVolume() will return false and we terminate this portal sequence
  338. // here.
  339. //
  340. // However, don't attempt to clip the portal if we are standing really close to or
  341. // even below the near distance away from the portal plane. In that case, trying to
  342. // clip the portal will only result in trouble so we stick to the original culling volume
  343. // in that case.
  344. bool haveClipped = false;
  345. if( viewPosDistToPortal > ( cameraState.getFrustum().getNearDist() + 0.1f ) )
  346. {
  347. SceneCullingVolume volume;
  348. if( !_generateCullingVolume( state, volume ) )
  349. return;
  350. state->pushCullingVolume( volume );
  351. haveClipped = true;
  352. }
  353. // Short-circuit things if we are stepping from an interior zone outside. In this
  354. // case we know that the only zone we care about is the outdoor zone so head straight
  355. // into it.
  356. if( isExteriorPortal() && sourceZoneId != SceneZoneSpaceManager::RootZoneId )
  357. getSceneManager()->getZoneManager()->getRootZone()->traverseZones( state );
  358. else
  359. {
  360. // Go through the zones that the portal connects to and
  361. // traverse into them.
  362. for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
  363. {
  364. SceneZoneSpace* targetSpace = ref->mZoneSpace;
  365. if( targetSpace == sourceZoneSpace )
  366. continue; // Skip space we originated from.
  367. // We have handled the case of stepping into the outdoor zone above and
  368. // by skipping the zone we originated from, we have implicitly handled the
  369. // case of stepping out of the outdoor zone. Thus, we should not see the
  370. // outdoor zone here. Important as getPosition() is meaningless for it.
  371. AssertFatal( targetSpace->getZoneRangeStart() != SceneZoneSpaceManager::RootZoneId,
  372. "Portal::_traverseConnectedZoneSpaces - Outdoor zone must have been handled already" );
  373. // Skip zones that lie on the same side as the zone
  374. // we originated from.
  375. if( getSideRelativeToPortalPlane( targetSpace->getPosition() ) == currentZoneSide )
  376. continue;
  377. // Traverse into the space.
  378. targetSpace->traverseZones( state );
  379. }
  380. }
  381. // If we have pushed our own clipping volume,
  382. // remove that from the stack now.
  383. if( haveClipped )
  384. state->popCullingVolume();
  385. }
  386. //-----------------------------------------------------------------------------
  387. bool Portal::_generateCullingVolume( SceneTraversalState* state, SceneCullingVolume& outVolume ) const
  388. {
  389. PROFILE_SCOPE( Portal_generateCullingVolume );
  390. SceneCullingState* cullingState = state->getCullingState();
  391. const SceneCullingVolume& currentVolume = state->getCurrentCullingVolume();
  392. // Clip the portal polygon against the current culling volume.
  393. Point3F vertices[ 64 ];
  394. U32 numVertices = 0;
  395. numVertices = currentVolume.getPlanes().clipPolygon(
  396. mPortalPolygonWS.address(),
  397. mPortalPolygonWS.size(),
  398. vertices,
  399. sizeof( vertices ) /sizeof( vertices[ 0 ] )
  400. );
  401. AssertFatal( numVertices == 0 || numVertices >= 3,
  402. "Portal::_generateCullingVolume - Clipping produced degenerate polygon" );
  403. if( !numVertices )
  404. return false;
  405. // Create a culling volume.
  406. return cullingState->createCullingVolume(
  407. vertices, numVertices,
  408. SceneCullingVolume::Includer,
  409. outVolume
  410. );
  411. }
  412. //-----------------------------------------------------------------------------
  413. void Portal::connectZoneSpace( SceneZoneSpace* zoneSpace )
  414. {
  415. Parent::connectZoneSpace( zoneSpace );
  416. // Update portal state. Unfortunately, we can't do that on demand
  417. // easily since everything must be in place before a traversal starts.
  418. _update();
  419. }
  420. //-----------------------------------------------------------------------------
  421. void Portal::disconnectZoneSpace( SceneZoneSpace* zoneSpace )
  422. {
  423. Parent::disconnectZoneSpace( zoneSpace );
  424. // Update portal state.
  425. _update();
  426. }
  427. //-----------------------------------------------------------------------------
  428. void Portal::_disconnectAllZoneSpaces()
  429. {
  430. Parent::_disconnectAllZoneSpaces();
  431. // Update portal state.
  432. _update();
  433. }
  434. //-----------------------------------------------------------------------------
  435. void Portal::_update()
  436. {
  437. if( mIsGeometryDirty )
  438. _updateGeometry();
  439. _updateConnectivity();
  440. }
  441. //-----------------------------------------------------------------------------
  442. void Portal::_updateGeometry()
  443. {
  444. const F32 boxHalfWidth = getScale().x * 0.5f;
  445. const F32 boxHalfHeight = getScale().z * 0.5f;
  446. const Point3F center = getTransform().getPosition();
  447. const Point3F up = getTransform().getUpVector() * boxHalfHeight;
  448. const Point3F right = getTransform().getRightVector() * boxHalfWidth;
  449. // Update the portal polygon and plane.
  450. if( mIsBox )
  451. {
  452. // It's a box so the portal polygon is a rectangle.
  453. // Simply compute the corner points by stepping from the
  454. // center to the corners using the up and right vector.
  455. mPortalPolygonWS.setSize( 4 );
  456. mPortalPolygonWS[ 0 ] = center + right - up; // bottom right
  457. mPortalPolygonWS[ 1 ] = center - right - up; // bottom left
  458. mPortalPolygonWS[ 2 ] = center - right + up; // top left
  459. mPortalPolygonWS[ 3 ] = center + right + up; // top right
  460. // Update the plane by going through three of the points.
  461. mPortalPlane = PlaneF(
  462. mPortalPolygonWS[ 0 ],
  463. mPortalPolygonWS[ 1 ],
  464. mPortalPolygonWS[ 2 ]
  465. );
  466. }
  467. else
  468. {
  469. // It's not necessarily a box so we must use the general
  470. // routine.
  471. // Update the portal plane by building a plane that cuts the
  472. // OBB in half vertically along its Y axis.
  473. mPortalPlane = PlaneF(
  474. center + right - up,
  475. center - right - up,
  476. center - right + up
  477. );
  478. // Slice the polyhedron along the same plane in object space.
  479. const PlaneF slicePlane = PlaneF( Point3F::Zero, Point3F( 0.f, 1.f, 0.f ) );
  480. mPortalPolygonWS.setSize( mPolyhedron.getNumEdges() );
  481. U32 numPoints = mPolyhedron.constructIntersection( slicePlane, mPortalPolygonWS.address(), mPortalPolygonWS.size() );
  482. mPortalPolygonWS.setSize( numPoints );
  483. // Transform the polygon to world space.
  484. for( U32 i = 0; i < numPoints; ++ i )
  485. {
  486. mPortalPolygonWS[ i ].convolve( getScale() );
  487. mObjToWorld.mulP( mPortalPolygonWS[ i ] );
  488. }
  489. }
  490. mIsGeometryDirty = false;
  491. }
  492. //-----------------------------------------------------------------------------
  493. void Portal::_updateConnectivity()
  494. {
  495. SceneZoneSpaceManager* zoneManager = getSceneManager()->getZoneManager();
  496. if( !zoneManager )
  497. return;
  498. // Find out where our connected zones are in respect to the portal
  499. // plane.
  500. bool haveInteriorZonesOnFrontSide = false;
  501. bool haveInteriorZonesOnBackSide = false;
  502. bool isConnectedToRootZone = ( mClassification == ExteriorPortal );
  503. for( ZoneSpaceRef* ref = mConnectedZoneSpaces;
  504. ref != NULL; ref = ref->mNext )
  505. {
  506. SceneZoneSpace* zone = dynamic_cast< SceneZoneSpace* >( ref->mZoneSpace );
  507. if( !zone || zone->isRootZone() )
  508. continue;
  509. if( getSideRelativeToPortalPlane( zone->getPosition() ) == FrontSide )
  510. haveInteriorZonesOnFrontSide = true;
  511. else
  512. haveInteriorZonesOnBackSide = true;
  513. }
  514. // If we have zones connected to us on only one side, we are an exterior
  515. // portal. Otherwise, we're an interior portal.
  516. SceneRootZone* rootZone = zoneManager->getRootZone();
  517. if( haveInteriorZonesOnFrontSide && haveInteriorZonesOnBackSide )
  518. {
  519. mClassification = InteriorPortal;
  520. }
  521. else if( haveInteriorZonesOnFrontSide || haveInteriorZonesOnBackSide )
  522. {
  523. mClassification = ExteriorPortal;
  524. // Remember where our interior zones are.
  525. if( haveInteriorZonesOnBackSide )
  526. mInteriorSide = BackSide;
  527. else
  528. mInteriorSide = FrontSide;
  529. // If we aren't currently connected to the root zone,
  530. // establish the connection now.
  531. if( !isConnectedToRootZone )
  532. {
  533. Parent::connectZoneSpace( rootZone );
  534. rootZone->connectZoneSpace( this );
  535. }
  536. }
  537. else
  538. mClassification = InvalidPortal;
  539. // If we have been connected to the outdoor zone already but the
  540. // portal got classified as invalid or interior now, break the
  541. // connection to the outdoor zone.
  542. if( isConnectedToRootZone &&
  543. ( mClassification == InvalidPortal || mClassification == InteriorPortal ) )
  544. {
  545. Parent::disconnectZoneSpace( rootZone );
  546. rootZone->disconnectZoneSpace( this );
  547. }
  548. }
  549. //-----------------------------------------------------------------------------
  550. Portal::Side Portal::getSideRelativeToPortalPlane( const Point3F& point ) const
  551. {
  552. // For our purposes, we consider PlaneF::Front and PlaneF::On
  553. // placement as FrontSide.
  554. PlaneF::Side planeSide = getPortalPlane().whichSide( point );
  555. if( planeSide == PlaneF::Front || planeSide == PlaneF::On )
  556. return FrontSide;
  557. else
  558. return BackSide;
  559. }
  560. //-----------------------------------------------------------------------------
  561. bool Portal::_setFrontSidePassable( void* object, const char* index, const char* data )
  562. {
  563. Portal* portal = reinterpret_cast< Portal* >( object );
  564. portal->setSidePassable( Portal::FrontSide, EngineUnmarshallData< bool >()( data ) );
  565. return false;
  566. }
  567. //-----------------------------------------------------------------------------
  568. bool Portal::_setBackSidePassable( void* object, const char* index, const char* data )
  569. {
  570. Portal* portal = reinterpret_cast< Portal* >( object );
  571. portal->setSidePassable( Portal::BackSide, EngineUnmarshallData< bool >()( data ) );
  572. return false;
  573. }
  574. //=============================================================================
  575. // Console API.
  576. //=============================================================================
  577. // MARK: ---- Console API ----
  578. //-----------------------------------------------------------------------------
  579. DefineEngineMethod( Portal, isInteriorPortal, bool, (),,
  580. "Test whether the portal connects interior zones only.\n\n"
  581. "@return True if the portal is an interior portal." )
  582. {
  583. return object->isInteriorPortal();
  584. }
  585. //-----------------------------------------------------------------------------
  586. DefineEngineMethod( Portal, isExteriorPortal, bool, (),,
  587. "Test whether the portal connects interior zones to the outdoor zone.\n\n"
  588. "@return True if the portal is an exterior portal." )
  589. {
  590. return object->isExteriorPortal();
  591. }