/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// WorldHeightMap.cpp
// Class to encapsulate height map.
// Author: John Ahlquist, April 2001
#define INSTANTIATE_WELL_KNOWN_KEYS
#include "windows.h"
#include "stdlib.h"
#include
#include "Common/STLTypedefs.h"
#include "Common/DataChunk.h"
//#include "Common/GameFileSystem.h"
#include "Common/FileSystem.h" // for LOAD_TEST_ASSETS
#include "Common/GlobalData.h"
#include "Common/MapReaderWriterInfo.h"
#include "Common/TerrainTypes.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/WellKnownKeys.h"
#include "GameLogic/PolygonTrigger.h"
#include "GameLogic/SidesList.h"
#include "W3DDevice/GameClient/WorldHeightMap.h"
#include "W3DDevice/GameClient/TileData.h"
#include "W3DDevice/GameClient/HeightMap.h"
#include "W3DDevice/GameClient/TerrainTex.h"
#include "W3DDevice/GameClient/W3DShadow.h"
#include "Common/file.h"
#define K_OBSOLETE_HEIGHT_MAP_VERSION 8
#define PATHFIND_CLIFF_SLOPE_LIMIT_F 9.8f
// -----------------------------------------------------------
static AsciiString validateName(AsciiString n, Int flags)
{
return n;
}
/* ********* GDIFileStream class ****************************/
class GDIFileStream : public InputStream
{
protected:
File* m_file;
public:
GDIFileStream(File* pFile):m_file(pFile) {};
virtual Int read(void *pData, Int numBytes) {
return(m_file->read(pData, numBytes));
};
};
/* ********* MapObject class ****************************/
/*static*/ MapObject *MapObject::TheMapObjectListPtr = NULL;
/*static*/ Dict MapObject::TheWorldDict;
MapObject::MapObject(Coord3D loc, AsciiString name, Real angle, Int flags, const Dict* props,
const ThingTemplate *thingTemplate )
{
m_objectName = validateName( name, flags );
m_thingTemplate = thingTemplate;
m_nextMapObject = NULL;
m_location = loc;
m_angle = normalizeAngle(angle);
m_color = (0xff)<<8; // Bright green.
m_flags = flags;
m_renderObj = NULL;
m_shadowObj = NULL;
m_runtimeFlags = 0;
if (props)
{
m_properties = *props;
if (thingTemplate && !props->known(TheKey_objectSelectable, Dict::DICT_BOOL)) {
Bool selectable = thingTemplate->isKindOf(KINDOF_SELECTABLE);
m_properties.setBool(TheKey_objectSelectable, selectable);
}
}
else
{
m_properties.setInt(TheKey_objectInitialHealth, 100);
m_properties.setBool(TheKey_objectEnabled, true);
m_properties.setBool(TheKey_objectIndestructible, false);
m_properties.setBool(TheKey_objectUnsellable, false);
m_properties.setBool(TheKey_objectPowered, true);
m_properties.setBool(TheKey_objectRecruitableAI, true);
m_properties.setBool(TheKey_objectTargetable, false );
Bool selectable = false;
if (thingTemplate) {
selectable = thingTemplate->isKindOf(KINDOF_SELECTABLE);
}
m_properties.setBool(TheKey_objectSelectable, selectable);
}
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
setBridgeRenderObject( (BridgeTowerType)i, NULL );
}
MapObject::~MapObject(void)
{
setRenderObj(NULL);
setShadowObj(NULL);
if (m_nextMapObject) {
MapObject *cur = m_nextMapObject;
MapObject *next;
while (cur) {
next = cur->getNext();
cur->setNextMap(NULL); // prevents recursion.
cur->deleteInstance();
cur = next;
}
}
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
setBridgeRenderObject( (BridgeTowerType)i, NULL );
}
MapObject *MapObject::duplicate(void)
{
MapObject *pObj = newInstance( MapObject)(m_location, m_objectName, m_angle, m_flags, &m_properties, m_thingTemplate);
pObj->setColor(getColor());
pObj->m_runtimeFlags = m_runtimeFlags;
return pObj;
}
void MapObject::setRenderObj(RenderObjClass *pObj)
{
REF_PTR_SET(m_renderObj, pObj);
}
void MapObject::setBridgeRenderObject( BridgeTowerType type, RenderObjClass* renderObj )
{
if( type >= 0 && type < BRIDGE_MAX_TOWERS )
REF_PTR_SET( m_bridgeTowers[ type ], renderObj );
}
RenderObjClass* MapObject::getBridgeRenderObject( BridgeTowerType type )
{
if( type >= 0 && type < BRIDGE_MAX_TOWERS )
return m_bridgeTowers[ type ];
return NULL;
}
void MapObject::validate(void)
{
verifyValidTeam();
verifyValidUniqueID();
}
void MapObject::verifyValidTeam(void)
{
// if this map object has a valid team, then do nothing.
// if it has an invalid team, the place it on the default neutral team, (by clearing the
// existing team name.)
Bool exists;
AsciiString teamName = getProperties()->getAsciiString(TheKey_originalOwner, &exists);
if (exists) {
Bool valid = false;
int numSides = TheSidesList->getNumTeams();
for (int i = 0; i < numSides; ++i) {
TeamsInfo *teamInfo = TheSidesList->getTeamInfo(i);
if (!teamInfo) {
continue;
}
Bool itBetter;
AsciiString testAgainstTeamName = teamInfo->getDict()->getAsciiString(TheKey_teamName, &itBetter);
if (itBetter) {
if (testAgainstTeamName.compare(teamName) == 0) {
valid = true;
}
}
}
if (!valid) {
getProperties()->remove(TheKey_originalOwner);
}
}
}
void MapObject::verifyValidUniqueID(void)
{
Bool exists;
AsciiString uniqueID = getProperties()->getAsciiString(TheKey_uniqueID, &exists);
MapObject *obj = MapObject::getFirstMapObject();
// -1 is the sentinel
int highestIndex = -1;
while (obj) {
if (obj == this) {
// the first object is THIS OBJECT, cause we've already been added.
obj = obj->getNext();
continue;
}
if (obj->isWaypoint()) {
// waypoints throw this off. Sad but true. :-(
obj = obj->getNext();
continue;
}
Bool iterateExists;
AsciiString tempStr = obj->getProperties()->getAsciiString(TheKey_uniqueID, &iterateExists);
const char* lastSpace = tempStr.reverseFind(' ');
int testIndex = -1;
if (lastSpace) {
testIndex = atoi(lastSpace);
}
if (testIndex > highestIndex) {
highestIndex = testIndex;
}
break;
}
int indexOfThisObject = highestIndex + 1;
const char* thingName;
if (getThingTemplate()) {
thingName = getThingTemplate()->getName().str();
} else if (isWaypoint()) {
thingName = getWaypointName().str();
} else {
thingName = getName().str();
}
const char* pName = thingName;
while (*thingName) {
if ((*thingName) == '/') {
pName = thingName + 1;
}
++thingName;
}
AsciiString newID;
if (isWaypoint()) {
newID.format("%s", pName);
} else {
newID.format("%s %d", pName, indexOfThisObject);
}
getProperties()->setAsciiString(TheKey_uniqueID, newID);
}
void MapObject::fastAssignAllUniqueIDs(void)
{
// here's what we do. Take all of them, push them onto a stack. Then, pop each one, setting its id.
// should be much faster than what we currently do.
MapObject *pMapObj = getFirstMapObject();
std::stack objStack;
Int actualNumObjects = 0;
while (pMapObj) {
++actualNumObjects;
objStack.push(pMapObj);
pMapObj = pMapObj->getNext();
}
Int indexOfThisObject = 0;
while (actualNumObjects) {
MapObject *obj = objStack.top();
const char* thingName;
if (obj->getThingTemplate()) {
thingName = obj->getThingTemplate()->getName().str();
} else if (obj->isWaypoint()) {
thingName = obj->getWaypointName().str();
} else {
thingName = obj->getName().str();
}
const char* pName = thingName;
while (*thingName) {
if ((*thingName) == '/') {
pName = thingName + 1;
}
++thingName;
}
AsciiString newID;
if (obj->isWaypoint()) {
newID.format("%s", pName);
} else {
newID.format("%s %d", pName, indexOfThisObject);
}
obj->getProperties()->setAsciiString(TheKey_uniqueID, newID);
objStack.pop();
++indexOfThisObject;
--actualNumObjects;
}
}
void MapObject::setThingTemplate(const ThingTemplate *thing)
{
m_thingTemplate = thing;
m_objectName = thing->getName();
}
void MapObject::setName(AsciiString name)
{
m_objectName = name;
}
WaypointID MapObject::getWaypointID() { return (WaypointID)getProperties()->getInt(TheKey_waypointID); }
AsciiString MapObject::getWaypointName() { return getProperties()->getAsciiString(TheKey_waypointName); }
void MapObject::setWaypointID(Int i) { getProperties()->setInt(TheKey_waypointID, i); }
void MapObject::setWaypointName(AsciiString n) { getProperties()->setAsciiString(TheKey_waypointName, n); }
/*static */ Int MapObject::countMapObjectsWithOwner(const AsciiString& n)
{
Int count = 0;
for (MapObject *pMapObj = MapObject::getFirstMapObject(); pMapObj; pMapObj = pMapObj->getNext())
{
if (pMapObj->getProperties()->getAsciiString(TheKey_originalOwner) == n)
++count;
}
return count;
}
//-------------------------------------------------------------------------------------------------
const ThingTemplate *MapObject::getThingTemplate( void ) const
{
if (m_thingTemplate)
return (const ThingTemplate*) m_thingTemplate->getFinalOverride();
return NULL;
}
/* ********* WorldHeightMap class ****************************/
//
// WorldHeightMap destructor .
//
WorldHeightMap::~WorldHeightMap(void)
{
if (m_data) {
delete(m_data);
m_data = NULL;
}
if (m_tileNdxes) {
delete(m_tileNdxes);
m_tileNdxes = NULL;
}
if (m_blendTileNdxes) {
delete(m_blendTileNdxes);
m_blendTileNdxes = NULL;
}
if (m_extraBlendTileNdxes) {
delete(m_extraBlendTileNdxes);
m_extraBlendTileNdxes = NULL;
}
if (m_cliffInfoNdxes) {
delete(m_cliffInfoNdxes);
m_cliffInfoNdxes = NULL;
}
if (m_cellFlipState)
{ delete (m_cellFlipState);
m_cellFlipState = NULL;
}
if (m_cellCliffState)
{ delete (m_cellCliffState);
m_cellCliffState = NULL;
}
int i;
for (i=0; ideleteInstance();
MapObject::TheMapObjectListPtr = NULL;
}
MapObject::getWorldDict()->clear();
}
/**
WorldHeightMap - create a new height map for class WorldHeightMap.
Note that there is 1 m_numBlendedTiles, which is the implied
transparent tile for non-blended tiles.
*/
WorldHeightMap::WorldHeightMap():
m_width(0), m_height(0), m_dataSize(0), m_data(NULL), m_cellFlipState(NULL),
m_drawOriginX(0), m_drawOriginY(0),
m_numTextureClasses(0),
m_drawWidthX(NORMAL_DRAW_WIDTH), m_drawHeightY(NORMAL_DRAW_HEIGHT),
m_tileNdxes(NULL), m_blendTileNdxes(NULL), m_extraBlendTileNdxes(NULL), m_cliffInfoNdxes(NULL),
m_terrainTexHeight(1), m_alphaTexHeight(1), m_cellCliffState(NULL),
#ifdef EVAL_TILING_MODES
m_tileMode(TILE_4x4),
#endif
m_numCliffInfo(1),
m_terrainTex(NULL), m_alphaTerrainTex(NULL), m_numBitmapTiles(0), m_numBlendedTiles(1)
{
Int i;
for (i=0; ivalidateSides();
}
#ifdef EVAL_TILING_MODES
static Bool ParseFunkyTilingDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
WorldHeightMap *pThis = (WorldHeightMap *)userData;
*((Int *)&pThis->m_tileMode) = file.readInt();
return true;
}
#endif
/**
* WorldHeightMap - read a height map from a file.
* Format is Chunky.
*
* Input: ChunkInputStream,
*
*/
WorldHeightMap::WorldHeightMap(ChunkInputStream *pStrm, Bool logicalDataOnly):
m_width(0), m_height(0), m_dataSize(0), m_data(NULL), m_cellFlipState(NULL),
m_drawOriginX(0), m_cellCliffState(NULL), m_drawOriginY(0),
m_numTextureClasses(0),
m_drawWidthX(NORMAL_DRAW_WIDTH), m_drawHeightY(NORMAL_DRAW_HEIGHT),
m_tileNdxes(NULL), m_blendTileNdxes(NULL), m_extraBlendTileNdxes(NULL), m_cliffInfoNdxes(NULL),
m_terrainTexHeight(1), m_alphaTexHeight(1),
#ifdef EVAL_TILING_MODES
m_tileMode(TILE_4x4),
#endif
m_numCliffInfo(1),
m_terrainTex(NULL), m_alphaTerrainTex(NULL), m_numBitmapTiles(0), m_numBlendedTiles(1)
{
int i;
for (i=0; im_stretchTerrain) {
m_drawWidthX=STRETCH_DRAW_WIDTH;
m_drawHeightY=STRETCH_DRAW_HEIGHT;
}
DataChunkInput file( pStrm );
if (logicalDataOnly) {
file.registerParser( AsciiString("HeightMapData"), AsciiString::TheEmptyString, ParseSizeOnlyInChunk );
file.registerParser( AsciiString("WorldInfo"), AsciiString::TheEmptyString, ParseWorldDictDataChunk );
file.registerParser( AsciiString("ObjectsList"), AsciiString::TheEmptyString, ParseObjectsDataChunk );
freeListOfMapObjects(); // just in case.
file.registerParser( AsciiString("PolygonTriggers"), AsciiString::TheEmptyString, PolygonTrigger::ParsePolygonTriggersDataChunk );
PolygonTrigger::deleteTriggers(); // just in case.
TheSidesList->emptySides();
file.registerParser(AsciiString("SidesList"), AsciiString::TheEmptyString, SidesList::ParseSidesDataChunk );
} else {
file.registerParser( AsciiString("HeightMapData"), AsciiString::TheEmptyString, ParseHeightMapDataChunk );
file.registerParser( AsciiString("BlendTileData"), AsciiString::TheEmptyString, ParseBlendTileDataChunk );
#ifdef EVAL_TILING_MODES
file.registerParser( AsciiString("FUNKY_TILING"), AsciiString::TheEmptyString, ParseFunkyTilingDataChunk );
#endif
file.registerParser( AsciiString("GlobalLighting"), AsciiString::TheEmptyString, ParseLightingDataChunk );
}
if (!file.parse(this)) {
throw(ERROR_CORRUPT_FILE_FORMAT);
}
// patch bad maps.
if (!logicalDataOnly) {
for(i=0; i= m_numCliffInfo) {
m_cliffInfoNdxes[i] = 0;
}
if (m_blendTileNdxes[i]<0 || m_blendTileNdxes[i]>= m_numBlendedTiles) {
m_blendTileNdxes[i] = 0;
}
if (m_extraBlendTileNdxes[i]<0 || m_extraBlendTileNdxes[i]>= m_numBlendedTiles) {
m_extraBlendTileNdxes[i] = 0;
}
}
}
if (TheGlobalData && TheGlobalData->m_drawEntireTerrain) {
m_drawWidthX=m_width;
m_drawHeightY=m_height;
}
if (m_drawWidthX > m_width) {
m_drawWidthX = m_width;
}
if (m_drawHeightY > m_height) {
m_drawHeightY = m_height;
}
TheSidesList->validateSides();
}
/** Optimized version of method to get triangle flip state of a terrain cell. Use this
* instead of getAlphaUVData() whenever possible.
*/
Bool WorldHeightMap::getFlipState(Int xIndex, Int yIndex) const
{
if (xIndex<0 || yIndex<0) return false;
if (yIndex>=m_height) return false;
if (xIndex>=m_width) return false;
if (!m_cellFlipState) return false;
return m_cellFlipState[yIndex*m_flipStateWidth + (xIndex >> 3)] & (1<<(xIndex&0x7));
}
/** Get whether the cell is a cliff cell (impassable to ground vehicles).
*/
Bool WorldHeightMap::getCliffState(Int xIndex, Int yIndex) const
{
if (xIndex<0 || yIndex<0) return false;
if (yIndex>=m_height) return false;
if (xIndex>=m_width) return false;
if (!m_cellCliffState) return false;
return m_cellCliffState[yIndex*m_flipStateWidth + (xIndex >> 3)] & (1<<(xIndex&0x7));
}
//=============================================================================
// setCliffState
//=============================================================================
/** Sets the cliff state for a given cell. */
//=============================================================================
void WorldHeightMap::setCliffState(Int xIndex, Int yIndex, Bool state)
{
if (xIndex<0 || yIndex<0) return;
if (yIndex>=m_height) return;
if (xIndex>=m_width) return;
if (!m_cellCliffState) return;
UnsignedByte flagByte = m_cellCliffState[yIndex*m_flipStateWidth + (xIndex >> 3)];
UnsignedByte flagMask = (1<<(xIndex&0x7));
if (state) {
flagByte |= flagMask;
} else {
flagByte &= (~flagMask);
}
m_cellCliffState[yIndex*m_flipStateWidth + (xIndex >> 3)] = flagByte;
}
Bool WorldHeightMap::ParseWorldDictDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
Dict d = file.readDict();
*MapObject::getWorldDict() = d;
Bool exists;
Int theWeather = MapObject::getWorldDict()->getInt(TheKey_weather, &exists);
if (exists) {
TheWritableGlobalData->m_weather = (Weather) theWeather;
}
return true;
}
/**
* WorldHeightMap::ParseLightingDataChunk - read a global lights chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseLightingDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
TheWritableGlobalData->m_timeOfDay = (TimeOfDay)file.readInt();
Int i;
GlobalData::TerrainLighting initLightValues = { { 0,0,0},{0,0,0},{0,0,-1.0f}};
// initialize the directions of the lights to not be totally invalid, in case old maps are read
for (i=0; i<4; i++) {
for (Int j=0;jm_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j]=initLightValues;
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j]=initLightValues;
}
}
for (i=0; i<4; i++) {
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].ambient.red = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].ambient.green = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].ambient.blue = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].diffuse.red = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].diffuse.green = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].diffuse.blue = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].lightPos.x = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].lightPos.y = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][0].lightPos.z = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].ambient.red = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].ambient.green = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].ambient.blue = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].diffuse.red = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].diffuse.green = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].diffuse.blue = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].lightPos.x = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].lightPos.y = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][0].lightPos.z = file.readReal();
if (info->version >= K_LIGHTING_VERSION_2) {
for (Int j=1; j<3; j++) //added support for 2 extra object lights
{
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].ambient.red = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].ambient.green = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].ambient.blue = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].diffuse.red = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].diffuse.green = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].diffuse.blue = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].lightPos.x = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].lightPos.y = file.readReal();
TheWritableGlobalData->m_terrainObjectsLighting[i+TIME_OF_DAY_FIRST][j].lightPos.z = file.readReal();
}
}
if (info->version >= K_LIGHTING_VERSION_3) {
for (Int j=1; j<3; j++) //added support for 2 extra terrain lights
{
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].ambient.red = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].ambient.green = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].ambient.blue = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].diffuse.red = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].diffuse.green = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].diffuse.blue = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].lightPos.x = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].lightPos.y = file.readReal();
TheWritableGlobalData->m_terrainLighting[i+TIME_OF_DAY_FIRST][j].lightPos.z = file.readReal();
}
}
}
if (!file.atEndOfChunk()) {
UnsignedInt shadowColor = file.readInt();
if (TheW3DShadowManager) {
TheW3DShadowManager->setShadowColor(shadowColor);
}
}
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over."));
return true;
}
/**
* WorldHeightMap::ParseObjectsDataChunk - read a height map chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseObjectsDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
file.m_currentObject = NULL;
file.registerParser( AsciiString("Object"), info->label, ParseObjectDataChunk );
return (file.parse(userData));
}
/**
* WorldHeightMap::ParseHeightMapData - read a height map chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseHeightMapDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
WorldHeightMap *pThis = (WorldHeightMap *)userData;
return pThis->ParseHeightMapData(file, info, userData);
}
/**
* WorldHeightMap::ParseHeightMapData - read a height map chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseHeightMapData(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
m_width = file.readInt();
m_height = file.readInt();
if (info->version >= K_HEIGHT_MAP_VERSION_3) {
m_borderSize = file.readInt();
} else {
m_borderSize = 0;
}
if (info->version >= K_HEIGHT_MAP_VERSION_4) {
Int numBorders = file.readInt();
m_boundaries.resize(numBorders);
for (int i = 0; i < numBorders; ++i) {
m_boundaries[i].x = file.readInt();
m_boundaries[i].y = file.readInt();
}
} else {
m_boundaries.resize(1);
m_boundaries[0].x = m_width - 2 * m_borderSize;
m_boundaries[0].y = m_height - 2 * m_borderSize;
}
m_dataSize = file.readInt();
m_data = MSGNEW("WorldHeightMap_ParseHeightMapData") UnsignedByte[m_dataSize];
if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) {
throw ERROR_CORRUPT_FILE_FORMAT ;
}
file.readArrayOfBytes((char *)m_data, m_dataSize);
// Resize me.
if (info->version == K_HEIGHT_MAP_VERSION_1) {
Int newWidth = (m_width+1)/2;
Int newHeight = (m_height+1)/2;
Int i, j;
for (i=0; iParseSizeOnly(file, info, userData);
}
/**
* WorldHeightMap::ParseHeightMapData - read a height map chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseSizeOnly(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
m_width = file.readInt();
m_height = file.readInt();
if (info->version >= K_HEIGHT_MAP_VERSION_3) {
m_borderSize = file.readInt();
} else {
m_borderSize = 0;
}
if (info->version >= K_HEIGHT_MAP_VERSION_4) {
Int numBorders = file.readInt();
m_boundaries.resize(numBorders);
for (int i = 0; i < numBorders; ++i) {
m_boundaries[i].x = file.readInt();
m_boundaries[i].y = file.readInt();
}
} else {
m_boundaries.resize(1);
m_boundaries[0].x = m_width - 2 * m_borderSize;
m_boundaries[0].y = m_height - 2 * m_borderSize;
}
m_dataSize = file.readInt();
m_data = MSGNEW("WorldHeightMap_ParseSizeOnly") UnsignedByte[m_dataSize];
if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) {
throw ERROR_CORRUPT_FILE_FORMAT ;
}
file.readArrayOfBytes((char *)m_data, m_dataSize);
// Resize me.
if (info->version == K_HEIGHT_MAP_VERSION_1) {
Int newWidth = (m_width+1)/2;
Int newHeight = (m_height+1)/2;
Int i, j;
for (i=0; iParseBlendTileData(file, info, userData);
}
/** Function to read in the tiles for a texture class. */
void WorldHeightMap::readTexClass(TXTextureClass *texClass, TileData **tileData)
{
char path[_MAX_PATH];
path[0] = 0;
File *theFile = NULL;
// get the file from the description in TheTerrainTypes
TerrainType *terrain = TheTerrainTypes->findTerrain( texClass->name );
char texturePath[ _MAX_PATH ];
if (terrain==NULL)
{
#ifdef LOAD_TEST_ASSETS
theFile = TheFileSystem->openFile( texClass->name.str(), File::READ|File::BINARY);
#endif
}
else
{
sprintf( texturePath, "%s%s", TERRAIN_TGA_DIR_PATH, terrain->getTexture().str() );
theFile = TheFileSystem->openFile( texturePath, File::READ|File::BINARY);
}
if (theFile != NULL) {
GDIFileStream theStream(theFile);
InputStream *pStr = &theStream;
Int numTiles = WorldHeightMap::countTiles(pStr);
theFile->seek(0, File::START);
if (numTiles >= texClass->numTiles) {
numTiles = texClass->numTiles;
Int width;
for (width = 10; width >= 1; width--) {
if (numTiles >= width*width) {
numTiles = width*width;
break;
}
}
WorldHeightMap::readTiles(pStr, tileData+texClass->firstTile, width);
}
theFile->close();
}
}
/**
* WorldHeightMap::ParseBlendTileData - read a blend tile info chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseBlendTileData(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
Int len = file.readInt();
if (m_dataSize != len) {
throw ERROR_CORRUPT_FILE_FORMAT ;
}
m_tileNdxes = MSGNEW("WorldHeightMap_ParseBlendTileData") Short[m_dataSize];
m_cliffInfoNdxes = MSGNEW("WorldHeightMap_ParseBlendTileData") Short[m_dataSize];
m_blendTileNdxes = MSGNEW("WorldHeightMap_ParseBlendTileData") Short[m_dataSize];
m_extraBlendTileNdxes = MSGNEW("WorldHeightMap_ParseBlendTileData") Short[m_dataSize];
// Note - we have one less cell than the width & height. But for paranoia, allocate
// extra row. jba.
//
Int numBytesX = (m_width+1)/8; //how many bytes to fit all bitflags
Int numBytesY = m_height;
m_flipStateWidth=numBytesX;
m_cellFlipState = MSGNEW("WorldHeightMap_getTerrainTexture") UnsignedByte[numBytesX*numBytesY];
m_cellCliffState = MSGNEW("WorldHeightMap_getTerrainTexture") UnsignedByte[numBytesX*numBytesY];
memset(m_cellFlipState,0,numBytesX*numBytesY); //clear all flags
memset(m_cellCliffState,0,numBytesX*numBytesY); //clear all flags
file.readArrayOfBytes((char*)m_tileNdxes, m_dataSize*sizeof(Short));
file.readArrayOfBytes((char*)m_blendTileNdxes, m_dataSize*sizeof(Short));
if (info->version >= K_BLEND_TILE_VERSION_6) {
file.readArrayOfBytes((char*)m_extraBlendTileNdxes, m_dataSize*sizeof(Short));
//Allow clearing of extra blend tiles via ini and resaving of map.
//Useful for flushing out initial maps made with buggy 3-way blending.
if (!TheGlobalData->m_use3WayTerrainBlends)
memset(m_extraBlendTileNdxes,0,m_dataSize*sizeof(Short));
}
if (info->version >= K_BLEND_TILE_VERSION_5) {
file.readArrayOfBytes((char*)m_cliffInfoNdxes, m_dataSize*sizeof(Short));
}
if (info->version >= K_BLEND_TILE_VERSION_7) {
file.readArrayOfBytes((char*)m_cellCliffState, m_height*m_flipStateWidth);
} else {
initCliffFlagsFromHeights();
}
m_numBitmapTiles = file.readInt();
DEBUG_ASSERTCRASH(m_numBitmapTiles>0 && m_numBitmapTiles<2048, ("Unlikely numBitmapTiles."));
m_numBlendedTiles = file.readInt();
DEBUG_ASSERTCRASH(m_numBlendedTiles>0 && m_numBlendedTilesversion >= K_BLEND_TILE_VERSION_5) {
m_numCliffInfo = file.readInt();
} else {
m_numCliffInfo = 1; // cliffInfo[0] is the default info.
}
// --> file loading here
int i;
m_numTextureClasses = file.readInt();
DEBUG_ASSERTCRASH(m_numTextureClasses>0 && m_numTextureClasses<200, ("Unlikely m_numTextureClasses."));
for (i=0; iversion >= K_BLEND_TILE_VERSION_4) {
m_numEdgeTiles = file.readInt();
m_numEdgeTextureClasses = file.readInt();
for (i=0; im_use3WayTerrainBlends)
m_blendedTiles[i].inverted &= ~FLIPPED_MASK; //filter out extra flips from 3-way
if (info->version >= K_BLEND_TILE_VERSION_3) {
m_blendedTiles[i].longDiagonal = file.readByte();
} else {
m_blendedTiles[i].longDiagonal = false;
}
if (info->version >= K_BLEND_TILE_VERSION_4) {
m_blendedTiles[i].customBlendEdgeClass = file.readInt();
} else {
m_blendedTiles[i].customBlendEdgeClass = -1;
}
flag = file.readInt();
DEBUG_ASSERTCRASH(flag==FLAG_VAL, ("Invalid format."));
if (flag != FLAG_VAL) {
throw ERROR_CORRUPT_FILE_FORMAT;
}
}
if (info->version >= K_BLEND_TILE_VERSION_5) {
for (i=1; iversion == K_BLEND_TILE_VERSION_1) {
Int newWidth = (m_width+1)/2;
Int newHeight = (m_height+1)/2;
Int i, j;
for (i=0; iParseObjectData(file, info, userData, info->version >= K_OBJECTS_VERSION_2);
}
/**
* WorldHeightMap::ParseObjectData - read a object info chunk.
* Format is the newer CHUNKY format.
* See WHeightMapEdit.cpp for the writer.
* Input: DataChunkInput
*
*/
Bool WorldHeightMap::ParseObjectData(DataChunkInput &file, DataChunkInfo *info, void *userData, Bool readDict)
{
MapObject *pPrevious = (MapObject *)file.m_currentObject;
Coord3D loc;
loc.x = file.readReal();
loc.y = file.readReal();
loc.z = file.readReal();
Real minZ = -100*MAP_XY_FACTOR;
Real maxZ = (255*10)*MAP_HEIGHT_SCALE;
if (info->version <= K_OBJECTS_VERSION_2) {
loc.z = 0;
}
Real angle = file.readReal();
Int flags = file.readInt();
AsciiString name = file.readAsciiString();
Dict d;
if (readDict)
{
d = file.readDict();
}
if (loc.zmaxZ) {
DEBUG_LOG(("Removing object at z height %f\n", loc.z));
return true;
}
MapObject *pThisOne;
// create the map object
pThisOne = newInstance( MapObject )( loc, name, angle, flags, &d,
TheThingFactory->findTemplate( name ) );
//DEBUG_LOG(("obj %s owner %s\n",name.str(),d.getAsciiString(TheKey_originalOwner).str()));
if (pThisOne->getProperties()->getType(TheKey_waypointID) == Dict::DICT_INT)
pThisOne->setIsWaypoint();
if (pThisOne->getProperties()->getType(TheKey_lightHeightAboveTerrain) == Dict::DICT_REAL)
pThisOne->setIsLight();
if (pThisOne->getProperties()->getType(TheKey_scorchType) == Dict::DICT_INT)
pThisOne->setIsScorch();
if (pPrevious) {
DEBUG_ASSERTCRASH(MapObject::TheMapObjectListPtr != NULL && pPrevious->getNext() == NULL, ("Bad linkage."));
pPrevious->setNextMap(pThisOne);
} else {
DEBUG_ASSERTCRASH(MapObject::TheMapObjectListPtr == NULL, ("Bad linkage."));
MapObject::TheMapObjectListPtr = pThisOne;
}
file.m_currentObject = pThisOne;
return true;
}
// Targa format: Header
typedef struct {
UnsignedByte idLength;
UnsignedByte colorMapType; // 0 = rgb, 1 = indexed.
UnsignedByte imageType; //0x1 = indexed, 0x2 = rgb, 0x8 = rle.
UnsignedByte colorMapInfo[5]; // we ignore, only do rgb.
Short xOrigin;
Short yOrigin;
Short imageWidth;
Short imageHeight;
UnsignedByte pixelDepth;
UnsignedByte flags; // &0x0F = alpha channel bits, &0x10 is right to left flag,
// 0x20 is top to bottom flag. (0x0? is left to right, bottom to top)
// 0x3? is top to bottom, right to left.
} TTargaHeader;
// followed by idLength bytes of ascii data
// followed by pixel data
// followed by optional data.
/// Count how many tiles come in from a targa file.
Int WorldHeightMap::countTiles(InputStream *pStr)
{
TTargaHeader hdr;
Int len = pStr->read(&hdr,sizeof(hdr));
if (len!=sizeof(hdr)) return(0);
Int tileWidth = hdr.imageWidth/TILE_PIXEL_EXTENT;
Int tileHeight = hdr.imageHeight/TILE_PIXEL_EXTENT;
if (hdr.colorMapType != 0) {
return(0); // we don't do indexed at this time. jba.
}
if (hdr.imageType != 0x2 && hdr.imageType != 0xA) {
return(0); // we don't do indexed at this time. jba.
}
if (hdr.pixelDepth < 24) return(false);
if (hdr.pixelDepth > 32) return(false);
// 3x3 gives 9,
// 2x2 gives 4,
// 1x1 gives 1,
// else 0;
if (tileWidth>10 || tileHeight>10) return(0); // don't do huge images, or bad files.
if (tileWidth>=10 && tileHeight >=10) return(100);
if (tileWidth>=9 && tileHeight >=9) return(81);
if (tileWidth>=8 && tileHeight >=8) return(64);
if (tileWidth>=7 && tileHeight >=7) return(49);
if (tileWidth>=6 && tileHeight >=6) return(36);
if (tileWidth>=5 && tileHeight >=5) return(25);
if (tileWidth>=4 && tileHeight >=4) return(16);
if (tileWidth>=3 && tileHeight >=3) return(9);
if (tileWidth>=2 && tileHeight >=2) return(4);
if (tileWidth>=1 && tileHeight >=1) return(1);
return(0);
}
/*Break down a .tga file into a collection of tiles. numRows * numRows total tiles.*/
Bool WorldHeightMap::readTiles(InputStream *pStr, TileData **tiles, Int numRows)
{
TTargaHeader hdr;
pStr->read(&hdr, sizeof(hdr));
Int tileWidth = hdr.imageWidth/TILE_PIXEL_EXTENT;
Int tileHeight = hdr.imageHeight/TILE_PIXEL_EXTENT;
if (tileWidth 4) return(false);
int i;
for (i=0; iread(&flag, 1);
repeatCount = flag&0x7f;
repeatCount++;
if (flag&0x80) {
running = true;
pStr->read(buf, bytesPerPixel);
} else {
running = false;
}
}
if (compressed) repeatCount--;
if (!running) {
pStr->read(buf, bytesPerPixel);
}
if (column >= (numRows*TILE_PIXEL_EXTENT)) continue;
r = buf[2]; g = buf[1]; b = buf[0];
int tileNdx = (column/TILE_PIXEL_EXTENT) + numRows*(row/TILE_PIXEL_EXTENT);
int pixelNdx = (column%TILE_PIXEL_EXTENT) + TILE_PIXEL_EXTENT*(row%TILE_PIXEL_EXTENT);
UnsignedByte *pixel = tiles[tileNdx]->getDataPtr();
pixel += pixelNdx*TILE_BYTES_PER_PIXEL;
*pixel++ = b;
*pixel++ = g;
*pixel++ = r;
*pixel = 0xFF; // solid alpha.
}
DEBUG_ASSERTCRASH(repeatCount==0, ("Invalid tga."));
}
for (i=0; iupdateMips();
}
return(true);
}
/** updateTileTexturePositions - assigns each tile a location in the texture.
*/
Int WorldHeightMap::updateTileTexturePositions(Int *edgeHeight)
{
Int i, j;
Int maxHeight = 0;
const Int tilesPerRow = TEXTURE_WIDTH/(TILE_PIXEL_EXTENT+TILE_OFFSET);
Bool availableGrid[tilesPerRow][tilesPerRow];
Int row, column;
for (row=0; rowm_tileLocationInTexture.x = 0;
m_sourceTiles[i]->m_tileLocationInTexture.y = 0;
}
}
/* put the normal tiles into the terrain texture */
Int texClass;
Int tileWidth;
for (tileWidth = tilesPerRow; tileWidth>0; tileWidth--) {
for (texClass=0; texClassm_tileLocationInTexture.x = x;
m_sourceTiles[baseNdx]->m_tileLocationInTexture.y = y;
}
}
}
}
for (i=0; im_tileLocationInTexture.x = 0;
m_edgeTiles[i]->m_tileLocationInTexture.y = 0;
}
}
/* put the blend edge tiles into the blend edges texture */
Int maxEdgeHeight = 0;
// Reset the grid, cause we're using a different texture now.
for (row=0; rowm_tileLocationInTexture.x = x;
m_edgeTiles[baseNdx]->m_tileLocationInTexture.y = y;
}
}
}
if (edgeHeight) *edgeHeight = maxEdgeHeight;
return maxHeight;
}
/** getUVData - Gets the texture coordinates to use. See getTerrainTexture.
*/
void WorldHeightMap::getUVForNdx(Int tileNdx, float *minU, float *minV, float *maxU, float*maxV, Bool fullTile)
{
Short baseNdx = tileNdx>>2;
if (m_sourceTiles[baseNdx] == NULL) {
// Missing texture.
*minU = *minV = *maxU = *maxV = 0.0f;
return;
}
ICoord2D pos = m_sourceTiles[baseNdx]->m_tileLocationInTexture;
*minU = pos.x;
*minV = pos.y;
*maxU = *minU+TILE_PIXEL_EXTENT;
*maxV = *minV+TILE_PIXEL_EXTENT;
#ifdef EVAL_TILING_MODES
if (m_tileMode == TILE_8x8) {
*maxU = *minU+TILE_PIXEL_EXTENT/2.0f;
*maxV = *minV+TILE_PIXEL_EXTENT/2.0f;
} else if (m_tileMode == TILE_6x6) {
*maxU = *minU+2.0f*TILE_PIXEL_EXTENT/3.0f;
*maxV = *minV+2.0f*TILE_PIXEL_EXTENT/3.0f;
} else {
*maxU = *minU+TILE_PIXEL_EXTENT;
*maxV = *minV+TILE_PIXEL_EXTENT;
}
#endif
*minU/=TEXTURE_WIDTH;
*minV/=m_terrainTexHeight;
*maxU/=TEXTURE_WIDTH;
*maxV/=m_terrainTexHeight;
if (!fullTile) {
// Tiles are 64x64 pixels, height grids map to 32x32.
// So get the proper quadrant of the tile.
Real midX = (*minU+*maxU)/2;
Real midY = (*minV+*maxV)/2;
if (tileNdx&2) { // y's are flipped.
*maxV = midY;
} else {
*minV = midY;
}
if (tileNdx&1) {
*minU = midX;
} else {
*maxU = midX;
}
}
}
/** getUVData - Gets the texture coordinates to use. See getTerrainTexture.
*/
void WorldHeightMap::getUVForBlend(Int edgeClass, Region2D *range)
{
ICoord2D pos = m_edgeTextureClasses[edgeClass].positionInTexture;
Int width = m_edgeTextureClasses[edgeClass].width;
range->lo.x = (Real)pos.x/TEXTURE_WIDTH;
range->lo.y = (Real)pos.y/m_alphaEdgeHeight;
range->hi.x = ((Real)pos.x + width*TILE_PIXEL_EXTENT)/TEXTURE_WIDTH;
range->hi.y = ((Real)pos.y + width*TILE_PIXEL_EXTENT)/m_alphaEdgeHeight;
}
/// Get whether something is cliff indexed with the offset that HeightMapRenderObjClass uses built in.
Bool WorldHeightMap::isCliffMappedTexture(Int x, Int y) {
Int ndx = x+m_drawOriginX+m_width*(y+m_drawOriginY);
if (ndx>=0 && ndxm_adjustCliffTextures) {
return false;
}
if (nU==0.0) {
return false; // missing texture.
}
if (fullTile) {
return false;
}
if (m_cliffInfoNdxes[ndx]) {
TCliffInfo info = m_cliffInfo[m_cliffInfoNdxes[ndx]];
Bool tilesMatch = false;
Int ndx1 = tileNdx>>2;
Int ndx2 = info.tileIndex>>2;
Int i;
for (i=0; im_numTextureClasses; i++) {
if (ndx1 >= m_textureClasses[i].firstTile && ndx1 < m_textureClasses[i].firstTile + m_textureClasses[i].numTiles) {
tilesMatch = ndx2 >= m_textureClasses[i].firstTile && ndx2 < m_textureClasses[i].firstTile + m_textureClasses[i].numTiles;
//tilesMatch = true;
break;
}
}
if (tilesMatch) {
Real minU = m_textureClasses[i].positionInTexture.x;
Real maxV = m_textureClasses[i].positionInTexture.y + m_textureClasses[i].width*TILE_PIXEL_EXTENT;
minU/=TEXTURE_WIDTH;
maxV/=m_terrainTexHeight;
Real vFactor = TEXTURE_WIDTH/m_terrainTexHeight;
U[0] = info.u0+minU;
U[1] = info.u1+minU;
U[2] = info.u2+minU;
U[3] = info.u3+minU;
V[0] = info.v0*vFactor+maxV;
V[1] = info.v1*vFactor+maxV;
V[2] = info.v2*vFactor+maxV;
V[3] = info.v3*vFactor+maxV;
return info.flip;
}
}
#define DO_OLD_UV
#ifdef DO_OLD_UV
// old uv adjustment for cliffs
static Real STRETCH_LIMIT = 1.5f; // If it is stretching less than this, don't adjust.
static Real TILE_LIMIT = 4.0; // Our tiles are currently 4 cells wide & tall, so dont'
// adjust to more than 4.0.
static Real TALL_STRETCH_LIMIT = 2.0f;
static Real DIAMOND_STRETCH_LIMIT = 2.4f;
static Real HEIGHT_SCALE = MAP_HEIGHT_SCALE / MAP_XY_FACTOR;
Real nU, nV, xU, xV;
nU=nV=xU=xV = 0.0f;
Int tilesPerRow = TEXTURE_WIDTH/(2*TILE_PIXEL_EXTENT+TILE_OFFSET);
tilesPerRow *= 4;
getUVForNdx(tileNdx, &nU, &nV, &xU, &xV, fullTile);
U[0] = nU; U[1] = xU; U[2] = xU; U[3] = nU;
V[0] = xV; V[1] = xV; V[2] = nV; V[3] = nV;
if (TheGlobalData && !TheGlobalData->m_adjustCliffTextures) {
return false;
}
if (nU==0.0) {
return false; // missing texture.
}
if (fullTile) {
return false;
}
// check for excessive heights.
if (ndx < this->m_dataSize - m_width - 1) {
Int h0 = m_data[ndx];
Int h1 = m_data[ndx+1];
Int h2 = m_data[ndx+m_width+1];
Int h3 = m_data[ndx+m_width];
Int minH, maxH;
minH = maxH = h0;
if (minH>h1) minH = h1;
if (maxHh2) minH = h2;
if (maxHh3) minH = h3;
if (maxHaboveLimit) above++;
if (h1>aboveLimit) above++;
if (h2>aboveLimit) above++;
if (h3>aboveLimit) above++;
if (deltaH*HEIGHT_SCALE < STRETCH_LIMIT) {
return false;
}
Short baseNdx = tileNdx>>2;
Short texClass;
for (texClass=0; texClass= m_textureClasses[texClass].firstTile &&
baseNdx < m_textureClasses[texClass].firstTile+m_textureClasses[texClass].numTiles) {
break;
}
}
if (texClass>= m_numTextureClasses) return false;
Real nUb, nVb, xUb, xVb;
nUb = m_textureClasses[texClass].positionInTexture.x;
nVb = m_textureClasses[texClass].positionInTexture.y;
xUb = nUb+m_textureClasses[texClass].width*TILE_PIXEL_EXTENT;
xVb = nVb+m_textureClasses[texClass].width*TILE_PIXEL_EXTENT;
nUb/=TEXTURE_WIDTH;
nVb/=m_terrainTexHeight;
xUb/=TEXTURE_WIDTH;
xVb/=m_terrainTexHeight;
// Now covers texture bounds.
// too much stretch.
Real divisor = TILE_LIMIT/(deltaH*HEIGHT_SCALE);
if (divisor > TILE_LIMIT) divisor = TILE_LIMIT;
if (divisor < 1.0f) divisor = 1.0f;
Real deltaV = (xVb-nVb);
// Real deltaU = (xUb-nUb);
if (above != 1 && below != 1 && (above!=2 || below != 2)) {
// diamond shaped. Use default if it is not too stretched, as
// the fix is not that appealing either.
if (deltaH*HEIGHT_SCALE < DIAMOND_STRETCH_LIMIT) {
return false;
}
}
if (below==1 || above>below) { //(avgH > minH + (2*deltaH+2)/3)
// we got one low guy.
if (h0==minH) {
V[0] = nV+deltaV/divisor;
} else if (h1 == minH) {
V[1] = nV+deltaV/divisor;
} else if (h2 == minH) {
V[2] = xV-deltaV/divisor;
} else if (h3 == minH) {
V[3] = xV-deltaV/divisor;
}
#if 0
nU = nV = xU = xV = 1.0f;
U[0] = nU; U[1] = xU; U[2] = xU; U[3] = nU;
V[0] = xV; V[1] = xV; V[2] = nV; V[3] = nV;
return false;
#endif
} else if (above==1 || below>above) { //(avgH < minH + (deltaH+1)/3)
// we got one high guy
if (h0==maxH) {
V[0] = nV+deltaV/divisor;
} else if (h1 == maxH) {
V[1] = nV+deltaV/divisor;
} else if (h2 == maxH) {
V[2] = xV-deltaV/divisor;
} else if (h3 == maxH) {
V[3] = xV-deltaV/divisor;
}
#if 0
nU = nV = xU = xV = 0.0f;
U[0] = nU; U[1] = xU; U[2] = xU; U[3] = nU;
V[0] = xV; V[1] = xV; V[2] = nV; V[3] = nV;
return false;
#endif
} else {
// we got two up and two down.
if (deltaH*HEIGHT_SCALE < TALL_STRETCH_LIMIT) {
return false;
}
#if 0
nU = nV = xU = xV = 0.0f;
U[0] = nU; U[1] = xU; U[2] = xU; U[3] = nU;
V[0] = xV; V[1] = xV; V[2] = nV; V[3] = nV;
return;
#endif
Real dx = (h3-h2)*HEIGHT_SCALE;
dx = sqrt(1+dx*dx); // lenght of the bottom of the cell
Real dy = (h3-h0)*HEIGHT_SCALE;
dy = sqrt(1+dy*dy); // length of the left side.
if (dxTILE_LIMIT) dx = TILE_LIMIT; // don't tile past the texture's edge.
if (dy>TILE_LIMIT) dy = TILE_LIMIT; // don't tile past the texture's edge.
dx *= xU-nU;
dy *= xV-nV;
U[0] = nU; U[1] = nU+dx; U[2] = nU+dx; U[3] = nU;
V[0] = nV+dy; V[1] = nV+dy; V[2] = nV; V[3] = nV;
if (below==1) {
below = 1;
}
// recalc for point 1.
dx = (h1-h0)*HEIGHT_SCALE;
dx = sqrt(1+dx*dx); // lenght of the bottom of the cell
dy = (h2-h1)*HEIGHT_SCALE;
dy = sqrt(1+dy*dy); // length of the left side.
if (dxTILE_LIMIT) dx = TILE_LIMIT; // don't tile past the texture's edge.
if (dy>TILE_LIMIT) dy = TILE_LIMIT; // don't tile past the texture's edge.
dx *= xU-nU;
dy *= xV-nV;
U[1] = U[0]+dx;
V[1] = V[3] + dy;
}
// Make sure we are within the texture;
Real adjU = 0;
Real adjV = 0;
Int i;
for (i=0; i<4; i++) {
if (nVb - V[i] > adjV) adjV = nVb - V[i];
}
for (i=0; i<4; i++) {
V[i] += adjV;
}
adjV = 0;
for (i=0; i<4; i++) {
if (U[i] - xUb > adjU) adjU = U[i]-xUb;
if (V[i] - xVb > adjV) adjV = V[i]-xVb;
}
for (i=0; i<4; i++) {
U[i] -= adjU;
V[i] -= adjV;
}
}
return true;
//
#endif
}
return false;
}
///@todo: Are the different "if" cases mutually exclusive? If so, should add else statements.
Bool WorldHeightMap::getExtraAlphaUVData(Int xIndex, Int yIndex, float U[4], float V[4], UnsignedByte alpha[4], Bool *needFlip, Bool *cliff)
{
Int ndx = (yIndex*m_width)+xIndex;
*needFlip = FALSE;
*cliff = FALSE;
if ( (ndx>=0) && (ndx=0) {
alpha[0] = alpha[1] = alpha[2] = alpha[3] = 0;
// No alpha blend, so never need to flip.
*needFlip = FALSE;
}
}
}
return TRUE;
}
/** getUVData - Gets the texture coordinates to use with the alpha texture.
xIndex and yIndex are the integer coorddinates into the height map.
U and V are the texture coordiantes for the 4 corners of a height map cell.
fullTile is true if we are doing 1/2 resolution height map, and require a full
tile to texture a cell. Otherwise, we use quarter tiles per cell.
flip is set if we need to flip the diagonal across the cell to make the
alpha coordinates blend properly. Filling a square with 2 triangles is not symmetrical :)
*/
void WorldHeightMap::getAlphaUVData(Int xIndex, Int yIndex, float U[4], float V[4],
UnsignedByte alpha[4], Bool *flip, Bool fullTile)
{
xIndex += m_drawOriginX;
yIndex += m_drawOriginY;
Int ndx = (yIndex*m_width)+xIndex;
Bool stretchedForCliff = false;
Bool needFlip = false;
if ((ndx=0) {
alpha[0] = alpha[1] = alpha[2] = alpha[3] = 0;
// No alpha blend, so never need to flip.
needFlip = false;
}
}
}
if (stretchedForCliff) {
// If we had to stretch for clif, check heights.
Int p0=getHeight(xIndex, yIndex);
Int p1=getHeight(xIndex+1, yIndex);
Int p2=getHeight(xIndex+1, yIndex+1);
Int p3=getHeight(xIndex, yIndex+1);
Int dz1 = abs(p0-p2);
Int dz2 = abs(p1-p3);
needFlip = dz1>dz2;
}
#ifdef FLIP_TRIANGLES
*flip = needFlip;
#endif
}
TextureClass *WorldHeightMap::getTerrainTexture(void)
{
if (m_terrainTex == NULL) {
Int edgeHeight;
Int height = updateTileTexturePositions(&edgeHeight);
Int pow2Height = 1;
while (pow2Heightupdate(this);
char buf[64];
sprintf(buf, "Base tex height %d\n", pow2Height);
DEBUG_LOG((buf));
REF_PTR_RELEASE(m_alphaTerrainTex);
m_alphaTerrainTex = MSGNEW("WorldHeightMap_getTerrainTexture") AlphaTerrainTextureClass(m_terrainTex);
pow2Height = 1;
while (pow2Heightupdate(this);
//Generate lookup table for determining triangle order in each terrain cell.
//Not the best place to put this but getAlphaUVData() requires a valid terrain
//texture to return valid values.
for (Int y=0; y<(m_height-1); y++)
for (Int x=0; x<(m_width-1); x++)
{
UnsignedByte alpha[4];
float UA[4], VA[4];
Bool flipForBlend;
getAlphaUVData(x, y, UA, VA, alpha, &flipForBlend, false);
m_cellFlipState[y*m_flipStateWidth+(x>>3)] |= flipForBlend << (x & 0x7);
DEBUG_ASSERTCRASH ((y*m_flipStateWidth+(x>>3)) < (m_flipStateWidth * m_height), ("Bad range"));
}
}
return m_terrainTex;
}
TextureClass *WorldHeightMap::getAlphaTerrainTexture(void)
{
if (m_alphaTerrainTex == NULL) {
getTerrainTexture();
}
return m_alphaTerrainTex;
}
TextureClass *WorldHeightMap::getEdgeTerrainTexture(void)
{
if (m_alphaEdgeTex == NULL) {
getTerrainTexture();
}
return m_alphaEdgeTex;
}
Bool WorldHeightMap::setDrawOrg(Int xOrg, Int yOrg)
{
Int newX, newY;
Int newWidth, newHeight;
newX = xOrg;
newY = yOrg;
newWidth = m_drawWidthX;
newHeight = m_drawHeightY;
if (TheGlobalData && TheGlobalData->m_stretchTerrain) {
newWidth=STRETCH_DRAW_WIDTH;
newHeight=STRETCH_DRAW_HEIGHT;
}
if (TheGlobalData && TheGlobalData->m_drawEntireTerrain) {
newWidth=m_width;
newHeight=m_height;
}
if (newWidth > m_width) newWidth = m_width;
if (newHeight > m_height) newHeight = m_height;
if (newX > m_width - newWidth) newX = m_width-newWidth;
if (newX<0) newX=0;
if (newY > m_height - newHeight) newY = m_height - newHeight;
if (newY<0) newY=0;
Bool anythingDifferent = (m_drawOriginX!=newX) ||
(m_drawOriginY!=newY) ||
(m_drawWidthX!=newWidth) ||
(m_drawHeightY!=newHeight) ;
if (anythingDifferent) {
m_drawOriginX=newX;
m_drawOriginY=newY;
m_drawWidthX=newWidth;
m_drawHeightY=newHeight;
return(true);
}
return(false);
}
/** Gets global texture class. */
Int WorldHeightMap::getTextureClass(Int xIndex, Int yIndex, Bool baseClass)
{
Int ndx = (yIndex*m_width)+xIndex;
DEBUG_ASSERTCRASH((ndx>=0 && ndxm_dataSize),("oops"));
if (ndx<0 || ndx >= this->m_dataSize) return(-1);
Int textureNdx = m_tileNdxes[ndx];
if (!baseClass && (m_blendTileNdxes[ndx] != 0 || m_extraBlendTileNdxes[ndx] != 0)) {
return(-1); // blended, so not of the original class.
}
return getTextureClassFromNdx(textureNdx);
}
/** Sets all the cliff flags in map based on height. */
void WorldHeightMap::initCliffFlagsFromHeights()
{
Int xIndex, yIndex;
for (xIndex=0; xIndex height2) minZ = height2;
if (minZ > height3) minZ = height3;
if (minZ > height4) minZ = height4;
Real maxZ = height1;
if (maxZ < height2) maxZ = height2;
if (maxZ < height3) maxZ = height3;
if (maxZ < height4) maxZ = height4;
const Real cliffRange = PATHFIND_CLIFF_SLOPE_LIMIT_F;
Bool isCliff = (maxZ-minZ > cliffRange);
setCliffState(xIndex, yIndex, isCliff);
}
/** Gets global texture class. */
Int WorldHeightMap::getTextureClassFromNdx(Int tileNdx)
{
Int i;
tileNdx = tileNdx>>2;
for (i=0; i= m_textureClasses[i].firstTile &&
tileNdx < m_textureClasses[i].firstTile+m_textureClasses[i].numTiles) {
return(m_textureClasses[i].globalTextureClass);
}
}
return(-1);
}
TXTextureClass WorldHeightMap::getTextureFromIndex( Int textureIndex )
{
return m_textureClasses[textureIndex];
}
void WorldHeightMap::getTerrainColorAt(Real x, Real y, RGBColor *pColor)
{
Int xIndex = REAL_TO_INT_FLOOR(x/MAP_XY_FACTOR);
Int yIndex = REAL_TO_INT_FLOOR(y/MAP_XY_FACTOR);
xIndex += m_borderSize;
yIndex += m_borderSize;
pColor->red = pColor->green = pColor->blue = 0;
if (xIndex<0) xIndex = 0;
if (yIndex<0) yIndex = 0;
if (xIndex >= m_width) xIndex = m_width-1;
if (yIndex >= m_height) yIndex = m_height-1;
Int ndx = (yIndex*m_width)+xIndex;
if (ndx<0 || ndx >= this->m_dataSize) return;
Int tileNdx = m_tileNdxes[ndx];
tileNdx = tileNdx>>2; // We pack 4 grids into a tile.
TileData *pTile = getSourceTile(tileNdx);
if (pTile) {
// pTile contains the bitmap data for 4 squares.
// Get the data mipped down to one pixel for the tile.
UnsignedByte *pData = pTile->getRGBDataForWidth(1);
// Data is in microsoft bgra format.
pColor->red = pData[2]/255.0;
pColor->green = pData[1]/255.0;
pColor->blue = pData[0]/255.0;
}
}
AsciiString WorldHeightMap::getTerrainNameAt(Real x, Real y)
{
Int xIndex = REAL_TO_INT_FLOOR(x/MAP_XY_FACTOR);
Int yIndex = REAL_TO_INT_FLOOR(y/MAP_XY_FACTOR);
xIndex += m_borderSize;
yIndex += m_borderSize;
if (xIndex<0) xIndex = 0;
if (yIndex<0) yIndex = 0;
if (xIndex >= m_width) xIndex = m_width-1;
if (yIndex >= m_height) yIndex = m_height-1;
Int ndx = (yIndex*m_width)+xIndex;
if (ndx<0 || ndx >= this->m_dataSize) return AsciiString::TheEmptyString;
Int tileNdx = m_tileNdxes[ndx];
tileNdx = tileNdx>>2; // We pack 4 grids into a tile.
Int i;
for (i=0; im_numTextureClasses; i++) {
if (tileNdx >= m_textureClasses[i].firstTile && tileNdx < m_textureClasses[i].firstTile + m_textureClasses[i].numTiles) {
return(m_textureClasses[i].name);
}
}
return AsciiString::TheEmptyString;
}