3014 lines
95 KiB
C++
Executable File
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);
|
|
}
|
|
*/ |