// Layer.cpp: implementation of the CLayer class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Layer.h" #include "Heightmap.h" #include "TerrainGrid.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif //! Size of the texture preview #define LAYER_TEX_PREVIEW_CX 128 //! Size of the texture preview #define LAYER_TEX_PREVIEW_CY 128 #define MAX_TEXTURE_SIZE (1024*1024*2) #define DEFAULT_MASK_RESOLUTION 4096 // Static member variables UINT CLayer::m_iInstanceCount = 0; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CLayer::CLayer() { //////////////////////////////////////////////////////////////////////// // Initialize member variables //////////////////////////////////////////////////////////////////////// // One more instance m_iInstanceCount++; m_bAutoGen = true; m_bNoise = true; m_noiseSize = 0; m_bSmooth = true; // Create a layer name based on the instance count m_strLayerName.Format("Layer %i", m_iInstanceCount); // Init member variables m_iLayerStart = 0; m_iLayerEnd = 255; m_strLayerTexPath = ""; m_cTextureDimensions.cx = 0; m_cTextureDimensions.cy = 0; m_minSlope = 0; m_maxSlope = 255; m_bNeedUpdate = true; m_bCompressedMaskValid = false; // Create the DCs m_dcLayerTexPrev.CreateCompatibleDC(NULL); // Create the bitmap VERIFY(m_bmpLayerTexPrev.CreateBitmap(LAYER_TEX_PREVIEW_CX, LAYER_TEX_PREVIEW_CX, 1, 32, NULL)); m_dcLayerTexPrev.SelectObject(&m_bmpLayerTexPrev); // Make sure the layer mask is updated the first time m_currWidth = m_currHeight = 0; // Layer is used m_bLayerInUse = true; m_bSelected = false; m_numSectors = 0; AllocateMaskGrid(); } CLayer::~CLayer() { //////////////////////////////////////////////////////////////////////// // Clean up on exit //////////////////////////////////////////////////////////////////////// m_iInstanceCount--; // Make sure the DCs are freed correctly m_dcLayerTexPrev.SelectObject((CBitmap *) NULL); // Free layer mask data m_layerMask.Release(); } CString CLayer::GetTextureFilename() { return CString(PathFindFileName(LPCTSTR(m_strLayerTexPath))); }; ////////////////////////////////////////////////////////////////////////// void CLayer::DrawLayerTexturePreview(LPRECT rcPos, CDC *pDC) { //////////////////////////////////////////////////////////////////////// // Draw the preview of the layer texture //////////////////////////////////////////////////////////////////////// ASSERT(rcPos); ASSERT(pDC); CBrush brshGray; if (m_bmpLayerTexPrev.m_hObject) { pDC->SetStretchBltMode(HALFTONE); pDC->StretchBlt(rcPos->left, rcPos->top, rcPos->right - rcPos->left, rcPos->bottom - rcPos->top, &m_dcLayerTexPrev, 0, 0, LAYER_TEX_PREVIEW_CX, LAYER_TEX_PREVIEW_CY, SRCCOPY); } else { brshGray.CreateSysColorBrush(COLOR_BTNFACE); pDC->FillRect(rcPos, &brshGray); } } void CLayer::Serialize( CXmlArchive &xmlAr ) { //////////////////////////////////////////////////////////////////////// // Save or restore the class //////////////////////////////////////////////////////////////////////// if (xmlAr.bLoading) { // We need an update InvalidateMask(); //////////////////////////////////////////////////////////////////////// // Loading //////////////////////////////////////////////////////////////////////// CLogFile::WriteLine("Loading layer..."); XmlNodeRef layer = xmlAr.root; layer->getAttr( "Name",m_strLayerName ); // Texture layer->getAttr( "Texture",m_strLayerTexPath ); layer->getAttr( "TextureWidth",m_cTextureDimensions.cx ); layer->getAttr( "TextureHeight",m_cTextureDimensions.cy ); // Parameters (Altitude, Slope...) layer->getAttr( "AltStart",m_iLayerStart ); layer->getAttr( "AltEnd",m_iLayerEnd ); layer->getAttr( "MinSlope",m_minSlope ); layer->getAttr( "MaxSlope",m_maxSlope ); // In use flag layer->getAttr( "InUse",m_bLayerInUse ); layer->getAttr( "AutoGenMask",m_bAutoGen ); layer->getAttr( "Noise",m_bNoise ); layer->getAttr( "NoiseSize",m_noiseSize ); layer->getAttr( "Smooth",m_bSmooth ); layer->getAttr( "SurfaceType",m_surfaceType ); /* if (!m_strLayerTexPath.IsEmpty()) { if (!LoadTexture( m_strLayerTexPath )) { CLogFile::FormatLine("Can't load layer texture data %s!",(const char*)m_strLayerTexPath ); } } */ void *pData; int nSize; if (xmlAr.pNamedData->GetDataBlock( CString("Layer_")+m_strLayerName,pData,nSize )) { // Load it if (!LoadTexture( (DWORD*)pData,m_cTextureDimensions.cx, m_cTextureDimensions.cy)) { CLogFile::WriteLine("Can't load layer texture data !"); AfxMessageBox("Critical error while loading texture from layer !"); } } else { // Try loading texture from external file, if (!LoadTexture( m_strLayerTexPath)) { CString str; str.Format( "Can't find or load layer texture: %s",(const char*)m_strLayerTexPath ); CLogFile::WriteLine( str ); AfxMessageBox( str ); } } if (!m_bAutoGen) { int maskWidth=0,maskHeight=0; layer->getAttr( "MaskWidth",maskWidth ); layer->getAttr( "MaskHeight",maskHeight ); m_maskResolution = maskWidth; if (m_maskResolution == 0) m_maskResolution = GetNativeMaskResolution(); bool bCompressed = true; CMemoryBlock *memBlock = xmlAr.pNamedData->GetDataBlock( CString("LayerMask_")+m_strLayerName,bCompressed ); if (memBlock) { m_compressedMask = *memBlock; m_bCompressedMaskValid = true; } else { // No compressed block, fallback to back-compatability mode. if (xmlAr.pNamedData->GetDataBlock( CString("LayerMask_")+m_strLayerName,pData,nSize )) { CByteImage mask; mask.Attach( (unsigned char*)pData,maskWidth,maskHeight ); if (maskWidth == DEFAULT_MASK_RESOLUTION) { m_layerMask.Allocate(m_maskResolution,m_maskResolution); m_layerMask.Copy(mask); } else { GenerateLayerMask( mask,m_maskResolution,m_maskResolution ); } } } } } else { //////////////////////////////////////////////////////////////////////// // Storing //////////////////////////////////////////////////////////////////////// CLogFile::WriteLine("Storing layer..."); XmlNodeRef layer = xmlAr.root; // Name layer->setAttr( "Name",m_strLayerName ); // Texture layer->setAttr( "Texture",m_strLayerTexPath ); layer->setAttr( "TextureWidth",m_cTextureDimensions.cx ); layer->setAttr( "TextureHeight",m_cTextureDimensions.cy ); // Parameters (Altitude, Slope...) layer->setAttr( "AltStart",m_iLayerStart ); layer->setAttr( "AltEnd",m_iLayerEnd ); layer->setAttr( "MinSlope",m_minSlope ); layer->setAttr( "MaxSlope",m_maxSlope ); // In use flag layer->setAttr( "InUse",m_bLayerInUse ); // Auto mask or explicit mask. layer->setAttr( "AutoGenMask",m_bAutoGen ); layer->setAttr( "Noise",m_bNoise ); layer->setAttr( "NoiseSize",m_noiseSize ); layer->setAttr( "Smooth",m_bSmooth ); layer->setAttr( "SurfaceType",m_surfaceType ); int layerTexureSize = m_cTextureDimensions.cx*m_cTextureDimensions.cy*sizeof(DWORD); if (layerTexureSize <= MAX_TEXTURE_SIZE) { PrecacheTexture(); xmlAr.pNamedData->AddDataBlock( CString("Layer_")+m_strLayerName,m_texture.GetData(),m_texture.GetSize() ); } if (!m_bAutoGen) { //xmlAr.pNamedData->AddDataBlock( CString("LayerMask_")+m_strLayerName, m_maskImage.GetData(),m_maskImage.GetSize() ); //CMemoryBlock mem; //SaveMaskGrid( mem ); //xmlAr.pNamedData->AddDataBlock( CString("LayerMaskGrid_")+m_strLayerName,mem.GetBuffer(),mem.GetSize() ); ////////////////////////////////////////////////////////////////////////// // Save old stuff... ////////////////////////////////////////////////////////////////////////// layer->setAttr( "MaskWidth",m_maskResolution ); layer->setAttr( "MaskHeight",m_maskResolution ); if (m_maskFile.IsEmpty()) layer->setAttr( "MaskFileName",m_maskFile ); /* if (m_maskImage.GetSize() > 1024*1024) { // If mask is too big only store mask file name. layer->setAttr( "MaskFileName",m_maskFile ); } else { xmlAr.pNamedData->AddDataBlock( CString("LayerMask_")+m_strLayerName, m_maskImage.GetData(),m_maskImage.GetSize() ); } */ if (!m_bCompressedMaskValid && m_layerMask.IsValid()) { CompressMask(); } if (m_bCompressedMaskValid) { // Store uncompressed block of data. xmlAr.pNamedData->AddDataBlock( CString("LayerMask_")+m_strLayerName, m_compressedMask ); } else { // no mask. } /* if (m_layerMask.IsValid()) { xmlAr.pNamedData->AddDataBlock( CString("LayerMask_")+m_strLayerName, m_layerMask.GetData(),m_layerMask.GetSize() ); */ ////////////////////////////////////////////////////////////////////////// } } } ////////////////////////////////////////////////////////////////////////// void CLayer::SetAutoGen( bool bAutoGen ) { bool prev = m_bAutoGen; m_bAutoGen = bAutoGen; if (prev != m_bAutoGen) { InvalidateMask(); if (!m_bAutoGen) { m_maskResolution = GetNativeMaskResolution(); // Not autogenerated layer must keep its mask. m_layerMask.Allocate( m_maskResolution,m_maskResolution ); m_layerMask.Clear(); SetAllSectorsValid(); } else { // Release layer mask. m_layerMask.Release(); InvalidateAllSectors(); } } }; void CLayer::FillWithColor( COLORREF col,int width,int height ) { m_cTextureDimensions = CSize(width, height); // Allocate new memory to hold the bitmap data m_cTextureDimensions = CSize(width,height); m_texture.Allocate( width,height ); uint *pTex = m_texture.GetData(); for (int i = 0; i < width * height; i++) { *pTex++ = col; } } bool CLayer::LoadTexture(LPCTSTR lpBitmapName, UINT iWidth, UINT iHeight) { //////////////////////////////////////////////////////////////////////// // Load a layer texture out of a ressource //////////////////////////////////////////////////////////////////////// CBitmap bmpLoad; BOOL bReturn; ASSERT(lpBitmapName); ASSERT(iWidth); ASSERT(iHeight); // Load the bitmap bReturn = bmpLoad.Attach(::LoadBitmap( AfxGetInstanceHandle(),lpBitmapName)); if (!bReturn) { ASSERT(bReturn); return false; } // Save the bitmap's dimensions m_cTextureDimensions = CSize(iWidth, iHeight); // Free old tetxure data // Allocate new memory to hold the bitmap data m_cTextureDimensions = CSize(iWidth,iHeight); m_texture.Allocate(iWidth,iHeight); // Retrieve the bits from the bitmap VERIFY(bmpLoad.GetBitmapBits(m_texture.GetSize(),m_texture.GetData() )); // Convert from BGR tp RGB BGRToRGB(); return true; } inline bool IsPower2( int n ) { for (int i = 0; i < 30; i++) { if (n == (1<GetRelativePath(strFileName); if (m_strLayerTexPath.IsEmpty()) m_strLayerTexPath = strFileName; if (!CImageUtil::LoadImage( m_strLayerTexPath,m_texture )) { CLogFile::FormatLine("Error loading layer texture (%s)...", (const char*)m_strLayerTexPath ); m_cTextureDimensions.cx = 0; m_cTextureDimensions.cy = 0; return false; } if (!IsPower2(m_texture.GetWidth()) && !IsPower2(m_texture.GetHeight())) { Warning( "Selected Layer Texture must have power of 2 size." ); m_strLayerTexPath = ""; m_texture.Allocate( 4,4 ); m_cTextureDimensions = CSize(m_texture.GetWidth(),m_texture.GetHeight()); return false; } // Store the size m_cTextureDimensions = CSize(m_texture.GetWidth(),m_texture.GetHeight()); CBitmap bmpLoad; CDC dcLoad; // Create the DC for blitting from the loaded bitmap VERIFY(dcLoad.CreateCompatibleDC(NULL)); CImage inverted; inverted.Allocate( m_texture.GetWidth(),m_texture.GetHeight() ); for (int y = 0; y < m_texture.GetHeight(); y++) { for (int x = 0; x < m_texture.GetWidth(); x++) { uint val = m_texture.ValueAt(x,y); inverted.ValueAt(x,y) = 0xFF000000 | RGB( GetBValue(val),GetGValue(val),GetRValue(val) ); } } bmpLoad.CreateBitmap(m_texture.GetWidth(),m_texture.GetHeight(), 1, 32, inverted.GetData() ); //bmpLoad.SetBitmapBits( m_texture.GetSize(),m_texture.GetData() ); // Select it into the DC dcLoad.SelectObject(&bmpLoad); // Copy it to the preview bitmap m_dcLayerTexPrev.SetStretchBltMode(COLORONCOLOR); m_dcLayerTexPrev.StretchBlt(0, 0, LAYER_TEX_PREVIEW_CX, LAYER_TEX_PREVIEW_CY, &dcLoad, 0, 0, m_texture.GetWidth(),m_texture.GetHeight(), SRCCOPY); dcLoad.DeleteDC(); // Convert from BGR tp RGB //BGRToRGB(); return true; } bool CLayer::LoadTexture(DWORD *pBitmapData, UINT iWidth, UINT iHeight) { //////////////////////////////////////////////////////////////////////// // Load a texture from an array into the layer //////////////////////////////////////////////////////////////////////// CDC dcLoad; CBitmap bmpLoad; DWORD *pPixData = NULL, *pPixDataEnd = NULL; if (iWidth == 0 || iHeight == 0) { ASSERT(0); return false; } // Allocate new memory to hold the bitmap data m_cTextureDimensions = CSize(iWidth,iHeight); m_texture.Allocate(iWidth,iHeight); // Copy the image data into the layer memcpy( m_texture.GetData(), pBitmapData, m_texture.GetSize() ); //////////////////////////////////////////////////////////////////////// // Generate the texture preview //////////////////////////////////////////////////////////////////////// // Set the loop pointers pPixData = pBitmapData; pPixDataEnd = &pBitmapData[GetTextureWidth() * GetTextureHeight()]; // Switch R and B while (pPixData != pPixDataEnd) { // Extract the bits, shift them, put them back and advance to the next pixel *pPixData++ = ((* pPixData & 0x00FF0000) >> 16) | (* pPixData & 0x0000FF00) | ((* pPixData & 0x000000FF) << 16); } // Create the DC for blitting from the loaded bitmap VERIFY(dcLoad.CreateCompatibleDC(NULL)); // Create the matching bitmap if (!bmpLoad.CreateBitmap(iWidth, iHeight, 1, 32, pBitmapData)) { ASSERT(FALSE); return false; } // Select it into the DC dcLoad.SelectObject(&bmpLoad); // Copy it to the preview bitmap m_dcLayerTexPrev.SetStretchBltMode(COLORONCOLOR); m_dcLayerTexPrev.StretchBlt(0, 0, LAYER_TEX_PREVIEW_CX, LAYER_TEX_PREVIEW_CY, &dcLoad, 0, 0, iWidth, iHeight, SRCCOPY); return true; } bool CLayer::LoadMask( const CString &strFileName ) { assert( m_layerMask.IsValid() ); if (!m_layerMask.IsValid()) return false; //////////////////////////////////////////////////////////////////////// // Load a BMP texture into the layer mask. //////////////////////////////////////////////////////////////////////// CLogFile::FormatLine("Loading mask texture (%s)...", (const char*)strFileName ); m_maskFile = GetIEditor()->GetRelativePath(strFileName); if (m_maskFile.IsEmpty()) m_maskFile = strFileName; // Load the bitmap CImage maskRGBA; if (!CImageUtil::LoadImage( m_maskFile,maskRGBA )) { CLogFile::FormatLine("Error loading layer mask (%s)...", (const char*)m_maskFile ); return false; } CByteImage mask; mask.Allocate( maskRGBA.GetWidth(),maskRGBA.GetHeight() ); unsigned char *dest = mask.GetData(); unsigned int *src = maskRGBA.GetData(); int size = maskRGBA.GetWidth()*maskRGBA.GetHeight(); for (int i = 0; i < size; i++) { *dest = *src & 0xFF; src++; dest++; } GenerateLayerMask( mask,m_layerMask.GetWidth(),m_layerMask.GetHeight() ); return true; } ////////////////////////////////////////////////////////////////////////// void CLayer::GenerateLayerMask( CByteImage &mask,int width,int height ) { // Mask is not valid anymore. InvalidateMask(); // Check if mask image is same size. if (width == mask.GetWidth() && height == mask.GetHeight()) { m_layerMask.Copy( mask ); return; } if (m_layerMask.GetWidth() != width || m_layerMask.GetHeight() != height) { // Allocate new memory for the layer mask m_layerMask.Allocate(width,height); } // Scale mask image to the size of destination layer mask. CImageUtil::ScaleToFit( mask,m_layerMask ); m_currWidth = width; m_currHeight = height; if (m_layerMask.GetWidth() != mask.GetWidth() || m_layerMask.GetHeight() != mask.GetHeight()) { if (m_bSmooth) { CImageUtil::SmoothImage( m_layerMask,2 ); } } // All sectors of mask are valid. SetAllSectorsValid(); // Free compressed mask. m_compressedMask.Free(); m_bCompressedMaskValid = false; } void CLayer::UpdateLayerMask16(float *pHeightmapPixels, UINT iHeightmapWidth, UINT iHeightmapHeight, bool bStreamFromDisk) { //////////////////////////////////////////////////////////////////////// // Update the layer mask. The heightmap bits are supplied for speed // reasons, repeated memory allocations during batch generations of // layers are to slow //////////////////////////////////////////////////////////////////////// PrecacheTexture(); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// return; uchar *pData = NULL; uchar *pArrayEnd = NULL; DWORD *pPreviewData = NULL; DWORD *pPreviewDataStart = NULL; unsigned int i, j; long iCurPos; float hVal = 0.0f; FILE *hLayerFile = NULL; CLogFile::WriteLine("Updating layer mask for this layer..."); // Only proceed when the mask needs an update if (m_bNeedUpdate == false && m_currWidth == iHeightmapWidth && m_currHeight == iHeightmapHeight) return; if (!IsAutoGen()) { /* if (m_maskImage.IsValid()) { GenerateLayerMask( m_maskImage,iHeightmapWidth,iHeightmapHeight ); m_currWidth = iHeightmapWidth; m_currHeight = iHeightmapHeight; m_bNeedUpdate = false; return; } */ } // Delete the old layer mask if (!pHeightmapPixels) return; // Allocate new memory for the layer mask if (m_layerMask.GetWidth() != iHeightmapWidth) { m_layerMask.Allocate( iHeightmapWidth,iHeightmapHeight ); m_layerMask.Clear(); InvalidateAllSectors(); } // Set the loop pointers uchar *pLayerMask = m_layerMask.GetData(); pData = &pLayerMask[iHeightmapWidth + 1]; pArrayEnd = &pData[(iHeightmapWidth - 1) * (iHeightmapHeight - 1)]; pHeightmapPixels = &pHeightmapPixels[iHeightmapWidth + 1]; // We need constant random numbers srand(0); float heightmapCompensate = (iHeightmapWidth / 128.0f) * 255.0f; float MinAltitude = m_iLayerStart; float MaxAltitude = m_iLayerEnd; int MinSlope = ftoi(m_minSlope*255.0f); int MaxSlope = ftoi(m_maxSlope*255.0f); // Scan the heightmap for pixels that belong to this layer while (pData != pArrayEnd) { // Get the height value from the heightmap hVal = *pHeightmapPixels; if (hVal < MinAltitude || hVal > MaxAltitude) { // Advance to the next heightmap value pHeightmapPixels++; *pData++ = 0; continue; } // Calculate the slope for this point float fs = (fabs((* (pHeightmapPixels + 1)) - hVal) + fabs((* (pHeightmapPixels - 1)) - hVal) + fabs((* (pHeightmapPixels + iHeightmapWidth)) - hVal) + fabs((* (pHeightmapPixels - iHeightmapWidth)) - hVal) + fabs((* (pHeightmapPixels + iHeightmapWidth + 1)) - hVal) + fabs((* (pHeightmapPixels - iHeightmapWidth - 1)) - hVal) + fabs((* (pHeightmapPixels + iHeightmapWidth - 1)) - hVal) + fabs((* (pHeightmapPixels - iHeightmapWidth + 1)) - hVal)); // Compensate the smaller slope for bigger heightfields int slope = ftoi(fs*heightmapCompensate); // Normalize the range to 0 - 255*255 if (slope > 255*255) slope = 255*255; // Check if the current point belongs to the layer if (slope >= MinSlope && slope <= MaxSlope) { *pData = 0xFF; } else { *pData = 0; } /* // Modify it by a random value if (*pData && m_bNoise) { *pData -= (unsigned char) (FloatToIntRet((float) rand() / (float) RAND_MAX * iBlendFactor)); } */ // Advance to the next heightmap value pHeightmapPixels++; *pData++; } CLogFile::WriteLine("Smoothing layer mask..."); if (m_bSmooth) { // Smooth the layer for (j=1; j> 16) | (* pPixData & 0x0000FF00) | ((* pPixData & 0x000000FF) << 16); } } void CLayer::ExportTexture(CString strFileName) { //////////////////////////////////////////////////////////////////////// // Save the texture data of this layer into a BMP file //////////////////////////////////////////////////////////////////////// DWORD *pTempBGR = NULL; DWORD *pPixData = NULL, *pPixDataEnd = NULL; CLogFile::WriteLine("Exporting texture from layer data to BMP..."); PrecacheTexture(); if (!m_texture.IsValid()) return; // Make a copy of the layer data CImage image; image.Allocate(GetTextureWidth(),GetTextureHeight()); pTempBGR = (DWORD*)image.GetData(); memcpy(pTempBGR, m_texture.GetData(),m_texture.GetSize()); // Set the loop pointers pPixData = pTempBGR; pPixDataEnd = &pTempBGR[GetTextureWidth() * GetTextureHeight()]; /* // Switch R and B while (pPixData != pPixDataEnd) { // Extract the bits, shift them, put them back and advance to the next pixel *pPixData++ = ((* pPixData & 0x00FF0000) >> 16) | (* pPixData & 0x0000FF00) | ((* pPixData & 0x000000FF) << 16); } */ // Write the bitmap CImageUtil::SaveImage( strFileName,image ); } void CLayer::ExportMask( const CString &strFileName ) { //////////////////////////////////////////////////////////////////////// // Save the texture data of this layer into a BMP file //////////////////////////////////////////////////////////////////////// DWORD *pTempBGR = NULL; CLogFile::WriteLine("Exporting layer mask to BMP..."); CByteImage layerMask; uchar *pLayerMask = NULL; int w,h; if (m_bAutoGen) { int nativeResolution = GetNativeMaskResolution(); w = h = nativeResolution; // Create mask at native texture resolution. CRect rect( 0,0,nativeResolution,nativeResolution ); CFloatImage hmap; hmap.Allocate( nativeResolution,nativeResolution ); layerMask.Allocate( nativeResolution,nativeResolution ); // Get hmap at native resolution. GetIEditor()->GetHeightmap()->GetData( rect,hmap,true,true ); AutogenLayerMask( rect,hmap,layerMask ); pLayerMask = layerMask.GetData(); } else { PrecacheMask(); pLayerMask = m_layerMask.GetData(); w = m_layerMask.GetWidth(); h = m_layerMask.GetHeight(); } if (w == 0 || h == 0 || pLayerMask == 0) { AfxMessageBox( "Cannot export empty mask" ); return; } // Make a copy of the layer data CImage image; image.Allocate(w,h); pTempBGR = (DWORD*)image.GetData(); for (int i = 0; i < w*h; i++) { uint col = pLayerMask[i]; pTempBGR[i] = col | col << 8 | col << 16; } // Write the mask CImageUtil::SaveImage( strFileName,image ); } //! Release allocated mask. void CLayer::ReleaseMask() { // Free layer mask data if (IsAutoGen()) m_layerMask.Release(); else { //CompressMask(); } m_currWidth = 0; m_currHeight = 0; // If texture image is too big, release it. if (m_texture.GetSize() > MAX_TEXTURE_SIZE) m_texture.Release(); } void CLayer::PrecacheTexture() { if (m_texture.IsValid()) return; if (m_strLayerTexPath.IsEmpty()) { m_cTextureDimensions = CSize(4,4); m_texture.Allocate(m_cTextureDimensions.cx,m_cTextureDimensions.cy); } else if (!CImageUtil::LoadImage( m_strLayerTexPath,m_texture )) { Error( "Error loading layer texture (%s)...", (const char*)m_strLayerTexPath ); m_cTextureDimensions = CSize(4,4); m_texture.Allocate(m_cTextureDimensions.cx,m_cTextureDimensions.cy); return; } m_cTextureDimensions.cx = m_texture.GetWidth(); m_cTextureDimensions.cx = m_texture.GetHeight(); // Convert from BGR tp RGB //BGRToRGB(); } ////////////////////////////////////////////////////////////////////////// void CLayer::InvalidateAllSectors() { // Fill all sectors with 0, (clears all flags). memset( &m_maskGrid[0],0,m_maskGrid.size()*sizeof(m_maskGrid[0]) ); } ////////////////////////////////////////////////////////////////////////// void CLayer::SetAllSectorsValid() { // Fill all sectors with 0xFF, (Set all flags). memset( &m_maskGrid[0],0xFF,m_maskGrid.size()*sizeof(m_maskGrid[0]) ); } ////////////////////////////////////////////////////////////////////////// void CLayer::InvalidateMaskSector( CPoint sector ) { GetSector(sector) = 0; m_bCompressedMaskValid = false; } ////////////////////////////////////////////////////////////////////////// void CLayer::InvalidateMask() { m_bNeedUpdate = true; InvalidateAllSectors(); if (m_scaledMask.IsValid()) m_scaledMask.Release(); /* if (m_layerMask.IsValid if (m_bCompressedMaskValid) m_compressedMask.Free(); m_bCompressedMaskValid = false; m_compressedMask.Free(); */ }; ////////////////////////////////////////////////////////////////////////// void CLayer::PrecacheMask() { if (IsAutoGen()) return; if (!m_layerMask.IsValid()) { m_layerMask.Allocate( m_maskResolution,m_maskResolution ); if (!m_layerMask.IsValid()) { Warning( "Layer %s compressed mask have invalid resolution %d.",(const char*)m_strLayerName,m_maskResolution ); m_layerMask.Allocate( GetNativeMaskResolution(),GetNativeMaskResolution() ); return; } if (m_bCompressedMaskValid) { bool bUncompressOk = m_layerMask.Uncompress( m_compressedMask ); m_compressedMask.Free(); m_bCompressedMaskValid = false; if (!bUncompressOk) { Warning( "Layer %s compressed mask have invalid resolution.",(const char*)m_strLayerName ); } } else { // No compressed layer data. m_layerMask.Clear(); } } } ////////////////////////////////////////////////////////////////////////// CByteImage& CLayer::GetMask() { PrecacheMask(); return m_layerMask; } ////////////////////////////////////////////////////////////////////////// bool CLayer::UpdateMaskForSector( CPoint sector,const CRect §orRect,const CFloatImage &hmap,CByteImage& mask ) { //////////////////////////////////////////////////////////////////////// // Update the layer mask. The heightmap bits are supplied for speed // reasons, repeated memory allocations during batch generations of // layers are to slow //////////////////////////////////////////////////////////////////////// PrecacheTexture(); PrecacheMask(); int resolution = hmap.GetWidth(); uchar §orFlags = GetSector(sector); if (sectorFlags & SECTOR_MASK_VALID && !m_bNeedUpdate) { if (m_layerMask.GetWidth() == resolution) { mask.Attach(m_layerMask); return true; } } m_bNeedUpdate = false; if (!IsAutoGen()) { if (!m_layerMask.IsValid()) return false; if (resolution == m_layerMask.GetWidth()) { mask.Attach(m_layerMask); } else if (resolution == m_scaledMask.GetWidth()) { mask.Attach(m_scaledMask); } else { m_scaledMask.Allocate( resolution,resolution ); CImageUtil::ScaleToFit( m_layerMask,m_scaledMask ); if (m_bSmooth) { CImageUtil::SmoothImage( m_scaledMask,2 ); } mask.Attach(m_scaledMask); } // Mark this sector as valid. sectorFlags |= SECTOR_MASK_VALID; // All valid. return true; } ////////////////////////////////////////////////////////////////////////// // Auto generate mask. ////////////////////////////////////////////////////////////////////////// // If layer mask differ in size, invalidate all sectors. if (resolution != m_layerMask.GetWidth()) { m_layerMask.Allocate( resolution,resolution ); m_layerMask.Clear(); InvalidateAllSectors(); } // Mark this sector as valid. sectorFlags |= SECTOR_MASK_VALID; mask.Attach(m_layerMask); float hVal = 0.0f; AutogenLayerMask( sectorRect,hmap,m_layerMask ); return true; } ////////////////////////////////////////////////////////////////////////// bool CLayer::UpdateMask( const CFloatImage &hmap,CByteImage& mask ) { PrecacheTexture(); PrecacheMask(); int resolution = hmap.GetWidth(); if (!m_bNeedUpdate && m_layerMask.GetWidth() == resolution) { mask.Attach( m_layerMask ); return true; } m_bNeedUpdate = false; if (!IsAutoGen()) { if (!m_layerMask.IsValid()) return false; if (resolution == m_layerMask.GetWidth()) { mask.Attach(m_layerMask); } else if (resolution == m_scaledMask.GetWidth()) { mask.Attach(m_scaledMask); } else { m_scaledMask.Allocate( resolution,resolution ); CImageUtil::ScaleToFit( m_layerMask,m_scaledMask ); if (m_bSmooth) { CImageUtil::SmoothImage( m_scaledMask,2 ); } mask.Attach(m_scaledMask); } // All valid. SetAllSectorsValid(); return true; } ////////////////////////////////////////////////////////////////////////// // Auto generate mask. ////////////////////////////////////////////////////////////////////////// // If layer mask differ in size, invalidate all sectors. if (resolution != m_layerMask.GetWidth()) { m_layerMask.Allocate( resolution,resolution ); m_layerMask.Clear(); } mask.Attach(m_layerMask); CRect rect(0,0,resolution,resolution); AutogenLayerMask( rect,hmap,m_layerMask ); SetAllSectorsValid(); return true; } ////////////////////////////////////////////////////////////////////////// void CLayer::AutogenLayerMask( const CRect &rc,const CFloatImage &hmap,CByteImage& mask ) { CRect rect = rc; assert( hmap.IsValid() ); assert( mask.IsValid() ); assert( hmap.GetWidth() == mask.GetWidth() ); int resolution = hmap.GetWidth(); // Inflate rectangle. rect.InflateRect(1,1,1,1); rect.left = max(rect.left,1); rect.top = max(rect.top,1); rect.right = min(rect.right,resolution-1); rect.bottom = min(rect.bottom,resolution-1); // Set the loop pointers uchar *pLayerMask = mask.GetData(); float *pHmap = hmap.GetData(); // We need constant random numbers srand(0); float heightmapCompensate = (resolution / 128.0f) * 256; float MinAltitude = m_iLayerStart; float MaxAltitude = m_iLayerEnd; int MinSlope = RoundFloatToInt(m_minSlope*256); int MaxSlope = RoundFloatToInt(m_maxSlope*256); // Scan the heightmap for pixels that belong to this layer unsigned int x,y,pos; unsigned int hw = resolution; float hVal = 0.0f; for (y = rect.top; y < rect.bottom; y++) { for (x = rect.left; x < rect.right; x++,pos++) { pos = x + y*resolution; // Get the height value from the heightmap float *h = &pHmap[pos]; hVal = *h; if (hVal < MinAltitude || hVal > MaxAltitude) { pLayerMask[pos] = 0; continue; } // Calculate the slope for this point float fs = ( fabs((*(h + 1)) - hVal) + fabs((*(h - 1)) - hVal) + fabs((*(h + hw)) - hVal) + fabs((*(h - hw)) - hVal) + fabs((*(h + hw + 1)) - hVal) + fabs((*(h - hw - 1)) - hVal) + fabs((*(h + hw - 1)) - hVal) + fabs((*(h - hw + 1)) - hVal)); // Compensate the smaller slope for bigger heightfields int slope = RoundFloatToInt(fs*heightmapCompensate); // Normalize the range to 0 - 255*256 if (slope > 255*256) slope = 255*256; // Check if the current point belongs to the layer if (slope >= MinSlope && slope <= MaxSlope) { pLayerMask[pos] = 0xFF; } else { pLayerMask[pos] = 0; } /* // Modify it by a random value if (*pData && m_bNoise) { *pData -= (unsigned char) (FloatToIntRet((float) rand() / (float) RAND_MAX * iBlendFactor)); } */ } } rect.DeflateRect(1,1,1,1); if (m_bSmooth) { // Smooth the layer for (y = rect.top; y < rect.bottom; y++) { pos = y*resolution + rect.left; for (x = rect.left; x < rect.right; x++,pos++) { // Smooth it out pLayerMask[pos] =( (uint)pLayerMask[pos] + pLayerMask[pos + 1] + pLayerMask[pos + hw] + pLayerMask[pos + hw + 1] + pLayerMask[pos - 1] + pLayerMask[pos - hw] + pLayerMask[pos - hw - 1] + pLayerMask[pos - hw + 1] + pLayerMask[pos + hw - 1]) / 9; } } } } ////////////////////////////////////////////////////////////////////////// void CLayer::AllocateMaskGrid() { CHeightmap *pHeightmap = GetIEditor()->GetHeightmap(); SSectorInfo si; pHeightmap->GetSectorsInfo( si ); m_numSectors = si.numSectors; m_maskGrid.resize( si.numSectors*si.numSectors ); memset( &m_maskGrid[0],0,m_maskGrid.size()*sizeof(m_maskGrid[0]) ); m_maskResolution = si.surfaceTextureSize; } ////////////////////////////////////////////////////////////////////////// uchar& CLayer::GetSector( CPoint sector ) { int p = sector.x + sector.y*m_numSectors; assert( p >= 0 && p < m_maskGrid.size() ); return m_maskGrid[p]; } ////////////////////////////////////////////////////////////////////////// void CLayer::CompressMask() { if (m_bCompressedMaskValid) return; m_bCompressedMaskValid = false; m_compressedMask.Free(); // Compress mask. if (m_layerMask.IsValid()) { m_layerMask.Compress( m_compressedMask ); m_bCompressedMaskValid = true; } m_layerMask.Release(); m_scaledMask.Release(); } ////////////////////////////////////////////////////////////////////////// int CLayer::GetSize() const { int size = sizeof(*this); size += m_texture.GetSize(); size += m_layerMask.GetSize(); size += m_scaledMask.GetSize(); size += m_compressedMask.GetSize(); size += m_maskGrid.size()*sizeof(unsigned char); return size; } ////////////////////////////////////////////////////////////////////////// //! Export layer block. void CLayer::ExportBlock( const CRect &rect,CXmlArchive &xmlAr ) { // ignore autogenerated layers. if (m_bAutoGen) return; XmlNodeRef node = xmlAr.root; PrecacheMask(); if (!m_layerMask.IsValid()) return; node->setAttr( "Name",m_strLayerName ); node->setAttr( "MaskWidth",m_layerMask.GetWidth() ); node->setAttr( "MaskHeight",m_layerMask.GetHeight() ); CRect subRc( 0,0,m_layerMask.GetWidth(),m_layerMask.GetHeight() ); subRc &= rect; node->setAttr( "X1",subRc.left ); node->setAttr( "Y1",subRc.top ); node->setAttr( "X2",subRc.right ); node->setAttr( "Y2",subRc.bottom ); if (!subRc.IsRectEmpty()) { CByteImage subImage; subImage.Allocate( subRc.Width(),subRc.Height() ); m_layerMask.GetSubImage( subRc.left,subRc.top,subRc.Width(),subRc.Height(),subImage ); xmlAr.pNamedData->AddDataBlock( CString("LayerMask_")+m_strLayerName,subImage.GetData(),subImage.GetSize() ); } } //! Import layer block. void CLayer::ImportBlock( CXmlArchive &xmlAr,CPoint offset ) { // ignore autogenerated layers. if (m_bAutoGen) return; XmlNodeRef node = xmlAr.root; PrecacheMask(); if (!m_layerMask.IsValid()) return; CRect subRc; node->getAttr( "X1",subRc.left ); node->getAttr( "Y1",subRc.top ); node->getAttr( "X2",subRc.right ); node->getAttr( "Y2",subRc.bottom ); void *pData; int nSize; if (xmlAr.pNamedData->GetDataBlock( CString("LayerMask_")+m_strLayerName, pData,nSize )) { CByteImage subImage; subImage.Attach( (unsigned char*)pData,subRc.Width(),subRc.Height() ); m_layerMask.SetSubImage( subRc.left+offset.x,subRc.top+offset.y,subImage ); } } ////////////////////////////////////////////////////////////////////////// int CLayer::GetNativeMaskResolution() const { CHeightmap *pHeightmap = GetIEditor()->GetHeightmap(); SSectorInfo si; pHeightmap->GetSectorsInfo( si ); return si.surfaceTextureSize; }