groundPlane.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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/groundPlane.h"
  24. #include "renderInstance/renderPassManager.h"
  25. #include "scene/sceneRenderState.h"
  26. #include "materials/sceneData.h"
  27. #include "materials/materialDefinition.h"
  28. #include "materials/materialManager.h"
  29. #include "materials/baseMatInstance.h"
  30. #include "math/util/frustum.h"
  31. #include "math/mPlane.h"
  32. #include "console/consoleTypes.h"
  33. #include "console/engineAPI.h"
  34. #include "core/stream/bitStream.h"
  35. #include "collision/boxConvex.h"
  36. #include "collision/abstractPolyList.h"
  37. #include "T3D/physics/physicsPlugin.h"
  38. #include "T3D/physics/physicsBody.h"
  39. #include "T3D/physics/physicsCollision.h"
  40. /// Minimum square size allowed. This is a cheap way to limit the amount
  41. /// of geometry possibly generated by the GroundPlane (vertex buffers have a
  42. /// limit, too). Dynamically clipping extents into range is a problem since the
  43. /// location of the horizon depends on the camera orientation. Just shifting
  44. /// squareSize as needed also doesn't work as that causes different geometry to
  45. /// be generated depending on the viewpoint and orientation which affects the
  46. /// texturing.
  47. static const F32 sMIN_SQUARE_SIZE = 16;
  48. IMPLEMENT_CO_NETOBJECT_V1( GroundPlane );
  49. ConsoleDocClass( GroundPlane,
  50. "@brief An infinite plane extending in all direction.\n\n"
  51. "%GroundPlane is useful for setting up simple testing scenes, or it can be "
  52. "placed under an existing scene to keep objects from falling into 'nothing'.\n\n"
  53. "%GroundPlane may not be moved or rotated, it is always at the world origin.\n\n"
  54. "@ingroup Terrain"
  55. );
  56. GroundPlane::GroundPlane()
  57. : mZOffset (0.0f),
  58. mSquareSize( 128.0f ),
  59. mScaleU( 1.0f ),
  60. mScaleV( 1.0f ),
  61. mMaterial( NULL ),
  62. mMin( 0.0f, 0.0f ),
  63. mMax( 0.0f, 0.0f ),
  64. mPhysicsRep( NULL )
  65. {
  66. mTypeMask |= StaticObjectType | StaticShapeObjectType;
  67. mNetFlags.set( Ghostable | ScopeAlways );
  68. mConvexList = new Convex;
  69. }
  70. GroundPlane::~GroundPlane()
  71. {
  72. if( mMaterial )
  73. SAFE_DELETE( mMaterial );
  74. mConvexList->nukeList();
  75. SAFE_DELETE( mConvexList );
  76. }
  77. void GroundPlane::initPersistFields()
  78. {
  79. addGroup( "Plane" );
  80. addField( "zOffset", TypeF32, Offset( mZOffset, GroundPlane ), "How high the ground plane should be" );
  81. addField( "squareSize", TypeF32, Offset( mSquareSize, GroundPlane ), "Square size in meters to which %GroundPlane subdivides its geometry." );
  82. addField( "scaleU", TypeF32, Offset( mScaleU, GroundPlane ), "Scale of texture repeat in the U direction." );
  83. addField( "scaleV", TypeF32, Offset( mScaleV, GroundPlane ), "Scale of texture repeat in the V direction." );
  84. addField( "material", TypeMaterialName, Offset( mMaterialName, GroundPlane ), "Name of Material used to render %GroundPlane's surface." );
  85. endGroup( "Plane" );
  86. Parent::initPersistFields();
  87. removeField( "scale" );
  88. removeField( "position" );
  89. removeField( "rotation" );
  90. }
  91. bool GroundPlane::onAdd()
  92. {
  93. if( !Parent::onAdd() )
  94. return false;
  95. if( isClientObject() )
  96. _updateMaterial();
  97. if( mSquareSize < sMIN_SQUARE_SIZE )
  98. {
  99. Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
  100. mSquareSize = sMIN_SQUARE_SIZE;
  101. }
  102. Parent::setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
  103. Parent::setTransform( MatrixF::Identity );
  104. setGlobalBounds();
  105. resetWorldBox();
  106. addToScene();
  107. if ( PHYSICSMGR )
  108. {
  109. PhysicsCollision *colShape = PHYSICSMGR->createCollision();
  110. colShape->addPlane( PlaneF( Point3F::Zero, Point3F( 0, 0, 1 ) ) );
  111. PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
  112. mPhysicsRep = PHYSICSMGR->createBody();
  113. mPhysicsRep->init( colShape, 0, 0, this, world );
  114. }
  115. return true;
  116. }
  117. void GroundPlane::onRemove()
  118. {
  119. SAFE_DELETE( mPhysicsRep );
  120. removeFromScene();
  121. Parent::onRemove();
  122. }
  123. void GroundPlane::inspectPostApply()
  124. {
  125. Parent::inspectPostApply();
  126. setMaskBits( U32( -1 ) );
  127. if( mSquareSize < sMIN_SQUARE_SIZE )
  128. {
  129. Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
  130. mSquareSize = sMIN_SQUARE_SIZE;
  131. }
  132. setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
  133. }
  134. void GroundPlane::setTransform( const MatrixF &mat )
  135. {
  136. // Ignore.
  137. }
  138. void GroundPlane::setScale( const Point3F& scale )
  139. {
  140. // Ignore.
  141. }
  142. U32 GroundPlane::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
  143. {
  144. U32 retMask = Parent::packUpdate( connection, mask, stream );
  145. stream->write( mZOffset );
  146. stream->write( mSquareSize );
  147. stream->write( mScaleU );
  148. stream->write( mScaleV );
  149. stream->write( mMaterialName );
  150. return retMask;
  151. }
  152. void GroundPlane::unpackUpdate( NetConnection* connection, BitStream* stream )
  153. {
  154. Parent::unpackUpdate( connection, stream );
  155. stream->read( &mZOffset );
  156. stream->read( &mSquareSize );
  157. stream->read( &mScaleU );
  158. stream->read( &mScaleV );
  159. stream->read( &mMaterialName );
  160. // If we're added then something possibly changed in
  161. // the editor... do an update of the material and the
  162. // geometry.
  163. if ( isProperlyAdded() )
  164. {
  165. _updateMaterial();
  166. mVertexBuffer = NULL;
  167. }
  168. }
  169. void GroundPlane::_updateMaterial()
  170. {
  171. if( mMaterialName.isEmpty() )
  172. {
  173. Con::warnf( "GroundPlane::_updateMaterial - no material set; defaulting to 'WarningMaterial'" );
  174. mMaterialName = "WarningMaterial";
  175. }
  176. // If the material name matches then don't
  177. // bother updating it.
  178. if ( mMaterial &&
  179. mMaterialName.compare( mMaterial->getMaterial()->getName() ) == 0 )
  180. return;
  181. SAFE_DELETE( mMaterial );
  182. mMaterial = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() );
  183. if ( !mMaterial )
  184. Con::errorf( "GroundPlane::_updateMaterial - no material called '%s'", mMaterialName.c_str() );
  185. }
  186. bool GroundPlane::castRay( const Point3F& start, const Point3F& end, RayInfo* info )
  187. {
  188. PlaneF plane( Point3F( 0.0f, 0.0f, 0.0f ), Point3F( 0.0f, 0.0f, 1.0f ) );
  189. F32 t = plane.intersect( start, end );
  190. if( t >= 0.0 && t <= 1.0 )
  191. {
  192. info->t = t;
  193. info->setContactPoint( start, end );
  194. info->normal.set( 0, 0, 1 );
  195. info->material = mMaterial;
  196. info->object = this;
  197. info->distance = 0;
  198. info->faceDot = 0;
  199. info->texCoord.set( 0, 0 );
  200. return true;
  201. }
  202. return false;
  203. }
  204. void GroundPlane::buildConvex( const Box3F& box, Convex* convex )
  205. {
  206. mConvexList->collectGarbage();
  207. Box3F planeBox = getPlaneBox();
  208. if ( !box.isOverlapped( planeBox ) )
  209. return;
  210. // See if we already have a convex in the working set.
  211. BoxConvex *boxConvex = NULL;
  212. CollisionWorkingList &wl = convex->getWorkingList();
  213. CollisionWorkingList *itr = wl.wLink.mNext;
  214. for ( ; itr != &wl; itr = itr->wLink.mNext )
  215. {
  216. if ( itr->mConvex->getType() == BoxConvexType &&
  217. itr->mConvex->getObject() == this )
  218. {
  219. boxConvex = (BoxConvex*)itr->mConvex;
  220. break;
  221. }
  222. }
  223. if ( !boxConvex )
  224. {
  225. boxConvex = new BoxConvex;
  226. mConvexList->registerObject( boxConvex );
  227. boxConvex->init( this );
  228. convex->addToWorkingList( boxConvex );
  229. }
  230. // Update our convex to best match the queried box
  231. if ( boxConvex )
  232. {
  233. Point3F queryCenter = box.getCenter();
  234. boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF );
  235. boxConvex->mSize = Point3F( box.getExtents().x,
  236. box.getExtents().y,
  237. GROUND_PLANE_BOX_HEIGHT_HALF );
  238. }
  239. }
  240. bool GroundPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& )
  241. {
  242. polyList->setObject( this );
  243. polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
  244. if(context == PLC_Navigation)
  245. {
  246. F32 z = getPosition().z;
  247. Point3F
  248. p0(box.minExtents.x, box.maxExtents.y, z),
  249. p1(box.maxExtents.x, box.maxExtents.y, z),
  250. p2(box.maxExtents.x, box.minExtents.y, z),
  251. p3(box.minExtents.x, box.minExtents.y, z);
  252. // Add vertices to poly list.
  253. U32 v0 = polyList->addPoint(p0);
  254. polyList->addPoint(p1);
  255. polyList->addPoint(p2);
  256. polyList->addPoint(p3);
  257. // Add plane between first three vertices.
  258. polyList->begin(0, 0);
  259. polyList->vertex(v0);
  260. polyList->vertex(v0+1);
  261. polyList->vertex(v0+2);
  262. polyList->plane(v0, v0+1, v0+2);
  263. polyList->end();
  264. // Add plane between last three vertices.
  265. polyList->begin(0, 1);
  266. polyList->vertex(v0+2);
  267. polyList->vertex(v0+3);
  268. polyList->vertex(v0);
  269. polyList->plane(v0+2, v0+3, v0);
  270. polyList->end();
  271. return true;
  272. }
  273. Box3F planeBox = getPlaneBox();
  274. polyList->addBox( planeBox, mMaterial );
  275. return true;
  276. }
  277. void GroundPlane::prepRenderImage( SceneRenderState* state )
  278. {
  279. PROFILE_SCOPE( GroundPlane_prepRenderImage );
  280. // TODO: Should we skip rendering the ground plane into
  281. // the shadows? Its not like you can ever get under it.
  282. if ( !mMaterial )
  283. return;
  284. // If we don't have a material instance after the override then
  285. // we can skip rendering all together.
  286. BaseMatInstance *matInst = state->getOverrideMaterial( mMaterial );
  287. if ( !matInst )
  288. return;
  289. PROFILE_SCOPE( GroundPlane_prepRender );
  290. // Update the geometry.
  291. createGeometry( state->getCullingFrustum() );
  292. if( mVertexBuffer.isNull() )
  293. return;
  294. // Add a render instance.
  295. RenderPassManager* pass = state->getRenderPass();
  296. MeshRenderInst* ri = pass->allocInst< MeshRenderInst >();
  297. ri->type = RenderPassManager::RIT_Mesh;
  298. ri->vertBuff = &mVertexBuffer;
  299. ri->primBuff = &mPrimitiveBuffer;
  300. ri->prim = &mPrimitive;
  301. ri->matInst = matInst;
  302. ri->objectToWorld = pass->allocUniqueXform( MatrixF::Identity );
  303. ri->worldToCamera = pass->allocSharedXform( RenderPassManager::View );
  304. ri->projection = pass->allocSharedXform( RenderPassManager::Projection );
  305. ri->visibility = 1.0f;
  306. ri->translucentSort = matInst->getMaterial()->isTranslucent();
  307. ri->defaultKey = matInst->getStateHint();
  308. if( ri->translucentSort )
  309. ri->type = RenderPassManager::RIT_Translucent;
  310. // If we need lights then set them up.
  311. if ( matInst->isForwardLit() )
  312. {
  313. LightQuery query;
  314. query.init( getWorldSphere() );
  315. query.getLights( ri->lights, 8 );
  316. }
  317. pass->addInst( ri );
  318. }
  319. /// Generate a subset of the ground plane matching the given frustum.
  320. void GroundPlane::createGeometry( const Frustum& frustum )
  321. {
  322. PROFILE_SCOPE( GroundPlane_createGeometry );
  323. enum { MAX_WIDTH = 256, MAX_HEIGHT = 256 };
  324. // Project the frustum onto the XY grid.
  325. Point2F min;
  326. Point2F max;
  327. projectFrustum( frustum, mSquareSize, min, max );
  328. // Early out if the grid projection hasn't changed.
  329. if( mVertexBuffer.isValid() &&
  330. min == mMin &&
  331. max == mMax )
  332. return;
  333. mMin = min;
  334. mMax = max;
  335. // Determine the grid extents and allocate the buffers.
  336. // Adjust square size permanently if with the given frustum,
  337. // we end up producing more than a certain limit of geometry.
  338. // This is to prevent this code from causing trouble with
  339. // long viewing distances.
  340. // This only affects the client object, of course, and thus
  341. // has no permanent effect.
  342. U32 width = mCeil( ( max.x - min.x ) / mSquareSize );
  343. if( width > MAX_WIDTH )
  344. {
  345. mSquareSize = mCeil( ( max.x - min.x ) / MAX_WIDTH );
  346. width = MAX_WIDTH;
  347. }
  348. else if( !width )
  349. width = 1;
  350. U32 height = mCeil( ( max.y - min.y ) / mSquareSize );
  351. if( height > MAX_HEIGHT )
  352. {
  353. mSquareSize = mCeil( ( max.y - min.y ) / MAX_HEIGHT );
  354. height = MAX_HEIGHT;
  355. }
  356. else if( !height )
  357. height = 1;
  358. const U32 numVertices = ( width + 1 ) * ( height + 1 );
  359. const U32 numTriangles = width * height * 2;
  360. // Only reallocate if the buffers are too small.
  361. if ( mVertexBuffer.isNull() || numVertices > mVertexBuffer->mNumVerts )
  362. {
  363. #if defined(TORQUE_OS_XENON)
  364. mVertexBuffer.set( GFX, numVertices, GFXBufferTypeVolatile );
  365. #else
  366. mVertexBuffer.set( GFX, numVertices, GFXBufferTypeDynamic );
  367. #endif
  368. }
  369. if ( mPrimitiveBuffer.isNull() || numTriangles > mPrimitiveBuffer->mPrimitiveCount )
  370. {
  371. #if defined(TORQUE_OS_XENON)
  372. mPrimitiveBuffer.set( GFX, numTriangles*3, numTriangles, GFXBufferTypeVolatile );
  373. #else
  374. mPrimitiveBuffer.set( GFX, numTriangles*3, numTriangles, GFXBufferTypeDynamic );
  375. #endif
  376. }
  377. // Generate the grid.
  378. generateGrid( width, height, mSquareSize, min, max, mVertexBuffer, mPrimitiveBuffer );
  379. // Set up GFX primitive.
  380. mPrimitive.type = GFXTriangleList;
  381. mPrimitive.numPrimitives = numTriangles;
  382. mPrimitive.numVertices = numVertices;
  383. }
  384. /// Project the given frustum onto the ground plane and return the XY bounds in world space.
  385. void GroundPlane::projectFrustum( const Frustum& frustum, F32 squareSize, Point2F& outMin, Point2F& outMax )
  386. {
  387. // Get the frustum's min and max XY coordinates.
  388. const Box3F bounds = frustum.getBounds();
  389. Point2F minPt( bounds.minExtents.x, bounds.minExtents.y );
  390. Point2F maxPt( bounds.maxExtents.x, bounds.maxExtents.y );
  391. // Round the min and max coordinates so they align on the grid.
  392. minPt.x -= mFmod( minPt.x, squareSize );
  393. minPt.y -= mFmod( minPt.y, squareSize );
  394. F32 maxDeltaX = mFmod( maxPt.x, squareSize );
  395. F32 maxDeltaY = mFmod( maxPt.y, squareSize );
  396. if( maxDeltaX != 0.0f )
  397. maxPt.x += ( squareSize - maxDeltaX );
  398. if( maxDeltaY != 0.0f )
  399. maxPt.y += ( squareSize - maxDeltaY );
  400. // Add a safezone, so we don't touch the clipping planes.
  401. minPt.x -= squareSize; minPt.y -= squareSize;
  402. maxPt.x += squareSize; maxPt.y += squareSize;
  403. outMin = minPt;
  404. outMax = maxPt;
  405. }
  406. /// Generate a triangulated grid spanning the given bounds into the given buffers.
  407. void GroundPlane::generateGrid( U32 width, U32 height, F32 squareSize,
  408. const Point2F& min, const Point2F& max,
  409. GFXVertexBufferHandle< VertexType >& outVertices,
  410. GFXPrimitiveBufferHandle& outPrimitives )
  411. {
  412. // Generate the vertices.
  413. VertexType* vertices = outVertices.lock();
  414. for( F32 y = min.y; y <= max.y; y += squareSize )
  415. for( F32 x = min.x; x <= max.x; x += squareSize )
  416. {
  417. vertices->point.x = x;
  418. vertices->point.y = y;
  419. vertices->point.z = ( mZOffset );
  420. vertices->texCoord.x = ( x / squareSize ) * mScaleU;
  421. vertices->texCoord.y = ( y / squareSize ) * -mScaleV;
  422. vertices->normal.x = 0.0f;
  423. vertices->normal.y = 0.0f;
  424. vertices->normal.z = 1.0f;
  425. vertices->tangent.x = 1.0f;
  426. vertices->tangent.y = 0.0f;
  427. vertices->tangent.z = 0.0f;
  428. vertices->binormal.x = 0.0f;
  429. vertices->binormal.y = 1.0f;
  430. vertices->binormal.z = 0.0f;
  431. vertices++;
  432. }
  433. outVertices.unlock();
  434. // Generate the indices.
  435. U16* indices;
  436. outPrimitives.lock( &indices );
  437. U16 corner1 = 0;
  438. U16 corner2 = 1;
  439. U16 corner3 = width + 1;
  440. U16 corner4 = width + 2;
  441. for( U32 y = 0; y < height; ++ y )
  442. {
  443. for( U32 x = 0; x < width; ++ x )
  444. {
  445. indices[ 0 ] = corner3;
  446. indices[ 1 ] = corner2;
  447. indices[ 2 ] = corner1;
  448. indices += 3;
  449. indices[ 0 ] = corner3;
  450. indices[ 1 ] = corner4;
  451. indices[ 2 ] = corner2;
  452. indices += 3;
  453. corner1 ++;
  454. corner2 ++;
  455. corner3 ++;
  456. corner4 ++;
  457. }
  458. corner1 ++;
  459. corner2 ++;
  460. corner3 ++;
  461. corner4 ++;
  462. }
  463. outPrimitives.unlock();
  464. }
  465. DefineEngineMethod( GroundPlane, postApply, void, (),,
  466. "Intended as a helper to developers and editor scripts.\n"
  467. "Force trigger an inspectPostApply. This will transmit "
  468. "material and other fields to client objects."
  469. )
  470. {
  471. object->inspectPostApply();
  472. }