tge/engine/sceneGraph/sceneLighting.cc
2025-02-17 23:17:30 -06:00

3014 lines
95 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "sceneGraph/sceneLighting.h"
#include "interior/interiorInstance.h"
#include "interior/interiorRes.h"
#include "interior/interior.h"
#include "dgl/gBitmap.h"
#include "math/mPlane.h"
#include "sceneGraph/sceneGraph.h"
#include "core/fileStream.h"
#include "console/consoleTypes.h"
#include "gui/core/guiCanvas.h"
#include "core/zipSubStream.h"
#include "game/gameConnection.h"
namespace {
static void findObjectsCallback(SceneObject* obj, void *val)
{
Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
list->push_back(obj);
}
// color: comes in clamped
// return U16: BGR1
static U16 convertColor(ColorF color)
{
#if defined(TORQUE_OS_MAC)
return((U32(color.red * 31.f + 0.5f) << 11) |
(U32(color.green * 31.f + 0.5f) << 6) |
(U32(color.blue * 31.f + 0.5f) << 1) | 1);
#else
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);
#endif
}
static const Point3F BoxNormals[] =
{
Point3F( 1, 0, 0),
Point3F(-1, 0, 0),
Point3F( 0, 1, 0),
Point3F( 0,-1, 0),
Point3F( 0, 0, 1),
Point3F( 0, 0,-1)
};
static U32 BoxVerts[][4] = {
{7,6,4,5}, // +x
{0,2,3,1}, // -x
{7,3,2,6}, // +y
{0,1,5,4}, // -y
{7,5,1,3}, // +z
{0,4,6,2} // -z
};
static U32 BoxSharedEdgeMask[][6] = {
{0, 0, 1, 4, 8, 2},
{0, 0, 2, 8, 4, 1},
{8, 2, 0, 0, 1, 4},
{4, 1, 0, 0, 2, 8},
{1, 4, 8, 2, 0, 0},
{2, 8, 4, 1, 0, 0}
};
static U32 TerrainSquareIndices[][3] = {
{2, 1, 0}, // 45
{3, 2, 0},
{3, 1, 0}, // 135
{3, 2, 1}
};
static Point3F BoxPnts[] = {
Point3F(0,0,0),
Point3F(0,0,1),
Point3F(0,1,0),
Point3F(0,1,1),
Point3F(1,0,0),
Point3F(1,0,1),
Point3F(1,1,0),
Point3F(1,1,1)
};
SceneLighting * gLighting = 0;
F32 gPlaneNormThresh = 0.999;
F32 gPlaneDistThresh = 0.001;
F32 gParellelVectorThresh = 0.01;
bool gTerminateLighting = false;
F32 gLightingProgress = 0.f;
const char * gCompleteCallback = 0;
U32 gConnectionMissionCRC = 0xffffffff;
}
//------------------------------------------------------------------------------
class SceneLightingProcessEvent : public SimEvent
{
private:
U32 mLightIndex;
S32 mObjectIndex;
public:
SceneLightingProcessEvent(U32 lightIndex, S32 objectIndex)
{
mLightIndex = lightIndex; // size(): end of lighting
mObjectIndex = objectIndex; // -1: preLight, size(): next light
}
void process(SimObject * object)
{
AssertFatal(object, "SceneLightingProcessEvent:: null event object!");
if(object)
static_cast<SceneLighting*>(object)->processEvent(mLightIndex, mObjectIndex);
};
};
//------------------------------------------------------------------------------
bool SceneLighting::smUseVertexLighting = false;
SceneLighting::SceneLighting()
{
mStartTime = 0;
mFileName[0] = 0;
smUseVertexLighting = Interior::smUseVertexLighting;
static bool initialized = false;
if(!initialized)
{
Con::addVariable("SceneLighting::terminateLighting", TypeBool, &gTerminateLighting);
Con::addVariable("SceneLighting::lightingProgress", TypeF32, &gLightingProgress);
initialized = true;
}
}
SceneLighting::~SceneLighting()
{
gLighting = 0;
gLightingProgress = 0.f;
ObjectProxy ** proxyItr;
for(proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++)
delete *proxyItr;
}
void SceneLighting::completed(bool success)
{
// process the cached lighting files
processCache();
if(success)
{
AssertFatal(smUseVertexLighting == Interior::smUseVertexLighting, "SceneLighting::completed: vertex lighting state changed during scene light");
// cannot do anything if vertex state has changed (since we only load in what is needed)
if(smUseVertexLighting == Interior::smUseVertexLighting)
{
if(!smUseVertexLighting)
{
gInteriorLMManager.downloadGLTextures();
gInteriorLMManager.destroyBitmaps();
}
else
gInteriorLMManager.destroyTextures();
}
}
if(gCompleteCallback && gCompleteCallback[0])
Con::executef(1, gCompleteCallback);
}
//------------------------------------------------------------------------------
// Static access method: there can be only one SceneLighting object
bool SceneLighting::lightScene(const char * callback, BitSet32 flags)
{
if(gLighting)
{
Con::errorf(ConsoleLogEntry::General, "SceneLighting:: forcing restart of lighting!");
gLighting->deleteObject();
gLighting = 0;
}
SceneLighting * lighting = new SceneLighting;
// register the object
if(!lighting->registerObject())
{
AssertFatal(0, "SceneLighting:: Unable to register SceneLighting object!");
Con::errorf(ConsoleLogEntry::General, "SceneLighting:: Unable to register SceneLighting object!");
delete lighting;
return(false);
}
// could have interior resources but no instances (hey, got this far didnt we...)
GameConnection * con = dynamic_cast<GameConnection*>(NetConnection::getConnectionToServer());
if(!con)
{
Con::errorf(ConsoleLogEntry::General, "SceneLighting:: no GameConnection");
return(false);
}
con->addObject(lighting);
// set the globals
gLighting = lighting;
gTerminateLighting = false;
gLightingProgress = 0.f;
gCompleteCallback = callback;
gConnectionMissionCRC = con->getMissionCRC();
// dont want to delete the current textures because bitmaps may already be loaded
gInteriorLMManager.purgeGLTextures();
if(!lighting->light(flags))
{
lighting->completed(true);
lighting->deleteObject();
return(false);
}
return(true);
}
ConsoleFunction(lightScene, bool, 1, 3, "(script_function completeCallback=NULL, string mode=\"\")"
"Relight the scene.\n\n"
"If mode is \"forceAlways\", the lightmaps will be regenerated regardless of whether "
"lighting cache files can be written to. If mode is \"forceWritable\", then the lightmaps "
"will be regenerated only if the lighting cache files can be written.")
{
const char * callback = StringTable->insert(argv[1]);
BitSet32 flags = 0;
if(argc>1)
{
if(!dStricmp(argv[2], "forceAlways"))
flags.set(SceneLighting::ForceAlways);
else if(!dStricmp(argv[2], "forceWritable"))
flags.set(SceneLighting::ForceWritable);
}
return(SceneLighting::lightScene(callback, flags));
}
bool SceneLighting::isLighting()
{
return(bool(gLighting));
}
//------------------------------------------------------------------------------
// Class SceneLighting::PersistInfo
//------------------------------------------------------------------------------
U32 SceneLighting::PersistInfo::smFileVersion = 0x10;
SceneLighting::PersistInfo::~PersistInfo()
{
for(U32 i = 0; i < mChunks.size(); i++)
delete mChunks[i];
}
//------------------------------------------------------------------------------
bool SceneLighting::PersistInfo::read(Stream & stream)
{
U32 version;
if(!stream.read(&version) || version != smFileVersion)
return(false);
U32 numChunks;
if(!stream.read(&numChunks))
return(false);
if(numChunks == 0)
return(false);
// read in all the chunks
for(U32 i = 0; i < numChunks; i++)
{
U32 chunkType;
if(!stream.read(&chunkType))
return(false);
// MissionChunk must be first chunk
if(i == 0 && chunkType != PersistChunk::MissionChunkType)
return(false);
if(i != 0 && chunkType == PersistChunk::MissionChunkType)
return(false);
// create the chunk
switch(chunkType)
{
case PersistChunk::MissionChunkType:
mChunks.push_back(new SceneLighting::PersistInfo::MissionChunk);
break;
case PersistChunk::InteriorChunkType:
mChunks.push_back(new SceneLighting::PersistInfo::InteriorChunk);
break;
case PersistChunk::TerrainChunkType:
mChunks.push_back(new SceneLighting::PersistInfo::TerrainChunk);
break;
default:
return(false);
break;
}
// load the chunk info
if(!mChunks[i]->read(stream))
return(false);
}
return(true);
}
bool SceneLighting::PersistInfo::write(Stream & stream)
{
if(!stream.write(smFileVersion))
return(false);
if(!stream.write((U32)mChunks.size()))
return(false);
for(U32 i = 0; i < mChunks.size(); i++)
{
if(!stream.write(mChunks[i]->mChunkType))
return(false);
if(!mChunks[i]->write(stream))
return(false);
}
return(true);
}
//------------------------------------------------------------------------------
// Class SceneLighting::PersistInfo::PersistChunk
//------------------------------------------------------------------------------
bool SceneLighting::PersistInfo::PersistChunk::read(Stream & stream)
{
if(!stream.read(&mChunkCRC))
return(false);
return(true);
}
bool SceneLighting::PersistInfo::PersistChunk::write(Stream & stream)
{
if(!stream.write(mChunkCRC))
return(false);
return(true);
}
//------------------------------------------------------------------------------
// Class SceneLighting::PersistInfo::MissionChunk
//------------------------------------------------------------------------------
SceneLighting::PersistInfo::MissionChunk::MissionChunk()
{
mChunkType = PersistChunk::MissionChunkType;
}
//------------------------------------------------------------------------------
// Class SceneLighting::PersistInfo::InteriorChunk
//------------------------------------------------------------------------------
SceneLighting::PersistInfo::InteriorChunk::InteriorChunk()
{
mChunkType = PersistChunk::InteriorChunkType;
}
SceneLighting::PersistInfo::InteriorChunk::~InteriorChunk()
{
for(U32 i = 0; i < mLightmaps.size(); i++)
delete mLightmaps[i];
}
//------------------------------------------------------------------------------
// - always read in vertex lighting, lightmaps may not be needed
bool SceneLighting::PersistInfo::InteriorChunk::read(Stream & stream)
{
if(!Parent::read(stream))
return(false);
U32 size;
U32 i;
// lightmaps->vertex-info
if(!SceneLighting::smUseVertexLighting)
{
// size of this minichunk
if(!stream.read(&size))
return(false);
// lightmaps
stream.read(&size);
mDetailLightmapCount.setSize(size);
for(i = 0; i < size; i++)
if(!stream.read(&mDetailLightmapCount[i]))
return(false);
stream.read(&size);
mDetailLightmapIndices.setSize(size);
for(i = 0; i < size; i++)
if(!stream.read(&mDetailLightmapIndices[i]))
return(false);
if(!stream.read(&size))
return(false);
mLightmaps.setSize(size);
for(i = 0; i < size; i++)
{
mLightmaps[i] = new GBitmap;
if(!mLightmaps[i]->readPNG(stream))
return(false);
}
}
else
{
// step past the lightmaps
if(!stream.read(&size))
return(false);
if(!stream.setPosition(stream.getPosition() + size))
return(false);
}
// size of the vertex lighting: need to reset stream position after zipStream reading
U32 zipStreamEnd;
if(!stream.read(&zipStreamEnd))
return(false);
zipStreamEnd += stream.getPosition();
// vertex lighting
ZipSubRStream zipStream;
if(!zipStream.attachStream(&stream))
return(false);
if(!zipStream.read(&size))
return(false);
mHasAlarmState = bool(size);
if(!zipStream.read(&size))
return(false);
mDetailVertexCount.setSize(size);
for(i = 0; i < size; i++)
if(!zipStream.read(&mDetailVertexCount[i]))
return(false);
size = 0;
for(i = 0; i < mDetailVertexCount.size(); i++)
size += mDetailVertexCount[i];
mVertexColorsNormal.setSize(size);
if(mHasAlarmState)
mVertexColorsAlarm.setSize(size);
U32 curPos = 0;
for(i = 0; i < mDetailVertexCount.size(); i++)
{
U32 count = mDetailVertexCount[i];
for(U32 j = 0; j < count; j++)
if(!zipStream.read(&mVertexColorsNormal[curPos + j]))
return(false);
// read in the alarm info
if(mHasAlarmState)
{
// same?
if(!zipStream.read(&size))
return(false);
if(bool(size))
dMemcpy(&mVertexColorsAlarm[curPos], &mVertexColorsNormal[curPos], count * sizeof(ColorI));
else
{
for(U32 j = 0; j < count; j++)
if(!zipStream.read(&mVertexColorsAlarm[curPos + j]))
return(false);
}
}
curPos += count;
}
zipStream.detachStream();
// since there is no resizeFilterStream the zipStream happily reads
// off the end of the compressed block... reset the position
stream.setPosition(zipStreamEnd);
return(true);
}
bool SceneLighting::PersistInfo::InteriorChunk::write(Stream & stream)
{
if(!Parent::write(stream))
return(false);
// lightmaps
U32 startPos = stream.getPosition();
if(!stream.write(U32(0)))
return(false);
U32 i;
if(!stream.write(U32(mDetailLightmapCount.size())))
return(false);
for(i = 0; i < mDetailLightmapCount.size(); i++)
if(!stream.write(mDetailLightmapCount[i]))
return(false);
if(!stream.write(U32(mDetailLightmapIndices.size())))
return(false);
for(i = 0; i < mDetailLightmapIndices.size(); i++)
if(!stream.write(mDetailLightmapIndices[i]))
return(false);
if(!stream.write(U32(mLightmaps.size())))
return(false);
for(i = 0; i < mLightmaps.size(); i++)
{
AssertFatal(mLightmaps[i], "SceneLighting::SceneLighting::PersistInfo::InteriorChunk::Write: Invalid bitmap!");
if(!mLightmaps[i]->writePNG(stream))
return(false);
}
// write out the lightmap portions size
U32 endPos = stream.getPosition();
if(!stream.setPosition(startPos))
return(false);
// don't include the offset in the size
if(!stream.write(U32(endPos - startPos - sizeof(U32))))
return(false);
if(!stream.setPosition(endPos))
return(false);
// vertex lighting: needs the size of the vertex info because the
// zip stream may read off the end of the chunk
startPos = stream.getPosition();
if(!stream.write(U32(0)))
return(false);
ZipSubWStream zipStream;
if(!zipStream.attachStream(&stream))
return(false);
if(!zipStream.write(U32(mHasAlarmState)))
return(false);
if(!zipStream.write(U32(mDetailVertexCount.size())))
return(false);
for(U32 i = 0; i < mDetailVertexCount.size(); i++)
if(!zipStream.write(mDetailVertexCount[i]))
return(false);
U32 curPos = 0;
for(i = 0; i < mDetailVertexCount.size(); i++)
{
U32 count = mDetailVertexCount[i];
for(U32 j = 0; j < count; j++)
if(!zipStream.write(mVertexColorsNormal[curPos + j]))
return(false);
// do alarm.. check if the same
if(mHasAlarmState)
{
if(!dMemcmp(&mVertexColorsNormal[curPos], &mVertexColorsAlarm[curPos], count * sizeof(ColorI)))
{
if(!zipStream.write(U32(1)))
return(false);
}
else
{
if(!zipStream.write(U32(0)))
return(false);
for(U32 j = 0; j < count; j++)
if(!zipStream.write(mVertexColorsAlarm[curPos + j]))
return(false);
}
}
curPos += count;
}
zipStream.detachStream();
// write out the vertex lighting portions size
endPos = stream.getPosition();
if(!stream.setPosition(startPos))
return(false);
// don't include the offset in the size
if(!stream.write(U32(endPos - startPos - sizeof(U32))))
return(false);
if(!stream.setPosition(endPos))
return(false);
return(true);
}
//------------------------------------------------------------------------------
// Class SceneLighting::PersistInfo::TerrainChunk
//------------------------------------------------------------------------------
SceneLighting::PersistInfo::TerrainChunk::TerrainChunk()
{
mChunkType = PersistChunk::TerrainChunkType;
mLightmap = 0;
}
SceneLighting::PersistInfo::TerrainChunk::~TerrainChunk()
{
delete [] mLightmap;
}
//------------------------------------------------------------------------------
bool SceneLighting::PersistInfo::TerrainChunk::read(Stream & stream)
{
if(!Parent::read(stream))
return(false);
GBitmap bitmap;
if(!bitmap.readPNG(stream))
return(false);
if((bitmap.getWidth() != TerrainBlock::LightmapSize) ||
(bitmap.getHeight() != TerrainBlock::LightmapSize) ||
(bitmap.getFormat() != GBitmap::RGB))
return(false);
mLightmap = new U16[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize];
dMemset(mLightmap, 0, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize);
// copy the bitmap: bits should already be clamped
U8 * bp = bitmap.getAddress(0,0);
for(U32 i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++)
{
mLightmap[i] = (U16(bp[0]) << 11) | (U16(bp[1]) << 6) | (U16(bp[2]) << 1) | 1;
bp += 3;
}
return(true);
}
bool SceneLighting::PersistInfo::TerrainChunk::write(Stream & stream)
{
if(!Parent::write(stream))
return(false);
if(!mLightmap)
return(false);
// write out an RGB bitmap so it can be PNG compressed
GBitmap bitmap(TerrainBlock::LightmapSize, TerrainBlock::LightmapSize, false, GBitmap::RGB);
U8 * bp = bitmap.getAddress(0,0);
for(U32 i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++)
{
bp[0] = mLightmap[i] >> 11;
bp[1] = (mLightmap[i] >> 6) & 0x1f;
bp[2] = (mLightmap[i] >> 1) & 0x1f;
bp += 3;
}
if(!bitmap.writePNG(stream))
return(false);
return(true);
}
//------------------------------------------------------------------------------
bool SceneLighting::verifyMissionInfo(SceneLighting::PersistInfo::PersistChunk * chunk)
{
SceneLighting::PersistInfo::MissionChunk * info = dynamic_cast<SceneLighting::PersistInfo::MissionChunk*>(chunk);
if(!info)
return(false);
SceneLighting::PersistInfo::MissionChunk curInfo;
if(!getMissionInfo(&curInfo))
return(false);
return(curInfo.mChunkCRC == info->mChunkCRC);
}
bool SceneLighting::getMissionInfo(SceneLighting::PersistInfo::PersistChunk * chunk)
{
SceneLighting::PersistInfo::MissionChunk * info = dynamic_cast<SceneLighting::PersistInfo::MissionChunk*>(chunk);
if(!info)
return(false);
info->mChunkCRC = gConnectionMissionCRC ^ PersistInfo::smFileVersion;
return(true);
}
//------------------------------------------------------------------------------
bool SceneLighting::loadPersistInfo(const char * fileName)
{
// open the file
Stream * stream = 0;
stream = ResourceManager->openStream(fileName);
if(!stream)
return(false);
PersistInfo persistInfo;
bool success = persistInfo.read(*stream);
delete stream;
if(!success)
return(false);
// verify the mission chunk
if(!verifyMissionInfo(persistInfo.mChunks[0]))
return(false);
if(mSceneObjects.size() != (persistInfo.mChunks.size() - 1))
return(false);
Vector<SceneLighting::PersistInfo::PersistChunk*> chunks;
// ensure that the scene objects are in the same order as the chunks
// - different instances will depend on this
U32 i;
for(i = 0; i < mSceneObjects.size(); i++)
{
// 0th chunk is the mission chunk
U32 chunkIdx = i+1;
if(chunkIdx >= persistInfo.mChunks.size())
return(false);
if(!mSceneObjects[i]->isValidChunk(persistInfo.mChunks[chunkIdx]))
return(false);
chunks.push_back(persistInfo.mChunks[chunkIdx]);
}
// get the objects to load in the persisted chunks
for(i = 0; i < mSceneObjects.size(); i++)
if(!mSceneObjects[i]->setPersistInfo(chunks[i]))
return(false);
return(true);
}
bool SceneLighting::savePersistInfo(const char * fileName)
{
// open the file
FileStream file;
if(!ResourceManager->openFileForWrite(file, fileName))
return(false);
PersistInfo persistInfo;
// add in the mission chunk
persistInfo.mChunks.push_back(new SceneLighting::PersistInfo::MissionChunk);
// get the mission info, will return false when there are 0 lights
if(!getMissionInfo(persistInfo.mChunks[0]))
return(false);
// get all the persist chunks
for(U32 i = 0; i < mSceneObjects.size(); i++)
{
if(isInterior(mSceneObjects[i]->mObj))
persistInfo.mChunks.push_back(new SceneLighting::PersistInfo::InteriorChunk);
else if(isTerrain(mSceneObjects[i]->mObj))
persistInfo.mChunks.push_back(new SceneLighting::PersistInfo::TerrainChunk);
else
return(false);
if(!mSceneObjects[i]->getPersistInfo(persistInfo.mChunks.last()))
return(false);
}
if(!persistInfo.write(file))
return(false);
file.close();
// open/close the stream to get the fileSize calculated on the resource object
ResourceManager->closeStream(ResourceManager->openStream(fileName));
return(true);
}
//------------------------------------------------------------------------------
// SceneLighting
//------------------------------------------------------------------------------
void SceneLighting::addInterior(ShadowVolumeBSP * shadowVolume, InteriorProxy & interior, LightInfo * light, S32 level)
{
if(light->mType != LightInfo::Vector)
return;
bool shadowedTree = true;
// check if just getting shadow detail
if(level == SHADOW_DETAIL)
{
shadowedTree = false;
level = interior->mInteriorRes->getNumDetailLevels() - 1;
}
Interior * detail = interior->mInteriorRes->getDetailLevel(level);
bool hasAlarm = detail->hasAlarmState();
// make sure surfaces do not get processed more than once
BitVector surfaceProcessed;
surfaceProcessed.setSize(detail->mSurfaces.size());
surfaceProcessed.clear();
// go through the zones of the interior and grab outside visible surfaces
for(U32 i = 0; i < detail->getNumZones(); i++)
{
Interior::Zone & zone = detail->mZones[i];
for(U32 j = 0; j < zone.surfaceCount; j++)
{
U32 surfaceIndex = detail->mZoneSurfaces[zone.surfaceStart + j];
// dont reprocess a surface
if(surfaceProcessed.test(surfaceIndex))
continue;
surfaceProcessed.set(surfaceIndex);
Interior::Surface & surface = detail->mSurfaces[surfaceIndex];
// outside visible?
if(!(surface.surfaceFlags & Interior::SurfaceOutsideVisible))
continue;
// good surface?
PlaneF plane = detail->getPlane(surface.planeIndex);
if(Interior::planeIsFlipped(surface.planeIndex))
plane.neg();
// project the plane
PlaneF projPlane;
mTransformPlane(interior->getTransform(), interior->getScale(), plane, &projPlane);
// fill with ambient? (need to do here, because surface will not be
// added to the SVBSP tree)
F32 dot = mDot(projPlane, light->mDirection);
if(dot > -gParellelVectorThresh)
{
if(shadowedTree)
{
// alarm lighting
TextureHandle * normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex));
TextureHandle * alarmHandle = 0;
GBitmap * normLightmap = normHandle->getBitmap();
GBitmap * alarmLightmap = 0;
// check if they share the lightmap
if(hasAlarm)
{
if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex))
{
alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex));
alarmLightmap = alarmHandle->getBitmap();
}
}
//
// Support for interior light map border sizes.
//
U32 xlen, ylen, xoff, yoff;
U32 lmborder = detail->getLightMapBorderSize();
xlen = surface.mapSizeX + (lmborder * 2);
ylen = surface.mapSizeY + (lmborder * 2);
xoff = surface.mapOffsetX - lmborder;
yoff = surface.mapOffsetY - lmborder;
// attempt to light normal and alarm lighting
for(U32 c = 0; c < 2; c++)
{
GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap;
if(!lightmap)
continue;
// fill it
for(U32 y = 0; y < ylen; y++)
{
ColorI color = light->mAmbient;
U8 * pBits = lightmap->getAddress(xoff, yoff + y);
for(U32 x = 0; x < xlen; x++)
{
#ifdef SET_COLORS
*pBits++ = color.red;
*pBits++ = color.green;
*pBits++ = color.blue;
#else
// the previous *pBit++ = ... code is broken.
U32 _r = static_cast<U32>( color.red ) + static_cast<U32>( *pBits );
*pBits = ( _r <= 255 ) ? _r : 255;
pBits++;
U32 _g = static_cast<U32>( color.green ) + static_cast<U32>( *pBits );
*pBits = ( _g <= 255 ) ? _g : 255;
pBits++;
U32 _b = static_cast<U32>( color.blue ) + static_cast<U32>( *pBits );
*pBits = ( _b <= 255 ) ? _b : 255;
pBits++;
#endif
}
}
}
}
continue;
}
ShadowVolumeBSP::SVPoly * poly = buildInteriorPoly(shadowVolume, interior, detail,
surfaceIndex, light, shadowedTree);
// insert it into the SVBSP tree
shadowVolume->insertPoly(poly);
}
}
}
//------------------------------------------------------------------------------
ShadowVolumeBSP::SVPoly * SceneLighting::buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP,
InteriorProxy & interior, Interior * detail, U32 surfaceIndex, LightInfo * light,
bool createSurfaceInfo)
{
// transform and add the points...
const MatrixF & transform = interior->getTransform();
const VectorF & scale = interior->getScale();
const Interior::Surface & surface = detail->mSurfaces[surfaceIndex];
ShadowVolumeBSP::SVPoly * poly = shadowVolumeBSP->createPoly();
poly->mWindingCount = surface.windingCount;
// project these points
for(U32 j = 0; j < poly->mWindingCount; j++)
{
Point3F iPnt = detail->mPoints[detail->mWindings[surface.windingStart + j]].point;
Point3F tPnt;
iPnt.convolve(scale);
transform.mulP(iPnt, &tPnt);
poly->mWinding[j] = tPnt;
}
// convert from fan
U32 tmpIndices[ShadowVolumeBSP::SVPoly::MaxWinding];
Point3F fanIndices[ShadowVolumeBSP::SVPoly::MaxWinding];
tmpIndices[0] = 0;
U32 idx = 1;
U32 i;
for(i = 1; i < poly->mWindingCount; i += 2)
tmpIndices[idx++] = i;
for(i = ((poly->mWindingCount - 1) & (~0x1)); i > 0; i -= 2)
tmpIndices[idx++] = i;
idx = 0;
for(i = 0; i < poly->mWindingCount; i++)
if(surface.fanMask & (1 << i))
fanIndices[idx++] = poly->mWinding[tmpIndices[i]];
// set the data
poly->mWindingCount = idx;
for(i = 0; i < poly->mWindingCount; i++)
poly->mWinding[i] = fanIndices[i];
// flip the plane - shadow volumes face inwards
PlaneF plane = detail->getPlane(surface.planeIndex);
if(!Interior::planeIsFlipped(surface.planeIndex))
plane.neg();
// transform the plane
mTransformPlane(transform, scale, plane, &poly->mPlane);
shadowVolumeBSP->buildPolyVolume(poly, light);
// do surface info?
if(createSurfaceInfo)
{
ShadowVolumeBSP::SurfaceInfo * surfaceInfo = new ShadowVolumeBSP::SurfaceInfo;
shadowVolumeBSP->mSurfaces.push_back(surfaceInfo);
// fill it
surfaceInfo->mSurfaceIndex = surfaceIndex;
surfaceInfo->mShadowVolume = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume);
// POLY and POLY node gets it too
ShadowVolumeBSP::SVNode * traverse = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume);
while(traverse->mFront)
{
traverse->mSurfaceInfo = surfaceInfo;
traverse = traverse->mFront;
}
// get some info from the poly node
poly->mSurfaceInfo = traverse->mSurfaceInfo = surfaceInfo;
surfaceInfo->mPlaneIndex = traverse->mPlaneIndex;
}
return(poly);
}
//--------------------------------------------------------------------------
static S32 QSORT_CALLBACK compareS32(const void * a, const void * b)
{
return(*((S32 *)a) - *((S32 *)b));
}
U32 SceneLighting::calcMissionCRC()
{
// all the objects + mission chunk
Vector<U32> crc;
// grab the object crcs
for(U32 i = 0; i < mSceneObjects.size(); i++)
crc.push_back(mSceneObjects[i]->mChunkCRC);
// grab the missions crc
SceneLighting::PersistInfo::MissionChunk curInfo;
getMissionInfo(&curInfo);
crc.push_back(curInfo.mChunkCRC);
// sort them (order may not have been preserved)
dQsort(crc.address(), crc.size(), sizeof(U32), compareS32);
return(calculateCRC(crc.address(), sizeof(U32) * crc.size(), 0xffffffff));
}
bool SceneLighting::light(BitSet32 flags)
{
if(!gClientSceneGraph)
return(false);
mStartTime = Platform::getRealMilliseconds();
// register static lights
LightManager * lManager = gClientSceneGraph->getLightManager();
lManager->registerLights(true);
// grab all the lights
mLights.clear();
lManager->getLights(mLights);
if(!mLights.size())
return(false);
// get all the objects and create proxy's for them
Vector<SceneObject *> objects;
gClientContainer.findObjects(InteriorObjectType | TerrainObjectType, findObjectsCallback, &objects);
for(SceneObject ** itr = objects.begin(); itr != objects.end(); itr++)
{
ObjectProxy * proxy;
if(isInterior(*itr))
{
// Reset the dynamic lighting flag where appropriate.
InteriorInstance *ii = dynamic_cast<InteriorInstance*>(*itr);
ii->mDoSimpleDynamicRender = ii->mUseGLLighting;
proxy = new InteriorProxy(*itr);
}
else if(isTerrain(*itr))
proxy = new TerrainProxy(*itr);
else
{
AssertFatal(0, "SceneLighting:: invalid object returned from container search");
continue;
}
if(!proxy->calcValidation())
{
Con::errorf(ConsoleLogEntry::General, "Failed to calculate validation info for object. Skipped.");
delete proxy;
continue;
}
if(!proxy->loadResources())
{
Con::errorf(ConsoleLogEntry::General, "Failed to load resources for object. Skipped.");
delete proxy;
continue;
}
mSceneObjects.push_back(proxy);
}
if(!mSceneObjects.size())
return(false);
// grab the missions crc
U32 missionCRC = calcMissionCRC();
// remove the '.mis' extension from the mission name
char misName[256];
dSprintf(misName, sizeof(misName), "%s", Con::getVariable("$Client::MissionFile"));
char * dot = dStrstr((const char*)misName, ".mis");
if(dot)
*dot = '\0';
// get the mission name
dSprintf(mFileName, sizeof(mFileName), "%s_%x.ml", misName, missionCRC);
if(!ResourceManager->isValidWriteFileName(mFileName))
{
Con::warnf("Invalid filename '%s'. Failed to light mission.", mFileName);
return(false);
}
// check for some persisted data, check if being forced..
if(!flags.test(ForceAlways|ForceWritable))
{
if(loadPersistInfo(mFileName))
{
Con::printf(" Successfully loaded mission lighting file: '%s'", mFileName);
// touch this file...
if(!dFileTouch(mFileName))
Con::warnf(" Failed to touch file '%s'. File may be read only.", mFileName);
return(false);
}
// texture manager must have lighting complete now
if(flags.test(LoadOnly))
{
Con::errorf(ConsoleLogEntry::General, "Failed to load mission lighting!");
return(false);
}
}
// don't light if file is read-only?
if(!flags.test(ForceAlways))
{
FileStream fileStream;
if(!ResourceManager->openFileForWrite(fileStream, mFileName))
{
Con::errorf(ConsoleLogEntry::General, "SceneLighting::Light: Failed to light mission. File '%s' cannot be written to.", mFileName);
return(false);
}
}
// initialize the objects for lighting
for(ObjectProxy ** proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++)
(*proxyItr)->init();
// get things started
Sim::postEvent(this, new SceneLightingProcessEvent(0, -1), Sim::getTargetTime() + 1);
return(true);
}
//------------------------------------------------------------------------------
void SceneLighting::processEvent(U32 light, S32 object)
{
// cancel lighting?
if(gTerminateLighting)
{
completed(false);
deleteObject();
return;
}
ObjectProxy ** proxyItr;
// last object?
if(object == mLitObjects.size())
{
for(proxyItr = mLitObjects.begin(); proxyItr != mLitObjects.end(); proxyItr++)
{
if(!(*proxyItr)->getObject())
{
AssertFatal(0, "SceneLighting:: missing sceneobject on light end");
continue;
}
(*proxyItr)->postLight(light == (mLights.size() - 1));
}
mLitObjects.clear();
Canvas->paint();
Sim::postEvent(this, new SceneLightingProcessEvent(light + 1, -1), Sim::getTargetTime() + 1);
}
else
{
// done lighting?
if(light == mLights.size())
{
Con::printf(" Scene lit in %3.3f seconds", (Platform::getRealMilliseconds()-mStartTime)/1000.f);
// save out the lighting?
if(Con::getBoolVariable("$pref::sceneLighting::cacheLighting", true))
{
if(!savePersistInfo(mFileName))
Con::errorf(ConsoleLogEntry::General, "SceneLighting::light: unable to persist lighting!");
else
Con::printf(" Successfully saved mission lighting file: '%s'", mFileName);
}
// wrap things up...
completed(true);
deleteObject();
}
else
{
// start of this light?
if(object == -1)
{
for(proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++)
{
if(!(*proxyItr)->getObject())
{
AssertFatal(0, "SceneLighting:: missing sceneobject on light start");
Con::errorf(ConsoleLogEntry::General, "SceneLighting:: missing sceneobject on light start");
continue;
}
if((*proxyItr)->preLight(mLights[light]))
mLitObjects.push_back(*proxyItr);
}
}
else
{
if(mLitObjects[object]->getObject())
{
gLightingProgress = (F32(light) / F32(mLights.size())) + ((F32(object + 1) / F32(mLitObjects.size())) / F32(mLights.size()));
mLitObjects[object]->light(mLights[light]);
}
else
{
AssertFatal(0, "SceneLighting:: missing sceneobject on light update");
Con::errorf(ConsoleLogEntry::General, "SceneLighting:: missing sceneobject on light update");
}
}
Canvas->paint();
Sim::postEvent(this, new SceneLightingProcessEvent(light, object + 1), Sim::getTargetTime() + 1);
}
}
}
//------------------------------------------------------------------------------
struct CacheEntry {
ResourceObject * mFileObject;
const char * mFileName;
CacheEntry() {
mFileObject = 0;
mFileName = 0;
};
};
// object list sort methods: want list in reverse
static int QSORT_CALLBACK minSizeSort(const void * p1, const void * p2)
{
const CacheEntry * entry1 = (const CacheEntry *)p1;
const CacheEntry * entry2 = (const CacheEntry *)p2;
return(entry2->mFileObject->fileSize - entry1->mFileObject->fileSize);
}
static int QSORT_CALLBACK maxSizeSort(const void * p1, const void * p2)
{
const CacheEntry * entry1 = (const CacheEntry *)p1;
const CacheEntry * entry2 = (const CacheEntry *)p2;
return(entry1->mFileObject->fileSize - entry2->mFileObject->fileSize);
}
static int QSORT_CALLBACK lastCreatedSort(const void * p1, const void * p2)
{
const CacheEntry * entry1 = (const CacheEntry *)p1;
const CacheEntry * entry2 = (const CacheEntry *)p2;
FileTime create[2];
FileTime modify;
bool ret[2];
ret[0] = Platform::getFileTimes(entry1->mFileName, &create[0], &modify);
ret[1] = Platform::getFileTimes(entry2->mFileName, &create[1], &modify);
// check return values
if(!ret[0] && !ret[1])
return(0);
if(!ret[0])
return(1);
if(!ret[1])
return(-1);
return(Platform::compareFileTimes(create[1], create[0]));
}
static int QSORT_CALLBACK lastModifiedSort(const void * p1, const void * p2)
{
const CacheEntry * entry1 = (const CacheEntry *)p1;
const CacheEntry * entry2 = (const CacheEntry *)p2;
FileTime create;
FileTime modify[2];
bool ret[2];
ret[0] = Platform::getFileTimes(entry1->mFileName, &create, &modify[0]);
ret[1] = Platform::getFileTimes(entry2->mFileName, &create, &modify[1]);
// check return values
if(!ret[0] && !ret[1])
return(0);
if(!ret[0])
return(1);
if(!ret[1])
return(-1);
return(Platform::compareFileTimes(modify[1], modify[0]));
}
void SceneLighting::processCache()
{
// get size in kb
S32 quota = Con::getIntVariable("$pref::sceneLighting::cacheSize", -1);
Vector<CacheEntry> files;
ResourceObject * match = 0;
const char * name;
S32 curCacheSize = 0;
match = ResourceManager->findMatch("*.ml", &name, 0);
while(match)
{
if(match->flags & ResourceObject::File)
{
// dont allow the current file to be removed...
if(!dStrstr(name, mFileName))
{
CacheEntry entry;
entry.mFileObject = match;
// get out of vfs...
char fileName[1024];
dSprintf(fileName, sizeof(fileName), "%s/%s", match->path, match->name);
entry.mFileName = StringTable->insert(fileName);
files.push_back(entry);
}
else
curCacheSize += match->fileSize;
}
match = ResourceManager->findMatch("*.ml", &name, match);
}
// remove old files
for(S32 i = files.size() - 1; i >= 0; i--)
{
char buf[1024];
dSprintf(buf, sizeof(buf), "%s/%s", files[i].mFileObject->path, files[i].mFileObject->name);
Stream * stream = ResourceManager->openStream(buf);
if(!stream)
continue;
// read in the version
U32 version;
bool ok = (stream->read(&version) && (version == SceneLighting::PersistInfo::smFileVersion));
ResourceManager->closeStream(stream);
// ok?
if(ok)
continue;
// delete the file
ResourceManager->freeResource(files[i].mFileObject);
// no sneaky names
if(!dStrstr(files[i].mFileName, ".."))
{
Con::warnf("Removing old lighting file '%s'.", files[i].mFileName);
dFileDelete(files[i].mFileName);
}
files.pop_back();
}
// no size restriction?
if(quota == -1 || !files.size())
return;
for(U32 i = 0; i < files.size(); i++)
curCacheSize += files[i].mFileObject->fileSize;
// need to remove?
if(quota > (curCacheSize >> 10))
return;
// sort the entries by the correct method
const char * purgeMethod = Con::getVariable("$pref::sceneLighting::purgeMethod");
if(!purgeMethod)
purgeMethod = "";
// determine the method (default to least recently used)
if(!dStricmp(purgeMethod, "minSize"))
dQsort(files.address(), files.size(), sizeof(CacheEntry), minSizeSort);
else if(!dStricmp(purgeMethod, "maxSize"))
dQsort(files.address(), files.size(), sizeof(CacheEntry), maxSizeSort);
else if(!dStricmp(purgeMethod, "lastCreated"))
dQsort(files.address(), files.size(), sizeof(CacheEntry), lastCreatedSort);
else
dQsort(files.address(), files.size(), sizeof(CacheEntry), lastModifiedSort);
// go through and remove the best candidate first (sorted reverse)
while(((curCacheSize >> 10) > quota) && files.size())
{
curCacheSize -= files.last().mFileObject->fileSize;
ResourceManager->freeResource(files.last().mFileObject);
// no sneaky names
if(!dStrstr(files.last().mFileName, ".."))
{
Con::warnf("Removing lighting file '%s'.", files.last().mFileName);
dFileDelete(files.last().mFileName);
}
files.pop_back();
}
}
//------------------------------------------------------------------------------
// Class SceneLighting::ObjectProxy:
//------------------------------------------------------------------------------
bool SceneLighting::ObjectProxy::calcValidation()
{
mChunkCRC = getResourceCRC();
if(!mChunkCRC)
return(false);
return(true);
}
bool SceneLighting::ObjectProxy::isValidChunk(PersistInfo::PersistChunk * chunk)
{
return(chunk->mChunkCRC == mChunkCRC);
}
bool SceneLighting::ObjectProxy::getPersistInfo(PersistInfo::PersistChunk * chunk)
{
chunk->mChunkCRC = mChunkCRC;
return(true);
}
bool SceneLighting::ObjectProxy::setPersistInfo(PersistInfo::PersistChunk * chunk)
{
mChunkCRC = chunk->mChunkCRC;
return(true);
}
//------------------------------------------------------------------------------
// Class SceneLighting::InteriorProxy:
//------------------------------------------------------------------------------
SceneLighting::InteriorProxy::InteriorProxy(SceneObject * obj) :
Parent(obj)
{
mBoxShadowBSP = 0;
}
SceneLighting::InteriorProxy::~InteriorProxy()
{
delete mBoxShadowBSP;
}
bool SceneLighting::InteriorProxy::loadResources()
{
InteriorInstance * interior = getObject();
if(!interior)
return(false);
Resource<InteriorResource> & interiorRes = interior->getResource();
if(!bool(interiorRes))
return(false);
if(!gInteriorLMManager.loadBaseLightmaps(interiorRes->getDetailLevel(0)->getLMHandle(),
interior->getLMHandle()))
return(false);
return(true);
}
void SceneLighting::InteriorProxy::init()
{
InteriorInstance * interior = getObject();
if(!interior)
return;
// clear out the lightmaps
for(U32 i = 0; i < interior->getResource()->getNumDetailLevels(); i++)
{
Interior * detail = interior->getResource()->getDetailLevel(i);
gInteriorLMManager.clearLightmaps(detail->getLMHandle(), interior->getLMHandle());
}
}
bool SceneLighting::InteriorProxy::preLight(LightInfo * light)
{
// create shadow volume of the bounding box of this object
InteriorInstance * interior = getObject();
if(!interior)
return(false);
if(light->mType != LightInfo::Vector)
return(false);
// reset
mLitBoxSurfaces.clear();
const Box3F & objBox = interior->getObjBox();
const MatrixF & objTransform = interior->getTransform();
const VectorF & objScale = interior->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]);
}
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);
mLitBoxSurfaces.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]];
PlaneF plane(pnts[2], pnts[1], pnts[0]);
mOppositeBoxPlanes.push_back(plane);
}
// 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(mLitBoxSurfaces[i]->mShadowVolume);
while(traverse->mFront)
{
if(!(mask & 1))
mTerrainTestPlanes.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 < mTerrainTestPlanes.size(); i++)
for(U32 j = 0; j < mTerrainTestPlanes.size(); j++)
{
if(i == j)
continue;
if((mDot(mTerrainTestPlanes[i], mTerrainTestPlanes[j]) > gPlaneNormThresh) &&
(mFabs(mTerrainTestPlanes[i].d - mTerrainTestPlanes[j].d) < gPlaneDistThresh))
{
mTerrainTestPlanes.erase(i);
i--;
break;
}
}
}
return(true);
}
bool SceneLighting::InteriorProxy::isShadowedBy(InteriorProxy * test)
{
// add if overlapping world box
if((*this)->getWorldBox().isOverlapped((*test)->getWorldBox()))
return(true);
// test the box shadow volume
for(U32 i = 0; i < mLitBoxSurfaces.size(); i++)
{
ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->copyPoly(mLitBoxSurfaces[i]);
if(test->mBoxShadowBSP->testPoly(poly))
return(true);
}
return(false);
}
void SceneLighting::InteriorProxy::light(LightInfo * light)
{
InteriorInstance * interior = getObject();
if(!interior)
return;
S32 time = Platform::getRealMilliseconds();
// create own shadow volume
ShadowVolumeBSP shadowVolume;
// add the other objects lit surfaces into shadow volume
for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++)
{
if(!(*itr)->getObject())
continue;
if(gLighting->isInterior((*itr)->mObj))
{
if(*itr == this)
continue;
if(isShadowedBy(static_cast<InteriorProxy*>(*itr)))
gLighting->addInterior(&shadowVolume, *static_cast<InteriorProxy*>(*itr), light, SceneLighting::SHADOW_DETAIL);
}
// insert the terrain squares
if(gLighting->isTerrain((*itr)->mObj))
{
TerrainProxy * terrain = static_cast<TerrainProxy*>(*itr);
Vector<PlaneF> clipPlanes;
clipPlanes = mTerrainTestPlanes;
for(U32 i = 0; i < mOppositeBoxPlanes.size(); i++)
clipPlanes.push_back(mOppositeBoxPlanes[i]);
Vector<U16> shadowList;
if(terrain->getShadowedSquares(clipPlanes, shadowList))
{
TerrainBlock * block = static_cast<TerrainBlock*>((*itr)->getObject());
Point3F offset;
block->getTransform().getColumn(3, &offset);
F32 squareSize = block->getSquareSize();
for(U32 j = 0; j < shadowList.size(); j++)
{
Point2I pos(shadowList[j] & TerrainBlock::BlockMask, shadowList[j] >> TerrainBlock::BlockShift);
Point2F wPos(pos.x * squareSize + offset.x,
pos.y * squareSize + offset.y);
Point3F pnts[4];
pnts[0].set(wPos.x, wPos.y, fixedToFloat(block->getHeight(pos.x, pos.y)));
pnts[1].set(wPos.x + squareSize, wPos.y, fixedToFloat(block->getHeight(pos.x + 1, pos.y)));
pnts[2].set(wPos.x + squareSize, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x + 1, pos.y + 1)));
pnts[3].set(wPos.x, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x, pos.y + 1)));
GridSquare * gs = block->findSquare(0, pos);
U32 squareIdx = (gs->flags & GridSquare::Split45) ? 0 : 2;
for(U32 k = squareIdx; k < (squareIdx + 2); k++)
{
// face plane inwards
PlaneF plane(pnts[TerrainSquareIndices[k][2]],
pnts[TerrainSquareIndices[k][1]],
pnts[TerrainSquareIndices[k][0]]);
if(mDot(plane, light->mDirection) > gParellelVectorThresh)
{
ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly();
poly->mWindingCount = 3;
poly->mWinding[0] = pnts[TerrainSquareIndices[k][0]];
poly->mWinding[1] = pnts[TerrainSquareIndices[k][1]];
poly->mWinding[2] = pnts[TerrainSquareIndices[k][2]];
poly->mPlane = plane;
// create the shadow volume for this and insert
shadowVolume.buildPolyVolume(poly, light);
shadowVolume.insertPoly(poly);
}
}
}
}
}
}
// light all details
for(U32 i = 0; i < interior->getResource()->getNumDetailLevels(); i++)
{
// clear lightmaps
Interior * detail = interior->getResource()->getDetailLevel(i);
gInteriorLMManager.clearLightmaps(detail->getLMHandle(), interior->getLMHandle());
// clear out the last inserted interior
shadowVolume.removeLastInterior();
bool hasAlarm = detail->hasAlarmState();
gLighting->addInterior(&shadowVolume, *this, light, i);
for(U32 j = 0; j < shadowVolume.mSurfaces.size(); j++)
{
ShadowVolumeBSP::SurfaceInfo * surfaceInfo = shadowVolume.mSurfaces[j];
U32 surfaceIndex = surfaceInfo->mSurfaceIndex;
const Interior::Surface & surface = detail->getSurface(surfaceIndex);
// alarm lighting
TextureHandle * normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex));
TextureHandle * alarmHandle = 0;
GBitmap * normLightmap = normHandle->getBitmap();
GBitmap * alarmLightmap = 0;
// check if the lightmaps are shared
if(hasAlarm)
{
if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex))
{
alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex));
alarmLightmap = alarmHandle->getBitmap();
}
}
// points right way?
PlaneF plane = detail->getPlane(surface.planeIndex);
if(Interior::planeIsFlipped(surface.planeIndex))
plane.neg();
const MatrixF & transform = interior->getTransform();
const Point3F & scale = interior->getScale();
//
PlaneF projPlane;
mTransformPlane(transform, scale, plane, &projPlane);
F32 dot = mDot(projPlane, -light->mDirection);
// shadowed?
if(!surfaceInfo->mShadowed.size())
{
// calc the color and convert to U8 rep
ColorF tmp = (light->mColor * dot) + light->mAmbient;
tmp.clamp();
ColorI color = tmp;
// attempt to light both the normal and the alarm states
for(U32 c = 0; c < 2; c++)
{
GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap;
if(!lightmap)
continue;
//
// Support for interior light map border sizes.
//
U32 xlen, ylen, xoff, yoff;
U32 lmborder = detail->getLightMapBorderSize();
xlen = surface.mapSizeX + (lmborder * 2);
ylen = surface.mapSizeY + (lmborder * 2);
xoff = surface.mapOffsetX - lmborder;
yoff = surface.mapOffsetY - lmborder;
// fill it
for(U32 y = 0; y < ylen; y++)
{
U8 * pBits = lightmap->getAddress(xoff, yoff + y);
for(U32 x = 0; x < xlen; x++)
{
#ifdef SET_COLORS
*pBits++ = color.red;
*pBits++ = color.green;
*pBits++ = color.blue;
#else
U32 _r = static_cast<U32>( color.red ) + static_cast<U32>( *pBits );
*pBits = ( _r <= 255 ) ? _r : 255;
pBits++;
U32 _g = static_cast<U32>( color.green ) + static_cast<U32>( *pBits );
*pBits = ( _g <= 255 ) ? _g : 255;
pBits++;
U32 _b = static_cast<U32>( color.blue ) + static_cast<U32>( *pBits );
*pBits = ( _b <= 255 ) ? _b : 255;
pBits++;
#endif
}
}
}
continue;
}
//
// Support for interior light map border sizes.
//
U32 xlen, ylen, xoff, yoff;
U32 lmborder = detail->getLightMapBorderSize();
xlen = surface.mapSizeX + (lmborder * 2);
ylen = surface.mapSizeY + (lmborder * 2);
xoff = surface.mapOffsetX - lmborder;
yoff = surface.mapOffsetY - lmborder;
// get the lmapGen...
const Interior::TexGenPlanes & lmTexGenEQ = detail->getLMTexGenEQ(surfaceIndex);
const F32 * const lGenX = lmTexGenEQ.planeX;
const F32 * const lGenY = lmTexGenEQ.planeY;
AssertFatal((lGenX[0] * lGenX[1] == 0.f) &&
(lGenX[0] * lGenX[2] == 0.f) &&
(lGenX[1] * lGenX[2] == 0.f), "Bad lmTexGen!");
AssertFatal((lGenY[0] * lGenY[1] == 0.f) &&
(lGenY[0] * lGenY[2] == 0.f) &&
(lGenY[1] * lGenY[2] == 0.f), "Bad lmTexGen!");
// get the axis index for the texgens (could be swapped)
S32 si;
S32 ti;
S32 axis = -1;
//
if(lGenX[0] == 0.f && lGenY[0] == 0.f) // YZ
{
axis = 0;
if(lGenX[1] == 0.f) { // swapped?
si = 2;
ti = 1;
} else {
si = 1;
ti = 2;
}
}
else if(lGenX[1] == 0.f && lGenY[1] == 0.f) // XZ
{
axis = 1;
if(lGenX[0] == 0.f) { // swapped?
si = 2;
ti = 0;
} else {
si = 0;
ti = 2;
}
}
else if(lGenX[2] == 0.f && lGenY[2] == 0.f) // XY
{
axis = 2;
if(lGenX[0] == 0.f) { // swapped?
si = 1;
ti = 0;
} else {
si = 0;
ti = 1;
}
}
AssertFatal(!(axis == -1), "SceneLighting::lightInterior: bad TexGen!");
const F32 * pNormal = ((const F32*)plane);
Point3F start;
F32 * pStart = ((F32*)start);
F32 lumelScale = 1 / (lGenX[si] * normLightmap->getWidth());
// get the start point on the lightmap
pStart[si] = (((xoff * lumelScale) / (1 / lGenX[si])) - lGenX[3]) / lGenX[si];
pStart[ti] = (((yoff * lumelScale) / (1 / lGenY[ti])) - lGenY[3]) / lGenY[ti];
pStart[axis] = ((pNormal[si] * pStart[si]) + (pNormal[ti] * pStart[ti]) + plane.d) / -pNormal[axis];
start.convolve(scale);
transform.mulP(start);
// get the s/t vecs oriented on the surface
Point3F sVec;
Point3F tVec;
F32 * pSVec = ((F32*)sVec);
F32 * pTVec = ((F32*)tVec);
F32 angle;
Point3F planeNormal;
// s
pSVec[si] = 1.f;
pSVec[ti] = 0.f;
planeNormal = plane;
((F32*)planeNormal)[ti] = 0.f;
planeNormal.normalize();
angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f));
pSVec[axis] = (((F32*)planeNormal)[si] < 0.f) ? mTan(angle) : -mTan(angle);
// t
pTVec[ti] = 1.f;
pTVec[si] = 0.f;
planeNormal = plane;
((F32*)planeNormal)[si] = 0.f;
planeNormal.normalize();
angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f));
pTVec[axis] = (((F32*)planeNormal)[ti] < 0.f) ? mTan(angle) : -mTan(angle);
sVec *= lumelScale;
tVec *= lumelScale;
// project vecs
transform.mulV(sVec);
sVec.convolve(scale);
transform.mulV(tVec);
tVec.convolve(scale);
Point3F & curPos = start;
Point3F sRun = sVec * xlen;
// get the lexel area
Point3F cross;
mCross(sVec, tVec, &cross);
F32 maxLexelArea = cross.len();
const PlaneF & surfacePlane = shadowVolume.getPlane(surfaceInfo->mPlaneIndex);
// get the world coordinate for each lexel
for(U32 y = 0; y < ylen; y++)
{
U8 * normBits = normLightmap->getAddress(xoff, yoff + y);
U8 * alarmBits = alarmLightmap ? alarmLightmap->getAddress(xoff, yoff + y) : 0;
for(U32 x = 0; x < xlen; x++)
{
ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly();
poly->mPlane = surfacePlane;
poly->mWindingCount = 4;
// set the poly indices
poly->mWinding[0] = curPos;
poly->mWinding[1] = curPos + sVec;
poly->mWinding[2] = curPos + sVec + tVec;
poly->mWinding[3] = curPos + tVec;
// // insert poly which has been clipped to own shadow volume
// ShadowVolumeBSP::SVPoly * store = 0;
// shadowVolume.clipToSelf(surfaceInfo->mShadowVolume, &store, poly);
//
// if(!store)
// continue;
//
// F32 lexelArea = shadowVolume.getPolySurfaceArea(store);
// F32 area = shadowVolume.getLitSurfaceArea(store, surfaceInfo);
F32 area = shadowVolume.getLitSurfaceArea(poly, surfaceInfo);
F32 shadowScale = mClampF(area / maxLexelArea, 0.f, 1.f);
// get the color into U8
ColorF tmp = (light->mColor * dot * shadowScale) + light->mAmbient;
tmp.clamp();
ColorI color = tmp;
// attempt to light both normal and alarm lightmaps
for(U32 c = 0; c < 2; c++)
{
U8* & pBits = (c == 0) ? normBits : alarmBits;
if(!pBits)
continue;
#ifdef SET_COLORS
*pBits++ = color.red;
*pBits++ = color.green;
*pBits++ = color.blue;
#else
U32 _r = static_cast<U32>( color.red ) + static_cast<U32>( *pBits );
*pBits = ( _r <= 255 ) ? _r : 255;
pBits++;
U32 _g = static_cast<U32>( color.green ) + static_cast<U32>( *pBits );
*pBits = ( _g <= 255 ) ? _g : 255;
pBits++;
U32 _b = static_cast<U32>( color.blue ) + static_cast<U32>( *pBits );
*pBits = ( _b <= 255 ) ? _b : 255;
pBits++;
#endif
}
curPos += sVec;
}
curPos -= sRun;
curPos += tVec;
}
}
}
Con::printf(" = interior lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f);
}
void SceneLighting::InteriorProxy::postLight(bool lastLight)
{
delete mBoxShadowBSP;
mBoxShadowBSP = 0;
InteriorInstance * interior = getObject();
if(!interior)
return;
// only rebuild the vertex colors after the last light
if(lastLight)
interior->rebuildVertexColors();
}
//------------------------------------------------------------------------------
U32 SceneLighting::InteriorProxy::getResourceCRC()
{
InteriorInstance * interior = getObject();
if(!interior)
return(0);
return(interior->getCRC());
}
//------------------------------------------------------------------------------
bool SceneLighting::InteriorProxy::setPersistInfo(SceneLighting::PersistInfo::PersistChunk * info)
{
if(!Parent::setPersistInfo(info))
return(false);
SceneLighting::PersistInfo::InteriorChunk * chunk = dynamic_cast<SceneLighting::PersistInfo::InteriorChunk*>(info);
AssertFatal(chunk, "SceneLighting::InteriorProxy::setPersistInfo: invalid info chunk!");
InteriorInstance * interior = getObject();
if(!interior)
return(false);
U32 numDetails = interior->getNumDetailLevels();
// check the lighting method
AssertFatal(SceneLighting::smUseVertexLighting == Interior::smUseVertexLighting, "SceneLighting::InteriorProxy::setPersistInfo: invalid vertex lighting state");
if(SceneLighting::smUseVertexLighting != Interior::smUseVertexLighting)
return(false);
// process the vertex lighting...
if(chunk->mDetailVertexCount.size() != numDetails)
return(false);
AssertFatal(chunk->mVertexColorsNormal.size(), "SceneLighting::InteriorProxy::setPersistInfo: invalid chunk info");
AssertFatal(!chunk->mHasAlarmState || chunk->mVertexColorsAlarm.size(), "SceneLighting::InteriorProxy::setPersistInfo: invalid chunk info");
AssertFatal(!(chunk->mHasAlarmState ^ interior->getDetailLevel(0)->hasAlarmState()), "SceneLighting::InteriorProxy::setPersistInfo: invalid chunk info");
U32 curPos = 0;
for(U32 i = 0; i < numDetails; i++)
{
U32 count = chunk->mDetailVertexCount[i];
Vector<ColorI>* normal = interior->getVertexColorsNormal(i);
Vector<ColorI>* alarm = interior->getVertexColorsAlarm(i);
AssertFatal(normal != NULL && alarm != NULL, "Error, bad vectors returned!");
normal->setSize(count);
dMemcpy(normal->address(), &chunk->mVertexColorsNormal[curPos], count * sizeof(ColorI));
if(chunk->mHasAlarmState)
{
alarm->setSize(count);
dMemcpy(alarm->address(), &chunk->mVertexColorsAlarm[curPos], count * sizeof(ColorI));
}
curPos += count;
}
// need lightmaps?
if(!SceneLighting::smUseVertexLighting)
{
if(chunk->mDetailLightmapCount.size() != numDetails)
return(false);
LM_HANDLE instanceHandle = interior->getLMHandle();
U32 idx = 0;
for(U32 i = 0; i < numDetails; i++)
{
Interior * detail = interior->getDetailLevel(i);
LM_HANDLE interiorHandle = detail->getLMHandle();
Vector<TextureHandle*> & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0);
if(chunk->mDetailLightmapCount[i] > baseHandles.size())
return(false);
for(U32 j = 0; j < chunk->mDetailLightmapCount[i]; j++)
{
U32 baseIndex = chunk->mDetailLightmapIndices[idx];
if(baseIndex >= baseHandles.size())
return(false);
AssertFatal(chunk->mLightmaps[idx], "SceneLighting::InteriorProxy::setPersistInfo: bunk bitmap!");
if(chunk->mLightmaps[idx]->getWidth() != baseHandles[baseIndex]->getWidth() ||
chunk->mLightmaps[idx]->getHeight() != baseHandles[baseIndex]->getHeight())
return(false);
TextureHandle * tHandle = gInteriorLMManager.duplicateBaseLightmap(interiorHandle, instanceHandle, baseIndex);
// create the diff bitmap
U8 * pDiff = chunk->mLightmaps[idx]->getAddress(0,0);
U8 * pBase = baseHandles[baseIndex]->getBitmap()->getAddress(0,0);
U8 * pDest = tHandle->getBitmap()->getAddress(0,0);
Point2I extent(tHandle->getWidth(), tHandle->getHeight());
for(U32 y = 0; y < extent.y; y++)
for(U32 x = 0; x < extent.x; x++)
{
*pDest++ = *pBase++ + *pDiff++;
*pDest++ = *pBase++ + *pDiff++;
*pDest++ = *pBase++ + *pDiff++;
}
idx++;
}
}
}
return(true);
}
bool SceneLighting::InteriorProxy::getPersistInfo(SceneLighting::PersistInfo::PersistChunk * info)
{
if(!Parent::getPersistInfo(info))
return(false);
SceneLighting::PersistInfo::InteriorChunk * chunk = dynamic_cast<SceneLighting::PersistInfo::InteriorChunk*>(info);
AssertFatal(chunk, "SceneLighting::InteriorProxy::getPersistInfo: invalid info chunk!");
InteriorInstance * interior = getObject();
if(!interior)
return(false);
LM_HANDLE instanceHandle = interior->getLMHandle();
AssertFatal(!chunk->mDetailLightmapCount.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid array!");
AssertFatal(!chunk->mDetailLightmapIndices.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid array!");
AssertFatal(!chunk->mLightmaps.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid array!");
U32 numDetails = interior->getNumDetailLevels();
U32 i;
for(i = 0; i < numDetails; i++)
{
Interior * detail = interior->getDetailLevel(i);
LM_HANDLE interiorHandle = detail->getLMHandle();
Vector<TextureHandle*> & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0);
Vector<TextureHandle*> & instanceHandles = gInteriorLMManager.getHandles(interiorHandle, instanceHandle);
U32 litCount = 0;
// walk all the instance lightmaps and grab diff lighting from them
for(U32 j = 0; j < instanceHandles.size(); j++)
{
if(!instanceHandles[j])
continue;
litCount++;
chunk->mDetailLightmapIndices.push_back(j);
GBitmap * baseBitmap = baseHandles[j]->getBitmap();
GBitmap * instanceBitmap = instanceHandles[j]->getBitmap();
Point2I extent(baseBitmap->getWidth(), baseBitmap->getHeight());
GBitmap * diffLightmap = new GBitmap(extent.x, extent.y, false, GBitmap::RGB);
U8 * pBase = baseBitmap->getAddress(0,0);
U8 * pInstance = instanceBitmap->getAddress(0,0);
U8 * pDest = diffLightmap->getAddress(0,0);
// fill the diff lightmap
for(U32 y = 0; y < extent.y; y++)
for(U32 x = 0; x < extent.x; x++)
{
*pDest++ = *pInstance++ - *pBase++;
*pDest++ = *pInstance++ - *pBase++;
*pDest++ = *pInstance++ - *pBase++;
}
chunk->mLightmaps.push_back(diffLightmap);
}
chunk->mDetailLightmapCount.push_back(litCount);
}
// process the vertex lighting...
AssertFatal(!chunk->mDetailVertexCount.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid chunk info");
AssertFatal(!chunk->mVertexColorsNormal.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid chunk info");
AssertFatal(!chunk->mVertexColorsAlarm.size(), "SceneLighting::InteriorProxy::getPersistInfo: invalid chunk info");
chunk->mHasAlarmState = interior->getDetailLevel(0)->hasAlarmState();
chunk->mDetailVertexCount.setSize(numDetails);
U32 size = 0;
for(i = 0; i < numDetails; i++)
{
Interior * detail = interior->getDetailLevel(i);
U32 count = detail->getWindingCount();
chunk->mDetailVertexCount[i] = count;
size += count;
}
chunk->mVertexColorsNormal.setSize(size);
if(chunk->mHasAlarmState)
chunk->mVertexColorsAlarm.setSize(size);
U32 curPos = 0;
for(i = 0; i < numDetails; i++)
{
Vector<ColorI>* normal = interior->getVertexColorsNormal(i);
Vector<ColorI>* alarm = interior->getVertexColorsAlarm(i);
AssertFatal(normal != NULL && alarm != NULL, "Error, no normal or alarm vertex colors!");
U32 count = chunk->mDetailVertexCount[i];
dMemcpy(&chunk->mVertexColorsNormal[curPos], normal->address(), count * sizeof(ColorI));
if(chunk->mHasAlarmState)
dMemcpy(&chunk->mVertexColorsAlarm[curPos], alarm->address(), count * sizeof(ColorI));
curPos += count;
}
return(true);
}
//-------------------------------------------------------------------------------
// Class SceneLighting::TerrainProxy:
//-------------------------------------------------------------------------------
SceneLighting::TerrainProxy::TerrainProxy(SceneObject * obj) :
Parent(obj)
{
mLightmap = 0;
}
SceneLighting::TerrainProxy::~TerrainProxy()
{
delete [] mLightmap;
}
//-------------------------------------------------------------------------------
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);
}
bool SceneLighting::TerrainProxy::preLight(LightInfo * light)
{
if(!bool(mObj))
return(false);
if(light->mType != LightInfo::Vector)
return(false);
mShadowMask.clear();
return(true);
}
void SceneLighting::TerrainProxy::light(LightInfo * light)
{
TerrainBlock * terrain = getObject();
if(!terrain)
return;
AssertFatal(light->mType == LightInfo::Vector, "wrong light type");
S32 time = Platform::getRealMilliseconds();
// reset
mShadowVolume = new ShadowVolumeBSP;
// 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);
}
lightVector(light);
// set the lightmap...
U16 * lPtr = (U16*)terrain->lightMap->getAddress(0,0);
for(U32 i = 0; i < (TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); i++)
lPtr[i] = convertColor(mLightmap[i]);
terrain->buildMaterialMap();
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(SceneLighting::PersistInfo::PersistChunk * info)
{
if(!Parent::setPersistInfo(info))
return(false);
SceneLighting::PersistInfo::TerrainChunk * chunk = dynamic_cast<SceneLighting::PersistInfo::TerrainChunk*>(info);
AssertFatal(chunk, "SceneLighting::TerrainProxy::setPersistInfo: invalid info chunk!");
TerrainBlock * terrain = getObject();
if(!terrain || !terrain->lightMap)
return(false);
dMemcpy(terrain->lightMap->getAddress(0,0), chunk->mLightmap, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize);
terrain->buildMaterialMap();
return(true);
}
bool SceneLighting::TerrainProxy::getPersistInfo(SceneLighting::PersistInfo::PersistChunk * info)
{
if(!Parent::getPersistInfo(info))
return(false);
SceneLighting::PersistInfo::TerrainChunk * chunk = dynamic_cast<SceneLighting::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);
}