Torque3D Documentation / _generateds / groundPlane.cpp

groundPlane.cpp

Engine/source/T3D/groundPlane.cpp

More...

Public Variables

Minimum square size allowed.

Public Functions

ConsoleDocClass(GroundPlane , "@brief An infinite plane extending in all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">direction.\n\n</a>" "%<a href="/coding/class/classgroundplane/">GroundPlane</a> is useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> setting up simple testing scenes, or it can be " "placed under an existing scene <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> keep objects from falling into 'nothing'.\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "%<a href="/coding/class/classgroundplane/">GroundPlane</a> may not be moved or rotated, it is always at the world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">origin.\n\n</a>" " @ingroup Terrain" )
DefineEngineMethod(GroundPlane , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "material and other fields <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )

Detailed Description

Public Variables

const F32 sMIN_SQUARE_SIZE 

Minimum square size allowed.

This is a cheap way to limit the amount of geometry possibly generated by the GroundPlane (vertex buffers have a limit, too). Dynamically clipping extents into range is a problem since the location of the horizon depends on the camera orientation. Just shifting squareSize as needed also doesn't work as that causes different geometry to be generated depending on the viewpoint and orientation which affects the texturing.

Public Functions

ConsoleDocClass(GroundPlane , "@brief An infinite plane extending in all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">direction.\n\n</a>" "%<a href="/coding/class/classgroundplane/">GroundPlane</a> is useful <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> setting up simple testing scenes, or it can be " "placed under an existing scene <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> keep objects from falling into 'nothing'.\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "%<a href="/coding/class/classgroundplane/">GroundPlane</a> may not be moved or rotated, it is always at the world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">origin.\n\n</a>" " @ingroup Terrain" )

DefineEngineMethod(GroundPlane , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "material and other fields <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )

IMPLEMENT_CO_NETOBJECT_V1(GroundPlane )

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