terrData.cpp

Engine/source/terrain/terrData.cpp

More...

Public Typedefs

Public Variables

_getTerrainHeight2 ("@brief Gets the terrain height at the specified position\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@return Returns the terrain height at the given point as an F32 value.\n\n" "@ingroup Terrain", NULL, "bool getTerrainHeight( F32 x, F32 y);")
_getTerrainHeightBelowPosition2 ("@brief Takes a world point and find the \"highest\" terrain underneath it\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@return Returns the closest terrain height below the given point as an F32 value.\n\n" "@ingroup Terrain", NULL, "bool getTerrainHeightBelowPosition( F32 x, F32 y);")
_getTerrainUnderWorldPoint1 ("@brief Gets the terrain block that is located under the given world point\n\n" "@param position The world space coordinate you wish to query at. Formatted as (\"x y z\")\n\n" "@return Returns the ID of the requested terrain block (0 if not found).\n\n" "@ingroup Terrain", NULL, "bool getTerrainUnderWorldPoint( Point3F position );")
_getTerrainUnderWorldPoint2 ("@brief Takes a world point and find the \"highest\" terrain underneath it\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@param z The Z coordinate in world space\n\n" "@return Returns the ID of the requested terrain block (0 if not found).\n\n" "@ingroup Terrain", NULL, "bool getTerrainUnderWorldPoint( F32 x, F32 y, F32 z);")

Public Functions

_getTerrainHeight1("@brief Gets the terrain height at the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">position\n\n</a>" "@param position The world space point, minus the z(height) value. Formatted as(\"x y\")\n\n" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n\n</a>" "@ingroup Terrain" , NULL , "bool getTerrainHeight( <a href="/coding/class/classpoint2i/">Point2I</a> position );" )
_getTerrainHeightBelowPosition1("@brief Takes a world point and <a href="/coding/file/talgorithm_8h/#talgorithm_8h_1a113846f47aa4d2409fe12e783dcd69cf">find</a> the \"highest\" terrain underneath <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">it\n\n</a>" "@param position The world space point, minus the z(height) value. Formatted as(\"x y\")\n\n" "@return Returns the closest terrain height below the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n\n</a>" "@ingroup Terrain" , NULL , "bool getTerrainHeightBelowPosition( <a href="/coding/class/classpoint2i/">Point2I</a> position );" )
ConsoleDocClass(TerrainBlock , "@brief Represent a terrain object in a Torque 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">level\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classterrainblock/">TerrainBlock</a>(theTerrain)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   terrainFile = \"art/terrains/Deathball Desert_0.ter\";\n" "   squareSize = \"2\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   tile = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   baseTexSize = \"1024\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   screenError = \"16\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   position = \"-1024 -1024 179.978\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   rotation = \"1 0 0 0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   scale = \"1 1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   isRenderEnabled = \"true\";\n" "   canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">TerrainMaterial\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Terrain\n</a>" )
DefineConsoleFunction(getTerrainHeight , F32 , (const char *ptOrX, const char *y) , ("") , "(Point2 pos) - gets the terrain height at the specified position." "@param pos The world space point, minus the z(height) <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value\n</a> Can be formatted as either(\"x y\") or (x,y)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n</a>" "@hide" )
DefineConsoleFunction(getTerrainHeightBelowPosition , F32 , (const char *ptOrX, const char *y, const char *z) , ("", "") , "(Point3F pos) - gets the terrain height at the specified position." "@param pos The world space point. Can be formatted as either (\"x y z\") or (x,y,z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@note This function is useful <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> you simply want <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> grab the terrain height underneath an <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n</a>" "@hide" )
DefineConsoleFunction(getTerrainUnderWorldPoint , S32 , (const char *ptOrX, const char *y, const char *z) , ("", "") , "(Point3F x/y/z) Gets the terrain block that is located under the given world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">point.\n</a>" "@param x/y/z The world coordinates (floating point values) you wish <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> query at. " "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the ID of the requested terrain block (0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@hide" )
DefineEngineMethod(TerrainBlock , save , bool , (const char *fileName) , "@brief Saves the terrain block's terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">name.\n\n</a>" "@param fileName Name and path of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save terrain data <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> save was successful, false otherwise" )
ImplementEnumType(baseTexFormat , "Description\n" "@ingroup ?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )

Detailed Description

Public Typedefs

typedef TerrainBlock::BaseTexFormat baseTexFormat 

Public Variables

ConsoleDocFragment _getTerrainHeight2 ("@brief Gets the terrain height at the specified position\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@return Returns the terrain height at the given point as an F32 value.\n\n" "@ingroup Terrain", NULL, "bool getTerrainHeight( F32 x, F32 y);")
ConsoleDocFragment _getTerrainHeightBelowPosition2 ("@brief Takes a world point and find the \"highest\" terrain underneath it\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@return Returns the closest terrain height below the given point as an F32 value.\n\n" "@ingroup Terrain", NULL, "bool getTerrainHeightBelowPosition( F32 x, F32 y);")
ConsoleDocFragment _getTerrainUnderWorldPoint1 ("@brief Gets the terrain block that is located under the given world point\n\n" "@param position The world space coordinate you wish to query at. Formatted as (\"x y z\")\n\n" "@return Returns the ID of the requested terrain block (0 if not found).\n\n" "@ingroup Terrain", NULL, "bool getTerrainUnderWorldPoint( Point3F position );")
ConsoleDocFragment _getTerrainUnderWorldPoint2 ("@brief Takes a world point and find the \"highest\" terrain underneath it\n\n" "@param x The X coordinate in world space\n" "@param y The Y coordinate in world space\n\n" "@param z The Z coordinate in world space\n\n" "@return Returns the ID of the requested terrain block (0 if not found).\n\n" "@ingroup Terrain", NULL, "bool getTerrainUnderWorldPoint( F32 x, F32 y, F32 z);")
 EndImplementEnumType 
Convex sTerrainConvexList 

Public Functions

_getTerrainHeight1("@brief Gets the terrain height at the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">position\n\n</a>" "@param position The world space point, minus the z(height) value. Formatted as(\"x y\")\n\n" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n\n</a>" "@ingroup Terrain" , NULL , "bool getTerrainHeight( <a href="/coding/class/classpoint2i/">Point2I</a> position );" )

_getTerrainHeightBelowPosition1("@brief Takes a world point and <a href="/coding/file/talgorithm_8h/#talgorithm_8h_1a113846f47aa4d2409fe12e783dcd69cf">find</a> the \"highest\" terrain underneath <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">it\n\n</a>" "@param position The world space point, minus the z(height) value. Formatted as(\"x y\")\n\n" "@return Returns the closest terrain height below the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n\n</a>" "@ingroup Terrain" , NULL , "bool getTerrainHeightBelowPosition( <a href="/coding/class/classpoint2i/">Point2I</a> position );" )

ConsoleDocClass(TerrainBlock , "@brief Represent a terrain object in a Torque 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">level\n\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "<a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/class/classterrainblock/">TerrainBlock</a>(theTerrain)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   terrainFile = \"art/terrains/Deathball Desert_0.ter\";\n" "   squareSize = \"2\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   tile = \"0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   baseTexSize = \"1024\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   screenError = \"16\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   position = \"-1024 -1024 179.978\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   rotation = \"1 0 0 0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   scale = \"1 1 1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   isRenderEnabled = \"true\";\n" "   canSaveDynamicFields = \"1\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">TerrainMaterial\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Terrain\n</a>" )

DefineConsoleFunction(getTerrainHeight , F32 , (const char *ptOrX, const char *y) , ("") , "(Point2 pos) - gets the terrain height at the specified position." "@param pos The world space point, minus the z(height) <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value\n</a> Can be formatted as either(\"x y\") or (x,y)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n</a>" "@hide" )

DefineConsoleFunction(getTerrainHeightBelowPosition , F32 , (const char *ptOrX, const char *y, const char *z) , ("", "") , "(Point3F pos) - gets the terrain height at the specified position." "@param pos The world space point. Can be formatted as either (\"x y z\") or (x,y,z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@note This function is useful <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> you simply want <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> grab the terrain height underneath an <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@return Returns the terrain height at the given point as an <a href="/coding/file/types_8h/#types_8h_1a841d3674577a1e86afdc2f4845f46c4b">F32</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">value.\n</a>" "@hide" )

DefineConsoleFunction(getTerrainUnderWorldPoint , S32 , (const char *ptOrX, const char *y, const char *z) , ("", "") , "(Point3F x/y/z) Gets the terrain block that is located under the given world <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">point.\n</a>" "@param x/y/z The world coordinates (floating point values) you wish <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> query at. " "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@return Returns the ID of the requested terrain block (0 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> not found).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@hide" )

DefineEngineMethod(TerrainBlock , save , bool , (const char *fileName) , "@brief Saves the terrain block's terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">name.\n\n</a>" "@param fileName Name and path of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save terrain data <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to.\n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> save was successful, false otherwise" )

DefineEnumType(baseTexFormat )

getTerrainUnderWorldPoint(const Point3F & wPos)

IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock )

ImplementEnumType(baseTexFormat , "Description\n" "@ingroup ?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" )

   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#include "platform/platform.h"
  25#include "terrain/terrData.h"
  26
  27#include "terrain/terrCollision.h"
  28#include "terrain/terrCell.h"
  29#include "terrain/terrRender.h"
  30#include "terrain/terrMaterial.h"
  31#include "terrain/terrCellMaterial.h"
  32#include "gui/worldEditor/terrainEditor.h"
  33#include "math/mathIO.h"
  34#include "core/stream/fileStream.h"
  35#include "core/stream/bitStream.h"
  36#include "console/consoleTypes.h"
  37#include "sim/netConnection.h"
  38#include "core/util/safeDelete.h"
  39#include "T3D/objectTypes.h"
  40#include "renderInstance/renderPassManager.h"
  41#include "scene/sceneRenderState.h"
  42#include "materials/materialManager.h"
  43#include "materials/baseMatInstance.h"
  44#include "gfx/gfxTextureManager.h"
  45#include "gfx/gfxCardProfile.h"
  46#include "core/resourceManager.h"
  47#include "T3D/physics/physicsPlugin.h"
  48#include "T3D/physics/physicsBody.h"
  49#include "T3D/physics/physicsCollision.h"
  50#include "console/engineAPI.h"
  51
  52#include "console/engineAPI.h"
  53using namespace Torque;
  54
  55IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock);
  56
  57ConsoleDocClass( TerrainBlock,
  58   "@brief Represent a terrain object in a Torque 3D level\n\n"
  59
  60  "@tsexample\n"
  61   "new TerrainBlock(theTerrain)\n"
  62   "{\n"
  63   "   terrainFile = \"art/terrains/Deathball Desert_0.ter\";\n"
  64   "   squareSize = \"2\";\n"
  65   "   tile = \"0\";\n"
  66   "   baseTexSize = \"1024\";\n"
  67   "   screenError = \"16\";\n"
  68   "   position = \"-1024 -1024 179.978\";\n"
  69   "   rotation = \"1 0 0 0\";\n"
  70   "   scale = \"1 1 1\";\n"
  71   "   isRenderEnabled = \"true\";\n"
  72   "   canSaveDynamicFields = \"1\";\n"
  73   "};\n"
  74   "@endtsexample\n\n"
  75
  76   "@see TerrainMaterial\n\n"
  77
  78   "@ingroup Terrain\n"
  79);
  80
  81
  82Signal<void(U32,TerrainBlock*,const Point2I& ,const Point2I&)> TerrainBlock::smUpdateSignal;
  83
  84F32 TerrainBlock::smLODScale = 1.0f;
  85F32 TerrainBlock::smDetailScale = 1.0f;
  86
  87
  88//RBP - Global function declared in Terrdata.h
  89TerrainBlock* getTerrainUnderWorldPoint(const Point3F & wPos)
  90{
  91   // Cast a ray straight down from the world position and see which
  92   // Terrain is the closest to our starting point
  93   Point3F startPnt = wPos;
  94   Point3F endPnt = wPos + Point3F(0.0f, 0.0f, -10000.0f);
  95
  96   S32 blockIndex = -1;
  97   F32 nearT = 1.0f;
  98
  99   SimpleQueryList queryList;
 100   gServerContainer.findObjects( TerrainObjectType, SimpleQueryList::insertionCallback, &queryList);
 101
 102   for (U32 i = 0; i < queryList.mList.size(); i++)
 103   {
 104      Point3F tStartPnt, tEndPnt;
 105      TerrainBlock* terrBlock = dynamic_cast<TerrainBlock*>(queryList.mList[i]);
 106      terrBlock->getWorldTransform().mulP(startPnt, &tStartPnt);
 107      terrBlock->getWorldTransform().mulP(endPnt, &tEndPnt);
 108
 109      RayInfo ri;
 110      if (terrBlock->castRayI(tStartPnt, tEndPnt, &ri, true))
 111      {
 112         if (ri.t < nearT)
 113         {
 114            blockIndex = i;
 115            nearT = ri.t;
 116         }
 117      }
 118   }
 119
 120   if (blockIndex > -1)
 121      return (TerrainBlock*)(queryList.mList[blockIndex]);
 122
 123   return NULL;
 124}
 125
 126
 127ConsoleDocFragment _getTerrainUnderWorldPoint1(
 128   "@brief Gets the terrain block that is located under the given world point\n\n"
 129   "@param position The world space coordinate you wish to query at. Formatted as (\"x y z\")\n\n"
 130   "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
 131   "@ingroup Terrain",
 132   NULL,
 133   "bool getTerrainUnderWorldPoint( Point3F position );"
 134);
 135ConsoleDocFragment _getTerrainUnderWorldPoint2(
 136   "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
 137   "@param x The X coordinate in world space\n"
 138   "@param y The Y coordinate in world space\n\n"
 139   "@param z The Z coordinate in world space\n\n"
 140   "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
 141   "@ingroup Terrain",
 142   NULL,
 143   "bool getTerrainUnderWorldPoint( F32 x, F32 y, F32 z);"
 144);
 145
 146DefineConsoleFunction( getTerrainUnderWorldPoint, S32, (const char* ptOrX, const char* y, const char* z), ("", ""),
 147                                                      "(Point3F x/y/z) Gets the terrain block that is located under the given world point.\n"
 148                                                      "@param x/y/z The world coordinates (floating point values) you wish to query at. " 
 149                                                      "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n"
 150                                                      "@return Returns the ID of the requested terrain block (0 if not found).\n\n"
 151                                                      "@hide")
 152{
 153   Point3F pos;
 154   if(!String::isEmpty(ptOrX) && String::isEmpty(y) && String::isEmpty(z))
 155      dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
 156   else if(!String::isEmpty(ptOrX) && !String::isEmpty(y) && !String::isEmpty(z))
 157   {
 158      pos.x = dAtof(ptOrX);
 159      pos.y = dAtof(y);
 160      pos.z = dAtof(z);
 161   }
 162   TerrainBlock* terrain = getTerrainUnderWorldPoint(pos);
 163   if(terrain != NULL)
 164   {
 165      return terrain->getId();
 166   }
 167   return 0;
 168}
 169
 170
 171typedef TerrainBlock::BaseTexFormat baseTexFormat;
 172DefineEnumType(baseTexFormat);
 173
 174ImplementEnumType(baseTexFormat,
 175   "Description\n"
 176   "@ingroup ?\n\n")
 177{ TerrainBlock::NONE, "NONE", "No cached terrain.\n" },
 178{ TerrainBlock::DDS, "DDS", "Cache the terrain in a DDS format.\n" },
 179{ TerrainBlock::PNG, "PNG", "Cache the terrain in a PNG format.\n" },
 180{ TerrainBlock::JPG, "JPG", "Cache the terrain in a JPG format.\n" },
 181EndImplementEnumType;
 182
 183TerrainBlock::TerrainBlock()
 184 : mLightMap( NULL ),
 185   mLightMapSize( 256 ),
 186   mCRC( 0 ),
 187   mMaxDetailDistance( 0.0f ),
 188   mBaseTexScaleConst( NULL ),
 189   mBaseTexIdConst( NULL ),
 190   mDetailsDirty( false ),
 191   mLayerTexDirty( false ),
 192   mBaseTexSize( 1024 ),
 193   mBaseTexFormat( TerrainBlock::JPG ),
 194   mCell( NULL ),
 195   mBaseMaterial( NULL ),
 196   mDefaultMatInst( NULL ),
 197   mSquareSize( 1.0f ),
 198   mPhysicsRep( NULL ),
 199   mScreenError( 16 ),
 200   mCastShadows( true ),
 201   mZoningDirty( false )
 202{
 203   mTypeMask = TerrainObjectType | StaticObjectType | StaticShapeObjectType;
 204   mNetFlags.set(Ghostable | ScopeAlways);
 205}
 206
 207
 208extern Convex sTerrainConvexList;
 209
 210TerrainBlock::~TerrainBlock()
 211{
 212   // Kill collision
 213   sTerrainConvexList.nukeList();
 214
 215   SAFE_DELETE(mLightMap);
 216   mLightMapTex = NULL;
 217
 218#ifdef TORQUE_TOOLS
 219   TerrainEditor* editor = dynamic_cast<TerrainEditor*>(Sim::findObject("ETerrainEditor"));
 220   if (editor)
 221      editor->detachTerrain(this);
 222#endif
 223}
 224
 225void TerrainBlock::_onTextureEvent( GFXTexCallbackCode code )
 226{
 227   if ( code == GFXZombify )
 228   {
 229      if (  mBaseTex.isValid() &&
 230            mBaseTex->isRenderTarget() )
 231         mBaseTex = NULL;
 232
 233      mLayerTex = NULL;
 234      mLightMapTex = NULL;
 235   }
 236}
 237
 238bool TerrainBlock::_setSquareSize( void *obj, const char *index, const char *data )
 239{
 240   TerrainBlock *terrain = static_cast<TerrainBlock*>( obj );
 241
 242   F32 newSqaureSize = dAtof( data );
 243   if ( !mIsEqual( terrain->mSquareSize, newSqaureSize ) )
 244   {
 245      terrain->mSquareSize = newSqaureSize;
 246
 247      if ( terrain->isServerObject() && terrain->isProperlyAdded() )
 248         terrain->_updateBounds();
 249
 250      terrain->setMaskBits( HeightMapChangeMask | SizeMask );
 251   }
 252
 253   return false;
 254}
 255
 256bool TerrainBlock::_setBaseTexSize( void *obj, const char *index, const char *data )
 257{
 258   TerrainBlock *terrain = static_cast<TerrainBlock*>( obj );
 259
 260   // NOTE: We're limiting the base texture size to 
 261   // 2048 as anything greater in size becomes too
 262   // large to generate for many cards.
 263   //
 264   // If you want to remove this limit feel free, but
 265   // prepare for problems if you don't ship the baked
 266   // base texture with your installer.
 267   //
 268
 269   S32 texSize = mClamp( dAtoi( data ), 0, 2048 );
 270   if ( terrain->mBaseTexSize != texSize )
 271   {
 272      terrain->mBaseTexSize = texSize;
 273      terrain->setMaskBits( MaterialMask );
 274   }
 275
 276   return false;
 277}
 278
 279bool TerrainBlock::_setBaseTexFormat(void *obj, const char *index, const char *data)
 280{
 281   TerrainBlock *terrain = static_cast<TerrainBlock*>(obj);
 282
 283   EngineEnumTable eTable = _baseTexFormat::_sEnumTable;
 284
 285   for (U8 i = 0; i < eTable.getNumValues(); i++)
 286   {
 287      if (strcasecmp(eTable[i].mName, data) == 0)
 288      {
 289         terrain->mBaseTexFormat = (BaseTexFormat)eTable[i].mInt;
 290         terrain->_updateMaterials();
 291
 292         if (terrain->isServerObject()) return false;
 293         terrain->_updateLayerTexture();
 294         // If the cached base texture is older that the terrain file or
 295         // it doesn't exist then generate and cache it.
 296         String baseCachePath = terrain->_getBaseTexCacheFileName();
 297         if (Platform::compareModifiedTimes(baseCachePath, terrain->mTerrFileName) < 0)
 298            terrain->_updateBaseTexture(true);
 299         break;
 300      }
 301   }
 302
 303   return false;
 304}
 305
 306bool TerrainBlock::_setLightMapSize( void *obj, const char *index, const char *data )
 307{
 308   TerrainBlock *terrain = static_cast<TerrainBlock*>(obj);
 309
 310   // Handle inspector value decrements correctly
 311   U32 mapSize = dAtoi( data );
 312   if ( mapSize == terrain->mLightMapSize-1 )
 313      mapSize = terrain->mLightMapSize/2;
 314
 315   // Limit the lightmap size, and ensure it is a power of 2
 316   const U32 maxTextureSize = GFX->getCardProfiler()->queryProfile( "maxTextureSize", 1024 );
 317   mapSize = mClamp( getNextPow2( mapSize ), 0, maxTextureSize );
 318
 319   if ( terrain->mLightMapSize != mapSize )
 320   {
 321      terrain->mLightMapSize = mapSize;
 322      terrain->setMaskBits( MaterialMask );
 323   }
 324
 325   return false;
 326}
 327
 328bool TerrainBlock::setFile( const FileName &terrFileName )
 329{
 330   if ( terrFileName == mTerrFileName )
 331      return mFile != NULL;
 332
 333   Resource<TerrainFile> file = ResourceManager::get().load( terrFileName );
 334   if( !file )
 335      return false;
 336      
 337   setFile( file );
 338   setMaskBits( FileMask | HeightMapChangeMask );
 339   
 340   return true;
 341}
 342
 343void TerrainBlock::setFile(const Resource<TerrainFile>& terr)
 344{
 345   mFile = terr;
 346   mTerrFileName = terr.getPath();
 347}
 348
 349bool TerrainBlock::save(const char *filename)
 350{
 351   return mFile->save(filename);
 352}
 353
 354bool TerrainBlock::_setTerrainFile( void *obj, const char *index, const char *data )
 355{
 356   static_cast<TerrainBlock*>( obj )->setFile( FileName( data ) );
 357   return false;
 358}
 359
 360void TerrainBlock::_updateBounds()
 361{
 362   if ( !mFile )
 363      return; // quick fix to stop crashing when deleting terrainblocks
 364
 365   // Setup our object space bounds.
 366   mBounds.minExtents.set( 0.0f, 0.0f, 0.0f );
 367   mBounds.maxExtents.set( getWorldBlockSize(), getWorldBlockSize(), 0.0f );
 368   getMinMaxHeight( &mBounds.minExtents.z, &mBounds.maxExtents.z );
 369
 370   // Set our mObjBox to be equal to mBounds
 371   if (  mObjBox.maxExtents != mBounds.maxExtents || 
 372         mObjBox.minExtents != mBounds.minExtents )
 373   {
 374      mObjBox = mBounds;
 375      resetWorldBox();
 376   }
 377}
 378
 379void TerrainBlock::_onZoningChanged( SceneZoneSpaceManager *zoneManager )
 380{
 381   if ( mCell == NULL || zoneManager != getSceneManager()->getZoneManager() )
 382      return;
 383
 384   mZoningDirty = true;
 385}
 386
 387void TerrainBlock::setHeight( const Point2I &pos, F32 height )
 388{
 389   U16 ht = floatToFixed( height );
 390   mFile->setHeight( pos.x, pos.y, ht );
 391
 392   // Note: We do not update the grid here as this could
 393   // be called several times in a loop.  We depend on the
 394   // caller doing a grid update when he is done.
 395}
 396
 397F32 TerrainBlock::getHeight( const Point2I &pos )
 398{
 399   U16 ht = mFile->getHeight( pos.x, pos.y );
 400   return fixedToFloat( ht );
 401}
 402
 403void TerrainBlock::updateGridMaterials( const Point2I &minPt, const Point2I &maxPt )
 404{
 405   if ( mCell )
 406   {
 407      // Tell the terrain cell that something changed.
 408      const RectI gridRect( minPt, maxPt - minPt );
 409      mCell->updateGrid( gridRect, true );
 410   }
 411
 412   // We mark us as dirty... it will be updated
 413   // before the next time we render the terrain.
 414   mLayerTexDirty = true;
 415
 416   // Signal anyone that cares that the opacity was changed.
 417   smUpdateSignal.trigger( LayersUpdate, this, minPt, maxPt );
 418}
 419
 420
 421Point2I TerrainBlock::getGridPos( const Point3F &worldPos ) const
 422{
 423   Point3F terrainPos = worldPos;
 424   getWorldTransform().mulP( terrainPos );
 425
 426   F32 squareSize = ( F32 ) getSquareSize();
 427   F32 halfSquareSize = squareSize / 2.0;
 428
 429   F32 x = ( terrainPos.x + halfSquareSize ) / squareSize;
 430   F32 y = ( terrainPos.y + halfSquareSize ) / squareSize;
 431
 432   Point2I gridPos( ( S32 ) mFloor( x ), ( S32 ) mFloor( y ) );
 433   return gridPos;
 434}
 435
 436void TerrainBlock::updateGrid( const Point2I &minPt, const Point2I &maxPt, bool updateClient )
 437{
 438   // On the client we just signal everyone that the height
 439   // map has changed... the server does the actual changes.
 440   if ( isClientObject() )
 441   {
 442      PROFILE_SCOPE( TerrainBlock_updateGrid_Client );
 443
 444      // This depends on the client getting this call 'after' the server.
 445      // Which is currently the case.
 446      _updateBounds();
 447      mZoningDirty = true;
 448
 449      smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt );
 450
 451      // Tell the terrain cell that the height changed.
 452      const RectI gridRect( minPt, maxPt - minPt );
 453      mCell->updateGrid( gridRect );
 454
 455      // Rebuild the physics representation.
 456      if ( mPhysicsRep )
 457      {
 458         // Delay the update by a few milliseconds so 
 459         // that we're not rebuilding during an active
 460         // editing operation.
 461         mPhysicsRep->queueCallback( 500, Delegate<void()>( this, &TerrainBlock::_updatePhysics ) );
 462      }
 463
 464      return;
 465   }
 466
 467   // Now on the server we rebuild the 
 468   // affected area of the grid map.
 469   mFile->updateGrid( minPt, maxPt );
 470
 471   // Fix up the bounds.
 472   _updateBounds();
 473
 474   // Rebuild the physics representation.
 475   if ( mPhysicsRep )
 476   {
 477      // Delay the update by a few milliseconds so 
 478      // that we're not rebuilding during an active
 479      // editing operation.
 480      mPhysicsRep->queueCallback( 500, Delegate<void()>( this, &TerrainBlock::_updatePhysics ) );
 481   }
 482
 483   // Signal again here for any server side observers.
 484   smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt );
 485
 486   // If this is a server object and the client update
 487   // was requested then try to use the local connection
 488   // pointer to do it.
 489   if ( updateClient && getClientObject() )
 490      ((TerrainBlock*)getClientObject())->updateGrid( minPt, maxPt, false );
 491}
 492
 493bool TerrainBlock::getHeight( const Point2F &pos, F32 *height ) const
 494{
 495   PROFILE_SCOPE( TerrainBlock_getHeight );
 496
 497   F32 invSquareSize = 1.0f / mSquareSize;
 498   F32 xp = pos.x * invSquareSize;
 499   F32 yp = pos.y * invSquareSize;
 500   S32 x = S32(xp);
 501   S32 y = S32(yp);
 502   xp -= (F32)x;
 503   yp -= (F32)y;
 504
 505   const U32 blockMask = mFile->mSize - 1;
 506
 507   if ( x & ~blockMask || y & ~blockMask )
 508      return false;
 509   
 510   x &= blockMask;
 511   y &= blockMask;
 512   
 513   const TerrainSquare *sq = mFile->findSquare( 0, x, y );
 514   if ( sq->flags & TerrainSquare::Empty )
 515      return false;
 516
 517   F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) );
 518   F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) );
 519   F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) );
 520   F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) );
 521
 522   if ( sq->flags & TerrainSquare::Split45 )
 523   {
 524      if (xp>yp)
 525         // bottom half
 526         *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
 527      else
 528         // top half
 529         *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
 530   }
 531   else
 532   {
 533      if (1.0f-xp>yp)
 534         // bottom half
 535         *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
 536      else
 537         // top half
 538         *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
 539   }
 540
 541   return true;
 542}
 543
 544bool TerrainBlock::getNormal( const Point2F &pos, Point3F *normal, bool normalize, bool skipEmpty ) const
 545{
 546   PROFILE_SCOPE( TerrainBlock_getNormal );
 547
 548   F32 invSquareSize = 1.0f / mSquareSize;
 549   F32 xp = pos.x * invSquareSize;
 550   F32 yp = pos.y * invSquareSize;
 551   S32 x = S32(xp);
 552   S32 y = S32(yp);
 553   xp -= (F32)x;
 554   yp -= (F32)y;
 555   
 556   const U32 blockMask = mFile->mSize - 1;
 557
 558   if ( x & ~blockMask || y & ~blockMask )
 559      return false;
 560   
 561   x &= blockMask;
 562   y &= blockMask;
 563   
 564   const TerrainSquare *sq = mFile->findSquare( 0, x, y );
 565   if ( skipEmpty && sq->flags & TerrainSquare::Empty )
 566      return false;
 567
 568   F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) );
 569   F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) );
 570   F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) );
 571   F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) );
 572
 573   if ( sq->flags & TerrainSquare::Split45 )
 574   {
 575      if (xp>yp)
 576         // bottom half
 577         normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
 578      else
 579         // top half
 580         normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
 581   }
 582   else
 583   {
 584      if (1.0f-xp>yp)
 585         // bottom half
 586         normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
 587      else
 588         // top half
 589         normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
 590   }
 591
 592   if (normalize)
 593      normal->normalize();
 594
 595   return true;
 596}
 597
 598bool TerrainBlock::getSmoothNormal( const Point2F &pos, 
 599                                    Point3F *normal, 
 600                                    bool normalize,
 601                                    bool skipEmpty ) const
 602{
 603   PROFILE_SCOPE( TerrainBlock_getSmoothNormal );
 604
 605   F32 invSquareSize = 1.0f / mSquareSize;
 606   F32 xp = pos.x * invSquareSize;
 607   F32 yp = pos.y * invSquareSize;
 608   S32 x = S32(xp);
 609   S32 y = S32(yp);
 610   
 611   const U32 blockMask = mFile->mSize - 1;
 612
 613   if ( x & ~blockMask || y & ~blockMask )
 614      return false;
 615   
 616   x &= blockMask;
 617   y &= blockMask;
 618   
 619   const TerrainSquare *sq = mFile->findSquare( 0, x, y );
 620   if ( skipEmpty && sq->flags & TerrainSquare::Empty )
 621      return false;
 622
 623   F32 h1 = fixedToFloat( mFile->getHeight( x + 1, y ) );
 624   F32 h2 = fixedToFloat( mFile->getHeight( x, y + 1 ) );
 625   F32 h3 = fixedToFloat( mFile->getHeight( x - 1, y ) );
 626   F32 h4 = fixedToFloat( mFile->getHeight( x, y - 1 ) );
 627
 628   normal->set( h3 - h1, h4 - h2, mSquareSize * 2.0f );
 629
 630   if ( normalize )
 631      normal->normalize();
 632
 633   return true;
 634}
 635
 636bool TerrainBlock::getNormalAndHeight( const Point2F &pos, Point3F *normal, F32 *height, bool normalize ) const
 637{
 638   PROFILE_SCOPE( TerrainBlock_getNormalAndHeight );
 639
 640   F32 invSquareSize = 1.0f / mSquareSize;
 641   F32 xp = pos.x * invSquareSize;
 642   F32 yp = pos.y * invSquareSize;
 643   S32 x = S32(xp);
 644   S32 y = S32(yp);
 645   xp -= (F32)x;
 646   yp -= (F32)y;
 647
 648   const U32 blockMask = mFile->mSize - 1;
 649
 650   if ( x & ~blockMask || y & ~blockMask )
 651      return false;
 652   
 653   x &= blockMask;
 654   y &= blockMask;
 655   
 656   const TerrainSquare *sq = mFile->findSquare( 0, x, y );
 657   if ( sq->flags & TerrainSquare::Empty )
 658      return false;
 659
 660   F32 zBottomLeft  = fixedToFloat( mFile->getHeight(x, y) );
 661   F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) );
 662   F32 zTopLeft     = fixedToFloat( mFile->getHeight(x, y + 1) );
 663   F32 zTopRight    = fixedToFloat( mFile->getHeight(x + 1, y + 1) );
 664
 665   if ( sq->flags & TerrainSquare::Split45 )
 666   {
 667      if (xp>yp)
 668      {
 669         // bottom half
 670         normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
 671         *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
 672      }
 673      else
 674      {
 675         // top half
 676         normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
 677         *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
 678      }
 679   }
 680   else
 681   {
 682      if (1.0f-xp>yp)
 683      {
 684         // bottom half
 685         normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
 686         *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
 687      }
 688      else
 689      {
 690         // top half
 691         normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
 692         *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
 693      }
 694   }
 695
 696   if (normalize)
 697      normal->normalize();
 698
 699   return true;
 700}
 701
 702
 703bool TerrainBlock::getNormalHeightMaterial(  const Point2F &pos, 
 704                                             Point3F *normal, 
 705                                             F32 *height, 
 706                                             StringTableEntry &matName ) const
 707{
 708   PROFILE_SCOPE( TerrainBlock_getNormalHeightMaterial );
 709
 710   F32 invSquareSize = 1.0f / mSquareSize;
 711   F32 xp = pos.x * invSquareSize;
 712   F32 yp = pos.y * invSquareSize;
 713   S32 x = S32(xp);
 714   S32 y = S32(yp);
 715   S32 xm = S32(mFloor( xp + 0.5f ));   
 716   S32 ym = S32(mFloor( yp + 0.5f ));   
 717   xp -= (F32)x;
 718   yp -= (F32)y;
 719
 720   const U32 blockMask = mFile->mSize - 1;
 721
 722   if ( x & ~blockMask || y & ~blockMask )
 723      return false;
 724   
 725   x &= blockMask;
 726   y &= blockMask;
 727   
 728   const TerrainSquare *sq = mFile->findSquare( 0, x, y );
 729   if ( sq->flags & TerrainSquare::Empty )
 730      return false;
 731
 732   F32 zBottomLeft  = fixedToFloat( mFile->getHeight(x, y) );
 733   F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) );
 734   F32 zTopLeft     = fixedToFloat( mFile->getHeight(x, y + 1) );
 735   F32 zTopRight    = fixedToFloat( mFile->getHeight(x + 1, y + 1) );
 736
 737   matName = mFile->getMaterialName( xm, ym );
 738
 739   if ( sq->flags & TerrainSquare::Split45 )
 740   {
 741      if (xp>yp)
 742      {
 743         // bottom half
 744         normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize);
 745         *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
 746      }
 747      else
 748      {
 749         // top half
 750         normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize);
 751         *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
 752      }
 753   }
 754   else
 755   {
 756      if (1.0f-xp>yp)
 757      {
 758         // bottom half
 759         normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize);
 760         *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
 761      }
 762      else
 763      {
 764         // top half
 765         normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize);
 766         *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
 767      }
 768   }
 769
 770   normal->normalize();
 771
 772   return true;
 773}
 774
 775U32 TerrainBlock::getMaterialCount() const
 776{
 777   return mFile->mMaterials.size();
 778}
 779
 780void TerrainBlock::addMaterial( const String &name, U32 insertAt )
 781{
 782   TerrainMaterial *mat = TerrainMaterial::findOrCreate( name );
 783
 784   if ( insertAt == -1 )
 785   {
 786      mFile->mMaterials.push_back( mat );
 787      mFile->_initMaterialInstMapping();
 788   }
 789   else
 790   {
 791
 792      // TODO: Insert and reindex!        
 793
 794   }
 795
 796   mDetailsDirty = true;
 797   mLayerTexDirty = true;
 798}
 799
 800void TerrainBlock::removeMaterial( U32 index )
 801{
 802   // Cannot delete if only one layer.
 803   if ( mFile->mMaterials.size() == 1 )
 804      return;
 805
 806   mFile->mMaterials.erase( index );
 807   mFile->_initMaterialInstMapping();
 808
 809   for ( S32 i = 0; i < mFile->mLayerMap.size(); i++ )
 810   {
 811      if ( mFile->mLayerMap[i] >= index &&
 812           mFile->mLayerMap[i] != 0 )
 813      {
 814            mFile->mLayerMap[i]--;
 815      }
 816   }
 817
 818   mDetailsDirty = true;
 819   mLayerTexDirty = true;     
 820}
 821
 822void TerrainBlock::updateMaterial( U32 index, const String &name )
 823{
 824   if ( index >= mFile->mMaterials.size() )
 825      return;
 826
 827   mFile->mMaterials[ index ] = TerrainMaterial::findOrCreate( name );
 828   mFile->_initMaterialInstMapping();
 829
 830   mDetailsDirty = true;
 831   mLayerTexDirty = true;
 832}
 833
 834TerrainMaterial* TerrainBlock::getMaterial( U32 index ) const
 835{
 836   if ( index >= mFile->mMaterials.size() )
 837      return NULL;
 838
 839   return mFile->mMaterials[ index ];
 840}
 841
 842void TerrainBlock::deleteAllMaterials()
 843{
 844   mFile->mMaterials.clear();
 845   mFile->mMaterialInstMapping.clearMatInstList();
 846}
 847
 848const char* TerrainBlock::getMaterialName( U32 index ) const
 849{
 850   if ( index < mFile->mMaterials.size() )
 851      return mFile->mMaterials[ index ]->getInternalName();
 852
 853   return NULL;
 854}
 855
 856void TerrainBlock::setLightMap( GBitmap *newLightMap )
 857{
 858   SAFE_DELETE( mLightMap );
 859   mLightMap = newLightMap;
 860   mLightMapTex = NULL;
 861}
 862
 863void TerrainBlock::clearLightMap()
 864{
 865   if ( !mLightMap )
 866      mLightMap = new GBitmap( mLightMapSize, mLightMapSize, 0, GFXFormatR8G8B8 );
 867
 868   mLightMap->fillWhite();
 869   mLightMapTex = NULL;
 870}
 871
 872GFXTextureObject* TerrainBlock::getLightMapTex()
 873{
 874   if ( mLightMapTex.isNull() && mLightMap )
 875   {
 876      mLightMapTex.set( mLightMap, 
 877                        &GFXDefaultStaticDiffuseProfile, 
 878                        false, 
 879                        "TerrainBlock::getLightMapTex()" );
 880   }
 881
 882   return mLightMapTex;
 883}
 884
 885void TerrainBlock::onEditorEnable()
 886{
 887}
 888
 889void TerrainBlock::onEditorDisable()
 890{
 891}
 892
 893bool TerrainBlock::onAdd()
 894{
 895   if(!Parent::onAdd())
 896      return false;
 897
 898   if ( mTerrFileName.isEmpty() )
 899   {
 900      mTerrFileName = Con::getVariable( "$Client::MissionFile" );
 901      String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
 902      if ( terrainDirectory.isEmpty() )
 903      {
 904         terrainDirectory = "art/terrains/";
 905      }
 906      mTerrFileName.replace("tools/levels/", terrainDirectory);
 907      mTerrFileName.replace("levels/", terrainDirectory);
 908
 909      Vector<String> materials;
 910      materials.push_back( "warning_material" );
 911      TerrainFile::create( &mTerrFileName, 256, materials );
 912   }
 913
 914   Resource<TerrainFile> terr = ResourceManager::get().load( mTerrFileName );
 915   if(terr == NULL)
 916   {
 917      if(isClientObject())
 918         NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName.c_str());
 919      return false;
 920   }
 921
 922   setFile( terr );
 923
 924   if ( terr->mNeedsResaving )
 925   {
 926      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      {
 928         terr->save(terr->mFilePath.getFullPath());
 929         terr->mNeedsResaving = false;
 930      }
 931   }
 932
 933   if (terr->mFileVersion != TerrainFile::FILE_VERSION || terr->mNeedsResaving)
 934   {
 935      Con::errorf(" *********************************************************");
 936      Con::errorf(" *********************************************************");
 937      Con::errorf(" *********************************************************");
 938      Con::errorf(" PLEASE RESAVE THE TERRAIN FILE FOR THIS MISSION!  THANKS!");
 939      Con::errorf(" *********************************************************");
 940      Con::errorf(" *********************************************************");
 941      Con::errorf(" *********************************************************");
 942   }
 943
 944   _updateBounds();
 945   
 946   resetWorldBox();
 947   setRenderTransform(mObjToWorld);
 948
 949   if (isClientObject())
 950   {
 951      if ( mCRC != terr.getChecksum() )
 952      {
 953         NetConnection::setLastError("Your terrain file doesn't match the version that is running on the server.");
 954         return false;
 955      }
 956
 957      clearLightMap();
 958
 959      // Init the detail layer rendering helper.
 960      _updateMaterials();
 961      _updateLayerTexture();
 962
 963      // If the cached base texture is older that the terrain file or
 964      // it doesn't exist then generate and cache it.
 965      String baseCachePath = _getBaseTexCacheFileName();
 966      if ( Platform::compareModifiedTimes( baseCachePath, mTerrFileName ) < 0 )
 967         _updateBaseTexture( true );
 968
 969      // The base texture should have been cached by now... so load it.
 970      mBaseTex.set( baseCachePath, &GFXDefaultStaticDiffuseProfile, "TerrainBlock::mBaseTex" );
 971
 972      GFXTextureManager::addEventDelegate( this, &TerrainBlock::_onTextureEvent );
 973      MATMGR->getFlushSignal().notify( this, &TerrainBlock::_onFlushMaterials );
 974
 975      // Build the terrain quadtree.
 976      _rebuildQuadtree();
 977
 978      // Preload all the materials.
 979      mCell->preloadMaterials();
 980
 981      mZoningDirty = true;
 982      SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &TerrainBlock::_onZoningChanged );
 983   }
 984   else
 985      mCRC = terr.getChecksum();
 986
 987   addToScene();
 988
 989   _updatePhysics();
 990
 991   return true;
 992}
 993
 994String TerrainBlock::_getBaseTexCacheFileName() const
 995{
 996   Torque::Path basePath( mTerrFileName );
 997   basePath.setFileName( basePath.getFileName() + "_basetex" );
 998   basePath.setExtension( formatToExtension(mBaseTexFormat) );
 999   return basePath.getFullPath();
1000}
1001
1002void TerrainBlock::_rebuildQuadtree()
1003{
1004   SAFE_DELETE( mCell );
1005
1006   // Recursively build the cells.
1007   mCell = TerrCell::init( this );
1008
1009   // Build the shared PrimitiveBuffer.
1010   mCell->createPrimBuffer( &mPrimBuffer );
1011}
1012
1013void TerrainBlock::_updatePhysics()
1014{
1015   if ( !PHYSICSMGR )
1016      return;
1017
1018   SAFE_DELETE( mPhysicsRep );
1019
1020   PhysicsCollision *colShape;
1021
1022   // If we can steal the collision shape from the local server
1023   // object then do so as it saves us alot of cpu time and memory.
1024   //
1025   // TODO: We should move this sharing down into TerrFile where
1026   // it probably belongs.
1027   //
1028   if ( getServerObject() )
1029   {
1030      TerrainBlock *serverTerrain = (TerrainBlock*)getServerObject();
1031      colShape = serverTerrain->mPhysicsRep->getColShape();
1032   }
1033   else
1034   {
1035      // Get empty state of each vert
1036      bool *holes = new bool[ getBlockSize() * getBlockSize() ];
1037      for ( U32 row = 0; row < getBlockSize(); row++ )
1038         for ( U32 column = 0; column < getBlockSize(); column++ )
1039            holes[ row + (column * getBlockSize()) ] = mFile->isEmptyAt( row, column );
1040
1041      colShape = PHYSICSMGR->createCollision();
1042      colShape->addHeightfield( mFile->getHeightMap().address(), holes, getBlockSize(), mSquareSize, MatrixF::Identity );
1043
1044      delete [] holes;
1045   }
1046
1047   PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
1048   mPhysicsRep = PHYSICSMGR->createBody();
1049   mPhysicsRep->init( colShape, 0, 0, this, world );
1050   mPhysicsRep->setTransform( getTransform() );
1051}
1052
1053void TerrainBlock::onRemove()
1054{
1055   removeFromScene();
1056   SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &TerrainBlock::_onZoningChanged );
1057
1058   SAFE_DELETE( mPhysicsRep );
1059
1060   if ( isClientObject() )
1061   {
1062      mBaseTex = NULL;
1063      mLayerTex = NULL;
1064      SAFE_DELETE( mBaseMaterial );
1065      SAFE_DELETE( mDefaultMatInst );
1066      SAFE_DELETE( mCell );
1067      mPrimBuffer = NULL;
1068      mBaseShader = NULL;
1069      GFXTextureManager::removeEventDelegate( this, &TerrainBlock::_onTextureEvent );
1070      MATMGR->getFlushSignal().remove( this, &TerrainBlock::_onFlushMaterials );
1071   }
1072
1073   Parent::onRemove();
1074}
1075
1076void TerrainBlock::prepRenderImage( SceneRenderState* state )
1077{
1078   PROFILE_SCOPE(TerrainBlock_prepRenderImage);
1079   
1080   // If we need to update our cached 
1081   // zone state then do it now.
1082   if ( mZoningDirty )
1083   {
1084      mZoningDirty = false;
1085      mCell->updateZoning( getSceneManager()->getZoneManager() );
1086   }
1087
1088   _renderBlock( state );
1089}
1090
1091void TerrainBlock::setTransform(const MatrixF & mat)
1092{
1093   Parent::setTransform( mat );
1094
1095   // Update world-space OBBs.
1096   if( mCell )
1097   {
1098      mCell->updateOBBs();
1099      mZoningDirty = true;
1100   }
1101
1102   if ( mPhysicsRep )
1103      mPhysicsRep->setTransform( mat );
1104
1105   setRenderTransform( mat );
1106   setMaskBits( TransformMask );
1107
1108   if(isClientObject())
1109      smUpdateSignal.trigger( HeightmapUpdate, this, Point2I::Zero, Point2I::Max );
1110}
1111
1112void TerrainBlock::setScale( const VectorF &scale )
1113{
1114   // We disable scaling... we never scale!
1115   Parent::setScale( VectorF::One );   
1116}
1117
1118void TerrainBlock::initPersistFields()
1119{
1120   addGroup( "Media" );
1121      
1122      addProtectedField( "terrainFile", TypeStringFilename, Offset( mTerrFileName, TerrainBlock ), 
1123         &TerrainBlock::_setTerrainFile, &defaultProtectedGetFn,
1124         "The source terrain data file." );
1125
1126   endGroup( "Media" );
1127
1128   addGroup( "Misc" );
1129
1130      addField( "castShadows", TypeBool, Offset( mCastShadows, TerrainBlock ),   
1131         "Allows the terrain to cast shadows onto itself and other objects."); 
1132   
1133      addProtectedField( "squareSize", TypeF32, Offset( mSquareSize, TerrainBlock ),
1134         &TerrainBlock::_setSquareSize, &defaultProtectedGetFn,
1135         "Indicates the spacing between points on the XY plane on the terrain." );
1136
1137      addProtectedField( "baseTexSize", TypeS32, Offset( mBaseTexSize, TerrainBlock ),
1138         &TerrainBlock::_setBaseTexSize, &defaultProtectedGetFn,
1139         "Size of base texture size per meter." );
1140
1141      addProtectedField("baseTexFormat", TYPEID<baseTexFormat>(), Offset(mBaseTexFormat, TerrainBlock),
1142         &TerrainBlock::_setBaseTexFormat, &defaultProtectedGetFn,
1143         "");
1144
1145      addProtectedField( "lightMapSize", TypeS32, Offset( mLightMapSize, TerrainBlock ),
1146         &TerrainBlock::_setLightMapSize, &defaultProtectedGetFn,
1147         "Light map dimensions in pixels." );
1148
1149      addField( "screenError", TypeS32, Offset( mScreenError, TerrainBlock ), "Not yet implemented." );
1150
1151   endGroup( "Misc" );
1152
1153   Parent::initPersistFields();
1154
1155   removeField( "scale" );
1156
1157   Con::addVariable( "$TerrainBlock::debugRender", TypeBool, &smDebugRender, "Triggers debug rendering of terrain cells\n\n"
1158      "@ingroup Terrain");
1159   
1160   Con::addVariable( "$pref::Terrain::lodScale", TypeF32, &smLODScale, "A global LOD scale used to tweak the default terrain screen error value.\n\n"
1161      "@ingroup Terrain");
1162
1163   Con::addVariable( "$pref::Terrain::detailScale", TypeF32, &smDetailScale, "A global detail scale used to tweak the material detail distances.\n\n" 
1164      "@ingroup Terrain");
1165}
1166
1167void TerrainBlock::inspectPostApply()
1168{
1169   Parent::inspectPostApply();
1170   setMaskBits( MiscMask );
1171}
1172
1173U32 TerrainBlock::packUpdate(NetConnection* con, U32 mask, BitStream *stream)
1174{
1175   U32 retMask = Parent::packUpdate( con, mask, stream );
1176   
1177   if ( stream->writeFlag( mask & TransformMask ) )
1178      mathWrite( *stream, getTransform() );
1179
1180   if ( stream->writeFlag( mask & FileMask ) )
1181   {
1182      stream->write( mTerrFileName );
1183      stream->write( mCRC );
1184   }
1185
1186   if ( stream->writeFlag( mask & SizeMask ) )
1187      stream->write( mSquareSize );
1188
1189   stream->writeFlag( mCastShadows );
1190   
1191   if ( stream->writeFlag( mask & MaterialMask ) )
1192   {
1193      stream->write( mBaseTexSize );
1194      stream->write( mLightMapSize );
1195   }
1196
1197   stream->writeFlag( mask & HeightMapChangeMask );
1198
1199   if ( stream->writeFlag( mask & MiscMask ) )
1200      stream->write( mScreenError );
1201
1202   stream->writeInt(mBaseTexFormat, 32);
1203
1204   return retMask;
1205}
1206
1207void TerrainBlock::unpackUpdate(NetConnection* con, BitStream *stream)
1208{
1209   Parent::unpackUpdate( con, stream );
1210   
1211   if ( stream->readFlag() ) // TransformMask
1212   {
1213      MatrixF mat;
1214      mathRead( *stream, &mat );
1215      setTransform( mat );
1216   }
1217
1218   if ( stream->readFlag() ) // FileMask
1219   {
1220      FileName terrFile;
1221      stream->read( &terrFile );
1222      stream->read( &mCRC );
1223
1224      if ( isProperlyAdded() )
1225         setFile( terrFile );
1226      else
1227         mTerrFileName = terrFile;
1228   }
1229
1230   if ( stream->readFlag() ) // SizeMask
1231      stream->read( &mSquareSize );
1232
1233   mCastShadows = stream->readFlag();
1234
1235   if ( stream->readFlag() ) // MaterialMask
1236   {
1237      U32 baseTexSize;
1238      stream->read( &baseTexSize );
1239      if ( mBaseTexSize != baseTexSize )
1240      {
1241         mBaseTexSize = baseTexSize;
1242         if ( isProperlyAdded() )
1243            _updateBaseTexture( NONE );
1244      }
1245
1246      U32 lightMapSize;
1247      stream->read( &lightMapSize );
1248      if ( mLightMapSize != lightMapSize )
1249      {
1250         mLightMapSize = lightMapSize;
1251         if ( isProperlyAdded() )
1252         {
1253            SAFE_DELETE( mLightMap );
1254            clearLightMap();
1255         }
1256      }
1257   }
1258
1259   if ( stream->readFlag() && isProperlyAdded() ) // HeightMapChangeMask
1260   {
1261      _updateBounds();
1262      _rebuildQuadtree();
1263      _updatePhysics();
1264      mDetailsDirty = true;
1265      mLayerTexDirty = true;
1266   }
1267
1268   if ( stream->readFlag() ) // MiscMask
1269      stream->read( &mScreenError );
1270
1271   mBaseTexFormat = (BaseTexFormat)stream->readInt(32);
1272}
1273
1274void TerrainBlock::getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const 
1275{
1276   // We can get the bound height from the last grid level.
1277   const TerrainSquare *sq = mFile->findSquare( mFile->mGridLevels, 0, 0 );
1278   *minHeight = fixedToFloat( sq->minHeight );
1279   *maxHeight = fixedToFloat( sq->maxHeight );
1280}
1281
1282//-----------------------------------------------------------------------------
1283// Console Methods
1284//-----------------------------------------------------------------------------
1285
1286DefineEngineMethod( TerrainBlock, save, bool, ( const char* fileName),,
1287               "@brief Saves the terrain block's terrain file to the specified file name.\n\n"
1288
1289               "@param fileName Name and path of file to save terrain data to.\n\n"
1290
1291               "@return True if file save was successful, false otherwise")
1292{
1293   char filename[256];
1294   dStrcpy(filename,fileName);
1295   char *ext = dStrrchr(filename, '.');
1296   if (!ext || dStricmp(ext, ".ter") != 0)
1297      dStrcat(filename, ".ter");
1298   return static_cast<TerrainBlock*>(object)->save(filename);
1299}
1300
1301//ConsoleMethod(TerrainBlock, save, bool, 3, 3, "(string fileName) - saves the terrain block's terrain file to the specified file name.")
1302//{
1303//   char filename[256];
1304//   dStrcpy(filename,argv[2]);
1305//   char *ext = dStrrchr(filename, '.');
1306//   if (!ext || dStricmp(ext, ".ter") != 0)
1307//      dStrcat(filename, ".ter");
1308//   return static_cast<TerrainBlock*>(object)->save(filename);
1309//}
1310
1311ConsoleDocFragment _getTerrainHeight1(
1312   "@brief Gets the terrain height at the specified position\n\n"
1313   "@param position The world space point, minus the z (height) value. Formatted as (\"x y\")\n\n"
1314   "@return Returns the terrain height at the given point as an F32 value.\n\n"
1315   "@ingroup Terrain",
1316   NULL,
1317   "bool getTerrainHeight( Point2I position );"
1318);
1319ConsoleDocFragment _getTerrainHeight2(
1320   "@brief Gets the terrain height at the specified position\n\n"
1321   "@param x The X coordinate in world space\n"
1322   "@param y The Y coordinate in world space\n\n"
1323   "@return Returns the terrain height at the given point as an F32 value.\n\n"
1324   "@ingroup Terrain",
1325   NULL,
1326   "bool getTerrainHeight( F32 x, F32 y);"
1327);
1328
1329DefineConsoleFunction( getTerrainHeight, F32, (const char* ptOrX, const char* y), (""), "(Point2 pos) - gets the terrain height at the specified position."
1330            "@param pos The world space point, minus the z (height) value\n Can be formatted as either (\"x y\") or (x,y)\n"
1331            "@return Returns the terrain height at the given point as an F32 value.\n"
1332            "@hide")
1333{
1334   F32 height = 0.0f;
1335
1336   Point2F pos;
1337   if(!String::isEmpty(ptOrX) && String::isEmpty(y))
1338      dSscanf(ptOrX, "%f %f", &pos.x, &pos.y);
1339   else if(!String::isEmpty(ptOrX) && !String::isEmpty(y))
1340   {
1341      pos.x = dAtof(ptOrX);
1342      pos.y = dAtof(y);
1343   }
1344
1345   TerrainBlock * terrain = getTerrainUnderWorldPoint(Point3F(pos.x, pos.y, 5000.0f));
1346   if(terrain && terrain->isServerObject())
1347   {
1348      Point3F offset;
1349      terrain->getTransform().getColumn(3, &offset);
1350      pos -= Point2F(offset.x, offset.y);
1351      terrain->getHeight(pos, &height);
1352   }
1353   return height;
1354}
1355
1356ConsoleDocFragment _getTerrainHeightBelowPosition1(
1357   "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
1358   "@param position The world space point, minus the z (height) value. Formatted as (\"x y\")\n\n"
1359   "@return Returns the closest terrain height below the given point as an F32 value.\n\n"
1360   "@ingroup Terrain",
1361   NULL,
1362   "bool getTerrainHeightBelowPosition( Point2I position );"
1363);
1364ConsoleDocFragment _getTerrainHeightBelowPosition2(
1365   "@brief Takes a world point and find the \"highest\" terrain underneath it\n\n"
1366   "@param x The X coordinate in world space\n"
1367   "@param y The Y coordinate in world space\n\n"
1368   "@return Returns the closest terrain height below the given point as an F32 value.\n\n"
1369   "@ingroup Terrain",
1370   NULL,
1371   "bool getTerrainHeightBelowPosition( F32 x, F32 y);"
1372);
1373
1374DefineConsoleFunction( getTerrainHeightBelowPosition, F32, (const char* ptOrX, const char* y, const char* z), ("", ""),
1375            "(Point3F pos) - gets the terrain height at the specified position."
1376            "@param pos The world space point. Can be formatted as either (\"x y z\") or (x,y,z)\n"
1377            "@note This function is useful if you simply want to grab the terrain height underneath an object.\n"
1378            "@return Returns the terrain height at the given point as an F32 value.\n"
1379            "@hide")
1380{
1381   F32 height = 0.0f;
1382
1383   Point3F pos;
1384   if(!String::isEmpty(ptOrX) && String::isEmpty(y) && String::isEmpty(z))
1385      dSscanf(ptOrX, "%f %f %f", &pos.x, &pos.y, &pos.z);
1386   else if(!String::isEmpty(ptOrX) && !String::isEmpty(y) && !String::isEmpty(z))
1387   {
1388      pos.x = dAtof(ptOrX);
1389      pos.y = dAtof(y);
1390      pos.z = dAtof(z);
1391   }
1392
1393   TerrainBlock * terrain = getTerrainUnderWorldPoint(pos);
1394   
1395   Point2F nohghtPos(pos.x, pos.y);
1396
1397   if(terrain)
1398   {
1399      if(terrain->isServerObject())
1400      {
1401         Point3F offset;
1402         terrain->getTransform().getColumn(3, &offset);
1403         nohghtPos -= Point2F(offset.x, offset.y);
1404         terrain->getHeight(nohghtPos, &height);
1405      }
1406   }
1407   
1408   return height;
1409}
1410