1753 lines
54 KiB
C++
Executable File
1753 lines
54 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "terrain/terrData.h"
|
|
#include "dgl/dgl.h"
|
|
#include "dgl/gBitmap.h"
|
|
#include "dgl/gTexManager.h"
|
|
#include "dgl/materialList.h"
|
|
#include "dgl/materialPropertyMap.h"
|
|
#include "math/mMath.h"
|
|
#include "math/mathIO.h"
|
|
#include "core/fileStream.h"
|
|
#include "core/bitStream.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "sceneGraph/sceneGraph.h"
|
|
#include "sceneGraph/sceneState.h"
|
|
#include "sim/netConnection.h"
|
|
#include "terrain/terrRender.h"
|
|
#include "terrain/blender.h"
|
|
|
|
extern bool gDGLRender;
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock);
|
|
|
|
namespace {
|
|
|
|
void terrainTextureEventCB(const U32 eventCode, void *userData)
|
|
{
|
|
TerrainBlock* pTerr = reinterpret_cast<TerrainBlock*>(userData);
|
|
pTerr->processTextureEvent(eventCode);
|
|
}
|
|
|
|
Point3F sgTexGenS;
|
|
Point3F sgTexGenT;
|
|
Point3F sgLMGenS;
|
|
Point3F sgLMGenT;
|
|
|
|
} // namespace {}
|
|
|
|
|
|
|
|
//--------------------------------------
|
|
TerrainBlock::TerrainBlock()
|
|
{
|
|
squareSize = 8;
|
|
|
|
mBlender = NULL;
|
|
lightMap = 0;
|
|
|
|
for(S32 i = BlockShift; i >= 0; i--)
|
|
gridMap[i] = NULL;
|
|
|
|
heightMap = NULL;
|
|
materialMap = NULL;
|
|
mBaseMaterialMap = NULL;
|
|
mMaterialFileName = NULL;
|
|
mTypeMask = TerrainObjectType | StaticObjectType | StaticRenderedObjectType;
|
|
mNetFlags.set(Ghostable | ScopeAlways);
|
|
mCollideEmpty = false;
|
|
mDetailTextureName = NULL;
|
|
|
|
mBumpTextureName = NULL;
|
|
mBumpScale = 1.0;
|
|
mBumpOffset = 0.01;
|
|
mZeroBumpScale = 8;
|
|
|
|
mCRC = 0;
|
|
flagMap = 0;
|
|
mVertexBuffer = -1;
|
|
mTile = true;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
TerrainBlock::~TerrainBlock()
|
|
{
|
|
delete lightMap;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TerrainBlock::setFile(Resource<TerrainFile> terr)
|
|
{
|
|
mFile = terr;
|
|
for(U32 i = 0; i <= BlockShift; i++)
|
|
gridMap[i] = mFile->mGridMap[i];
|
|
|
|
mBaseMaterialMap = mFile->mBaseMaterialMap;
|
|
mMaterialFileName= mFile->mMaterialFileName;
|
|
mChunkMap = mFile->mChunkMap;
|
|
materialMap = mFile->mMaterialMap;
|
|
heightMap = mFile->mHeightMap;
|
|
flagMap = mFile->mFlagMap;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool TerrainBlock::save(const char *filename)
|
|
{
|
|
return mFile->save(filename);
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
static U16 calcDev(PlaneF &pl, Point3F &pt)
|
|
{
|
|
F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z;
|
|
F32 diff = z - pt.z;
|
|
if(diff < 0)
|
|
diff = -diff;
|
|
|
|
if(diff > 0xFFFF)
|
|
return 0xFFFF;
|
|
else
|
|
return U16(diff);
|
|
}
|
|
|
|
static U16 Umax(U16 u1, U16 u2)
|
|
{
|
|
return u1 > u2 ? u1 : u2;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool TerrainBlock::unpackEmptySquares()
|
|
{
|
|
U32 size = BlockSquareWidth * BlockSquareWidth;
|
|
|
|
U32 i;
|
|
for(i = 0; i < size; i++)
|
|
materialMap[i].flags &= ~Material::Empty;
|
|
|
|
// walk the pairs
|
|
for(i = 0; i < mEmptySquareRuns.size(); i++)
|
|
{
|
|
U32 offset = mEmptySquareRuns[i] & 0xffff;
|
|
U32 run = U32(mEmptySquareRuns[i]) >> 16;
|
|
|
|
//
|
|
for(U32 j = 0; j < run; j++)
|
|
{
|
|
if((offset+j) >= size)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "TerrainBlock::unpackEmpties: invalid entry.");
|
|
return(false);
|
|
}
|
|
materialMap[offset+j].flags |= Material::Empty;
|
|
}
|
|
}
|
|
|
|
rebuildEmptyFlags();
|
|
return(true);
|
|
}
|
|
|
|
void TerrainBlock::packEmptySquares()
|
|
{
|
|
AssertFatal(isServerObject(), "TerrainBlock::packEmptySquares: client!");
|
|
mEmptySquareRuns.clear();
|
|
|
|
// walk the map
|
|
U32 run = 0;
|
|
U32 offset;
|
|
|
|
U32 size = BlockSquareWidth * BlockSquareWidth;
|
|
for(U32 i = 0; i < size; i++)
|
|
{
|
|
if(materialMap[i].flags & Material::Empty)
|
|
{
|
|
if(!run)
|
|
offset = i;
|
|
run++;
|
|
}
|
|
else if(run)
|
|
{
|
|
mEmptySquareRuns.push_back((run << 16) | offset);
|
|
run = 0;
|
|
|
|
// cap it
|
|
if(mEmptySquareRuns.size() == MaxEmptyRunPairs)
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
if(run && mEmptySquareRuns.size() != MaxEmptyRunPairs)
|
|
mEmptySquareRuns.push_back((run << 16) | offset);
|
|
|
|
if(mEmptySquareRuns.size() == MaxEmptyRunPairs)
|
|
Con::warnf(ConsoleLogEntry::General, "TerrainBlock::packEmptySquares: More than %d run pairs encountered. Extras will not persist.", MaxEmptyRunPairs);
|
|
|
|
//
|
|
mNetFlags |= EmptyMask;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainBlock::rebuildEmptyFlags()
|
|
{
|
|
// rebuild entire maps empty flags!
|
|
for(U32 y = 0; y < TerrainBlock::ChunkSquareWidth; y++)
|
|
{
|
|
for(U32 x = 0; x < TerrainBlock::ChunkSquareWidth; x++)
|
|
{
|
|
GridChunk &gc = *(mChunkMap + x + (y << TerrainBlock::ChunkShift));
|
|
gc.emptyFlags = 0;
|
|
U32 sx = x << TerrainBlock::ChunkDownShift;
|
|
U32 sy = y << TerrainBlock::ChunkDownShift;
|
|
for(U32 j = 0; j < 4; j++)
|
|
{
|
|
for(U32 i = 0; i < 4; i++)
|
|
{
|
|
TerrainBlock::Material *mat = getMaterial(sx + i, sy + j);
|
|
if(mat->flags & TerrainBlock::Material::Empty)
|
|
gc.emptyFlags |= (1 << (j * 4 + i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(S32 i = BlockShift; i >= 0; i--)
|
|
{
|
|
S32 squareCount = 1 << (BlockShift - i);
|
|
S32 squareSize = (TerrainBlock::BlockSize) / squareCount;
|
|
|
|
for(S32 squareX = 0; squareX < squareCount; squareX++)
|
|
{
|
|
for(S32 squareY = 0; squareY < squareCount; squareY++)
|
|
{
|
|
GridSquare *parent = NULL;
|
|
if(i < BlockShift)
|
|
parent = findSquare(i+1, Point2I(squareX * squareSize, squareY * squareSize));
|
|
bool empty = true;
|
|
|
|
for(S32 sizeX = 0; empty && sizeX <= squareSize; sizeX++)
|
|
{
|
|
for(S32 sizeY = 0; empty && sizeY <= squareSize; sizeY++)
|
|
{
|
|
S32 x = squareX * squareSize + sizeX;
|
|
S32 y = squareY * squareSize + sizeY;
|
|
|
|
if(sizeX != squareSize && sizeY != squareSize)
|
|
{
|
|
TerrainBlock::Material *mat = getMaterial(x, y);
|
|
if(!(mat->flags & TerrainBlock::Material::Empty))
|
|
empty = false;
|
|
}
|
|
}
|
|
}
|
|
GridSquare *sq = findSquare(i, Point2I(squareX * squareSize, squareY * squareSize));
|
|
if(empty)
|
|
sq->flags |= GridSquare::Empty;
|
|
else
|
|
sq->flags &= ~GridSquare::Empty;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainBlock::setHeight(const Point2I & pos, float height)
|
|
{
|
|
// set the height
|
|
U16 ht = floatToFixed(height);
|
|
*((U16*)getHeightAddress(pos.x, pos.y)) = ht;
|
|
}
|
|
|
|
|
|
inline void getMinMax(U16 &min, U16 &max, U16 height)
|
|
{
|
|
if(height < min)
|
|
min = height;
|
|
if(height > max)
|
|
max = height;
|
|
}
|
|
|
|
inline void checkSquareMinMax(GridSquare *parent, GridSquare *child)
|
|
{
|
|
if(parent->minHeight > child->minHeight)
|
|
parent->minHeight = child->minHeight;
|
|
if(parent->maxHeight < child->maxHeight)
|
|
parent->maxHeight = child->maxHeight;
|
|
}
|
|
|
|
void TerrainBlock::updateGridMaterials(Point2I min, Point2I max)
|
|
{
|
|
// ok:
|
|
// build the chunk materials flags for all the chunks in the bounding rect
|
|
// ((min - 1) >> ChunkDownShift) up to ((max + ChunkWidth) >> ChunkDownShift)
|
|
|
|
// we have to make sure to cover boundary conditions as as stated above
|
|
// since, for example, x = 0 affects 2 chunks
|
|
|
|
for (S32 y = min.y - 1; y < max.y + 1; y++)
|
|
{
|
|
for (S32 x=min.x - 1; x < max.x + 1; x++)
|
|
{
|
|
GridSquare *sq = findSquare(0, Point2I(x, y));
|
|
sq->flags &= (GridSquare::MaterialStart -1);
|
|
S32 xpl = (x + 1) & TerrainBlock::BlockMask;
|
|
S32 ypl = (y + 1) & TerrainBlock::BlockMask;
|
|
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
|
|
{
|
|
if (mFile->mMaterialAlphaMap[i] != NULL) {
|
|
U32 mapVal = (mFile->mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + x] |
|
|
mFile->mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + x] |
|
|
mFile->mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + xpl] |
|
|
mFile->mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + xpl]);
|
|
if(mapVal)
|
|
sq->flags |= (GridSquare::MaterialStart << i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (S32 y = min.y - 2; y < max.y + 2; y += 2)
|
|
{
|
|
for (S32 x= min.x - 2; x < max.x + 2; x += 2)
|
|
{
|
|
GridSquare *sq = findSquare(1, Point2I(x, y));
|
|
GridSquare *s1 = findSquare(0, Point2I(x, y));
|
|
GridSquare *s2 = findSquare(0, Point2I(x+1, y));
|
|
GridSquare *s3 = findSquare(0, Point2I(x, y+1));
|
|
GridSquare *s4 = findSquare(0, Point2I(x+1, y+1));
|
|
sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1);
|
|
}
|
|
}
|
|
TerrainRender::flushCacheRect(RectI(min, max - min));
|
|
}
|
|
|
|
void TerrainBlock::updateGrid(Point2I min, Point2I max)
|
|
{
|
|
// ok:
|
|
// build the chunk deviance for all the chunks in the bounding rect,
|
|
// ((min - 1) >> ChunkDownShift) up to ((max + ChunkWidth) >> ChunkDownShift)
|
|
|
|
// update the chunk deviances for the affected chunks
|
|
// we have to make sure to cover boundary conditions as as stated above
|
|
// since, for example, x = 0 affects 2 chunks
|
|
|
|
for(S32 x = (min.x - 1) >> ChunkDownShift;x < (max.x + ChunkSize) >> ChunkDownShift; x++)
|
|
{
|
|
for(S32 y = (min.y - 1) >> ChunkDownShift;y < (max.y + ChunkSize) >> ChunkDownShift; y++)
|
|
{
|
|
S32 px = x;
|
|
S32 py = y;
|
|
if(px < 0)
|
|
px += BlockSize >> ChunkDownShift;
|
|
if(py < 0)
|
|
py += BlockSize >> ChunkDownShift;
|
|
|
|
buildChunkDeviance(px, py);
|
|
}
|
|
}
|
|
|
|
// ok the chunk deviances are rebuilt... now rebuild the affected area
|
|
// of the grid map:
|
|
|
|
// here's how it works:
|
|
// for the current terrain renderer we only care about
|
|
// the minHeight and maxHeight on the GridSquare
|
|
// so we do one pass through, updating minHeight and maxHeight
|
|
// on the level 0 squares, then we loop up the grid map from 1 to
|
|
// the top, expanding the bounding boxes as necessary.
|
|
// this should end up being way, way, way, way faster for the terrain
|
|
// editor
|
|
|
|
for(S32 y = min.y - 1; y < max.y + 1; y++)
|
|
{
|
|
for(S32 x = min.x - 1; x < max.x + 1; x++)
|
|
{
|
|
S32 px = x;
|
|
S32 py = y;
|
|
if(px < 0)
|
|
px += BlockSize;
|
|
if(py < 0)
|
|
py += BlockSize;
|
|
|
|
GridSquare *sq = findSquare(0, px, py);
|
|
|
|
sq->minHeight = 0xFFFF;
|
|
sq->maxHeight = 0;
|
|
|
|
getMinMax(sq->minHeight, sq->maxHeight, getHeight(x, y));
|
|
getMinMax(sq->minHeight, sq->maxHeight, getHeight(x+1, y));
|
|
getMinMax(sq->minHeight, sq->maxHeight, getHeight(x, y+1));
|
|
getMinMax(sq->minHeight, sq->maxHeight, getHeight(x+1, y+1));
|
|
}
|
|
}
|
|
|
|
// ok, all the level 0 grid squares are updated:
|
|
// now update all the parent grid squares that need to be updated:
|
|
|
|
for(S32 level = 1; level <= TerrainBlock::BlockShift; level++)
|
|
{
|
|
S32 size = 1 << level;
|
|
S32 halfSize = size >> 1;
|
|
for(S32 y = (min.y - 1) >> level; y < (max.y + size) >> level; y++)
|
|
{
|
|
for(S32 x = (min.x - 1) >> level; x < (max.x + size) >> level; x++)
|
|
{
|
|
S32 px = x << level;
|
|
S32 py = y << level;
|
|
|
|
GridSquare *square = findSquare(level, px, py);
|
|
square->minHeight = 0xFFFF;
|
|
square->maxHeight = 0;
|
|
|
|
checkSquareMinMax(square, findSquare(level - 1, px, py));
|
|
checkSquareMinMax(square, findSquare(level - 1, px + halfSize, py));
|
|
checkSquareMinMax(square, findSquare(level - 1, px, py + halfSize));
|
|
checkSquareMinMax(square, findSquare(level - 1, px + halfSize, py + halfSize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
bool TerrainBlock::getHeight(const Point2F &pos, F32 *height)
|
|
{
|
|
float invSquareSize = 1.0f / (float)squareSize;
|
|
float xp = pos.x * invSquareSize;
|
|
float yp = pos.y * invSquareSize;
|
|
int x = (S32)mFloor(xp);
|
|
int y = (S32)mFloor(yp);
|
|
xp -= (float)x;
|
|
yp -= (float)y;
|
|
x &= BlockMask;
|
|
y &= BlockMask;
|
|
GridSquare * gs = findSquare(0, Point2I(x,y));
|
|
|
|
if (gs->flags & GridSquare::Empty)
|
|
return false;
|
|
|
|
float zBottomLeft = fixedToFloat(getHeight(x, y));
|
|
float zBottomRight = fixedToFloat(getHeight(x + 1, y));
|
|
float zTopLeft = fixedToFloat(getHeight(x, y + 1));
|
|
float zTopRight = fixedToFloat(getHeight(x + 1, y + 1));
|
|
|
|
if(gs->flags & GridSquare::Split45)
|
|
{
|
|
if (xp>yp)
|
|
// bottom half
|
|
*height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
|
|
else
|
|
// top half
|
|
*height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
|
|
}
|
|
else
|
|
{
|
|
if (1.0f-xp>yp)
|
|
// bottom half
|
|
*height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
|
|
else
|
|
// top half
|
|
*height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TerrainBlock::getNormal(const Point2F & pos, Point3F * normal, bool normalize)
|
|
{
|
|
float invSquareSize = 1.0f / (float)squareSize;
|
|
float xp = pos.x * invSquareSize;
|
|
float yp = pos.y * invSquareSize;
|
|
int x = (S32)mFloor(xp);
|
|
int y = (S32)mFloor(yp);
|
|
xp -= (float)x;
|
|
yp -= (float)y;
|
|
x &= BlockMask;
|
|
y &= BlockMask;
|
|
GridSquare * gs = findSquare(0, Point2I(x,y));
|
|
|
|
if (gs->flags & GridSquare::Empty)
|
|
return false;
|
|
|
|
float zBottomLeft = fixedToFloat(getHeight(x, y));
|
|
float zBottomRight = fixedToFloat(getHeight(x + 1, y));
|
|
float zTopLeft = fixedToFloat(getHeight(x, y + 1));
|
|
float zTopRight = fixedToFloat(getHeight(x + 1, y + 1));
|
|
|
|
if(gs->flags & GridSquare::Split45)
|
|
{
|
|
if (xp>yp)
|
|
// bottom half
|
|
normal->set(zBottomLeft-zBottomRight,zBottomRight-zTopRight,squareSize);
|
|
else
|
|
// top half
|
|
normal->set(zTopLeft-zTopRight,zBottomLeft-zTopLeft,squareSize);
|
|
}
|
|
else
|
|
{
|
|
if (1.0f-xp>yp)
|
|
// bottom half
|
|
normal->set(zBottomLeft-zBottomRight,zBottomLeft-zTopLeft,squareSize);
|
|
else
|
|
// top half
|
|
normal->set(zTopLeft-zTopRight,zBottomRight-zTopRight,squareSize);
|
|
}
|
|
if (normalize)
|
|
normal->normalize();
|
|
return true;
|
|
}
|
|
|
|
bool TerrainBlock::getNormalAndHeight(const Point2F & pos, Point3F * normal, F32 * height, bool normalize)
|
|
{
|
|
float invSquareSize = 1.0f / (float)squareSize;
|
|
float xp = pos.x * invSquareSize;
|
|
float yp = pos.y * invSquareSize;
|
|
int x = (S32)mFloor(xp);
|
|
int y = (S32)mFloor(yp);
|
|
xp -= (float)x;
|
|
yp -= (float)y;
|
|
x &= BlockMask;
|
|
y &= BlockMask;
|
|
GridSquare * gs = findSquare(0, Point2I(x,y));
|
|
|
|
if (gs->flags & GridSquare::Empty)
|
|
return false;
|
|
|
|
float zBottomLeft = fixedToFloat(getHeight(x, y));
|
|
float zBottomRight = fixedToFloat(getHeight(x + 1, y));
|
|
float zTopLeft = fixedToFloat(getHeight(x, y + 1));
|
|
float zTopRight = fixedToFloat(getHeight(x + 1, y + 1));
|
|
|
|
if(gs->flags & GridSquare::Split45)
|
|
{
|
|
if (xp>yp)
|
|
{
|
|
// bottom half
|
|
normal->set(zBottomLeft-zBottomRight,zBottomRight-zTopRight,squareSize);
|
|
*height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight);
|
|
}
|
|
else
|
|
{
|
|
// top half
|
|
normal->set(zTopLeft-zTopRight,zBottomLeft-zTopLeft,squareSize);
|
|
*height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (1.0f-xp>yp)
|
|
{
|
|
// bottom half
|
|
normal->set(zBottomLeft-zBottomRight,zBottomLeft-zTopLeft,squareSize);
|
|
*height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft);
|
|
}
|
|
else
|
|
{
|
|
// top half
|
|
normal->set(zTopLeft-zTopRight,zBottomRight-zTopRight,squareSize);
|
|
*height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight);
|
|
}
|
|
}
|
|
if (normalize)
|
|
normal->normalize();
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainBlock::setBaseMaterials(S32 argc, const char *argv[])
|
|
{
|
|
for (S32 i = 0; i < argc; i++)
|
|
mMaterialFileName[i] = StringTable->insert(argv[i]);
|
|
for (S32 j = argc; j < MaterialGroups; j++)
|
|
mMaterialFileName[j] = NULL;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//--------------------------------------
|
|
|
|
bool TerrainBlock::buildMaterialMap()
|
|
{
|
|
TerrainRender::flushCache();
|
|
return initMMXBlender();
|
|
}
|
|
|
|
// This routine takes 256x256 bitmap and makes
|
|
// sure that each 128 x 128 grid has borders that
|
|
// match adjacent 128 x 128 grids. This fixes a
|
|
// terrain blending error/defect.
|
|
void tweakTerrainBmp(GBitmap * pBitmap)
|
|
{
|
|
AssertFatal(pBitmap->getWidth() == pBitmap->getHeight() &&
|
|
pBitmap->getFormat()==GBitmap::RGB,
|
|
"tweakTerrainBmp - This should have been caught before, but this is an invalid terrain texture.");
|
|
|
|
S32 size = pBitmap->getWidth();
|
|
for (int k = 0; k < pBitmap->getNumMipLevels() - 4; k++)
|
|
{
|
|
U8 * bits = pBitmap->getWritableBits(k);
|
|
S32 offset1 = -1;
|
|
S32 offset2 = 0;
|
|
S32 base;
|
|
for (base=0; base<size; base += size / 2)
|
|
{
|
|
S32 top1 = (base + offset1 + size) % size;
|
|
top1 *= 3;
|
|
S32 top2 = (base + offset2 + size) % size;
|
|
top2 *= 3;
|
|
|
|
// slide down column
|
|
for (S32 i=0; i<size; i++)
|
|
{
|
|
for (S32 j=0; j<3; j++)
|
|
{
|
|
U8 val1 = bits[top1 + i*size*3 + j];
|
|
U8 val2 = bits[top2 + i*size*3 + j];
|
|
U8 val = (val1 >> 1) + (val2 >> 1);
|
|
if (val1 & val2 & 1)
|
|
++val;
|
|
bits[top1 + i*size*3 + j] = bits[top2 + i*size*3 + j] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (base=0; base<size; base += size / 2)
|
|
{
|
|
S32 left1 = (base + offset1 + size) % size;
|
|
left1 *= size * 3;
|
|
S32 left2 = (base + offset2 + size) % size;
|
|
left2 *= size * 3;
|
|
|
|
// slide across row
|
|
for (S32 i=0; i<size; i++)
|
|
{
|
|
for (S32 j=0; j<3; j++)
|
|
{
|
|
U8 val1 = bits[left1 + i*3 + j];
|
|
U8 val2 = bits[left2 + i*3 + j];
|
|
U8 val = (val1 >> 1) + (val2 >> 1);
|
|
if (val1 & val2 & 1)
|
|
++val;
|
|
bits[left1+i*3 + j] = bits[left2 + i*3 + j] = val;
|
|
}
|
|
}
|
|
}
|
|
size /= 2;
|
|
}
|
|
}
|
|
|
|
bool TerrainBlock::initMMXBlender()
|
|
{
|
|
// DMMNOTE: come back to this
|
|
delete mBlender;
|
|
mBlender = NULL;
|
|
|
|
char fileBuf[256];
|
|
|
|
U32 validMaterials = 0;
|
|
S32 i;
|
|
for (i = 0; i < MaterialGroups; i++)
|
|
{
|
|
if (mMaterialFileName[i] && *mMaterialFileName[i])
|
|
validMaterials++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
AssertFatal(validMaterials != 0, "Error, must have SOME materials here!");
|
|
|
|
// Submit alphamaps
|
|
U8* alphaMaterials[MaterialGroups];
|
|
dMemset(alphaMaterials, 0, sizeof(alphaMaterials));
|
|
for (i = 0; i < validMaterials; i++) {
|
|
if (getMaterialAlphaMap(i) == NULL) {
|
|
AssertFatal(getMaterialAlphaMap(i) != NULL, "Error, need an alpha map here!");
|
|
return false;
|
|
}
|
|
alphaMaterials[i] = getMaterialAlphaMap(i);
|
|
}
|
|
|
|
mBlender = new Blender(validMaterials, 5, alphaMaterials);
|
|
|
|
// Ok, we have validMaterials set correctly
|
|
bool matsValid = true;
|
|
for(i = 0; i < validMaterials; i++)
|
|
{
|
|
AssertFatal(mMaterialFileName[i] && *mMaterialFileName[i], "Error, something wacky here");
|
|
StringTableEntry fn = mMaterialFileName[i];
|
|
|
|
GBitmap* pBitmap = TextureManager::loadBitmapInstance(fn);
|
|
|
|
if (!pBitmap)
|
|
{
|
|
dStrcpyl(fileBuf, sizeof(fileBuf), mFile.getFilePath(), "/", fn, NULL);
|
|
pBitmap = TextureManager::loadBitmapInstance(fileBuf);
|
|
|
|
if(pBitmap)
|
|
mMaterialFileName[i] = StringTable->insert(fileBuf, true);
|
|
|
|
if(!pBitmap)
|
|
{
|
|
// Try the default..
|
|
Con::errorf("Missing terrain texture: %s",fileBuf);
|
|
matsValid = false;
|
|
dStrcpyl(fileBuf, sizeof(fileBuf), mFile.getFilePath(), "/","default",NULL);
|
|
pBitmap = TextureManager::loadBitmapInstance(fileBuf);
|
|
if(!pBitmap)
|
|
pBitmap = new GBitmap(256,256);
|
|
}
|
|
}
|
|
|
|
pBitmap->extrudeMipLevels();
|
|
tweakTerrainBmp(pBitmap);
|
|
|
|
// Submit as material for group i
|
|
const U8* pMipmaps[5];
|
|
for (U32 j = 0; j < 5; j++)
|
|
pMipmaps[j] = pBitmap->getBits(j);
|
|
|
|
mBlender->addSourceTexture(i, pMipmaps);
|
|
|
|
delete pBitmap;
|
|
}
|
|
|
|
// ok, if the material list load failed...
|
|
// if this is a local connection, we'll assume that's ok
|
|
// and just have white textures...
|
|
// otherwise we want to return false.
|
|
return matsValid || bool(mServerObject);
|
|
}
|
|
|
|
void TerrainBlock::getMaterialAlpha(Point2I pos, U8 alphas[MaterialGroups])
|
|
{
|
|
pos.x &= BlockMask;
|
|
pos.y &= BlockMask;
|
|
U32 offset = pos.x + pos.y * BlockSize;
|
|
for(S32 i = 0; i < MaterialGroups; i++)
|
|
{
|
|
U8 *map = mFile->mMaterialAlphaMap[i];
|
|
if(map)
|
|
alphas[i] = map[offset];
|
|
else
|
|
alphas[i] = 0;
|
|
}
|
|
}
|
|
|
|
void TerrainBlock::setMaterialAlpha(Point2I pos, const U8 alphas[MaterialGroups])
|
|
{
|
|
pos.x &= BlockMask;
|
|
pos.y &= BlockMask;
|
|
U32 offset = pos.x + pos.y * BlockSize;
|
|
for(S32 i = 0; i < MaterialGroups; i++)
|
|
{
|
|
U8 *map = mFile->mMaterialAlphaMap[i];
|
|
if(map)
|
|
map[offset] = alphas[i];
|
|
}
|
|
}
|
|
|
|
S32 TerrainBlock::getMaterialAlphaIndex(const char *materialName)
|
|
{
|
|
|
|
for (S32 i = 0; i < MaterialGroups; i++)
|
|
if (mMaterialFileName[i] && *mMaterialFileName[i] && !dStricmp(materialName, mMaterialFileName[i]))
|
|
return i;
|
|
// ok, it wasn't in the list
|
|
// see if we can add it:
|
|
for (S32 i = 0; i < MaterialGroups; i++)
|
|
{
|
|
if (!mMaterialFileName[i] || !*mMaterialFileName[i])
|
|
{
|
|
mMaterialFileName[i] = StringTable->insert(materialName);
|
|
mFile->mMaterialAlphaMap[i] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize];
|
|
dMemset(mFile->mMaterialAlphaMap[i], 0, TerrainBlock::BlockSize * TerrainBlock::BlockSize);
|
|
buildMaterialMap();
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainBlock::refreshMaterialLists()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void TerrainBlock::onEditorEnable()
|
|
{
|
|
// need to load in the material base material lists
|
|
if(isClientObject())
|
|
refreshMaterialLists();
|
|
}
|
|
|
|
void TerrainBlock::onEditorDisable()
|
|
{
|
|
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool TerrainBlock::onAdd()
|
|
{
|
|
if(!Parent::onAdd())
|
|
return false;
|
|
|
|
setPosition(Point3F(-squareSize * (BlockSize >> 1), -squareSize * (BlockSize >> 1), 0));
|
|
|
|
Resource<TerrainFile> terr = ResourceManager->load(mTerrFileName, true);
|
|
if(!bool(terr))
|
|
{
|
|
if(isClientObject())
|
|
NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName);
|
|
return false;
|
|
}
|
|
|
|
setFile(terr);
|
|
MaterialPropertyMap* pMatMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
|
|
|
|
for (int i = 0; i < TerrainBlock::MaterialGroups; i++)
|
|
{
|
|
StringTableEntry fn = mMaterialFileName[i];
|
|
if (fn != NULL)
|
|
{
|
|
if(!dStrncmp(fn, "terrain.", 8))
|
|
fn += 8;
|
|
|
|
char nameBuff[512];
|
|
dStrcpy(nameBuff, mTerrFileName);
|
|
const char *p = dStrrchr(fn, '/');
|
|
|
|
if(p)
|
|
p++;
|
|
else
|
|
p = nameBuff;
|
|
|
|
mMPMIndex[i] = pMatMap->getIndexFromName( p );
|
|
}
|
|
else
|
|
mMPMIndex[i] = -1;
|
|
}
|
|
|
|
setGlobalBounds();
|
|
resetWorldBox();
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
if (isClientObject())
|
|
{
|
|
if(mCRC != terr.getCRC())
|
|
{
|
|
NetConnection::setLastError("Your terrain file doesn't match the version that is running on the server.");
|
|
return false;
|
|
}
|
|
|
|
if(mDetailTextureName && mDetailTextureName[0])
|
|
mDetailTextureHandle = TextureHandle(mDetailTextureName, DetailTexture);
|
|
|
|
if (mBumpTextureName && mBumpTextureName[0])
|
|
{
|
|
mBumpTextureHandle = TextureHandle(mBumpTextureName, BumpTexture);
|
|
if (!mBumpTextureHandle.getGLName())
|
|
Con::errorf("Error: missing bump map texture\n%s", mBumpTextureName);
|
|
else
|
|
mInvertedBumpTextureHandle = TextureHandle(mBumpTextureName, InvertedBumpTexture);
|
|
}
|
|
|
|
lightMap = new GBitmap(LightmapSize, LightmapSize, false, GBitmap::RGB5551);
|
|
|
|
if (!buildMaterialMap())
|
|
return false;
|
|
|
|
mTextureCallbackKey = TextureManager::registerEventCallback(terrainTextureEventCB, this);
|
|
|
|
mDynLightTexture = TextureHandle("common/lighting/lightFalloffMono", BitmapTexture, true);
|
|
|
|
if (dglDoesSupportVertexBuffer())
|
|
mVertexBuffer = glAllocateVertexBufferEXT(VertexBufferSize,GL_V12MTVFMT_EXT,true);
|
|
else
|
|
mVertexBuffer = -1;
|
|
}
|
|
else
|
|
mCRC = terr.getCRC();
|
|
|
|
addToScene();
|
|
|
|
if(!unpackEmptySquares())
|
|
return(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------
|
|
void TerrainBlock::onRemove()
|
|
{
|
|
delete mBlender;
|
|
mBlender = NULL;
|
|
|
|
removeFromScene();
|
|
|
|
if (mVertexBuffer != -1)
|
|
{
|
|
if (dglDoesSupportVertexBuffer())
|
|
glFreeVertexBufferEXT(mVertexBuffer);
|
|
else
|
|
AssertFatal(false,"Vertex buffer should have already been freed!");
|
|
mVertexBuffer = -1;
|
|
}
|
|
|
|
TerrainRender::flushCache();
|
|
TextureManager::unregisterEventCallback(mTextureCallbackKey);
|
|
Parent::onRemove();
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TerrainBlock::processTextureEvent(const U32 eventCode)
|
|
{
|
|
if (eventCode == TextureManager::BeginZombification) {
|
|
// Delete any textures we have built from the dmls...
|
|
TerrainRender::flushCache();
|
|
for(S32 i = 0; i < 16; i++)
|
|
baseTextures[i] = NULL;
|
|
if (mVertexBuffer != -1)
|
|
{
|
|
if (dglDoesSupportVertexBuffer())
|
|
glFreeVertexBufferEXT(mVertexBuffer);
|
|
else
|
|
AssertFatal(false,"Vertex buffer should have already been freed!");
|
|
mVertexBuffer = -1;
|
|
}
|
|
} else if (eventCode == TextureManager::CacheResurrected) {
|
|
if (dglDoesSupportVertexBuffer())
|
|
mVertexBuffer = glAllocateVertexBufferEXT(VertexBufferSize,GL_V12MTVFMT_EXT,true);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool TerrainBlock::prepRenderImage(SceneState* state, const U32 stateKey,
|
|
const U32 /*startZone*/, const bool /*modifyBaseState*/)
|
|
{
|
|
if (isLastState(state, stateKey))
|
|
return false;
|
|
setLastState(state, stateKey);
|
|
|
|
// This should be sufficient for most objects that don't manage zones, and
|
|
// don't need to return a specialized RenderImage...
|
|
bool render = false;
|
|
if (state->isTerrainOverridden() == false)
|
|
render = state->isObjectRendered(this);
|
|
else
|
|
render = true;
|
|
|
|
if (render == true) {
|
|
SceneRenderImage* image = new SceneRenderImage;
|
|
image->obj = this;
|
|
image->sortType = SceneRenderImage::Terrain;
|
|
state->insertRenderImage(image);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TerrainBlock::buildChunkDeviance(S32 x, S32 y)
|
|
{
|
|
mFile->buildChunkDeviance(x, y);
|
|
}
|
|
|
|
void TerrainBlock::buildGridMap()
|
|
{
|
|
mFile->buildGridMap();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void TerrainBlock::setTransform(const MatrixF & mat)
|
|
{
|
|
Parent::setTransform(mat);
|
|
|
|
// Since the terrain is a static object, it's render transform changes 1 to 1
|
|
// with it's collision transform
|
|
setRenderTransform(mat);
|
|
}
|
|
|
|
void TerrainBlock::renderObject(SceneState* state, SceneRenderImage*)
|
|
{
|
|
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
|
|
|
|
RectI viewport;
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
dglGetViewport(&viewport);
|
|
|
|
if (state->isTerrainOverridden() == false)
|
|
{
|
|
state->setupObjectProjection(this);
|
|
}
|
|
else
|
|
{
|
|
state->setupBaseProjection();
|
|
}
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
dglMultMatrix(&mRenderObjToWorld);
|
|
glScalef(mObjScale.x, mObjScale.y, mObjScale.z);
|
|
|
|
TerrainRender::renderBlock(this, state);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPopMatrix();
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
dglSetViewport(viewport);
|
|
|
|
dglSetCanonicalState();
|
|
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
|
|
}
|
|
|
|
//--------------------------------------
|
|
void TerrainBlock::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addGroup("Media");
|
|
addField("detailTexture", TypeFilename, Offset(mDetailTextureName, TerrainBlock));
|
|
addField("terrainFile", TypeFilename, Offset(mTerrFileName, TerrainBlock));
|
|
addField("bumpTexture", TypeFilename, Offset(mBumpTextureName, TerrainBlock));
|
|
endGroup("Media");
|
|
|
|
addGroup("Misc");
|
|
addField("squareSize", TypeS32, Offset(squareSize, TerrainBlock));
|
|
addField("emptySquares", TypeS32Vector, Offset(mEmptySquareRuns, TerrainBlock));
|
|
addField("bumpScale", TypeF32, Offset(mBumpScale, TerrainBlock));
|
|
addField("bumpOffset", TypeF32, Offset(mBumpOffset, TerrainBlock));
|
|
addField("zeroBumpScale", TypeS32, Offset(mZeroBumpScale, TerrainBlock));
|
|
addField("tile", TypeBool, Offset(mTile, TerrainBlock));
|
|
endGroup("Misc");
|
|
|
|
removeField("position");
|
|
}
|
|
|
|
void TerrainBlock::inspectPostApply()
|
|
{
|
|
Parent::inspectPostApply();
|
|
if(isServerObject())
|
|
setMaskBits(InitMask);
|
|
}
|
|
|
|
//--------------------------------------
|
|
U32 TerrainBlock::packUpdate(NetConnection *, U32 mask, BitStream *stream)
|
|
{
|
|
if(stream->writeFlag(mask & InitMask))
|
|
{
|
|
stream->write(mCRC);
|
|
stream->writeFlag(mTile);
|
|
stream->writeString(mTerrFileName);
|
|
stream->writeString(mDetailTextureName);
|
|
|
|
stream->writeString(mBumpTextureName);
|
|
//don't have negative bump scale and don't have one too small
|
|
if (mBumpScale <= 0)
|
|
{
|
|
Con::errorf("Bump scale cannot be less than or equal to 0. Setting to 0.0001.");
|
|
mBumpScale = 0.0001;
|
|
}
|
|
stream->write(mBumpScale);
|
|
stream->write(mBumpOffset);
|
|
stream->write(mZeroBumpScale);
|
|
|
|
stream->write(squareSize);
|
|
|
|
// write out the empty rle vector
|
|
stream->write(mEmptySquareRuns.size());
|
|
for(U32 i = 0; i < mEmptySquareRuns.size(); i++)
|
|
stream->write(mEmptySquareRuns[i]);
|
|
}
|
|
else // normal update
|
|
{
|
|
if(stream->writeFlag(mask & EmptyMask))
|
|
{
|
|
// write out the empty rle vector
|
|
stream->write(mEmptySquareRuns.size());
|
|
for(U32 i = 0; i < mEmptySquareRuns.size(); i++)
|
|
stream->write(mEmptySquareRuns[i]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TerrainBlock::unpackUpdate(NetConnection *, BitStream *stream)
|
|
{
|
|
if(stream->readFlag()) // init
|
|
{
|
|
stream->read(&mCRC);
|
|
mTile = stream->readFlag();
|
|
mTerrFileName = stream->readSTString();
|
|
mDetailTextureName = stream->readSTString();
|
|
//CW - bump mapping stuff
|
|
mBumpTextureName = stream->readSTString();
|
|
stream->read(&mBumpScale);
|
|
stream->read(&mBumpOffset);
|
|
stream->read(&mZeroBumpScale);
|
|
//CW - end bump mapping stuff
|
|
stream->read(&squareSize);
|
|
|
|
// read in the empty rle
|
|
U32 size;
|
|
stream->read(&size);
|
|
mEmptySquareRuns.setSize(size);
|
|
for(U32 i = 0; i < size; i++)
|
|
stream->read(&mEmptySquareRuns[i]);
|
|
|
|
// make sure detail and bump textures are set correctly
|
|
if((mDetailTextureName && mDetailTextureName[0]) || (mBumpTextureName && mBumpTextureName[0]))
|
|
{
|
|
mDetailTextureHandle = TextureHandle(mDetailTextureName, DetailTexture);
|
|
mBumpTextureHandle = TextureHandle(mBumpTextureName, BumpTexture);
|
|
mInvertedBumpTextureHandle = TextureHandle(mBumpTextureName, InvertedBumpTexture);
|
|
}
|
|
else
|
|
{
|
|
mDetailTextureHandle = NULL;
|
|
mBumpTextureHandle = NULL;
|
|
mInvertedBumpTextureHandle = NULL;
|
|
}
|
|
}
|
|
else // normal update
|
|
{
|
|
if(stream->readFlag()) // empty
|
|
{
|
|
// read in the empty rle
|
|
U32 size;
|
|
stream->read(&size);
|
|
mEmptySquareRuns.setSize(size);
|
|
for(U32 i = 0; i < size; i++)
|
|
stream->read(&mEmptySquareRuns[i]);
|
|
|
|
//
|
|
if(materialMap)
|
|
unpackEmptySquares();
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
ConsoleFunction(makeTestTerrain, void, 2, 10, "(string fileName, ...) - makes a test terrain file - arguments after the fileName are the names of the initial terrain materials.")
|
|
{
|
|
TerrainFile *file = new TerrainFile;
|
|
S32 nMaterialTypes;
|
|
argc -= 2;
|
|
|
|
// Load materials
|
|
if (argc > 1)
|
|
{
|
|
nMaterialTypes = argc;
|
|
for (S32 i=0; i<TerrainBlock::MaterialGroups && i < argc; i++)
|
|
{
|
|
char material[256];
|
|
char *ext;
|
|
dStrcpy(material, argv[i+2]);
|
|
ext = dStrrchr(material, '.');
|
|
if (ext)
|
|
*ext = 0;
|
|
file->mMaterialFileName[i] = StringTable->insert(material);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nMaterialTypes = 1;
|
|
file->mMaterialFileName[0] = StringTable->insert("Default");
|
|
}
|
|
|
|
// create circular cone in the middle of the map:
|
|
S32 i, j;
|
|
for(i = 0; i < TerrainBlock::BlockSize; i++)
|
|
{
|
|
for(j = 0; j < TerrainBlock::BlockSize; j++)
|
|
{
|
|
S32 x = i & 0x7f;
|
|
S32 y = j & 0x7f;
|
|
|
|
F32 dist = mSqrt((64 - x) * (64 - x) + (64 - y) * (64 - y));
|
|
dist /= 64.0f;
|
|
|
|
if(dist > 1)
|
|
dist = 1;
|
|
|
|
U32 offset = i + (j << TerrainBlock::BlockShift);
|
|
file->mHeightMap[offset] = (U16)((1 - dist) * (1 - dist) * 20000);
|
|
file->mBaseMaterialMap[offset] = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
char filename[256];
|
|
dStrcpy(filename,argv[1]);
|
|
char* ext = dStrrchr(filename, '.');
|
|
if (!ext || dStricmp(ext, ".ter") != 0)
|
|
dStrcat(filename, ".ter");
|
|
file->save(filename);
|
|
delete file;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
ConsoleMethod(TerrainBlock, save, bool, 3, 3, "(string fileName) - saves the terrain block's terrain file to the specified file name.")
|
|
{
|
|
char filename[256];
|
|
dStrcpy(filename,argv[2]);
|
|
char *ext = dStrrchr(filename, '.');
|
|
if (!ext || dStricmp(ext, ".ter") != 0)
|
|
dStrcat(filename, ".ter");
|
|
return object->save(filename);
|
|
}
|
|
|
|
ConsoleMethod(TerrainBlock, getSquareSize, F32, 1, 1, "Return the square size of the terrain.")
|
|
{
|
|
return object->getSquareSize();
|
|
}
|
|
|
|
ConsoleFunction(getTerrainHeight, F32, 2, 2, "(Point2I pos) - gets the terrain height at the specified position.")
|
|
{
|
|
Point2F pos;
|
|
F32 height = 0.0f;
|
|
dSscanf(argv[1],"%g %g",&pos.x,&pos.y);
|
|
TerrainBlock * terrain = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
|
|
if(terrain)
|
|
if(terrain->isServerObject())
|
|
{
|
|
Point3F offset;
|
|
terrain->getTransform().getColumn(3, &offset);
|
|
pos -= Point2F(offset.x, offset.y);
|
|
terrain->getHeight(pos, &height);
|
|
}
|
|
return height;
|
|
}
|
|
|
|
ConsoleMethod(TerrainBlock, setTextureScript, void, 3, 3, "(string script) - sets the texture script associated with this terrain file.")
|
|
{
|
|
object->getFile()->setTextureScript(argv[2]);
|
|
}
|
|
|
|
ConsoleMethod(TerrainBlock, setHeightfieldScript, void, 3, 3, "(string script) - sets the heightfield script associated with this terrain file.")
|
|
{
|
|
object->getFile()->setHeightfieldScript(argv[2]);
|
|
}
|
|
|
|
ConsoleMethod(TerrainBlock, getTextureScript, const char *, 2, 2, "() - gets the texture script associated with the terrain file.")
|
|
{
|
|
return object->getFile()->getTextureScript();
|
|
}
|
|
|
|
ConsoleMethod(TerrainBlock, getHeightfieldScript, const char *, 2, 2, "() - gets the heightfield script associated with the terrain file.")
|
|
{
|
|
return object->getFile()->getHeightfieldScript();
|
|
}
|
|
|
|
|
|
void TerrainBlock::flushCache()
|
|
{
|
|
TerrainRender::flushCache();
|
|
}
|
|
|
|
//--------------------------------------
|
|
TerrainFile::TerrainFile()
|
|
{
|
|
for(S32 i=0; i < TerrainBlock::MaterialGroups; i++)
|
|
{
|
|
mMaterialFileName[i] = NULL;
|
|
mMaterialAlphaMap[i] = NULL;
|
|
}
|
|
mTextureScript = 0;
|
|
mHeightfieldScript = 0;
|
|
}
|
|
|
|
TerrainFile::~TerrainFile()
|
|
{
|
|
for(S32 i=0; i < TerrainBlock::MaterialGroups; i++) {
|
|
delete[] mMaterialAlphaMap[i];
|
|
mMaterialAlphaMap[i] = NULL;
|
|
}
|
|
dFree(mTextureScript);
|
|
dFree(mHeightfieldScript);
|
|
}
|
|
|
|
void TerrainFile::setTextureScript(const char *script)
|
|
{
|
|
dFree(mTextureScript);
|
|
mTextureScript = dStrdup(script);
|
|
}
|
|
|
|
void TerrainFile::setHeightfieldScript(const char *script)
|
|
{
|
|
dFree(mHeightfieldScript);
|
|
mHeightfieldScript = dStrdup(script);
|
|
}
|
|
|
|
const char *TerrainFile::getTextureScript()
|
|
{
|
|
return mTextureScript ? mTextureScript : "";
|
|
}
|
|
|
|
const char *TerrainFile::getHeightfieldScript()
|
|
{
|
|
return mHeightfieldScript ? mHeightfieldScript : "";
|
|
}
|
|
|
|
//--------------------------------------
|
|
bool TerrainFile::save(const char *filename)
|
|
{
|
|
FileStream writeFile;
|
|
if (!ResourceManager->openFileForWrite(writeFile, filename))
|
|
return false;
|
|
|
|
// write the VERSION and HeightField
|
|
writeFile.write((U8)FILE_VERSION);
|
|
for (S32 i=0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++)
|
|
writeFile.write(mHeightMap[i]);
|
|
|
|
// write the material group map, after merging the flags...
|
|
TerrainBlock::Material * materialMap = (TerrainBlock::Material*)mMaterialMap;
|
|
for (S32 j=0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++)
|
|
{
|
|
U8 val = mBaseMaterialMap[j];
|
|
val |= materialMap[j].flags & TerrainBlock::Material::PersistMask;
|
|
writeFile.write(val);
|
|
}
|
|
|
|
// write the MaterialList Info
|
|
S32 k;
|
|
for(k=0; k < TerrainBlock::MaterialGroups; k++)
|
|
{
|
|
// ok, only write out the material string if there is a non-zero
|
|
// alpha material:
|
|
if(mMaterialFileName[k] && mMaterialFileName[k][0])
|
|
{
|
|
S32 n;
|
|
for(n = 0; n < TerrainBlock::BlockSize * TerrainBlock::BlockSize; n++)
|
|
if(mMaterialAlphaMap[k][n])
|
|
break;
|
|
if(n == TerrainBlock::BlockSize * TerrainBlock::BlockSize)
|
|
mMaterialFileName[k] = 0;
|
|
}
|
|
writeFile.writeString(mMaterialFileName[k]);
|
|
}
|
|
for(k=0; k < TerrainBlock::MaterialGroups; k++) {
|
|
if(mMaterialFileName[k] && mMaterialFileName[k][0]) {
|
|
AssertFatal(mMaterialAlphaMap[k] != NULL, "Error, must have a material map here!");
|
|
writeFile.write(TerrainBlock::BlockSize * TerrainBlock::BlockSize, mMaterialAlphaMap[k]);
|
|
}
|
|
}
|
|
if(mTextureScript)
|
|
{
|
|
U32 len = dStrlen(mTextureScript);
|
|
writeFile.write(len);
|
|
writeFile.write(len, mTextureScript);
|
|
}
|
|
else
|
|
writeFile.write(U32(0));
|
|
|
|
if(mHeightfieldScript)
|
|
{
|
|
U32 len = dStrlen(mHeightfieldScript);
|
|
writeFile.write(len);
|
|
writeFile.write(len, mHeightfieldScript);
|
|
}
|
|
else
|
|
writeFile.write(U32(0));
|
|
|
|
return (writeFile.getStatus() == FileStream::Ok);
|
|
}
|
|
|
|
//--------------------------------------
|
|
|
|
void TerrainFile::heightDevLine(U32 p1x, U32 p1y, U32 p2x, U32 p2y, U32 pmx, U32 pmy, U16 *devPtr)
|
|
{
|
|
S32 h1 = getHeight(p1x, p1y);
|
|
S32 h2 = getHeight(p2x, p2y);
|
|
S32 hm = getHeight(pmx, pmy);
|
|
S32 dev = ((h2 + h1) >> 1) - hm;
|
|
if(dev < 0)
|
|
dev = -dev;
|
|
if(dev > *devPtr)
|
|
*devPtr = dev;
|
|
}
|
|
|
|
void TerrainFile::buildChunkDeviance(S32 x, S32 y)
|
|
{
|
|
GridChunk &gc = *(mChunkMap + x + (y << TerrainBlock::ChunkShift));
|
|
gc.emptyFlags = 0;
|
|
U32 sx = x << TerrainBlock::ChunkDownShift;
|
|
U32 sy = y << TerrainBlock::ChunkDownShift;
|
|
|
|
gc.heightDeviance[0] = 0;
|
|
|
|
heightDevLine(sx, sy, sx + 2, sy, sx + 1, sy, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 2, sy, sx + 4, sy, sx + 3, sy, &gc.heightDeviance[0]);
|
|
|
|
heightDevLine(sx, sy + 2, sx + 2, sy + 2, sx + 1, sy + 2, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 2, sy + 2, sx + 4, sy + 2, sx + 3, sy + 2, &gc.heightDeviance[0]);
|
|
|
|
heightDevLine(sx, sy + 4, sx + 2, sy + 4, sx + 1, sy + 4, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 2, sy + 4, sx + 4, sy + 4, sx + 3, sy + 4, &gc.heightDeviance[0]);
|
|
|
|
heightDevLine(sx, sy, sx, sy + 2, sx, sy + 1, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 2, sy, sx + 2, sy + 2, sx + 2, sy + 1, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 4, sy, sx + 4, sy + 2, sx + 4, sy + 1, &gc.heightDeviance[0]);
|
|
|
|
heightDevLine(sx, sy + 2, sx, sy + 4, sx, sy + 3, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 2, sy + 2, sx + 2, sy + 4, sx + 2, sy + 3, &gc.heightDeviance[0]);
|
|
heightDevLine(sx + 4, sy + 2, sx + 4, sy + 4, sx + 4, sy + 3, &gc.heightDeviance[0]);
|
|
|
|
gc.heightDeviance[1] = gc.heightDeviance[0];
|
|
|
|
heightDevLine(sx, sy, sx + 2, sy + 2, sx + 1, sy + 1, &gc.heightDeviance[1]);
|
|
heightDevLine(sx + 2, sy, sx, sy + 2, sx + 1, sy + 1, &gc.heightDeviance[1]);
|
|
|
|
heightDevLine(sx + 2, sy, sx + 4, sy + 2, sx + 3, sy + 1, &gc.heightDeviance[1]);
|
|
heightDevLine(sx + 2, sy + 2, sx + 4, sy, sx + 3, sy + 1, &gc.heightDeviance[1]);
|
|
|
|
heightDevLine(sx, sy + 2, sx + 2, sy + 4, sx + 1, sy + 3, &gc.heightDeviance[1]);
|
|
heightDevLine(sx + 2, sy + 4, sx, sy + 2, sx + 1, sy + 3, &gc.heightDeviance[1]);
|
|
|
|
heightDevLine(sx + 2, sy + 2, sx + 4, sy + 4, sx + 3, sy + 3, &gc.heightDeviance[1]);
|
|
heightDevLine(sx + 2, sy + 4, sx + 4, sy + 2, sx + 3, sy + 3, &gc.heightDeviance[1]);
|
|
|
|
gc.heightDeviance[2] = gc.heightDeviance[1];
|
|
|
|
heightDevLine(sx, sy, sx + 4, sy, sx + 2, sy, &gc.heightDeviance[2]);
|
|
heightDevLine(sx, sy + 4, sx + 4, sy + 4, sx + 2, sy + 4, &gc.heightDeviance[2]);
|
|
heightDevLine(sx, sy, sx, sy + 4, sx, sy + 2, &gc.heightDeviance[2]);
|
|
heightDevLine(sx + 4, sy, sx + 4, sy + 4, sx + 4, sy + 2, &gc.heightDeviance[2]);
|
|
|
|
for(U32 j = 0; j < 4; j++)
|
|
{
|
|
for(U32 i = 0; i < 4; i++)
|
|
{
|
|
TerrainBlock::Material *mat = getMaterial(sx + i, sy + j);
|
|
if(mat->flags & TerrainBlock::Material::Empty)
|
|
gc.emptyFlags |= (1 << (j * 4 + i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainFile::buildGridMap()
|
|
{
|
|
S32 y;
|
|
for(y = 0; y < TerrainBlock::ChunkSquareWidth; y++)
|
|
for(U32 x = 0; x < TerrainBlock::ChunkSquareWidth; x++)
|
|
buildChunkDeviance(x, y);
|
|
|
|
GridSquare * gs = mGridMapBase;
|
|
S32 i;
|
|
for(i = TerrainBlock::BlockShift; i >= 0; i--)
|
|
{
|
|
mGridMap[i] = gs;
|
|
gs += 1 << (2 * (TerrainBlock::BlockShift - i));
|
|
}
|
|
for(i = TerrainBlock::BlockShift; i >= 0; i--)
|
|
{
|
|
S32 squareCount = 1 << (TerrainBlock::BlockShift - i);
|
|
S32 squareSize = (TerrainBlock::BlockSize) / squareCount;
|
|
|
|
for(S32 squareX = 0; squareX < squareCount; squareX++)
|
|
{
|
|
for(S32 squareY = 0; squareY < squareCount; squareY++)
|
|
{
|
|
U16 min = 0xFFFF;
|
|
U16 max = 0;
|
|
U16 mindev45 = 0;
|
|
U16 mindev135 = 0;
|
|
|
|
Point3F p1, p2, p3, p4;
|
|
|
|
// determine max error for both possible splits.
|
|
PlaneF pl1, pl2, pl3, pl4;
|
|
|
|
p1.set(0, 0, getHeight(squareX * squareSize, squareY * squareSize));
|
|
p2.set(0, squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize));
|
|
p3.set(squareSize, squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize));
|
|
p4.set(squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize));
|
|
|
|
// pl1, pl2 = split45, pl3, pl4 = split135
|
|
pl1.set(p1, p2, p3);
|
|
pl2.set(p1, p3, p4);
|
|
pl3.set(p1, p2, p4);
|
|
pl4.set(p2, p3, p4);
|
|
bool parentSplit45 = false;
|
|
GridSquare *parent = NULL;
|
|
if(i < TerrainBlock::BlockShift)
|
|
{
|
|
parent = findSquare(i+1, Point2I(squareX * squareSize, squareY * squareSize));
|
|
parentSplit45 = parent->flags & GridSquare::Split45;
|
|
}
|
|
bool empty = true;
|
|
bool hasEmpty = false;
|
|
|
|
for(S32 sizeX = 0; sizeX <= squareSize; sizeX++)
|
|
{
|
|
for(S32 sizeY = 0; sizeY <= squareSize; sizeY++)
|
|
{
|
|
S32 x = squareX * squareSize + sizeX;
|
|
S32 y = squareY * squareSize + sizeY;
|
|
|
|
if(sizeX != squareSize && sizeY != squareSize)
|
|
{
|
|
TerrainBlock::Material *mat = getMaterial(x, y);
|
|
if(!(mat->flags & TerrainBlock::Material::Empty))
|
|
empty = false;
|
|
else
|
|
hasEmpty = true;
|
|
}
|
|
U16 ht = getHeight(x, y);
|
|
|
|
if(ht < min)
|
|
min = ht;
|
|
if(ht > max)
|
|
max = ht;
|
|
Point3F pt(sizeX, sizeY, ht);
|
|
U16 dev;
|
|
|
|
if(sizeX < sizeY)
|
|
dev = calcDev(pl1, pt);
|
|
else if(sizeX > sizeY)
|
|
dev = calcDev(pl2, pt);
|
|
else
|
|
dev = Umax(calcDev(pl1, pt), calcDev(pl2, pt));
|
|
|
|
if(dev > mindev45)
|
|
mindev45 = dev;
|
|
|
|
if(sizeX + sizeY < squareSize)
|
|
dev = calcDev(pl3, pt);
|
|
else if(sizeX + sizeY > squareSize)
|
|
dev = calcDev(pl4, pt);
|
|
else
|
|
dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt));
|
|
|
|
if(dev > mindev135)
|
|
mindev135 = dev;
|
|
}
|
|
}
|
|
GridSquare *sq = findSquare(i, Point2I(squareX * squareSize, squareY * squareSize));
|
|
sq->minHeight = min;
|
|
sq->maxHeight = max;
|
|
|
|
sq->flags = empty ? GridSquare::Empty : 0;
|
|
if(hasEmpty)
|
|
sq->flags |= GridSquare::HasEmpty;
|
|
|
|
bool shouldSplit45 = ((squareX ^ squareY) & 1) == 0;
|
|
bool split45;
|
|
|
|
//split45 = shouldSplit45;
|
|
if(i == 0)
|
|
split45 = shouldSplit45;
|
|
else if(i < 4 && shouldSplit45 == parentSplit45)
|
|
split45 = shouldSplit45;
|
|
else
|
|
split45 = mindev45 < mindev135;
|
|
|
|
//split45 = shouldSplit45;
|
|
if(split45)
|
|
{
|
|
sq->flags |= GridSquare::Split45;
|
|
sq->heightDeviance = mindev45;
|
|
}
|
|
else
|
|
sq->heightDeviance = mindev135;
|
|
if(parent)
|
|
if(parent->heightDeviance < sq->heightDeviance)
|
|
parent->heightDeviance = sq->heightDeviance;
|
|
}
|
|
}
|
|
}
|
|
for (y = 0; y < TerrainBlock::BlockSize; y++)
|
|
{
|
|
for (S32 x=0; x < TerrainBlock::BlockSize; x++)
|
|
{
|
|
GridSquare *sq = findSquare(0, Point2I(x, y));
|
|
S32 xpl = (x + 1) & TerrainBlock::BlockMask;
|
|
S32 ypl = (y + 1) & TerrainBlock::BlockMask;
|
|
for(U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
|
|
{
|
|
if (mMaterialAlphaMap[i] != NULL) {
|
|
U32 mapVal = (mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + x] +
|
|
mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + x] +
|
|
mMaterialAlphaMap[i][(ypl << TerrainBlock::BlockShift) + xpl] +
|
|
mMaterialAlphaMap[i][(y << TerrainBlock::BlockShift) + xpl]);
|
|
if(mapVal)
|
|
sq->flags |= (GridSquare::MaterialStart << i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (y = 0; y < TerrainBlock::BlockSize; y += 2)
|
|
{
|
|
for (S32 x=0; x < TerrainBlock::BlockSize; x += 2)
|
|
{
|
|
GridSquare *sq = findSquare(1, Point2I(x, y));
|
|
GridSquare *s1 = findSquare(0, Point2I(x, y));
|
|
GridSquare *s2 = findSquare(0, Point2I(x+1, y));
|
|
GridSquare *s3 = findSquare(0, Point2I(x, y+1));
|
|
GridSquare *s4 = findSquare(0, Point2I(x+1, y+1));
|
|
sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1);
|
|
}
|
|
}
|
|
GridSquare *s = findSquare(1, Point2I(0, 0));
|
|
U16 *dflags = mFlagMap;
|
|
U16 *eflags = mFlagMap + TerrainBlock::FlagMapWidth * TerrainBlock::FlagMapWidth;
|
|
|
|
for(;dflags != eflags;s++,dflags++)
|
|
*dflags = s->flags;
|
|
|
|
}
|
|
|
|
//--------------------------------------
|
|
ResourceInstance *constructTerrainFile(Stream &stream)
|
|
{
|
|
U8 version;
|
|
stream.read(&version);
|
|
if (version > TerrainFile::FILE_VERSION)
|
|
return NULL;
|
|
|
|
if (version != TerrainFile::FILE_VERSION) {
|
|
Con::errorf(" ****************************************************");
|
|
Con::errorf(" ****************************************************");
|
|
Con::errorf(" ****************************************************");
|
|
Con::errorf(" PLEASE RESAVE THE TERRAIN FILE FOR THIS MISSION! THANKS!");
|
|
Con::errorf(" ****************************************************");
|
|
Con::errorf(" ****************************************************");
|
|
Con::errorf(" ****************************************************");
|
|
}
|
|
|
|
TerrainFile* ret = new TerrainFile;
|
|
// read the HeightField
|
|
for (S32 i=0; i < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); i++)
|
|
stream.read(&ret->mHeightMap[i]);
|
|
|
|
// read the material group map and flags...
|
|
dMemset(ret->mMaterialMap, 0, sizeof(ret->mMaterialMap));
|
|
TerrainBlock::Material * materialMap = (TerrainBlock::Material*)ret->mMaterialMap;
|
|
|
|
AssertFatal(!(TerrainBlock::Material::PersistMask & TerrainFile::MATERIAL_GROUP_MASK),
|
|
"Doh! We have flag clobberage...");
|
|
|
|
for (S32 j=0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++)
|
|
{
|
|
U8 val;
|
|
stream.read(&val);
|
|
|
|
//
|
|
ret->mBaseMaterialMap[j] = val & TerrainFile::MATERIAL_GROUP_MASK;
|
|
materialMap[j].flags = val & TerrainBlock::Material::PersistMask;
|
|
}
|
|
|
|
// read the MaterialList Info
|
|
S32 k, maxMaterials = TerrainBlock::MaterialGroups;
|
|
for(k=0; k < maxMaterials;)
|
|
{
|
|
ret->mMaterialFileName[k] = stream.readSTString(true);
|
|
if(ret->mMaterialFileName[k] && ret->mMaterialFileName[k][0])
|
|
k++;
|
|
else
|
|
maxMaterials--;
|
|
}
|
|
for(;k < TerrainBlock::MaterialGroups; k++)
|
|
ret->mMaterialFileName[k] = NULL;
|
|
|
|
|
|
if(version == 1)
|
|
{
|
|
for(S32 j = 0; j < (TerrainBlock::BlockSize * TerrainBlock::BlockSize); j++) {
|
|
if (ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]] == NULL) {
|
|
ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize];
|
|
dMemset(ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]], 0, TerrainBlock::BlockSize * TerrainBlock::BlockSize);
|
|
}
|
|
|
|
ret->mMaterialAlphaMap[ret->mBaseMaterialMap[j]][j] = 255;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(S32 k=0; k < TerrainBlock::MaterialGroups; k++) {
|
|
if(ret->mMaterialFileName[k] && ret->mMaterialFileName[k][0]) {
|
|
AssertFatal(ret->mMaterialAlphaMap[k] == NULL, "Bad assumption. There should be no alpha map at this point...");
|
|
ret->mMaterialAlphaMap[k] = new U8[TerrainBlock::BlockSize * TerrainBlock::BlockSize];
|
|
stream.read(TerrainBlock::BlockSize * TerrainBlock::BlockSize, ret->mMaterialAlphaMap[k]);
|
|
}
|
|
}
|
|
}
|
|
if(version >= 3)
|
|
{
|
|
U32 len;
|
|
stream.read(&len);
|
|
ret->mTextureScript = (char *) dMalloc(len + 1);
|
|
stream.read(len, ret->mTextureScript);
|
|
ret->mTextureScript[len] = 0;
|
|
|
|
stream.read(&len);
|
|
ret->mHeightfieldScript = (char *) dMalloc(len + 1);
|
|
stream.read(len, ret->mHeightfieldScript);
|
|
ret->mHeightfieldScript[len] = 0;
|
|
}
|
|
else
|
|
{
|
|
ret->mTextureScript = 0;
|
|
ret->mHeightfieldScript = 0;
|
|
}
|
|
|
|
ret->buildGridMap();
|
|
return ret;
|
|
}
|
|
|
|
void TerrainBlock::setBaseMaterial(U32 /*x*/, U32 /*y*/, U8 /*matGroup*/)
|
|
{
|
|
|
|
}
|
|
|
|
S32 TerrainBlock::getTerrainMapIndex(Point3F& pt)
|
|
{
|
|
U8 alphas[MaterialGroups];
|
|
int mapIndex;
|
|
|
|
const MatrixF & mat = getTransform();
|
|
Point3F origin;
|
|
mat.getColumn(3, &origin);
|
|
F32 squareSize = (F32) getSquareSize();
|
|
F32 halfSquareSize = squareSize / 2;
|
|
float x = (pt.x - origin.x + halfSquareSize) / squareSize;
|
|
float y = (pt.y - origin.y + halfSquareSize) / squareSize;
|
|
getMaterialAlpha(Point2I(x,y), alphas);
|
|
MaterialPropertyMap* matMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
|
|
if (matMap != NULL)
|
|
{
|
|
U8 maxAlpha = alphas[0];
|
|
U8 maxAlpha2;
|
|
int indexTexture = 0;
|
|
for (int i=1; i<MaterialGroups; i++)
|
|
{
|
|
maxAlpha2 = getMax(alphas[i],maxAlpha);
|
|
if (maxAlpha2 != maxAlpha)
|
|
{
|
|
indexTexture = i;
|
|
maxAlpha = maxAlpha2;
|
|
}
|
|
}
|
|
mapIndex = mMPMIndex[indexTexture];
|
|
}
|
|
return mapIndex;
|
|
}
|