tge/engine/lightingSystem/sgSceneLightingTerrain.cc
2017-04-17 06:17:10 -06:00

1026 lines
29 KiB
C++
Executable File

//-----------------------------------------------
// Synapse Gaming - Lighting System
// Copyright © Synapse Gaming 2003
// Written by John Kabus
//-----------------------------------------------
#include "editor/editTSCtrl.h"
#include "editor/worldEditor.h"
#include "game/shadow.h"
#include "game/vehicles/wheeledVehicle.h"
#include "game/gameConnection.h"
#include "sceneGraph/sceneGraph.h"
#include "terrain/terrRender.h"
#include "game/shapeBase.h"
#include "gui/core/guiCanvas.h"
#include "ts/tsShape.h"
#include "ts/tsShapeInstance.h"
#include "game/staticShape.h"
#include "game/tsStatic.h"
#include "collision/concretePolyList.h"
#include "lightingSystem/sgSceneLighting.h"
#include "lightingSystem/sgLightMap.h"
#include "lightingSystem/sgSceneLightingGlobals.h"
#define TERRAIN_OVERRANGE 2.0f
extern SceneLighting *gLighting;
/// adds the ability to bake point lights into terrain light maps.
void SceneLighting::TerrainProxy::sgAddUniversalPoint(LightInfo *light)
{
// add the light...
sgLights.push_back(light);
}
void SceneLighting::TerrainProxy::sgLightUniversalPoint(LightInfo *light, TerrainBlock * terrain)
{
// only continue on the first light...
if(sgLights.first() != light)
return;
// process the lighting...
const F32 length = (terrain->getSquareSize() * TerrainBlock::BlockSize);
const F32 halflength = 0 - (length * 0.5);
// texel world space size...
const F32 offset = length / ((F32)TerrainBlock::LightmapSize);
sgTerrainLightMap *lightmap = new sgTerrainLightMap(TerrainBlock::LightmapSize, TerrainBlock::LightmapSize, terrain);
lightmap->sgWorldPosition = Point3D(halflength, halflength, 0);
lightmap->sgLightMapSVector = Point3D(offset, 0, 0);
lightmap->sgLightMapTVector = Point3D(0, offset, 0);
for(U32 i=0; i<sgLights.size(); i++)
lightmap->sgCalculateLighting(sgLights[i]);
lightmap->sgMergeLighting(sgBakedLightmap);
delete lightmap;
}
SceneLighting::TerrainProxy::TerrainProxy(SceneObject * obj) :
Parent(obj)
{
mLightmap = 0;
sgBakedLightmap = 0;
}
SceneLighting::TerrainProxy::~TerrainProxy()
{
delete [] mLightmap;
if(sgBakedLightmap)
delete[] sgBakedLightmap;
}
//-------------------------------------------------------------------------------
void SceneLighting::TerrainProxy::init()
{
mLightmap = new ColorF[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize];
dMemset(mLightmap, 0, TerrainBlock::LightmapSize * TerrainBlock::LightmapSize * sizeof(ColorF));
mShadowMask.setSize(TerrainBlock::BlockSize * TerrainBlock::BlockSize);
sgBakedLightmap = new ColorF[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize];
dMemset(sgBakedLightmap, 0, TerrainBlock::LightmapSize * TerrainBlock::LightmapSize * sizeof(ColorF));
}
/// reroutes TerrainProxy::preLight for point light and TSStatic support.
bool SceneLighting::TerrainProxy::preLight(LightInfo * light)
{
if(!bool(mObj))
return(false);
if((light->mType != LightInfo::Vector) &&
(light->mType != LightInfo::SGStaticPoint) &&
(light->mType != LightInfo::SGStaticSpot))
return(false);
if((light->mType == LightInfo::SGStaticPoint) || (light->mType == LightInfo::SGStaticSpot))
sgAddUniversalPoint(light);
mShadowMask.clear();
return(true);
}
static inline U16 convertColor(ColorF color)
{
return((U32(color.blue * 31.f + 0.5f) << 11) |
(U32(color.green * 31.f + 0.5f) << 6) |
(U32(color.red * 31.f + 0.5f) << 1) | 1);
}
void sgProcessLightMap(TerrainBlock *terrain, ColorF *mLightmap, ColorF *sgBakedLightmap)
{
ColorF color;
U16 * lPtr = (U16*)terrain->lightMap->getAddress(0,0);
for(U32 i = 0; i < (TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); i++)
{
color.red = mLightmap[i].red + sgBakedLightmap[i].red;
color.green = mLightmap[i].green + sgBakedLightmap[i].green;
color.blue = mLightmap[i].blue + sgBakedLightmap[i].blue;
color.clamp();
lPtr[i] = convertColor(color);
}
}
/// reroutes TerrainProxy::postLight for point light and TSStatic support.
void SceneLighting::TerrainProxy::postLight(bool lastLight)
{
TerrainBlock * terrain = getObject();
if((!terrain) || (!lastLight))
return;
// set the lightmap...
//ColorF *sgBakedLightmap = sgBakedLightmap;
sgProcessLightMap(terrain, mLightmap, sgBakedLightmap);
// trigger a reload...
terrain->triggerLightmapReload();
terrain->buildMaterialMap();
}
/// reroutes TerrainProxy::light for point light and TSStatic support.
void SceneLighting::TerrainProxy::light(LightInfo * light)
{
TerrainBlock * terrain = getObject();
if(!terrain)
return;
AssertFatal(((light->mType == LightInfo::Vector) ||
(light->mType == LightInfo::SGStaticPoint) ||
(light->mType == LightInfo::SGStaticSpot)), "wrong light type");
//S32 time = Platform::getRealMilliseconds();
if((light->mType == LightInfo::SGStaticPoint) || (light->mType == LightInfo::SGStaticSpot))
{
//do baked point light here...
sgLightUniversalPoint(light, terrain);
//Con::printf(" = terrain lit in %3.3f seconds (sg)", (Platform::getRealMilliseconds()-time)/1000.f);
return;
}
// reset
mShadowVolume = new ShadowVolumeBSP;
if((light->mType == LightInfo::Vector) && LightManager::sgAllowShadows())
{
// build interior shadow volume
for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++)
{
if(!gLighting->isInterior((*itr)->mObj))
continue;
if(markInteriorShadow(static_cast<InteriorProxy*>(*itr)))
gLighting->addInterior(mShadowVolume, *static_cast<InteriorProxy*>(*itr), light, SceneLighting::SHADOW_DETAIL);
}
// Static object support...
Vector<SceneObject *> objects;
gClientContainer.findObjects(ShadowCasterObjectType, sgFindObjectsCallback, &objects);
// build static shadow volume
for(U32 o=0; o<objects.size(); o++)
{
SceneObject *obj = objects[o];
if(gLighting->sgIsCorrectStaticObjectType(obj))
{
if(sgMarkStaticShadow(this, obj, light))
gLighting->addStatic(this, mShadowVolume, obj, light, SceneLighting::SHADOW_DETAIL);
}
}
}
//--------------------
lightVector(light);
delete mShadowVolume;
//Con::printf(" = terrain lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f);
}
//------------------------------------------------------------------------------
S32 SceneLighting::TerrainProxy::testSquare(const Point3F & min, const Point3F & max, S32 mask, F32 expand, const Vector<PlaneF> & clipPlanes)
{
expand = 0;
S32 retMask = 0;
Point3F minPoint, maxPoint;
for(S32 i = 0; i < clipPlanes.size(); i++)
{
if(mask & (1 << i))
{
if(clipPlanes[i].x > 0)
{
maxPoint.x = max.x;
minPoint.x = min.x;
}
else
{
maxPoint.x = min.x;
minPoint.x = max.x;
}
if(clipPlanes[i].y > 0)
{
maxPoint.y = max.y;
minPoint.y = min.y;
}
else
{
maxPoint.y = min.y;
minPoint.y = max.y;
}
if(clipPlanes[i].z > 0)
{
maxPoint.z = max.z;
minPoint.z = min.z;
}
else
{
maxPoint.z = min.z;
minPoint.z = max.z;
}
F32 maxDot = mDot(maxPoint, clipPlanes[i]);
F32 minDot = mDot(minPoint, clipPlanes[i]);
F32 planeD = clipPlanes[i].d;
if(maxDot <= -(planeD + expand))
return(U16(-1));
if(minDot <= -planeD)
retMask |= (1 << i);
}
}
return(retMask);
}
bool SceneLighting::TerrainProxy::getShadowedSquares(const Vector<PlaneF> & clipPlanes, Vector<U16> & shadowList)
{
TerrainBlock * terrain = getObject();
if(!terrain)
return(false);
SquareStackNode stack[TerrainBlock::BlockShift * 4];
stack[0].mLevel = TerrainBlock::BlockShift;
stack[0].mClipFlags = 0xff;
stack[0].mPos.set(0,0);
U32 stackSize = 1;
Point3F blockPos;
terrain->getTransform().getColumn(3, &blockPos);
S32 squareSize = terrain->getSquareSize();
bool marked = false;
// push through all the levels of the quadtree
while(stackSize)
{
SquareStackNode * node = &stack[stackSize - 1];
S32 clipFlags = node->mClipFlags;
Point2I pos = node->mPos;
GridSquare * sq = terrain->findSquare(node->mLevel, pos);
Point3F minPoint, maxPoint;
minPoint.set(squareSize * pos.x + blockPos.x,
squareSize * pos.y + blockPos.y,
fixedToFloat(sq->minHeight));
maxPoint.set(minPoint.x + (squareSize << node->mLevel),
minPoint.y + (squareSize << node->mLevel),
fixedToFloat(sq->maxHeight));
// test the square against the current level
if(clipFlags)
{
clipFlags = testSquare(minPoint, maxPoint, clipFlags, squareSize, clipPlanes);
if(clipFlags == U16(-1))
{
stackSize--;
continue;
}
}
// shadowed?
if(node->mLevel == 0)
{
marked = true;
shadowList.push_back(pos.x + (pos.y << TerrainBlock::BlockShift));
stackSize--;
continue;
}
// setup the next level of squares
U8 nextLevel = node->mLevel - 1;
S32 squareHalfSize = 1 << nextLevel;
for(U32 i = 0; i < 4; i++)
{
node[i].mLevel = nextLevel;
node[i].mClipFlags = clipFlags;
}
node[3].mPos = pos;
node[2].mPos.set(pos.x + squareHalfSize, pos.y);
node[1].mPos.set(pos.x, pos.y + squareHalfSize);
node[0].mPos.set(pos.x + squareHalfSize, pos.y + squareHalfSize);
stackSize += 3;
}
return(marked);
}
bool SceneLighting::TerrainProxy::markInteriorShadow(InteriorProxy * proxy)
{
U32 i;
// setup the clip planes: add the test and the lit planes
Vector<PlaneF> clipPlanes;
clipPlanes = proxy->mTerrainTestPlanes;
for(i = 0; i < proxy->mLitBoxSurfaces.size(); i++)
clipPlanes.push_back(proxy->mLitBoxSurfaces[i]->mPlane);
Vector<U16> shadowList;
if(!getShadowedSquares(clipPlanes, shadowList))
return(false);
// set the correct bit
for(i = 0; i < shadowList.size(); i++)
mShadowMask.set(shadowList[i]);
return(true);
}
//-------------------------------------------------------------------------------
// BUGS: does not work with x or y directions of 0
// : light dir of 0.1, 0.3, -0.8 causes strange behavior
void SceneLighting::TerrainProxy::lightVector(LightInfo * light)
{
TerrainBlock * terrain = getObject();
if(!terrain)
return;
Point3F lightDir = light->mDirection;
lightDir.normalize();
ColorF & ambient = light->mAmbient;
ColorF & lightColor = light->mColor;
if(lightDir.x == 0 && lightDir.y == 0)
return;
S32 generateLevel = Con::getIntVariable("$pref::sceneLighting::terrainGenerateLevel", 0);
generateLevel = mClamp(generateLevel, 0, 4);
bool allowLexelSplits = Con::getBoolVariable("$pref::sceneLighting::terrainAllowLexelSplits", false);
U32 generateDim = TerrainBlock::LightmapSize << generateLevel;
U32 generateShift = TerrainBlock::LightmapShift + generateLevel;
U32 generateMask = generateDim - 1;
F32 zStep;
F32 frac;
Point2I blockColStep;
Point2I blockRowStep;
Point2I blockFirstPos;
Point2I lmFirstPos;
F32 terrainDim = F32(terrain->getSquareSize()) * F32(TerrainBlock::BlockSize);
F32 stepSize = F32(terrain->getSquareSize()) / F32(generateDim / TerrainBlock::BlockSize);
if(mFabs(lightDir.x) >= mFabs(lightDir.y))
{
if(lightDir.x > 0)
{
zStep = lightDir.z / lightDir.x;
frac = lightDir.y / lightDir.x;
blockColStep.set(1, 0);
blockRowStep.set(0, 1);
blockFirstPos.set(0, 0);
lmFirstPos.set(0,0);
}
else
{
zStep = -lightDir.z / lightDir.x;
frac = -lightDir.y / lightDir.x;
blockColStep.set(-1, 0);
blockRowStep.set(0, 1);
blockFirstPos.set(255, 0);
lmFirstPos.set(TerrainBlock::LightmapSize - 1,0);
}
}
else
{
if(lightDir.y > 0)
{
zStep = lightDir.z / lightDir.y;
frac = lightDir.x / lightDir.y;
blockColStep.set(0, 1);
blockRowStep.set(1, 0);
blockFirstPos.set(0, 0);
lmFirstPos.set(0,0);
}
else
{
zStep = -lightDir.z / lightDir.y;
frac = -lightDir.x / lightDir.y;
blockColStep.set(0, -1);
blockRowStep.set(1, 0);
blockFirstPos.set(0, 255);
lmFirstPos.set(0, TerrainBlock::LightmapSize - 1);
}
}
zStep *= stepSize;
F32 * heightArray = new F32[generateDim];
S32 fracStep = -1;
if(frac < 0)
{
fracStep = 1;
frac = -frac;
}
F32 * nextHeightArray = new F32[generateDim];
F32 oneMinusFrac = 1 - frac;
U32 blockShift = generateShift - TerrainBlock::BlockShift;
U32 lightmapShift = generateShift - TerrainBlock::LightmapShift;
U32 blockStep = 1 << blockShift;
U32 blockMask = blockStep - 1;
U32 lightmapStep = 1 << lightmapShift;
U32 lightmapNormalOffset = lightmapStep >> 1;
U32 lightmapMask = lightmapStep - 1;
Point2I bp = blockFirstPos;
F32 terrainHeights[2][TerrainBlock::BlockSize];
U32 i;
F32 * pTerrainHeights = static_cast<F32*>(terrainHeights[0]);
F32 * pNextTerrainHeights = static_cast<F32*>(terrainHeights[1]);
// get first set of heights
for(i = 0; i < TerrainBlock::BlockSize; i++) {
pTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y));
bp += blockRowStep;
}
// get second set of heights
bp = blockFirstPos + blockColStep;
for(i = 0; i < TerrainBlock::BlockSize; i++) {
pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y));
bp += blockRowStep;
}
F32 heightStep = 1.f / blockStep;
F32 terrainZRowStep[TerrainBlock::BlockSize];
F32 nextTerrainZRowStep[TerrainBlock::BlockSize];
F32 terrainZColStep[TerrainBlock::BlockSize];
// fill in the row steps
for(i = 0; i < TerrainBlock::BlockSize; i++)
{
terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep;
nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep;
terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep;
}
// get first row of process heights
for(i = 0; i < generateDim; i++)
{
U32 bi = i >> blockShift;
heightArray[i] = pTerrainHeights[bi] + (i & blockMask) * terrainZRowStep[bi];
}
bp = blockFirstPos;
if(generateDim == TerrainBlock::BlockSize)
bp += blockColStep;
// generate the initial run
U32 x, y;
for(x = 1; x < generateDim; x++)
{
U32 xmask = x & blockMask;
// generate new height step rows?
if(!xmask)
{
F32 * tmp = pTerrainHeights;
pTerrainHeights = pNextTerrainHeights;
pNextTerrainHeights = tmp;
bp += blockColStep;
Point2I bwalk = bp;
for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep)
pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y));
// fill in the row steps
for(i = 0; i < TerrainBlock::BlockSize; i++)
{
terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep;
nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep;
terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep;
}
}
Point2I bwalk = bp - blockRowStep;
for(y = 0; y < generateDim; y++)
{
U32 ymask = y & blockMask;
if(!ymask)
bwalk += blockRowStep;
U32 bi = y >> blockShift;
U32 binext = (bi + 1) & TerrainBlock::BlockMask;
F32 height;
// 135?
if((bwalk.x ^ bwalk.y) & 1)
{
U32 xsub = blockStep - xmask;
if(xsub > ymask) // bottom
height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] +
ymask * terrainZRowStep[bi];
else // top
height = pNextTerrainHeights[bi] - xmask * terrainZColStep[binext] +
ymask * nextTerrainZRowStep[bi];
}
else
{
if(xmask > ymask) // bottom
height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] +
ymask * nextTerrainZRowStep[bi];
else // top
height = pTerrainHeights[bi] + xmask * terrainZColStep[binext] +
ymask * terrainZRowStep[bi];
}
F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep;
nextHeightArray[y] = getMax(height, intHeight);
}
// swap the height rows
F32 * tmp = heightArray;
heightArray = nextHeightArray;
nextHeightArray = tmp;
}
F32 squareSize = terrain->getSquareSize();
F32 lexelDim = squareSize * F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize);
// calculate normal runs
Point3F normals[2][TerrainBlock::BlockSize];
Point3F * pNormals = static_cast<Point3F*>(normals[0]);
Point3F * pNextNormals = static_cast<Point3F*>(normals[1]);
// calculate the normal lookup table
F32 * normTable = new F32 [blockStep * blockStep * 4];
Point2F corners[4] = {
Point2F(0.f, 0.f),
Point2F(1.f, 0.f),
Point2F(1.f, 1.f),
Point2F(0.f, 1.f)
};
U32 idx = 0;
F32 step = 1.f / blockStep;
Point2F pos(0.f, 0.f);
// fill it
for(x = 0; x < blockStep; x++, pos.x += step, pos.y = 0.f) {
for(y = 0; y < blockStep; y++, pos.y += step) {
for(i = 0; i < 4; i++, idx++)
normTable[idx] = 1.f - getMin(Point2F(pos - corners[i]).len(), 1.f);
}
}
// fill first column
bp = blockFirstPos;
for(x = 0; x < TerrainBlock::BlockSize; x++)
{
pNextTerrainHeights[x] = fixedToFloat(terrain->getHeight(bp.x, bp.y));
Point2F pos(bp.x * squareSize, bp.y * squareSize);
terrain->getNormal(pos, &pNextNormals[x]);
bp += blockRowStep;
}
// get swapped on first pass
pTerrainHeights = static_cast<F32*>(terrainHeights[1]);
pNextTerrainHeights = static_cast<F32*>(terrainHeights[0]);
// get the world offset of the terrain
const MatrixF & transform = terrain->getTransform();
Point3F worldOffset;
transform.getColumn(3, &worldOffset);
F32 ratio = F32(1 << lightmapShift);
F32 ratioSquared = ratio * ratio;
F32 inverseRatioSquared = 1.f / ratioSquared;
F32 lightScale[TerrainBlock::LightmapSize];
// walk it...
bp = blockFirstPos - blockColStep;
for(x = 0; x < generateDim; x++)
{
U32 xmask = x & blockMask;
U32 lxmask = x & lightmapMask;
// generate new runs?
if(!xmask)
{
bp += blockColStep;
// do the normals
Point3F * temp = pNormals;
pNormals = pNextNormals;
pNextNormals = temp;
// fill the row
Point2I bwalk = bp + blockColStep;
for(i = 0; i < TerrainBlock::BlockSize; i++)
{
Point2F pos(bwalk.x * squareSize, bwalk.y * squareSize);
terrain->getNormal(pos, &pNextNormals[i]);
bwalk += blockRowStep;
}
// do the heights
F32 * tmp = pTerrainHeights;
pTerrainHeights = pNextTerrainHeights;
pNextTerrainHeights = tmp;
bwalk = bp + blockColStep;
for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep)
pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y));
// fill in the row steps
for(i = 0; i < TerrainBlock::BlockSize; i++)
{
terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep;
nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep;
terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep;
}
}
// reset the light scale table
if(!lxmask)
for(i = 0; i < TerrainBlock::LightmapSize; i++)
lightScale[i] = 1.f;
Point2I bwalk = bp - blockRowStep;
for(y = 0; y < generateDim; y++)
{
U32 lymask = y & lightmapMask;
U32 ymask = y & blockMask;
if(!ymask)
bwalk += blockRowStep;
U32 bi = y >> blockShift;
U32 binext = (bi + 1) & TerrainBlock::BlockMask;
F32 height;
F32 xstep, ystep;
// 135?
if((bwalk.x ^ bwalk.y) & 1)
{
U32 xsub = blockStep - xmask;
if(xsub > ymask) // bottom
{
xstep = terrainZColStep[bi];
ystep = terrainZRowStep[bi];
height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep;
}
else // top
{
xstep = -terrainZColStep[binext];
ystep = nextTerrainZRowStep[bi];
height = pNextTerrainHeights[bi] + xsub * xstep + ymask * ystep;
}
}
else
{
if(xmask > ymask) // bottom
{
xstep = terrainZColStep[bi];
ystep = nextTerrainZRowStep[bi];
height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep;
}
else // top
{
xstep = terrainZColStep[binext];
ystep = terrainZRowStep[bi];
height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep;
}
}
F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep;
U32 lsi = y >> lightmapShift;
Point2I lmPos = lmFirstPos + blockColStep * (x >> lightmapShift) + blockRowStep * lsi;
ColorF & col = mLightmap[lmPos.x + (lmPos.y << TerrainBlock::LightmapShift)];
// lexel shaded by an interior?
Point2I terrPos = lmPos;
terrPos.x >>= TerrainBlock::LightmapShift - TerrainBlock::BlockShift;
terrPos.y >>= TerrainBlock::LightmapShift - TerrainBlock::BlockShift;
if(!lxmask && !lymask && mShadowMask.test(terrPos.x + (terrPos.y << TerrainBlock::BlockShift)))
{
U32 idx = (xmask + lightmapNormalOffset + ((ymask + lightmapNormalOffset) << blockShift)) << 2;
// get the normal for this lexel
Point3F normal = pNormals[bi] * normTable[idx++];
normal += pNormals[binext] * normTable[idx++];
normal += pNextNormals[binext] * normTable[idx++];
normal += pNextNormals[bi] * normTable[idx];
normal.normalize();
nextHeightArray[y] = height;
F32 colorScale = -mDot(normal, lightDir);
if(colorScale > 0.f)
{
// split lexels which cross the square split?
if(allowLexelSplits)
{
// jff:todo
}
else
{
Point2F wPos((lmPos.x) * lexelDim + worldOffset.x,
(lmPos.y) * lexelDim + worldOffset.y);
F32 xh = xstep * ratio;
F32 yh = ystep * ratio;
ShadowVolumeBSP::SVPoly * poly = mShadowVolume->createPoly();
poly->mWindingCount = 4;
poly->mWinding[0].set(wPos.x, wPos.y, height);
poly->mWinding[1].set(wPos.x + lexelDim, wPos.y, height + xh);
poly->mWinding[2].set(wPos.x + lexelDim, wPos.y + lexelDim, height + xh + yh);
poly->mWinding[3].set(wPos.x, wPos.y + lexelDim, height + yh);
poly->mPlane.set(poly->mWinding[0], poly->mWinding[1], poly->mWinding[2]);
F32 lexelSize = mShadowVolume->getPolySurfaceArea(poly);
F32 intensity = mShadowVolume->getClippedSurfaceArea(poly) / lexelSize;
lightScale[lsi] = mClampF(intensity, 0.f, 1.f);
}
}
else
lightScale[lsi] = 0.f;
}
// non shadowed?
if(height >= intHeight)
{
U32 idx = (xmask + (ymask << blockShift)) << 2;
Point3F normal = pNormals[bi] * normTable[idx++];
normal += pNormals[binext] * normTable[idx++];
normal += pNextNormals[binext] * normTable[idx++];
normal += pNextNormals[bi] * normTable[idx];
normal.normalize();
nextHeightArray[y] = height;
F32 colorScale = -mDot(normal, lightDir);
if(colorScale > 0.f)
col += ambient + lightColor * colorScale * lightScale[lsi];
else
col += ambient;
}
else
{
nextHeightArray[y] = intHeight;
col += ambient;
}
}
F32 * tmp = heightArray;
heightArray = nextHeightArray;
nextHeightArray = tmp;
}
// set the proper color
for(i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++)
{
mLightmap[i] *= inverseRatioSquared;
mLightmap[i].clamp();
}
delete [] normTable;
delete [] heightArray;
delete [] nextHeightArray;
}
//--------------------------------------------------------------------------
U32 SceneLighting::TerrainProxy::getResourceCRC()
{
TerrainBlock * terrain = getObject();
if(!terrain)
return(0);
return(terrain->getCRC());
}
//--------------------------------------------------------------------------
bool SceneLighting::TerrainProxy::setPersistInfo(PersistInfo::PersistChunk * info)
{
if(!Parent::setPersistInfo(info))
return(false);
PersistInfo::TerrainChunk * chunk = dynamic_cast<PersistInfo::TerrainChunk*>(info);
AssertFatal(chunk, "SceneLighting::TerrainProxy::setPersistInfo: invalid info chunk!");
TerrainBlock * terrain = getObject();
if(!terrain || !terrain->lightMap)
return(false);
// trigger a reload...
terrain->triggerLightmapReload();
dMemcpy(terrain->lightMap->getAddress(0,0), chunk->mLightmap, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize);
terrain->buildMaterialMap();
return(true);
}
bool SceneLighting::TerrainProxy::getPersistInfo(PersistInfo::PersistChunk * info)
{
if(!Parent::getPersistInfo(info))
return(false);
PersistInfo::TerrainChunk * chunk = dynamic_cast<PersistInfo::TerrainChunk*>(info);
AssertFatal(chunk, "SceneLighting::TerrainProxy::getPersistInfo: invalid info chunk!");
TerrainBlock * terrain = getObject();
if(!terrain || !terrain->lightMap)
return(false);
chunk->mLightmap = new U16[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize];
dMemcpy(chunk->mLightmap, terrain->lightMap->getAddress(0,0), sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize);
return(true);
}
/// adds TSStatic objects as shadow casters.
bool SceneLighting::TerrainProxy::sgMarkStaticShadow(void *terrainproxy,
SceneObject *sceneobject, LightInfo *light)
{
SceneLighting::TerrainProxy *terrain = (SceneLighting::TerrainProxy *)terrainproxy;
if((!terrain) || (!sceneobject))
return false;
// create shadow volume of the bounding box of this object
if(light->mType != LightInfo::Vector)
return(false);
Vector<ShadowVolumeBSP::SVPoly*> shadowvolumesurfaces;
Vector<PlaneF> shadowplanes;
const Box3F & objBox = sceneobject->getObjBox();
const MatrixF & objTransform = sceneobject->getTransform();
const VectorF & objScale = sceneobject->getScale();
// grab the surfaces which form the shadow volume
U32 numPlanes = 0;
PlaneF testPlanes[3];
U32 planeIndices[3];
// grab the bounding planes which face the light
U32 i;
for(i = 0; (i < 6) && (numPlanes < 3); i++)
{
PlaneF plane;
plane.x = BoxNormals[i].x;
plane.y = BoxNormals[i].y;
plane.z = BoxNormals[i].z;
if(i&1)
plane.d = (((const float*)objBox.min)[(i-1)>>1]);
else
plane.d = -(((const float*)objBox.max)[i>>1]);
// project
mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]);
planeIndices[numPlanes] = i;
if(mDot(testPlanes[numPlanes], light->mDirection) < gParellelVectorThresh)
numPlanes++;
}
AssertFatal(numPlanes, "SceneLighting::InteriorProxy::preLight: no planes found");
// project the points
Point3F projPnts[8];
for(i = 0; i < 8; i++)
{
Point3F pnt;
pnt.set(BoxPnts[i].x ? objBox.max.x : objBox.min.x,
BoxPnts[i].y ? objBox.max.y : objBox.min.y,
BoxPnts[i].z ? objBox.max.z : objBox.min.z);
// scale it
pnt.convolve(objScale);
objTransform.mulP(pnt, &projPnts[i]);
}
ShadowVolumeBSP *mBoxShadowBSP = new ShadowVolumeBSP;
// insert the shadow volumes for the surfaces
for(i = 0; i < numPlanes; i++)
{
ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->createPoly();
poly->mWindingCount = 4;
U32 j;
for(j = 0; j < 4; j++)
poly->mWinding[j] = projPnts[BoxVerts[planeIndices[i]][j]];
testPlanes[i].neg();
poly->mPlane = testPlanes[i];
mBoxShadowBSP->buildPolyVolume(poly, light);
shadowvolumesurfaces.push_back(mBoxShadowBSP->copyPoly(poly));
mBoxShadowBSP->insertPoly(poly);
// create the opposite planes for testing against terrain
Point3F pnts[3];
for(j = 0; j < 3; j++)
pnts[j] = projPnts[BoxVerts[planeIndices[i]^1][j]];
}
// grab the unique planes for terrain checks
for(i = 0; i < numPlanes; i++)
{
U32 mask = 0;
for(U32 j = 0; j < numPlanes; j++)
mask |= BoxSharedEdgeMask[planeIndices[i]][planeIndices[j]];
ShadowVolumeBSP::SVNode * traverse = mBoxShadowBSP->getShadowVolume(shadowvolumesurfaces[i]->mShadowVolume);
while(traverse->mFront)
{
if(!(mask & 1))
shadowplanes.push_back(mBoxShadowBSP->getPlane(traverse->mPlaneIndex));
mask >>= 1;
traverse = traverse->mFront;
}
}
// there will be 2 duplicate node planes if there were only 2 planes lit
if(numPlanes == 2)
{
for(S32 i = 0; i < shadowvolumesurfaces.size(); i++)
for(U32 j = 0; j < shadowvolumesurfaces.size(); j++)
{
if(i == j)
continue;
if((mDot(shadowplanes[i], shadowplanes[j]) > gPlaneNormThresh) &&
(mFabs(shadowplanes[i].d - shadowplanes[j].d) < gPlaneDistThresh))
{
shadowplanes.erase(i);
i--;
break;
}
}
}
// setup the clip planes: add the test and the lit planes
Vector<PlaneF> clipPlanes;
clipPlanes = shadowplanes;
for(i = 0; i < shadowvolumesurfaces.size(); i++)
clipPlanes.push_back(shadowvolumesurfaces[i]->mPlane);
Vector<U16> shadowList;
if(!terrain->getShadowedSquares(clipPlanes, shadowList))
{
delete mBoxShadowBSP;
return(false);
}
// set the correct bit
for(i = 0; i < shadowList.size(); i++)
terrain->mShadowMask.set(shadowList[i]);
delete mBoxShadowBSP;
return(true);
}