//----------------------------------------------- // Synapse Gaming - Lighting System // Copyright © Synapse Gaming 2003 // Written by John Kabus //----------------------------------------------- #include "editor/editTSCtrl.h" #include "editor/worldEditor.h" #include "game/shadow.h" #include "game/vehicles/wheeledVehicle.h" #include "game/gameConnection.h" #include "sceneGraph/sceneGraph.h" #include "terrain/terrRender.h" #include "game/shapeBase.h" #include "gui/core/guiCanvas.h" #include "ts/tsShape.h" #include "ts/tsShapeInstance.h" #include "game/staticShape.h" #include "game/tsStatic.h" #include "collision/concretePolyList.h" #include "lightingSystem/sgSceneLighting.h" namespace { static void findObjectsCallback(SceneObject* obj, void *val) { Vector * list = (Vector*)val; list->push_back(obj); } bool gTerminateLighting = false; F32 gLightingProgress = 0.f; const char * gCompleteCallback = 0; U32 gConnectionMissionCRC = 0xffffffff; } SceneLighting *gLighting = 0; F32 gParellelVectorThresh = 0.01; F32 gPlaneNormThresh = 0.999; F32 gPlaneDistThresh = 0.001; bool SceneLighting::smUseVertexLighting = false; void SceneLighting::sgNewEvent(U32 light, S32 object, U32 event) { Sim::postEvent(this, new sgSceneLightingProcessEvent(light, object, event), Sim::getTargetTime() + 1); } //----------------------------------------------- /* * Called once per scenelighting - entry point for event system */ void SceneLighting::sgLightingStartEvent() { Con::printf(""); Con::printf("//-----------------------------------------------"); Con::printf("Synapse Gaming Lighting Pack"); Con::printf(""); Con::printf("Starting scene lighting..."); sgTimeTemp2 = Platform::getRealMilliseconds(); // clear static mesh shadow data... sgShadowObjects::sgClearStaticMeshBVPTData(); // clear interior light maps for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) { // is there an object? if(!(*proxyItr)->getObject()) { AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); continue; } InteriorInstance *interior = dynamic_cast((*proxyItr)->getObject()); if(!interior) continue; for(U32 d=0; dgetResource()->getNumDetailLevels(); d++) { Interior *detail = interior->getResource()->getDetailLevel(d); gInteriorLMManager.clearLightmaps(detail->getLMHandle(), interior->getLMHandle()); } } Canvas->paint(); sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgTGEPassSetupEventType); //sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGPassSetupEventType); } /* * Called once per scenelighting - exit from event system */ void SceneLighting::sgLightingCompleteEvent() { // 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); } // clear static mesh shadow data... sgShadowObjects::sgClearStaticMeshBVPTData(); Con::printf("Scene lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp2)/1000.f); Con::printf("//-----------------------------------------------"); Con::printf(""); Canvas->paint(); completed(true); deleteObject(); } //----------------------------------------------- /* * Called once per scenelighting - used for prepping the * event system for TGE style scenelighting */ void SceneLighting::sgTGEPassSetupEvent() { Con::printf(" Starting TGE based scene lighting..."); Canvas->paint(); sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); } /* * Called once per light - used for calling preLight on all objects * Only TGE lights call prelight and continue on to the process event */ void SceneLighting::sgTGELightStartEvent(U32 light) { // catch bad light index and jump to complete event if(light >= mLights.size()) { sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightCompleteEventType); return; } // can we use the light? if(mLights[light]->mType != LightInfo::Vector) { sgNewEvent((light+1), 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); return; } // process pre-lighting Con::printf(" Lighting with light #%d (TGE vector light)...", (light+1)); LightInfo *lightobj = mLights[light]; mLitObjects.clear(); for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) { // is there an object? if(!(*proxyItr)->getObject()) { AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); continue; } // if prelight works add to the list if(dynamic_cast(*proxyItr)) { if((*proxyItr)->preLight(lightobj)) mLitObjects.push_back(*proxyItr); } else if(dynamic_cast(*proxyItr)) { // need this for interior shadows on the terrain... // but these cannot preLight... mLitObjects.push_back(*proxyItr); } } // kick off lighting Canvas->paint(); sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightProcessEventType); } /* * Called once for each TGE light and object - used for calling light on an object */ void SceneLighting::sgTGELightProcessEvent(U32 light, S32 object) { // catch bad light or object index if((light >= mLights.size()) || (object >= mLitObjects.size())) { sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightCompleteEventType); return; } // light object InteriorInstance *interior = dynamic_cast(mLitObjects[object]->getObject()); if((interior) && (interior->mInteriorFileName)) Con::printf(" Lighting interior object %d of %d (%s)...", (object+1), mLitObjects.size(), interior->mInteriorFileName); else Con::printf(" Lighting object %d of %d...", (object+1), mLitObjects.size()); Canvas->paint(); //process object and light S32 time = Platform::getRealMilliseconds(); if(dynamic_cast(mLitObjects[object])) mLitObjects[object]->light(mLights[light]); sgTGESetProgress(light, object); Con::printf(" Object lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-time)/1000.f); // kick off next object event Canvas->paint(); sgNewEvent(light, (object+1), sgSceneLightingProcessEvent::sgTGELightProcessEventType); } /* * Called once per TGE light - used for calling postLight on all objects */ void SceneLighting::sgTGELightCompleteEvent(U32 light) { // catch bad light index and move to the next pass event if(light >= mLights.size()) { sgTGESetProgress(mLights.size(), mLitObjects.size()); Con::printf(" TGE based scene lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp2)/1000.f); Canvas->paint(); sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGPassSetupEventType); //sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgLightingCompleteEventType); return; } // process post-lighting // don't do this, SG lighting events will copy terrain light map... /*bool islast = (light == (mLights.size() - 1)); for(U32 o=0; o(mLitObjects[o])) mLitObjects[o]->postLight(islast); }*/ // kick off next light event Canvas->paint(); sgNewEvent((light+1), 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); } void SceneLighting::sgTGESetProgress(U32 light, S32 object) { // TGE is light based... F32 val = (light * mLitObjects.size()) + object; F32 total = mLights.size() * mLitObjects.size(); if(total == 0.0f) return; val = getMin(val, total); // two passes... total *= 2.0f; gLightingProgress = val / total; } //----------------------------------------------- /* * Called once per scenelighting - used for prepping the * event system for SG style scenelighting */ void SceneLighting::sgSGPassSetupEvent() { Con::printf(" Starting Synapse Gaming Lighting Pack scene lighting..."); mLitObjects.clear(); for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) { // is there an object? if(!(*proxyItr)->getObject()) { AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); continue; } // add all lights mLitObjects.push_back(*proxyItr); } // stats... sgStatistics::sgClear(); sgStatistics::sgInteriorObjectCount += mLitObjects.size(); Canvas->paint(); sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGObjectStartEventType); } /* * Called once per object - used for calling preLight on all SG lights */ void SceneLighting::sgSGObjectStartEvent(S32 object) { // catch bad light index and jump to complete event if(object >= mLitObjects.size()) { sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectCompleteEventType); return; } // process pre-lighting InteriorInstance *interior = dynamic_cast(mLitObjects[object]->getObject()); if((interior) && (interior->mInteriorFileName)) Con::printf(" Lighting interior object %d of %d (%s)...", (object+1), mLitObjects.size(), interior->mInteriorFileName); else Con::printf(" Lighting object %d of %d...", (object+1), mLitObjects.size()); ObjectProxy *obj = mLitObjects[object]; for(U32 i=0; imType == LightInfo::SGStaticPoint) || (lightobj->mType == LightInfo::SGStaticSpot)) obj->preLight(lightobj); } sgTimeTemp = Platform::getRealMilliseconds(); // kick off lighting Canvas->paint(); // this is slow with multiple objects... //sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectProcessEventType); // jump right to the method... sgSGObjectProcessEvent(0, object); } /* * Called once per object and SG light - used for calling light on an object */ void SceneLighting::sgSGObjectProcessEvent(U32 light, S32 object) { // catch bad light or object index if((light >= mLights.size()) || (object >= mLitObjects.size())) { // this is slow with multiple objects... //sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectCompleteEventType); // jump right to the method... sgSGObjectCompleteEvent(object); return; } // avoid the event overhead... // 80 lights == 0.6 seconds an interior without ANY lighting (events only)... U32 time = Platform::getRealMilliseconds(); while((light < mLights.size()) && ((Platform::getRealMilliseconds() - time) < 500)) { // can we use the light? LightInfo *lightobj = mLights[light]; if(!((lightobj->mType == LightInfo::Vector) && (dynamic_cast(mLitObjects[object])))) { // light part of the object mLitObjects[object]->light(lightobj); } sgSGSetProgress(light, object); light++; } light--; // kick off next light event Canvas->paint(); sgNewEvent((light+1), object, sgSceneLightingProcessEvent::sgSGObjectProcessEventType); } /* * Called once per object - used for calling postLight on all SG lights */ void SceneLighting::sgSGObjectCompleteEvent(S32 object) { // catch bad light index and move to the next pass event if(object >= mLitObjects.size()) { sgSGSetProgress(mLights.size(), mLitObjects.size()); Con::printf(" Synapse Gaming Lighting Pack scene lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp2)/1000.f); // stats... sgStatistics::sgPrint(); Canvas->paint(); sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgLightingCompleteEventType); return; } // process post-lighting Con::printf(" Object lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp)/1000.f); // only the last light does something mLitObjects[object]->postLight(true); /* InteriorInstance *interiorinst = dynamic_cast(mLitObjects[object]->getObject()); if(interiorinst) { Interior *detail = interiorinst->getDetailLevel(0); for(U32 i=0; imNormalLMapIndices.size(); i++) { GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interiorinst->getLMHandle(), detail->getNormalLMapIndex(i)); GBitmap *normLightmap = normHandle->getBitmap(); FileStream output; output.open(avar("lightmaps/lm_%d_%d.png", object, i), FileStream::Write); normLightmap->writePNG(output); } } */ /*ObjectProxy *obj = mLitObjects[object]; for(U32 i=0; imType == LightInfo::SGStaticPoint) || (lightobj->mType == LightInfo::SGStaticSpot)) obj->postLight((i == (mLights.size() - 1))); }*/ // kick off next light event Canvas->paint(); // this is slow with multiple objects... //sgNewEvent(0, (object+1), sgSceneLightingProcessEvent::sgSGObjectStartEventType); // jump right to the method... sgSGObjectStartEvent((object+1)); } void SceneLighting::sgSGSetProgress(U32 light, S32 object) { // SG is object based... F32 val = (object * mLights.size()) + light; F32 total = mLights.size() * mLitObjects.size(); if(total == 0.0f) return; val = getMin(val, total); // two passes... total *= 2.0f; gLightingProgress = (val / total) + 0.5; } //----------------------------------------------- void SceneLighting::processEvent(U32 light, S32 object) { sgNewEvent(light, object, sgSceneLightingProcessEvent::sgLightingStartEventType); } //----------------------------------------------- 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; } bool SceneLighting::light(BitSet32 flags) { if(!gClientSceneGraph) return(false); mStartTime = Platform::getRealMilliseconds(); // register static lights LightManager * lManager = gClientSceneGraph->getLightManager(); lManager->sgRegisterGlobalLights(true); // grab all the lights mLights.clear(); lManager->sgGetAllUnsortedLights(mLights); if(!mLights.size()) return(false); // get all the objects and create proxy's for them Vector objects; gClientContainer.findObjects(InteriorObjectType | TerrainObjectType | AtlasObjectType, sgFindObjectsCallback, &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(*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 if(LightManager::sgAllowFullLightMaps()) dSprintf(mFileName, sizeof(mFileName), "%s_%x.ml", misName, missionCRC); else dSprintf(mFileName, sizeof(mFileName), "%s_%x-raw.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 sgSceneLightingProcessEvent(0, -1, sgSceneLightingProcessEvent::sgLightingStartEventType), Sim::getTargetTime() + 1); return(true); } 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(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); } bool SceneLighting::isLighting() { return(bool(gLighting)); } bool SceneLighting::sgIsCorrectStaticObjectType(SceneObject *obj) { if((dynamic_cast(obj)) != NULL) return true; if((dynamic_cast(obj)) != NULL) return true; return false; } /// adds TSStatic objects as shadow casters. void SceneLighting::addStatic(void *terrainproxy, ShadowVolumeBSP *shadowVolume, SceneObject *sceneobject, LightInfo *light, S32 level) { SceneLighting::TerrainProxy *terrain = (SceneLighting::TerrainProxy *)terrainproxy; if((!terrain) || (!sceneobject)) return; if(light->mType != LightInfo::Vector) return; ConcretePolyList polylist; const Box3F box; const SphereF sphere; sceneobject->buildPolyList(&polylist, box, sphere); // retrieve the poly list (uses the collision mesh)... //sobj->sgAdvancedStaticOptionsData.sgBuildPolyList(sobj, &polylist); S32 i, count, vertind[3]; ConcretePolyList::Poly *poly; count = polylist.mPolyList.size(); // add the polys to the shadow volume... for(i=0; ivertexCount == 3), "Hmmm... vert count is greater than 3."); vertind[0] = polylist.mIndexList[poly->vertexStart]; vertind[1] = polylist.mIndexList[poly->vertexStart + 1]; vertind[2] = polylist.mIndexList[poly->vertexStart + 2]; if(mDot(PlaneF(polylist.mVertexList[vertind[0]], polylist.mVertexList[vertind[1]], polylist.mVertexList[vertind[2]]), light->mDirection) < gParellelVectorThresh) { ShadowVolumeBSP::SVPoly *svpoly = shadowVolume->createPoly(); svpoly->mWindingCount = 3; svpoly->mWinding[0].set(polylist.mVertexList[vertind[0]]); svpoly->mWinding[1].set(polylist.mVertexList[vertind[1]]); svpoly->mWinding[2].set(polylist.mVertexList[vertind[2]]); svpoly->mPlane = PlaneF(svpoly->mWinding[0], svpoly->mWinding[1], svpoly->mWinding[2]); svpoly->mPlane.neg(); shadowVolume->buildPolyVolume(svpoly, light); shadowVolume->insertPoly(svpoly); } } } //------------------------------------------------------------------------------ bool SceneLighting::verifyMissionInfo(PersistInfo::PersistChunk * chunk) { PersistInfo::MissionChunk * info = dynamic_cast(chunk); if(!info) return(false); PersistInfo::MissionChunk curInfo; if(!getMissionInfo(&curInfo)) return(false); return(curInfo.mChunkCRC == info->mChunkCRC); } bool SceneLighting::getMissionInfo(PersistInfo::PersistChunk * chunk) { PersistInfo::MissionChunk * info = dynamic_cast(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 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 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 PersistInfo::InteriorChunk); else if(isTerrain(mSceneObjects[i]->mObj)) persistInfo.mChunks.push_back(new PersistInfo::TerrainChunk); else if(isAtlas(mSceneObjects[i]->mObj)) continue; 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); } 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 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 == 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(); } } 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 crc; // grab the object crcs for(U32 i = 0; i < mSceneObjects.size(); i++) crc.push_back(mSceneObjects[i]->mChunkCRC); // grab the missions crc 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::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); }