/* ** 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; }