You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ryzom-core/code/nel/src/3d/zone_lighter.cpp

3865 lines
112 KiB
C++

15 years ago
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "std3d.h"
// FIXME: shouldn't this be configured outside?
#ifndef USE_JPEG
#define USE_JPEG
#endif
#include "nel/3d/zone_lighter.h"
#include "nel/3d/landscape.h"
#include "nel/3d/patchuv_locator.h"
#include "nel/3d/shape.h"
#include "nel/3d/mesh.h"
#include "nel/3d/mesh_multi_lod.h"
#include "nel/3d/mesh_mrm.h"
#include "nel/3d/transform_shape.h"
#include "nel/3d/water_shape.h"
#include "nel/3d/texture_file.h"
#include "nel/misc/common.h"
#include "nel/misc/thread.h"
#include "nel/misc/path.h"
#include "nel/misc/file.h"
#include "nel/misc/aabbox.h"
#include "nel/misc/algo.h"
#ifdef NL_OS_WINDOWS
# define WIN32_LEAN_AND_MEAN
# define NOMINMAX
# include <windows.h>
# include <winbase.h>
#endif // NL_OS_WINDOWS
using namespace NLMISC;
using namespace NL3D;
using namespace std;
// Define this to render the zbuffers into a bitmap zbuffer and save it into a jpeg
// #define SAVE_ZBUFFER "c:/temp"
#define DEFAULT_JITTER 0.4f
#define DEFAULT_ZBUFFER_LANDSCAPE_SIZE 32768
#define DEFAULT_ZBUFFER_OBJECT_SIZE (32768*3)
#define DEFAULT_SUN_DISTANCE 50000
#define DEFAULT_SUN_FOV (NLMISC::Pi/6)
#define DEFAULT_SUN_CENTER (CVector (0, 0, 0))
#define DEFAULT_SUN_RADIUS 5000
#define DEFAULT_SUN_SRQT_SAMPLES 4
// Bad coded: don't set too big else it allocates too much memory.
#define NL3D_ZONE_LIGHTER_CUBE_GRID_SIZE 16
// ***************************************************************************
/*
Documentation:
To light a zone, you must first adding shadow caster triangles using the addTriangle() methods.
Triangles can come from landscape zones or IG meshes. Then call the lighting process with ligth().
addTriangle ()
- Add landscape triangles to shadow caster triangle list
- Tesselate the landscape to shadow accuracy (2 meters)
- Add others triangles to shadow caster triangle list (trees, building)
- AlphaTest textures can be used here
light ()
The lighting process uses a software zbuffers render to compute shadow attenuation of the zone.
CRenderZBuffer () (multithread)
- Render shadow caster triangles into the light zbuffers for shadows. Each z value is tested and
written in the pixel and in the 8 neighbor pixels.
- There is a zbuffer per landscape softshadow sample and an additionnal zbuffer for objects. So
landscape triangles cast softshadows and object (trees and building) cast antialiased shadows.
- Render shadow caster triangles into a heightfield used for radiosity
buildZoneInformation ()
- Tesselate the landscape to shadow accuracy (2 meters)
- Compute lumel positions.
Lumel position is the average of lumel triangles center overlapping
the lumel but using the shadow accuracy triangle position because
we need the same triangles than shadow caster polygons.
Border lumel position are extended to fit the patch border.
- Tesselate to lumel accuracy (0.5 meter)
- Compute lumel normal
- Lumel normal is the average of lumel triangles normals. The normals
comes from the lumel accuracy to get more precise lighting. So normals
are interpolated from the center of the lumel but they will be rendered
on the patch border. Unlike the position, we can extand the normal to the
border without loosing normal precision because normal interpolation is
aligned with the tesselation. So we need some border normal smoothing.
- Border normal smoothing
- Normals on the border of the patches are smoothed with neighbor normals.
CLightRunnable () (multithread)
- For each patches and for each lumels
attenuation ()
- Compute shadow attenuation
- Get an antialised attenuation value in each lansdcape softshadow zbuffer.
Average it with jitter.
- Get an antialised attenuation value from the object zbuffer.
- Return the smaller value of the both.
- Compute sun lighting (dot product)
getSkyContribution ()
- Compute sky lighting (radiosity)
- Algorithm
- Get the lumel position in the heightfield
- Lookup in the 8 2d directions for max height
- Compute an approximation of the sky surface visible from
the lumel position
- Store final lumel luminosity
*/
// ***************************************************************************
inline float easineasout(float x)
{
float y;
// cubic tq f(0)=0, f'(0)=0, f(1)=1, f'(1)=0.
float x2=x*x;
float x3=x2*x;
y= -2*x3 + 3*x2;
return y;
}
// ***************************************************************************
inline void transformVectorToZBuffer (const CZoneLighter::CZBuffer& zbuffer, const CVector &world, CVector &projected)
{
projected = zbuffer.WorldToZBuffer * world;
float temp = projected.z;
projected.z = projected.y;
projected.y = -temp;
projected = zbuffer.WorldToZBufferFrustum.project (projected);
// Scale to zbuffer size
projected.x *= zbuffer.ZBufferPixelSize;
projected.y *= zbuffer.ZBufferPixelSize;
projected.z = temp;
}
// ***********************************************************
static const sint DeltaZ[9][2]=
{
{0, 0},
{-1, 0},
{1, 0},
{0, -1},
{0, 1},
{-1, -1},
{1, 1},
{1, -1},
{-1, 1},
};
// ***************************************************************************
inline float testZPercentageCloserFilter (float x, float y, float z, CZoneLighter::CZBuffer &zbuffer, const CZoneLighter::CLightDesc &description, bool &zBufferOverflowFlag)
{
// See "Rendering Antialiased Shadows With Depth Maps" Reeves, Salesint, Cook, ACM 1987
// Bilinear filtering
float biliValues[2][2];
float ix = (float)floor (x-0.5f);
float factorX = x - (ix+0.5f);
nlassert (factorX>=0);
nlassert (factorX<=1);
float iy = (float)floor (y-0.5f);
float factorY = y - (iy+0.5f);
nlassert (factorY>=0);
nlassert (factorY<=1);
sint dx, dy;
for (dy=0; dy<2; dy++)
for (dx=0; dx<2; dx++)
{
const sint fx = dx + (sint)ix;
const sint fy = dy + (sint)iy;
if ((fx >= 0) && (fx < zbuffer.LocalZBufferWidth) && (fy >= 0) && (fy < zbuffer.LocalZBufferHeight))
{
const float zRed = zbuffer.Pixels[fx + (zbuffer.LocalZBufferHeight - 1 - fy) * zbuffer.LocalZBufferWidth];
biliValues[dx][dy] = (zRed < (-z)) ? 0.f : 1.f;
}
else
{
biliValues[dx][dy] = 1;
zBufferOverflowFlag = true;
}
}
// Bilinear
return (biliValues[0][0] * (1 - factorX) + biliValues[1][0] * factorX) * (1 - factorY) +
(biliValues[0][1] * (1 - factorX) + biliValues[1][1] * factorX) * factorY;
}
// ***************************************************************************
CZoneLighter::CZoneLighter () : _PatchComputed ("PatchComputed")
{
}
// ***************************************************************************
void CZoneLighter::init ()
{
// Precalc some values
for (uint i=0; i<8; i++)
{
// Precalc sinP and cosP
float sinP=(float)(sin((Pi/4)*(i+0.5))-sin((Pi/4)*(i-0.5)));
float cosP=(float)(cos((Pi/4)*(i-0.5))-cos((Pi/4)*(i+0.5)));
for (uint phi=0; phi<256; phi++)
{
// Real phi
float fPhi=(float)((Pi/2)*phi/256.0);
// Tmp result
float tmp0=(float)(fPhi-sin(2*fPhi)/2);
float tmp1=(float)sin(fPhi);
// Calc K
_K[phi][i].set (tmp0*sinP, tmp0*cosP, (float)((Pi/4)*tmp1*tmp1));
}
}
// Init some containers
_ZBufferOverflow = false;
_Bitmaps.clear ();
}
// ***************************************************************************
// N - NW - W - SW - S - SE - E - NE
static const sint deltaDirection[8][2]=
{
{1, 0},
{1, 1},
{0, 1},
{-1, 1},
{-1, 0},
{-1, -1},
{0, -1},
{1, -1},
};
// ***************************************************************************
float CZoneLighter::calcSkyContribution (sint s, sint t, float height, float skyIntensity, const CVector& normal) const
{
// Sky contribution
float skyContribution;
// Calc k
CVector k (0, 0, 0);
// For the height direction
for (uint i=0; i<8; i++)
{
// Get phi for this point
uint8 phi=getMaxPhi (s, t, deltaDirection[i][0], deltaDirection[i][1], height);
// Add to k
k+=_K[phi][i];
}
// Finalize sky contribution
skyContribution=(float)(skyIntensity*(normal*k)/(2*Pi));
skyContribution=(float)(skyIntensity*(normal*k)/(2*Pi));
clamp (skyContribution, 0.f, 1.f);
return skyContribution;
}
// ***************************************************************************
void NEL3DCalcBase (CVector &direction, CMatrix& matrix)
{
direction.normalize();
CVector K=-direction;
CVector I=CVector::K^K;
CVector J=K^I;
J.normalize();
I=J^K;
I.normalize();
matrix.identity();
matrix.setRot(I,J,K, true);
}
// ***************************************************************************
void setCPUMask (IThread *thread, uint process)
{
// Set the processor mask
uint64 mask = IProcess::getCurrentProcess()->getCPUMask ();
// Mask must not be NULL
nlassert (mask != 0);
if (mask != 0)
{
uint i=0;
uint count = 0;
for(;;)
{
if (mask & (UINT64_CONSTANT(1)<<i))
{
if (count == process)
break;
count++;
}
i++;
if (i==64)
i = 0;
}
// Set the CPU mask
thread->setCPUMask (1<<i);
}
}
// ***************************************************************************
class NL3D::CLightRunnable : public IRunnable
{
// Members
uint _Process;
CZoneLighter *_ZoneLighter;
const CZoneLighter::CLightDesc *_Description;
public:
IThread *Thread;
public:
// Ctor
CLightRunnable (uint process, CZoneLighter *zoneLighter, const CZoneLighter::CLightDesc *description)
{
_ZoneLighter = zoneLighter;
_Process = process;
_Description = description;
}
// Run method
void run()
{
// Set the CPU mask
setCPUMask (Thread, _Process);
_ZoneLighter->processCalc (_Process, *_Description);
_ZoneLighter->_ProcessExited++;
}
};
// ***************************************************************************
class NL3D::CRenderZBuffer : public IRunnable
{
// Members
uint _Process;
CZoneLighter *_ZoneLighter;
// The lighting decription
const CZoneLighter::CLightDesc *_Description;
// Triangles to render
uint _FirstTriangle;
uint _NumTriangle;
const vector<CZoneLighter::CTriangle> *_Triangles;
public:
IThread *Thread;
public:
// Ctor
CRenderZBuffer (uint process, CZoneLighter *zoneLighter, const CZoneLighter::CLightDesc *description, uint firstTriangle, uint numTriangle, const vector<CZoneLighter::CTriangle> *triangles)
{
_ZoneLighter = zoneLighter;
_Description = description;
_Process = process;
_FirstTriangle = firstTriangle;
_NumTriangle = numTriangle;
_Triangles = triangles;
}
// Run method
virtual void run ();
};
// ***************************************************************************
#define CLIPPED_TOP 1
#define CLIPPED_BOTTOM 2
#define CLIPPED_RIGHT 3
#define CLIPPED_LEFT 4
#define CLIPPED_ALL (CLIPPED_TOP|CLIPPED_BOTTOM|CLIPPED_LEFT|CLIPPED_RIGHT)
void RenderTriangle (const CZoneLighter::CTriangle &triangle, const CZoneLighter::CLightDesc &description, CPolygon2D::TRasterVect &borders,
CFastMutex &mutex, CZoneLighter::CZBuffer &zbuffer, uint radius)
{
// *** Transform it in the zbuffer basis
// 2d polygon used for rasteriation
CPolygon2D zBasis;
zBasis.Vertices.resize (3);
// 3d polygon used for the gradient
NLMISC::CTriangle gradientTriangle;
// One over z value
float ooz[3];
// Clipping
uint8 in = 0;
// For each vertex
for (uint j=0; j<3; j++)
{
// Pointer on the vector
const CVector *pt = (&triangle.Triangle.V0)+j;
CVector *ptDest = (&gradientTriangle.V0)+j;
// Transform it in the zbuffer basis
transformVectorToZBuffer (zbuffer, *pt, *ptDest);
// Clip
if (ptDest->x >= zbuffer.LocalZBufferXMin)
in |= CLIPPED_LEFT;
if (ptDest->x <= zbuffer.LocalZBufferXMax)
in |= CLIPPED_RIGHT;
if (ptDest->y >= zbuffer.LocalZBufferYMin)
in |= CLIPPED_TOP;
if (ptDest->y <= zbuffer.LocalZBufferYMax)
in |= CLIPPED_BOTTOM;
// Set the 2d points
zBasis.Vertices[j].x = ptDest->x - (float)zbuffer.LocalZBufferXMin;
zBasis.Vertices[j].y = ptDest->y - (float)zbuffer.LocalZBufferYMin;
ooz[j] = 1.f / ptDest->z;
// No z
ptDest->z = 0;
}
// Not clipped ?
if (in == CLIPPED_ALL)
{
// Rasterise
sint minimumY;
borders.clear ();
zBasis.computeBorders (borders, minimumY);
// Compute the gradient for one over z
CVector ozzGradient;
gradientTriangle.computeGradient (ooz[0], ooz[1], ooz[2], ozzGradient);
// Need uv ?
bool needUV = triangle.Texture != NULL;
// Compute the gradient for uv
CVector uGradient;
CVector vGradient;
if (needUV)
{
gradientTriangle.computeGradient (triangle.U[0], triangle.U[1], triangle.U[2], uGradient);
gradientTriangle.computeGradient (triangle.V[0], triangle.V[1], triangle.V[2], vGradient);
}
// Texture information
15 years ago
uint width=0;
uint height=0;
const CObjectVector<uint8> *pixels = 0;
if (needUV)
{
// Get pixels
pixels = &triangle.Texture->getPixels ();
// Get width and height
width = triangle.Texture->getWidth ();
height = triangle.Texture->getHeight ();
}
// For each scanlines
sint y = std::max (minimumY, 0);
sint yMax = std::min ((sint)(minimumY+borders.size ()), zbuffer.LocalZBufferHeight);
for (; y<yMax; y++)
{
// Ref on the raster
const CPolygon2D::TRaster &raster = borders[y-minimumY];
// Gradient y for ooz, u and v
const float deltaY = (float)y - zBasis.Vertices[0].y;
const float oozGradientY = deltaY * ozzGradient.y;
float uGradientY=0.0f;
float vGradientY=0.0f;
if (needUV)
{
uGradientY = deltaY * uGradient.y;
vGradientY = deltaY * vGradient.y;
}
// Clip it
sint x = std::max (raster.first, 0);
sint xMax = std::min (raster.second+1, zbuffer.LocalZBufferWidth);
for (; x<xMax; x++)
{
// Gradient x for ooz, u and v
const float deltaX = (float)x - zBasis.Vertices[0].x;
const float oozGradientX = deltaX * ozzGradient.x;
float uGradientX=0.0f;
float vGradientX=0.0f;
if (needUV)
{
uGradientX = deltaX * uGradient.x;
vGradientX = deltaX * vGradient.x;
}
// Calc z
float z = - 1.f / (ooz[0] + oozGradientX + oozGradientY);
// Calc u & v
float u;
float v;
bool alphaTest = true;
if (needUV)
{
// Compute uv
u = triangle.U[0] + uGradientX + uGradientY;
v = triangle.V[0] + vGradientX + vGradientY;
// Clamp or wrap ?
if (triangle.Flags & CZoneLighter::CTriangle::ClampU)
clamp (u, 0.f, 1.f);
else
u -= (float)floor (u);
if (triangle.Flags & CZoneLighter::CTriangle::ClampV)
clamp (v, 0.f, 1.f);
else
v -= (float)floor (v);
// Lookup in the texture
u *= width;
v *= height;
clamp (u, 0, width-1);
clamp (v, 0, height-1);
uint8 alpha = ((const CRGBA*)&((*pixels)[(((uint)u)+((uint)v)*width)*sizeof (CRGBA)]))->A;
// Alpha test
alphaTest = alpha >= triangle.AlphaTestThreshold;
}
// Good alpha test ?
if (alphaTest)
{
// Enter the mutex
mutex.enter ();
// Write Z around
uint d;
for (d=0; d<radius; d++)
{
// Ref in the zbuffer
sint fx = x + DeltaZ[d][0];
sint fy = y + DeltaZ[d][1];
if ( (fx >= 0) && (fx < zbuffer.LocalZBufferWidth) && (fy >= 0) && (fy < zbuffer.LocalZBufferHeight) )
{
float &zValue = zbuffer.Pixels[fx+(zbuffer.LocalZBufferHeight-fy-1)*zbuffer.LocalZBufferWidth];
// Z test
if (z < zValue)
{
// Render z in zbuffer
zValue = z;
}
}
}
// Leave the mutex
mutex.leave ();
}
}
}
}
}
void NL3D::CRenderZBuffer::run()
{
// Set the CPU mask
setCPUMask (Thread, _Process);
// Span array
CPolygon2D::TRasterVect borders;
// For each triangles
uint i;
for (i=_FirstTriangle; i<_FirstTriangle+_NumTriangle; i++)
{
// Triangle reference
const CZoneLighter::CTriangle &triangle = (*_Triangles)[i];
// Keep backface and doublesided polygons
if ((triangle.Flags & CZoneLighter::CTriangle::DoubleSided) || ((triangle.getPlane ().getNormal() * _ZoneLighter->_SunDirection) > 0))
{
// Landscape triangle ?
if (triangle.Flags & CZoneLighter::CTriangle::Landscape)
{
// For each landscape zbuffer
uint sample;
const uint samples = _Description->SoftShadowSamplesSqrt*_Description->SoftShadowSamplesSqrt;
for (sample=0; sample<samples; sample++)
{
RenderTriangle (triangle, *_Description, borders, _ZoneLighter->_Mutex, _ZoneLighter->_ZBufferLandscape[sample], 9);
}
}
else
{
// Render in a high resolution zbuffer
RenderTriangle (triangle, *_Description, borders, _ZoneLighter->_Mutex, _ZoneLighter->_ZBufferObject, 1);
}
}
_ZoneLighter->_NumberOfPatchComputed++;
}
// Exit
_ZoneLighter->_ProcessExited++;
}
// ***************************************************************************
class NL3D::CCalcLightableShapeRunnable : public IRunnable
{
public:
CCalcLightableShapeRunnable(uint process,
CZoneLighter *zoneLighter,
const CZoneLighter::CLightDesc *description,
CZoneLighter::TShapeVect *shapeToLit,
uint firstShape,
uint lastShape
)
:
_ZoneLighter(zoneLighter),
_Description(description),
_ShapesToLit(shapeToLit),
_FirstShape(firstShape),
_LastShape(lastShape),
_Process(process)
{
}
void run()
{
_ZoneLighter->processLightableShapeCalc(_Process, _ShapesToLit, _FirstShape, _LastShape, *_Description);
_ZoneLighter->_ProcessExited++;
}
private:
CZoneLighter *_ZoneLighter;
const CZoneLighter::CLightDesc *_Description;
CZoneLighter::TShapeVect *_ShapesToLit;
uint _FirstShape, _LastShape;
uint _Process;
};
// ***************************************************************************
void draw2dLine (CBitmap &bitmap, float x0, float y0, float x1, float y1, const CRGBA &color)
{
static vector< std::pair<sint, sint> > lines;
drawFullLine (x0, y0, x1, y1, lines);
// Bitmap pixels
CRGBA *pixels = (CRGBA*)&(bitmap.getPixels ()[0]);
// Bitmap size
sint width = (sint)bitmap.getWidth ();
sint height = (sint)bitmap.getHeight ();
// Draw the line
uint i;
for (i=0; i<lines.size (); i++)
{
sint x = lines[i].first;
sint y = lines[i].second;
// Clip
if ( (x >= 0) && (x < width) && (y >= 0) && (y < height) )
{
pixels[x+(height-y-1)*width] = color;
}
}
}
// ***************************************************************************
void InitZBuffer (CZoneLighter::CZBuffer &zbuffer, const CVector &SunPosition, const CMatrix &rayBasis, const CAABBoxExt &zoneBB, uint zBufferPixelSize, const CZoneLighter::CLightDesc& description)
{
// Clac the zbuffer world size
const float zBufferWorldSize = (float)(tan (description.SunFOV/2)*description.SunDistance*2);
// ** Compute the zbuffer basis
zbuffer.WorldToZBuffer.identity ();
zbuffer.WorldToZBuffer = rayBasis;
zbuffer.WorldToZBuffer.setPos (SunPosition);
zbuffer.WorldToZBuffer.invert ();
zbuffer.WorldToZBufferFrustum.init ((float)zBufferWorldSize, (float)zBufferWorldSize, description.SunDistance, description.SunDistance*2);
// Zbuffer size
zbuffer.ZBufferPixelSize = zBufferPixelSize;
// Evaluate the size of the local zbuffer
// The zone bounding box
CVector bMin = zoneBB.getMin ();
CVector bMax = zoneBB.getMax ();
transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMax.y, bMin.z), zbuffer.BoundingBoxVectors[0]);
transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMin.y, bMin.z), zbuffer.BoundingBoxVectors[1]);
transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMin.y, bMin.z), zbuffer.BoundingBoxVectors[2]);
transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMax.y, bMin.z), zbuffer.BoundingBoxVectors[3]);
transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMax.y, bMax.z), zbuffer.BoundingBoxVectors[4]);
transformVectorToZBuffer (zbuffer, CVector (bMin.x, bMin.y, bMax.z), zbuffer.BoundingBoxVectors[5]);
transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMin.y, bMax.z), zbuffer.BoundingBoxVectors[6]);
transformVectorToZBuffer (zbuffer, CVector (bMax.x, bMax.y, bMax.z), zbuffer.BoundingBoxVectors[7]);
// Get the min and max
zbuffer.LocalZBufferXMin = 0x7fffffff;
zbuffer.LocalZBufferYMin = 0x7fffffff;
zbuffer.LocalZBufferXMax = 0x80000000;
zbuffer.LocalZBufferYMax = 0x80000000;
zbuffer.LocalZBufferZMin = FLT_MAX;
zbuffer.LocalZBufferZMax = -FLT_MAX;
uint j;
for (j=0; j<8; j++)
{
sint minX = (sint)floor (zbuffer.BoundingBoxVectors[j].x);
sint maxX = (sint)ceil (zbuffer.BoundingBoxVectors[j].x);
sint minY = (sint)floor (zbuffer.BoundingBoxVectors[j].y);
sint maxY = (sint)ceil (zbuffer.BoundingBoxVectors[j].y);
if (minX<zbuffer.LocalZBufferXMin)
zbuffer.LocalZBufferXMin = minX;
if (maxX>zbuffer.LocalZBufferXMax)
zbuffer.LocalZBufferXMax = maxX;
if (minY<zbuffer.LocalZBufferYMin)
zbuffer.LocalZBufferYMin = minY;
if (maxY>zbuffer.LocalZBufferYMax)
zbuffer.LocalZBufferYMax = maxY;
if ((-zbuffer.BoundingBoxVectors[j].z)<zbuffer.LocalZBufferZMin)
zbuffer.LocalZBufferZMin = -zbuffer.BoundingBoxVectors[j].z;
if ((-zbuffer.BoundingBoxVectors[j].z)>zbuffer.LocalZBufferZMax)
zbuffer.LocalZBufferZMax = -zbuffer.BoundingBoxVectors[j].z;
}
// Expand the zbuffer
zbuffer.LocalZBufferXMax++;
zbuffer.LocalZBufferXMin--;
zbuffer.LocalZBufferYMax++;
zbuffer.LocalZBufferYMin--;
zbuffer.LocalZBufferWidth = zbuffer.LocalZBufferXMax-zbuffer.LocalZBufferXMin;
zbuffer.LocalZBufferHeight = zbuffer.LocalZBufferYMax-zbuffer.LocalZBufferYMin;
// Resize and clear the zbuffer
zbuffer.Pixels.resize (0);
zbuffer.Pixels.resize (zbuffer.LocalZBufferWidth*zbuffer.LocalZBufferHeight, FLT_MAX);
}
// ***************************************************************************
#ifdef SAVE_ZBUFFER
void SaveZBuffer (CZoneLighter::CZBuffer &zbuffer, const char *filename)
{
// Resize the bitmap
CBitmap bitmap;
bitmap.resize (zbuffer.LocalZBufferWidth, zbuffer.LocalZBufferHeight, CBitmap::Luminance);
// Get pixels
CObjectVector<uint8> &pixels = bitmap.getPixels ();
// Draw it
uint samples = zbuffer.LocalZBufferWidth*zbuffer.LocalZBufferHeight;
for (uint i=0; i<samples; i++)
{
// Get the value
float value = (zbuffer.Pixels[i] - zbuffer.LocalZBufferZMin) * 255 / (zbuffer.LocalZBufferZMax - zbuffer.LocalZBufferZMin);
clamp (value, 0, 255);
pixels[i] = (uint8)value;
}
// Convert to RGBA
bitmap.convertToType (CBitmap::RGBA);
// Draw some red lines
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[0].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[0].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[1].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[1].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[0].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[0].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[3].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[3].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[2].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[2].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[1].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[1].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[2].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[2].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[3].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[3].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[4].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[4].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[5].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[5].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[4].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[4].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[7].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[7].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[6].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[6].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[5].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[5].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[6].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[6].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[7].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[7].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[0].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[0].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[4].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[4].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[1].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[1].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[5].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[5].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[2].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[2].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[6].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[6].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
draw2dLine (bitmap, zbuffer.BoundingBoxVectors[3].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[3].y-(float)zbuffer.LocalZBufferYMin, zbuffer.BoundingBoxVectors[7].x-(float)zbuffer.LocalZBufferXMin, zbuffer.BoundingBoxVectors[7].y-(float)zbuffer.LocalZBufferYMin, CRGBA::Red);
// Render it
COFile outputZFile;
// Open it
if (outputZFile.open (filename))
{
// Save the new zone
try
{
// Convert to RGBA
bitmap.convertToType (CBitmap::RGBA);
// Save it
bitmap.writeJPG (outputZFile, 128);
}
catch (Exception& except)
{
// Error message
nlwarning ("ERROR writing %s: %s\n", filename, except.what());
}
}
else
{
// Error can't open the file
nlwarning ("ERROR Can't open %s for writing\n", filename);
}
}
#endif // SAVE_ZBUFFER
// ***************************************************************************
void FilterZBuffer (CZoneLighter::CZBuffer &zbuffer, uint filterRadius)
{
// Resize the temp buffer
static std::vector<float> tempPixels;
tempPixels = zbuffer.Pixels;
sint x, y;
for (y=0; y<zbuffer.LocalZBufferHeight; y++)
for (x=0; x<zbuffer.LocalZBufferWidth; x++)
{
// The Value
float &newValue = tempPixels[x+y*zbuffer.LocalZBufferWidth];
uint n;
for (n=1; n<filterRadius; n++)
{
const sint fx = x + DeltaZ[n][0];
const sint fy = y + DeltaZ[n][1];
// Clip
if ( (fx>=0) && (fx < zbuffer.LocalZBufferWidth) && (fy>=0) && (fy < zbuffer.LocalZBufferHeight) )
{
const float &testValue = zbuffer.Pixels[fx+fy*zbuffer.LocalZBufferWidth];
if (testValue < newValue)
newValue = testValue;
}
}
}
// Copy the new zbuffer
zbuffer.Pixels = tempPixels;
}
// ***************************************************************************
void CZoneLighter::light (CLandscape &landscape, CZone& output, uint zoneToLight, const CLightDesc& description, std::vector<CTriangle>& obstacles, vector<uint> &listZone)
{
/*
* Lighting algorithm
* ------------------
*
* - Create a quad grid to store shadow casting triangles
* - Create a heightfield used for global illumination. Cells are initialized with -FLT_MAX
* - Insert each shadow casting triangles in the quad grid and fill the heightfield's cells overlapped by the bounding box of the triangle with
* the max height of the triangle if its height is > than the current height in the heightfield's cell.
* -
*/
// Backup thread mask
IThread *currentThread = IThread::getCurrentThread ();
uint64 threadMask = currentThread->getCPUMask();
currentThread->setCPUMask (1);
// Calc the ray basis
_SunDirection=description.SunDirection;
NEL3DCalcBase (_SunDirection, _RayBasis);
// Zone to light
_ZoneToLight=zoneToLight;
// Landscape
_Landscape=&landscape;
// Process count
_ProcessCount=description.NumCPU;
if (_ProcessCount==0)
{
// Create a doomy thread
IProcess *pProcess=IProcess::getCurrentProcess ();
_CPUMask = pProcess->getCPUMask();
_ProcessCount = 0;
uint64 i;
for (i=0; i<64; i++)
{
if (_CPUMask&((uint64)1<<i))
_ProcessCount++;
}
}
if (_ProcessCount>MAX_CPU_PROCESS)
_ProcessCount=MAX_CPU_PROCESS;
// Number of obstacle polygones
printf ("Obstacle polygones : %zu\n", obstacles.size ());
// Number of CPUS used
printf ("Number of CPU used: %d\n", _ProcessCount);
// Zone pointer
CZone *pZone=landscape.getZone (_ZoneToLight);
if (pZone)
{
// *** Compute center of the object
// Get the zone bounding box
const CAABBoxExt &zoneBB=pZone->getZoneBB();
// Get the center
CVector center = zoneBB.getCenter ();
// *** Compute planes
const uint size=(uint)obstacles.size();
15 years ago
uint triangleId;
for (triangleId=0; triangleId<size; triangleId++)
{
// Triangle ref
CZoneLighter::CTriangle& triangle=obstacles[triangleId];
// Calc the plane
triangle._Plane.make (triangle.Triangle.V0, triangle.Triangle.V1, triangle.Triangle.V2);
}
// Create landscape zbuffers
_ZBufferLandscape.resize (description.SoftShadowSamplesSqrt*description.SoftShadowSamplesSqrt);
uint sampleX;
uint sampleY;
for (sampleY=0; sampleY<description.SoftShadowSamplesSqrt; sampleY++)
for (sampleX=0; sampleX<description.SoftShadowSamplesSqrt; sampleX++)
{
// *** Render the light zbuffer
CZBuffer &zbuffer = _ZBufferLandscape[sampleX + sampleY*description.SoftShadowSamplesSqrt];
// Delta pos for area light
float deltaX = ( (float)sampleX + 0.5f - (float)description.SoftShadowSamplesSqrt / 2.f) / (float)description.SoftShadowSamplesSqrt;
float deltaY = ( (float)sampleY + 0.5f - (float)description.SoftShadowSamplesSqrt / 2.f) / (float)description.SoftShadowSamplesSqrt;
CVector lightPos = _RayBasis.getI () * ((float)description.SunRadius * deltaX) + _RayBasis.getJ () * ((float)description.SunRadius * deltaY);
lightPos = description.SunCenter - (description.SunDirection * description.SunDistance) + lightPos;
InitZBuffer (zbuffer, lightPos, _RayBasis, zoneBB, description.ZBufferLandscapeSize, description);
printf ("Zbuffer %d size : %d x %d\n", sampleX+sampleY*description.SoftShadowSamplesSqrt, zbuffer.LocalZBufferWidth, zbuffer.LocalZBufferHeight);
}
// *** Init the zbuffer for the vegetation
CVector lightPos = description.SunCenter - (description.SunDirection * description.SunDistance);
InitZBuffer (_ZBufferObject, lightPos, _RayBasis, zoneBB, description.ZBufferObjectSize, description);
printf ("Zbuffer object size : %d x %d\n", _ZBufferObject.LocalZBufferWidth, _ZBufferObject.LocalZBufferHeight);
// Compute the zbuffer in multi thread
_ProcessExited = 0;
// Number of triangle to render per thread
uint numTriangle = ((uint)obstacles.size () / _ProcessCount) + 1;
15 years ago
// First triangle for the thread
uint firstTriangle = 0;
// Count
_NumberOfPatchComputed = 0;
for (uint process=0; process<_ProcessCount; process++)
{
// Get list of triangles to render
uint lastTriangle=firstTriangle+numTriangle;
if (lastTriangle>obstacles.size ())
lastTriangle=(uint)obstacles.size ();
15 years ago
// Create a thread
CRenderZBuffer *runnable = new CRenderZBuffer (process, this, &description, firstTriangle, lastTriangle - firstTriangle, &obstacles);
IThread *pThread=IThread::create (runnable);
runnable->Thread = pThread;
// New first patch
firstTriangle = lastTriangle;
// Launch
pThread->start();
}
// Wait for others processes
while (_ProcessExited!=_ProcessCount)
{
nlSleep (1000);
// Call the progress callback
progress ("Render triangles", (float)_NumberOfPatchComputed/(float)obstacles.size());
}
// * Save the zbuffer
uint sample;
const uint samples = description.SoftShadowSamplesSqrt*description.SoftShadowSamplesSqrt;
#ifdef SAVE_ZBUFFER
for (sample=0; sample<samples; sample++)
{
// *** The zbuffer
CZBuffer &zbuffer = _ZBufferLandscape[sample];
string zbufferFilename = SAVE_ZBUFFER"/zbuffer_landscape_" + toString (sample) + ".jpg";
SaveZBuffer (zbuffer, zbufferFilename.c_str ());
}
// Save the object zbuffer
SaveZBuffer (_ZBufferObject, SAVE_ZBUFFER"/zbuffer_object.jpg");
#endif // SAVE_ZBUFFER
// *** Filter the zbuffer
for (sample=0; sample<samples; sample++)
{
// For landscape zbuffer, expand the z to neighbor
FilterZBuffer (_ZBufferLandscape[sample], 5);
}
// Change the quadGrid basis
CMatrix invRayBasis=_RayBasis;
invRayBasis.invert ();
// Init the heightfield
_HeightfieldCellSize=description.HeightfieldCellSize;
_HeightFieldCellCount=(sint)(description.HeightfieldSize/_HeightfieldCellSize);
nlassert (_HeightFieldCellCount!=0);
_OrigineHeightField=zoneBB.getCenter ()-CVector (description.HeightfieldSize/2, description.HeightfieldSize/2, 0);
_HeightField.resize (_HeightFieldCellCount*_HeightFieldCellCount, -FLT_MAX);
// Fill the quadGrid and the heightField
for (triangleId=0; triangleId<size; triangleId++)
{
// Progress bar
if ( (triangleId&0xff) == 0)
progress ("Build quadtree and heightfield", (float)triangleId/(float)size);
// Triangle ref
CZoneLighter::CTriangle& triangle=obstacles[triangleId];
// Look for the min coordinate, in World Basis
CVector minv;
minv.minof (triangle.Triangle.V0, triangle.Triangle.V1);
minv.minof (minv, triangle.Triangle.V2);
// Look for the max coordinate, in World Basis
CVector maxv;
maxv.maxof (triangle.Triangle.V0, triangle.Triangle.V1);
maxv.maxof (maxv, triangle.Triangle.V2);
// Lanscape tri ?
if (triangle.Flags & CTriangle::Landscape)
{
// Fill the heightfield
sint minX=std::max (0, (sint)floor (0.5f+(minv.x-_OrigineHeightField.x)/_HeightfieldCellSize));
sint maxX=std::min (_HeightFieldCellCount, (sint)floor (0.5f+(maxv.x-_OrigineHeightField.x)/_HeightfieldCellSize));
sint minY=std::max (0, (sint)floor (0.5f+(minv.y-_OrigineHeightField.y)/_HeightfieldCellSize));
sint maxY=std::min (_HeightFieldCellCount, (sint)floor (0.5f+(maxv.y-_OrigineHeightField.y)/_HeightfieldCellSize));
// Calc position in the heightfield
for (sint y=minY; y<maxY; y++)
for (sint x=minX; x<maxX; x++)
{
// Valid position, try to insert it
if (maxv.z>_HeightField[x+y*_HeightFieldCellCount])
{
// New height in this cell
_HeightField[x+y*_HeightFieldCellCount]=maxv.z;
}
}
}
}
// Retrieve the zone to fill its shaded value
pZone->retrieve (_PatchInfo, _BorderVertices);
// Number of patch
uint patchCount=(uint)_PatchInfo.size();
15 years ago
// Bit array to know if the lumel is shadowed
if (description.Shadow)
_ShadowArray.resize (patchCount);
// A lumel vector by patch
vector<vector<CLumelDescriptor> > lumels;
lumels.resize (patchCount);
// Build zone information
15 years ago
buildZoneInformation (landscape,
listZone,
description);
}
// Number of patch
uint patchCount=(uint)_PatchInfo.size();
15 years ago
// Reset patch count
{
CSynchronized<std::vector<bool> >::CAccessor access (&_PatchComputed);
access.value().resize (0);
access.value().resize (patchCount, false);
}
// Patch by thread
uint patchCountByThread = patchCount/_ProcessCount;
patchCountByThread++;
// Patch to allocate
uint firstPatch=0;
_NumberOfPatchComputed = 0;
// Reset exited process
_ProcessExited=0;
// Set the thread state
_LastPatchComputed.resize (_ProcessCount);
// Launch threads
uint process;
for (process=0; process<_ProcessCount; process++)
{
// Last patch
uint lastPatch=firstPatch+patchCountByThread;
if (lastPatch>patchCount)
lastPatch=patchCount;
// Last patch computed
_LastPatchComputed[process] = firstPatch;
// Create a thread
CLightRunnable *runnable = new CLightRunnable (process, this, &description);
IThread *pThread=IThread::create (runnable);
runnable->Thread = pThread;
// New first patch
firstPatch=lastPatch;
// Launch
pThread->start();
}
// Wait for others processes
while (_ProcessExited!=_ProcessCount)
{
nlSleep (1000);
// Call the progress callback
progress ("Lighting patches", (float)_NumberOfPatchComputed/(float)_PatchInfo.size());
}
// Reset old thread mask
currentThread->setCPUMask (threadMask);
// overflow ?
if (_ZBufferOverflow)
nlwarning ("Error : zbuffer overflow");
// Progress bar
progress ("Compute Influences of PointLights", 0.f);
// Compute PointLight influences on zone.
// Some precalc.
compilePointLightRT(description.GridSize, description.GridCellSize, obstacles, description.Shadow);
// Influence patchs and get light list of interest
std::vector<CPointLightNamed> listPointLight;
processZonePointLightRT(listPointLight);
// Rebuild the zone
// Progress bar
progress ("Compress the lightmap", 0.6f);
// Build, with list of lights.
CZoneInfo zinfo;
zinfo.ZoneId= _ZoneToLight;
zinfo.Patchs= _PatchInfo;
zinfo.BorderVertices= _BorderVertices;
zinfo.PointLights= listPointLight;
output.build (zinfo);
/// copy the tiles flags from the zone to light to the output zone
copyTileFlags(output, *(landscape.getZone(zoneToLight)));
/// Perform lightning of some ig's of the current zone (if any)
lightShapes(zoneToLight, description);
}
// *************************************************************************************
void CZoneLighter::copyTileFlags(CZone &destZone, const CZone &srcZone)
{
nlassert(destZone.getZoneId() == srcZone.getZoneId());
nlassert(destZone.getNumPatchs() == srcZone.getNumPatchs());
for (sint k = 0; k < srcZone.getNumPatchs(); ++k)
{
destZone.copyTilesFlags(k, srcZone.getPatch(k));
}
}
// ***************************************************************************
float CZoneLighter::getSkyContribution(const CVector &pos, const CVector &normal, float skyIntensity) const
{
float s=(pos.x-_OrigineHeightField.x)/_HeightfieldCellSize;
float t=(pos.y-_OrigineHeightField.y)/_HeightfieldCellSize;
sint sInt=(sint)(floor (s+0.5f));
sint tInt=(sint)(floor (t+0.5f));
// Bilinear
float skyContributionTab[2][2];
skyContributionTab[0][0] = calcSkyContribution (sInt-1, tInt-1, pos.z, skyIntensity, normal);
skyContributionTab[1][0] = calcSkyContribution (sInt, tInt-1, pos.z, skyIntensity, normal);
skyContributionTab[1][1] = calcSkyContribution (sInt, tInt, pos.z, skyIntensity, normal);
skyContributionTab[0][1] = calcSkyContribution (sInt-1, tInt, pos.z, skyIntensity, normal);
float sFact=s+0.5f-sInt;
float tFact=t+0.5f-tInt;
return (skyContributionTab[0][0]*(1.f-sFact) + skyContributionTab[1][0]*sFact)*(1.f-tFact) +
(skyContributionTab[0][1]*(1.f-sFact) + skyContributionTab[1][1]*sFact)*tFact;
}
// ***************************************************************************
void CZoneLighter::processCalc (uint process, const CLightDesc& description)
{
// *** Raytrace each patches
// Get a patch
uint patch = getAPatch (process);
while (patch != 0xffffffff)
{
// For each patch
if (description.Shadow)
{
// Lumels
std::vector<CLumelDescriptor> &lumels=_Lumels[patch];
// Lumel count
uint lumelCount=(uint)lumels.size();
15 years ago
CPatchInfo &patchInfo=_PatchInfo[patch];
nlassert (patchInfo.Lumels.size()==lumelCount);
// Resize shadow array
_ShadowArray[patch].resize (lumelCount);
// For each lumel
for (uint lumel=0; lumel<lumelCount; lumel++)
{
float factor=0;
factor = attenuation (lumels[lumel].Position, description);
patchInfo.Lumels[lumel]=(uint)(factor*255);
}
}
else
{
// Lumels
std::vector<CLumelDescriptor> &lumels=_Lumels[patch];
// Lumel count
uint lumelCount=(uint)lumels.size();
15 years ago
CPatchInfo &patchInfo=_PatchInfo[patch];
nlassert (patchInfo.Lumels.size()==lumelCount);
// For each lumel
for (uint lumel=0; lumel<lumelCount; lumel++)
{
// Not shadowed
patchInfo.Lumels[lumel]=255;
}
}
// *** Lighting
// Get the patch info
CPatchInfo &patchInfo=_PatchInfo[patch];
// ** Pointer on arries
std::vector<CLumelDescriptor> &lumels=_Lumels[patch];
// Go for light each lumel
for (uint lumel=0; lumel<lumels.size(); lumel++)
{
// Sky contribution
float skyContribution;
if (description.SkyContribution)
{
skyContribution = getSkyContribution(lumels[lumel].Position, lumels[lumel].Normal, description.SkyIntensity);
}
else
{
skyContribution = 0.f;
}
// Sun contribution
float sunContribution;
if (description.SunContribution)
{
sunContribution=(-lumels[lumel].Normal*_SunDirection)-skyContribution;
clamp (sunContribution, 0.f, 1.f);
}
else
sunContribution=0;
// Final lighting
sint finalLighting=(sint)(255.f*(((float)patchInfo.Lumels[lumel])*sunContribution/255.f+skyContribution));
clamp (finalLighting, 0, 255);
patchInfo.Lumels[lumel]=finalLighting;
}
// Next patch
patch = getAPatch (process);
}
}
// ***************************************************************************
uint8 CZoneLighter::getMaxPhi (sint s, sint t, sint deltaS, sint deltaT, float heightPos) const
{
// Start position
s+=deltaS;
t+=deltaT;
// Distance increment
float stepDistance=CVector (deltaS*_HeightfieldCellSize, deltaT*_HeightfieldCellSize,0).norm ();
// Current distance
float distance=stepDistance;
// Max height
float maxHeight=0;
float maxTanTeta=0;
// For all the line
while ((s<_HeightFieldCellCount)&&(t<_HeightFieldCellCount)&&(s>=0)&&(t>=0))
{
// Get height
float height=_HeightField[s+t*_HeightFieldCellCount];
height-=heightPos;
// Better ?
if (height>maxHeight)
{
// Calc sin teta
float tanTeta=height/distance;
nlassert (tanTeta>=0);
// Better ?
if (tanTeta>maxTanTeta)
{
// New max height
maxHeight=height;
maxTanTeta=tanTeta;
}
}
s+=deltaS;
t+=deltaT;
distance+=stepDistance;
}
// return phi
float teta=(float)atan (maxTanTeta);
nlassert (teta>=0);
nlassert (teta<=Pi/2);
clamp (teta, 0.f, (float)Pi/2);
sint res=(sint)((Pi/2-teta)*256/(Pi/2));
clamp (res, 0, 255);
return (uint8)res;
}
// ***************************************************************************
#define AllFront 0
#define AllBack 1
#define Clipped 2
// ***************************************************************************
bool CZoneLighter::isLumelOnEdgeMustBeOversample (uint patch, uint edge, sint s, sint t, const vector<bool> &binded,
const vector<bool> &oversampleEdges, vector<CPatchUVLocator> &locator,
uint8 shadowed, vector<vector<uint8> >& shadowBuffer)
{
// Must force oversampling of this edge ?
if (oversampleEdges[edge])
return true;
else
{
// binded ?
if (binded[edge])
{
// Lumel coord
CVector2f lumelCoord (((float)(s+_GetNormalDeltaS[edge])+0.5f)/4.f, ((float)(t+_GetNormalDeltaT[edge])+0.5f)/4.f);
uint otherPatch=locator[edge].selectPatch(lumelCoord);
// Get uv
CVector2f neighborUV;
CPatch *patchOut;
locator[edge].locateUV (lumelCoord, otherPatch, patchOut, neighborUV);
// Is the same shadowed flag ?
sint ss=(sint)(neighborUV.x*4.f);
sint tt=(sint)(neighborUV.y*4.f);
return (shadowBuffer[patchOut->getPatchId()][ss+(patchOut->getOrderS()<<2)*tt]!=shadowed);
}
else
{
// Not oversample if not binded
return false;
}
}
}
// ***************************************************************************
float easineasoutC2(float x)
{
float y;
// 5-nome tq f(0)=0, f'(0)=0, f''(0)=0, f(1)=1, f'(1)=0, f''(1)=0.
float x3=x*x*x;
float x4=x3*x;
float x5=x4*x;
y= 6*x5 -15*x4 +10*x3;
return y;
}
// ***************************************************************************
sint16 CZoneLighter::_GetNormalDeltaS[4]={ -1, 0, 1, 0 };
sint16 CZoneLighter::_GetNormalDeltaT[4]={ 0, 1, 0, -1 };
// ***************************************************************************
void CZoneLighter::getNormal (const CPatch *pPatch, sint16 lumelS, sint16 lumelT, vector<CPatchUVLocator> &locator,
const vector<CPatch::CBindInfo> &bindInfo, const vector<bool> &binded, set<uint64>& visited,
float deltaS, float deltaT, uint rotation, const CBezierPatch &bezierPatch, uint lastEdge)
{
// Build a desc srructure
uint64 id=(uint64)lumelS|(((uint64)lumelT)<<16)|(((uint64)pPatch->getPatchId())<<32)|(((uint64)pPatch->getZone()->getZoneId())<<48);
// Insert it
if (visited.insert (id).second)
{
// Clip
float sqDist=deltaS*deltaS+deltaT*deltaT;
if ( sqDist < 1 )
{
// Continue...
sint orderSx4=pPatch->getOrderS()<<2;
sint orderTx4=pPatch->getOrderT()<<2;
sint16 _GetNormalBorderS[4]={ 0, -10, 1, -10 };
sint16 _GetNormalBorderT[4]={ -10, 1, -10, 0 };
_GetNormalBorderS[2]=orderSx4-1;
_GetNormalBorderT[1]=orderTx4-1;
// Add normal
_GetNormalNormal+=bezierPatch.evalNormal ( ((float)lumelS+0.5f)/(float)orderSx4, ((float)lumelT+0.5f)/(float)orderTx4 );
// For the four neighbors
for (uint edge=0; edge<4; edge++)
{
// Not last edge ?
if (edge!=lastEdge)
{
// Direction
uint globalDirection=(edge+(4-rotation))&0x3;
// Neighbor
if ( (lumelS==_GetNormalBorderS[edge]) || (lumelT==_GetNormalBorderT[edge]) )
{
// Binded ?
bool bind=binded[edge];
bool smooth=pPatch->getSmoothFlag (edge);
if (bind&&smooth)
{
// Lumel coord
CVector2f lumelCoord ( ((float)(lumelS+_GetNormalDeltaS[edge])+0.5f)/4,
((float)(lumelT+_GetNormalDeltaT[edge])+0.5f)/4 );
// Get neighbor pixel
uint otherPatch=locator[edge].selectPatch(lumelCoord);
// Get uv
CVector2f neighborUV;
CPatch *patchOut;
locator[edge].locateUV (lumelCoord, otherPatch, patchOut, neighborUV);
// New coordinates
sint16 newLumelS=(sint16)(4.f*neighborUV.x);
sint16 newLumelT=(sint16)(4.f*neighborUV.y);
// Zone id
uint16 patchId=patchOut->getPatchId();
uint16 zoneId=_ZoneId[patchOut->getZone()->getZoneId ()];
// Get edge
uint newEdge=0;
uint i;
for (i=0; i<=(uint)bindInfo[edge].NPatchs; i++)
{
// Good patch ?
if (bindInfo[edge].Next[i]==patchOut)
{
// Get its edge
newEdge=bindInfo[edge].Edge[i];
break;
}
}
// Rotation
uint newRotation=(2-edge+rotation+newEdge)&0x3;
// Must found it
nlassert (i!=(uint)bindInfo[edge].NPatchs);
// Get the bezier patch
CBezierPatch &NewBezierPatch=_BezierPatch[zoneId][patchId];
// Next lumel
getNormal (patchOut, newLumelS, newLumelT, _Locator[zoneId][patchId], _BindInfo[zoneId][patchId],
_Binded[zoneId][patchId], visited, deltaS+_GetNormalDeltaS[globalDirection],
deltaT+_GetNormalDeltaT[globalDirection], newRotation, NewBezierPatch, newEdge);
}
}
else
{
// Left internal
getNormal (pPatch, lumelS+_GetNormalDeltaS[edge], lumelT+_GetNormalDeltaT[edge], locator, bindInfo, binded, visited,
deltaS+_GetNormalDeltaS[globalDirection], deltaT+_GetNormalDeltaT[globalDirection], rotation, bezierPatch, (edge+2)&0x3);
}
}
}
}
}
}
// ***************************************************************************
void CZoneLighter::addTriangles (CLandscape &landscape, vector<uint> &listZone, uint order, std::vector<CTriangle>& triangleArray)
{
// Set all to refine
excludeAllPatchFromRefineAll (landscape, listZone, false);
// Setup the landscape
landscape.setThreshold (0);
landscape.setTileMaxSubdivision (order);
// Refine it
landscape.refineAll (CVector (0, 0, 0));
// Dump tesselated triangles
std::vector<const CTessFace*> leaves;
landscape.getTessellationLeaves(leaves);
// Number of leaves
uint leavesCount=(uint)leaves.size();
15 years ago
// Reserve the array
triangleArray.reserve (triangleArray.size()+leavesCount);
// Scan each leaves
for (uint leave=0; leave<leavesCount; leave++)
{
// Leave
const CTessFace *face=leaves[leave];
// Add a triangle
triangleArray.push_back (CTriangle (NLMISC::CTriangle (face->VBase->EndPos, face->VLeft->EndPos, face->VRight->EndPos)));
}
// Setup the landscape
landscape.setThreshold (1000);
landscape.setTileMaxSubdivision (0);
// Remove all triangles
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
landscape.refineAll (CVector (0, 0, 0));
}
// ***************************************************************************
void CZoneLighter::addTriangles (const IShape &shape, const CMatrix& modelMT, std::vector<CTriangle>& triangleArray)
{
// Cast to CMesh
const CMesh *mesh=dynamic_cast<const CMesh*>(&shape);
// Cast to CMeshMultiLod
const CMeshMultiLod *meshMulti=dynamic_cast<const CMeshMultiLod*>(&shape);
// Cast to CMeshMultiLod
const CMeshMRM *meshMRM=dynamic_cast<const CMeshMRM*>(&shape);
// It is a mesh ?
if (mesh)
{
// Add its triangles
addTriangles (*mesh, mesh->getMeshGeom (), modelMT, triangleArray);
}
// It is a CMeshMultiLod ?
else if (meshMulti)
{
// Get the first geommesh
const IMeshGeom *meshGeom=&meshMulti->getMeshGeom (0);
// Dynamic cast
const CMeshGeom *geomMesh=dynamic_cast<const CMeshGeom*>(meshGeom);
if (geomMesh)
{
addTriangles (*meshMulti, *geomMesh, modelMT, triangleArray);
}
// Dynamic cast
const CMeshMRMGeom *mrmGeomMesh=dynamic_cast<const CMeshMRMGeom*>(meshGeom);
if (mrmGeomMesh)
{
addTriangles (*meshMulti, *mrmGeomMesh, modelMT, triangleArray);
}
}
// It is a CMeshMultiLod ?
else if (meshMRM)
{
// Get the first lod mesh geom
addTriangles (*meshMRM, meshMRM->getMeshGeom (), modelMT, triangleArray);
}
}
// ***************************************************************************
void CZoneLighter::addTriangles (const CMeshBase &meshBase, const CMeshGeom &meshGeom, const CMatrix& modelMT, std::vector<CTriangle>& triangleArray)
{
// Get the vertex buffer
const CVertexBuffer &vb=meshGeom.getVertexBuffer();
CVertexBufferRead vba;
vb.lock (vba);
// For each matrix block
uint numBlock=meshGeom.getNbMatrixBlock();
for (uint block=0; block<numBlock; block++)
{
// For each render pass
uint numRenderPass=meshGeom.getNbRdrPass(block);
for (uint pass=0; pass<numRenderPass; pass++)
{
// Get the primitive block
const CIndexBuffer &primitive=meshGeom.getRdrPassPrimitiveBlock ( block, pass);
// Get the material
const CMaterial &material = meshBase.getMaterial (meshGeom.getRdrPassMaterial ( block, pass));
// ** Get the bitmap
// Texture information, not NULL only if texture is used for alpha test
15 years ago
CBitmap *texture;
bool clampU;
bool clampV;
uint8 alphaTestThreshold;
bool doubleSided;
if (getTexture (material, texture, clampU, clampV, alphaTestThreshold, doubleSided))
{
// Dump triangles
CIndexBufferRead iba;
primitive.lock (iba);
if (iba.getFormat() == CIndexBuffer::Indices32)
{
const uint32* triIndex= (const uint32*) iba.getPtr ();
uint numTri=primitive.getNumIndexes ()/3;
uint tri;
for (tri=0; tri<numTri; tri++)
{
// Vertex
CVector v0=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3]));
CVector v1=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+1]));
CVector v2=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+2]));
// UV
float u[3];
float v[3];
for (uint i=0; i<3; i++)
{
// Get UV coordinates
const float *uv = (const float*)vba.getTexCoordPointer (triIndex[tri*3+i], 0);
if (uv)
{
// Copy it
u[i] = uv[0];
v[i] = uv[1];
}
}
// Make a triangle
triangleArray.push_back (CTriangle (NLMISC::CTriangle (v0, v1, v2), doubleSided, texture, clampU, clampV, u, v,
alphaTestThreshold));
}
}
else
{
const uint16* triIndex=(const uint16*)iba.getPtr ();
uint numTri=primitive.getNumIndexes ()/3;
uint tri;
for (tri=0; tri<numTri; tri++)
{
// Vertex
CVector v0=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3]));
CVector v1=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+1]));
CVector v2=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+2]));
// UV
float u[3];
float v[3];
for (uint i=0; i<3; i++)
{
// Get UV coordinates
const float *uv = (const float*)vba.getTexCoordPointer (triIndex[tri*3+i], 0);
if (uv)
{
// Copy it
u[i] = uv[0];
v[i] = uv[1];
}
}
// Make a triangle
triangleArray.push_back (CTriangle (NLMISC::CTriangle (v0, v1, v2), doubleSided, texture, clampU, clampV, u, v,
alphaTestThreshold));
}
}
}
}
}
}
// ***************************************************************************
bool CZoneLighter::getTexture (const CMaterial &material, CBitmap *&result, bool &clampU, bool &clampV, uint8 &alphaTestThreshold, bool &doubleSided)
{
// Texture information, not NULL only if texture is used for alpha test
15 years ago
result = NULL;
clampU = false;
clampV = false;
// Alpha test threashold
float alphaTestThresholdF = material.getAlphaTestThreshold () * 255;
clamp (alphaTestThresholdF, 0.f, 255.f);
alphaTestThreshold = (uint8)alphaTestThresholdF;
// Drawable material ?
if (material.getBlend ())
return false;
// Use alpha test ?
if (material.getAlphaTest ())
{
// Get the texture
ITexture *texture = material.getTexture (0);
// Is texture shared ?
if (texture && texture->supportSharing ())
{
// Share name
string name = texture->getShareName();
// Texture exist ?
std::map<string, NLMISC::CBitmap>::iterator ite = _Bitmaps.find (name);
if (ite != _Bitmaps.end ())
{
// Yes
result = &(ite->second);
}
else
{
// No, add it
ite = _Bitmaps.insert (std::map<string, NLMISC::CBitmap>::value_type (name, CBitmap())).first;
result = &(ite->second);
// Generate the texture
texture->generate ();
// Convert to RGBA
texture->convertToType (CBitmap::RGBA);
// Copy it
*result = *texture;
// Release the texture
texture->release ();
}
// Wrap flags
clampU = texture->getWrapS () == ITexture::Clamp;
clampV = texture->getWrapT () == ITexture::Clamp;
}
}
// Get double sided flag
doubleSided = material.getDoubleSided ();
return true;
}
// ***************************************************************************
void CZoneLighter::addTriangles (const CMeshBase &meshBase, const CMeshMRMGeom &meshGeom, const CMatrix& modelMT, std::vector<CTriangle>& triangleArray)
{
// Get the vertex buffer
const CVertexBuffer &vb=meshGeom.getVertexBuffer();
CVertexBufferRead vba;
vb.lock (vba);
// For each render pass
uint numRenderPass=meshGeom.getNbRdrPass(0);
for (uint pass=0; pass<numRenderPass; pass++)
{
// Get the primitive block
const CIndexBuffer &primitive=meshGeom.getRdrPassPrimitiveBlock ( 0, pass);
// Get the material
const CMaterial &material = meshBase.getMaterial (meshGeom.getRdrPassMaterial (0, pass));
// ** Get the bitmap
// Texture information, not NULL only if texture is used for alpha test
15 years ago
CBitmap *texture;
bool clampU;
bool clampV;
uint8 alphaTestThreshold;
bool doubleSided;
if (getTexture (material, texture, clampU, clampV, alphaTestThreshold, doubleSided))
{
// Dump triangles
CIndexBufferRead iba;
primitive.lock (iba);
const uint32* triIndex= (const uint32 *) iba.getPtr ();
uint numTri=primitive.getNumIndexes ()/3;
uint tri;
for (tri=0; tri<numTri; tri++)
{
// Vertex
CVector v0=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3]));
CVector v1=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+1]));
CVector v2=modelMT*(*vba.getVertexCoordPointer (triIndex[tri*3+2]));
// UV
float u[3];
float v[3];
for (uint i=0; i<3; i++)
{
// Get UV coordinates
float *uv = (float*)vba.getTexCoordPointer (triIndex[tri*3+i], 0);
if (uv)
{
// Copy it
u[i] = uv[0];
v[i] = uv[1];
}
}
// Make a triangle
triangleArray.push_back (CTriangle (NLMISC::CTriangle (v0, v1, v2), doubleSided, texture, clampU, clampV, u, v,
alphaTestThreshold));
}
}
}
}
// ***************************************************************************
void CZoneLighter::excludeAllPatchFromRefineAll (CLandscape &landscape, vector<uint> &listZone, bool exclude)
{
// For each zone
for (uint zone=0; zone<listZone.size(); zone++)
{
// Get num patches
uint patchCount=landscape.getZone(listZone[zone])->getNumPatchs();
// For each patches
for (uint patch=0; patch<patchCount; patch++)
{
// Exclude all the patches from refine all
landscape.excludePatchFromRefineAll (listZone[zone], patch, exclude);
}
}
}
// ***************************************************************************
const sint8 CZoneLighter::TriangleIndexes[10][2][3] =
{
{{0, 11, 6}, {0, 6, 9}},
{{9, 6, 4}, {4, 6, 14}},
{{4, 14, 8}, {4, 8, 10}},
{{10, 8, 1}, {-1, -1, -1}},
{{11, 5, 6}, {5, 13, 6}},
{{6, 13, 3}, {6, 3, 14}},
{{3, 8, 14}, {-1, -1, -1}},
{{5, 12, 7}, {5, 7, 13}},
{{7, 3, 13}, {-1, -1, -1}},
{{12, 2, 7}, {-1, -1, -1}}
};
// ***************************************************************************
// lumel vertex ID, tesselation edge ID (0 for base<->right, 1 for base<->left), tesselation edge vertex 0, tesselation edge vertex 1
const sint8 CZoneLighter::VertexThanCanBeSnappedOnABorder[8][4] =
{
{0, 0, 0, 9},
{1, 0, 9, 4},
{2, 0, 4, 10},
{3, 0, 10, 1},
{0, 1, 0, 11},
{4, 1, 11, 5},
{7, 1, 5, 12},
{9, 1, 12, 2},
};
// ***************************************************************************
// lumel vertex ID, tesselation corner vertex
const sint8 CZoneLighter::VertexThanCanBeSnappedOnACorner[3][2] =
{
{0, 0},
{3, 1},
{9, 2},
};
// ***************************************************************************
void CZoneLighter::buildZoneInformation (CLandscape &landscape, const vector<uint> &listZone, const CLightDesc &lightDesc)
{
// Bool visit
vector<vector<uint> > visited;
// Zone count
uint zoneCount=(uint)listZone.size();
15 years ago
// Resize arries
_Locator.resize (zoneCount);
_Binded.resize (zoneCount);
_BindInfo.resize (zoneCount);
_BezierPatch.resize (zoneCount);
// For each zone
for (uint zone=0; zone<zoneCount; zone++)
{
// Get num patches
uint patchCount=landscape.getZone(listZone[zone])->getNumPatchs();
// Insert zone id
_ZoneId.insert (map<uint, uint>::value_type (listZone[zone], zone));
// This is the zone to light ?
if (listZone[zone]==_ZoneToLight)
{
// Resize the arraies
_Lumels.resize(patchCount);
// _LumelCorners.resize(patchCount);
// _BezierPatch.resize(patchCount);
_OversampleEdges.resize(patchCount);
visited.resize(patchCount);
}
// Common arries
_Locator[zone].resize(patchCount);
_Binded[zone].resize(patchCount);
_BindInfo[zone].resize(patchCount);
_BezierPatch[zone].resize(patchCount);
// For each patch
uint patch;
for (patch=0; patch<patchCount; patch++)
{
// Get a patch pointer
const CPatch* pPatch=(const_cast<const CZone*>(landscape.getZone(listZone[zone])))->getPatch (patch);
// Progress bar
progress ("Scan all patches", (float)patch/(float)patchCount);
// Get pointer on arries
vector<bool> &binded=_Binded[zone][patch];
vector<CPatch::CBindInfo> &bindInfo=_BindInfo[zone][patch];
vector<CPatchUVLocator> &locator=_Locator[zone][patch];
CBezierPatch &bezierPatch=_BezierPatch[zone][patch];
binded.resize (4, false);
bindInfo.resize (4);
locator.resize (4);
// Contruct the patch
bezierPatch=*pPatch->unpackIntoCache();
// Same zone ?
if (listZone[zone]==_ZoneToLight)
{
// oversample this edge
_OversampleEdges[patch].resize (4, false);
}
// *** Build bind info
// *** Build neighboorhood information
uint edge;
for (edge=0; edge<4; edge++)
{
// Bond neighbor
pPatch->getBindNeighbor (edge, bindInfo[edge]);
// Patch binded
if (bindInfo[edge].NPatchs>0)
{
// This edeg is binded
binded[edge]=true;
// Same zone ?
if ((listZone[zone]==_ZoneToLight)&&(bindInfo[edge].Zone->getZoneId()!=_ZoneToLight))
{
// oversample this edge
_OversampleEdges[patch][edge]=true;
}
locator[edge].build (pPatch, edge, bindInfo[edge]);
}
else
{
if (listZone[zone]==_ZoneToLight)
{
// oversample this edge
_OversampleEdges[patch][edge]=true;
}
}
}
// This is the zone to light ?
if (listZone[zone]==_ZoneToLight)
{
// *** Resize lumel array for this patch
// Get patch order
uint orderS=pPatch->getOrderS();
uint orderT=pPatch->getOrderT();
// Number of lumels
uint lumelCount = orderS*orderT*16;
// Resize the lumel descriptor
CLumelDescriptor descriptor;
descriptor.Normal.set (0,0,0);
descriptor.Position.set (0,0,0);
descriptor.S=0;
descriptor.T=0;
_Lumels[patch].resize (lumelCount, descriptor);
visited[patch].resize (lumelCount, 0);
// _LumelCorners[patch].resize (lumelCornerCount);
// *** Unexclude this patch
// Exclude all the patches from refine all
landscape.excludePatchFromRefineAll (listZone[zone], patch, false);
}
else
{
// Exclude all the patches from refine all
landscape.excludePatchFromRefineAll (listZone[zone], patch, true);
}
}
}
// *** Now tesselate this zone to shadow casters accuracy
// Setup the landscape
landscape.setThreshold (0);
landscape.setTileMaxSubdivision (0);
// Refine all
progress ("Refine landscape to shadow accuracy", 0.5f);
landscape.refineAll (CVector (0, 0, 0));
// Get tesselated faces
std::vector<const CTessFace*> leaves;
landscape.getTessellationLeaves(leaves);
if (_WaterShapes.size() != 0) // any water shape in this zone ?
{
/// make a quad grid of each water shape
makeQuadGridFromWaterShapes(landscape.getZone(_ZoneToLight)->getZoneBB().getAABBox());
/// check for each tile if it is above / below water
computeTileFlagsForPositionTowardWater(lightDesc, leaves);
}
else
{
setTileFlagsToDefault(leaves);
}
// Id of this zone in the array
uint zoneNumber=_ZoneId[_ZoneToLight];
// Scan each leaves
uint leavesCount=(uint)leaves.size();
15 years ago
uint leave;
for (leave=0; leave<leavesCount; leave++)
{
// Progress bar
if ( (leave&0xff) == 0)
progress ("Precompute lumel position", (float)leave/(float)leavesCount);
// Leave
const CTessFace *face=leaves[leave];
// Get zone id
if (face->Patch->getZone()->getZoneId()==_ZoneToLight)
{
// Get a patch pointer
const CPatch* pPatch=face->Patch;
// Get order
uint orderS=pPatch->getOrderS();
uint orderT=pPatch->getOrderT();
// *** Base Coordinates
CVector pos[15];
pos[0]=face->VBase->EndPos; // p0
pos[1]=face->VRight->EndPos;
pos[2]=face->VLeft->EndPos; // p2
pos[3]=(pos[1]+pos[2])/2;
pos[4]=(pos[0]+pos[1])/2; // p4
pos[5]=(pos[0]+pos[2])/2;
pos[6]=(pos[0]+pos[3])/2; // p6
pos[7]=(pos[2]+pos[3])/2;
pos[8]=(pos[1]+pos[3])/2; // p8
pos[9]=(pos[0]+pos[4])/2;
pos[10]=(pos[1]+pos[4])/2; // p10
pos[11]=(pos[0]+pos[5])/2;
pos[12]=(pos[2]+pos[5])/2; // p12
pos[13]=(pos[3]+pos[5])/2;
pos[14]=(pos[3]+pos[4])/2; // p14
float s0=face->PVBase.getS();
float s1=face->PVRight.getS();
float s2=face->PVLeft.getS();
float s3=(s1+s2)/2;
float s4=(s0+s1)/2;
float s5=(s0+s2)/2;
float s6=(s4+s5)/2;
float s7=(s2+s3)/2;
float s8=(s1+s3)/2;
float t0=face->PVBase.getT();
float t1=face->PVRight.getT();
float t2=face->PVLeft.getT();
float t3=(t1+t2)/2;
float t4=(t0+t1)/2;
float t5=(t0+t2)/2;
float t6=(t4+t5)/2;
float t7=(t2+t3)/2;
float t8=(t1+t3)/2;
// *** Interpolated value
CVector interpolatedP[10]=
{
(pos[0]+pos[6])/2,
(pos[4]+pos[6])/2,
(pos[4]+pos[8])/2,
(pos[1]+pos[8])/2,
(pos[5]+pos[6])/2,
(pos[3]+pos[6])/2,
(pos[3]+pos[8])/2,
(pos[5]+pos[7])/2,
(pos[3]+pos[7])/2,
(pos[2]+pos[7])/2,
};
// Does the border are snapped ?
uint sBase = (uint)floor ((float)orderS * face->PVBase.getS() + 0.5);
uint tBase = (uint)floor ((float)orderT * face->PVBase.getT() + 0.5);
uint sLeft = (uint)floor ((float)orderS * face->PVLeft.getS() + 0.5);
uint tLeft = (uint)floor ((float)orderT * face->PVLeft.getT() + 0.5);
uint sRight = (uint)floor ((float)orderS * face->PVRight.getS() + 0.5);
uint tRight = (uint)floor ((float)orderT * face->PVRight.getT() + 0.5);
bool snapedLeft[2]=
{
(sBase == 0) && (sRight == 0),
(sBase == 0) && (sLeft == 0),
};
bool snapedRight[2]=
{
(sBase == orderS) && (sRight == orderS),
(sBase == orderS) && (sLeft == orderS),
};
bool snapedTop[2]=
{
(tBase == 0) && (tRight == 0),
(tBase == 0) && (tLeft == 0),
};
bool snapedBottom[2]=
{
(tBase == orderT) && (tRight == orderT),
(tBase == orderT) && (tLeft == orderT),
};
bool snapedBorder[2]=
{
snapedLeft[0]||snapedRight[0]||snapedTop[0]||snapedBottom[0],
snapedLeft[1]||snapedRight[1]||snapedTop[1]||snapedBottom[1],
};
bool snapedCorner[3]=
{
((sBase == 0) && ((tBase == 0) || (tBase == orderT))) ||
((sBase == orderS) && ((tBase == 0) || (tBase == orderT))),
((sRight == 0) && ((tRight == 0) || (tRight == orderT))) ||
((sRight == orderS) && ((tRight == 0) || (tRight == orderT))),
((sLeft == 0) && ((tLeft == 0) || (tLeft == orderT))) ||
((sLeft == orderS) && ((tLeft == 0) || (tLeft == orderT))),
};
// Snap on the border
uint i;
for (i=0; i<8; i++)
{
// Snaped on left ?
if (snapedBorder[VertexThanCanBeSnappedOnABorder[i][1]])
{
// Compute the border vertex
interpolatedP[VertexThanCanBeSnappedOnABorder[i][0]] = (pos[VertexThanCanBeSnappedOnABorder[i][2]]
+ pos[VertexThanCanBeSnappedOnABorder[i][3]])/2;
}
}
// Snap on the corner
for (i=0; i<3; i++)
{
// Snaped on a corner ?
uint tesselCornerIndex = VertexThanCanBeSnappedOnACorner[i][1];
if ( snapedCorner[tesselCornerIndex] )
{
// Compute the border vertex
interpolatedP[VertexThanCanBeSnappedOnACorner[i][0]] = pos[tesselCornerIndex];
}
}
float interpolatedS[10]=
{
(s0+s6)/2,
(s4+s6)/2,
(s4+s8)/2,
(s1+s8)/2,
(s5+s6)/2,
(s3+s6)/2,
(s3+s8)/2,
(s5+s7)/2,
(s3+s7)/2,
(s2+s7)/2,
};
float interpolatedT[10]=
{
(t0+t6)/2,
(t4+t6)/2,
(t4+t8)/2,
(t1+t8)/2,
(t5+t6)/2,
(t3+t6)/2,
(t3+t8)/2,
(t5+t7)/2,
(t3+t7)/2,
(t2+t7)/2,
};
for (i=0; i<10; i++)
{
sint s=(sint)((float)orderS*4*interpolatedS[i]);
sint t=(sint)((float)orderT*4*interpolatedT[i]);
if ((s>=0)&&(s<(sint)orderS*4)&&(t>=0)&&(t<(sint)orderT*4))
{
// Triangle index
uint index=s+t*orderS*4;
// Ge tthe patch id
uint patchId=pPatch->getPatchId();
// Get lumel array
vector<CLumelDescriptor> &lumels=_Lumels[patchId];
// Visited
visited[patchId][index]++;
// Position
lumels[index].Position+=interpolatedP[i];
}
}
}
}
// *** Now, finalise patch information for shadow source positions
15 years ago
// For each patches
uint patchCount=landscape.getZone(_ZoneToLight)->getNumPatchs();
uint patch;
for (patch=0; patch<patchCount; patch++)
{
// Info
progress ("Finalize lumel positions", (float)patch/(float)patchCount);
// *** Resize lumel array for this patch
// Get a patch pointer
const CPatch* pPatch=(const_cast<const CZone*>(landscape.getZone(_ZoneToLight)))->getPatch (patch);
uint orderS=pPatch->getOrderS();
uint orderT=pPatch->getOrderT();
// Get lumel array
vector<CLumelDescriptor> &lumels=_Lumels[patch];
// *** Average position
// Renormalize
nlassert (isPowerOf2 (orderS));
nlassert (isPowerOf2 (orderT));
uint lumelS=4<<getPowerOf2 (orderS);
uint lumelT=4<<getPowerOf2 (orderT);
for (uint t=0; t<lumelT; t++)
for (uint s=0; s<lumelS; s++)
{
// Lumel index
uint lumelIndex=s+t*lumelS;
// *** Number of visit
uint visitedCount=visited[patch][lumelIndex];
// If visited, renormalise other values
if (visitedCount)
{
// Normalise position
lumels[lumelIndex].Position/=(float)visitedCount;
}
// Not visited for next pass
visited[patch][lumelIndex]=false;
}
}
// *** Now tesselate this zone to shadow receivers accuracy
// Setup the landscape
landscape.setThreshold (0);
landscape.setTileMaxSubdivision (4);
// Refine all
progress ("Refine landscape to lumels", 0.5f);
landscape.refineAll (CVector (0, 0, 0));
// Get tesselated faces
leaves.clear ();
landscape.getTessellationLeaves(leaves);
// Scan each leaves
leavesCount=(uint)leaves.size();
15 years ago
for (leave=0; leave<leavesCount; leave++)
{
// Progress bar
if ( (leave&0xff) == 0)
progress ("Precompute tesselation", (float)leave/(float)leavesCount);
// Leave
const CTessFace *face=leaves[leave];
// Get zone id
if (face->Patch->getZone()->getZoneId()==_ZoneToLight)
{
// Get a patch pointer
const CPatch* pPatch=face->Patch;
// Get order
uint orderS=pPatch->getOrderS();
uint orderT=pPatch->getOrderT();
// Coordinates
float fS=(face->PVBase.getS()+face->PVLeft.getS()+face->PVRight.getS())/3.f;
float fT=(face->PVBase.getT()+face->PVLeft.getT()+face->PVRight.getT())/3.f;
uint s=(uint)((float)orderS*4*fS);
uint t=(uint)((float)orderT*4*fT);
//nlassert (s>=0);
nlassert (s<orderS*4);
//nlassert (t>=0);
nlassert (t<orderT*4);
// Triangle index
uint index=s+t*orderS*4;
// Ge tthe patch id
uint patchId=pPatch->getPatchId();
// Get lumel array
vector<CLumelDescriptor> &lumels=_Lumels[patchId];
// Visited
visited[patchId][index]++;
// Lumel s and t
lumels[index].S+=fS;
lumels[index].T+=fT;
// Normal
CPlane plane;
plane.make (face->VBase->EndPos, face->VLeft->EndPos, face->VRight->EndPos);
lumels[index].Normal+=plane.getNormal();
}
}
// *** Now, finalise patch information
15 years ago
// For each patches
patchCount=landscape.getZone(_ZoneToLight)->getNumPatchs();
for (patch=0; patch<patchCount; patch++)
{
// Info
progress ("Finalize patches", (float)patch/(float)patchCount);
// *** Resize lumel array for this patch
// Get a patch pointer
const CPatch* pPatch=(const_cast<const CZone*>(landscape.getZone(_ZoneToLight)))->getPatch (patch);
uint orderS=pPatch->getOrderS();
uint orderT=pPatch->getOrderT();
// Get lumel array
vector<CLumelDescriptor> &lumels=_Lumels[patch];
// *** Compute an interpolated normal
// Get pointer on arries
vector<bool> &binded=_Binded[zoneNumber][patch];
vector<CPatchUVLocator> &locator=_Locator[zoneNumber][patch];
vector<CPatch::CBindInfo> &bindInfo=_BindInfo[zoneNumber][patch];
CBezierPatch &bezierPatch=_BezierPatch[zoneNumber][patch];
// Renormalize
nlassert (isPowerOf2 (orderS));
nlassert (isPowerOf2 (orderT));
uint powerS=getPowerOf2 (orderS);
uint powerT=getPowerOf2 (orderT);
uint lumelS=4<<powerS;
uint lumelT=4<<powerT;
// Sample edge normal
CVector normals[NL_MAX_TILES_BY_PATCH_EDGE*NL_LUMEL_BY_TILE+1][4];
uint sFixed[4] = { 0, 0xffffffff, lumelS-1, 0xffffffff };
uint tFixed[4] = { 0xffffffff, lumelT-1, 0xffffffff, 0 };
float sOri[4] = { 0, -1, (float)lumelS, -1 };
float tOri[4] = { -1, (float)lumelT, -1, 0 };
for (uint edge=0; edge<4; edge++)
{
// s and t
uint count=(edge&1)?lumelS:lumelT;
for (uint lumel=0; lumel<=count; lumel++)
{
// Start coordinates
float origineS;
float origineT;
uint startS;
uint startT;
if (edge&1)
{
if (lumel==count)
startS=count-1;
else
startS=lumel;
startT=tFixed[edge];
origineS=(float)lumel;
origineT=tOri[edge];
}
else
{
if (lumel==count)
startT=count-1;
else
startT=lumel;
startS=sFixed[edge];
origineT=(float)lumel;
origineS=sOri[edge];
}
_GetNormalNormal=CVector::Null;
set<uint64> visitedLumels;
getNormal (pPatch, startS, startT, locator, bindInfo, binded, visitedLumels,
startS+0.5f-origineS, startT+0.5f-origineT, 0, bezierPatch);
_GetNormalNormal.normalize ();
normals[lumel][edge]=_GetNormalNormal;
}
// Smooth the corners
#define BLUR_SIZE 4
for (uint i=1; i<BLUR_SIZE; i++)
{
float value=(float)i/BLUR_SIZE;
value=easineasout(value);
normals[i][edge]=normals[0][edge]*(1-value)+normals[i][edge]*value;
normals[i][edge].normalize();
normals[count-i][edge]=normals[count][edge]*(1-value)+normals[count-i][edge]*value;
normals[count-i][edge].normalize();
}
}
for (uint t=0; t<lumelT; t++)
for (uint s=0; s<lumelS; s++)
{
// Lumel index
uint lumelIndex=s+t*lumelS;
// *** Calc the smoothed normal
// For each edge
CVector normalS=bezierPatch.evalNormal (((float)s+0.5f)/(float)lumelS, ((float)t+0.5f)/(float)lumelT);
float sFactor=0;
CVector normalT=normalS;
float tFactor=0;
bool sGood=false, tGood=false;
if (s<BLUR_SIZE)
{
sGood=true;
// Average the two normals
CVector average=normals[t][0];
average+=normals[t+1][0];
average/=2;
// Blend
float value=s+0.5f;
sFactor=BLUR_SIZE-value;
value/=BLUR_SIZE;
value=easineasout(value);
normalS=(normalS*value+average*(1-value));
normalS.normalize();
}
if (s>=lumelS-BLUR_SIZE)
{
sGood=true;
// Average the two normals
CVector average=normals[t][2];
average+=normals[t+1][2];
average/=2;
// Blend
float value=s+0.5f;
sFactor=BLUR_SIZE-(lumelS-value);
value=(lumelS-value)/BLUR_SIZE;
value=easineasout(value);
normalS=(normalS*value+average*(1-value));
normalS.normalize();
}
if (t<BLUR_SIZE)
{
tGood=true;
// Average the two normals
CVector average=normals[s][3];
average+=normals[s+1][3];
average/=2;
// Blend
float value=t+0.5f;
tFactor=BLUR_SIZE-value;
value/=BLUR_SIZE;
value=easineasout(value);
normalT=(normalT*value+average*(1-value));
normalT.normalize();
}
if (t>=lumelT-BLUR_SIZE)
{
tGood=true;
// Average the two normals
CVector average=normals[s][1];
average+=normals[s+1][1];
average/=2;
// Blend
float value=t+0.5f;
tFactor=BLUR_SIZE-(lumelT-value);
value=((lumelT)-value)/BLUR_SIZE;
value=easineasout(value);
normalT=(normalT*value+average*(1-value));
normalT.normalize();
}
// The smooth normal
CVector smoothNormal;
if ((sGood)&&(tGood))
{
if ((sFactor!=BLUR_SIZE)||(tFactor!=BLUR_SIZE))
smoothNormal=normalS*(BLUR_SIZE-tFactor)+normalT*(BLUR_SIZE-sFactor);
else
smoothNormal=normalS+normalT;
}
else if (sGood)
smoothNormal=normalS;
else
smoothNormal=normalT;
// Normalize it
smoothNormal.normalize();
// The pure normal
CVector purNormal=bezierPatch.evalNormal (((float)s+0.5f)/(float)lumelS, ((float)t+0.5f)/(float)lumelT);
// Normalize the noisy normal
lumels[lumelIndex].Normal.normalize();
// Final normal
lumels[lumelIndex].Normal=lumels[lumelIndex].Normal-purNormal+smoothNormal;
lumels[lumelIndex].Normal.normalize ();
// *** Number of visit
uint visitedCount=visited[patch][lumelIndex];
// Some lumel have not been found in tesselation
//nlassert (visitedCount==2);
// If visited, renormalise other values
if (visitedCount)
{
// Normalise position
lumels[lumelIndex].S/=(float)visitedCount;
lumels[lumelIndex].T/=(float)visitedCount;
}
}
}
}
// ***************************************************************************
void CZoneLighter::computeTileFlagsOnly (CLandscape &landscape, CZone& output, uint zoneToLight, const CLightDesc& description,
std::vector<uint> &listZone)
{
// Zone to light
_ZoneToLight=zoneToLight;
// Landscape
_Landscape=&landscape;
// Zone count
uint zoneCount=(uint)listZone.size();
15 years ago
// For each zone
for (uint zone=0; zone<zoneCount; zone++)
{
// Get num patches
uint patchCount=landscape.getZone(listZone[zone])->getNumPatchs();
// Insert zone id
_ZoneId.insert (map<uint, uint>::value_type (listZone[zone], zone));
// For each patch
uint patch;
for (patch=0; patch<patchCount; patch++)
{
// Progress bar
progress ("Scan all patches", (float)patch/(float)patchCount);
// This is the zone to light ?
if (listZone[zone]==_ZoneToLight)
{
// unExclude all the patches from refine all
landscape.excludePatchFromRefineAll (listZone[zone], patch, false);
}
else
{
// Exclude all the patches from refine all
landscape.excludePatchFromRefineAll (listZone[zone], patch, true);
}
}
}
// *** Now tesselate this zone to max accuracy
// Setup the landscape
landscape.setThreshold (0);
landscape.setTileMaxSubdivision (0);
// Refine all
progress ("Refine landscape to maximum", 0.5f);
landscape.refineAll (CVector (0, 0, 0));
// Get tesselated faces
std::vector<const CTessFace*> leaves;
landscape.getTessellationLeaves(leaves);
// compute only the water states
if (_WaterShapes.size() != 0) // any water shape in this zone ?
{
/// make a quad grid of each water shape
makeQuadGridFromWaterShapes(landscape.getZone(_ZoneToLight)->getZoneBB().getAABBox());
/// check for each tile if it is above / below water
computeTileFlagsForPositionTowardWater(description, leaves);
}
else
{
setTileFlagsToDefault(leaves);
}
/// verify that the zonew and the zonel (output) are compatible
bool ok= true;
CZone &zonew= *(landscape.getZone(zoneToLight));
if(zonew.getNumPatchs() == output.getNumPatchs())
{
// verify for each patch that the tile array are same
for(uint i=0;i<(uint)zonew.getNumPatchs();i++)
{
const CPatch &p0= *const_cast<const CZone&>(zonew).getPatch(i);
const CPatch &p1= *const_cast<const CZone&>(output).getPatch(i);
if( p0.getOrderS()!=p1.getOrderS() || p0.getOrderT()!=p1.getOrderT() )
{
ok= false;
break;
}
}
}
else
ok= false;
// can't copy tile flags
if(!ok)
throw Exception("The input zonew, and ouput zonel are too different: not same patchs!!");
/// copy the tiles flags from the zone to light to the output zone
copyTileFlags(output, zonew);
}
// ***************************************************************************
CZoneLighter::CLightDesc::CLightDesc ()
{
SunDirection.set (1, 1, -1);
GridSize=512;
GridCellSize=4;
HeightfieldSize=200;
HeightfieldCellSize=20;
SkyContribution=true;
SkyIntensity=0.25;
ZBufferLandscapeSize = DEFAULT_ZBUFFER_LANDSCAPE_SIZE;
ZBufferObjectSize = DEFAULT_ZBUFFER_OBJECT_SIZE;
SoftShadowJitter = DEFAULT_JITTER;
SunDistance = DEFAULT_SUN_DISTANCE;
SunFOV = (float)DEFAULT_SUN_FOV;
SunCenter = DEFAULT_SUN_CENTER;
SunRadius = DEFAULT_SUN_RADIUS;
SoftShadowSamplesSqrt = DEFAULT_SUN_SRQT_SAMPLES;
}
// ***************************************************************************
void CZoneLighter::addLightableShape(IShape *shape, const NLMISC::CMatrix& MT)
{
CShapeInfo lsi;
lsi.MT = MT;
lsi.Shape = shape;
_LightableShapes.push_back(lsi);
}
// ***************************************************************************
bool CZoneLighter::isLightableShape(IShape &shape)
{
/// for now, the only shape that we lit are water shapes
if (dynamic_cast<CWaterShape *>(&shape) != NULL)
{
// check that this water surface has a diffuse map that is a CTextureFile (we must be able to save it !)
CWaterShape *ws = static_cast<CWaterShape *>(&shape);
const ITexture *tex = ws->getColorMap();
if (dynamic_cast<const CTextureFile *>(tex) != NULL)
{
return ws->isLightMappingEnabled();
}
}
return false;
}
// ***************************************************************************
void CZoneLighter::lightShapes(uint zoneID, const CLightDesc& description)
{
/// compute light for the lightable shapes in the given zone
if (_LightableShapes.size() == 0) return;
uint numShapePerThread = 1 + ((uint)_LightableShapes.size() / _ProcessCount);
15 years ago
uint currShapeIndex = 0;
uint process = 0;
_ProcessExited = 0;
_NumLightableShapesProcessed = 0;
progress("Processing lightable shapes", 0);
for (uint k = 0; k < _LightableShapes.size(); ++k, ++process)
{
uint lastShapeIndex = currShapeIndex + numShapePerThread;
lastShapeIndex = std::min((uint)_LightableShapes.size(), lastShapeIndex);
IThread *pThread = IThread::create (new CCalcLightableShapeRunnable(process, this, &description, &_LightableShapes, currShapeIndex, lastShapeIndex));
pThread->start();
currShapeIndex = lastShapeIndex;
}
/// wait for other process
while (_ProcessExited != _ProcessCount)
{
nlSleep (10);
}
}
// ***************************************************************************
void CZoneLighter::processLightableShapeCalc (uint process,
TShapeVect *shapesToLit,
uint firstShape,
uint lastShape,
const CLightDesc& description)
{
// for each lightable shape
for (uint k = firstShape; k < lastShape; ++k)
{
nlassert(isLightableShape(* (*shapesToLit)[k].Shape)); // make sure it is a lightable shape
lightSingleShape((*shapesToLit)[k], description, process);
}
}
// ***************************************************************************
void CZoneLighter::lightSingleShape(CShapeInfo &si, const CLightDesc& description, uint cpu)
{
/// we compute the lighting for one single shape
if (dynamic_cast<CWaterShape *>(si.Shape))
{
lightWater(* static_cast<CWaterShape *>(si.Shape), si.MT, description, cpu);
}
++_NumLightableShapesProcessed;
progress("Processing lightable shapes", (float) _NumLightableShapesProcessed / _LightableShapes.size());
return;
}
// ***************************************************************************
// utility function to get the directory of a fileName
static std::string getDir (const std::string& path)
{
char tmpPath[512];
strcpy (tmpPath, path.c_str());
char* slash=strrchr (tmpPath, '/');
if (!slash)
{
slash=strrchr (tmpPath, '\\');
}
if (!slash)
return "";
slash++;
*slash=0;
return tmpPath;
}
// ***************************************************************************
// utility function to get a file name fdrom a path
static std::string getName (const std::string& path)
{
std::string dir=getDir (path);
char tmpPath[512];
strcpy (tmpPath, path.c_str());
char *name=tmpPath;
nlassert (dir.length()<=strlen(tmpPath));
name+=dir.length();
char* point=strrchr (name, '.');
if (point)
*point=0;
return name;
}
// ***************************************************************************
// utility function to get the extension of a fileName
static std::string getExt (const std::string& path)
{
std::string dir = getDir (path);
std::string name = getName (path);
char tmpPath[512];
strcpy (tmpPath, path.c_str());
char *ext=tmpPath;
nlassert (dir.length()+name.length()<=strlen(tmpPath));
ext+=dir.length()+name.length();
return ext;
}
// ***************************************************************************
void CZoneLighter::lightWater(CWaterShape &ws, const CMatrix &MT, const CLightDesc& description, uint cpu)
{
try
{
/// get the diffuse map
CTextureFile *diffuseTex = NLMISC::safe_cast<CTextureFile *>(ws.getColorMap());
std::string texFileName = CPath::lookup(diffuseTex->getFileName());
diffuseTex->generate();
const uint width = diffuseTex->getWidth();
const uint height = diffuseTex->getHeight();
/// build a matrix to convert from water space to uv space
NLMISC::CMatrix worldSpaceToUVs;
NLMISC::CVector2f col0, col1, pos;
ws.getColorMapMat(col0, col1, pos);
worldSpaceToUVs.setRot(NLMISC::CVector(col0.x * width, col0.y * height, 0),
NLMISC::CVector(col1.x * width, col1.y * height, 0),
NLMISC::CVector::K);
worldSpaceToUVs.setPos(NLMISC::CVector(pos.x * width, pos.y * height, 0));
/// get min and max uvs
NLMISC::CPolygon p;
ws.getShapeInWorldSpace(p);
float minU, maxU;
float minV, maxV;
NLMISC::CVector uvs = worldSpaceToUVs * p.Vertices[0];
minU = maxU = uvs.x;
minV = maxV = uvs.y;
for (uint k = 1; k < (uint) p.getNumVertices(); ++k)
{
uvs = worldSpaceToUVs * p.Vertices[k];
minU = std::min(uvs.x, minU);
minV = std::min(uvs.y, minV);
maxU = std::max(uvs.x, maxU);
maxV = std::max(uvs.y, maxV);
}
sint iMinU = (sint) minU;
sint iMaxU = (sint) maxU;
sint iMinV = (sint) minV;
sint iMaxV = (sint) maxV;
NLMISC::clamp(iMinU, 0, (sint) width);
NLMISC::clamp(iMaxU, 0, (sint) width);
NLMISC::clamp(iMinV, 0, (sint) height);
NLMISC::clamp(iMaxV, 0, (sint) height);
// matrix to go from uv space to worldspace
NLMISC::CMatrix UVSpaceToWorldSpace = worldSpaceToUVs.inverted();
CObjectVector<uint8> &pixs8 = diffuseTex->getPixels();
NLMISC::CRGBA *rgbPixs = (NLMISC::CRGBA *) &pixs8[0];
/// raytrace each texel
for (sint x = iMinU; x < iMaxU; ++x)
{
for (sint y = iMinV; y < iMaxV; ++y)
{
float factor;
NLMISC::CVector pos = UVSpaceToWorldSpace * NLMISC::CVector( x + 0.5f, y + 0.5f, 0 )
+ description.WaterShadowBias * NLMISC::CVector::K;
if (description.Shadow)
{
factor = attenuation (pos, description);
}
else
{
factor = - NLMISC::CVector::K * description.SunDirection;
}
clamp(factor, 0.f, 1.f);
factor = factor * description.WaterDiffuse + description.WaterAmbient;
if (description.SkyContributionForWater)
{
factor += getSkyContribution(pos, NLMISC::CVector::K, description.SkyIntensity);
}
clamp(factor, 0.f, 1.f);
uint intensity = (uint8) (255 * factor);
NLMISC::CRGBA srcCol(intensity,
intensity,
intensity,
255);
if (!description.ModulateWaterColor)
{
rgbPixs[x + y * width] = srcCol;
}
else
{
NLMISC::CRGBA &col = rgbPixs[x + y * width];
col.modulateFromColor(col, srcCol);
}
}
}
/// now, save the result
if (getExt(texFileName) != ".tga")
{
nlwarning("Zone lighter : error when lighting a water surface : input bitmap is not a tga file");
}
else
{
try
{
COFile of;
of.open(texFileName);
diffuseTex->writeTGA(of, 24);
of.close();
}
catch (NLMISC::Exception &)
{
nlwarning("Zone lighter : while lighting a water shape, writing %s failed! ", texFileName.c_str());
}
}
}
catch(NLMISC::Exception &e)
{
nlwarning("Water shape lighting failed !");
nlwarning(e.what());
}
}
// ***********************************************************
void CZoneLighter::addWaterShape(CWaterShape *shape, const NLMISC::CMatrix &MT)
{
/// make sure it hasn't been inserted twice
CShapeInfo ci;
ci.Shape = shape;
ci.MT = MT;
_WaterShapes.push_back(ci);
}
// ***********************************************************
void CZoneLighter::makeQuadGridFromWaterShapes(NLMISC::CAABBox zoneBBox)
{
if (!_WaterShapes.size()) return;
NLMISC::CAABBox tmpBox;
/// the number of cells we want in the quad grid
const uint numCells = 16;
/// get the dimension
float width = zoneBBox.getMax().x - zoneBBox.getMin().x;
float height = zoneBBox.getMax().y - zoneBBox.getMin().y;
float dim = std::max(width, height);
/// init the quad grid
_WaterShapeQuadGrid.create(numCells, dim / numCells);
uint count = 0, totalCount = (uint)_WaterShapes.size();
15 years ago
/// now, insert all water shapes
for (TShapeVect::iterator it = _WaterShapes.begin(); it != _WaterShapes.end(); ++it, ++count)
{
/// get the current shape bbox in the world
it->Shape->getAABBox(tmpBox);
NLMISC::CAABBox currBB = NLMISC::CAABBox::transformAABBox(it->MT, tmpBox);
/// test if it intesect the zone bbox
if (zoneBBox.intersect(currBB))
{
_WaterShapeQuadGrid.insert(currBB.getMin(), currBB.getMax(), *it);
}
progress("Building quadtree from water surfaces", (float) count / totalCount);
}
/// free the vector of water shapes
NLMISC::contReset(_WaterShapes);
}
//==================================================================
/// a struct that helps us to know which tile we've processed
struct CTileOfPatch
{
uint8 TileId;
CPatch *Patch;
CTileOfPatch();
CTileOfPatch(uint8 tileId, CPatch *patch) : TileId(tileId), Patch(patch)
{
}
};
// ***************************************************************************
// ***************************************************************************
// Static point lights.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CZoneLighter::CPointLightRT::CPointLightRT()
{
RefCount= 0;
}
// ***************************************************************************
bool CZoneLighter::CPointLightRT::testRaytrace(const CVector &v)
{
CVector dummy;
if(!BSphere.include(v))
return false;
// If Ambient light, just skip
if(PointLight.getType()== CPointLight::AmbientLight)
return false;
// If SpotLight verify in angle radius.
if(PointLight.getType()== CPointLight::SpotLight)
{
float att= PointLight.computeLinearAttenuation(v);
if (att==0)
return false;
}
// Select in the cubeGrid
FaceCubeGrid.select(v);
// For all faces selected
while(!FaceCubeGrid.isEndSel())
{
const CTriangle *tri= FaceCubeGrid.getSel();
// If intersect, the point is occluded.
if( tri->Triangle.intersect(BSphere.Center, v, dummy, tri->getPlane()) )
return false;
// next
FaceCubeGrid.nextSel();
}
// Ok the point is visilbe from the light
return true;
}
// ***************************************************************************
void CZoneLighter::addStaticPointLight(const CPointLightNamed &pln)
{
// build the plRT.
CPointLightRT plRT;
plRT.PointLight= pln;
// compute plRT.OODeltaAttenuation
plRT.OODeltaAttenuation= pln.getAttenuationEnd() - pln.getAttenuationBegin();
if(plRT.OODeltaAttenuation <=0 )
plRT.OODeltaAttenuation= 0;
else
plRT.OODeltaAttenuation= 1.0f / plRT.OODeltaAttenuation;
// compute plRT.BSphere
plRT.BSphere.Center= pln.getPosition();
plRT.BSphere.Radius= pln.getAttenuationEnd();
// NB: FaceCubeGrid will be computed during light()
// add the plRT
_StaticPointLights.push_back(plRT);
}
// ***************************************************************************
void CZoneLighter::compilePointLightRT(uint gridSize, float gridCellSize, std::vector<CTriangle>& obstacles, bool doShadow)
{
uint i;
// Fill the quadGrid of Lights.
// ===========
_StaticPointLightQuadGrid.create(gridSize, gridCellSize);
for(i=0; i<_StaticPointLights.size();i++)
{
CPointLightRT &plRT= _StaticPointLights[i];
// Compute the bbox of the light
CAABBox bbox;
bbox.setCenter(plRT.BSphere.Center);
float hl= plRT.BSphere.Radius;
bbox.setHalfSize(CVector(hl,hl,hl));
// Insert the pointLight in the quadGrid.
_StaticPointLightQuadGrid.insert(bbox.getMin(), bbox.getMax(), &plRT);
}
// Append triangles to cubeGrid ??
if(doShadow)
{
// Point lights ?
if (!_StaticPointLights.empty ())
{
// For all obstacles, Fill a quadGrid.
// ===========
CQuadGrid<CTriangle*> obstacleGrid;
obstacleGrid.create(gridSize, gridCellSize);
uint size= (uint)obstacles.size();
15 years ago
for(i=0; i<size; i++)
{
// bbox of triangle
CAABBox bbox;
bbox.setCenter(obstacles[i].Triangle.V0);
bbox.extend(obstacles[i].Triangle.V1);
bbox.extend(obstacles[i].Triangle.V2);
// insert triangle in quadGrid.
obstacleGrid.insert(bbox.getMin(), bbox.getMax(), &obstacles[i]);
}
// For all PointLights, fill his CubeGrid
// ===========
for(i=0; i<_StaticPointLights.size();i++)
{
// progress
progress ("Compute Influences of PointLights", 0.5f*i / (float)(_StaticPointLights.size()-1));
CPointLightRT &plRT= _StaticPointLights[i];
// Create the cubeGrid
plRT.FaceCubeGrid.create(plRT.PointLight.getPosition(), NL3D_ZONE_LIGHTER_CUBE_GRID_SIZE);
// AmbiantLIghts: do nothing.
if(plRT.PointLight.getType()!=CPointLight::AmbientLight)
{
// Select only obstacle Faces around the light. Other are not useful
CAABBox bbox;
bbox.setCenter(plRT.PointLight.getPosition());
float hl= plRT.PointLight.getAttenuationEnd();
bbox.setHalfSize(CVector(hl,hl,hl));
obstacleGrid.select(bbox.getMin(), bbox.getMax());
// For all faces, fill the cubeGrid.
CQuadGrid<CTriangle*>::CIterator itObstacle;
itObstacle= obstacleGrid.begin();
while( itObstacle!=obstacleGrid.end() )
{
CTriangle &tri= *(*itObstacle);
// Triangle bounding box
CAABBox triBbox;
triBbox.setCenter (tri.Triangle.V0);
triBbox.extend (tri.Triangle.V1);
triBbox.extend (tri.Triangle.V2);
// Triangle in the light
if (triBbox.intersect (bbox))
{
// Test BackFace culling. Only faces which are BackFace the point light are inserted.
// This is to avoid AutoOccluding problems
if( tri.getPlane() * plRT.BSphere.Center < 0)
{
// Insert the triangle in the CubeGrid
plRT.FaceCubeGrid.insert( tri.Triangle, &tri);
}
}
itObstacle++;
}
}
// Compile the CubeGrid.
plRT.FaceCubeGrid.compile();
// And Reset RefCount.
plRT.RefCount= 0;
}
}
}
// else, just build empty grid
else
{
for(i=0; i<_StaticPointLights.size();i++)
{
// progress
progress ("Compute Influences of PointLights", 0.5f*i / (float)(_StaticPointLights.size()-1));
CPointLightRT &plRT= _StaticPointLights[i];
// Create a dummy empty cubeGrid => no rayTrace :)
plRT.FaceCubeGrid.create(plRT.PointLight.getPosition(), 4);
// Compile the CubeGrid.
plRT.FaceCubeGrid.compile();
// And Reset RefCount.
plRT.RefCount= 0;
}
}
}
// ***************************************************************************
bool CZoneLighter::CPredPointLightToPoint::operator() (CPointLightRT *pla, CPointLightRT *plb) const
{
float ra= (pla->BSphere.Center - Point).norm();
float rb= (plb->BSphere.Center - Point).norm();
float infA= (pla->PointLight.getAttenuationEnd() - ra) * pla->OODeltaAttenuation;
float infB= (plb->PointLight.getAttenuationEnd() - rb) * plb->OODeltaAttenuation;
// return which light impact the most.
// If same impact
if(infA==infB)
// return nearest
return ra < rb;
else
// return better impact
return infA > infB;
}
// ***************************************************************************
void CZoneLighter::processZonePointLightRT(vector<CPointLightNamed> &listPointLight)
{
uint i;
vector<CPointLightRT*> lightInfs;
lightInfs.reserve(1024);
// clear result list
listPointLight.clear();
// zoneToLight
CZone *zoneToLight= _Landscape->getZone(_ZoneToLight);
if(!zoneToLight)
return;
// Build patchForPLs
//===========
vector<CPatchForPL> patchForPLs;
patchForPLs.resize(_PatchInfo.size());
for(i=0; i<patchForPLs.size(); i++)
{
// Get OrderS/OrderT
patchForPLs[i].OrderS= _PatchInfo[i].OrderS;
patchForPLs[i].OrderT= _PatchInfo[i].OrderT;
// resize TileLightInfluences
uint w= patchForPLs[i].WidthTLI= patchForPLs[i].OrderS/2 +1 ;
uint h= patchForPLs[i].HeightTLI= patchForPLs[i].OrderT/2 +1;
patchForPLs[i].TileLightInfluences.resize(w*h);
}
// compute each TileLightInfluence
//===========
for(i=0; i<patchForPLs.size(); i++)
{
// progress
progress ("Compute Influences of PointLights", 0.5f + 0.5f*i / (float)patchForPLs.size());
CPatchForPL &pfpl= patchForPLs[i];
const CPatch *patch= const_cast<const CZone*>(zoneToLight)->getPatch(i);
uint x, y;
for(y= 0; y<pfpl.HeightTLI; y++)
{
for(x= 0; x<pfpl.WidthTLI; x++)
{
// compute the point and normal (normalized) where the TLI lies.
//---------
CVector pos, normal;
float s, t;
s= (float)x / (pfpl.WidthTLI-1);
t= (float)y / (pfpl.HeightTLI-1);
// Compute the Vertex, with Noise information (important for accurate raytracing).
pos= patch->computeVertex(s, t);
// Use UnNoised normal from BezierPatch, because the lighting does not need to be so precise.
CBezierPatch *bp= patch->unpackIntoCache();
normal= bp->evalNormal(s, t);
// Compute Which light influences him.
//---------
lightInfs.clear();
// Search possible lights around the position.
_StaticPointLightQuadGrid.select(pos, pos);
// For all of them, get the ones which touch this point.
CQuadGrid<CPointLightRT*>::CIterator it= _StaticPointLightQuadGrid.begin();
while(it != _StaticPointLightQuadGrid.end())
{
CPointLightRT *pl= *it;
// a light influence a TLI only if this one is FrontFaced to the light !!
if( ( pl->BSphere.Center - pos ) * normal > 0)
{
// Add 5cm else it fails in some case where ( pl->BSphere.Center - pos ) * normal is
// nearly 0 and the point should be occluded.
const float deltaY= 0.05f;
CVector posToRT= pos + normal * deltaY;
// Test if really in the radius of the light, if no occlusion, and if in SpotAngle
if( pl->testRaytrace(posToRT) )
{
// Ok, add the light to the lights which influence the TLI
lightInfs.push_back(pl);
}
}
// next
it++;
}
// Choose the Best ones.
//---------
CPredPointLightToPoint predPLTP;
predPLTP.Point= pos;
// sort.
sort(lightInfs.begin(), lightInfs.end(), predPLTP);
// truncate.
lightInfs.resize( min((uint)lightInfs.size(), (uint)CTileLightInfluence::NumLightPerCorner) );
// For each of them, fill TLI
//---------
CTileLightInfUnpack tli;
uint lightInfId;
for(lightInfId=0; lightInfId<lightInfs.size(); lightInfId++)
{
CPointLightRT *pl= lightInfs[lightInfId];
// copy light.
tli.Light[lightInfId]= pl;
// Compute light Diffuse factor.
CVector dir= pl->BSphere.Center - pos;
dir.normalize();
tli.LightFactor[lightInfId]= dir * normal;
clamp(tli.LightFactor[lightInfId], 0.f, 1.f);
// modulate by light attenuation.
tli.LightFactor[lightInfId]*= pl->PointLight.computeLinearAttenuation(pos);
// Inc RefCount of the light.
pl->RefCount++;
}
// Reset any empty slot to NULL.
for(; lightInfId<CTileLightInfluence::NumLightPerCorner; lightInfId++)
{
tli.Light[lightInfId]= NULL;
}
// Set TLI in patch.
//---------
pfpl.TileLightInfluences[y*pfpl.WidthTLI + x]= tli;
}
}
}
// compress and setup _PatchInfo with compressed data.
//===========
uint plId= 0;
// Process each pointLights
for(i=0; i<_StaticPointLights.size(); i++)
{
CPointLightRT &plRT= _StaticPointLights[i];
// If this light is used.
if(plRT.RefCount > 0)
{
// Must Copy it into Zone.
listPointLight.push_back(plRT.PointLight);
plRT.DstId= plId++;
// If index >= 255, too many lights (NB: => because 255 is a NULL code).
if(plId>=0xFF)
{
throw Exception("Too many Static Point Lights influence the zone!!");
}
}
}
// For each patch, compress TLI in PatchInfo.
for(i=0; i<patchForPLs.size(); i++)
{
CPatchForPL &pfpl= patchForPLs[i];
CPatchInfo &pInfo= _PatchInfo[i];
uint w= pfpl.WidthTLI;
uint h= pfpl.HeightTLI;
// Fill pInfo.TileLightInfluences
pInfo.TileLightInfluences.resize(w*h);
uint x, y;
for(y= 0; y<h; y++)
{
for(x= 0; x<w; x++)
{
uint tliId= y*w + x;
// For all light slot
for(uint lightId= 0; lightId<CTileLightInfluence::NumLightPerCorner; lightId++)
{
CTileLightInfUnpack &tliSrc= pfpl.TileLightInfluences[tliId];
CTileLightInfluence &tliDst= pInfo.TileLightInfluences[tliId];
if(tliSrc.Light[lightId] == NULL)
{
// Mark as unused.
tliDst.Light[lightId]= 0xFF;
}
else
{
// Get index.
tliDst.Light[lightId]= tliSrc.Light[lightId]->DstId;
// Get Diffuse Factor.
tliDst.setDiffuseLightFactor(lightId, (uint8)(tliSrc.LightFactor[lightId]*255));
}
}
}
}
}
}
// ***********************************************************
// ***********************************************************
// TileFlagsForPositionTowardWater
// ***********************************************************
// ***********************************************************
//==================================================================
/// for map insertion of CTileOfPatch structs
static inline bool operator < (const CTileOfPatch &lhs, const CTileOfPatch &rhs)
{
return lhs.Patch == rhs.Patch ?
lhs.TileId < rhs.TileId :
lhs.Patch < rhs.Patch;
};
/// A set of tiles from patch and their bbox
typedef std::map<CTileOfPatch, NLMISC::CAABBox> TTileOfPatchMap;
// ***********************************************************
void CZoneLighter::computeTileFlagsForPositionTowardWater(const CLightDesc &lightDesc,
std::vector<const CTessFace*> &tessFaces
)
{
uint numTileAbove = 0;
uint numTileBelow = 0;
uint numTileIntersect = 0;
/// the tiles that we have setupped so far...
TTileOfPatchMap tiles;
///////////////////////////////////////////
// First, build the bbox for all tiles //
///////////////////////////////////////////
uint triCount = 0, totalTriCount = (uint)tessFaces.size();
15 years ago
nlinfo("Dealing with %d tessFaces", tessFaces.size());
for (std::vector<const CTessFace*>::iterator it = tessFaces.begin(); it != tessFaces.end(); ++it, ++triCount)
{
/// does the face belong to the zone to light ?
if ((*it)->Patch->getZone()->getZoneId() != _ZoneToLight) continue;
/// if the tile flags say that micro vegetation is disabled, just skip that
if ((*it)->Patch->Tiles[(*it)->TileId].getVegetableState() == CTileElement::VegetableDisabled)
continue;
CTileOfPatch top((*it)->TileId, (*it)->Patch);
TTileOfPatchMap::iterator tileIt = tiles.find(top);
/// test whether we've seen face(s) from this tile before
if (tileIt == tiles.end()) // first time ?
{
/// build a bbox for this face
NLMISC::CAABBox b;
b.setMinMax((*it)->VBase->EndPos, (*it)->VLeft->EndPos);
b.extend((*it)->VRight->EndPos);
b.extend(b.getMax() + lightDesc.VegetableHeight * NLMISC::CVector::K); // adds vegetable height
tiles[top] = b;
}
else // extends the bbox with the given face
{
NLMISC::CAABBox &b = tileIt->second;
b.extend((*it)->VBase->EndPos);
b.extend((*it)->VRight->EndPos);
b.extend((*it)->VLeft->EndPos);
}
if ((triCount % 100) == 0)
{
progress("Building bbox from tiles", (float) triCount / totalTriCount);
}
}
progress("Building bbox from tiles", 1.f);
////////////////////////////////////////////////////
// Now, check each tile bbox against water shapes //
////////////////////////////////////////////////////
NLMISC::CPolygon waterPoly;
NLMISC::CPolygon2D tilePoly;
tilePoly.Vertices.resize(4);
uint tileCount = 0, totalTileCount = (uint)tiles.size();
15 years ago
for (TTileOfPatchMap::iterator tileIt = tiles.begin(); tileIt != tiles.end(); ++tileIt, ++tileCount)
{
const NLMISC::CVector v0 = tileIt->second.getMin();
const NLMISC::CVector v1 = tileIt->second.getMax();
/// build a top view from the bbox
tilePoly.Vertices[0].set(v0.x, v0.y);
tilePoly.Vertices[1].set(v1.x, v0.y);
tilePoly.Vertices[2].set(v1.x, v1.y);
tilePoly.Vertices[3].set(v0.x, v1.y);
/// Select the candidate water shape from the quad grid
_WaterShapeQuadGrid.clearSelection();
_WaterShapeQuadGrid.select(tileIt->second.getMin(), tileIt->second.getMax());
CTileElement &te = tileIt->first.Patch->Tiles[tileIt->first.TileId]; // alias to the current tile element
/// test more accurate intersection for each water shape
TWaterShapeQuadGrid::CIterator qgIt;
for (qgIt = _WaterShapeQuadGrid.begin(); qgIt != _WaterShapeQuadGrid.end(); ++qgIt)
{
CWaterShape *waterShape= safe_cast<CWaterShape*>((*qgIt).Shape);
waterShape->getShapeInWorldSpace(waterPoly, (*qgIt).MT);
NLMISC::CPolygon2D poly(waterPoly);
if (poly.intersect(tilePoly)) // above or below a water surface ?
{
/// height of water
float waterHeight = waterPoly.Vertices[0].z;
if (v1.z < waterHeight)
{
// below
te.setVegetableState(CTileElement::UnderWater);
//nlassert(te.getVegetableState() == CTileElement::UnderWater);
++ numTileBelow;
}
else if (v0. z > waterHeight)
{
// above
te.setVegetableState(CTileElement::AboveWater);
//nlassert(te.getVegetableState() == CTileElement::AboveWater);
++ numTileAbove;
}
else
{
// intersect water
te.setVegetableState(CTileElement::IntersectWater);
//nlassert(te.getVegetableState() == CTileElement::IntersectWater);
++ numTileIntersect;
}
break;
}
}
if (qgIt == _WaterShapeQuadGrid.end()) // no intersection found ? if yes it's above water
{
te.setVegetableState(CTileElement::AboveWater);
//nlassert(te.getVegetableState() == CTileElement::AboveWater);
++ numTileAbove;
}
if ((tileCount % 50) == 0)
{
progress("Computing tile position towards water", (float) tileCount / totalTileCount);
}
}
progress("Computing tile position towards water", 1.f);
nlinfo(" %d tiles are above water.", numTileAbove);
nlinfo(" %d tiles are below water.", numTileBelow);
nlinfo(" %d tiles intersect water.", numTileIntersect);
/// delete the quadgrid now
NLMISC::contReset(_WaterShapeQuadGrid);
}
// ***********************************************************
void CZoneLighter::setTileFlagsToDefault(std::vector<const CTessFace*> &tessFaces)
{
/// We may setup a tile several time, but this doesn't matter here...
for (std::vector<const CTessFace*>::iterator it = tessFaces.begin(); it != tessFaces.end(); ++it)
{
if ((*it)->Patch->getZone()->getZoneId() != _ZoneToLight) continue;
CTileElement &te = (*it)->Patch->Tiles[(*it)->TileId];
if (te.getVegetableState() != CTileElement::VegetableDisabled)
{
te.setVegetableState(CTileElement::AboveWater);
}
}
}
// ***********************************************************
uint CZoneLighter::getAPatch (uint process)
{
// Accessor
CSynchronized<std::vector<bool> >::CAccessor access (&_PatchComputed);
// Current index
uint index = _LastPatchComputed[process];
uint firstIndex = index;
if (access.value().size() == 0)
// no more patches
return 0xffffffff;
while (access.value()[index])
{
// Next patch
index++;
// Last patch ?
if (index == _PatchInfo.size())
index = 0;
// First ?
if (firstIndex == index)
// no more patches
return 0xffffffff;
}
// Visited
access.value()[index] = true;
// Last index
_LastPatchComputed[process] = index;
_NumberOfPatchComputed++;
// Return the index
return index;
}
// ***********************************************************
float CZoneLighter::attenuation (const CVector &pos, const CZoneLighter::CLightDesc &description)
{
// Clipped ?
// *** Landscape attenuation
// Current value
float averageAttenuation = 0;
float randomSum = 0;
// For each sample
uint sample;
const uint samples = description.SoftShadowSamplesSqrt*description.SoftShadowSamplesSqrt;
for (sample=0; sample<samples; sample++)
{
// The zbuffer
CZBuffer &zbuffer = _ZBufferLandscape[sample];
// Get position in z buffer
CVector zPos;
transformVectorToZBuffer (zbuffer, pos, zPos);
// Get the z
float random = (float)_Random.rand () * description.SoftShadowJitter + _Random.RandMax * (1.f - description.SoftShadowJitter);
averageAttenuation += random * testZPercentageCloserFilter (zPos.x-(float)zbuffer.LocalZBufferXMin, zPos.y-(float)zbuffer.LocalZBufferYMin, zPos.z, zbuffer, description, _ZBufferOverflow);
randomSum += random;
}
// Average landscape attenuation
averageAttenuation /= randomSum;
// *** Attenuation in the object zbuffer
// Get position in z buffer
CVector zPos;
transformVectorToZBuffer (_ZBufferObject, pos, zPos);
const float objectAttenuation = testZPercentageCloserFilter (zPos.x-(float)_ZBufferObject.LocalZBufferXMin, zPos.y-(float)_ZBufferObject.LocalZBufferYMin, zPos.z, _ZBufferObject, description, _ZBufferOverflow);
// *** Return the min of the both
return std::min (objectAttenuation, averageAttenuation);
}
// ***********************************************************