//----------------------------------------------- // 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 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; isgCleanupUnused(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(parentobject); WheeledVehicle *wheeledvehicle = dynamic_cast(parentobject); LightInfoList lights; LightManager *lm = gClientSceneGraph->getLightManager(); lm->sgGetBestLights(lights); // env light must be last! for(U32 i=0; imType != 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; imType != 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; igetImageShapeInstance(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; wgetWheelCount(); 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; imType == 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); }