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

393 lines
11 KiB
C++

//
// Crytek Source code
//
// written my Martin Mittring
//
// **** tiled heightmap
// check out of memory with exception handling
//
//
// Dependencies: HorizonTracker.h
//
#pragma once
#include <assert.h> // assert()
#include "HorizonTracker.h" // CHorizonTracker
#include <math.h> // sin()
#include <vector> // STL vector<>
// you can use CHemisphereSolid
// or implement your own CHemisphereSink_Solid type (e.g. sum colors from given diffuse texture, sum circular spot for soft shadow, ... )
// Full Sky is 255
// brightness is equally distributed over the hemisphere
// sample precision is 8.8 fixpoint
// can be used as template argument (THemisphere)
class CHemisphereSink_Solid
{
public:
typedef unsigned short SampleType; //!< 8.8 fix point
// ---------------------------------------------------------------------------------
//! constructor
//! \param indwAngleSteps [1..[
CHemisphereSink_Solid( const DWORD indwAngleSteps )
{
// to scale the input to the intermediate range
m_fScale= 256.0f // 256 for 8bit fix point,
*255.0f // 255 for max unsigned char
/((float)indwAngleSteps) // AddToIntermediate is called indwAngleSteps times
/(float)(gf_PI_DIV_2*gf_PI_DIV_2); // scale form [0..PI/2[ * [0..PI/2[ to 1
}
//!
//!
void InitSample( SampleType &inoutValue ) const
{
inoutValue=0; // 8.8 fix point
}
//! \infAngle infAngle in rad (not needed here because every direction is equal)
void SetAngle( const float infAngle )
{
}
//! \param infSlope [0..[
//! \param inoutValue result is added to this value
void AddWedgeArea( const float infSlope, SampleType &inoutValue ) const
{
float fWedgeHorizonAngle=(float)(gf_PI_DIV_2-atanf(-infSlope)); // [0..PI/2[
inoutValue += (SampleType)(m_fScale*(fWedgeHorizonAngle*fWedgeHorizonAngle));
}
// ---------------------------------------------------------------------------------
protected:
float m_fScale; //!< to scale the input to the intermediate range
}; // CHemisphereSink_Solid
// ------------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------
// only area between min and max angle is adding to the result
// useful for simulating a area light source
class CHemisphereSink_Slice : public CHemisphereSink_Solid
{
public:
//! constructor
CHemisphereSink_Slice( const DWORD indwAngleSteps ) :CHemisphereSink_Solid(indwAngleSteps)
{
m_fAngleAreaSubtract=0;
m_fFullAngleArea=0;
}
void SetMinAndMax( const float infMinAngle, const float infMaxAngle )
{
m_fAngleAreaSubtract=infMinAngle*infMinAngle;
m_fFullAngleArea=infMaxAngle*infMaxAngle - m_fAngleAreaSubtract;
m_fFullAngleArea=max(m_fFullAngleArea,0.0001f); // to avoid divide by zero
m_fScale*=(gf_PI_DIV_2*gf_PI_DIV_2) / m_fFullAngleArea;
}
void AddWedgeArea( const float infSlope, SampleType &inoutValue ) const
{
float fWedgeHorizonAngle=(float)(gf_PI_DIV_2-atanf(-infSlope)); // [0..PI/2[
float fAngleWedgeArea=( fWedgeHorizonAngle*fWedgeHorizonAngle - m_fAngleAreaSubtract);
if(fAngleWedgeArea>0)
{
if(fAngleWedgeArea<m_fFullAngleArea) inoutValue += (SampleType)(m_fScale*fAngleWedgeArea);
else inoutValue += (SampleType)(m_fScale*m_fFullAngleArea);
}
}
private:
float m_fAngleAreaSubtract; //!<
float m_fFullAngleArea; //!<
};
// ------------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------
template <class THemisphereSink>
class CHeightmapAccessibility
{
public:
//typedef typename THemisphereSink::SampleType SampleType;
typedef unsigned short SampleType; //!< 8.8 fix point
//! constructor
//! \param indwAngleSteps try 10 for average quality or more for better quality
//! \param indwWidth has to be power of two
//! \param indwHeight has to be power of two
CHeightmapAccessibility( const DWORD indwWidth, const DWORD indwHeight,
const DWORD indwAngleSteps,
const float infAngleStart=0.0f, const float infAngleEnd=(float)(gf_PI_MUL_2) ) :m_Sink(indwAngleSteps)
{
m_fAngleStart=infAngleStart;
m_fAngleEnd=infAngleEnd;
m_dwAngleSteps=indwAngleSteps;
assert(indwWidth>=0);
assert(indwHeight>=0);
m_dwResultWidth=indwWidth;
m_dwResultHeight=indwHeight;
m_ResultBuffer.resize(m_dwResultWidth*m_dwResultHeight); // check out of memory with exception handling
assert(m_ResultBuffer.size()==m_dwResultWidth*m_dwResultHeight);
m_bTiling=false;
}
//! check out of memory with exception handling
//! \param indwWidth has to be power of two
//! \param indwHeight has to be power of two
//! \return true=success, false= out of memory
bool Calc( const float *inpHeightmap )
{
assert(inpHeightmap);
assert(m_fAngleEnd>=m_fAngleStart);
// clear buffer
{
for(DWORD i=0;i<m_dwResultWidth*m_dwResultHeight;i++)
m_Sink.InitSample(m_ResultBuffer[i]);
}
bool bProgress = false;
if (m_dwResultWidth >= 512)
bProgress = true;
CWaitProgress progress( "Calculating Sky Accessibility",false );
if (bProgress)
progress.Start();
// the horizon is subdivided in wedges
for(DWORD dwWedge=0;dwWedge<m_dwAngleSteps;dwWedge++)
{
if (bProgress)
{
if (!progress.Step((100*dwWedge)/m_dwAngleSteps))
return false;
}
float fAngle=(float)(dwWedge+0.5f)/(float)m_dwAngleSteps * (m_fAngleEnd-m_fAngleStart) + m_fAngleStart;
float fDx=cosf(fAngle), fDy=sinf(fAngle);
if(fabs(fDx)<fabs(fDy))
{
// lines are mainly vertical
float dx=fDx/fabsf(fDy);
CalcHeightmapAccessWedge( inpHeightmap, //
0,fDy>0?1:-1, // line direction
dx>0?1:-1,0, // line start direction (to fill area) = line step direction
fabsf(dx),m_dwResultWidth,m_dwResultHeight); //
}
else
{
// lines are mainly horizontal
float dy=fDy/fabsf(fDx);
CalcHeightmapAccessWedge( inpHeightmap, //
fDx>0?1:-1,0, // line direction
0,dy>0?1:-1, // line start direction (to fill area) = line step direction
fabsf(dy),m_dwResultHeight,m_dwResultWidth); //
}
}
if (bProgress)
progress.Stop();
return(true); // success
}
SampleType GetSampleAt( const DWORD indwX, const DWORD indwY ) const
{
return(m_ResultBuffer[indwX+indwY*m_dwResultWidth]);
}
const SampleType *GetSamplePtr() const
{
assert(m_ResultBuffer.size()==m_dwResultWidth*m_dwResultHeight);
return(&(m_ResultBuffer[0]));
}
// ---------------------------------------------------------------------------------
// public to make if more convenient for the user
THemisphereSink m_Sink; //!< is
bool m_bTiling; //!< true with tiling, false=faster
// ---------------------------------------------------------------------------------
private:
// properties (for a full hemisphere m_fAngleEnd-m_fAngleStart = gf_PI_DIV_2)
float m_fAngleStart; //!< in rad
float m_fAngleEnd; //!< in rad
DWORD m_dwAngleSteps; //!< should be >4, more m_dwAngleSteps results in better quality and less speed
typedef std::vector<SampleType> CSampleBuffer;
typedef std::vector<SampleType>::iterator CSampleBufferIt;
// result
CSampleBuffer m_ResultBuffer; //!<
DWORD m_dwResultWidth; //!< value is power of two
DWORD m_dwResultHeight; //!< value is power of two
// ---------------------------------------------------------------------------------
//! U is the direction we go always one pixel in positive direction (start point of the lines)
//! V is the line direction we go one pixel in negative or in positive or not (depending on iniFix8)
//! uses CalcHeightmapAccessWedge()
//! \param inpHeightmap must not be 0
//! \param infStep 0..1 (0.0f=no steps, 1.0f=45 degrees)
//! \param indwLineCount
//! \param indwLineLength
//! \param infResultScale
void CalcHeightmapAccessWedge( const float *inpHeightmap,
const int iLineDirX, const int iLineDirY,
const int iLineStepX, const int iLineStepY,
const float infStep, const DWORD indwLineCount, const DWORD indwLineLength )
{
assert(inpHeightmap);
const float fLenStep=sqrtf( fabsf(infStep) + 1.0f);
// this works only for power of two width and height
const DWORD dwXMask=m_dwResultWidth-1;
const DWORD dwYMask=m_dwResultHeight-1;
const DWORD dwXShift=GetIntLog2(m_dwResultWidth);
const DWORD dwYShift=GetIntLog2(m_dwResultHeight);
CHorizonTracker HorizonTracker;
// many lines fill the whole block
for(DWORD dwLine=0;dwLine<indwLineCount;dwLine++)
{
HorizonTracker.Clear();
int iX,iY;
// start position of each line
if(iLineDirY==0) // mainly horizonal lines
{
iX = iLineDirX>0 ? 0 : m_dwResultWidth-1;
iY = dwLine;
}
else // mainly vertical lines
{
iX = dwLine;
iY = iLineDirY>0 ? 0 : m_dwResultHeight-1;
}
float fFilterValue=0.5f; // 0..1
float fLen=0.0f;
// one line
if(!m_bTiling)
for(DWORD dwLinePos=0; dwLinePos<indwLineLength; dwLinePos++,fLen+=fLenStep)
{
assert(fFilterValue>=0 && fFilterValue<=1.0f);
// get two height samples
float fHeight1=inpHeightmap[ ( iX & dwXMask) + (( iY & dwYMask)<<dwXShift) ];
float fHeight2=inpHeightmap[ ((iX+iLineStepX) & dwXMask) + (((iY+iLineStepY) & dwYMask)<<dwXShift) ];
// get linear filtered height
float fFilteredHeight=fHeight1*(1.0f-fFilterValue) + fHeight2*fFilterValue;
if((iX>>dwXShift)!=0 || (iY>>dwYShift)!=0)
{
HorizonTracker.Clear();
iX&=dwXMask;
iY&=dwYMask;
}
float fSlope=HorizonTracker.Insert(fLen,fFilteredHeight);
m_Sink.AddWedgeArea(fSlope,m_ResultBuffer[iX + (iY<<dwXShift)]);
// advance one pixel
iX+=iLineDirX;iY+=iLineDirY;
fFilterValue+=infStep;
if(fFilterValue>1.0f)
{
fFilterValue-=1.0f;
iX+=iLineStepX;iY+=iLineStepY;
}
}
// one line (two times longer to be sure the result is correct with tiling - optimizable)
if(m_bTiling)
for(DWORD dwLinePos=0; dwLinePos<indwLineLength*2; dwLinePos++,fLen+=fLenStep)
{
assert(fFilterValue>=0 && fFilterValue<=1.0f);
// get two height samples
float fHeight1=inpHeightmap[ ( iX & dwXMask) + (( iY & dwYMask)<<dwXShift) ];
float fHeight2=inpHeightmap[ ((iX+iLineStepX) & dwXMask) + (((iY+iLineStepY) & dwYMask)<<dwXShift) ];
// get linear filtered height
float fFilteredHeight=fHeight1*(1.0f-fFilterValue) + fHeight2*fFilterValue;
float fSlope=HorizonTracker.Insert(fLen,fFilteredHeight);
if(dwLinePos>=indwLineLength) // first half of line length is neccessary to make it tileable
m_Sink.AddWedgeArea(fSlope,m_ResultBuffer[(iX& dwXMask) + ((iY&dwYMask)<<dwXShift)]);
// advance one pixel
iX+=iLineDirX;iY+=iLineDirY;
fFilterValue+=infStep;
if(fFilterValue>1.0f)
{
fFilterValue-=1.0f;
iX+=iLineStepX;iY+=iLineStepY;
}
}
}
}
}; // CHeightmapAccessibility