525 lines
13 KiB
C++
Executable File
525 lines
13 KiB
C++
Executable File
//-----------------------------------------------
|
|
// Synapse Gaming - Lighting System
|
|
// Copyright © Synapse Gaming 2003
|
|
// Written by John Kabus
|
|
//-----------------------------------------------
|
|
#include "sceneGraph/sceneGraph.h"
|
|
#include "math/mathUtils.h"
|
|
#include "game/shapeBase.h"
|
|
#include "game/vehicles/wheeledVehicle.h"
|
|
|
|
#include "lightingSystem/sgLighting.h"
|
|
#include "lightingSystem/sgLightingModel.h"
|
|
#include "lightingSystem/sgObjectShadows.h"
|
|
|
|
#define SG_UNUSED_TIMEOUT 2000
|
|
#define SG_SHADOW_TIMEOUT 6000
|
|
// this can be a long time, only used for cleaning
|
|
// up texture memory after an intensive scene (TSE only)...
|
|
//#define SG_TEXTURE_TIMEOUT 30000
|
|
|
|
extern SceneGraph* gClientSceneGraph;
|
|
|
|
Vector<sgObjectShadows *> sgObjectShadowMonitor::sgAllObjectShadows;
|
|
|
|
F32 sgObjectShadows::sgCurrentFade = 1.0;
|
|
|
|
|
|
bool sgTimeElapsed(U32 currenttime, U32 &lasttime, U32 period)
|
|
{
|
|
if(currenttime < lasttime)
|
|
{
|
|
lasttime = 0;
|
|
return false;
|
|
}
|
|
|
|
if((currenttime - lasttime) < period)
|
|
return false;
|
|
|
|
lasttime = currenttime;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
|
|
void sgObjectShadowMonitor::sgRegister(sgObjectShadows *shadows)
|
|
{
|
|
sgAllObjectShadows.push_back(shadows);
|
|
}
|
|
|
|
void sgObjectShadowMonitor::sgUnregister(sgObjectShadows *shadows)
|
|
{
|
|
for(U32 i=0; i<sgAllObjectShadows.size(); i++)
|
|
{
|
|
if(sgAllObjectShadows[i] == shadows)
|
|
{
|
|
sgAllObjectShadows.erase_fast(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void sgObjectShadowMonitor::sgCleanupUnused()
|
|
{
|
|
static U32 lasttime = 0;
|
|
|
|
U32 time = Platform::getRealMilliseconds();
|
|
if(!sgTimeElapsed(time, lasttime, SG_UNUSED_TIMEOUT))
|
|
return;
|
|
|
|
//Con::warnf("Checking for unused shadows...");
|
|
|
|
for(U32 i=0; i<sgAllObjectShadows.size(); i++)
|
|
sgAllObjectShadows[i]->sgCleanupUnused(time);
|
|
|
|
//Con::warnf("Done.");
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
|
|
sgObjectShadows::sgObjectShadows()
|
|
{
|
|
sgRegistered = false;
|
|
sgLastRenderTime = 0;
|
|
|
|
sgSingleShadowSource.mColor = ColorF(0.5, 0.5, 0.5);
|
|
|
|
//sgEnable = false;
|
|
//sgCanMove = false;
|
|
//sgCanRTT = false;
|
|
//sgCanSelfShadow = false;
|
|
//sgRequestedShadowSize = 64;
|
|
//sgFrameSkip = 5;
|
|
//sgMaxVisibleDistance = 15.0f;
|
|
//sgProjectionDistance = 7.0f;
|
|
|
|
//sgFirstEntry = sgGetFirstShadowEntry();
|
|
}
|
|
|
|
sgObjectShadows::~sgObjectShadows()
|
|
{
|
|
sgClearMap();
|
|
}
|
|
|
|
void sgObjectShadows::sgClearMap()
|
|
{
|
|
sgShadowMultimap *entry = sgGetFirstShadowEntry();
|
|
while(entry)
|
|
{
|
|
if(entry->info)
|
|
{
|
|
delete entry->info;
|
|
entry->info = NULL;
|
|
}
|
|
|
|
entry = entry->linkHigh;
|
|
}
|
|
|
|
// all shadows are deleted, so nothing left to monitor...
|
|
if(sgRegistered)
|
|
{
|
|
sgObjectShadowMonitor::sgUnregister(this);
|
|
sgRegistered = false;
|
|
}
|
|
}
|
|
|
|
void sgObjectShadows::sgRender(SceneObject *parentobject,
|
|
TSShapeInstance *shapeinstance, F32 dist, F32 fogamount,
|
|
F32 genericshadowlevel, F32 noshadowlevel, U32 shadownode,
|
|
bool move, bool animate)
|
|
{
|
|
if(!LightManager::sgAllowDynamicShadows())
|
|
return;
|
|
|
|
if(Shadow::getGlobalShadowDetailLevel() < noshadowlevel)
|
|
return;
|
|
if(shapeinstance->getShape()->subShapeFirstTranslucentObject.empty() ||
|
|
shapeinstance->getShape()->subShapeFirstTranslucentObject[0] == 0)
|
|
return;
|
|
|
|
// prior to this no shadows exist, so no resources are used...
|
|
if(!sgRegistered)
|
|
{
|
|
sgObjectShadowMonitor::sgRegister(this);
|
|
sgRegistered = true;
|
|
}
|
|
|
|
sgLastRenderTime = Platform::getRealMilliseconds();
|
|
|
|
ShapeBase *shapebase = dynamic_cast<ShapeBase *>(parentobject);
|
|
WheeledVehicle *wheeledvehicle = dynamic_cast<WheeledVehicle *>(parentobject);
|
|
|
|
LightInfoList lights;
|
|
LightManager *lm = gClientSceneGraph->getLightManager();
|
|
|
|
lm->sgGetBestLights(lights);
|
|
|
|
// env light must be last!
|
|
for(U32 i=0; i<lights.size(); i++)
|
|
{
|
|
LightInfo *light = lights[i];
|
|
if((light->mType != LightInfo::Ambient) &&
|
|
(light->mType != LightInfo::Vector))
|
|
continue;
|
|
|
|
U32 il = lights.size() - 1;
|
|
if(i == il)
|
|
continue;
|
|
|
|
// swap...
|
|
LightInfo *last = lights[il];
|
|
lights[il] = light;
|
|
lights[i] = last;
|
|
break;
|
|
}
|
|
|
|
F32 shadowLen = 10.0f * shapeinstance->getShape()->radius;
|
|
Point3F pos = shapeinstance->getShape()->center;
|
|
|
|
// this is a bit of a hack...move generic shadows towards feet/base of shape
|
|
if(Shadow::getGlobalShadowDetailLevel() < genericshadowlevel)
|
|
pos *= 0.5f;
|
|
S32 shadowNode = shadownode;
|
|
if(shadowNode >= 0)
|
|
{
|
|
// adjust for movement of shape outside of bounding box by tracking this node
|
|
Point3F offset;
|
|
shapeinstance->mNodeTransforms[shadowNode].getColumn(3,&offset);
|
|
offset -= shapeinstance->getShape()->defaultTranslations[shadowNode];
|
|
offset.z = 0.0f;
|
|
pos += offset;
|
|
}
|
|
pos.convolve(parentobject->getScale());
|
|
parentobject->getRenderTransform().mulP(pos);
|
|
|
|
U32 usedlights = 0;
|
|
|
|
for(U32 i=0; i<lights.size(); i++)
|
|
{
|
|
LightInfo *light = lights[i];
|
|
|
|
// we want the env light!
|
|
if((!LightManager::sgMultipleDynamicShadows) &&
|
|
(light->mType != LightInfo::Vector) &&
|
|
(light->mType != LightInfo::Ambient))
|
|
continue;
|
|
|
|
// cast shadows?
|
|
if(!light->sgCastsShadows)
|
|
continue;
|
|
|
|
// avoid overlapping dynamic and static shadows...
|
|
if(((light->mType == LightInfo::SGStaticPoint) ||
|
|
(light->mType == LightInfo::SGStaticSpot) ||
|
|
(light->mType == LightInfo::Vector)) && (parentobject->getTypeMask() & ShadowCasterObjectType))
|
|
continue;
|
|
|
|
// find the shadow...
|
|
Shadow *shadow = sgFindShadow(parentobject, light, shapeinstance);
|
|
AssertFatal((shadow), "Shadow not found?");
|
|
|
|
if(Shadow::getGlobalShadowDetailLevel() < genericshadowlevel)
|
|
shadow->setGeneric(true);
|
|
else
|
|
shadow->setGeneric(false);
|
|
|
|
shadow->setMoving(move);
|
|
shadow->setAnimating(animate);
|
|
|
|
// Get a real light dir...
|
|
// keep this default, it makes the sun shadow darker...
|
|
F32 fade = 3.0;
|
|
Point3F lightDir;
|
|
|
|
if(LightManager::sgMultipleDynamicShadows)
|
|
{
|
|
//if(light->mType == LightInfo::Vector)
|
|
// lightDir = light->mDirection;
|
|
//else if(light->mType == LightInfo::Ambient)
|
|
if((light->mType == LightInfo::Vector) ||
|
|
(light->mType == LightInfo::Ambient))
|
|
{
|
|
lightDir = sgGetShadowLightDirection(parentobject, shadow);
|
|
|
|
if(usedlights > 0)
|
|
fade *= 1.0f / F32(usedlights + 1);
|
|
}
|
|
else
|
|
{
|
|
lightDir = parentobject->getPosition() - light->mPos;
|
|
F32 len = lightDir.len();
|
|
if(len == 0.0f)
|
|
continue;
|
|
lightDir /= len;
|
|
|
|
sgLightingModel &model = sgLightingModelManager::sgGetLightingModel(
|
|
light->sgLightingModelName);
|
|
model.sgSetState(light);
|
|
//F32 maxrad = model.sgGetMaxRadius(true, true);
|
|
SphereF s;
|
|
s.center = parentobject->getPosition();
|
|
fade = model.sgScoreLight(light, s);
|
|
model.sgResetState();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lightDir = sgGetShadowLightDirection(parentobject, shadow);
|
|
}
|
|
|
|
// help out dim lights a little...
|
|
if(light->sgAssignedToParticleSystem)
|
|
fade *= SG_PARTICLESYSTEMLIGHT_FIXED_INTENSITY;
|
|
else
|
|
fade *= getMax(light->mColor.red, getMax(light->mColor.green, light->mColor.blue));
|
|
fade *= 1.5f;
|
|
// prevent dark shadows...
|
|
fade = mClampF(fade, 0.0f, 1.0f);
|
|
if(fade <= 0.1f)
|
|
continue;
|
|
|
|
usedlights++;
|
|
sgObjectShadows::sgCurrentFade = fade;
|
|
|
|
// pos is where shadow will be centered (in world space)
|
|
if(wheeledvehicle)
|
|
pos -= lightDir;
|
|
Point3F scale = parentobject->getScale();
|
|
shadow->setRadius(shapeinstance, scale);
|
|
if(!shadow->prepare(pos, lightDir, shadowLen, scale, dist, fogamount, shapeinstance))
|
|
continue;
|
|
|
|
F32 maxscale = getMax(scale.x, getMax(scale.y, scale.z));
|
|
|
|
if(shadow->needBitmap())
|
|
{
|
|
shadow->beginRenderToBitmap();
|
|
shadow->selectShapeDetail(shapeinstance, dist, maxscale);
|
|
shadow->renderToBitmap(shapeinstance, parentobject->getRenderTransform(), pos, scale);
|
|
|
|
if(shapebase)
|
|
{
|
|
// render mounted items to shadow bitmap
|
|
for(U32 i=0; i<ShapeBase::MaxMountedImages; i++)
|
|
{
|
|
TSShapeInstance *instance = shapebase->getImageShapeInstance(i);
|
|
if(instance)
|
|
{
|
|
MatrixF mat;
|
|
shapebase->getRenderImageTransform(i, &mat);
|
|
shadow->selectShapeDetail(instance, dist, maxscale);
|
|
shadow->renderToBitmap(instance, mat, pos, Point3F(1,1,1));
|
|
}
|
|
}
|
|
|
|
// We only render the first mounted object for now...
|
|
ShapeBase *mount = shapebase->getMountedObject(0);
|
|
if(mount && mount->getShapeInstance())
|
|
{
|
|
Point3F linkscale = mount->getScale();
|
|
maxscale = getMax(linkscale.x, getMax(linkscale.y, linkscale.z));
|
|
shadow->selectShapeDetail(mount->getShapeInstance(), dist, maxscale);
|
|
shadow->renderToBitmap(mount->getShapeInstance(),
|
|
mount->getRenderTransform(), pos, linkscale);
|
|
}
|
|
}
|
|
|
|
if(wheeledvehicle)
|
|
{
|
|
for(U32 w=0; w<wheeledvehicle->getWheelCount(); w++)
|
|
{
|
|
WheeledVehicle::Wheel *wheel = wheeledvehicle->getWheel(w);
|
|
|
|
if(wheel->shapeInstance)
|
|
{
|
|
MatrixF m = parentobject->getRenderTransform();
|
|
MatrixF hub(EulerF(0, 0, wheeledvehicle->getSteering().x * wheel->steering));
|
|
Point3F p = wheel->data->pos;
|
|
p.z -= wheel->spring->length * wheel->extension;
|
|
hub.setColumn(3, p);
|
|
m.mul(hub);
|
|
|
|
MatrixF rot(EulerF(wheel->apos * M_2PI,0,0));
|
|
m.mul(rot);
|
|
|
|
MatrixF wrot(EulerF(0, 0, (wheel->data->pos.x > 0)? M_PI/2: -M_PI/2));
|
|
m.mul(wrot);
|
|
|
|
shadow->selectShapeDetail(wheel->shapeInstance, dist, maxscale);
|
|
shadow->renderToBitmap(wheel->shapeInstance, m, pos, scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
shadow->endRenderToBitmap();
|
|
}
|
|
|
|
shadow->render();
|
|
shadow->sgLastRenderTime = sgLastRenderTime;
|
|
|
|
if(!LightManager::sgMultipleDynamicShadows)
|
|
{
|
|
//only need to render one here...
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Point3F sgObjectShadows::sgGetShadowLightDirection(SceneObject *obj, Shadow *shadow) const
|
|
{
|
|
// don't want this to run too fast on newer systems (otherwise shadows snap into place)...
|
|
U32 time = Platform::getRealMilliseconds();
|
|
|
|
if((time - shadow->sgPreviousShadowTime) < SG_DYNAMIC_SHADOW_TIME)
|
|
return shadow->sgPreviousShadowLightingVector;
|
|
|
|
shadow->sgPreviousShadowTime = time;
|
|
|
|
LightManager *lm = gClientSceneGraph->getLightManager();
|
|
|
|
// ok get started...
|
|
U32 zone = obj->getCurrZone(0);
|
|
|
|
VectorF vectcomposite = VectorF(0, 0, -1.0f);
|
|
if(!LightManager::sgMultipleDynamicShadows)
|
|
{
|
|
U32 score;
|
|
U32 maxscore[2] = {0, 0};
|
|
LightInfo *light[2] = {NULL, NULL};
|
|
VectorF vector[2] = {VectorF(0, 0, 0), VectorF(0, 0, 0)};
|
|
|
|
LightInfoList lights;
|
|
lm->sgGetBestLights(lights);
|
|
|
|
for(U32 i=0; i<lights.size(); i++)
|
|
{
|
|
LightInfo *l = lights[i];
|
|
|
|
if((l->mType == LightInfo::Ambient) || (l->mType == LightInfo::Vector))
|
|
score = l->mScore / SG_LIGHTMANAGER_SUN_PRIORITY;
|
|
else if((l->mType == LightInfo::SGStaticPoint) || (l->mType == LightInfo::SGStaticSpot))
|
|
score = l->mScore / SG_LIGHTMANAGER_STATIC_PRIORITY;
|
|
else
|
|
score = l->mScore / SG_LIGHTMANAGER_DYNAMIC_PRIORITY;
|
|
|
|
if(score > maxscore[0])
|
|
{
|
|
light[1] = light[0];
|
|
maxscore[1] = maxscore[0];
|
|
|
|
light[0] = l;
|
|
maxscore[0] = score;
|
|
}
|
|
else if(score > maxscore[1])
|
|
{
|
|
light[1] = l;
|
|
maxscore[1] = score;
|
|
}
|
|
}
|
|
|
|
for(U32 i=0; i<2; i++)
|
|
{
|
|
if(!light[i])
|
|
break;
|
|
|
|
if((light[i]->mType == LightInfo::Ambient) || (light[i]->mType == LightInfo::Vector))
|
|
{
|
|
if(zone == 0)
|
|
vector[i] = light[i]->mDirection;
|
|
else
|
|
vector[i] = Point3F(0, 0, -1.0);
|
|
}
|
|
else
|
|
{
|
|
VectorF vect = obj->getPosition() - light[i]->mPos;
|
|
vect.normalize();
|
|
vector[i] = vect;
|
|
}
|
|
}
|
|
|
|
if(light[0])
|
|
{
|
|
if(light[1])
|
|
{
|
|
F32 ratio = F32(maxscore[0]) / F32(maxscore[0] + maxscore[1]);
|
|
vectcomposite = (vector[0] * ratio) + (vector[1] * (1.0f - ratio));
|
|
}
|
|
else
|
|
vectcomposite = vector[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(zone != 0)
|
|
vectcomposite = VectorF(0.0f, 0.0f, -1.0f);
|
|
else
|
|
{
|
|
LightInfo *sun = lm->sgGetSpecialLight(LightManager::sgSunLightType);
|
|
vectcomposite = sun->mDirection;
|
|
}
|
|
}
|
|
|
|
VectorF step = (vectcomposite - shadow->sgPreviousShadowLightingVector) / SG_DYNAMIC_SHADOW_STEPS;
|
|
shadow->sgPreviousShadowLightingVector += step;
|
|
shadow->sgPreviousShadowLightingVector.normalize();
|
|
|
|
return shadow->sgPreviousShadowLightingVector;
|
|
}
|
|
|
|
void sgObjectShadows::sgCleanupUnused(U32 time)
|
|
{
|
|
// wrapped around?
|
|
if(time < sgLastRenderTime)
|
|
{
|
|
sgLastRenderTime = 0;
|
|
return;
|
|
}
|
|
|
|
// try to ditch the whole thing first...
|
|
if((time - sgLastRenderTime) > SG_SHADOW_TIMEOUT)
|
|
{
|
|
//Con::warnf("Found a whole set...");
|
|
|
|
sgClearMap();
|
|
return;
|
|
}
|
|
|
|
// no? alright lets try to get rid of some old shadows...
|
|
sgShadowMultimap *entry = sgGetFirstShadowEntry();
|
|
while(entry)
|
|
{
|
|
if(entry->info)
|
|
{
|
|
if(sgTimeElapsed(time, entry->info->sgLastRenderTime, SG_SHADOW_TIMEOUT))
|
|
{
|
|
//Con::warnf("Found one...");
|
|
|
|
delete entry->info;
|
|
entry->info = NULL;
|
|
}
|
|
}
|
|
|
|
entry = entry->linkHigh;
|
|
}
|
|
}
|
|
|
|
Shadow *sgObjectShadows::sgFindShadow(SceneObject *parentobject,
|
|
LightInfo *light, TSShapeInstance *shapeinstance)
|
|
{
|
|
sgShadowMultimap *entry = sgShadows.find(sgLightToHash(light));
|
|
|
|
if(entry->info)
|
|
return entry->info;
|
|
|
|
Shadow *shadow = new Shadow();
|
|
entry->info = shadow;
|
|
|
|
return entry->info;
|
|
}
|
|
|
|
sgShadowMultimap *sgObjectShadows::sgGetFirstShadowEntry()
|
|
{
|
|
return sgShadows.find(sgosFirstEntryHash);
|
|
}
|
|
|