Call the correct system rename
[Torque-3d.git] / Engine / source / terrain / terrData.cpp
blobec102ba78115ff4eb4c1e8c2fd66ac361bfa1680
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:
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
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 //-----------------------------------------------------------------------------
23 #include "platform/platform.h"
24 #include "terrain/terrData.h"
26 #include "terrain/terrCollision.h"
27 #include "terrain/terrCell.h"
28 #include "terrain/terrRender.h"
29 #include "terrain/terrMaterial.h"
30 #include "terrain/terrCellMaterial.h"
31 #include "gui/worldEditor/terrainEditor.h"
32 #include "math/mathIO.h"
33 #include "core/stream/fileStream.h"
34 #include "core/stream/bitStream.h"
35 #include "console/consoleTypes.h"
36 #include "sim/netConnection.h"
37 #include "core/util/safeDelete.h"
38 #include "T3D/objectTypes.h"
39 #include "renderInstance/renderPassManager.h"
40 #include "scene/sceneRenderState.h"
41 #include "materials/materialManager.h"
42 #include "materials/baseMatInstance.h"
43 #include "gfx/gfxTextureManager.h"
44 #include "gfx/gfxCardProfile.h"
45 #include "core/resourceManager.h"
46 #include "T3D/physics/physicsPlugin.h"
47 #include "T3D/physics/physicsBody.h"
48 #include "T3D/physics/physicsCollision.h"
49 #include "console/engineAPI.h"
51 #include "console/engineAPI.h"
52 using namespace Torque;
54 IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock);
56 ConsoleDocClass( TerrainBlock,
57 "@brief Represent a terrain object in a Torque 3D level\n\n"
59 "@tsexample\n"
60 "new TerrainBlock(theTerrain)\n"
61 "{\n"
62 " terrainFile = \"art/terrains/Deathball Desert_0.ter\";\n"
63 " squareSize = \"2\";\n"
64 " tile = \"0\";\n"
65 " baseTexSize = \"1024\";\n"
66 " screenError = \"16\";\n"
67 " position = \"-1024 -1024 179.978\";\n"
68 " rotation = \"1 0 0 0\";\n"
69 " scale = \"1 1 1\";\n"
70 " isRenderEnabled = \"true\";\n"
71 " canSaveDynamicFields = \"1\";\n"
72 "};\n"
73 "@endtsexample\n\n"
75 "@see TerrainMaterial\n\n"
77 "@ingroup Terrain\n"
81 Signal<void(U32,TerrainBlock*,const Point2I& ,const Point2I&)> TerrainBlock::smUpdateSignal;
83 F32 TerrainBlock::smLODScale = 1.0f;
84 F32 TerrainBlock::smDetailScale = 1.0f;
87 //RBP - Global function declared in Terrdata.h
88 TerrainBlock* getTerrainUnderWorldPoint(const Point3F & wPos)
90 // Cast a ray straight down from the world position and see which
91 // Terrain is the closest to our starting point
92 Point3F startPnt = wPos;
93 Point3F endPnt = wPos + Point3F(0.0f, 0.0f, -10000.0f);
95 S32 blockIndex = -1;
96 F32 nearT = 1.0f;
98 SimpleQueryList queryList;
99 gServerContainer.findObjects( TerrainObjectType, SimpleQueryList::insertionCallback, &queryList);
101 for (U32 i = 0; i < queryList.mList.size(); i++)
103 Point3F tStartPnt, tEndPnt;
104 TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(queryList.mList[i]);
105 terrBlock->getWorldTransform().mulP(startPnt, &tStartPnt);
106 terrBlock->getWorldTransform().mulP(endPnt, &tEndPnt);
108 RayInfo ri;
109 if (terrBlock->castRayI(tStartPnt, tEndPnt, &ri, true))
111 if (ri.t < nearT)
113 blockIndex = i;
114 nearT = ri.t;
119 if (blockIndex > -1)
120 return (TerrainBlock*)(queryList.mList[blockIndex]);
122 return NULL;
126 ConsoleDocFragment _getTerrainUnderWorldPoint1(
127 "@brief Gets the terrain block that is located under the given world point\n\n"
128 "@param position The world space coordinate you wish to query at. Formatted as (\"x y z\")\n\n"
129 "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
130 "@ingroup Terrain",
131 NULL,
132 "bool getTerrainUnderWorldPoint( Point3F position );"
134 ConsoleDocFragment _getTerrainUnderWorldPoint2(
135 "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
136 "@param x The X coordinate in world space\n"
137 "@param y The Y coordinate in world space\n\n"
138 "@param z The Z coordinate in world space\n\n"
139 "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
140 "@ingroup Terrain",
141 NULL,
142 "bool getTerrainUnderWorldPoint( F32 x, F32 y, F32 z);"
145 DefineConsoleFunction( getTerrainUnderWorldPoint, S32, (const char* ptOrX, const char* y, const char* z), ("", ""),
146 "(Point3F x/y/z) Gets the terrain block that is located under the given world point.\n"
147 "@param x/y/z The world coordinates (floating point values) you wish to query at. "
148 "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n"
149 "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
150 "@hide")
152 Point3F pos;
153 if(!String::isEmpty(ptOrX) && String::isEmpty(y) && String::isEmpty(z))
154 dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
155 else if(!String::isEmpty(ptOrX) && !String::isEmpty(y) && !String::isEmpty(z))
157 pos.x = dAtof(ptOrX);
158 pos.y = dAtof(y);
159 pos.z = dAtof(z);
161 TerrainBlock* terrain = getTerrainUnderWorldPoint(pos);
162 if(terrain != NULL)
164 return terrain->getId();
166 return 0;
170 typedef TerrainBlock::BaseTexFormat baseTexFormat;
171 DefineEnumType(baseTexFormat);
173 ImplementEnumType(baseTexFormat,
174 "Description\n"
175 "@ingroup ?\n\n")
176 { TerrainBlock::NONE, "NONE", "No cached terrain.\n" },
177 { TerrainBlock::DDS, "DDS", "Cache the terrain in a DDS format.\n" },
178 { TerrainBlock::PNG, "PNG", "Cache the terrain in a PNG format.\n" },
179 { TerrainBlock::JPG, "JPG", "Cache the terrain in a JPG format.\n" },
180 EndImplementEnumType;
182 TerrainBlock::TerrainBlock()
183 : mLightMap( NULL ),
184 mLightMapSize( 256 ),
185 mCRC( 0 ),
186 mMaxDetailDistance( 0.0f ),
187 mBaseTexScaleConst( NULL ),
188 mBaseTexIdConst( NULL ),
189 mDetailsDirty( false ),
190 mLayerTexDirty( false ),
191 mBaseTexSize( 1024 ),
192 mBaseTexFormat( TerrainBlock::JPG ),
193 mCell( NULL ),
194 mBaseMaterial( NULL ),
195 mDefaultMatInst( NULL ),
196 mSquareSize( 1.0f ),
197 mPhysicsRep( NULL ),
198 mScreenError( 16 ),
199 mCastShadows( true ),
200 mZoningDirty( false )
202 mTypeMask = TerrainObjectType | StaticObjectType | StaticShapeObjectType;
203 mNetFlags.set(Ghostable | ScopeAlways);
207 extern Convex sTerrainConvexList;
209 TerrainBlock::~TerrainBlock()
211 // Kill collision
212 sTerrainConvexList.nukeList();
214 SAFE_DELETE(mLightMap);
215 mLightMapTex = NULL;
217 #ifdef TORQUE_TOOLS
218 TerrainEditor* editor = dynamic_cast<TerrainEditor*>(Sim::findObject("ETerrainEditor"));
219 if (editor)
220 editor->detachTerrain(this);
221 #endif
224 void TerrainBlock::_onTextureEvent( GFXTexCallbackCode code )
226 if ( code == GFXZombify )
228 if ( mBaseTex.isValid() &&
229 mBaseTex->isRenderTarget() )
230 mBaseTex = NULL;
232 mLayerTex = NULL;
233 mLightMapTex = NULL;
237 bool TerrainBlock::_setSquareSize( void *obj, const char *index, const char *data )
239 TerrainBlock *terrain = static_cast<TerrainBlock*>( obj );
241 F32 newSqaureSize = dAtof( data );
242 if ( !mIsEqual( terrain->mSquareSize, newSqaureSize ) )
244 terrain->mSquareSize = newSqaureSize;
246 if ( terrain->isServerObject() && terrain->isProperlyAdded() )
247 terrain->_updateBounds();
249 terrain->setMaskBits( HeightMapChangeMask | SizeMask );
252 return false;
255 bool TerrainBlock::_setBaseTexSize( void *obj, const char *index, const char *data )
257 TerrainBlock *terrain = static_cast<TerrainBlock*>( obj );
259 // NOTE: We're limiting the base texture size to
260 // 2048 as anything greater in size becomes too
261 // large to generate for many cards.
263 // If you want to remove this limit feel free, but
264 // prepare for problems if you don't ship the baked
265 // base texture with your installer.
268 S32 texSize = mClamp( dAtoi( data ), 0, 2048 );
269 if ( terrain->mBaseTexSize != texSize )
271 terrain->mBaseTexSize = texSize;
272 terrain->setMaskBits( MaterialMask );
275 return false;
278 bool TerrainBlock::_setBaseTexFormat(void *obj, const char *index, const char *data)
280 TerrainBlock *terrain = static_cast<TerrainBlock*>(obj);
282 EngineEnumTable eTable = _baseTexFormat::_sEnumTable;
284 for (U8 i = 0; i < eTable.getNumValues(); i++)
286 if (strcasecmp(eTable[i].mName, data) == 0)
288 terrain->mBaseTexFormat = (BaseTexFormat)eTable[i].mInt;
289 terrain->_updateMaterials();
291 if (terrain->isServerObject()) return false;
292 terrain->_updateLayerTexture();
293 // If the cached base texture is older that the terrain file or
294 // it doesn't exist then generate and cache it.
295 String baseCachePath = terrain->_getBaseTexCacheFileName();
296 if (Platform::compareModifiedTimes(baseCachePath, terrain->mTerrFileName) < 0)
297 terrain->_updateBaseTexture(true);
298 break;
302 return false;
305 bool TerrainBlock::_setLightMapSize( void *obj, const char *index, const char *data )
307 TerrainBlock *terrain = static_cast<TerrainBlock*>(obj);
309 // Handle inspector value decrements correctly
310 U32 mapSize = dAtoi( data );
311 if ( mapSize == terrain->mLightMapSize-1 )
312 mapSize = terrain->mLightMapSize/2;
314 // Limit the lightmap size, and ensure it is a power of 2
315 const U32 maxTextureSize = GFX->getCardProfiler()->queryProfile( "maxTextureSize", 1024 );
316 mapSize = mClamp( getNextPow2( mapSize ), 0, maxTextureSize );
318 if ( terrain->mLightMapSize != mapSize )
320 terrain->mLightMapSize = mapSize;
321 terrain->setMaskBits( MaterialMask );
324 return false;
327 bool TerrainBlock::setFile( const FileName &terrFileName )
329 if ( terrFileName == mTerrFileName )
330 return mFile != NULL;
332 Resource<TerrainFile> file = ResourceManager::get().load( terrFileName );
333 if( !file )
334 return false;
336 setFile( file );
337 setMaskBits( FileMask | HeightMapChangeMask );
339 return true;
342 void TerrainBlock::setFile(const Resource<TerrainFile>& terr)
344 mFile = terr;
345 mTerrFileName = terr.getPath();
348 bool TerrainBlock::save(const char *filename)
350 return mFile->save(filename);
353 bool TerrainBlock::_setTerrainFile( void *obj, const char *index, const char *data )
355 static_cast<TerrainBlock*>( obj )->setFile( FileName( data ) );
356 return false;
359 void TerrainBlock::_updateBounds()
361 if ( !mFile )
362 return; // quick fix to stop crashing when deleting terrainblocks
364 // Setup our object space bounds.
365 mBounds.minExtents.set( 0.0f, 0.0f, 0.0f );
366 mBounds.maxExtents.set( getWorldBlockSize(), getWorldBlockSize(), 0.0f );
367 getMinMaxHeight( &mBounds.minExtents.z, &mBounds.maxExtents.z );
369 // Set our mObjBox to be equal to mBounds
370 if ( mObjBox.maxExtents != mBounds.maxExtents ||
371 mObjBox.minExtents != mBounds.minExtents )
373 mObjBox = mBounds;
374 resetWorldBox();
378 void TerrainBlock::_onZoningChanged( SceneZoneSpaceManager *zoneManager )
380 if ( mCell == NULL || zoneManager != getSceneManager()->getZoneManager() )
381 return;
383 mZoningDirty = true;
386 void TerrainBlock::setHeight( const Point2I &pos, F32 height )
388 U16 ht = floatToFixed( height );
389 mFile->setHeight( pos.x, pos.y, ht );
391 // Note: We do not update the grid here as this could
392 // be called several times in a loop. We depend on the
393 // caller doing a grid update when he is done.
396 F32 TerrainBlock::getHeight( const Point2I &pos )
398 U16 ht = mFile->getHeight( pos.x, pos.y );
399 return fixedToFloat( ht );
402 void TerrainBlock::updateGridMaterials( const Point2I &minPt, const Point2I &maxPt )
404 if ( mCell )
406 // Tell the terrain cell that something changed.
407 const RectI gridRect( minPt, maxPt - minPt );
408 mCell->updateGrid( gridRect, true );
411 // We mark us as dirty... it will be updated
412 // before the next time we render the terrain.
413 mLayerTexDirty = true;
415 // Signal anyone that cares that the opacity was changed.
416 smUpdateSignal.trigger( LayersUpdate, this, minPt, maxPt );
420 Point2I TerrainBlock::getGridPos( const Point3F &worldPos ) const
422 Point3F terrainPos = worldPos;
423 getWorldTransform().mulP( terrainPos );
425 F32 squareSize = ( F32 ) getSquareSize();
426 F32 halfSquareSize = squareSize / 2.0;
428 F32 x = ( terrainPos.x + halfSquareSize ) / squareSize;
429 F32 y = ( terrainPos.y + halfSquareSize ) / squareSize;
431 Point2I gridPos( ( S32 ) mFloor( x ), ( S32 ) mFloor( y ) );
432 return gridPos;
435 void TerrainBlock::updateGrid( const Point2I &minPt, const Point2I &maxPt, bool updateClient )
437 // On the client we just signal everyone that the height
438 // map has changed... the server does the actual changes.
439 if ( isClientObject() )
441 PROFILE_SCOPE( TerrainBlock_updateGrid_Client );
443 // This depends on the client getting this call 'after' the server.
444 // Which is currently the case.
445 _updateBounds();
446 mZoningDirty = true;
448 smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt );
450 // Tell the terrain cell that the height changed.
451 const RectI gridRect( minPt, maxPt - minPt );
452 mCell->updateGrid( gridRect );
454 // Rebuild the physics representation.
455 if ( mPhysicsRep )
457 // Delay the update by a few milliseconds so
458 // that we're not rebuilding during an active
459 // editing operation.
460 mPhysicsRep->queueCallback( 500, Delegate<void()>( this, &TerrainBlock::_updatePhysics ) );
463 return;
466 // Now on the server we rebuild the
467 // affected area of the grid map.
468 mFile->updateGrid( minPt, maxPt );
470 // Fix up the bounds.
471 _updateBounds();
473 // Rebuild the physics representation.
474 if ( mPhysicsRep )
476 // Delay the update by a few milliseconds so
477 // that we're not rebuilding during an active
478 // editing operation.
479 mPhysicsRep->queueCallback( 500, Delegate<void()>( this, &TerrainBlock::_updatePhysics ) );
482 // Signal again here for any server side observers.
483 smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt );
485 // If this is a server object and the client update
486 // was requested then try to use the local connection
487 // pointer to do it.
488 if ( updateClient && getClientObject() )
489 ((TerrainBlock*)getClientObject())->updateGrid( minPt, maxPt, false );
492 bool TerrainBlock::getHeight( const Point2F &pos, F32 *height ) const
494 PROFILE_SCOPE( TerrainBlock_getHeight );
496 F32 invSquareSize = 1.0f / mSquareSize;
497 F32 xp = pos.x * invSquareSize;
498 F32 yp = pos.y * invSquareSize;
499 S32 x = S32(xp);
500 S32 y = S32(yp);
501 xp -= (F32)x;
502 yp -= (F32)y;
504 const U32 blockMask = mFile->mSize - 1;
506 if ( x & ~blockMask || y & ~blockMask )
507 return false;
509 x &= blockMask;
510 y &= blockMask;
512 const TerrainSquare *sq = mFile->findSquare( 0, x, y );
513 if ( sq->flags & TerrainSquare::Empty )
514 return false;
516 F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) );
517 F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) );
518 F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) );
519 F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) );
521 if ( sq->flags & TerrainSquare::Split45 )
523 if (xp>yp)
524 // bottom half
525 *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
526 else
527 // top half
528 *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
530 else
532 if (1.0f-xp>yp)
533 // bottom half
534 *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
535 else
536 // top half
537 *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
540 return true;
543 bool TerrainBlock::getNormal( const Point2F &pos, Point3F *normal, bool normalize, bool skipEmpty ) const
545 PROFILE_SCOPE( TerrainBlock_getNormal );
547 F32 invSquareSize = 1.0f / mSquareSize;
548 F32 xp = pos.x * invSquareSize;
549 F32 yp = pos.y * invSquareSize;
550 S32 x = S32(xp);
551 S32 y = S32(yp);
552 xp -= (F32)x;
553 yp -= (F32)y;
555 const U32 blockMask = mFile->mSize - 1;
557 if ( x & ~blockMask || y & ~blockMask )
558 return false;
560 x &= blockMask;
561 y &= blockMask;
563 const TerrainSquare *sq = mFile->findSquare( 0, x, y );
564 if ( skipEmpty && sq->flags & TerrainSquare::Empty )
565 return false;
567 F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) );
568 F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) );
569 F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) );
570 F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) );
572 if ( sq->flags & TerrainSquare::Split45 )
574 if (xp>yp)
575 // bottom half
576 normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
577 else
578 // top half
579 normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
581 else
583 if (1.0f-xp>yp)
584 // bottom half
585 normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
586 else
587 // top half
588 normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
591 if (normalize)
592 normal->normalize();
594 return true;
597 bool TerrainBlock::getSmoothNormal( const Point2F &pos,
598 Point3F *normal,
599 bool normalize,
600 bool skipEmpty ) const
602 PROFILE_SCOPE( TerrainBlock_getSmoothNormal );
604 F32 invSquareSize = 1.0f / mSquareSize;
605 F32 xp = pos.x * invSquareSize;
606 F32 yp = pos.y * invSquareSize;
607 S32 x = S32(xp);
608 S32 y = S32(yp);
610 const U32 blockMask = mFile->mSize - 1;
612 if ( x & ~blockMask || y & ~blockMask )
613 return false;
615 x &= blockMask;
616 y &= blockMask;
618 const TerrainSquare *sq = mFile->findSquare( 0, x, y );
619 if ( skipEmpty && sq->flags & TerrainSquare::Empty )
620 return false;
622 F32 h1 = fixedToFloat( mFile->getHeight( x + 1, y ) );
623 F32 h2 = fixedToFloat( mFile->getHeight( x, y + 1 ) );
624 F32 h3 = fixedToFloat( mFile->getHeight( x - 1, y ) );
625 F32 h4 = fixedToFloat( mFile->getHeight( x, y - 1 ) );
627 normal->set( h3 - h1, h4 - h2, mSquareSize * 2.0f );
629 if ( normalize )
630 normal->normalize();
632 return true;
635 bool TerrainBlock::getNormalAndHeight( const Point2F &pos, Point3F *normal, F32 *height, bool normalize ) const
637 PROFILE_SCOPE( TerrainBlock_getNormalAndHeight );
639 F32 invSquareSize = 1.0f / mSquareSize;
640 F32 xp = pos.x * invSquareSize;
641 F32 yp = pos.y * invSquareSize;
642 S32 x = S32(xp);
643 S32 y = S32(yp);
644 xp -= (F32)x;
645 yp -= (F32)y;
647 const U32 blockMask = mFile->mSize - 1;
649 if ( x & ~blockMask || y & ~blockMask )
650 return false;
652 x &= blockMask;
653 y &= blockMask;
655 const TerrainSquare *sq = mFile->findSquare( 0, x, y );
656 if ( sq->flags & TerrainSquare::Empty )
657 return false;
659 F32 zBottomLeft = fixedToFloat( mFile->getHeight(x, y) );
660 F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) );
661 F32 zTopLeft = fixedToFloat( mFile->getHeight(x, y + 1) );
662 F32 zTopRight = fixedToFloat( mFile->getHeight(x + 1, y + 1) );
664 if ( sq->flags & TerrainSquare::Split45 )
666 if (xp>yp)
668 // bottom half
669 normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
670 *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
672 else
674 // top half
675 normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
676 *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
679 else
681 if (1.0f-xp>yp)
683 // bottom half
684 normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
685 *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
687 else
689 // top half
690 normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
691 *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
695 if (normalize)
696 normal->normalize();
698 return true;
702 bool TerrainBlock::getNormalHeightMaterial( const Point2F &pos,
703 Point3F *normal,
704 F32 *height,
705 StringTableEntry &matName ) const
707 PROFILE_SCOPE( TerrainBlock_getNormalHeightMaterial );
709 F32 invSquareSize = 1.0f / mSquareSize;
710 F32 xp = pos.x * invSquareSize;
711 F32 yp = pos.y * invSquareSize;
712 S32 x = S32(xp);
713 S32 y = S32(yp);
714 S32 xm = S32(mFloor( xp + 0.5f ));
715 S32 ym = S32(mFloor( yp + 0.5f ));
716 xp -= (F32)x;
717 yp -= (F32)y;
719 const U32 blockMask = mFile->mSize - 1;
721 if ( x & ~blockMask || y & ~blockMask )
722 return false;
724 x &= blockMask;
725 y &= blockMask;
727 const TerrainSquare *sq = mFile->findSquare( 0, x, y );
728 if ( sq->flags & TerrainSquare::Empty )
729 return false;
731 F32 zBottomLeft = fixedToFloat( mFile->getHeight(x, y) );
732 F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) );
733 F32 zTopLeft = fixedToFloat( mFile->getHeight(x, y + 1) );
734 F32 zTopRight = fixedToFloat( mFile->getHeight(x + 1, y + 1) );
736 matName = mFile->getMaterialName( xm, ym );
738 if ( sq->flags & TerrainSquare::Split45 )
740 if (xp>yp)
742 // bottom half
743 normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
744 *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
746 else
748 // top half
749 normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
750 *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
753 else
755 if (1.0f-xp>yp)
757 // bottom half
758 normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
759 *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
761 else
763 // top half
764 normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
765 *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
769 normal->normalize();
771 return true;
774 U32 TerrainBlock::getMaterialCount() const
776 return mFile->mMaterials.size();
779 void TerrainBlock::addMaterial( const String &name, U32 insertAt )
781 TerrainMaterial *mat = TerrainMaterial::findOrCreate( name );
783 if ( insertAt == -1 )
785 mFile->mMaterials.push_back( mat );
786 mFile->_initMaterialInstMapping();
788 else
791 // TODO: Insert and reindex!
795 mDetailsDirty = true;
796 mLayerTexDirty = true;
799 void TerrainBlock::removeMaterial( U32 index )
801 // Cannot delete if only one layer.
802 if ( mFile->mMaterials.size() == 1 )
803 return;
805 mFile->mMaterials.erase( index );
806 mFile->_initMaterialInstMapping();
808 for ( S32 i = 0; i < mFile->mLayerMap.size(); i++ )
810 if ( mFile->mLayerMap[i] >= index &&
811 mFile->mLayerMap[i] != 0 )
813 mFile->mLayerMap[i]--;
817 mDetailsDirty = true;
818 mLayerTexDirty = true;
821 void TerrainBlock::updateMaterial( U32 index, const String &name )
823 if ( index >= mFile->mMaterials.size() )
824 return;
826 mFile->mMaterials[ index ] = TerrainMaterial::findOrCreate( name );
827 mFile->_initMaterialInstMapping();
829 mDetailsDirty = true;
830 mLayerTexDirty = true;
833 TerrainMaterial* TerrainBlock::getMaterial( U32 index ) const
835 if ( index >= mFile->mMaterials.size() )
836 return NULL;
838 return mFile->mMaterials[ index ];
841 void TerrainBlock::deleteAllMaterials()
843 mFile->mMaterials.clear();
844 mFile->mMaterialInstMapping.clearMatInstList();
847 const char* TerrainBlock::getMaterialName( U32 index ) const
849 if ( index < mFile->mMaterials.size() )
850 return mFile->mMaterials[ index ]->getInternalName();
852 return NULL;
855 void TerrainBlock::setLightMap( GBitmap *newLightMap )
857 SAFE_DELETE( mLightMap );
858 mLightMap = newLightMap;
859 mLightMapTex = NULL;
862 void TerrainBlock::clearLightMap()
864 if ( !mLightMap )
865 mLightMap = new GBitmap( mLightMapSize, mLightMapSize, 0, GFXFormatR8G8B8 );
867 mLightMap->fillWhite();
868 mLightMapTex = NULL;
871 GFXTextureObject* TerrainBlock::getLightMapTex()
873 if ( mLightMapTex.isNull() && mLightMap )
875 mLightMapTex.set( mLightMap,
876 &GFXDefaultStaticDiffuseProfile,
877 false,
878 "TerrainBlock::getLightMapTex()" );
881 return mLightMapTex;
884 void TerrainBlock::onEditorEnable()
888 void TerrainBlock::onEditorDisable()
892 bool TerrainBlock::onAdd()
894 if(!Parent::onAdd())
895 return false;
897 if ( mTerrFileName.isEmpty() )
899 mTerrFileName = Con::getVariable( "$Client::MissionFile" );
900 String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
901 if ( terrainDirectory.isEmpty() )
903 terrainDirectory = "art/terrains/";
905 mTerrFileName.replace("tools/levels/", terrainDirectory);
906 mTerrFileName.replace("levels/", terrainDirectory);
908 Vector<String> materials;
909 materials.push_back( "warning_material" );
910 TerrainFile::create( &mTerrFileName, 256, materials );
913 Resource<TerrainFile> terr = ResourceManager::get().load( mTerrFileName );
914 if(terr == NULL)
916 if(isClientObject())
917 NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName.c_str());
918 return false;
921 setFile( terr );
923 if ( terr->mNeedsResaving )
925 if (Platform::messageBox("Update Terrain File", "You appear to have a Terrain file in an older format. Do you want Torque to update it?", MBOkCancel, MIQuestion) == MROk)
927 terr->save(terr->mFilePath.getFullPath());
928 terr->mNeedsResaving = false;
932 if (terr->mFileVersion != TerrainFile::FILE_VERSION || terr->mNeedsResaving)
934 Con::errorf(" *********************************************************");
935 Con::errorf(" *********************************************************");
936 Con::errorf(" *********************************************************");
937 Con::errorf(" PLEASE RESAVE THE TERRAIN FILE FOR THIS MISSION! THANKS!");
938 Con::errorf(" *********************************************************");
939 Con::errorf(" *********************************************************");
940 Con::errorf(" *********************************************************");
943 _updateBounds();
945 resetWorldBox();
946 setRenderTransform(mObjToWorld);
948 if (isClientObject())
950 if ( mCRC != terr.getChecksum() )
952 NetConnection::setLastError("Your terrain file doesn't match the version that is running on the server.");
953 return false;
956 clearLightMap();
958 // Init the detail layer rendering helper.
959 _updateMaterials();
960 _updateLayerTexture();
962 // If the cached base texture is older that the terrain file or
963 // it doesn't exist then generate and cache it.
964 String baseCachePath = _getBaseTexCacheFileName();
965 if ( Platform::compareModifiedTimes( baseCachePath, mTerrFileName ) < 0 )
966 _updateBaseTexture( true );
968 // The base texture should have been cached by now... so load it.
969 mBaseTex.set( baseCachePath, &GFXDefaultStaticDiffuseProfile, "TerrainBlock::mBaseTex" );
971 GFXTextureManager::addEventDelegate( this, &TerrainBlock::_onTextureEvent );
972 MATMGR->getFlushSignal().notify( this, &TerrainBlock::_onFlushMaterials );
974 // Build the terrain quadtree.
975 _rebuildQuadtree();
977 // Preload all the materials.
978 mCell->preloadMaterials();
980 mZoningDirty = true;
981 SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &TerrainBlock::_onZoningChanged );
983 else
984 mCRC = terr.getChecksum();
986 addToScene();
988 _updatePhysics();
990 return true;
993 String TerrainBlock::_getBaseTexCacheFileName() const
995 Torque::Path basePath( mTerrFileName );
996 basePath.setFileName( basePath.getFileName() + "_basetex" );
997 basePath.setExtension( formatToExtension(mBaseTexFormat) );
998 return basePath.getFullPath();
1001 void TerrainBlock::_rebuildQuadtree()
1003 SAFE_DELETE( mCell );
1005 // Recursively build the cells.
1006 mCell = TerrCell::init( this );
1008 // Build the shared PrimitiveBuffer.
1009 mCell->createPrimBuffer( &mPrimBuffer );
1012 void TerrainBlock::_updatePhysics()
1014 if ( !PHYSICSMGR )
1015 return;
1017 SAFE_DELETE( mPhysicsRep );
1019 PhysicsCollision *colShape;
1021 // If we can steal the collision shape from the local server
1022 // object then do so as it saves us alot of cpu time and memory.
1024 // TODO: We should move this sharing down into TerrFile where
1025 // it probably belongs.
1027 if ( getServerObject() )
1029 TerrainBlock *serverTerrain = (TerrainBlock*)getServerObject();
1030 colShape = serverTerrain->mPhysicsRep->getColShape();
1032 else
1034 // Get empty state of each vert
1035 bool *holes = new bool[ getBlockSize() * getBlockSize() ];
1036 for ( U32 row = 0; row < getBlockSize(); row++ )
1037 for ( U32 column = 0; column < getBlockSize(); column++ )
1038 holes[ row + (column * getBlockSize()) ] = mFile->isEmptyAt( row, column );
1040 colShape = PHYSICSMGR->createCollision();
1041 colShape->addHeightfield( mFile->getHeightMap().address(), holes, getBlockSize(), mSquareSize, MatrixF::Identity );
1043 delete [] holes;
1046 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
1047 mPhysicsRep = PHYSICSMGR->createBody();
1048 mPhysicsRep->init( colShape, 0, 0, this, world );
1049 mPhysicsRep->setTransform( getTransform() );
1052 void TerrainBlock::onRemove()
1054 removeFromScene();
1055 SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &TerrainBlock::_onZoningChanged );
1057 SAFE_DELETE( mPhysicsRep );
1059 if ( isClientObject() )
1061 mBaseTex = NULL;
1062 mLayerTex = NULL;
1063 SAFE_DELETE( mBaseMaterial );
1064 SAFE_DELETE( mDefaultMatInst );
1065 SAFE_DELETE( mCell );
1066 mPrimBuffer = NULL;
1067 mBaseShader = NULL;
1068 GFXTextureManager::removeEventDelegate( this, &TerrainBlock::_onTextureEvent );
1069 MATMGR->getFlushSignal().remove( this, &TerrainBlock::_onFlushMaterials );
1072 Parent::onRemove();
1075 void TerrainBlock::prepRenderImage( SceneRenderState* state )
1077 PROFILE_SCOPE(TerrainBlock_prepRenderImage);
1079 // If we need to update our cached
1080 // zone state then do it now.
1081 if ( mZoningDirty )
1083 mZoningDirty = false;
1084 mCell->updateZoning( getSceneManager()->getZoneManager() );
1087 _renderBlock( state );
1090 void TerrainBlock::setTransform(const MatrixF & mat)
1092 Parent::setTransform( mat );
1094 // Update world-space OBBs.
1095 if( mCell )
1097 mCell->updateOBBs();
1098 mZoningDirty = true;
1101 if ( mPhysicsRep )
1102 mPhysicsRep->setTransform( mat );
1104 setRenderTransform( mat );
1105 setMaskBits( TransformMask );
1107 if(isClientObject())
1108 smUpdateSignal.trigger( HeightmapUpdate, this, Point2I::Zero, Point2I::Max );
1111 void TerrainBlock::setScale( const VectorF &scale )
1113 // We disable scaling... we never scale!
1114 Parent::setScale( VectorF::One );
1117 void TerrainBlock::initPersistFields()
1119 addGroup( "Media" );
1121 addProtectedField( "terrainFile", TypeStringFilename, Offset( mTerrFileName, TerrainBlock ),
1122 &TerrainBlock::_setTerrainFile, &defaultProtectedGetFn,
1123 "The source terrain data file." );
1125 endGroup( "Media" );
1127 addGroup( "Misc" );
1129 addField( "castShadows", TypeBool, Offset( mCastShadows, TerrainBlock ),
1130 "Allows the terrain to cast shadows onto itself and other objects.");
1132 addProtectedField( "squareSize", TypeF32, Offset( mSquareSize, TerrainBlock ),
1133 &TerrainBlock::_setSquareSize, &defaultProtectedGetFn,
1134 "Indicates the spacing between points on the XY plane on the terrain." );
1136 addProtectedField( "baseTexSize", TypeS32, Offset( mBaseTexSize, TerrainBlock ),
1137 &TerrainBlock::_setBaseTexSize, &defaultProtectedGetFn,
1138 "Size of base texture size per meter." );
1140 addProtectedField("baseTexFormat", TYPEID<baseTexFormat>(), Offset(mBaseTexFormat, TerrainBlock),
1141 &TerrainBlock::_setBaseTexFormat, &defaultProtectedGetFn,
1142 "");
1144 addProtectedField( "lightMapSize", TypeS32, Offset( mLightMapSize, TerrainBlock ),
1145 &TerrainBlock::_setLightMapSize, &defaultProtectedGetFn,
1146 "Light map dimensions in pixels." );
1148 addField( "screenError", TypeS32, Offset( mScreenError, TerrainBlock ), "Not yet implemented." );
1150 endGroup( "Misc" );
1152 Parent::initPersistFields();
1154 removeField( "scale" );
1156 Con::addVariable( "$TerrainBlock::debugRender", TypeBool, &smDebugRender, "Triggers debug rendering of terrain cells\n\n"
1157 "@ingroup Terrain");
1159 Con::addVariable( "$pref::Terrain::lodScale", TypeF32, &smLODScale, "A global LOD scale used to tweak the default terrain screen error value.\n\n"
1160 "@ingroup Terrain");
1162 Con::addVariable( "$pref::Terrain::detailScale", TypeF32, &smDetailScale, "A global detail scale used to tweak the material detail distances.\n\n"
1163 "@ingroup Terrain");
1166 void TerrainBlock::inspectPostApply()
1168 Parent::inspectPostApply();
1169 setMaskBits( MiscMask );
1172 U32 TerrainBlock::packUpdate(NetConnection* con, U32 mask, BitStream *stream)
1174 U32 retMask = Parent::packUpdate( con, mask, stream );
1176 if ( stream->writeFlag( mask & TransformMask ) )
1177 mathWrite( *stream, getTransform() );
1179 if ( stream->writeFlag( mask & FileMask ) )
1181 stream->write( mTerrFileName );
1182 stream->write( mCRC );
1185 if ( stream->writeFlag( mask & SizeMask ) )
1186 stream->write( mSquareSize );
1188 stream->writeFlag( mCastShadows );
1190 if ( stream->writeFlag( mask & MaterialMask ) )
1192 stream->write( mBaseTexSize );
1193 stream->write( mLightMapSize );
1196 stream->writeFlag( mask & HeightMapChangeMask );
1198 if ( stream->writeFlag( mask & MiscMask ) )
1199 stream->write( mScreenError );
1201 stream->writeInt(mBaseTexFormat, 32);
1203 return retMask;
1206 void TerrainBlock::unpackUpdate(NetConnection* con, BitStream *stream)
1208 Parent::unpackUpdate( con, stream );
1210 if ( stream->readFlag() ) // TransformMask
1212 MatrixF mat;
1213 mathRead( *stream, &mat );
1214 setTransform( mat );
1217 if ( stream->readFlag() ) // FileMask
1219 FileName terrFile;
1220 stream->read( &terrFile );
1221 stream->read( &mCRC );
1223 if ( isProperlyAdded() )
1224 setFile( terrFile );
1225 else
1226 mTerrFileName = terrFile;
1229 if ( stream->readFlag() ) // SizeMask
1230 stream->read( &mSquareSize );
1232 mCastShadows = stream->readFlag();
1234 if ( stream->readFlag() ) // MaterialMask
1236 U32 baseTexSize;
1237 stream->read( &baseTexSize );
1238 if ( mBaseTexSize != baseTexSize )
1240 mBaseTexSize = baseTexSize;
1241 if ( isProperlyAdded() )
1242 _updateBaseTexture( NONE );
1245 U32 lightMapSize;
1246 stream->read( &lightMapSize );
1247 if ( mLightMapSize != lightMapSize )
1249 mLightMapSize = lightMapSize;
1250 if ( isProperlyAdded() )
1252 SAFE_DELETE( mLightMap );
1253 clearLightMap();
1258 if ( stream->readFlag() && isProperlyAdded() ) // HeightMapChangeMask
1260 _updateBounds();
1261 _rebuildQuadtree();
1262 _updatePhysics();
1263 mDetailsDirty = true;
1264 mLayerTexDirty = true;
1267 if ( stream->readFlag() ) // MiscMask
1268 stream->read( &mScreenError );
1270 mBaseTexFormat = (BaseTexFormat)stream->readInt(32);
1273 void TerrainBlock::getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const
1275 // We can get the bound height from the last grid level.
1276 const TerrainSquare *sq = mFile->findSquare( mFile->mGridLevels, 0, 0 );
1277 *minHeight = fixedToFloat( sq->minHeight );
1278 *maxHeight = fixedToFloat( sq->maxHeight );
1281 //-----------------------------------------------------------------------------
1282 // Console Methods
1283 //-----------------------------------------------------------------------------
1285 DefineEngineMethod( TerrainBlock, save, bool, ( const char* fileName),,
1286 "@brief Saves the terrain block's terrain file to the specified file name.\n\n"
1288 "@param fileName Name and path of file to save terrain data to.\n\n"
1290 "@return True if file save was successful, false otherwise")
1292 char filename[256];
1293 dStrcpy(filename,fileName);
1294 char *ext = dStrrchr(filename, '.');
1295 if (!ext || dStricmp(ext, ".ter") != 0)
1296 dStrcat(filename, ".ter");
1297 return static_cast<TerrainBlock*>(object)->save(filename);
1300 //ConsoleMethod(TerrainBlock, save, bool, 3, 3, "(string fileName) - saves the terrain block's terrain file to the specified file name.")
1302 // char filename[256];
1303 // dStrcpy(filename,argv[2]);
1304 // char *ext = dStrrchr(filename, '.');
1305 // if (!ext || dStricmp(ext, ".ter") != 0)
1306 // dStrcat(filename, ".ter");
1307 // return static_cast<TerrainBlock*>(object)->save(filename);
1310 ConsoleDocFragment _getTerrainHeight1(
1311 "@brief Gets the terrain height at the specified position\n\n"
1312 "@param position The world space point, minus the z (height) value. Formatted as (\"x y\")\n\n"
1313 "@return Returns the terrain height at the given point as an F32 value.\n\n"
1314 "@ingroup Terrain",
1315 NULL,
1316 "bool getTerrainHeight( Point2I position );"
1318 ConsoleDocFragment _getTerrainHeight2(
1319 "@brief Gets the terrain height at the specified position\n\n"
1320 "@param x The X coordinate in world space\n"
1321 "@param y The Y coordinate in world space\n\n"
1322 "@return Returns the terrain height at the given point as an F32 value.\n\n"
1323 "@ingroup Terrain",
1324 NULL,
1325 "bool getTerrainHeight( F32 x, F32 y);"
1328 DefineConsoleFunction( getTerrainHeight, F32, (const char* ptOrX, const char* y), (""), "(Point2 pos) - gets the terrain height at the specified position."
1329 "@param pos The world space point, minus the z (height) value\n Can be formatted as either (\"x y\") or (x,y)\n"
1330 "@return Returns the terrain height at the given point as an F32 value.\n"
1331 "@hide")
1333 F32 height = 0.0f;
1335 Point2F pos;
1336 if(!String::isEmpty(ptOrX) && String::isEmpty(y))
1337 dSscanf(ptOrX, "%f %f", &pos.x, &pos.y);
1338 else if(!String::isEmpty(ptOrX) && !String::isEmpty(y))
1340 pos.x = dAtof(ptOrX);
1341 pos.y = dAtof(y);
1344 TerrainBlock * terrain = getTerrainUnderWorldPoint(Point3F(pos.x, pos.y, 5000.0f));
1345 if(terrain && terrain->isServerObject())
1347 Point3F offset;
1348 terrain->getTransform().getColumn(3, &offset);
1349 pos -= Point2F(offset.x, offset.y);
1350 terrain->getHeight(pos, &height);
1352 return height;
1355 ConsoleDocFragment _getTerrainHeightBelowPosition1(
1356 "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
1357 "@param position The world space point, minus the z (height) value. Formatted as (\"x y\")\n\n"
1358 "@return Returns the closest terrain height below the given point as an F32 value.\n\n"
1359 "@ingroup Terrain",
1360 NULL,
1361 "bool getTerrainHeightBelowPosition( Point2I position );"
1363 ConsoleDocFragment _getTerrainHeightBelowPosition2(
1364 "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
1365 "@param x The X coordinate in world space\n"
1366 "@param y The Y coordinate in world space\n\n"
1367 "@return Returns the closest terrain height below the given point as an F32 value.\n\n"
1368 "@ingroup Terrain",
1369 NULL,
1370 "bool getTerrainHeightBelowPosition( F32 x, F32 y);"
1373 DefineConsoleFunction( getTerrainHeightBelowPosition, F32, (const char* ptOrX, const char* y, const char* z), ("", ""),
1374 "(Point3F pos) - gets the terrain height at the specified position."
1375 "@param pos The world space point. Can be formatted as either (\"x y z\") or (x,y,z)\n"
1376 "@note This function is useful if you simply want to grab the terrain height underneath an object.\n"
1377 "@return Returns the terrain height at the given point as an F32 value.\n"
1378 "@hide")
1380 F32 height = 0.0f;
1382 Point3F pos;
1383 if(!String::isEmpty(ptOrX) && String::isEmpty(y) && String::isEmpty(z))
1384 dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
1385 else if(!String::isEmpty(ptOrX) && !String::isEmpty(y) && !String::isEmpty(z))
1387 pos.x = dAtof(ptOrX);
1388 pos.y = dAtof(y);
1389 pos.z = dAtof(z);
1392 TerrainBlock * terrain = getTerrainUnderWorldPoint(pos);
1394 Point2F nohghtPos(pos.x, pos.y);
1396 if(terrain)
1398 if(terrain->isServerObject())
1400 Point3F offset;
1401 terrain->getTransform().getColumn(3, &offset);
1402 nohghtPos -= Point2F(offset.x, offset.y);
1403 terrain->getHeight(nohghtPos, &height);
1407 return height;