//----------------------------------------------------------------------------- // V12 Engine // // Copyright (c) 2001 GarageGames.Com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- #include "interior/interiorInstance.h" #include "interior/lightUpdateGrouper.h" #include "interior/interior.h" #include "interior/interiorSubObject.h" #include "console/consoleTypes.h" #include "scenegraph/sceneGraph.h" #include "scenegraph/sceneState.h" #include "core/bitStream.h" #include "platformWIN32/platformGL.h" #include "dgl/dgl.h" #include "dgl/gBitmap.h" #include "math/mathIO.h" #include "dgl/materialList.h" #include "editor/editor.h" #include "interior/interiorResObjects.h" #include "game/trigger.h" #include "sim/simPath.h" #include "interior/forceField.h" #include "dgl/materialList.h" #include "scenegraph/lightManager.h" #include "collision/convex.h" #include "audio/audioDataBlock.h" #include "sim/frameAllocator.h" #include "sim/netConnection.h" #include "platform/profiler.h" //-------------------------------------------------------------------------- //-------------------------------------- Local classes, data, and functions // namespace { const U32 csgMaxZoneSize = 256; bool sgScopeBoolArray[256]; class InteriorRenderImage : public SceneRenderImage { public: InteriorRenderImage() : mDetailLevel(0) { } U32 mDetailLevel; U32 mBaseZone; }; #if defined(DEBUG) || defined (INTERNAL_RELEASE) void cInteriorSetRenderMode(SimObject*, S32, const char **argv) { S32 mode = dAtoi(argv[1]); if (mode < 0 || mode > Interior::ShowDetailLevel) mode = 0; Interior::smRenderMode = mode; } void cInteriorSetFocusedDebug(SimObject*, S32, const char** argv) { if (dAtob(argv[1])) { Interior::smFocusedDebug = true; } else { Interior::smFocusedDebug = false; } } #endif void cInteriorSetAlarmMode(SimObject* obj, S32, const char** argv) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); bool alarm; if (dStricmp(argv[2], "On") == 0) alarm = true; else alarm = false; InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } interior->setAlarmMode(alarm); } void cInteriorActivateLight(SimObject* obj, S32, const char** argv) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } const char* pLightName = argv[2]; interior->activateLight(pLightName); } void cInteriorDeactivateLight(SimObject* obj, S32, const char** argv) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } const char* pLightName = argv[2]; interior->deactivateLight(pLightName); } void cInteriorEchoTriggerableLights(SimObject* obj, S32, const char**) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } interior->echoTriggerableLights(); } void cInteriorAddChildren(SimObject* obj, S32, const char**) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } interior->addChildren(); } void cInteriorSetSkinBase(SimObject* obj, S32, const char** argv) { AssertFatal(dynamic_cast(obj) != NULL, "Error, how did a non-interior get here?"); InteriorInstance* interior = static_cast(obj); if (interior->isClientObject()) { Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); return; } interior->setSkinBase(argv[2]); } static bool cIsPointInside(SimObject *, S32 argc, const char ** argv) { static bool lastValue = false; if(!(argc == 2 || argc == 4)) { Con::errorf(ConsoleLogEntry::General, "cIsPointInside: invalid parameters"); return(lastValue); } Point3F pos; if(argc == 2) dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); else { pos.x = dAtof(argv[1]); pos.y = dAtof(argv[2]); pos.z = dAtof(argv[3]); } RayInfo collision; if(gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - 2000.f), InteriorObjectType, &collision)) { if(collision.face == -1) Con::errorf(ConsoleLogEntry::General, "cIsPointInside: failed to find hit face on interior"); else { InteriorInstance * interior = dynamic_cast(collision.object); if(interior) lastValue = !interior->getDetailLevel(0)->isSurfaceOutsideVisible(collision.face); else Con::errorf(ConsoleLogEntry::General, "cIsPointInside: invalid interior on collision"); } } return(lastValue); } static S32 cInteriorGetNumDetailLevels(SimObject * obj, S32, const char **) { InteriorInstance * instance = static_cast(obj); return(instance->getNumDetailLevels()); } static void cInteriorSetDetailLevel(SimObject * obj, S32, const char ** argv) { InteriorInstance * instance = static_cast(obj); if(instance->isServerObject()) { NetConnection * toServer = NetConnection::getServerConnection(); NetConnection * toClient = NetConnection::getLocalClientConnection(); if(!toClient || !toServer) return; S32 index = toClient->getGhostIndex(instance); if(index == -1) return; InteriorInstance * clientInstance = dynamic_cast(toServer->resolveGhost(index)); if(clientInstance) clientInstance->setDetailLevel(dAtoi(argv[2])); } else instance->setDetailLevel(dAtoi(argv[2])); } } // namespace {} //-------------------------------------------------------------------------- //-------------------------------------- Static functions // IMPLEMENT_CO_NETOBJECT_V1(InteriorInstance); //------------------------------------------------------------------------------ //-------------------------------------- InteriorInstance // bool InteriorInstance::smDontRestrictOutside = false; bool InteriorInstance::smRenderDynamicLights = true; U32 InteriorInstance::smLightUpdatePeriod = 66; // 66 ms between updates F32 InteriorInstance::smDetailModification = 1.0f; InteriorInstance::InteriorInstance() { mAlarmState = false; mInteriorFileName = NULL; mTypeMask = InteriorObjectType | StaticObjectType | StaticRenderedObjectType; mNetFlags.set(Ghostable | ScopeAlways); mUpdateGrouper = NULL; mShowTerrainInside = false; mSkinBase = StringTable->insert("base"); mAudioProfile = 0; mAudioEnvironment = 0; mForcedDetailLevel = -1; mConvexList = new Convex; mCRC = 0; } InteriorInstance::~InteriorInstance() { U32 i; for (i = 0; i < mLightInfo.size(); i++) destructInPlace(&mLightInfo[i]); for (i = 0; i < mInteriorSubObjects.size(); i++) { // DMM: Fix for first-class objects... for (U32 j = 0; j < mInteriorSubObjects[i].size(); j++) delete (mInteriorSubObjects[i])[j]; destructInPlace(&mInteriorSubObjects[i]); } delete mUpdateGrouper; delete mConvexList; mConvexList = NULL; for (i = 0; i < mVertexColorsNormal.size(); i++) { delete mVertexColorsNormal[i]; mVertexColorsNormal[i] = NULL; } for (i = 0; i < mVertexColorsAlarm.size(); i++) { delete mVertexColorsAlarm[i]; mVertexColorsAlarm[i] = NULL; } for (i = 0; i < mMaterialMaps.size(); i++) { delete mMaterialMaps[i]; mMaterialMaps[i] = NULL; } } void InteriorInstance::init() { // Does nothing for the moment } void InteriorInstance::destroy() { // Also does nothing for the moment } //-------------------------------------------------------------------------- // Inspection static AudioProfile * saveAudioProfile = 0; static AudioEnvironment * saveAudioEnvironment = 0; void InteriorInstance::inspectPreApply() { saveAudioProfile = mAudioProfile; saveAudioEnvironment = mAudioEnvironment; } void InteriorInstance::inspectPostApply() { if((mAudioProfile != saveAudioProfile) || (mAudioEnvironment != saveAudioEnvironment)) setMaskBits(AudioMask); } //-------------------------------------------------------------------------- //-------------------------------------- Console functionality // void InteriorInstance::initPersistFields() { Parent::initPersistFields(); addField("interiorFile", TypeString, Offset(mInteriorFileName, InteriorInstance)); addField("showTerrainInside", TypeBool, Offset(mShowTerrainInside, InteriorInstance)); addField("audioProfile", TypeAudioProfilePtr, Offset(mAudioProfile, InteriorInstance)); addField("audioEnvironment", TypeAudioEnvironmentPtr, Offset(mAudioEnvironment, InteriorInstance)); } void InteriorInstance::consoleInit() { //-------------------------------------- Class level variables Con::addVariable("pref::Interior::LightUpdatePeriod", TypeS32, &smLightUpdatePeriod); Con::addVariable("pref::Interior::ShowEnvironmentMaps", TypeBool, &Interior::smRenderEnvironmentMaps); Con::addVariable("pref::Interior::DynamicLights", TypeBool, &smRenderDynamicLights); Con::addVariable("pref::Interior::VertexLighting", TypeBool, &Interior::smUseVertexLighting); Con::addVariable("pref::Interior::TexturedFog", TypeBool, &Interior::smUseTexturedFog); Con::addVariable("pref::Interior::lockArrays", TypeBool, &Interior::smLockArrays); Con::addVariable("pref::Interior::detailAdjust", TypeF32, &InteriorInstance::smDetailModification); // DEBUG ONLY!!! #ifdef DEBUG Con::addVariable("Interior::DontRestrictOutside", TypeBool, &smDontRestrictOutside); #endif //-------------------------------------- Class level commands #if defined(DEBUG) || defined (INTERNAL_RELEASE) Con::addCommand("setInteriorRenderMode", cInteriorSetRenderMode, "setInteriorRenderMode(modeNum)", 2, 2); Con::addCommand("setInteriorFocusedDebug", cInteriorSetFocusedDebug, "setInteriorFocusedDebug([\"true\" | \"false\")", 2, 2); #endif Con::addCommand("isPointInside", cIsPointInside, "isPointInside(point)", 2, 4); //-------------------------------------- Object level commands Con::addCommand("InteriorInstance", "magicButton", cInteriorAddChildren, "[InteriorObject].magicButton()", 2, 2); Con::addCommand("InteriorInstance", "setSkinBase", cInteriorSetSkinBase, "[InteriorObject].setSkinBase()", 3, 3); // Lighting Con::addCommand("InteriorInstance", "setAlarmMode", cInteriorSetAlarmMode, "[InteriorObject].setAlarmMode(\"On\"|\"Off\")", 3, 3); Con::addCommand("InteriorInstance", "activateLight", cInteriorActivateLight, "[InteriorObject].activateLight()", 3, 3); Con::addCommand("InteriorInstance", "deactivateLight", cInteriorDeactivateLight, "[InteriorObject].deactivateLight()", 3, 3); Con::addCommand("InteriorInstance", "echoTriggerableLights", cInteriorEchoTriggerableLights, "[InteriorObject].echoTriggerableLights()", 2, 2); Con::addCommand("InteriorInstance", "getNumDetailLevels", cInteriorGetNumDetailLevels, "[InteriorObject].getNumDetailLevels()", 2, 2); Con::addCommand("InteriorInstance", "setDetailLevel", cInteriorSetDetailLevel, "[InteriorObject].setDetailLeve(level)", 3, 3); } //-------------------------------------------------------------------------- void InteriorInstance::renewOverlays() { StringTableEntry baseName = dStricmp(mSkinBase, "base") == 0 ? "blnd" : mSkinBase; for (U32 i = 0; i < mMaterialMaps.size(); i++) { MaterialList* pMatList = mMaterialMaps[i]; for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++) { const char* pName = pMatList->mMaterialNames[j]; const U32 len = dStrlen(pName); if (len < 6) continue; const char* possible = pName + (len - 5); if (dStricmp(".blnd", possible) == 0) { char newName[256]; AssertFatal(len < 200, "InteriorInstance::renewOverlays: Error, len exceeds allowed name length"); dStrncpy(newName, pName, possible - pName); newName[possible - pName] = '\0'; dStrcat(newName, "."); dStrcat(newName, baseName); TextureHandle test = TextureHandle(newName, MeshTexture, false); if (test.getGLName() != 0) { pMatList->mMaterials[j] = test; } else { pMatList->mMaterials[j] = TextureHandle(pName, MeshTexture, false); } } } } } //-------------------------------------------------------------------------- void InteriorInstance::setSkinBase(const char* newBase) { if (dStricmp(mSkinBase, newBase) == 0) return; mSkinBase = StringTable->insert(newBase); if (isServerObject()) setMaskBits(SkinBaseMask); else renewOverlays(); } //-------------------------------------------------------------------------- // from resManager.cc extern U32 calculateCRC(void * buffer, S32 len, U32 crcVal ); bool InteriorInstance::onAdd() { U32 i; // Load resource char buffer[256]; dSprintf(buffer, sizeof(buffer), "interiors/%s", mInteriorFileName); mInteriorRes = ResourceManager->load(buffer, true); if (bool(mInteriorRes) == false) { Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mInteriorFileName); NetConnection::setLastError("Unable to load interior: %s", mInteriorFileName); return false; } if(isClientObject()) { if(mCRC != mInteriorRes.getCRC()) { NetConnection::setLastError("Local interior file '%s' does not match version on server.", mInteriorFileName); return false; } } else mCRC = mInteriorRes.getCRC(); if(!Parent::onAdd()) return false; mVertexColorsNormal.setSize(mInteriorRes->getNumDetailLevels()); mVertexColorsAlarm.setSize(mInteriorRes->getNumDetailLevels()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { mVertexColorsNormal[i] = new Vector; mVertexColorsAlarm[i] = new Vector; VECTOR_SET_ASSOCIATION((*mVertexColorsNormal[i])); VECTOR_SET_ASSOCIATION((*mVertexColorsAlarm[i])); } // Ok, everything's groovy! Let's cache our hashed filename for renderimage sorting... mInteriorFileHash = _StringTable::hashString(mInteriorFileName); // Setup bounding information mObjBox = mInteriorRes->getDetailLevel(0)->getBoundingBox(); resetWorldBox(); setRenderTransform(mObjToWorld); // Setup mLightInfo structure // mLightInfo.setSize(mInteriorRes->getNumDetailLevels()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { Interior* pInterior = mInteriorRes->getDetailLevel(i); constructInPlace(&mLightInfo[i]); LightInfo& rInfo = mLightInfo[i]; rInfo.mSurfaceInvalid.setSize(pInterior->mSurfaces.size()); rInfo.mSurfaceInvalid.clear(); rInfo.mStateDataInfo.setSize(pInterior->mNumLightStateEntries); dMemset(rInfo.mStateDataInfo.address(), 0x00, sizeof(LightInfo::StateDataInfo) * rInfo.mStateDataInfo.size()); rInfo.mLights.setSize(pInterior->mAnimatedLights.size()); for (U32 j = 0; j < rInfo.mLights.size(); j++) { LightInfo::Light& rLight = rInfo.mLights[j]; rLight.curState = 0; rLight.curTime = 0; Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[j].stateIndex]; rLight.curColor.set(rState.red, rState.green, rState.blue); rLight.active = false; rLight.alarm = (pInterior->mAnimatedLights[j].flags & Interior::AlarmLight) != 0; installLight(i, j); } } // Setup lightgrouper mUpdateGrouper = new LightUpdateGrouper(LightUpdateBitStart, LightUpdateBitEnd); for (U32 d = 0; d < mInteriorRes->getNumDetailLevels(); d++) { Interior* pInterior = mInteriorRes->getDetailLevel(d); for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) mUpdateGrouper->addKey(makeUpdateKey(d, j)); } if (isClientObject()) { // create all the subObjects mInteriorSubObjects.setSize(mInteriorRes->getNumDetailLevels()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { constructInPlace(&mInteriorSubObjects[i]); Interior* pInterior = mInteriorRes->getDetailLevel(i); for (U32 j = 0; j < pInterior->mSubObjects.size(); j++) mInteriorSubObjects[i].push_back(pInterior->mSubObjects[j]->clone(this)); } } else { // creates all subobjects mInteriorSubObjects.setSize(mInteriorRes->getNumDetailLevels()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { constructInPlace(&mInteriorSubObjects[i]); Interior* pInterior = mInteriorRes->getDetailLevel(i); for (U32 j = 0; j < pInterior->mSubObjects.size(); j++) mInteriorSubObjects[i].push_back(pInterior->mSubObjects[j]->clone(this)); } } // Do any handle loading, etc. required. if (isClientObject()) { setLightUpdatedTime(Platform::getVirtualMilliseconds()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { Interior* pInterior = mInteriorRes->getDetailLevel(i); pInterior->prepForRendering(); gInteriorLMManager.addInstance(pInterior->getLMHandle(), mLMHandle, this); mMaterialMaps.push_back(new MaterialList(pInterior->mMaterialList)); // A client interior starts up it's ambient animations on add. Ambients // are just past the triggerables. for (U32 j = pInterior->mNumTriggerableLights; j < pInterior->mAnimatedLights.size(); j++) activateLight(i, j); } renewOverlays(); } else { } addToScene(); return true; } void InteriorInstance::onRemove() { mConvexList->nukeList(); // if(isClientObject()) { if (mLMHandle != 0xFFFFFFFF) { for(U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { Interior * pInterior = mInteriorRes->getDetailLevel(i); if (pInterior->getLMHandle() != 0xFFFFFFFF) gInteriorLMManager.removeInstance(pInterior->getLMHandle(), mLMHandle); } } } removeFromScene(); Parent::onRemove(); } //-------------------------------------------------------------------------- bool InteriorInstance::onSceneAdd(SceneGraph* pGraph) { AssertFatal(bool(mInteriorRes) == true, "Error, should not have been added to the scene if there's no interior!"); if (Parent::onSceneAdd(pGraph) == false) return false; if (mInteriorRes->getDetailLevel(0)->mZones.size() > 1) { AssertWarn(getNumCurrZones() == 1, "There should be one and only one zone for an interior that manages zones"); mSceneManager->registerZones(this, (mInteriorRes->getDetailLevel(0)->mZones.size() - 1)); } return true; } //-------------------------------------------------------------------------- void InteriorInstance::onSceneRemove() { AssertFatal(bool(mInteriorRes) == true, "Error, should not have been added to the scene if there's no interior!"); if (isManagingZones()) mSceneManager->unregisterZones(this); Parent::onSceneRemove(); } //-------------------------------------------------------------------------- bool InteriorInstance::getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones) { MatrixF xForm(true); Point3F invScale(1.0f / getScale().x, 1.0f / getScale().y, 1.0f / getScale().z); xForm.scale(invScale); xForm.mul(getWorldTransform()); xForm.mul(obj->getTransform()); xForm.scale(obj->getScale()); U32 waterMark = FrameAllocator::getWaterMark(); U16* zoneVector = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mZones.size() * sizeof(U16)); U32 numRetZones = 0; bool outsideToo = mInteriorRes->getDetailLevel(0)->scanZones(obj->getObjBox(), xForm, zoneVector, &numRetZones); if (numRetZones > SceneObject::MaxObjectZones) { Con::warnf(ConsoleLogEntry::General, "Too many zones returned for query on %s. Returning first %d", mInteriorFileName, SceneObject::MaxObjectZones); } for (U32 i = 0; i < getMin(numRetZones, U32(SceneObject::MaxObjectZones)); i++) zones[i] = zoneVector[i] + mZoneRangeStart - 1; *numZones = numRetZones; FrameAllocator::setWaterMark(waterMark); return outsideToo; } //-------------------------------------------------------------------------- U32 InteriorInstance::getPointZone(const Point3F& p) { AssertFatal(bool(mInteriorRes), "Error, no interior!"); Point3F osPoint = p; mWorldToObj.mulP(osPoint); osPoint.convolveInverse(mObjScale); S32 zone = mInteriorRes->getDetailLevel(0)->getZoneForPoint(osPoint); // If we're in solid (-1) or outside, we need to return 0 if (zone == -1 || zone == 0) return 0; return (zone-1) + mZoneRangeStart; } // does a hack check to determine how much a point is 'inside'.. should have // portals prebuilt with the transfer energy to each other portal in the zone // from the neighboring zone.. these values can be used to determine the factor // from within an individual zone.. also, each zone could be marked with // average material property for eax environment audio // ~0: outside -> 1: inside bool InteriorInstance::getPointInsideScale(const Point3F & pos, F32 * pScale) { AssertFatal(bool(mInteriorRes), "InteriorInstance::getPointInsideScale: no interior"); Interior * interior = mInteriorRes->getDetailLevel(0); Point3F p = pos; mWorldToObj.mulP(p); p.convolveInverse(mObjScale); U32 zoneIndex = interior->getZoneForPoint(p); if(zoneIndex == -1) // solid? { *pScale = 1.f; return(true); } else if(zoneIndex == 0) // outside? { *pScale = 0.f; return(true); } U32 waterMark = FrameAllocator::getWaterMark(); const Interior::Portal** portals = (const Interior::Portal**)FrameAllocator::alloc(256 * sizeof(const Interior::Portal*)); U32 numPortals = 0; Interior::Zone & zone = interior->mZones[zoneIndex]; U32 i; for(i = 0; i < zone.portalCount; i++) { const Interior::Portal & portal = interior->mPortals[interior->mZonePortalList[zone.portalStart + i]]; if(portal.zoneBack == 0 || portal.zoneFront == 0) { AssertFatal(numPortals < 256, "Error, overflow in temporary portal buffer!"); portals[numPortals++] = &portal; } } // inside? if(numPortals == 0) { *pScale = 1.f; FrameAllocator::setWaterMark(waterMark); return(true); } Point3F* portalCenters = (Point3F*)FrameAllocator::alloc(numPortals * sizeof(Point3F)); U32 numPortalCenters = 0; // scale using the distances to the portals in this zone... for(i = 0; i < numPortals; i++) { const Interior::Portal * portal = portals[i]; if(!portal->triFanCount) continue; Point3F center(0, 0, 0); for(U32 j = 0; j < portal->triFanCount; j++) { const Interior::TriFan & fan = interior->mWindingIndices[portal->triFanStart + j]; U32 numPoints = fan.windingCount; if(!numPoints) continue; for(U32 k = 0; k < numPoints; k++) { const Point3F & a = interior->mPoints[interior->mWindings[fan.windingStart + k]].point; center += a; } center /= numPoints; portalCenters[numPortalCenters++] = center; } } // 'magic' check here... F32 magic = Con::getFloatVariable("Interior::insideDistanceFalloff", 10.f); F32 val = 0.f; for(i = 0; i < numPortalCenters; i++) val += 1.f - mClampF(Point3F(portalCenters[i] - p).len() / magic, 0.f, 1.f); *pScale = 1.f - mClampF(val, 0.f, 1.f); FrameAllocator::setWaterMark(waterMark); return(true); } //-------------------------------------------------------------------------- ColorF gInteriorFogColor(1, 1, 1); void InteriorInstance::renderObject(SceneState* state, SceneRenderImage* sceneImage) { AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); if(gEditingMission && isHidden()) return; PROFILE_START(InteriorRenderObject); PROFILE_START(IRO_GetZones); U32 storedWaterMark = FrameAllocator::getWaterMark(); dglSetRenderPrimType(2); InteriorRenderImage* interiorImage = static_cast(sceneImage); Point3F osPoint = state->getCameraPosition(); mRenderWorldToObj.mulP(osPoint); osPoint.convolveInverse(mObjScale); // Get the object space y vector Point3F osCamVector; state->mModelview.getColumn(1, &osCamVector); mRenderWorldToObj.mulV(osCamVector); osCamVector.convolve(getScale()); osCamVector.normalize(); Point3F osZVec(0, 0, 1); mRenderWorldToObj.mulV(osZVec); osZVec.convolve(getScale()); // First, we want to test the planes and setup the fog... U32 zoneOffset = mZoneRangeStart != 0xFFFFFFFF ? mZoneRangeStart : 0; Interior* pInterior = mInteriorRes->getDetailLevel(interiorImage->mDetailLevel); U32 baseZone = 0xFFFFFFFF; if (getNumCurrZones() == 1) { baseZone = getCurrZone(0); } else { for (U32 i = 0; i < getNumCurrZones(); i++) { if (state->getZoneState(getCurrZone(i)).render == true) { if (baseZone == 0xFFFFFFFF) { baseZone = getCurrZone(i); break; } } } if (baseZone == 0xFFFFFFFF) baseZone = getCurrZone(0); } PROFILE_END(); PROFILE_START(IRO_ComputeActivePolys); Point3F worldOrigin; getRenderTransform().getColumn(3, &worldOrigin); ZoneVisDeterminer zoneVis; if (interiorImage->mDetailLevel == 0) { zoneVis.runFromState(state, zoneOffset, baseZone); pInterior->setupActivePolyList(zoneVis, state, osPoint, osCamVector, osZVec, worldOrigin.z, getScale()); } else { // Something else... pInterior->prepTempRender(state, getCurrZone(0), 0, mRenderObjToWorld, mObjScale, state->mFlipCull); zoneVis.runFromRects(state, zoneOffset, baseZone); pInterior->setupActivePolyList(zoneVis, state, osPoint, osCamVector, osZVec, worldOrigin.z, getScale()); } PROFILE_END(); PROFILE_START(IRO_UpdateAnimatedLights); // Update the animated lights... if (!Interior::smUseVertexLighting) { LightInfo& rLightInfo = mLightInfo[interiorImage->mDetailLevel]; downloadLightmaps(state, pInterior, rLightInfo); } PROFILE_END(); PROFILE_START(IRO_RenderSolids); // Set up the model view and the global render state... RectI viewport; dglGetViewport(&viewport); glMatrixMode(GL_PROJECTION); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); dglMultMatrix(&mRenderObjToWorld); glScalef(mObjScale.x, mObjScale.y, mObjScale.z); glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); // We need to decide if we're going to use the low-res textures Point3F camPoint = state->getCameraPosition(); Point3F closestPoint = getRenderWorldBox().getClosestPoint(camPoint); F32 dist = (camPoint - closestPoint).len(); if (dist != 0.0) { F32 length = dglProjectRadius(dist, 1.0f / pInterior->mAveTexGenLength); if (length < (1.0 / 16.0)) { TextureManager::setSmallTexturesActive(true); } } pInterior->setupFog(state); pInterior->setOSCamPosition(osPoint); if (interiorImage->mBaseZone != 0) state->setupZoneProjection(interiorImage->mBaseZone + mZoneRangeStart - 1); else state->setupObjectProjection(this); gInteriorFogColor = state->getFogColor(); pInterior->render(mAlarmState, mMaterialMaps[interiorImage->mDetailLevel], mLMHandle, mVertexColorsNormal[interiorImage->mDetailLevel], mVertexColorsAlarm[interiorImage->mDetailLevel]); pInterior->clearFog(); PROFILE_END(); PROFILE_START(IRO_RenderDynamicLights); if (smRenderDynamicLights == true) { S32 numLights = gClientSceneGraph->getLightManager()->getNumLights(); if (numLights > 0) { U32 lightWaterMark = FrameAllocator::getWaterMark(); ::LightInfo** lightArray = (::LightInfo**)FrameAllocator::alloc(sizeof(::LightInfo*) * numLights); gClientSceneGraph->getLightManager()->getLights(lightArray); for (U32 i = 0; i < numLights; i++) { if (lightArray[i]->mType != ::LightInfo::Point) continue; Point3F lightPoint = lightArray[i]->mPos; mRenderWorldToObj.mulP(lightPoint); lightPoint.convolveInverse(mObjScale); Box3F box; box.min = lightPoint; box.max = lightPoint; box.min -= Point3F(lightArray[i]->mRadius, lightArray[i]->mRadius, lightArray[i]->mRadius); box.max += Point3F(lightArray[i]->mRadius, lightArray[i]->mRadius, lightArray[i]->mRadius); // TODO: Account for scale... if (mObjBox.isOverlapped(box) == false) continue; // The number of light surfaces cannot exceed the total number of non-null surfaces in // interior... U32 subWaterMark = FrameAllocator::getWaterMark(); U32* lightSurfaces = (U32*)FrameAllocator::alloc(pInterior->mSurfaces.size() * sizeof(U32)); U32 numLightSurfaces = 0; if (pInterior->buildLightPolyList(lightSurfaces, &numLightSurfaces, box, mRenderWorldToObj, getScale()) == false) { FrameAllocator::setWaterMark(subWaterMark); continue; } pInterior->renderLights(lightArray[i], mRenderWorldToObj, getScale(), lightSurfaces, numLightSurfaces); FrameAllocator::setWaterMark(subWaterMark); } FrameAllocator::setWaterMark(lightWaterMark); } } PROFILE_END(); glDisable(GL_BLEND); if (dglDoesSupportARBMultitexture()) { glActiveTextureARB(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glActiveTextureARB(GL_TEXTURE0_ARB); glDisable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } else { glDisable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); } glMatrixMode(GL_PROJECTION); glPopMatrix(); dglSetViewport(viewport); glMatrixMode(GL_MODELVIEW); glPopMatrix(); AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); FrameAllocator::setWaterMark(storedWaterMark); dglSetRenderPrimType(0); // Reset the small textures... TextureManager::setSmallTexturesActive(false); PROFILE_END(); } //-------------------------------------------------------------------------- bool InteriorInstance::scopeObject(const Point3F& rootPosition, const F32 /*rootDistance*/, bool* zoneScopeState) { AssertFatal(isManagingZones(), "Error, should be a zone manager if we are called on to scope the scene!"); if (bool(mInteriorRes) == false) return false; Interior* pInterior = getDetailLevel(0); AssertFatal(pInterior->mZones.size() <= csgMaxZoneSize, "Error, too many zones! Increase max"); bool* pInteriorScopingState = sgScopeBoolArray; dMemset(pInteriorScopingState, 0, sizeof(bool) * pInterior->mZones.size()); // First, let's transform the point into the interior's space Point3F interiorRoot = rootPosition; getWorldTransform().mulP(interiorRoot); interiorRoot.convolveInverse(getScale()); S32 realStartZone = getPointZone(rootPosition); if (realStartZone != 0) realStartZone = realStartZone - mZoneRangeStart + 1; bool continueOut = pInterior->scopeZones(realStartZone, interiorRoot, pInteriorScopingState); // Copy pInteriorScopingState to zoneScopeState for (S32 i = 1; i < pInterior->mZones.size(); i++) zoneScopeState[i + mZoneRangeStart - 1] = pInteriorScopingState[i]; return continueOut; } //-------------------------------------------------------------------------- U32 InteriorInstance::calcDetailLevel(SceneState* state, const Point3F& wsPoint) { AssertFatal(bool(mInteriorRes), "Error, should not try to calculate the deatil level without a resource to work with!"); AssertFatal(getNumCurrZones() > 0, "Error, must belong to a zone for this to work"); if (smDetailModification < 0.3) smDetailModification = 0.3; if (smDetailModification > 1.0) smDetailModification = 1.0; // Early out for simple interiors if (mInteriorRes->getNumDetailLevels() == 1) return 0; if((mForcedDetailLevel >= 0) && (mForcedDetailLevel < mInteriorRes->getNumDetailLevels())) return(mForcedDetailLevel); Point3F osPoint = wsPoint; mRenderWorldToObj.mulP(osPoint); osPoint.convolveInverse(mObjScale); // First, see if the point is in the object space bounding box of the highest detail // If it is, then the detail level is zero. if (mObjBox.isContained(osPoint)) return 0; // Otherwise, we're going to have to do some ugly trickery to get the projection. // I've stolen the worldToScreenScale from dglMatrix, we'll have to calculate the // projection of the bounding sphere of the lowest detail level. // worldToScreenScale = (near * view.extent.x) / (right - left) RectI viewport; F64 frustum[4] = { 1e10, -1e10, 1e10, -1e10 }; bool init = false; SceneObjectRef* pWalk = mZoneRefHead; AssertFatal(pWalk != NULL, "Error, object must exist in at least one zone to call this!"); while (pWalk) { const SceneState::ZoneState& rState = state->getZoneState(pWalk->zone); if (rState.render == true) { // frustum if (rState.frustum[0] < frustum[0]) frustum[0] = rState.frustum[0]; if (rState.frustum[1] > frustum[1]) frustum[1] = rState.frustum[1]; if (rState.frustum[2] < frustum[2]) frustum[2] = rState.frustum[2]; if (rState.frustum[3] > frustum[3]) frustum[3] = rState.frustum[3]; // viewport if (init == false) viewport = rState.viewport; else viewport.unionRects(rState.viewport); init = true; } pWalk = pWalk->nextInObj; } AssertFatal(init, "Error, at least one zone must be rendered here!"); F32 worldToScreenScale = (state->getNearPlane() * viewport.extent.x) / (frustum[1] - frustum[0]); const SphereF& lowSphere = mInteriorRes->getDetailLevel(mInteriorRes->getNumDetailLevels() - 1)->mBoundingSphere; F32 dist = (lowSphere.center - osPoint).len(); F32 projRadius = (lowSphere.radius / dist) * worldToScreenScale; // Scale the projRadius based on the objects maximum scale axis projRadius *= getMax(mFabs(mObjScale.x), getMax(mFabs(mObjScale.y), mFabs(mObjScale.z))); // Multiply based on detail preference... projRadius *= smDetailModification; // Ok, now we have the projected radius, we need to search through the interiors to // find the largest interior that will support this projection. U32 final = mInteriorRes->getNumDetailLevels() - 1; for (U32 i = 0; i< mInteriorRes->getNumDetailLevels() - 1; i++) { Interior* pDetail = mInteriorRes->getDetailLevel(i); if (pDetail->mMinPixels < projRadius) { final = i; break; } } // Ok, that's it. return final; } //-------------------------------------------------------------------------- bool InteriorInstance::prepRenderImage(SceneState* state, const U32 stateKey, const U32 startZone, const bool modifyBaseState) { if (isLastState(state, stateKey)) return false; PROFILE_START(InteriorPrepRenderImage); setLastState(state, stateKey); U32 realStartZone; if (startZone != 0xFFFFFFFF) { AssertFatal(startZone != 0, "Hm. This really shouldn't happen. Should only get inside zones here"); AssertFatal(isManagingZones(), "Must be managing zones if we're here..."); realStartZone = startZone - mZoneRangeStart + 1; } else { realStartZone = getPointZone(state->getCameraPosition()); if (realStartZone != 0) realStartZone = realStartZone - mZoneRangeStart + 1; } bool render = true; if (modifyBaseState == false) { // Regular query. We only return a render zone if our parent zone is rendered. // Otherwise, we always render if (state->isObjectRendered(this) == false) { PROFILE_END(); return false; } } else { if (mShowTerrainInside == true) state->enableTerrainOverride(); } U32 detailLevel = 0; if (startZone == 0xFFFFFFFF) detailLevel = calcDetailLevel(state, state->getCameraPosition()); if (!Interior::smUseVertexLighting) { // Since we're rendering: update the lights and the alarm state U32 msSinceLightsUpdated = Platform::getVirtualMilliseconds() - getLightUpdatedTime(); if (msSinceLightsUpdated > smLightUpdatePeriod) { setLightUpdatedTime(Platform::getVirtualMilliseconds()); updateAllLights(msSinceLightsUpdated); } } U32 baseZoneForPrep = getCurrZone(0); bool multipleZones = false; if (getNumCurrZones() > 1) { U32 numRenderedZones = 0; baseZoneForPrep = 0xFFFFFFFF; for (U32 i = 0; i < getNumCurrZones(); i++) { if (state->getZoneState(getCurrZone(i)).render == true) { numRenderedZones++; if (baseZoneForPrep == 0xFFFFFFFF) baseZoneForPrep = getCurrZone(i); } } if (numRenderedZones > 1) multipleZones = true; } bool continueOut = mInteriorRes->getDetailLevel(0)->prepRender(state, baseZoneForPrep, realStartZone, mZoneRangeStart, mRenderObjToWorld, mObjScale, modifyBaseState & !smDontRestrictOutside, smDontRestrictOutside | multipleZones, state->mFlipCull); if (smDontRestrictOutside) continueOut = true; InteriorRenderImage* image = new InteriorRenderImage; image->obj = this; image->mDetailLevel = detailLevel; image->mBaseZone = realStartZone; image->textureSortKey = mInteriorFileHash; state->insertRenderImage(image); // Add renderimages for any sub-objects, first the independant subs, then the dependants // for this detail... Point3F osPoint = state->getCameraPosition(); mRenderWorldToObj.mulP(osPoint); osPoint.convolveInverse(mObjScale); U32 i; for (i = 0; i < mInteriorSubObjects[0].size(); i++) { InteriorSubObject* iso = (mInteriorSubObjects[0])[i]; if (iso->renderDetailDependant() == false) { // We want to check the zone that this object is in. If we are traversing upwards // though, we have no information about our parent zone, and must return the // render image regardless. if (modifyBaseState && iso->getZone() == 0) { // Must return } else { U32 realZone; if (iso->getZone() == 0) { realZone = getCurrZone(0); } else { realZone = iso->getZone() + mZoneRangeStart - 1; } if (state->getZoneState(realZone).render == false) { // Nuke it... continue; } } SubObjectRenderImage* sri = iso->getRenderImage(state, osPoint); if (sri) { sri->mDetailLevel = 0; state->insertRenderImage(sri); } } } for (i = 0; i < mInteriorSubObjects[detailLevel].size(); i++) { InteriorSubObject* iso = (mInteriorSubObjects[detailLevel])[i]; if (iso->renderDetailDependant() == true) { // We want to check the zone that this object is in. If we are traversing upwards // though, we have no information about our parent zone, and must return the // render image regardless. if (modifyBaseState && iso->getZone() == 0) { // Must return } else { U32 realZone; if (iso->getZone() == 0) { realZone = getCurrZone(0); } else { realZone = iso->getZone() + mZoneRangeStart - 1; } if (state->getZoneState(realZone).render == false) { // Nuke it... continue; } } SubObjectRenderImage* sri = iso->getRenderImage(state, osPoint); if (sri) { sri->mDetailLevel = detailLevel; state->insertRenderImage(sri); } } } PROFILE_END(); return continueOut; } //-------------------------------------------------------------------------- bool InteriorInstance::castRay(const Point3F& s, const Point3F& e, RayInfo* info) { info->object = this; if (mInteriorRes->getDetailLevel(0)->castRay(s, e, info)) { PlaneF fakePlane; fakePlane.x = info->normal.x; fakePlane.y = info->normal.y; fakePlane.z = info->normal.z; fakePlane.d = 0; PlaneF result; mTransformPlane(getTransform(), getScale(), fakePlane, &result); info->normal = result; return true; } return false; } //------------------------------------------------------------------------------ void InteriorInstance::setTransform(const MatrixF & mat) { Parent::setTransform(mat); // Since the interior is a static object, it's render transform changes 1 to 1 // with it's collision transform setRenderTransform(mat); // Notify subobjects that care about the transform change... // if (bool(mInteriorRes)) { for (U32 i = 0; i < mInteriorSubObjects.size(); i++) { for (U32 j = 0; j < mInteriorSubObjects[i].size(); j++) (mInteriorSubObjects[i])[j]->noteTransformChange(); } } if (isServerObject()) setMaskBits(TransformMask); } //------------------------------------------------------------------------------ bool InteriorInstance::buildPolyList(AbstractPolyList* list, const Box3F& wsBox, const SphereF&) { if (bool(mInteriorRes) == false) return false; // Setup collision state data list->setTransform(&getTransform(), getScale()); list->setObject(this); return mInteriorRes->getDetailLevel(0)->buildPolyList(list, wsBox, mWorldToObj, getScale()); } void InteriorInstance::buildConvex(const Box3F& box, Convex* convex) { if (bool(mInteriorRes) == false) return; mConvexList->collectGarbage(); Box3F realBox = box; mWorldToObj.mul(realBox); realBox.min.convolveInverse(mObjScale); realBox.max.convolveInverse(mObjScale); if (realBox.isOverlapped(getObjBox()) == false) return; U32 waterMark = FrameAllocator::getWaterMark(); if ((convex->getObject()->getType() & VehicleObjectType) && mInteriorRes->getDetailLevel(0)->mVehicleConvexHulls.size() > 0) { // Can never have more hulls than there are hulls in the interior... U16* hulls = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mVehicleConvexHulls.size() * sizeof(U16)); U32 numHulls = 0; Interior* pInterior = mInteriorRes->getDetailLevel(0); if (pInterior->getIntersectingVehicleHulls(realBox, hulls, &numHulls) == false) { FrameAllocator::setWaterMark(waterMark); return; } for (U32 i = 0; i < numHulls; i++) { // See if this hull exists in the working set already... Convex* cc = 0; CollisionWorkingList& wl = convex->getWorkingList(); for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { if (itr->mConvex->getType() == InteriorConvexType && (static_cast(itr->mConvex)->getObject() == this && static_cast(itr->mConvex)->hullId == -S32(hulls[i] + 1))) { cc = itr->mConvex; break; } } if (cc) continue; // Create a new convex. InteriorConvex* cp = new InteriorConvex; mConvexList->registerObject(cp); convex->addToWorkingList(cp); cp->mObject = this; cp->pInterior = pInterior; cp->hullId = -S32(hulls[i] + 1); cp->box.min.x = pInterior->mVehicleConvexHulls[hulls[i]].minX; cp->box.min.y = pInterior->mVehicleConvexHulls[hulls[i]].minY; cp->box.min.z = pInterior->mVehicleConvexHulls[hulls[i]].minZ; cp->box.max.x = pInterior->mVehicleConvexHulls[hulls[i]].maxX; cp->box.max.y = pInterior->mVehicleConvexHulls[hulls[i]].maxY; cp->box.max.z = pInterior->mVehicleConvexHulls[hulls[i]].maxZ; } } else { // Can never have more hulls than there are hulls in the interior... U16* hulls = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mConvexHulls.size() * sizeof(U16)); U32 numHulls = 0; Interior* pInterior = mInteriorRes->getDetailLevel(0); if (pInterior->getIntersectingHulls(realBox, hulls, &numHulls) == false) { FrameAllocator::setWaterMark(waterMark); return; } for (U32 i = 0; i < numHulls; i++) { // See if this hull exists in the working set already... Convex* cc = 0; CollisionWorkingList& wl = convex->getWorkingList(); for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { if (itr->mConvex->getType() == InteriorConvexType && (static_cast(itr->mConvex)->getObject() == this && static_cast(itr->mConvex)->hullId == hulls[i])) { cc = itr->mConvex; break; } } if (cc) continue; // Create a new convex. InteriorConvex* cp = new InteriorConvex; mConvexList->registerObject(cp); convex->addToWorkingList(cp); cp->mObject = this; cp->pInterior = pInterior; cp->hullId = hulls[i]; cp->box.min.x = pInterior->mConvexHulls[hulls[i]].minX; cp->box.min.y = pInterior->mConvexHulls[hulls[i]].minY; cp->box.min.z = pInterior->mConvexHulls[hulls[i]].minZ; cp->box.max.x = pInterior->mConvexHulls[hulls[i]].maxX; cp->box.max.y = pInterior->mConvexHulls[hulls[i]].maxY; cp->box.max.z = pInterior->mConvexHulls[hulls[i]].maxZ; } } FrameAllocator::setWaterMark(waterMark); } //------------------------------------------------------------------------------ U32 InteriorInstance::packUpdate(NetConnection* c, U32 mask, BitStream* stream) { U32 retMask = Parent::packUpdate(c, mask, stream); if (stream->writeFlag((mask & InitMask) != 0)) { // Initial update, write the whole kit and kaboodle stream->write(mCRC); stream->writeString(mInteriorFileName); stream->writeFlag(mShowTerrainInside); // Write the transform (do _not_ use writeAffineTransform. Since this is a static // object, the transform must be RIGHT THE *&)*$&^ ON or it will goof up the // syncronization between the client and the server. mathWrite(*stream, mObjToWorld); mathWrite(*stream, mObjScale); // Write the alarm state stream->writeFlag(mAlarmState); // Write the skinbase stream->writeString(mSkinBase); // audio profile if(stream->writeFlag(mAudioProfile)) stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); // audio environment: if(stream->writeFlag(mAudioEnvironment)) stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); } else { if (stream->writeFlag((mask & TransformMask) != 0)) { mathWrite(*stream, mObjToWorld); mathWrite(*stream, mObjScale); } stream->writeFlag(mAlarmState); // Check the lights to see if we need to update any of their states LightUpdateGrouper::BitIterator itr; for (itr = mUpdateGrouper->begin(); itr.valid() && itr.getNumKeys(); itr++) { if (stream->writeFlag((mask & itr.getMask()) != 0)) { LightUpdateGrouper::BitIterator::iterator kItr; for (kItr = itr.begin(); kItr != itr.end(); kItr++) { U32 key = *kItr; U32 detail = detailFromUpdateKey(key); U32 index = indexFromUpdateKey(key); stream->writeFlag(mLightInfo[detail].mLights[index].active); } } } if (stream->writeFlag(mask & SkinBaseMask)) stream->writeString(mSkinBase); // audio update: if(stream->writeFlag(mask & AudioMask)) { // profile: if(stream->writeFlag(mAudioProfile)) stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); // environment: if(stream->writeFlag(mAudioEnvironment)) stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); } } return retMask; } //------------------------------------------------------------------------------ void InteriorInstance::unpackUpdate(NetConnection* c, BitStream* stream) { Parent::unpackUpdate(c, stream); MatrixF temp; Point3F tempScale; if (stream->readFlag()) { // Initial Update // CRC stream->read(&mCRC); // File mInteriorFileName = stream->readSTString(); // Terrain flag mShowTerrainInside = stream->readFlag(); // Transform mathRead(*stream, &temp); mathRead(*stream, &tempScale); setScale(tempScale); setTransform(temp); // Alarm state: Note that we handle this ourselves on the initial update // so that the state is always full on or full off... mAlarmState = stream->readFlag(); mSkinBase = stream->readSTString(); // audio profile: if(stream->readFlag()) { U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); mAudioProfile = dynamic_cast(Sim::findObject(profileId)); } else mAudioProfile = 0; // audio environment: if(stream->readFlag()) { U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); mAudioEnvironment = dynamic_cast(Sim::findObject(profileId)); } else mAudioEnvironment = 0; } else { // Normal update if (stream->readFlag()) { mathRead(*stream, &temp); mathRead(*stream, &tempScale); setScale(tempScale); setTransform(temp); } setAlarmMode(stream->readFlag()); LightUpdateGrouper::BitIterator itr; for (itr = mUpdateGrouper->begin(); itr.valid() && itr.getNumKeys(); itr++) { if (stream->readFlag()) { LightUpdateGrouper::BitIterator::iterator kItr; for (kItr = itr.begin(); kItr != itr.end(); kItr++) { U32 key = *kItr; U32 detail = detailFromUpdateKey(key); U32 index = indexFromUpdateKey(key); if (stream->readFlag()) activateLight(detail, index); else deactivateLight(detail, index); } } } if (stream->readFlag()) { mSkinBase = stream->readSTString(); renewOverlays(); } // audio update: if(stream->readFlag()) { // profile: if(stream->readFlag()) { U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); mAudioProfile = dynamic_cast(Sim::findObject(profileId)); } else mAudioProfile = 0; // environment: if(stream->readFlag()) { U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); mAudioEnvironment = dynamic_cast(Sim::findObject(profileId)); } else mAudioEnvironment = 0; } } } //------------------------------------------------------------------------------ Interior* InteriorInstance::getDetailLevel(const U32 level) { return mInteriorRes->getDetailLevel(level); } U32 InteriorInstance::getNumDetailLevels() { return mInteriorRes->getNumDetailLevels(); } //-------------------------------------------------------------------------- //-------------------------------------- Alarm functionality // void InteriorInstance::setAlarmMode(const bool alarm) { if (mInteriorRes->getDetailLevel(0)->mHasAlarmState == false) return; if (mAlarmState == alarm) return; mAlarmState = alarm; if (isServerObject()) { setMaskBits(AlarmMask); } else { // DMMTODO: Invalidate current light state } } //-------------------------------------------------------------------------- void InteriorInstance::rebuildVertexColors() { U32 i; for (i = 0; i < mVertexColorsNormal.size(); i++) { delete mVertexColorsNormal[i]; mVertexColorsNormal[i] = NULL; } for (i = 0; i < mVertexColorsAlarm.size(); i++) { delete mVertexColorsAlarm[i]; mVertexColorsAlarm[i] = NULL; } if (bool(mInteriorRes) == false) return; mVertexColorsNormal.setSize(mInteriorRes->getNumDetailLevels()); mVertexColorsAlarm.setSize(mInteriorRes->getNumDetailLevels()); for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { mVertexColorsNormal[i] = new Vector; mVertexColorsAlarm[i] = new Vector; VECTOR_SET_ASSOCIATION((*mVertexColorsNormal[i])); VECTOR_SET_ASSOCIATION((*mVertexColorsAlarm[i])); } for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { Interior* pInterior = mInteriorRes->getDetailLevel(i); pInterior->rebuildVertexColors(mLMHandle, mVertexColorsNormal[i], mVertexColorsAlarm[i]); } } //-------------------------------------------------------------------------- void InteriorInstance::updateLightMap(Interior* pInterior, LightInfo& rLightInfo, const U32 surfaceIndex) { AssertFatal(surfaceIndex < pInterior->mSurfaces.size(), "Error, out of range surface index"); static U8 _newLightMapBuffer[256*256*3]; const Interior::Surface& rSurface = pInterior->mSurfaces[surfaceIndex]; if (rSurface.lightCount == 0) return; // Get the surface's original bitmap TextureHandle* originalLMapHandle = ((mAlarmState == false) ? gInteriorLMManager.getHandle(pInterior->getLMHandle(), mLMHandle, pInterior->mNormalLMapIndices[surfaceIndex]) : gInteriorLMManager.getHandle(pInterior->getLMHandle(), mLMHandle, pInterior->mAlarmLMapIndices[surfaceIndex])); const GBitmap* pOriginalLMap = originalLMapHandle->getBitmap(); AssertFatal(pOriginalLMap != NULL, "error, no lightmap on the handle!"); AssertFatal(pOriginalLMap->getFormat() == GBitmap::RGB, "error, bad lightmap format!"); // First, we need to create a buffer that will receive the new lightmap U32 dimX = rSurface.mapSizeX; U32 dimY = rSurface.mapSizeY; U8* pNewLightmap = _newLightMapBuffer; // copy the original lightmap const U8 * src = pOriginalLMap->getAddress(rSurface.mapOffsetX, rSurface.mapOffsetY); U8 * dest = pNewLightmap; U32 runSize = rSurface.mapSizeX * 3; U32 srcStep = pOriginalLMap->getWidth() * 3; for(U32 y = 0; y < rSurface.mapSizeY; y++) { dMemcpy(dest, src, runSize); dest += runSize; src += srcStep; } // ...now we have the original lightmap, add in the animateds... for (U32 i = 0; i < rSurface.lightCount; i++) { const LightInfo::StateDataInfo& rInfo = rLightInfo.mStateDataInfo[rSurface.lightStateInfoStart + i]; // Only add in states that affect this surface...duh. if (rInfo.curMap != NULL && rInfo.alarm == (mAlarmState != Normal)) { intensityMapMerge(pNewLightmap, dimX, dimY, rInfo.curMap, rInfo.curColor); } } // OK, now we have the final, current lightmap. subimage it in... glBindTexture(GL_TEXTURE_2D, originalLMapHandle->getGLName()); if (Con::getBoolVariable("$pref::OpenGL::disableSubImage", false)) { const U8 *src = pNewLightmap; U8 *dest = (U8 *) pOriginalLMap->getAddress(rSurface.mapOffsetX, rSurface.mapOffsetY); U32 destStep = pOriginalLMap->getWidth() * 3; // copy back into the original lightmap for (U32 y = 0; y < rSurface.mapSizeY; y++) { dMemcpy(dest, src, runSize); src += runSize; dest += destStep; } glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, pOriginalLMap->getWidth(), pOriginalLMap->getHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, pOriginalLMap->getBits(0)); } else glTexSubImage2D(GL_TEXTURE_2D, 0, rSurface.mapOffsetX, rSurface.mapOffsetY, rSurface.mapSizeX, rSurface.mapSizeY, GL_RGB, GL_UNSIGNED_BYTE, pNewLightmap); } //-------------------------------------------------------------------------- void InteriorInstance::downloadLightmaps(SceneState* /*state*/, Interior* pInterior, LightInfo& rLightInfo) { extern U16* sgActivePolyList; extern U32 sgActivePolyListSize; for (U32 i = 0; i < sgActivePolyListSize; i++) { if (rLightInfo.mSurfaceInvalid.test(sgActivePolyList[i]) == true) { updateLightMap(pInterior, rLightInfo, sgActivePolyList[i]); rLightInfo.mSurfaceInvalid.clear(sgActivePolyList[i]); } } } //-------------------------------------------------------------------------- void InteriorInstance::addChildren() { // if (bool(mInteriorRes) == false) // return; // // char nameBuffer[256]; // U32 i; // // // First thing to do, add a group with our name // SimGroup* myGroup = new SimGroup; // dSprintf(nameBuffer, 255, "%s_sg", getName()); // if (!myGroup->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register interior subgroup. MagicButton aborted!"); // return; // } // getGroup()->addObject(myGroup, nameBuffer); // // // Next, for each door in our resource, we'll be creating a group // for (i = 0; i < mInteriorRes->getNumInteriorPathFollowers(); i++) { // InteriorPathFollower* pSource = mInteriorRes->getInteriorPathFollower(i); // PathedInterior* child = new PathedInterior; // child->mName = pSource->mName; // child->mInteriorResIndex = pSource->mInteriorResIndex; // child->mPathIndex = pSource->mPathIndex; // child->mOffset = pSource->mOffset; // child->mInteriorResName = mInteriorFileName; // child->setField("dataBlock", "defaultPathFollow"); // // // Create a group for the door // SimGroup* doorGroup = new SimGroup; // dSprintf(nameBuffer, 255, "%s_g", child->mName); // if (!doorGroup->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register door subgroup. Door skipped!"); // delete child; // delete doorGroup; // continue; // } // myGroup->addObject(doorGroup, nameBuffer); // // // Ok, now we need to add the path and any triggers that affect the door here... // Path* pSimPath = new Path; // if (!pSimPath->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register door path. Path skipped!"); // delete child; // delete pSimPath; // continue; // } // doorGroup->addObject(pSimPath); // // InteriorPath* pPath = mInteriorRes->getPath(child->mPathIndex); // for (U32 j = 0; j < pPath->mNumWayPoints; j++) { // Marker* pMarker = new Marker; // pMarker->mSeqNum = j; // pMarker->mMSToNext = pPath->mWayPoints[j].msToNext; // // if (!pMarker->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register path marker. Marker skipped!"); // delete pMarker; // continue; // } // pSimPath->addObject(pMarker); // // Point3F markerPos = pPath->mWayPoints[j].pos; // markerPos.convolve(mObjScale); // getTransform().mulP(markerPos); // MatrixF xform(true); // xform.setColumn(3, markerPos); // pMarker->setTransform(xform); // } // pSimPath->finishPath(); // // // Now we need to add any triggers relevant to this door... // // // for (U32 k = 0; k < pSource->mTriggers.size(); k++) { // StringTableEntry triggerName = pSource->mTriggers[k]; // // for (U32 j = 0; j < mInteriorRes->getNumTriggers(); j++) { // InteriorResTrigger* pITrigger = mInteriorRes->getTrigger(j); // // if (dStricmp(pITrigger->mName, triggerName) == 0) { // // Add the object... // Trigger* pTrigger = new Trigger; // pTrigger->setField("dataBlock", "defaultTrigger"); // // if (!pTrigger->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register trigger. Trigger skipped!"); // delete pTrigger; // continue; // } // doorGroup->addObject(pTrigger, pITrigger->mName); // // MatrixF newXForm; // createTriggerTransform(pITrigger, &newXForm); // pTrigger->setTriggerPolyhedron(pITrigger->mPolyhedron); // pTrigger->setScale(mObjScale); // pTrigger->setTransform(newXForm); // } // } // } // // // And finally, add the door, taking care to get the transform right... // MatrixF childTransform(true); // Point3F childOffset = child->mOffset; // childOffset.convolve(mObjScale); // getTransform().mulP(childOffset); // childOffset.neg(); // childTransform.setColumn(3, childOffset); // childTransform.mul(getTransform()); // child->mBaseTransform = childTransform; // child->mBaseScale = mObjScale; // if (!child->registerObject()) { // Con::warnf(ConsoleLogEntry::General, "Warning, could not register door. Door skipped!"); // delete child; // continue; // } // doorGroup->addObject(child, child->mName); // } } void InteriorInstance::createTriggerTransform(const InteriorResTrigger* trigger, MatrixF* transform) { Point3F offset; MatrixF xform = getTransform(); xform.getColumn(3, &offset); Point3F triggerOffset = trigger->mOffset; triggerOffset.convolve(mObjScale); getTransform().mulV(triggerOffset); offset += triggerOffset; xform.setColumn(3, offset); *transform = xform; } bool InteriorInstance::readLightmaps(GBitmap**** lightmaps) { AssertFatal(bool(mInteriorRes), "Error, no interior loaded!"); AssertFatal(lightmaps, "Error, no lightmaps or numdetails result pointers"); AssertFatal(*lightmaps == NULL, "Error, already have a pointer in the lightmaps result field!"); // Load resource char buffer[256]; dSprintf(buffer, sizeof(buffer), "interiors/%s", mInteriorFileName); Stream* pStream = ResourceManager->openStream(buffer); if (pStream == NULL) { Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mInteriorFileName); return false; } InteriorResource* pResource = new InteriorResource; bool success = pResource->read(*pStream); ResourceManager->closeStream(pStream); if (success == false) { delete pResource; return false; } AssertFatal(pResource->getNumDetailLevels() == mInteriorRes->getNumDetailLevels(), "Mismatched detail levels!"); *lightmaps = new GBitmap**[mInteriorRes->getNumDetailLevels()]; for (U32 i = 0; i < pResource->getNumDetailLevels(); i++) { Interior* pInterior = pResource->getDetailLevel(i); (*lightmaps)[i] = new GBitmap*[pInterior->mLightmaps.size()]; for (U32 j = 0; j < pInterior->mLightmaps.size(); j++) { ((*lightmaps)[i])[j] = pInterior->mLightmaps[j]; pInterior->mLightmaps[j] = NULL; } pInterior->mLightmaps.clear(); } delete pResource; return true; }