1 //-----------------------------------------------------------------------------
2 // Copyright (c) 2012 GarageGames, LLC
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
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"
60 "new TerrainBlock(theTerrain)\n"
62 " terrainFile = \"art/terrains/Deathball Desert_0.ter\";\n"
63 " squareSize = \"2\";\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"
75 "@see TerrainMaterial\n\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
);
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
);
109 if (terrBlock
->castRayI(tStartPnt
, tEndPnt
, &ri
, true))
120 return (TerrainBlock
*)(queryList
.mList
[blockIndex
]);
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"
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"
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"
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
);
161 TerrainBlock
* terrain
= getTerrainUnderWorldPoint(pos
);
164 return terrain
->getId();
170 typedef TerrainBlock::BaseTexFormat baseTexFormat
;
171 DefineEnumType(baseTexFormat
);
173 ImplementEnumType(baseTexFormat
,
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()
184 mLightMapSize( 256 ),
186 mMaxDetailDistance( 0.0f
),
187 mBaseTexScaleConst( NULL
),
188 mBaseTexIdConst( NULL
),
189 mDetailsDirty( false ),
190 mLayerTexDirty( false ),
191 mBaseTexSize( 1024 ),
192 mBaseTexFormat( TerrainBlock::JPG
),
194 mBaseMaterial( NULL
),
195 mDefaultMatInst( NULL
),
199 mCastShadows( true ),
200 mZoningDirty( false )
202 mTypeMask
= TerrainObjectType
| StaticObjectType
| StaticShapeObjectType
;
203 mNetFlags
.set(Ghostable
| ScopeAlways
);
207 extern Convex sTerrainConvexList
;
209 TerrainBlock::~TerrainBlock()
212 sTerrainConvexList
.nukeList();
214 SAFE_DELETE(mLightMap
);
218 TerrainEditor
* editor
= dynamic_cast<TerrainEditor
*>(Sim::findObject("ETerrainEditor"));
220 editor
->detachTerrain(this);
224 void TerrainBlock::_onTextureEvent( GFXTexCallbackCode code
)
226 if ( code
== GFXZombify
)
228 if ( mBaseTex
.isValid() &&
229 mBaseTex
->isRenderTarget() )
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
);
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
);
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);
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
);
327 bool TerrainBlock::setFile( const FileName
&terrFileName
)
329 if ( terrFileName
== mTerrFileName
)
330 return mFile
!= NULL
;
332 Resource
<TerrainFile
> file
= ResourceManager::get().load( terrFileName
);
337 setMaskBits( FileMask
| HeightMapChangeMask
);
342 void TerrainBlock::setFile(const Resource
<TerrainFile
>& 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
) );
359 void TerrainBlock::_updateBounds()
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
)
378 void TerrainBlock::_onZoningChanged( SceneZoneSpaceManager
*zoneManager
)
380 if ( mCell
== NULL
|| zoneManager
!= getSceneManager()->getZoneManager() )
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
)
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
) );
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.
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.
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
) );
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.
473 // Rebuild the physics representation.
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
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
;
504 const U32 blockMask
= mFile
->mSize
- 1;
506 if ( x
& ~blockMask
|| y
& ~blockMask
)
512 const TerrainSquare
*sq
= mFile
->findSquare( 0, x
, y
);
513 if ( sq
->flags
& TerrainSquare::Empty
)
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
)
525 *height
= zBottomLeft
+ xp
* (zBottomRight
-zBottomLeft
) + yp
* (zTopRight
-zBottomRight
);
528 *height
= zBottomLeft
+ xp
* (zTopRight
-zTopLeft
) + yp
* (zTopLeft
-zBottomLeft
);
534 *height
= zBottomRight
+ (1.0f
-xp
) * (zBottomLeft
-zBottomRight
) + yp
* (zTopLeft
-zBottomLeft
);
537 *height
= zBottomRight
+ (1.0f
-xp
) * (zTopLeft
-zTopRight
) + yp
* (zTopRight
-zBottomRight
);
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
;
555 const U32 blockMask
= mFile
->mSize
- 1;
557 if ( x
& ~blockMask
|| y
& ~blockMask
)
563 const TerrainSquare
*sq
= mFile
->findSquare( 0, x
, y
);
564 if ( skipEmpty
&& sq
->flags
& TerrainSquare::Empty
)
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
)
576 normal
->set(zBottomLeft
-zBottomRight
, zBottomRight
-zTopRight
, mSquareSize
);
579 normal
->set(zTopLeft
-zTopRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
585 normal
->set(zBottomLeft
-zBottomRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
588 normal
->set(zTopLeft
-zTopRight
, zBottomRight
-zTopRight
, mSquareSize
);
597 bool TerrainBlock::getSmoothNormal( const Point2F
&pos
,
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
;
610 const U32 blockMask
= mFile
->mSize
- 1;
612 if ( x
& ~blockMask
|| y
& ~blockMask
)
618 const TerrainSquare
*sq
= mFile
->findSquare( 0, x
, y
);
619 if ( skipEmpty
&& sq
->flags
& TerrainSquare::Empty
)
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
);
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
;
647 const U32 blockMask
= mFile
->mSize
- 1;
649 if ( x
& ~blockMask
|| y
& ~blockMask
)
655 const TerrainSquare
*sq
= mFile
->findSquare( 0, x
, y
);
656 if ( sq
->flags
& TerrainSquare::Empty
)
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
)
669 normal
->set(zBottomLeft
-zBottomRight
, zBottomRight
-zTopRight
, mSquareSize
);
670 *height
= zBottomLeft
+ xp
* (zBottomRight
-zBottomLeft
) + yp
* (zTopRight
-zBottomRight
);
675 normal
->set(zTopLeft
-zTopRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
676 *height
= zBottomLeft
+ xp
* (zTopRight
-zTopLeft
) + yp
* (zTopLeft
-zBottomLeft
);
684 normal
->set(zBottomLeft
-zBottomRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
685 *height
= zBottomRight
+ (1.0f
-xp
) * (zBottomLeft
-zBottomRight
) + yp
* (zTopLeft
-zBottomLeft
);
690 normal
->set(zTopLeft
-zTopRight
, zBottomRight
-zTopRight
, mSquareSize
);
691 *height
= zBottomRight
+ (1.0f
-xp
) * (zTopLeft
-zTopRight
) + yp
* (zTopRight
-zBottomRight
);
702 bool TerrainBlock::getNormalHeightMaterial( const Point2F
&pos
,
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
;
714 S32 xm
= S32(mFloor( xp
+ 0.5f
));
715 S32 ym
= S32(mFloor( yp
+ 0.5f
));
719 const U32 blockMask
= mFile
->mSize
- 1;
721 if ( x
& ~blockMask
|| y
& ~blockMask
)
727 const TerrainSquare
*sq
= mFile
->findSquare( 0, x
, y
);
728 if ( sq
->flags
& TerrainSquare::Empty
)
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
)
743 normal
->set(zBottomLeft
-zBottomRight
, zBottomRight
-zTopRight
, mSquareSize
);
744 *height
= zBottomLeft
+ xp
* (zBottomRight
-zBottomLeft
) + yp
* (zTopRight
-zBottomRight
);
749 normal
->set(zTopLeft
-zTopRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
750 *height
= zBottomLeft
+ xp
* (zTopRight
-zTopLeft
) + yp
* (zTopLeft
-zBottomLeft
);
758 normal
->set(zBottomLeft
-zBottomRight
, zBottomLeft
-zTopLeft
, mSquareSize
);
759 *height
= zBottomRight
+ (1.0f
-xp
) * (zBottomLeft
-zBottomRight
) + yp
* (zTopLeft
-zBottomLeft
);
764 normal
->set(zTopLeft
-zTopRight
, zBottomRight
-zTopRight
, mSquareSize
);
765 *height
= zBottomRight
+ (1.0f
-xp
) * (zTopLeft
-zTopRight
) + yp
* (zTopRight
-zBottomRight
);
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();
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 )
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() )
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() )
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();
855 void TerrainBlock::setLightMap( GBitmap
*newLightMap
)
857 SAFE_DELETE( mLightMap
);
858 mLightMap
= newLightMap
;
862 void TerrainBlock::clearLightMap()
865 mLightMap
= new GBitmap( mLightMapSize
, mLightMapSize
, 0, GFXFormatR8G8B8
);
867 mLightMap
->fillWhite();
871 GFXTextureObject
* TerrainBlock::getLightMapTex()
873 if ( mLightMapTex
.isNull() && mLightMap
)
875 mLightMapTex
.set( mLightMap
,
876 &GFXDefaultStaticDiffuseProfile
,
878 "TerrainBlock::getLightMapTex()" );
884 void TerrainBlock::onEditorEnable()
888 void TerrainBlock::onEditorDisable()
892 bool TerrainBlock::onAdd()
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
);
917 NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName
.c_str());
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(" *********************************************************");
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.");
958 // Init the detail layer rendering helper.
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.
977 // Preload all the materials.
978 mCell
->preloadMaterials();
981 SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &TerrainBlock::_onZoningChanged
);
984 mCRC
= terr
.getChecksum();
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()
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();
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
);
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()
1055 SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &TerrainBlock::_onZoningChanged
);
1057 SAFE_DELETE( mPhysicsRep
);
1059 if ( isClientObject() )
1063 SAFE_DELETE( mBaseMaterial
);
1064 SAFE_DELETE( mDefaultMatInst
);
1065 SAFE_DELETE( mCell
);
1068 GFXTextureManager::removeEventDelegate( this, &TerrainBlock::_onTextureEvent
);
1069 MATMGR
->getFlushSignal().remove( this, &TerrainBlock::_onFlushMaterials
);
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.
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.
1097 mCell
->updateOBBs();
1098 mZoningDirty
= true;
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" );
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
,
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." );
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);
1206 void TerrainBlock::unpackUpdate(NetConnection
* con
, BitStream
*stream
)
1208 Parent::unpackUpdate( con
, stream
);
1210 if ( stream
->readFlag() ) // TransformMask
1213 mathRead( *stream
, &mat
);
1214 setTransform( mat
);
1217 if ( stream
->readFlag() ) // FileMask
1220 stream
->read( &terrFile
);
1221 stream
->read( &mCRC
);
1223 if ( isProperlyAdded() )
1224 setFile( terrFile
);
1226 mTerrFileName
= terrFile
;
1229 if ( stream
->readFlag() ) // SizeMask
1230 stream
->read( &mSquareSize
);
1232 mCastShadows
= stream
->readFlag();
1234 if ( stream
->readFlag() ) // MaterialMask
1237 stream
->read( &baseTexSize
);
1238 if ( mBaseTexSize
!= baseTexSize
)
1240 mBaseTexSize
= baseTexSize
;
1241 if ( isProperlyAdded() )
1242 _updateBaseTexture( NONE
);
1246 stream
->read( &lightMapSize
);
1247 if ( mLightMapSize
!= lightMapSize
)
1249 mLightMapSize
= lightMapSize
;
1250 if ( isProperlyAdded() )
1252 SAFE_DELETE( mLightMap
);
1258 if ( stream
->readFlag() && isProperlyAdded() ) // HeightMapChangeMask
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 //-----------------------------------------------------------------------------
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")
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"
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"
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"
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
);
1344 TerrainBlock
* terrain
= getTerrainUnderWorldPoint(Point3F(pos
.x
, pos
.y
, 5000.0f
));
1345 if(terrain
&& terrain
->isServerObject())
1348 terrain
->getTransform().getColumn(3, &offset
);
1349 pos
-= Point2F(offset
.x
, offset
.y
);
1350 terrain
->getHeight(pos
, &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"
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"
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"
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
);
1392 TerrainBlock
* terrain
= getTerrainUnderWorldPoint(pos
);
1394 Point2F
nohghtPos(pos
.x
, pos
.y
);
1398 if(terrain
->isServerObject())
1401 terrain
->getTransform().getColumn(3, &offset
);
1402 nohghtPos
-= Point2F(offset
.x
, offset
.y
);
1403 terrain
->getHeight(nohghtPos
, &height
);