// Heightmap.cpp: implementation of the CHeightmap class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Heightmap.h" #include "Noise.h" #include "Layer.h" #include "CryEditDoc.h" #include "TerrainFormulaDlg.h" #include "VegetationMap.h" #include "TerrainGrid.h" #include "Util\DynamicArray2D.h" #include #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #define DEFAULT_TEXTURE_SIZE 4096 //! Size of terrain sector in meters. #define SECTOR_SIZE_IN_METERS 64 //! Size of noise array. #define NOISE_ARRAY_SIZE 512 //! Filename used when Holding/Fetching heightmap. #define HEIGHTMAP_HOLD_FETCH_FILE "Heightmap.hld" #define OVVERIDE_LAYER_SURFACETYPE_FROM 128 ////////////////////////////////////////////////////////////////////////// //! Undo object for heightmap modifications. class CUndoHeightmapModify : public IUndoObject { public: CUndoHeightmapModify( int x1,int y1,int width,int height,CHeightmap *heightmap ) { m_hmap.Attach( heightmap->GetData(),heightmap->GetWidth(),heightmap->GetHeight() ); // Store heightmap block. m_rc = CRect( x1,y1,x1+width,y1+height ); m_rc &= CRect( 0,0,m_hmap.GetWidth(),m_hmap.GetHeight() ); m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_undo ); } protected: virtual void Release() { delete this; }; virtual int GetSize() { return sizeof(*this) + m_undo.GetSize() + m_redo.GetSize(); }; virtual const char* GetDescription() { return "Heightmap Modify"; }; virtual void Undo( bool bUndo ) { if (bUndo) { // Store for redo. m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_redo ); } // Restore image. m_hmap.SetSubImage( m_rc.left,m_rc.top,m_undo ); GetIEditor()->GetHeightmap()->UpdateEngineTerrain( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),true,false ); } virtual void Redo() { if (m_redo.IsValid()) { // Restore image. m_hmap.SetSubImage( m_rc.left,m_rc.top,m_redo ); GetIEditor()->GetHeightmap()->UpdateEngineTerrain( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),true,false ); } } private: CRect m_rc; TImage m_undo; TImage m_redo; TImage m_hmap; }; ////////////////////////////////////////////////////////////////////////// //! Undo object for heightmap modifications. class CUndoHeightmapInfo : public IUndoObject { public: CUndoHeightmapInfo( int x1,int y1,int width,int height,CHeightmap *heightmap ) { TImage hmap; m_hmap.Attach( heightmap->GetInfoData(),heightmap->GetWidth(),heightmap->GetHeight() ); // Store heightmap block. m_hmap.GetSubImage( x1,y1,width,height,m_undo ); m_rc = CRect(x1,y1,x1+width,y1+height); } protected: virtual void Release() { delete this; }; virtual int GetSize() { return sizeof(*this) + m_undo.GetSize() + m_redo.GetSize(); }; virtual const char* GetDescription() { return "Heightmap Hole"; }; virtual void Undo( bool bUndo ) { if (bUndo) { // Store for redo. m_hmap.GetSubImage( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height(),m_redo ); } // Restore image. m_hmap.SetSubImage( m_rc.left,m_rc.top,m_undo ); GetIEditor()->GetHeightmap()->UpdateEngineHole( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height() ); } virtual void Redo() { if (m_redo.IsValid()) { // Restore image. m_hmap.SetSubImage( m_rc.left,m_rc.top,m_redo ); GetIEditor()->GetHeightmap()->UpdateEngineHole( m_rc.left,m_rc.top,m_rc.Width(),m_rc.Height() ); } } private: CRect m_rc; TImage m_undo; TImage m_redo; TImage m_hmap; }; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CHeightmap::CHeightmap() { // Init member variables m_pHeightmap = NULL; m_iWidth = 0; m_iHeight = 0; m_pNoise = NULL; m_fWaterLevel = 16; m_unitSize = 2; m_cachedResolution = 0; m_numSectors = 0; m_vegetationMap = new CVegetationMap; m_textureSize = DEFAULT_TEXTURE_SIZE; m_terrainGrid = new CTerrainGrid(this); InitNoise(); } CHeightmap::~CHeightmap() { // Reset the heightmap CleanUp(); delete m_terrainGrid; // Remove the noise array if (m_pNoise) { delete m_pNoise; m_pNoise = NULL; } } ////////////////////////////////////////////////////////////////////////// void CHeightmap::CleanUp() { //////////////////////////////////////////////////////////////////////// // Free the data //////////////////////////////////////////////////////////////////////// if (m_pHeightmap) { delete [] m_pHeightmap; m_pHeightmap = NULL; } if (m_vegetationMap) delete m_vegetationMap; m_iWidth = 0; m_iHeight = 0; } ////////////////////////////////////////////////////////////////////////// void CHeightmap::Resize( int iWidth, int iHeight,int unitSize,bool bCleanOld ) { //////////////////////////////////////////////////////////////////////// // Resize the heightmap //////////////////////////////////////////////////////////////////////// ASSERT(iWidth && iHeight); int prevWidth,prevHeight,prevUnitSize; prevWidth = m_iWidth; prevHeight = m_iHeight; prevUnitSize = m_unitSize; TImage prevHeightmap; TImage prevInfo; if (bCleanOld) { // Free old heightmap CleanUp(); } else { if (m_pHeightmap) { // Remember old state. prevHeightmap.Allocate( m_iWidth,m_iHeight ); memcpy( prevHeightmap.GetData(),m_pHeightmap,prevHeightmap.GetSize() ); } if (m_info.IsValid()) { prevInfo.Allocate( m_iWidth,m_iHeight ); memcpy( prevInfo.GetData(),m_info.GetData(),prevInfo.GetSize() ); } } // Save width and height m_iWidth = iWidth; m_iHeight = iHeight; m_unitSize = unitSize; int sectorSize = SECTOR_SIZE_IN_METERS; m_numSectors = (m_iWidth*m_unitSize) / sectorSize; // Allocate new data m_pHeightmap = new t_hmap[iWidth * iHeight]; Verify(); m_info.Allocate( iWidth,iHeight ); if (bCleanOld) { // Set to zero Clear(); } else { // Copy from previous data. if (prevHeightmap.IsValid() && prevInfo.IsValid()) { CWaitCursor wait; CopyFrom( prevHeightmap.GetData(),prevInfo.GetData(),prevWidth ); } } m_terrainGrid->InitSectorGrid( m_numSectors ); m_terrainGrid->SetResolution( m_textureSize ); if (bCleanOld) { m_vegetationMap = new CVegetationMap; m_vegetationMap->Allocate( this ); } else { CWaitCursor wait; bool bVegSaved = false; CXmlArchive ar("Temp"); if (m_vegetationMap) { m_vegetationMap->Serialize( ar ); bVegSaved = true; } m_vegetationMap = new CVegetationMap; m_vegetationMap->Allocate( this ); if (bVegSaved) { // Load back from archive. ar.bLoading = true; m_vegetationMap->Serialize(ar); } } if (!bCleanOld) { int numLayers = GetIEditor()->GetDocument()->GetLayerCount(); for (int i = 0; i < numLayers; i++) { CLayer *pLayer = GetIEditor()->GetDocument()->GetLayer(i); pLayer->AllocateMaskGrid(); } } // We modified the heightmap. SetModified(); } ////////////////////////////////////////////////////////////////////////// CPoint CHeightmap::WorldToHmap( const Vec3 &wp ) { //swap x/y. return CPoint(wp.y/m_unitSize,wp.x/m_unitSize); } ////////////////////////////////////////////////////////////////////////// Vec3 CHeightmap::HmapToWorld( CPoint hpos ) { return Vec3(hpos.y*m_unitSize,hpos.x*m_unitSize,0); } ////////////////////////////////////////////////////////////////////////// void CHeightmap::InvalidateLayers() { GetIEditor()->GetDocument()->InvalidateLayers(); } void CHeightmap::Clear() { if (m_iWidth && m_iHeight) { memset( m_pHeightmap,0,sizeof(t_hmap)*m_iWidth*m_iHeight ); m_info.Clear(); } }; void CHeightmap::LoadBMP(LPCSTR pszFileName, bool bNoiseAndFilter) { //////////////////////////////////////////////////////////////////////// // Load a BMP file as current heightmap //////////////////////////////////////////////////////////////////////// CImage image; CImage hmap; if (!CImageUtil::LoadImage( pszFileName,image )) { AfxMessageBox( _T("Load image failed."),MB_OK|MB_ICONERROR ); return; } if (image.GetWidth() != m_iWidth || image.GetHeight() != m_iHeight) { hmap.Allocate( m_iWidth,m_iHeight ); CImageUtil::ScaleToFit( image,hmap ); } else { hmap.Attach( image ); } uint *pData = hmap.GetData(); int size = hmap.GetWidth()*hmap.GetHeight(); for (int i = 0; i < size; i++) { // Extract a color channel and rescale the value // before putting it into the heightmap int col = pData[i] & 0x000000FF; m_pHeightmap[i] = col; } /* // Noise and filter ? if (bNoiseAndFilter) { Smooth(); Noise(); } */ // We modified the heightmap. SetModified(); CLogFile::FormatLine("Heightmap loaded from file %s", pszFileName); } void CHeightmap::SaveImage(LPCSTR pszFileName, bool bNoiseAndFilter) { uint *pImageData = NULL; unsigned int i, j; uint8 iColor; t_hmap *pHeightmap = NULL; UINT iWidth = GetWidth(); UINT iHeight = GetHeight(); // Allocate memory to export the heightmap CImage image; image.Allocate(iWidth,iHeight); pImageData = image.GetData(); // Get a pointer to the heightmap data pHeightmap = GetData(); // BMP // Write heightmap into the image data array for (j=0; jm_Array[iNoiseX % NOISE_ARRAY_SIZE][iNoiseY % NOISE_ARRAY_SIZE])); } } } // We modified the heightmap. SetModified(); } ////////////////////////////////////////////////////////////////////////// void CHeightmap::Smooth( CFloatImage &hmap,const CRect &rect ) { int w = hmap.GetWidth(); int h = hmap.GetHeight(); int x1 = max(rect.left+2,1); int y1 = max(rect.top+2,1); int x2 = min(rect.right-2,w-1); int y2 = min(rect.bottom-2,h-1); t_hmap* pData = hmap.GetData(); int i,j,pos; // Smooth it for (j=y1; j < y2; j++) { pos = j*w; for (i=x1; i < x2; i++) { pData[i + pos] = (pData[i + pos] + pData[i + 1 + pos] + pData[i - 1 + pos] + pData[i + pos+w] + pData[i + pos-w] + pData[(i - 1) + pos-w] + pData[(i + 1) + pos-w] + pData[(i - 1) + pos+w] + pData[(i + 1) + pos+w]) * (1.0f/9.0f); } } /* for (j=y2-1; j > y1; j--) { pos = j*w; for (i=x2-1; i > x1; i--) { pData[i + pos] = (pData[i + pos] + pData[i + 1 + pos] + pData[i - 1 + pos] + pData[i + pos+w] + pData[i + pos-w] + pData[(i - 1) + pos-w] + pData[(i + 1) + pos-w] + pData[(i - 1) + pos+w] + pData[(i + 1) + pos+w]) * (1.0f/9.0f); } } */ } void CHeightmap::Smooth() { //////////////////////////////////////////////////////////////////////// // Smooth the heightmap //////////////////////////////////////////////////////////////////////// unsigned int i, j; Verify(); // Smooth it for (i=1; i= 1024; // Only log significant allocations. This also prevents us from cluttering the // log file during the lightmap preview generation if (iDestWidth >= 512) CLogFile::FormatLine("Retrieving heightmap data (Width: %i)...", iDestWidth); CWaitProgress wait( "Scaling Heightmap",bProgress ); // Loop trough each field of the new image and interpolate the value // from the source heightmap for (j=0; j= 0.0f && fYSrc <= dwHeightmapWidth); // Precalculate floor and ceiling values. Use fast asm integer floor and // fast asm float / integer conversion iYSrcFl = ifloor(fYSrc); iYSrcCe = iYSrcFl+1; // Clamp the ceiling coordinates to a save range if (iYSrcCe >= (int) dwHeightmapWidth) iYSrcCe = dwHeightmapWidth - 1; // Distribution between top and bottom height values fHeightWeight[3] = fYSrc-(float)iYSrcFl; fHeightWeight[2] = 1.0f-fHeightWeight[3]; for (i=0; i= 0.0f && fXSrc <= dwHeightmapWidth); // Precalculate floor and ceiling values. Use fast asm integer floor and // fast asm float / integer conversion iXSrcFl = ifloor(fXSrc); iXSrcCe = iXSrcFl+1; // Distribution between left and right height values fHeightWeight[1] = fXSrc-(float)iXSrcFl; fHeightWeight[0] = 1.0f-fHeightWeight[1]; /* // Avoid error when floor() and ceil() return the same value if (fHeightWeight[0] == 0.0f && fHeightWeight[1] == 0.0f) { fHeightWeight[0] = 0.5f; fHeightWeight[1] = 0.5f; } // Calculate how much weight each height value has // Avoid error when floor() and ceil() return the same value if (fHeightWeight[2] == 0.0f && fHeightWeight[3] == 0.0f) { fHeightWeight[2] = 0.5f; fHeightWeight[3] = 0.5f; } */ if (iXSrcCe >= (int) dwHeightmapWidth) iXSrcCe = dwHeightmapWidth - 1; // Get the four nearest height values fHeight[0] = (float) m_pHeightmap[iXSrcFl + iYSrcFl * dwHeightmapWidth]; fHeight[1] = (float) m_pHeightmap[iXSrcCe + iYSrcFl * dwHeightmapWidth]; fHeight[2] = (float) m_pHeightmap[iXSrcFl + iYSrcCe * dwHeightmapWidth]; fHeight[3] = (float) m_pHeightmap[iXSrcCe + iYSrcCe * dwHeightmapWidth]; // Interpolate between the four nearest height values // Get the height for the given X position trough interpolation between // the left and the right height fHeightBottom = (fHeight[0] * fHeightWeight[0] + fHeight[1] * fHeightWeight[1]); fHeightTop = (fHeight[2] * fHeightWeight[0] + fHeight[3] * fHeightWeight[1]); // Set the new value in the destination heightmap *pData++ = (t_hmap) (fHeightBottom * fHeightWeight[2] + fHeightTop * fHeightWeight[3]); } } if (bNoise) { pData = pDataStart; // Smooth it for (i=1; i < iDestWidth-1; i++) for (j=1; j < iDestWidth-1; j++) { *pData++ += (((float)rand())/RAND_MAX) * 1.0f/16.0f; } } if (bSmooth) { CFloatImage img; img.Attach( pDataStart,iDestWidth,iDestWidth ); Smooth( img,CRect(0,0,iDestWidth,iDestWidth) ); } // Cache rescaled data. m_cachedResolution = iDestWidth; /* int nSize = m_cachedResolution*m_cachedResolution*sizeof(float); CMemoryBlock temp; temp.Allocate( nSize ); temp.Copy( pDataStart,nSize ); temp.Compress( m_cachedHeightmap ); */ return true; } ////////////////////////////////////////////////////////////////////////// bool CHeightmap::GetData( CRect &srcRect,CFloatImage &hmap, bool bSmooth,bool bNoise ) { //////////////////////////////////////////////////////////////////////// // Retrieve heightmap data. Scaling and noising is optional //////////////////////////////////////////////////////////////////////// unsigned int i, j; int iXSrcFl, iXSrcCe, iYSrcFl, iYSrcCe; float fXSrc, fYSrc; float fHeight[4]; float fHeightWeight[4]; float fHeightBottom; float fHeightTop; UINT dwHeightmapWidth = GetWidth(); int resolution = hmap.GetWidth(); t_hmap *pDataStart = hmap.GetData(); t_hmap *pData = pDataStart; int x1 = max(srcRect.left,0); int y1 = max(srcRect.top,0); int x2 = min(srcRect.right,resolution); int y2 = min(srcRect.bottom,resolution); int trgW = x2 - x1; bool bProgress = trgW >= 1024; CWaitProgress wait( "Scaling Heightmap",bProgress ); // Loop trough each field of the new image and interpolate the value // from the source heightmap for (j = y1; j < y2; j++) { if (bProgress) { if (!wait.Step( (j-y1)*100/(y2-y1) )) return false; } pData = &pDataStart[j*resolution + x1]; // Calculate the average source array position fYSrc = ((float) j / (float) resolution) * dwHeightmapWidth; assert(fYSrc >= 0.0f && fYSrc <= dwHeightmapWidth); // Precalculate floor and ceiling values. Use fast asm integer floor and // fast asm float / integer conversion iYSrcFl = ifloor(fYSrc); iYSrcCe = iYSrcFl+1; // Clamp the ceiling coordinates to a save range if (iYSrcCe >= (int) dwHeightmapWidth) iYSrcCe = dwHeightmapWidth - 1; // Distribution between top and bottom height values fHeightWeight[3] = fYSrc - (float)iYSrcFl; fHeightWeight[2] =1.0f - fHeightWeight[3]; for (i = x1; i < x2; i++) { // Calculate the average source array position fXSrc = ((float) i / (float) resolution) * dwHeightmapWidth; assert(fXSrc >= 0.0f && fXSrc <= dwHeightmapWidth); // Precalculate floor and ceiling values. Use fast asm integer floor and // fast asm float / integer conversion iXSrcFl = ifloor(fXSrc); iXSrcCe = iXSrcFl+1; if (iXSrcCe >= (int) dwHeightmapWidth) iXSrcCe = dwHeightmapWidth - 1; // Distribution between left and right height values fHeightWeight[1] = fXSrc - (float)iXSrcFl; fHeightWeight[0] = 1.0f - fHeightWeight[1]; /* // Avoid error when floor() and ceil() return the same value if (fHeightWeight[0] == 0.0f && fHeightWeight[1] == 0.0f) { fHeightWeight[0] = 0.5f; fHeightWeight[1] = 0.5f; } // Calculate how much weight each height value has // Avoid error when floor() and ceil() return the same value if (fHeightWeight[2] == 0.0f && fHeightWeight[3] == 0.0f) { fHeightWeight[2] = 0.5f; fHeightWeight[3] = 0.5f; } */ // Get the four nearest height values fHeight[0] = (float) m_pHeightmap[iXSrcFl + iYSrcFl * dwHeightmapWidth]; fHeight[1] = (float) m_pHeightmap[iXSrcCe + iYSrcFl * dwHeightmapWidth]; fHeight[2] = (float) m_pHeightmap[iXSrcFl + iYSrcCe * dwHeightmapWidth]; fHeight[3] = (float) m_pHeightmap[iXSrcCe + iYSrcCe * dwHeightmapWidth]; // Interpolate between the four nearest height values // Get the height for the given X position trough interpolation between // the left and the right height fHeightBottom = (fHeight[0] * fHeightWeight[0] + fHeight[1] * fHeightWeight[1]); fHeightTop = (fHeight[2] * fHeightWeight[0] + fHeight[3] * fHeightWeight[1]); // Set the new value in the destination heightmap *pData++ = (t_hmap) (fHeightBottom * fHeightWeight[2] + fHeightTop * fHeightWeight[3]); } } // Only if requested resolution, higher then current resolution. if (resolution > m_iWidth) { if (bNoise) { // Smooth it for (int y = y1; y < y2; y++) { pData = &pDataStart[y*resolution+x1]; for (int x = x1; x < x2; x++) { *pData++ += (((float)rand())/RAND_MAX) * 1.0f/16.0f; //*pData++ += (((float)rand())/RAND_MAX) * 1.0f/8.0f; } } } } if (bSmooth) { Smooth( hmap,srcRect ); } return true; } ////////////////////////////////////////////////////////////////////////// bool CHeightmap::GetPreviewBitmap( DWORD *pBitmapData,int width,bool bSmooth, bool bNoise ) { bool res = false; t_hmap *pHeightmap = new t_hmap[width*width]; res = GetDataEx( pHeightmap,width,bSmooth,bNoise ); if (res) { DWORD *pWaterTexData = NULL; CBitmap bmpLoad; t_hmap *pH = pHeightmap; pWaterTexData = new DWORD[128 * 128]; // Retrieve the bits from the bitmap VERIFY( bmpLoad.Attach(::LoadBitmap(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDB_WATER))) ); VERIFY( bmpLoad.GetBitmapBits(128 * 128 * sizeof(DWORD), pWaterTexData) ); int w = width; int h = width; UINT iWaterLevel = m_fWaterLevel; // Fill the preview with the heightmap image for (int iY=0; iY < h; iY++) for (int iX=0; iX < w; iX++) { // Use the height value as grayscale if the current heightmap point is above the water // level. Use a texel from the tiled water texture when it is below the water level uint val = ftoi(*pH); pBitmapData[iX + iY*w] = (*pH < iWaterLevel) ? pWaterTexData[(iX % 128) + (iY % 128) * 128] : RGB(val,val,val); pH++; } delete []pWaterTexData; } delete []pHeightmap; return res; } void CHeightmap::GenerateTerrain(const SNoiseParams &noiseParam) { //////////////////////////////////////////////////////////////////////// // Generate a new terrain with the parameters stored in sParam //////////////////////////////////////////////////////////////////////// unsigned int i, j; CDynamicArray2D cHeightmap(GetWidth(), GetHeight()); CNoise cNoise; float fYScale = 255.0f; float *pArrayShortcut = NULL; DWORD *pImageData = NULL; DWORD *pImageDataStart = NULL; SNoiseParams sParam = noiseParam; ASSERT(sParam.iWidth == m_iWidth && sParam.iHeight == m_iHeight); ////////////////////////////////////////////////////////////////////// // Generate the noise array ////////////////////////////////////////////////////////////////////// AfxGetMainWnd()->BeginWaitCursor(); CLogFile::WriteLine("Noise..."); // Set the random value srand(sParam.iRandom); // Process layers for (i=0; iGetDocument()->InvalidateLayers(); AfxGetMainWnd()->EndWaitCursor(); } void CHeightmap::SmoothSlope() { ////////////////////////////////////////////////////////////////////// // Remove areas with high slope from the heightmap ////////////////////////////////////////////////////////////////////// UINT iCurPos; float fAverage; unsigned int i, j; CLogFile::WriteLine("Smoothing the slope of the heightmap..."); // Remove the high slope areas (horizontal) for (j=1; j fAverage * 0.001f) { // Negativ / Positiv ? if (*pValue < fAverage) fClampedVal = fAverage - (fAverage * 0.001f); else fClampedVal = fAverage + (fAverage * 0.001f); // Renormalize it if (fClampedVal > 255.0f) fClampedVal = 255.0f; else if (fClampedVal < 0) fClampedVal = 0; *pValue = fClampedVal; } } void CHeightmap::MakeIsle() { ////////////////////////////////////////////////////////////////////// // Convert any terrain into an isle ////////////////////////////////////////////////////////////////////// int i, j; t_hmap *pHeightmapData = m_pHeightmap; // UINT m_iHeightmapDiag; float fDeltaX, fDeltaY; float fDistance; float fCurHeight, fFade; CLogFile::WriteLine("Modifying heightmap to an isle..."); // Calculate the length of the diagonale trough the heightmap // m_iHeightmapDiag = (UINT) sqrt(GetWidth() * GetWidth() + // GetHeight() * GetHeight()); float fMaxDistance = sqrtf((GetWidth()/2) * (GetWidth()/2) + (GetHeight()/2) * (GetHeight()/2)); for (j=0; j 255.0f) fCurHeight = 255.0f; if (fCurHeight < 0.0f) fCurHeight = 0.0f; // Write the value back and andvance *pHeightmapData++ = fCurHeight; } } // We modified the heightmap. SetModified(); } void CHeightmap::Flatten(float fFactor) { //////////////////////////////////////////////////////////////////////// // Increase the number of flat areas on the heightmap (TODO: Fix !) //////////////////////////////////////////////////////////////////////// t_hmap *pHeightmapData = m_pHeightmap; t_hmap *pHeightmapDataEnd = &m_pHeightmap[m_iWidth * m_iHeight]; float fRes; CLogFile::WriteLine("Flattening heightmap..."); // Perform the conversion while (pHeightmapData != pHeightmapDataEnd) { // Get the exponential value for this height value fRes = ExpCurve(*pHeightmapData, 128.0f, 0.985f); // Is this part of the landscape a potential flat area ? // Only modify parts of the landscape that are above the water level if (fRes < 100 && *pHeightmapData > m_fWaterLevel) { // Yes, apply the factor to it *pHeightmapData = (t_hmap) (*pHeightmapData * fFactor); // When we use a factor greater than 0.5, we don't want to drop below // the water level *pHeightmapData++ = __max(m_fWaterLevel, *pHeightmapData); } else { // No, use the exponential function to make smooth transitions *pHeightmapData++ = (t_hmap) fRes; } } // We modified the heightmap. SetModified(); } float CHeightmap::ExpCurve(float v, float fCover, float fSharpness) { ////////////////////////////////////////////////////////////////////// // Exponential function ////////////////////////////////////////////////////////////////////// float c; c = v - fCover; if (c < 0) c = 0; return 255.0f - (float) ((pow(fSharpness, c)) * 255.0f); } void CHeightmap::MakeBeaches() { ////////////////////////////////////////////////////////////////////// // Create flat areas around beaches ////////////////////////////////////////////////////////////////////// CTerrainFormulaDlg TerrainFormulaDlg; TerrainFormulaDlg.m_dParam1 = 5; TerrainFormulaDlg.m_dParam2 = 1000; TerrainFormulaDlg.m_dParam3 = 1; if(TerrainFormulaDlg.DoModal()!=IDOK) return; unsigned int i, j; t_hmap *pHeightmapData = NULL; t_hmap *pHeightmapDataStart = NULL; double dCurHeight; CLogFile::WriteLine("Applaing formula ..."); // Get the water level double dWaterLevel = m_fWaterLevel; // Make the beaches for (j=0; j 255) dCurHeight = 255; else if(dCurHeight<0) dCurHeight=0; // Write the value back and andvance to the next pixel m_pHeightmap[i + j * m_iWidth] = dCurHeight; } } m_pHeightmap[0]=0; m_pHeightmap[1]=255; // Normalize because the beach creation distorted the value range /// Normalize(); // We modified the heightmap. SetModified(); } void CHeightmap::LowerRange(float fFactor) { ////////////////////////////////////////////////////////////////////// // Lower the value range of the heightmap, effectively making it // more flat ////////////////////////////////////////////////////////////////////// unsigned int i; float fWaterHeight = m_fWaterLevel; CLogFile::WriteLine("Lowering range..."); // Lower the range, make sure we don't put anything below the water level for (i=0; iIsUndoRecording()) GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-iWidth,iY-iWidth,iWidth*2,iWidth*2,this) ); for (j=(long) -iWidth; j (long) m_iWidth - 1 || iPosY > (long) m_iHeight - 1) { continue; } // Calculate the array index iIndex = iPosX + iPosY * m_iWidth; // Calculate attenuation factor fAttenuation = 1.0f - __min(1.0f, sqrtf((float) (i * i + fJSquared)) / fMaxDist); // Which drawing mode are we in ? if (fSetToHeight >= 0.0f) { // Set to height mode, modify the location towards the specified height fCurHeight = m_pHeightmap[iIndex]; m_pHeightmap[iIndex] *= 4.0f; m_pHeightmap[iIndex] += (1.0f - fAttenuation) * fCurHeight + fAttenuation * fSetToHeight; m_pHeightmap[iIndex] /= 5.0f; } else if (bAddNoise) { // Noise brush if (fAddVal > 0.0f) { m_pHeightmap[iIndex] += fAddVal/100 * ((float) fabs(m_pNoise->m_Array[iPosX % NOISE_ARRAY_SIZE][iPosY % NOISE_ARRAY_SIZE])) * fAttenuation; } else { m_pHeightmap[iIndex] += fAddVal/100 * (float) (-fabs(m_pNoise->m_Array[iPosX % NOISE_ARRAY_SIZE][iPosY % NOISE_ARRAY_SIZE])) * fAttenuation; } } else { // No, modify the location with a normal brush m_pHeightmap[iIndex] += fAddVal * fAttenuation; } // Clamp if (m_pHeightmap[iIndex] > 255.0f) m_pHeightmap[iIndex] = 255.0f; else if (m_pHeightmap[iIndex] < 0.0f) m_pHeightmap[iIndex] = 0.0f; } } // We modified the heightmap. SetModified(); } void CHeightmap::DrawSpot2( int iX, int iY, int radius,float insideRadius,float fHeight,float fHardness,bool bAddNoise,float noiseFreq,float noiseScale ) { //////////////////////////////////////////////////////////////////////// // Draw an attenuated spot on the map //////////////////////////////////////////////////////////////////////// int i, j; int iPosX, iPosY, iIndex; float fMaxDist, fAttenuation, fYSquared; float fCurHeight; if (GetIEditor()->IsUndoRecording()) GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) ); // Calculate the maximum distance fMaxDist = radius; for (j=(long) -radius; j m_iWidth - 1 || iPosY > m_iHeight - 1) continue; // Only circle. float dist = sqrtf(fYSquared + i*i); if (dist > fMaxDist) continue; // Calculate the array index iIndex = iPosX + iPosY * m_iWidth; // Calculate attenuation factor if (dist <= insideRadius) fAttenuation = 1.0f; else fAttenuation = 1.0f - __min(1.0f, (dist-insideRadius)/fMaxDist); // Set to height mode, modify the location towards the specified height fCurHeight = m_pHeightmap[iIndex]; float dh = fHeight - fCurHeight; float h = fCurHeight + (fAttenuation)*dh*fHardness; if (bAddNoise) { int nx = ftoi(iPosX*noiseFreq) % NOISE_ARRAY_SIZE; int ny = ftoi(iPosY*noiseFreq) % NOISE_ARRAY_SIZE; float noise = m_pNoise->m_Array[nx][ny]; h += (float)(noise)*fAttenuation*noiseScale; } // Clamp if (h > 255.0f) h = 255.0f; else if (h < 0.0f) h = 0.0f; m_pHeightmap[iIndex] = h; } } // We modified the heightmap. SetModified(); } void CHeightmap::RiseLowerSpot( int iX, int iY, int radius,float insideRadius,float fHeight,float fHardness,bool bAddNoise,float noiseFreq,float noiseScale ) { //////////////////////////////////////////////////////////////////////// // Draw an attenuated spot on the map //////////////////////////////////////////////////////////////////////// int i, j; int iPosX, iPosY, iIndex; float fMaxDist, fAttenuation, fYSquared; float fCurHeight; if (GetIEditor()->IsUndoRecording()) GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) ); // Calculate the maximum distance fMaxDist = radius; for (j=(long) -radius; j m_iWidth - 1 || iPosY > m_iHeight - 1) continue; // Only circle. float dist = sqrtf(fYSquared + i*i); if (dist > fMaxDist) continue; // Calculate the array index iIndex = iPosX + iPosY * m_iWidth; // Calculate attenuation factor if (dist <= insideRadius) fAttenuation = 1.0f; else fAttenuation = 1.0f - __min(1.0f, (dist-insideRadius)/fMaxDist); // Set to height mode, modify the location towards the specified height fCurHeight = m_pHeightmap[iIndex]; float dh = fHeight; float h = fCurHeight + (fAttenuation)*dh*fHardness; if (bAddNoise) { int nx = ftoi(iPosX*noiseFreq) % NOISE_ARRAY_SIZE; int ny = ftoi(iPosY*noiseFreq) % NOISE_ARRAY_SIZE; float noise = m_pNoise->m_Array[nx][ny]; h += (float)(noise)*fAttenuation*noiseScale; } // Clamp if (h > 255.0f) h = 255.0f; else if (h < 0.0f) h = 0.0f; m_pHeightmap[iIndex] = h; } } // We modified the heightmap. SetModified(); } void CHeightmap::SmoothSpot( int iX, int iY, int radius, float fHeight,float fHardness ) { //////////////////////////////////////////////////////////////////////// // Draw an attenuated spot on the map //////////////////////////////////////////////////////////////////////// int i, j; int iPosX, iPosY; float fMaxDist, fYSquared; if (GetIEditor()->IsUndoRecording()) GetIEditor()->RecordUndo( new CUndoHeightmapModify(iX-radius,iY-radius,radius*2,radius*2,this) ); // Calculate the maximum distance fMaxDist = radius; for (j=(long) -radius; j m_iHeight-2) continue; for (i=(long) -radius; i m_iWidth-2) continue; // Only circle. float dist = sqrtf(fYSquared + i*i); if (dist > fMaxDist) continue; int pos = iPosX + iPosY*m_iWidth; float h; h = (m_pHeightmap[pos] + m_pHeightmap[pos+1] + m_pHeightmap[pos-1] + m_pHeightmap[pos+m_iWidth] + m_pHeightmap[pos-m_iWidth] + m_pHeightmap[pos+1+m_iWidth] + m_pHeightmap[pos+1-m_iWidth] + m_pHeightmap[pos-1+m_iWidth] + m_pHeightmap[pos-1-m_iWidth]) / 9.0f; float currH = m_pHeightmap[pos]; m_pHeightmap[pos] = currH + (h-currH)*fHardness; } } // We modified the heightmap. SetModified(); } void CHeightmap::Hold() { //////////////////////////////////////////////////////////////////////// // Save a backup copy of the heightmap //////////////////////////////////////////////////////////////////////// FILE *hFile = NULL; CLogFile::WriteLine("Saving temporary copy of the heightmap"); AfxGetMainWnd()->BeginWaitCursor(); // Open the hold / fetch file hFile = fopen(HEIGHTMAP_HOLD_FETCH_FILE, "wb"); ASSERT(hFile); // Write the dimensions VERIFY(fwrite(&m_iWidth, sizeof(m_iWidth), 1, hFile)); VERIFY(fwrite(&m_iHeight, sizeof(m_iHeight), 1, hFile)); // Write the data VERIFY(fwrite(m_pHeightmap, sizeof(t_hmap), m_iWidth * m_iHeight, hFile)); //! Write the info. VERIFY(fwrite( m_info.GetData(),sizeof(unsigned char), m_info.GetSize(), hFile)); fclose(hFile); AfxGetMainWnd()->EndWaitCursor(); } void CHeightmap::Fetch() { //////////////////////////////////////////////////////////////////////// // Read a backup copy of the heightmap //////////////////////////////////////////////////////////////////////// CLogFile::WriteLine("Loading temporary copy of the heightmap"); AfxGetMainWnd()->BeginWaitCursor(); if (!Read(HEIGHTMAP_HOLD_FETCH_FILE)) { AfxMessageBox("You need to use 'Hold' before 'Fetch' !"); return; } AfxGetMainWnd()->EndWaitCursor(); } bool CHeightmap::Read(CString strFileName) { //////////////////////////////////////////////////////////////////////// // Load a heightmap from a file //////////////////////////////////////////////////////////////////////// FILE *hFile = NULL; UINT iWidth, iHeight; if (strFileName.IsEmpty()) return false; // Open the hold / fetch file hFile = fopen(strFileName.GetBuffer(0), "rb"); if (!hFile) return false; // Read the dimensions VERIFY(fread(&iWidth, sizeof(iWidth), 1, hFile)); VERIFY(fread(&iHeight, sizeof(iHeight), 1, hFile)); // Resize the heightmap Resize(iWidth, iHeight,m_unitSize); // Load the data VERIFY(fread(m_pHeightmap, sizeof(t_hmap), m_iWidth * m_iHeight, hFile)); //! Write the info. m_info.Allocate( m_iWidth,m_iHeight ); VERIFY(fread( m_info.GetData(),sizeof(unsigned char), m_info.GetSize(), hFile)); fclose(hFile); return true; } void CHeightmap::InitNoise() { //////////////////////////////////////////////////////////////////////// // Initialize the noise array //////////////////////////////////////////////////////////////////////// CNoise cNoise; static bool bFirstQuery = true; float fFrequency = 6.0f; float fFrequencyStep = 2.0f; float fYScale = 1.0f; float fFade = 0.46f; float fLowestPoint = 256000.0f, fHighestPoint = -256000.0f; float fValueRange; unsigned int i, j; assert(!m_pNoise); // Allocate a new array class to m_pNoise = new CDynamicArray2D(NOISE_ARRAY_SIZE, NOISE_ARRAY_SIZE); CLogFile::WriteLine("Initializing noise array..."); // Process layers for (i=0; i<8; i++) { // Apply the fractal noise function to the array cNoise.FracSynthPass(m_pNoise, fFrequency, fYScale, NOISE_ARRAY_SIZE, NOISE_ARRAY_SIZE, TRUE); // Modify noise generation parameters fFrequency *= fFrequencyStep; fYScale *= fFade; } // Find the value range for (i=0; im_Array[i][j]); fHighestPoint = __max(fHighestPoint, m_pNoise->m_Array[i][j]); } // Storing the value range in this way saves us a division and a multiplication fValueRange = 1.0f / (fHighestPoint + (float) fabs(fLowestPoint)) * 255.0f; // Normalize the heightmap for (i=0; im_Array[i][j] += (float) (fLowestPoint); m_pNoise->m_Array[i][j] *= fValueRange; // Keep signed / unsigned balance //m_pNoise->m_Array[i][j] += 2500.0f/255.0f; } } ////////////////////////////////////////////////////////////////////////// void CHeightmap::CalcSurfaceTypes( const CRect *rect ) { int i; bool first = true; CRect rc; CFloatImage hmap; hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight ); if (rect) rc = *rect; else rc.SetRect( 0,0,m_iWidth,m_iHeight ); unsigned char *pInfo = m_info.GetData(); // Generate the masks CCryEditDoc *doc = GetIEditor()->GetDocument(); int numLayers = doc->GetLayerCount(); for (i = 0; i < numLayers; i++) { CLayer *pLayer = doc->GetLayer(i); if (!pLayer->IsInUse()) continue; int sfType = doc->FindSurfaceType(pLayer->GetSurfaceType()); if (sfType < 0) continue; sfType = (sfType<GetMask(); if (!layerMask.IsValid()) continue; int layerWidth = layerMask.GetWidth(); int layerHeight = layerMask.GetHeight(); float xScale = (float)layerWidth / m_iWidth; float yScale = (float)layerHeight / m_iHeight; uchar *pLayerMask = layerMask.GetData(); // For every pixel of layer update surface type. for (uint y = rc.top; y < rc.bottom; y++) { int yp = y*m_iWidth; for (uint x = rc.left; x < rc.right; x++) { uchar a = pLayerMask[ftoi(x*xScale + layerWidth*y*yScale)]; if (a > OVVERIDE_LAYER_SURFACETYPE_FROM) { pInfo[x+yp] &= ~HEIGHTMAP_INFO_SFTYPE_MASK; pInfo[x+yp] |= sfType; } } } } } } void CHeightmap::SetLightingBit( CBitArray &lightbits,int w,int h ) { // We can only encode shadow and light informations into the // heightmap if a full-size lightmap has been created // Calculate the scale factor to scale from heightmap coordinates // into lightmap / surface texture coordinates float fScaleX = (float)w / m_iWidth; float fScaleY = (float)h / m_iHeight; // Encode the shadow / unshadowed bit into the heightmap data for (int j=0; j image; image.Allocate( w,h ); ushort *terrainBlock = image.GetData(); unsigned char *pInfo = m_info.GetData(); ushort terrUpdateFlags = 0; if (bInfoBits) { terrUpdateFlags = SURFACE_TYPE_MASK; // Only heightmap and surface type. for (int y = y1; y < y2; y++) { int yp = y*m_iWidth; for (int x = x1; x < x2; x++) { unsigned short *p = &terrainBlock[(y-y1)*w + (x-x1)]; // Must be 256 for compatability with engine. //*p = ftoi( m_pHeightmap[x + yp]*256.0f ); //m_info[x+yp] &= ~HEIGHTMAP_INFO_SFTYPE_MASK; unsigned char hinfo = pInfo[x+yp]; *p = ((hinfo&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT); if (hinfo&HEIGHTMAP_INFO_HOLE) { *p |= SURFACE_TYPE_MASK; } } } GetIEditor()->Get3DEngine()->SetTerainHightMapBlock( y1, x1, w,h, terrainBlock, terrUpdateFlags ); } if (bElevation) { terrUpdateFlags = 0xFFFF & (~31); // Only heightmap. for (int y = y1; y < y2; y++) { for (int x = x1; x < x2; x++) { unsigned short *p = &terrainBlock[(y-y1)*w + (x-x1)]; // Must be 256 for compatability with engine. *p = ftoi( m_pHeightmap[x + y*m_iWidth]*256.0f ); } } GetIEditor()->Get3DEngine()->SetTerainHightMapBlock( y1, x1, w,h, terrainBlock, terrUpdateFlags ); } } void CHeightmap::Serialize( CXmlArchive &xmlAr,bool bSerializeVegetation ) { if (xmlAr.bLoading) { // Loading XmlNodeRef heightmap = xmlAr.root->findChild( "Heightmap" ); if (!heightmap) return; heightmap->getAttr( "Width",m_iWidth ); heightmap->getAttr( "Height",m_iHeight ); heightmap->getAttr( "WaterLevel",m_fWaterLevel ); heightmap->getAttr( "UnitSize",m_unitSize ); int textureSize; if (heightmap->getAttr( "TextureSize",textureSize )) { SetSurfaceTextureSize(textureSize,textureSize); } void *pData; int size1,size2; // Allocate new memory Resize(m_iWidth, m_iHeight,m_unitSize); // Load heightmap data. if (xmlAr.pNamedData->GetDataBlock( "HeightmapDataW",pData,size1 )) { ushort *pSrc = (ushort*)pData; for (int i = 0; i < m_iWidth*m_iHeight; i++) { m_pHeightmap[i] = (float)pSrc[i] / 255.0f; } } else if (xmlAr.pNamedData->GetDataBlock( "HeightmapData",pData,size1 )) { // Backward compatability for float heigthmap data. memcpy( m_pHeightmap,pData,size1 ); } if (xmlAr.pNamedData->GetDataBlock( "HeightmapInfo",pData,size2 )) { memcpy( m_info.GetData(),pData,MAX(size2,m_info.GetSize()) ); } // After heightmap serialization, update terrain in 3D Engine. UpdateEngineTerrain(false); if (m_vegetationMap && bSerializeVegetation) { m_vegetationMap->Serialize( xmlAr ); } } else { // Storing XmlNodeRef heightmap = xmlAr.root->newChild( "Heightmap" ); heightmap->setAttr( "Width",m_iWidth ); heightmap->setAttr( "Height",m_iHeight ); heightmap->setAttr( "WaterLevel",m_fWaterLevel ); heightmap->setAttr( "UnitSize",m_unitSize ); heightmap->setAttr( "TextureSize",m_textureSize ); // Save heightmap data as words. //xmlAr.pNamedData->AddDataBlock( "HeightmapData",m_pHeightmap,m_iWidth * m_iHeight * sizeof(t_hmap) ); { CWordImage hdata; hdata.Allocate( m_iWidth,m_iHeight ); ushort *pTrg = hdata.GetData(); for (int i = 0; i < m_iWidth*m_iHeight; i++) { float val = m_pHeightmap[i]; // Clamp in the range 0-255 if (val > 255.0f) val = 255.0f; if (val < 0.0f) val = 0.0f; pTrg[i] = ftoi(val*255.0f); } xmlAr.pNamedData->AddDataBlock( "HeightmapDataW",hdata.GetData(),hdata.GetSize() ); } xmlAr.pNamedData->AddDataBlock( "HeightmapInfo",m_info.GetData(),m_info.GetSize() ); if (m_vegetationMap && bSerializeVegetation) { m_vegetationMap->Serialize( xmlAr ); } } } ////////////////////////////////////////////////////////////////////////// void CHeightmap::SerializeVegetation( CXmlArchive &xmlAr ) { if (m_vegetationMap) { m_vegetationMap->Serialize( xmlAr ); } } ////////////////////////////////////////////////////////////////////////// void CHeightmap::SetWaterLevel( float waterLevel ) { // We modified the heightmap. SetModified(); m_fWaterLevel = waterLevel; }; ////////////////////////////////////////////////////////////////////////// void CHeightmap::SetModified() { GetIEditor()->SetModifiedFlag(); m_cachedResolution = 0; m_cachedHeightmap.Free(); } ////////////////////////////////////////////////////////////////////////// // Make hole. void CHeightmap::MakeHole( int x1,int y1,int width,int height,bool bMake ) { if (GetIEditor()->IsUndoRecording()) GetIEditor()->RecordUndo( new CUndoHeightmapInfo(x1,y1,width+1,height+1,this) ); I3DEngine *engine = GetIEditor()->Get3DEngine(); int x2 = x1 + width; int y2 = y1 + height; for (int x = x1; x <= x2; x++) { for (int y = y1; y <= y2; y++) { if (bMake) { // Swap X/Y engine->SetTerrainSurfaceType( y*m_unitSize,x*m_unitSize,0xFFFFFFFF ); InfoAt(x,y) |= HEIGHTMAP_INFO_HOLE; } else { int sfType = (InfoAt(x,y)&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT; // Swap X/Y engine->SetTerrainSurfaceType( y*m_unitSize,x*m_unitSize,sfType ); InfoAt(x,y) &= ~HEIGHTMAP_INFO_HOLE; } } } } ////////////////////////////////////////////////////////////////////////// void CHeightmap::UpdateEngineHole( int x1,int y1,int width,int height ) { int x2 = x1 + width; int y2 = y1 + height; I3DEngine *engine = GetIEditor()->Get3DEngine(); for (int x = x1; x <= x2; x++) { for (int y = y1; y <= y2; y++) { if (InfoAt(x,y) & HEIGHTMAP_INFO_HOLE) { // Swap X/Y engine->SetTerrainSurfaceType( y*m_unitSize,x*m_unitSize,0xFFFFFFFF ); } else { int sfType = (InfoAt(x,y)&HEIGHTMAP_INFO_SFTYPE_MASK) >> HEIGHTMAP_INFO_SFTYPE_SHIFT; // Swap X/Y engine->SetTerrainSurfaceType( y*m_unitSize,x*m_unitSize,sfType ); } } } } ////////////////////////////////////////////////////////////////////////// CVegetationMap* CHeightmap::GetVegetationMap() { return m_vegetationMap; } ////////////////////////////////////////////////////////////////////////// void CHeightmap::GetSectorsInfo( SSectorInfo &si ) { ZeroStruct(si); si.unitSize = m_unitSize; si.sectorSize = SECTOR_SIZE_IN_METERS; si.numSectors = m_numSectors; si.sectorTexSize = m_textureSize / si.numSectors; si.surfaceTextureSize = m_textureSize; } ////////////////////////////////////////////////////////////////////////// void CHeightmap::SetSurfaceTextureSize( int width,int height ) { assert( width == height ); if (width != 0) { m_textureSize = width; } m_terrainGrid->SetResolution( m_textureSize ); } /* ////////////////////////////////////////////////////////////////////////// void CHeightmap::MoveBlock( const CRect &rc,CPoint offset ) { CRect hrc(0,0,m_iWidth,m_iHeight ); CRect subRc = rc & hrc; CRect trgRc = rc; trgRc.OffsetRect(offset); trgRc &= hrc; if (subRc.IsRectEmpty() || trgRc.IsRectEmpty()) return; if (CUndo::IsRecording()) { // Must be square. int size = (trgRc.Width() > trgRc.Height()) ? trgRc.Width() : trgRc.Height(); CUndo::Record( new CUndoHeightmapModify(trgRc.left,trgRc.top,size,size,this) ); } CFloatImage hmap; CFloatImage hmapSubImage; hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight ); hmapSubImage.Allocate( subRc.Width(),subRc.Height() ); hmap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),hmapSubImage ); hmap.SetSubImage( trgRc.left,trgRc.top,hmapSubImage ); CByteImage infoMap; CByteImage infoSubImage; infoMap.Attach( &m_info[0],m_iWidth,m_iHeight ); infoSubImage.Allocate( subRc.Width(),subRc.Height() ); infoMap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),infoSubImage ); infoMap.SetSubImage( trgRc.left,trgRc.top,infoSubImage ); // Move Vegetation. if (m_vegetationMap) { Vec3 p1 = HmapToWorld(CPoint(subRc.left,subRc.top)); Vec3 p2 = HmapToWorld(CPoint(subRc.right,subRc.bottom)); Vec3 ofs = HmapToWorld(offset); CRect worldRC( p1.x,p1.y,p2.x,p2.y ); // Export and import to block. CXmlArchive ar("Root"); ar.bLoading = false; m_vegetationMap->ExportBlock( worldRC,ar ); ar.bLoading = true; m_vegetationMap->ImportBlock( ar,CPoint(ofs.x,ofs.y) ); } } */ ////////////////////////////////////////////////////////////////////////// int CHeightmap::LogLayerSizes() { int totalSize = 0; CCryEditDoc *doc = GetIEditor()->GetDocument(); int numLayers = doc->GetLayerCount(); for (int i = 0; i < numLayers; i++) { CLayer *pLayer = doc->GetLayer(i); int layerSize = pLayer->GetSize(); totalSize += layerSize; CLogFile::FormatLine( "Layer %s: %dM",(const char*)pLayer->GetLayerName(),layerSize/(1024*1024) ); } CLogFile::FormatLine( "Total Layers Size: %dM",totalSize/(1024*1024) ); return totalSize; } ////////////////////////////////////////////////////////////////////////// void CHeightmap::ExportBlock( const CRect &inrect,CXmlArchive &xmlAr ) { // Storing CLogFile::WriteLine("Exporting Heightmap settings..."); XmlNodeRef heightmap = xmlAr.root->newChild( "Heightmap" ); CRect subRc( 0,0,m_iWidth,m_iHeight ); subRc &= inrect; heightmap->setAttr( "Width",m_iWidth ); heightmap->setAttr( "Height",m_iHeight ); // Save rectangle dimensions to root of terrain block. xmlAr.root->setAttr( "X1",subRc.left ); xmlAr.root->setAttr( "Y1",subRc.top ); xmlAr.root->setAttr( "X2",subRc.right ); xmlAr.root->setAttr( "Y2",subRc.bottom ); // Rectangle. heightmap->setAttr( "X1",subRc.left ); heightmap->setAttr( "Y1",subRc.top ); heightmap->setAttr( "X2",subRc.right ); heightmap->setAttr( "Y2",subRc.bottom ); heightmap->setAttr( "UnitSize",m_unitSize ); CFloatImage hmap; CFloatImage hmapSubImage; hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight ); hmapSubImage.Allocate( subRc.Width(),subRc.Height() ); hmap.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),hmapSubImage ); CByteImage infoSubImage; infoSubImage.Allocate( subRc.Width(),subRc.Height() ); m_info.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),infoSubImage ); // Save heightmap. xmlAr.pNamedData->AddDataBlock( "HeightmapData",hmapSubImage.GetData(),hmapSubImage.GetSize() ); xmlAr.pNamedData->AddDataBlock( "HeightmapInfo",infoSubImage.GetData(),infoSubImage.GetSize() ); Vec3 p1 = HmapToWorld(CPoint(subRc.left,subRc.top)); Vec3 p2 = HmapToWorld(CPoint(subRc.right,subRc.bottom)); if (m_vegetationMap) { CRect worldRC( p1.x,p1.y,p2.x,p2.y ); m_vegetationMap->ExportBlock( worldRC,xmlAr ); } SSectorInfo si; GetSectorsInfo(si); float pixelsPerMeter = ((float)si.sectorTexSize) / si.sectorSize; CRect layerRc; layerRc.left = subRc.left*si.unitSize * pixelsPerMeter; layerRc.top = subRc.top*si.unitSize * pixelsPerMeter; layerRc.right = subRc.right*si.unitSize * pixelsPerMeter; layerRc.bottom = subRc.bottom*si.unitSize * pixelsPerMeter; XmlNodeRef layers = xmlAr.root->newChild( "Layers" ); // Export layers settings. CCryEditDoc *doc = GetIEditor()->GetDocument(); for (int i = 0; i < doc->GetLayerCount(); i++) { CLayer *layer = doc->GetLayer(i); if (layer->IsAutoGen()) continue; CXmlArchive ar( xmlAr ); ar.root = layers->newChild( "Layer" ); layer->ExportBlock( layerRc,ar ); } } ////////////////////////////////////////////////////////////////////////// CPoint CHeightmap::ImportBlock( CXmlArchive &xmlAr,CPoint newPos,bool bUseNewPos ) { CLogFile::WriteLine("Importing Heightmap settings..."); XmlNodeRef heightmap = xmlAr.root->findChild( "Heightmap" ); if (!heightmap) return CPoint(0,0); UINT width,height; heightmap->getAttr( "Width",width ); heightmap->getAttr( "Height",height ); CPoint offset(0,0); if (width != m_iWidth || height != m_iHeight) { MessageBox( AfxGetMainWnd()->GetSafeHwnd(),_T("Terrain Block dimensions differ from current terrain."),_T("Warning"),MB_OK|MB_ICONEXCLAMATION ); return CPoint(0,0); } CRect subRc; // Rectangle. heightmap->getAttr( "X1",subRc.left ); heightmap->getAttr( "Y1",subRc.top ); heightmap->getAttr( "X2",subRc.right ); heightmap->getAttr( "Y2",subRc.bottom ); if (bUseNewPos) { offset = CPoint( newPos.x-subRc.left,newPos.y-subRc.top ); subRc.OffsetRect( offset ); } void *pData; int size; // Load heightmap data. if (xmlAr.pNamedData->GetDataBlock( "HeightmapData",pData,size )) { // Backward compatability for float heigthmap data. CFloatImage hmap; CFloatImage hmapSubImage; hmap.Attach( m_pHeightmap,m_iWidth,m_iHeight ); hmapSubImage.Attach( (float*)pData,subRc.Width(),subRc.Height() ); hmap.SetSubImage( subRc.left,subRc.top,hmapSubImage ); } if (xmlAr.pNamedData->GetDataBlock( "HeightmapInfo",pData,size )) { CByteImage infoSubImage; infoSubImage.Attach( (unsigned char*)pData,subRc.Width(),subRc.Height() ); m_info.SetSubImage( subRc.left,subRc.top,infoSubImage ); } // After heightmap serialization, update terrain in 3D Engine. UpdateEngineTerrain( subRc.left-1,subRc.top-1,subRc.Width()+2,subRc.Height()+2,true,true ); if (m_vegetationMap) { Vec3 ofs = HmapToWorld(offset); m_vegetationMap->ImportBlock( xmlAr,CPoint(ofs.x,ofs.y) ); } // Import layers. XmlNodeRef layers = xmlAr.root->findChild( "Layers" ); if (layers) { SSectorInfo si; GetSectorsInfo(si); float pixelsPerMeter = ((float)si.sectorTexSize) / si.sectorSize; CPoint layerOffset; layerOffset.x = offset.x*si.unitSize * pixelsPerMeter; layerOffset.y = offset.y*si.unitSize * pixelsPerMeter; // Export layers settings. CCryEditDoc *doc = GetIEditor()->GetDocument(); for (int i = 0; i < layers->getChildCount(); i++) { CXmlArchive ar( xmlAr ); ar.root = layers->getChild(i); CString layerName; if (!ar.root->getAttr( "Name",layerName )) continue; CLayer *layer = doc->FindLayer( layerName ); if (!layer) continue; layer->ImportBlock( ar,layerOffset ); } } return offset; } ////////////////////////////////////////////////////////////////////////// void CHeightmap::CopyFrom( t_hmap *pHmap,unsigned char *pInfo,int resolution ) { int x,y; int res = min(resolution,m_iWidth); for (y = 0; y < res; y++) { for (x = 0; x < res; x++) { SetXY( x,y, pHmap[x + y*resolution] ); InfoAt( x,y ) = pInfo[x + y*resolution]; } } }