Files
FC1/Editor/TerrainTexGen.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

2116 lines
58 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// Crytek Engine Source File.
// Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
// File name: TerrainTexGen.cpp
// Version: v1.00
// Created: 8/10/2002 by Timur.
// Compilers: Visual Studio.NET
// Description:
// -------------------------------------------------------------------------
// History:
//
////////////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "TerrainTexGen.h"
#include "CryEditDoc.h"
#include "TerrainLighting.h"
#include "Heightmap.h"
#include "TerrainGrid.h"
#include "Layer.h"
#include "VegetationMap.h"
#include "HeightmapAccessibility.h" // CHeightmapAccessibility
#include "Util\Thread.h"
#include <I3Dengine.h>
#include <afxmt.h>
// Sector flags.
enum
{
eSectorHeightmapValid = 0x01,
eSectorLightmapValid = 0x02,
eSectorLayersValid = 0x04
};
#define MAX_BRIGHTNESS 100
//#define LIGHTBIT_INSHADOW_LEVEL 196
#define LIGHTBIT_INSHADOW_LEVEL (min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fAmbient*1.25f*pSettings->sunMultiplier) ))
//////////////////////////////////////////////////////////////////////////
CTerrainTexGen::CTerrainTexGen( int resolution )
{
m_pLightingBits = NULL;
m_bLog = true;
m_waterLayer = NULL;
m_vegetationMap = NULL;
m_iCachedSkyAccessiblityQuality=0;
m_bNotValid = false;
Init(resolution);
}
CTerrainTexGen::CTerrainTexGen()
{
m_pLightingBits = NULL;
m_bLog = true;
m_waterLayer = NULL;
m_vegetationMap = NULL;
m_iCachedSkyAccessiblityQuality=0;
m_bNotValid = false;
m_heightmap = GetIEditor()->GetHeightmap();
assert( m_heightmap );
SSectorInfo si;
m_heightmap->GetSectorsInfo( si );
Init(si.surfaceTextureSize);
}
//////////////////////////////////////////////////////////////////////////
CTerrainTexGen::~CTerrainTexGen()
{
// Release masks for all layers to save memory.
int numLayers = GetLayerCount();
for (int i = 0; i < numLayers; i++)
{
if (m_layers[i].layerMask)
delete m_layers[i].layerMask;
CLayer *pLayer = GetLayer(i);
pLayer->ReleaseMask();
}
for (int i = 0; i < m_sectorGrid.size(); i++)
{
if (m_sectorGrid[i].lightmap)
delete m_sectorGrid[i].lightmap;
}
if (m_waterLayer)
{
delete m_waterLayer;
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::Init( int resolution )
{
int i;
m_heightmap = GetIEditor()->GetHeightmap();
assert( m_heightmap );
m_terrainMaxZ = 255.0f;
m_vegetationMap = GetIEditor()->GetVegetationMap();
// Fill layers array.
ClearLayers();
CCryEditDoc *pDocument = GetIEditor()->GetDocument();
int numLayers = pDocument->GetLayerCount();
m_layers.reserve( numLayers + 2 ); // Leave some space for water layers.
for (i = 0; i < numLayers; i++)
{
SLayerInfo li;
li.pLayer = pDocument->GetLayer(i);
m_layers.push_back(li);
}
SSectorInfo si;
m_heightmap->GetSectorsInfo( si );
m_resolution = resolution;
m_numSectors = si.numSectors;
m_sectorResolution = m_resolution / m_numSectors;
m_pixelSizeInMeters = float(si.numSectors*si.sectorSize) / (float)m_resolution;
//! Allocate heightmap big enough.
if (m_hmap.GetWidth() != resolution)
{
if (!m_hmap.Allocate( resolution,resolution ))
m_bNotValid = true;
// Invalidate all sectors.
m_sectorGrid.resize( m_numSectors*m_numSectors );
memset( &m_sectorGrid[0],0,m_sectorGrid.size()*sizeof(m_sectorGrid[0]) );
}
// Resolution is always power of 2.
m_resolutionShift = 1;
for (i = 0; i<32 ;i++)
{
if ((1<<i)==m_resolution)
{
m_resolutionShift = i;
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::SetLightingBits( CBitArray *array )
{
m_pLightingBits = array;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::UpdateSectorLayers( CPoint sector )
{
bool bFirstUsedLayer = true;
CRect sectorRect;
GetSectorRect( sector,sectorRect );
int numLayers = GetLayerCount();
for (int i = 0; i < numLayers; i++)
{
CLayer *pLayer = GetLayer(i);
// Skip the layer if it is not in use
if (!pLayer->IsInUse() || !pLayer->HasTexture())
continue;
// For first used layer mask is not needed.
if (!bFirstUsedLayer)
{
if (!m_layers[i].layerMask)
{
m_layers[i].layerMask = new CByteImage;
}
if (pLayer->UpdateMaskForSector(sector,sectorRect,m_hmap,*m_layers[i].layerMask ))
{
}
}
bFirstUsedLayer = false;
}
// For this sector all layers are valid.
SetSectorFlags( sector,eSectorLayersValid );
}
// written by M.M.
float CTerrainTexGen::GetSunAmount( const float *inpHeightmapData,int iniX,int iniY,
float infInvHeightScale, const Vec3 &vSunShadowVector, const float infShadowBlur ) const
{
assert(inpHeightmapData);
assert(iniX<m_resolution);
assert(iniY<m_resolution);
float fForwardStepSize=1.0f; // [0.1f .. [ the less size, the better quality but it will be slower
// fForwardStepSize *= (float)iniWidth/1024.0f;
const int iFixPointBits=8; // wide range .. more precision
const int iFixPointBase=1<<iFixPointBits; //
float fZInit=inpHeightmapData[iniX+(iniY<<m_resolutionShift)];
float fSlope=vSunShadowVector.z*infInvHeightScale*fForwardStepSize;
float fSlopeTop = fSlope + infShadowBlur;
float fSlopeBottom = fSlope - infShadowBlur;
assert(fSlopeTop>=0.0f);
fZInit+=0.1f; // Bias to avoid little bumps in the result
int iDirX=(int)(vSunShadowVector.x*fForwardStepSize*iFixPointBase);
int iDirY=(int)(vSunShadowVector.y*fForwardStepSize*iFixPointBase);
// float fLen=(float)(rand()) * (fForwardStepSize/RAND_MAX);
float fLen=0.0f;
int iX=iniX*iFixPointBase+iFixPointBase/2,iY=iniY*iFixPointBase+iFixPointBase/2;
float fZBottom=fZInit+0.1f,fZTop=fZInit+1.4f;
float fArea=1.0f;
// inner loop
for(;fZBottom<m_terrainMaxZ;)
{
assert(fZBottom<=fZTop);
iX+=iDirX;iY+=iDirY;
fZBottom+=fSlopeBottom;fZTop+=fSlopeTop;
int iXBound=(iX>>iFixPointBits); // shift right iFixPointBits bits = /iFixPointBase
int iYBound=(iY>>iFixPointBits); // shift right iFixPointBits bits = /iFixPointBase
if(iXBound>=m_resolution)break;
if(iYBound>=m_resolution)break;
float fGround=inpHeightmapData[iXBound + (iYBound<<m_resolutionShift)];
if(fZTop<fGround) // ground hit
return(0.0f); // full shadow
if(fZBottom<fGround) // ground hit
{
float fNewArea=(fZTop-fGround)/(fZTop-fZBottom); // this is slow in the penumbra of the shadow (but this is a rare case)
if(fNewArea<fArea)fArea=fNewArea;
assert(fArea>=0.0f);
assert(fArea<=1.0f);
}
}
return(fArea);
}
float CTerrainTexGen::GetSkyAccessibilityFast( const int iniX, const int iniY ) const
{
if(!m_SkyAccessiblity.GetData())
return 1.0f; // RefreshAccessibility was not sucessful
assert(m_SkyAccessiblity.GetWidth()!=0);
assert(m_SkyAccessiblity.GetHeight()!=0);
int iW=m_hmap.GetWidth();
int iH=m_hmap.GetHeight();
int invScaleX=iW / m_SkyAccessiblity.GetWidth();
int invScaleY=iH / m_SkyAccessiblity.GetHeight();
int iXmul256=256/invScaleX;
int iYmul256=256/invScaleY;
return CImageUtil::GetBilinearFilteredAt(iniX*iXmul256,iniY*iYmul256,m_SkyAccessiblity)*(1.0f/255.0f);
}
float CTerrainTexGen::GetSunAccessibilityFast( const int iniX, const int iniY ) const
{
if(!m_SunAccessiblity.GetData())
return 1.0f; // RefreshAccessibility was not sucessful
assert(m_SunAccessiblity.GetWidth()!=0);
assert(m_SunAccessiblity.GetHeight()!=0);
int iW=m_hmap.GetWidth();
int iH=m_hmap.GetHeight();
int invScaleX=iW / m_SunAccessiblity.GetWidth();
int invScaleY=iH / m_SunAccessiblity.GetHeight();
int iXmul256=256/invScaleX;
int iYmul256=256/invScaleY;
return CImageUtil::GetBilinearFilteredAt(iniX*iXmul256,iniY*iYmul256,m_SunAccessiblity)*(1.0f/255.0f);
}
/*
// written by M.M.
float CTerrainTexGen::GetSkyAccessibilityNoise( const float *inpHeightmapData,
const int iniWidth, const int iniHeight, const int iniX, const int iniY, const int iniQuality, const float infHeightScale ) const
{
assert(inpHeightmapData);
assert(iniX<iniWidth);
assert(iniY<iniHeight);
int iAngleSteps; // [4..[ the more steps, the less noise but it will be slower
float fForwardStepSize; // [0.1f .. [ the less size, the better quality but it will be slower
switch(iniQuality)
{
case 1: iAngleSteps=3; fForwardStepSize=8.0f;break;
case 2: iAngleSteps=4; fForwardStepSize=6.0f;break;
case 3: iAngleSteps=5; fForwardStepSize=5.0f;break;
case 4: iAngleSteps=6; fForwardStepSize=4.0f;break;
case 5: iAngleSteps=7; fForwardStepSize=3.5f;break;
case 6: iAngleSteps=8; fForwardStepSize=3.0f;break;
case 7: iAngleSteps=10; fForwardStepSize=2.0f;break;
case 8: iAngleSteps=12; fForwardStepSize=1.5f;break;
case 9: iAngleSteps=14; fForwardStepSize=1.0f;break;
case 10: iAngleSteps=16; fForwardStepSize=0.7f;break;
default: return(1.0f); // no sampling at all
}
// fForwardStepSize *= (float)iniWidth/1024.0f;
const int iFixPointBits=8; // wide range .. more precision
const int iFixPointBase=1<<iFixPointBits; //
float const fAngleStepsSize=gf_PI_MUL_2/(float)iAngleSteps;
float fJitterAngle=(float)(rand()) * (fAngleStepsSize/RAND_MAX);
float fZInit=inpHeightmapData[iniX+(iniY<<m_resolutionShift)];
float fSlopeInit=0.0f; //
assert(fSlopeInit>=0.0f);
fZInit+=0.1f; // Bias to avoid little bumps in the accessibility result
float fArea=0.0f;
// To sample the whole hemisphere we divide it into wedges
for(float iWedgeNo=fJitterAngle;iWedgeNo<iAngleSteps;iWedgeNo++)
{
float fAngle = iWedgeNo*fAngleStepsSize + fJitterAngle;
int iDirX=(int)(sin(fAngle)*fForwardStepSize*iFixPointBase);
int iDirY=(int)(cos(fAngle)*fForwardStepSize*iFixPointBase);
float fSlope=fSlopeInit;
float fLen=(float)(rand()) * (fForwardStepSize/RAND_MAX);
int iX=iniX*iFixPointBase+iFixPointBase/2,iY=iniY*iFixPointBase+iFixPointBase/2;
// for every wedge a shadow horizon is searched
// inner loop
for(float fZ=fZInit;fZ<m_terrainMaxZ;fZ+=fSlope)
{
int iXBound=(iX>>iFixPointBits); // shift right iFixPointBits bits = /iFixPointBase
int iYBound=(iY>>iFixPointBits); // shift right iFixPointBits bits = /iFixPointBase
if(((DWORD)iXBound)>=iniWidth)break;
if(((DWORD)iYBound)>=iniHeight)break;
float fGround=inpHeightmapData[iXBound + (iYBound<<m_resolutionShift)];
if(fZ<fGround) // ground hit
{
assert(fLen!=0.0f);
fSlope=(fGround-fZInit)/fLen; assert(fSlope>=0.0f);
fZ=fGround+0.1f; // Bias to avoid little bumps in the accessibility result
}
fLen+=fForwardStepSize;
iX+=iDirX;iY+=iDirY;
}
float fWedgeHorizonPart=1.0f-atanf(fSlope*infHeightScale)/gf_PI_DIV_2;
float fWedgeArea = fWedgeHorizonPart*fWedgeHorizonPart/(float)iAngleSteps;
fArea+=fWedgeArea;
}
return(fArea);
}
*/
////////////////////////////////////////////////////////////////////////
// Generate the surface texture with the current layer and lighting
// configuration and write the result to surfaceTexture.
// Also give out the results of the terrain lighting if pLightingBit is not NULL.
////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::GenerateSectorTexture( CPoint sector,const CRect &rect,int flags,CImage &surfaceTexture )
{
if (m_bNotValid)
return false;
// set flags.
bool bUseLighting = flags & ETTG_LIGHTING;
bool bShowWater = flags & ETTG_SHOW_WATER;
bool bConvertToABGR = flags & ETTG_ABGR;
bool bNoTexture = flags & ETTG_NOTEXTURE;
bool bUseLightmap = flags & ETTG_USE_LIGHTMAPS;
//bool bKeepsLayers = flags & ETTG_KEEP_LAYERMASKS;
m_bLog = !(flags & ETTG_QUIET);
if (flags & ETTG_INVALIDATE_LAYERS)
{
InvalidateLayers();
}
uint i;
CCryEditDoc *pDocument = GetIEditor()->GetDocument();
CHeightmap *pHeightmap = GetIEditor()->GetHeightmap();
SectorInfo& sectorInfo = GetSectorInfo(sector);
int sectorFlags = sectorInfo.flags;
assert( pDocument );
assert( pHeightmap );
float waterLevel = pHeightmap->GetWaterLevel();
uint *pSurface = surfaceTexture.GetData();
// Update heightmap for that sector.
UpdateSectorHeightmap(sector);
if (bNoTexture)
{
// fill texture with white color if there is no texture present
surfaceTexture.Fill( 255 );
}
else
{
// Enable texturing.
//////////////////////////////////////////////////////////////////////////
// Setup water layer.
if (bShowWater && m_waterLayer == NULL)
{
// Apply water level.
// Add a temporary water layer to the list
SLayerInfo li;
m_waterLayer = new CLayer;
m_waterLayer->LoadTexture(MAKEINTRESOURCE(IDB_WATER), 128, 128);
m_waterLayer->SetAutoGen(true);
m_waterLayer->SetLayerStart(0);
m_waterLayer->SetLayerEnd(waterLevel);
li.pLayer = m_waterLayer;
m_layers.push_back( li );
/*
//m_waterLayer->FillWithColor( pDocument->GetWaterColor(),8,8 );
m_waterLayer->GenerateWaterLayer16(m_hmap.GetData(),m_resolution, m_resolution, waterLevel );
li.pLayer = m_waterLayer;
li.layerMask.Attach( m_waterLayer->GetMask() );
m_layers.push_back( li );
*/
}
////////////////////////////////////////////////////////////////////////
// Generate the layer masks
////////////////////////////////////////////////////////////////////////
// if lLayers not valid for this sector, update them.
// Update layers for this sector.
if (!(sectorFlags & eSectorLayersValid))
UpdateSectorLayers( sector );
bool bFirstLayer = true;
// Calculate sector rectangle.
CRect sectorRect;
GetSectorRect( sector,sectorRect );
// Calculate surface texture rectangle.
CRect layerRc( sectorRect.left+rect.left,sectorRect.top+rect.top, sectorRect.left+rect.right, sectorRect.top+rect.bottom );
CByteImage layerMask;
////////////////////////////////////////////////////////////////////////
// Generate the masks and the texture.
////////////////////////////////////////////////////////////////////////
int numLayers = GetLayerCount();
for (i = 0; i < (int)numLayers; i++)
{
CLayer *pLayer = GetLayer(i);
// Skip the layer if it is not in use
if (!pLayer->IsInUse() || !pLayer->HasTexture())
continue;
// Set the write pointer (will be incremented) for the surface data
unsigned int *pTex = pSurface;
uint layerWidth = pLayer->GetTextureWidth();
uint layerHeight = pLayer->GetTextureHeight();
if (bFirstLayer)
{
bFirstLayer = false;
// Draw the first layer, without layer mask.
for (int y = layerRc.top; y < layerRc.bottom; y++)
{
uint layerY = y & (layerHeight-1);
for (int x = layerRc.left; x < layerRc.right; x++)
{
uint layerX = x & (layerWidth-1);
// Get the color of the tiling texture at this position
// WAT_EDIT
*pTex++ = pLayer->GetTexturePixel( layerX,layerY );
//unsigned int tmp = pLayer->GetTexturePixel( layerX,layerY );
//*pTex = (tmp & 0xff00ff00) | ((tmp & 0x000000ff) << 16) | ((tmp & 0x00ff0000) >> 16);
//pTex++;
}
}
}
else
{
if (!m_layers[i].layerMask || !m_layers[i].layerMask->IsValid())
continue;
layerMask.Attach( *m_layers[i].layerMask );
uint iBlend;
COLORREF clr;
// Draw the current layer with layer mask.
for (int y = layerRc.top; y < layerRc.bottom; y++)
{
uint layerY = y & (layerHeight-1);
for (int x = layerRc.left; x < layerRc.right; x++)
{
uint layerX = x & (layerWidth-1);
// Scale the current preview coordinate to the layer mask and get the value.
iBlend = layerMask.ValueAt(x,y);
// Check if this pixel should be drawn.
if (iBlend == 0)
{
pTex++;
continue;
}
// Get the color of the tiling texture at this position
clr = pLayer->GetTexturePixel( layerX,layerY );
// Just overdraw when the opaqueness of the new layer is maximum
if (iBlend == 255)
{
*pTex = clr;
}
else
{
// Blend the layer into the existing color, iBlend is the blend factor taken from the layer
int iBlendSrc = 255 - iBlend;
// WAT_EDIT
*pTex = ((iBlendSrc * (*pTex & 0x000000FF) + (clr & 0x000000FF) * iBlend) >> 8) |
(((iBlendSrc * (*pTex & 0x0000FF00) >> 8) + ((clr & 0x0000FF00) >> 8) * iBlend) >> 8) << 8 |
(((iBlendSrc * (*pTex & 0x00FF0000) >> 16) + ((clr & 0x00FF0000) >> 16) * iBlend) >> 8) << 16;
//*pTex = ((iBlendSrc * (*pTex & 0x00ff0000) + (clr & 0x00ff0000) * iBlend) >> 8) |
// (((iBlendSrc * (*pTex & 0x0000FF00) >> 8) + ((clr & 0x0000FF00) >> 8) * iBlend) >> 8) << 8 |
// (((iBlendSrc * (*pTex & 0x000000ff) >> 16) + ((clr & 0x000000ff) >> 16) * iBlend) >> 8) << 16;
}
pTex++;
}
}
}
}
}
////////////////////////////////////////////////////////////////////////
// Light the texture
////////////////////////////////////////////////////////////////////////
if (bUseLighting)
{
LightingSettings *ls = pDocument->GetLighting();
if (bUseLightmap)
{
CImage *pSectorLightmap = sectorInfo.lightmap;
if (!(sectorFlags & eSectorLightmapValid) || !pSectorLightmap)
{
if (sectorInfo.lightmap)
{
delete sectorInfo.lightmap;
sectorInfo.lightmap = 0;
}
pSectorLightmap = new CImage;
if (!pSectorLightmap->Allocate( m_sectorResolution,m_sectorResolution ))
{
m_bNotValid = true;
return false;
}
pSectorLightmap->Fill( 255 );
sectorInfo.lightmap = pSectorLightmap;
// Generate Lightmap for this sector.
if (!GenerateLightmap( sector,ls,*pSectorLightmap,flags,m_pLightingBits ))
return false;
}
Vec3 blendColor;
blendColor.x = ls->sunMultiplier * 255; // Red // *2 for overbrighting two times
blendColor.y = ls->sunMultiplier * 255; // Green
blendColor.z = ls->sunMultiplier * 255; // Blue
// Blend lightmap with base texture.
BlendLightmap( sector,surfaceTexture,rect,*pSectorLightmap,blendColor );
}
else
{
// If not lightmaps.
// Pass base texture instead of lightmap (Higher precision).
if (!GenerateLightmap( sector,ls,surfaceTexture,flags,m_pLightingBits ))
return false;
}
}
if (bConvertToABGR)
{
ConvertToABGR( surfaceTexture,rect );
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::GetSectorRect( CPoint sector,CRect &rect )
{
rect.left = sector.x * m_sectorResolution;
rect.top = sector.y * m_sectorResolution;
rect.right = rect.left + m_sectorResolution;
rect.bottom = rect.top + m_sectorResolution;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::BlendLightmap( CPoint sector,CImage &surfaceTexture,const CRect &rect,const CImage &lightmap,const Vec3& blendColor )
{
uint *pTexStart = surfaceTexture.GetData();
uint *pTex = pTexStart;
uint *pLightmapStart = lightmap.GetData();
uint *pLightmap = pLightmapStart;
int lx = rect.left;
int ly = rect.top;
uint blend_r = blendColor.x;
uint blend_g = blendColor.y;
uint blend_b = blendColor.z;
uint maxBlendValue = 32768;
blend_r = min(blend_r,maxBlendValue);
blend_g = min(blend_g,maxBlendValue);
blend_b = min(blend_b,maxBlendValue);
uint r,g,b;
int texWidth = surfaceTexture.GetWidth();
int lightmapWidth = lightmap.GetWidth();
int w = rect.Width();
int h = rect.Height();
for (int y = 0; y < h; y++)
{
pTex = &pTexStart[y*texWidth];
pLightmap = &pLightmapStart[(y+ly)*lightmapWidth + lx];
for (int x = 0; x < w; x++)
{
//*pTex++ = ((* pTex & 0x00FF0000) >> 16) | (* pTex & 0x0000FF00) | ((* pTex & 0x000000FF) << 16);
uint l = *pLightmap++;
r = (blend_r * GetRValue(l) * GetRValue(*pTex)) >> 16;
g = (blend_g * GetGValue(l) * GetGValue(*pTex)) >> 16;
b = (blend_b * GetBValue(l) * GetBValue(*pTex)) >> 16;
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
*pTex++ = RGB(r,g,b);
}
}
}
/*
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::GetLightmapTexture( CPoint sector,CImage &surfaceTexture,const CRect &rect,const CImage &lightmap )
{
uint *pTexStart = surfaceTexture.GetData();
uint *pTex = pTexStart;
uint *pLightmapStart = lightmap.GetData();
uint *pLightmap = pLightmapStart;
int lx = rect.left;
int ly = rect.top;
int texWidth = surfaceTexture.GetWidth();
int lightmapWidth = lightmap.GetWidth();
int w = rect.Width();
int h = rect.Height();
for (int y = 0; y < h; y++)
{
pTex = &pTexStart[y*texWidth];
pLightmap = &pLightmapStart[(y+ly)*lightmapWidth + lx];
for (int x = 0; x < w; x++)
{
*pTex++ = RGB(GetRValue(*pLightmap),GetGValue(*pLightmap),GetBValue(*pLightmap));
pLightmap++;
}
}
}
*/
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::ConvertToABGR( CImage &surfaceTexture,const CRect &rect )
{
int texWidth = surfaceTexture.GetWidth();
// Set the write pointer (will be incremented) for the surface data
uint *pTex = surfaceTexture.GetData();
uint *pTexEnd = pTex + surfaceTexture.GetWidth()*surfaceTexture.GetHeight();
while (pTex != pTexEnd)
{
*pTex = ((*pTex & 0x00FF0000) >> 16) | (* pTex & 0x0000FF00) | ((* pTex & 0x000000FF) << 16);
pTex++;
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::Log( const char *format,... )
{
if (!m_bLog)
return;
va_list ArgList;
char szBuffer[1024];
va_start(ArgList, format);
vsprintf(szBuffer, format, ArgList);
va_end(ArgList);
GetIEditor()->SetStatusText(szBuffer);
CLogFile::WriteLine(szBuffer);
}
//////////////////////////////////////////////////////////////////////////
// Lighting.
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
inline Vec3 CalcVertexNormal( int x,int y,float *pHeightmapData,int resolution,float fHeightScale )
{
if (x < resolution-1 && y < resolution-1 && x>0 && y>0)
{
Vec3 v1,v2,vNormal;
/*
// First triangle
Vec3 Tri[3];
Tri[0].x = x;
Tri[0].y = y;
Tri[0].z = pHeightmapData[x+y*resolution] * fHeightScale;
Tri[1].x = x+1;
Tri[1].y = y;
Tri[1].z = pHeightmapData[(x+1)+y*resolution] * fHeightScale;
Tri[2].x = x+1;
Tri[2].y = y+1;
Tri[2].z = pHeightmapData[(x+1)+(y+1)*resolution] * fHeightScale;
// Calculate the normal
v1 = Tri[1] - Tri[0];
v2 = Tri[2] - Tri[0];
*/
// faster and better quality
v1 = Vec3( 2,0, (pHeightmapData[x+1+y*resolution]-pHeightmapData[x-1+y*resolution])*fHeightScale );
v2 = Vec3( 0,2, (pHeightmapData[x+(y+1)*resolution]-pHeightmapData[x+(y-1)*resolution])*fHeightScale );
vNormal = v1.Cross(v2);
vNormal.Normalize();
return vNormal;
}
else
return(Vec3(0,0,1));
}
// r,g,b in range 0-1.
inline float ColorLuminosity( float r,float g,float b )
{
float mx,mn;
mx = MAX( r,g );
mx = MAX( mx,b );
mn = MIN( r,g );
mn = MIN( mn,b );
return (mx + mn) / 2.0f;
}
// r,g,b in range 0-255.
inline uint ColorLuminosityInt( uint r,uint g,uint b )
{
uint mx,mn;
mx = MAX( r,g );
mx = MAX( mx,b );
mn = MIN( r,g );
mn = MIN( mn,b );
return (mx + mn) >> 1;
}
//!
float CalcHeightScale( const DWORD indwTargetResolution, const uint inHeightmapsize, const int iniUnitSize )
{
return (float)(indwTargetResolution) / (float)(inHeightmapsize) / (float)(iniUnitSize);
}
//////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::GenerateLightmap( CPoint sector,LightingSettings *pSettings,CImage &lightmap,int genFlags,CBitArray *pLightingBits )
{
////////////////////////////////////////////////////////////////////////
// Light the color values in a DWORD array with the generated lightmap.
// Parameters are queried from the document. In case of the lighting
// bit you are supposed to pass a NULL pointer if your don't want it.
////////////////////////////////////////////////////////////////////////
int i, j;
float fDP3;
CRect rc;
GetSectorRect(sector,rc);
float fSunCol[3];
fSunCol[0] = GetRValue(pSettings->dwSunColor)/255.0f * pSettings->sunMultiplier; // Red
fSunCol[1] = GetGValue(pSettings->dwSunColor)/255.0f * pSettings->sunMultiplier; // Green
fSunCol[2] = GetBValue(pSettings->dwSunColor)/255.0f * pSettings->sunMultiplier; // Blue
float fSkyCol[3];
fSkyCol[0] = GetRValue( pSettings->dwSkyColor )/255.0f * pSettings->sunMultiplier; // Red
fSkyCol[1] = GetGValue( pSettings->dwSkyColor )/255.0f * pSettings->sunMultiplier; // Green
fSkyCol[2] = GetBValue( pSettings->dwSkyColor )/255.0f * pSettings->sunMultiplier; // Blue
assert(pSettings->sunMultiplier>=0);
assert(fSunCol[0]>=0 && fSunCol[1]>=0 && fSunCol[2]>=0);
assert(fSkyCol[0]>=0 && fSkyCol[1]>=0 && fSkyCol[2]>=0);
float fHeighmapSizeInMeters = m_heightmap->GetWidth()*m_heightmap->GetUnitSize();
// Calculate a height scalation factor. This is needed to convert the
// relative nature of the height values. The contrast value is used to
// raise the slope of the triangles, whoch results in higher contrast
// lighting
// commented out because this is doing more harm than good
// float fContrast = 2.0f*((float)pSettings->iContrast / 1000.0f);
// float fHeightScale = fContrast*m_resolution/fHeighmapSizeInMeters;
float fHeightScale=CalcHeightScale(m_resolution,m_heightmap->GetWidth(),m_heightmap->GetUnitSize());
float fInvHeightScale = 1.0f/fHeightScale;
uint iWidth = m_resolution;
uint iHeight = m_resolution;
//////////////////////////////////////////////////////////////////////////
// Prepare constants.
//////////////////////////////////////////////////////////////////////////
// Calculate the light vector
Vec3 sunVector = pSettings->GetSunVector();
//////////////////////////////////////////////////////////////////////////
// Get heightmap data for this sector.
//////////////////////////////////////////////////////////////////////////
UpdateSectorHeightmap(sector);
float *pHeightmapData = m_hmap.GetData();
//////////////////////////////////////////////////////////////////////////
// generate accessiblity for this sector.
//////////////////////////////////////////////////////////////////////////
if (!RefreshAccessibility(pSettings,genFlags))
{
CLogFile::FormatLine( "RefreshAccessibility Failed." );
return false;
}
//////////////////////////////////////////////////////////////////////////
// Generate shadowmap.
//////////////////////////////////////////////////////////////////////////
CByteImage shadowmap;
float shadowAmmount = 255.0f*pSettings->iShadowIntensity/100.0f;
float fShadowIntensity = (float)pSettings->iShadowIntensity/100.0f;
float fShadowBlur=pSettings->iShadowBlur*0.04f; // defines the slope blurring, 0=no blurring, .. (angle would be better but is slower)
bool bStatObjShadows = genFlags & ETTG_STATOBJ_SHADOWS;
bool bPaintBrightness = (genFlags & ETTG_STATOBJ_PAINTBRIGHTNESS) && (m_vegetationMap != 0);
bool bUseFastLighting = genFlags & ETTG_FAST_LLIGHTING;
bool bTerrainShadows = pSettings->bTerrainShadows && (!(genFlags & ETTG_NO_TERRAIN_SHADOWS));
if(!pSettings->bObjectShadows) // no shadows for objects
bStatObjShadows=false;
if(bStatObjShadows)
{
if (!shadowmap.Allocate(m_sectorResolution,m_sectorResolution))
{
m_bNotValid = true;
return false;
}
if(pSettings->eAlgo==ePrecise)
GenerateShadowmap( sector,shadowmap,255,sunVector ); // shadow is full shadow (realistic)
else
GenerateShadowmap( sector,shadowmap,shadowAmmount,sunVector ); // shadow percentage (more flexible?)
}
////////////////////////////////////////////////////////////////////////
// Perform lighting
////////////////////////////////////////////////////////////////////////
// Calculate the climb factor for the shadow calculations. The expressions
// in the braces are used to express the (anit-)proportional relationship
// between the climb factor and various input parameters. This is f.e.
// necessary to guarantee a consistent look of the shadows for various
// output image sizes
/*
float dxStepAdjust = fHeighmapSizeInMeters/m_resolution;
Vec3 sunXYVector = Vec3(sunVector.x,sunVector.y,0);
float sunXYLen = GetLength(sunXYVector);
float sunLen = GetLength(sunVector);
// dyStep^2 = vSun^2 - dxStep^2
float dyStep = sqrtf( sunLen*sunLen - sunXYLen*sunXYLen );
float fClimbPerStep = dyStep*dxStepAdjust;
int lightDirX = ftoi( (-sunVector.x) * m_resolution );
int lightDirY = ftoi( (-sunVector.y) * m_resolution );
*/
uint ambient = pSettings->iAmbient;
float fAmbient255 = ambient;
float fAmbient = fAmbient255 / 255.0f;
int brightness,brightness_shadowmap;
uint *pLightmap = lightmap.GetData();
Vec3 lightVector = -sunVector;
Vec3 vSunShadowVector;
{
float invR=1.0f/(sqrtf(lightVector.x*lightVector.x + lightVector.y*lightVector.y)+0.001f);
vSunShadowVector = lightVector*invR;
}
float fSkyLuminosity = ColorLuminosity( fSkyCol[0],fSkyCol[1],fSkyCol[2] );
float fBrightness,fBrightnessShadowmap;
eLightAlgorithm lightAlgo = pSettings->eAlgo;
if (bUseFastLighting)
lightAlgo = eDP3;
// Execute actual lighting algorithm
if (lightAlgo == eDP3)
{
////////////////////////////////////////////////////////////////////////
// DP3 lighting
////////////////////////////////////////////////////////////////////////
for (j = rc.top; j < rc.bottom; j++)
{
for (i = rc.left; i < rc.right; i++)
{
// Calc vertex normal and Make the dot product
Vec3 vNormal = CalcVertexNormal( i,j,pHeightmapData,m_resolution,fHeightScale );
fDP3 = lightVector.Dot(vNormal);
assert(fDP3>=-1.01f && fDP3<=1.01f);
// Calculate the intensity
float fLight = ((fDP3 + 1.0f)*0.5f + fAmbient) * 255.0f;
fBrightness = min(1,(fDP3+1.0f) + fAmbient );
assert(fLight>=0.0f);
float fSunVisibility = 1;
if (bTerrainShadows)
{
fSunVisibility = GetSunAmount(pHeightmapData,i,j,fInvHeightScale,vSunShadowVector,fShadowBlur);
if (fSunVisibility < 1)
{
fSunVisibility = (1.0f-fShadowIntensity) + (fShadowIntensity)*fSunVisibility;
fLight = fAmbient255*(1.0f-fSunVisibility) + fSunVisibility*fLight;
fBrightness = fAmbient*(1.0f-fSunVisibility) + fSunVisibility*fBrightness;
}
}
fBrightnessShadowmap = fBrightness;
assert(fLight>=0.0f);
// Apply shadow map.
if (bStatObjShadows && fSunVisibility > 0)
{
float fShadow = shadowmap.ValueAt(i-rc.left,j-rc.top)*(1.0f/255.0f);
fLight = fAmbient255*fShadow + (1.0f-fShadow)*fLight;
// Calculate brightness after applying shadow map.
fBrightnessShadowmap = fAmbient*fShadow + (1.0f-fShadow)*fBrightness;
}
assert(fLight>=0.0f);
// Get Current LM pixel.
uint *pLMPixel = &pLightmap[(i-rc.left) + (j-rc.top)*m_sectorResolution];
//! Multiply light color with lightmap and sun color.
uint lr = ftoi(fLight*fSunCol[0]*GetRValue(*pLMPixel)) >> 8;
uint lg = ftoi(fLight*fSunCol[1]*GetGValue(*pLMPixel)) >> 8;
uint lb = ftoi(fLight*fSunCol[2]*GetBValue(*pLMPixel)) >> 8;
//! Clamp color to 255.
if (lr > 255) lr = 255;
if (lg > 255) lg = 255;
if (lb > 255) lb = 255;
*pLMPixel = RGB(lr,lg,lb);
if (bPaintBrightness)
{
brightness = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightness*pSettings->sunMultiplier ) );
brightness_shadowmap = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightnessShadowmap*pSettings->sunMultiplier) );
// swap X/Y
float worldPixelX = i*m_pixelSizeInMeters;
float worldPixelY = j*m_pixelSizeInMeters;
m_vegetationMap->PaintBrightness( worldPixelY,worldPixelX,m_pixelSizeInMeters,m_pixelSizeInMeters,brightness,brightness_shadowmap );
// Write the lighting bits
if (pLightingBits)
{
if (brightness_shadowmap > LIGHTBIT_INSHADOW_LEVEL)
{
uint pos = i + j*iWidth;
pLightingBits->set(pos);
}
}
}
}
}
}
else if (lightAlgo == eHemisphere)
{
////////////////////////////////////////////////////////////////////////
// Hemisphere lighting
////////////////////////////////////////////////////////////////////////
float fSunWeight = 1;
float fSkyWeight = 0;
float fMaxWeight = 0.5f;
for (j = rc.top; j < rc.bottom; j++)
{
for (i = rc.left; i < rc.right; i++)
{
// Calc vertex normal and Make the dot product
Vec3 vNormal = CalcVertexNormal( i,j,pHeightmapData,m_resolution,fHeightScale );
fDP3 = lightVector.Dot(vNormal);
//assert(fDP3 >= -1.0f);
//assert(fDP3 <= 1.0f);
if (fDP3 <= -1.0f) fDP3 = -0.9999f;
if (fDP3 >= 1.0f) fDP3 = 0.9999f;
fBrightness = min(1,(fDP3+1.0f) + fAmbient );
// Calculate the angle
float fW = acos(fDP3);
//assert(!_isnan(fW));
//assert(fW >= 0.0f);
//assert(fW <= PI);
// Calculate weights for sun / sky color
if (fW > PI_HALF)
{
fSunWeight = sinf(fW) * 0.5f;
fSkyWeight = 1.0f - fSunWeight;
}
else
{
fSkyWeight = sinf(fW) * 0.5f;
fSunWeight = 1.0f - fSkyWeight;
}
float fSunVisibility = 1;
if (bTerrainShadows)
{
fSunVisibility = GetSunAmount(pHeightmapData,i,j,fInvHeightScale,vSunShadowVector,fShadowBlur);
if (fSunVisibility < 1)
{
fSunVisibility = (1.0f-fShadowIntensity) + (fShadowIntensity)*fSunVisibility;
fSunWeight *= fSunVisibility;
fSkyWeight = (1.0f-fSunWeight);
fBrightness = fAmbient*(1.0f-fSunVisibility) + fSunVisibility*fBrightness;
}
}
fBrightnessShadowmap = fBrightness;
/*
if (bPaintBrightness)
{
// Calculate brightness before applying shadowmaps.
if (fDP3 < 0)
{
// Dark side.
float l = fSunWeight * pSettings->sunMultiplier;
float lum = l + fSkyWeight*fSkyLuminosity;
brightness = min( MAX_BRIGHTNESS,ftoi(lum*MAX_BRIGHTNESS) );
}
else
{
// Sunny side.
float sun = fSunVisibility;
float sky = 1.0f - sun;
float lum = sun*pSettings->sunMultiplier + sky*fSkyLuminosity;
brightness = min( MAX_BRIGHTNESS,ftoi(lum*MAX_BRIGHTNESS) );
}
brightness_shadowmap = brightness;
}
*/
// Apply shadow map.
if (bStatObjShadows && fSunVisibility > 0)
{
float fShadow = shadowmap.ValueAt(i-rc.left,j-rc.top)*(1.0f/255.0f);
fSunWeight = fSunWeight * (1.0f-fShadow);
fSkyWeight = (1.0f - fSunWeight);
// Calculate brightness after applying shadow map.
fBrightnessShadowmap = fAmbient*fShadow + (1.0f-fShadow)*fBrightness;
/*
if (bPaintBrightness)
{
if (fDP3 < 0)
{
// Dark side.
// Calculate brightness before after applying shadowmaps.
float l = fSunWeight * pSettings->sunMultiplier;
float lum = l + fSkyWeight*fSkyLuminosity;
brightness_shadowmap = min( MAX_BRIGHTNESS,ftoi(lum*MAX_BRIGHTNESS) );
}
else
{
// Sunny side.
// Calculate brightness before after applying shadowmaps.
float sun = (1.0f - fShadow)*fSunVisibility;
float sky = 1.0f - sun;
float lum = sun*pSettings->sunMultiplier + sky*fSkyLuminosity;
brightness_shadowmap = min( MAX_BRIGHTNESS,ftoi(lum*MAX_BRIGHTNESS) );
}
}
*/
}
if (bPaintBrightness)
{
brightness = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightness*pSettings->sunMultiplier ) );
brightness_shadowmap = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightnessShadowmap*pSettings->sunMultiplier) );
// swap X/Y
float worldPixelX = i*m_pixelSizeInMeters;
float worldPixelY = j*m_pixelSizeInMeters;
m_vegetationMap->PaintBrightness( worldPixelY,worldPixelX,m_pixelSizeInMeters,m_pixelSizeInMeters,brightness,brightness_shadowmap );
// Write the lighting bits
if (pLightingBits)
{
if (brightness_shadowmap > LIGHTBIT_INSHADOW_LEVEL)
{
uint pos = i + j*iWidth;
pLightingBits->set(pos);
}
}
}
// ScaleSun^2
//fScaleSun *= fScaleSun;
// ScaleSky^2
//fScaleSky *= fScaleSky;
// Get Current LM pixel.
uint *pLMPixel = &pLightmap[(i-rc.left) + (j-rc.top)*m_sectorResolution];
//assert(fScaleSun>=0);
//assert(fScaleSky>=0);
// Apply weights to colors, and multiply with lightmap.
uint lr = ftoi((fSunWeight*fSunCol[0] + fSkyWeight*fSkyCol[0]) * GetRValue(*pLMPixel));
uint lg = ftoi((fSunWeight*fSunCol[1] + fSkyWeight*fSkyCol[1]) * GetGValue(*pLMPixel));
uint lb = ftoi((fSunWeight*fSunCol[2] + fSkyWeight*fSkyCol[2]) * GetBValue(*pLMPixel));
//! Clamp color to 255.
if (lr > 255) lr = 255;
if (lg > 255) lg = 255;
if (lb > 255) lb = 255;
*pLMPixel = RGB(lr,lg,lb);
}
}
}
else if (lightAlgo == ePrecise)
{
assert(m_heightmap->GetWidth()==m_heightmap->GetHeight());
bool bHaveSkyColor = (fSkyCol[0]!=0) || (fSkyCol[1]!=0) || (fSkyCol[2]!=0);
float fSunBrightness=0;
////////////////////////////////////////////////////////////////////////
// Precise lighting
////////////////////////////////////////////////////////////////////////
for (j = rc.top; j < rc.bottom; j++)
for (i = rc.left; i < rc.right; i++)
{
float fr=0.0f,fg=0.0f,fb=0.0f; // 0..1..
// Calc vertex normal and Make the dot product
Vec3 vNormal = CalcVertexNormal( i,j,pHeightmapData,m_resolution,fHeightScale);
//assert(vNormal.z>=0.0f);
fDP3 = lightVector.Dot(vNormal);
// Calculate the intensity (basic lambert, this is incorrect for fuzzy materials like grass, more smooth around 0 would be better)
float fLight = max(fDP3,0); // 0..1
fBrightness = min(1,(fDP3+1.0f) + fAmbient );
float fSunVisibility = 1;
// in shadow
if (bTerrainShadows)
{
// label_dddd remove soon - and integrate in other calculation modes
fSunVisibility = GetSunAccessibilityFast(i,j);
fBrightness = fAmbient*(1.0f-fSunVisibility) + fSunVisibility*fBrightness;
}
fBrightnessShadowmap = fBrightness;
fLight *= fSunVisibility;
float fSkyWeight = 0;
if(bHaveSkyColor)
{
if(pSettings->iHemiSamplQuality)
{
fSkyWeight=GetSkyAccessibilityFast(i,j); // 0..1
}
else fSkyWeight=vNormal.z*0.6f+0.4f; // approximate the sky accissibility without hills, only based only on surface slope
assert(fSkyWeight>=0.0f && fSkyWeight<=1.0f);
//! Multiply light color with lightmap and sun color.
fr += fSkyWeight*fSkyCol[0]; // 0..1..
fg += fSkyWeight*fSkyCol[1]; // 0..1..
fb += fSkyWeight*fSkyCol[2]; // 0..1..
}
// Apply shadow map (shadows from objects)
if(bStatObjShadows)
{
float fShadow = shadowmap.ValueAt(i-rc.left,j-rc.top)*(1.0f/255.0f);
float fShadowMapVisibility = 1.0f - fShadow;
fLight *= fShadowMapVisibility;
// Calculate brightness after applying shadow map.
fBrightnessShadowmap = fAmbient*fShadow + (1.0f-fShadow)*fBrightness;
}
fr += fLight*fSunCol[0]; // 0..1..
fg += fLight*fSunCol[1]; // 0..1..
fb += fLight*fSunCol[2]; // 0..1..
// Get Current LM pixel.
uint *pLMPixel = &pLightmap[(i-rc.left) + (j-rc.top)*m_sectorResolution]; // uint RGB
// Clamp color to 255.
uint lr = min(ftoi(fr*GetRValue(*pLMPixel)),255); // 0..255
uint lg = min(ftoi(fg*GetGValue(*pLMPixel)),255); // 0..255
uint lb = min(ftoi(fb*GetBValue(*pLMPixel)),255); // 0..255
// store it
*pLMPixel = RGB(lr,lg,lb);
// brightness for vegetation
if (bPaintBrightness)
{
brightness = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightness*pSettings->sunMultiplier ) );
brightness_shadowmap = min( MAX_BRIGHTNESS,ftoi( MAX_BRIGHTNESS*fBrightnessShadowmap*pSettings->sunMultiplier) );
// swap X/Y
float worldPixelX = i*m_pixelSizeInMeters;
float worldPixelY = j*m_pixelSizeInMeters;
m_vegetationMap->PaintBrightness( worldPixelY,worldPixelX,m_pixelSizeInMeters,m_pixelSizeInMeters,brightness,brightness_shadowmap );
// Write the lighting bits
if (pLightingBits)
{
if (brightness_shadowmap > LIGHTBIT_INSHADOW_LEVEL)
{
uint pos = i + j*iWidth;
pLightingBits->set(pos);
}
}
}
}
}
SetSectorFlags( sector,eSectorLightmapValid );
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::IsOccluded( float *pHeightmapData, int iWidth, int iHeight,int iStartX, int iStartY, int lightDirX,int lightDirY,float fClimbPerStep)
{
////////////////////////////////////////////////////////////////////////
// Checks if a terrain point can be illuminated by a given directional
// light
////////////////////////////////////////////////////////////////////////
int iDeltaX, iDeltaY;
int iUDeltaX, iUDeltaY;
int iXAdd, iYAdd;
int iError, iLoop;
int X1, Y1, X2, Y2;
if (iStartX < 0 || iStartY < 0 || iStartX >= iWidth || iStartY >= iHeight)
return false;
// Initial terrain height
// Set the startpoint of the ray
X1 = iStartX;
Y1 = iStartY;
// Calculate the endpoint
X2 = X1 + lightDirX;
Y2 = Y1 + lightDirY;
iDeltaX = X2 - X1; // Work out X delta
iDeltaY = Y2 - Y1; // Work out Y delta
iUDeltaX = abs(iDeltaX); // iUDeltaX is the unsigned X delta
iUDeltaY = abs(iDeltaY); // iUDeltaY is the unsigned Y delta
// Work out direction to step in the Y direction
if (iDeltaX < 0)
iXAdd = -1;
else
iXAdd = 1;
// Work out direction to step in the Y direction
if (iDeltaY < 0)
iYAdd = -1;
else
iYAdd = 1;
iError = 0;
iLoop = 0;
/*
int iTerrainPointHeight = ftoi(pHeightmapData[X1 + Y1*iWidth]*255.0f);
if (iUDeltaX > iUDeltaY)
{
// Delta X > Delta Y
do
{
iError += iUDeltaY;
// Time to move up / down ?
if (iError >= iUDeltaX)
{
iError -= iUDeltaX;
Y1 += iYAdd;
}
iLoop++;
// Stop when we reach the border
if (X1 < 0 || Y1 < 0 || X1 >= iWidth || Y1 >= iHeight)
return false;
// Verify heightpoint at (X1, Y1)
if (iTerrainPointHeight < ftoi(pHeightmapData[X1 + Y1 * iWidth]*255.0f))
return true;
// Increase the height to similuate the elevated sun
iTerrainPointHeight += iClimbPerStep;
// Abort when it is impossible for the terrian point to be occluded
if (iTerrainPointHeight > 65536)
return false;
// Move horizontally
X1 += iXAdd;
}
while (iLoop < iUDeltaX); // Repeat for x length of line
}
else
{
// Delta Y > Delta X
do
{
iError += iUDeltaX;
// Time to move left / right?
if (iError >= iUDeltaY)
{
iError -= iUDeltaY;
// Move across
X1 += iXAdd;
}
iLoop++;
// Stop when we reach the border
if (X1 < 0 || Y1 < 0 || X1 >= iWidth || Y1 >= iHeight)
return false;
// Verify heightpoint at (X1, Y1)
if (iTerrainPointHeight < ftoi(pHeightmapData[X1 + Y1 * iWidth]*255.0f))
return true;
// Increase the height to similuate the elevated sun
iTerrainPointHeight += iClimbPerStep;
// Abort when it is impossible for the terrian point to be occluded
if (iTerrainPointHeight > 65536)
return false;
// Move up / down a row
Y1 += iYAdd;
}
while (iLoop < iUDeltaY); // Repeat for y length of line
}
*/
// maxSteps - Abort when it is impossible for the terrian point to be occluded, ray goes out of height borders.
float fTerrainPointHeight = pHeightmapData[X1 + Y1*iWidth];
int maxSteps = ftoi((m_terrainMaxZ - fTerrainPointHeight) / fClimbPerStep);
int maxStepsX;
int maxStepsY;
if (iXAdd > 0)
maxStepsX = (iWidth-X1);
else
maxStepsX = (X1-0);
if (iYAdd > 0)
maxStepsY = (iHeight - Y1);
else
maxStepsY = (Y1 - 0);
maxSteps = MIN( maxSteps,maxStepsX );
maxSteps = MIN( maxSteps,maxStepsY );
if (iUDeltaX > iUDeltaY)
{
int NumSteps = iUDeltaX;
NumSteps = MIN( NumSteps,maxSteps );
// Delta X > Delta Y
while (iLoop < NumSteps) // Repeat for x length of line
{
iError += iUDeltaY;
// Time to move up / down ?
if (iError >= iUDeltaX)
{
iError -= iUDeltaX;
Y1 += iYAdd;
}
// Increase the height to similuate the elevated sun
fTerrainPointHeight += fClimbPerStep;
/*
// Stop when we reach the border
if (X1 < 0 || Y1 < 0 || X1 >= iWidth || Y1 >= iHeight)
return false;
*/
// Verify heightpoint at (X1, Y1)
if (fTerrainPointHeight <= pHeightmapData[X1 + Y1 * iWidth])
return true;
// Move horizontally
X1 += iXAdd;
iLoop++;
}
}
else
{
int NumSteps = iUDeltaY;
NumSteps = MIN( NumSteps,maxSteps );
// Delta Y > Delta X
while (iLoop < NumSteps) // Repeat for y length of line
{
iError += iUDeltaX;
// Time to move left / right?
if (iError >= iUDeltaY)
{
iError -= iUDeltaY;
// Move across
X1 += iXAdd;
}
// Increase the height to similuate the elevated sun
fTerrainPointHeight += fClimbPerStep;
/*
// Stop when we reach the border
if (X1 < 0 || Y1 < 0 || X1 >= iWidth || Y1 >= iHeight)
return false;
*/
// Verify heightpoint at (X1, Y1)
if (fTerrainPointHeight <= pHeightmapData[X1 + Y1 * iWidth])
return true;
// Move up / down a row
Y1 += iYAdd;
iLoop++;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! Generate shadows from static objects and place them in shadow map bitarray.
void CTerrainTexGen::GenerateShadowmap( CPoint sector,CByteImage &shadowmap,float shadowAmmount,const Vec3 &sunVector )
{
// if(!m_pTerrain)
// return;
SSectorInfo si;
GetIEditor()->GetHeightmap()->GetSectorsInfo( si );
int width = shadowmap.GetWidth();
int height = shadowmap.GetHeight();
int i = 0;
int numSectors = si.numSectors;
int sectorTexSize = shadowmap.GetWidth();
int sectorTexSize2 = sectorTexSize*2;
assert( sectorTexSize > 0 );
uint shadowValue = shadowAmmount;
CMemoryBlock mem;
if (!mem.Allocate( sectorTexSize2*sectorTexSize2*3 ))
{
m_bNotValid = true;
return;
}
unsigned char *sectorImage2 = (unsigned char*)mem.GetBuffer();
Vec3 wp = GetIEditor()->GetHeightmap()->GetTerrainGrid()->SectorToWorld( sector );
GetIEditor()->Get3DEngine()->MakeSectorLightMap( wp.x+0.1f,wp.y+0.1f,sectorImage2,sectorTexSize2 );
// Scale sector image down and store into the shadow map.
int pos;
uint color;
for (int j = 0; j < sectorTexSize; j++)
{
int sx1 = sectorTexSize - j - 1;
for (int i = 0; i < sectorTexSize; i++)
{
pos = (i + j*sectorTexSize2)*2*3;
color = (shadowValue*
((uint)
(255-sectorImage2[pos]) +
(255-sectorImage2[pos+3]) +
(255-sectorImage2[pos+sectorTexSize2*3]) +
(255-sectorImage2[pos+sectorTexSize2*3+3])
)) >> 10;
// color = color*shadowValue >> 8;
// swap x/y
//color = (255-sectorImage2[(i+j*sectorTexSize)*3]);
shadowmap.ValueAt(sx1,i) = color;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::CalcTerrainMaxZ()
{
// Caluclate max terrain height.
m_terrainMaxZ = 0;
int hmapSize = m_hmap.GetSize() / sizeof(float);
float *pData = m_hmap.GetData();
for (int i = 0; i < hmapSize; i++)
{
if (pData[i] > m_terrainMaxZ)
m_terrainMaxZ = pData[i];
}
}
//////////////////////////////////////////////////////////////////////////
class CGenSectorThread : public CThread
{
public:
CImage sectorImage;
CImage *pSurfaceTexture;
CTerrainTexGen *pTexGen;
int sectorResolution;
int flags;
volatile bool bKillThread;
MTDeque<CPoint> *pJobs;
// Event 0 == start job.
CEvent eventStart;
CEvent *pEventDone;
CGenSectorThread( int resolution )
{
sectorResolution = resolution;
// start this thread.
sectorImage.Allocate( sectorResolution,sectorResolution );
bKillThread = false;
}
bool DoJob()
{
CPoint sector;
if (!pJobs->pop_front(sector))
{
// Nothing to do.
return false;
}
CRect rect(0,0,sectorResolution,sectorResolution);
if (!pTexGen->GenerateSectorTexture( sector,rect,flags,sectorImage ))
return false;
CRect sectorRect;
pTexGen->GetSectorRect(sector,sectorRect);
pSurfaceTexture->SetSubImage( sectorRect.left,sectorRect.top,sectorImage );
return true;
}
protected:
virtual void Run()
{
// start this thread.
sectorImage.Allocate( sectorResolution,sectorResolution );
//CSingleLock lock( &eventStart );
//eventStart.Lock();
//eventStart.ResetEvent();
while (!bKillThread)
{
if (bKillThread)
break;
if (!DoJob())
{
bKillThread = true;
break;
}
}
// Signal done event.
pEventDone->SetEvent();
}
};
//////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::GenerateSurfaceTexture( int flags,CImage &surfaceTexture )
{
if (m_bNotValid)
return false;
int num = 0;
Init( surfaceTexture.GetWidth() );
// Generate texture for all sectors.
bool bProgress = surfaceTexture.GetWidth() >= 1024;
if (!UpdateWholeHeightmap())
return false;
//////////////////////////////////////////////////////////////////////////
LightingSettings *ls = GetIEditor()->GetDocument()->GetLighting();
// Needed for faster shadow calculations and hemisphere sampling
CalcTerrainMaxZ();
//////////////////////////////////////////////////////////////////////////
CWaitProgress wait( _T("Generating Surface Texture") );
if (bProgress)
wait.Start();
if (flags & ETTG_INVALIDATE_LAYERS)
{
// Only invalidate layres ones at start.
InvalidateLayers();
flags &= ~ETTG_INVALIDATE_LAYERS;
}
// Multi-Threaded surface generation.
/*
flags &= ~ETTG_STATOBJ_SHADOWS;
MTDeque<CPoint> jobs;
CRect sectorRect;
CImage sectorImage;
sectorImage.Allocate( m_sectorResolution,m_sectorResolution );
CRect rect(0,0,m_sectorResolution,m_sectorResolution);
int freeThread = 0;
// Push all jobs in.
for (int y = 0; y < m_numSectors; y++)
{
for (int x = 0; x < m_numSectors; x++)
{
jobs.push_back( CPoint(x,y) );
}
}
int totalJobs = jobs.size();
// Start threads.
#define NUM_THREADS 2
CGenSectorThread *pThread[NUM_THREADS];
CEvent threadEventDone[NUM_THREADS];
CSyncObject* pEvents[NUM_THREADS];
// Start threads.
int i;
for (i = 0; i < NUM_THREADS; i++)
{
pEvents[i] = &threadEventDone[i];
pThread[i] = new CGenSectorThread( m_sectorResolution );
pThread[i]->pJobs = &jobs;
pThread[i]->pEventDone = &threadEventDone[i];
pThread[i]->pSurfaceTexture = &surfaceTexture;
pThread[i]->pTexGen = this;
pThread[i]->flags = flags;
}
CMultiLock multiLockEventDone( pEvents,NUM_THREADS );
// Do first sector, preload all.
// Do it in main thread.
pThread[0]->DoJob();
for (i = 0; i < NUM_THREADS; i++)
{
pThread[i]->Start();
}
// Track progress.
if (bProgress)
{
while (!jobs.empty())
{
int jobsLeft = jobs.size();
int numDone = totalJobs - jobsLeft;
wait.Step( numDone*100/totalJobs );
Sleep(100);
// num++;
}
}
// Wait untill all threads die.
multiLockEventDone.Lock( INFINITE );
*/
CRect sectorRect;
CRect rect(0,0,m_sectorResolution,m_sectorResolution);
CImage sectorImage;
if (!sectorImage.Allocate( m_sectorResolution,m_sectorResolution ))
{
m_bNotValid = true;
return false;
}
// Normal, not multithreaded surface generation code.
for (int y = 0; y < m_numSectors; y++)
{
for (int x = 0; x < m_numSectors; x++)
{
if (bProgress)
{
if (!wait.Step( num*100/(m_numSectors*m_numSectors) ))
return false;
num++;
}
CPoint sector(x,y);
if (!GenerateSectorTexture( sector,rect,flags,sectorImage ))
return false;
GetSectorRect(sector,sectorRect);
surfaceTexture.SetSubImage( sectorRect.left,sectorRect.top,sectorImage );
}
}
// M.M. to take a look at the result of the lighting calculation
// CImageUtil::SaveImage( "c:\\temp\\out.bmp", surfaceTexture );
if (bProgress)
wait.Stop();
return true;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::UpdateSectorHeightmap( CPoint sector )
{
int sectorFlags = GetSectorFlags(sector);
if (!(sectorFlags & eSectorHeightmapValid))
{
CRect rect;
GetSectorRect(sector,rect);
// Increase rectangle by one pixel..
rect.InflateRect( 2,2,2,2 );
//m_heightmap->GetData( rect,m_hmap,true,true );
m_heightmap->GetData( rect,m_hmap,true,true );
SetSectorFlags( sector,eSectorHeightmapValid );
}
}
//////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::UpdateWholeHeightmap()
{
bool bAllValid = true;
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
if (!(m_sectorGrid[i].flags & eSectorHeightmapValid))
{
// Mark all heighmap sector flags as valid.
bAllValid = false;
m_sectorGrid[i].flags |= eSectorHeightmapValid;
}
}
if (!bAllValid)
{
CRect rect( 0,0,m_hmap.GetWidth(),m_hmap.GetHeight() );
if (!m_heightmap->GetData( rect,m_hmap,true,true ))
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
int CTerrainTexGen::GetSectorFlags( CPoint sector )
{
assert( sector.x >= 0 && sector.x < m_numSectors && sector.y >= 0 && sector.y < m_numSectors );
int index = sector.x + sector.y*m_numSectors;
return m_sectorGrid[index].flags;
}
//////////////////////////////////////////////////////////////////////////
CTerrainTexGen::SectorInfo& CTerrainTexGen::GetSectorInfo( CPoint sector )
{
assert( sector.x >= 0 && sector.x < m_numSectors && sector.y >= 0 && sector.y < m_numSectors );
int index = sector.x + sector.y*m_numSectors;
return m_sectorGrid[index];
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::SetSectorFlags( CPoint sector,int flags )
{
assert( sector.x >= 0 && sector.x < m_numSectors && sector.y >= 0 && sector.y < m_numSectors );
m_sectorGrid[sector.x + sector.y*m_numSectors].flags |= flags;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::InvalidateSector( CPoint sector )
{
assert( sector.x >= 0 && sector.x < m_numSectors && sector.y >= 0 && sector.y < m_numSectors );
int pos = sector.x + sector.y*m_numSectors;
m_sectorGrid[pos].flags = 0;
if (m_sectorGrid[pos].lightmap)
{
delete m_sectorGrid[pos].lightmap;
m_sectorGrid[pos].lightmap = 0;
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::InvalidateLayers()
{
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
m_sectorGrid[i].flags &= ~eSectorLayersValid;
}
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::InvalidateLighting()
{
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
m_sectorGrid[i].flags &= ~eSectorLightmapValid;
if (m_sectorGrid[i].lightmap)
{
delete m_sectorGrid[i].lightmap;
m_sectorGrid[i].lightmap = 0;
}
}
}
//////////////////////////////////////////////////////////////////////////
int CTerrainTexGen::GetLayerCount() const
{
return m_layers.size();
}
//////////////////////////////////////////////////////////////////////////
CLayer* CTerrainTexGen::GetLayer( int index ) const
{
assert( index >= 0 && index < m_layers.size() );
return m_layers[index].pLayer;
}
//////////////////////////////////////////////////////////////////////////
void CTerrainTexGen::ClearLayers()
{
for (int i = 0; i < m_layers.size(); i++)
{
}
m_layers.clear();
}
//////////////////////////////////////////////////////////////////////////
CByteImage* CTerrainTexGen::GetLayerMask( CLayer* layer )
{
for (int i = 0; i < m_layers.size(); i++)
{
if (m_layers[i].pLayer == layer)
return m_layers[i].layerMask;
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
bool CTerrainTexGen::RefreshAccessibility( const LightingSettings *inpLSettings,int genFlags )
{
// refresh sky?
bool bUpdateSkyAccessiblity=false;
if(!m_SkyAccessiblity.GetData() || m_iCachedSkyAccessiblityQuality!=inpLSettings->iHemiSamplQuality)
{
if(inpLSettings->eAlgo == ePrecise && !(genFlags&ETTG_FAST_LLIGHTING))
{
m_iCachedSkyAccessiblityQuality=inpLSettings->iHemiSamplQuality;
if(inpLSettings->iHemiSamplQuality)
bUpdateSkyAccessiblity=true;
}
}
bool bTerrainShadows = inpLSettings->bTerrainShadows && (!(genFlags & ETTG_NO_TERRAIN_SHADOWS));
// refresh sun?
bool bUpdateSunAccessiblity=false;
if(bTerrainShadows)
if(!m_SunAccessiblity.GetData()
|| m_iCachedSunBlurLevel!=inpLSettings->iShadowBlur
|| m_vCachedSunDirection!=inpLSettings->GetSunVector())
{
m_iCachedSunBlurLevel=inpLSettings->iShadowBlur;
m_vCachedSunDirection=inpLSettings->GetSunVector();
bUpdateSunAccessiblity=true;
}
if(!bUpdateSkyAccessiblity && !bUpdateSunAccessiblity)
return true; // no update neccessary because
DWORD w=m_heightmap->GetWidth(),h=m_heightmap->GetHeight(); // // unscaled
if(m_resolution<w)w=m_resolution; // use minimum needed size (fast in preview))
if(m_resolution<h)h=m_resolution; // use minimum needed size (fast in preview)
CFloatImage FloatHeightMap; // unscaled
if (!FloatHeightMap.Allocate(w,h))
{
m_bNotValid = true;
return false;
}
CRect full(0,0,m_heightmap->GetWidth(),m_heightmap->GetHeight());
m_heightmap->GetData( full,FloatHeightMap,false,false ); // no smooth, no noise
// scale height to fit with the scaling in x and y direction
{
assert(w==h);
float fHeightScale=CalcHeightScale(w,m_heightmap->GetWidth(),m_heightmap->GetUnitSize());
float *p=FloatHeightMap.GetData();
// copy intermediate to result
for(DWORD i=0;i<w*h;i++,p++)
(*p) *= fHeightScale; // from 8.8 fixpoint to 8bit
}
// sky
if(bUpdateSkyAccessiblity)
{
DWORD dwSkyAngleSteps=inpLSettings->iHemiSamplQuality*3+1; // 1 .. 31
CHeightmapAccessibility<CHemisphereSink_Solid> calc(w,h,dwSkyAngleSteps);
if(!calc.Calc(FloatHeightMap.GetData()))
return(false);
// store result more compressed
if (!m_SkyAccessiblity.Allocate(w,h))
{
m_bNotValid = true;
return false;
}
const unsigned short *src=calc.GetSamplePtr();
unsigned char *dst=m_SkyAccessiblity.GetData();
// copy intermediate to result
for(DWORD i=0;i<w*h;i++)
{
unsigned short in = *src++;
unsigned char out = (unsigned char) min( ((in+0x88)>>8), 255 );
*dst++ = out; // from 8.8 fixpoint to 8bit
}
}
// sun
if(bUpdateSunAccessiblity)
{
// quality depends on blur level
DWORD dwSunAngleSteps=(inpLSettings->iShadowBlur+5*2-1)/5; // 1..
float fBlurAngle=inpLSettings->iShadowBlur*0.5f*gf_DEGTORAD; // 0.5 means 1 unit is 0.5 degrees
float fHAngle=-(inpLSettings->iSunRotation-90.0f) * gf_DEGTORAD;
float fMinHAngle=fHAngle-fBlurAngle*0.5f, fMaxHAngle=fHAngle+fBlurAngle*0.5f;
float fVAngle=gf_PI_DIV_2-asin(inpLSettings->iSunHeight*0.01f);
float fMinVAngle=max(0,fVAngle-fBlurAngle*0.5f), fMaxVAngle=min(gf_PI_DIV_2,fVAngle+fBlurAngle*0.5f);
CHeightmapAccessibility<CHemisphereSink_Slice> calc(w,h,dwSunAngleSteps,fMinHAngle,fMaxHAngle);
calc.m_Sink.SetMinAndMax(fMinVAngle,fMaxVAngle);
if(!calc.Calc(FloatHeightMap.GetData()))
return(false);
// store result more compressed
if (!m_SunAccessiblity.Allocate(w,h))
{
m_bNotValid = true;
return false;
}
const unsigned short *src=calc.GetSamplePtr();
unsigned char *dst=m_SunAccessiblity.GetData();
// copy intermediate to result
for(DWORD i=0;i<w*h;i++)
{
unsigned short in = *src++;
unsigned char out = (unsigned char) min( ((in+0x88)>>8), 255 );
*dst++ = out; // from 8.8 fixpoint to 8bit
}
}
return true;
}