tge/engine/interior/interiorInstance.cc
2025-02-17 23:17:30 -06:00

2099 lines
68 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "interior/interiorInstance.h"
#include "interior/lightUpdateGrouper.h"
#include "interior/interior.h"
#include "interior/interiorSubObject.h"
#include "interior/pathedInterior.h"
#include "console/consoleTypes.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "core/bitStream.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 "core/frameAllocator.h"
#include "sim/netConnection.h"
#include "platform/profiler.h"
//--------------------------------------------------------------------------
//-------------------------------------- Local classes, data, and functions
//
const U32 csgMaxZoneSize = 256;
bool sgScopeBoolArray[256];
class InteriorRenderImage : public SceneRenderImage
{
public:
InteriorRenderImage() : mDetailLevel(0) { }
U32 mDetailLevel;
U32 mBaseZone;
};
ConsoleFunctionGroupBegin(Interiors, "");
#if defined(TORQUE_DEBUG) || defined (INTERNAL_RELEASE)
ConsoleFunction( setInteriorRenderMode, void, 2, 2, "(int modeNum)")
{
S32 mode = dAtoi(argv[1]);
if (mode < 0 || mode > Interior::ShowDetailLevel)
mode = 0;
Interior::smRenderMode = mode;
}
ConsoleFunction( setInteriorFocusedDebug, void, 2, 2, "(bool enable)")
{
if (dAtob(argv[1])) {
Interior::smFocusedDebug = true;
} else {
Interior::smFocusedDebug = false;
}
}
#endif
ConsoleFunction( isPointInside, bool, 2, 4, "(Point3F pos) or (float x, float y, float z)")
{
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], "%g %g %g", &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<InteriorInstance *>(collision.object);
if(interior)
lastValue = !interior->getDetailLevel(0)->isSurfaceOutsideVisible(collision.face);
else
Con::errorf(ConsoleLogEntry::General, "cIsPointInside: invalid interior on collision");
}
}
return(lastValue);
}
ConsoleFunctionGroupEnd(Interiors);
ConsoleMethod( InteriorInstance, setAlarmMode, void, 3, 3, "(string mode) Mode is 'On' or 'Off'")
{
bool alarm;
if (dStricmp(argv[2], "On") == 0)
alarm = true;
else
alarm = false;
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
object->setAlarmMode(alarm);
}
ConsoleMethod( InteriorInstance, activateLight, void, 3, 3, "(string lightName)")
{
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
const char* pLightName = argv[2];
object->activateLight(pLightName);
}
ConsoleMethod( InteriorInstance, deactivateLight, void, 3, 3, "(string lightName)")
{
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
const char* pLightName = argv[2];
object->deactivateLight(pLightName);
}
ConsoleMethod( InteriorInstance, echoTriggerableLights, void, 2, 2, "List lights usable with activateLight()/deactivateLight().")
{
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
object->echoTriggerableLights();
}
ConsoleMethod( InteriorInstance, magicButton, void, 2, 2, "@deprecated unimplemented")
{
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
object->addChildren();
}
ConsoleMethod( InteriorInstance, setSkinBase, void, 3, 3, "(string basename)")
{
if (object->isClientObject()) {
Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored");
return;
}
object->setSkinBase(argv[2]);
}
ConsoleMethod( InteriorInstance, getNumDetailLevels, S32, 2, 2, "")
{
return(object->getNumDetailLevels());
}
ConsoleMethod( InteriorInstance, setDetailLevel, void, 3, 3, "(int level)")
{
if(object->isServerObject())
{
NetConnection * toServer = NetConnection::getConnectionToServer();
NetConnection * toClient = NetConnection::getLocalClientConnection();
if(!toClient || !toServer)
return;
S32 index = toClient->getGhostIndex(object);
if(index == -1)
return;
InteriorInstance * clientInstance = dynamic_cast<InteriorInstance*>(toServer->resolveGhost(index));
if(clientInstance)
clientInstance->setDetailLevel(dAtoi(argv[2]));
}
else
object->setDetailLevel(dAtoi(argv[2]));
}
//--------------------------------------------------------------------------
//-------------------------------------- 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;
mDoSimpleDynamicRender = false;
mUseGLLighting = 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);
// Update the Transform on Editor Apply.
setMaskBits(TransformMask);
}
void InteriorInstance::onStaticModified( const char* slotName )
{
if( (!dStricmp( slotName, "position") || !dStricmp( slotName, "rotation") || !dStricmp( slotName, "scale")) && !mUseGLLighting )
Con::executef( 1, "onNeedRelight" );
if( !dStricmp( slotName, "useGLLighting" ))
setMaskBits(InitMask);
}
//--------------------------------------------------------------------------
//-------------------------------------- Console functionality
//
void InteriorInstance::initPersistFields()
{
Parent::initPersistFields();
addGroup("Media");
addField("interiorFile", TypeFilename, Offset(mInteriorFileName, InteriorInstance));
endGroup("Media");
addGroup("Audio");
addField("audioProfile", TypeAudioProfilePtr, Offset(mAudioProfile, InteriorInstance));
addField("audioEnvironment", TypeAudioEnvironmentPtr, Offset(mAudioEnvironment, InteriorInstance));
endGroup("Audio");
addGroup("Misc");
addField("useGLLighting", TypeBool, Offset(mUseGLLighting, InteriorInstance));
addField("showTerrainInside", TypeBool, Offset(mShowTerrainInside, InteriorInstance));
endGroup("Misc");
}
void InteriorInstance::consoleInit()
{
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 TORQUE_DEBUG
Con::addVariable("Interior::DontRestrictOutside", TypeBool, &smDontRestrictOutside);
#endif
}
//--------------------------------------------------------------------------
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
mInteriorRes = ResourceManager->load(mInteriorFileName, 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;
}
for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++)
{
// ok, if the material list load failed...
// if this is a local connection, we'll assume that's ok
// and just have white textures...
// otherwise we want to return false.
Interior* pInterior = mInteriorRes->getDetailLevel(i);
if(!pInterior->prepForRendering(mInteriorRes.getFilePath()) )
return false;
}
}
else
mCRC = mInteriorRes.getCRC();
if(!Parent::onAdd())
return false;
// Set up vertex lighting.
mVertexColorsNormal.setSize(mInteriorRes->getNumDetailLevels());
mVertexColorsAlarm.setSize(mInteriorRes->getNumDetailLevels());
for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++)
{
mVertexColorsNormal[i] = new Vector<ColorI>;
mVertexColorsAlarm[i] = new Vector<ColorI>;
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);
// Force the lightmap manager to download textures if we're
// running the mission editor. Normally they are only
// downloaded after the whole scene is lit.
gInteriorLMManager.addInstance(pInterior->getLMHandle(), mLMHandle, this);
if (gEditingMission)
{
gInteriorLMManager.useBaseTextures(pInterior->getLMHandle(), mLMHandle);
gInteriorLMManager.downloadGLTextures(pInterior->getLMHandle());
}
// Install material list
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
{
// WTF goes here? -- BJG
}
addToScene();
return true;
}
void InteriorInstance::onRemove()
{
// Kill our convexes.
mConvexList->nukeList();
// Clean up our lightmaps.
if(isClientObject())
{
if(bool(mInteriorRes) && 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(mInteriorRes, "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(mInteriorRes, "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(mInteriorRes, "Error, no interior!");
Point3F osPoint = p;
mWorldToObj.mulP(osPoint);
osPoint.convolveInverse(mObjScale);
const 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(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];
const 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<InteriorRenderImage*>(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);
if(mDoSimpleDynamicRender)
installLights();
// 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();
// make sure to build vertex color information in case interiors were
// added after the relight scene
if(&((*mVertexColorsNormal[interiorImage->mDetailLevel])[0]) == NULL)
rebuildVertexColors();
if(mDoSimpleDynamicRender)
{
pInterior->renderAsShape();
}
else
{
pInterior->render(mAlarmState, mMaterialMaps[interiorImage->mDetailLevel], mLMHandle,
mVertexColorsNormal[interiorImage->mDetailLevel],
mVertexColorsAlarm[interiorImage->mDetailLevel]);
}
pInterior->clearFog();
if(mDoSimpleDynamicRender)
uninstallLights();
PROFILE_END();
PROFILE_START(IRO_RenderDynamicLights);
// Do dynamic lights if appropriate.
if (smRenderDynamicLights == true)
{
// Are there any lights?
const S32 numLights = gClientSceneGraph->getLightManager()->getNumLights();
if (numLights > 0)
{
// Read out light info...
U32 lightWaterMark = FrameAllocator::getWaterMark();
::LightInfo** lightArray = (::LightInfo**)FrameAllocator::alloc(sizeof(::LightInfo*) * numLights);
gClientSceneGraph->getLightManager()->getLights(lightArray);
for (U32 i = 0; i < numLights; i++)
{
// Only consider point lights.
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(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, "InteriorInstance::calcDetailLevel - Error, at least one zone must be rendered here!");
const F32 worldToScreenScale = (state->getNearPlane() * viewport.extent.x) / (frustum[1] - frustum[0]);
const SphereF& lowSphere = mInteriorRes->getDetailLevel(mInteriorRes->getNumDetailLevels() - 1)->mBoundingSphere;
const 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;
}
}
// Register the render image, if any, for this ISO.
SubObjectRenderImage* sri = iso->getRenderImage(state, osPoint);
if (sri)
{
sri->mDetailLevel = 0;
state->insertRenderImage(sri);
}
}
}
// Condier any ISOs from the current LOD.
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;
return mInteriorRes->getDetailLevel(0)->castRay(s, e, info);
}
//------------------------------------------------------------------------------
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<InteriorConvex*>(itr->mConvex)->getObject() == this &&
static_cast<InteriorConvex*>(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<InteriorConvex*>(itr->mConvex)->getObject() == this &&
static_cast<InteriorConvex*>(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
// synchronization 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);
// Lighting behavior.
stream->writeFlag(mUseGLLighting);
}
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<AudioProfile*>(Sim::findObject(profileId));
}
else
mAudioProfile = 0;
// audio environment:
if(stream->readFlag())
{
U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
mAudioEnvironment = dynamic_cast<AudioEnvironment*>(Sim::findObject(profileId));
}
else
mAudioEnvironment = 0;
// Lighting behavior.
mDoSimpleDynamicRender = mUseGLLighting = stream->readFlag();
}
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<AudioProfile*>(Sim::findObject(profileId));
}
else
mAudioProfile = 0;
// environment:
if(stream->readFlag())
{
U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
mAudioEnvironment = dynamic_cast<AudioEnvironment*>(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<ColorI>;
mVertexColorsAlarm[i] = new Vector<ColorI>;
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 = getGroup();
// Load all the interior game objects...
for(i = 0; i < mInteriorRes->getNumGameEntities(); i++)
{
ItrGameEntity *ent = mInteriorRes->getGameEntity(i);
ConsoleObject *obj = ConsoleObject::create(ent->mGameClass);
GameBase *gb = dynamic_cast<GameBase*>(obj);
if(!gb)
{
Con::errorf("Invalid game class for entity: %s", ent->mGameClass);
delete obj;
continue;
}
gb->setField("dataBlock", ent->mDataBlock);
gb->setModStaticFields(true);
for(U32 j = 0; j < ent->mDictionary.size(); j++)
gb->setDataField(StringTable->insert(ent->mDictionary[j].name), NULL, ent->mDictionary[j].value);
gb->setModStaticFields(false);
Point3F origin = ent->mPos;
origin.convolve(mObjScale);
getTransform().mulP(origin);
MatrixF xform(true);
xform.setColumn(3, origin);
gb->setTransform(xform);
if(!gb->registerObject())
{
Con::errorf("Failed to register entity: %s: %s", ent->mGameClass, ent->mDataBlock);
delete gb;
continue;
}
myGroup->addObject(gb);
}
// 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);
StringTableEntry name = StringTable->insert(pSource->mName);
PathedInterior* child = new PathedInterior;
child->mName = name;
child->mInteriorResIndex = pSource->mInteriorResIndex;
child->mPathIndex = pSource->mPathIndex;
child->mOffset = pSource->mOffset;
child->mInteriorResName = mInteriorFileName;
child->setField("dataBlock", pSource->mDataBlock);
for(U32 i = 0; i < pSource->mDictionary.size(); i++)
child->setDataField(pSource->mDictionary[i].name, NULL, pSource->mDictionary[i].value);
// Create a group for the door
SimGroup* doorGroup = new SimGroup;
dSprintf(nameBuffer, 255, "%s_g", child->mName);
doorGroup->registerObject();
myGroup->addObject(doorGroup, nameBuffer);
// Ok, now we need to add the path and any triggers that affect the door here...
Path* pSimPath = new Path;
pSimPath->registerObject();
doorGroup->addObject(pSimPath);
for (U32 j = 0; j < pSource->mWayPoints.size(); j++)
{
Marker* pMarker = new Marker;
pMarker->mSeqNum = j;
pMarker->mMSToNext = pSource->mWayPoints[j].msToNext;
pMarker->mSmoothingType = pSource->mWayPoints[j].smoothingType;
pMarker->registerObject();
pSimPath->addObject(pMarker);
Point3F markerPos = pSource->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->mTriggerIds.size(); k++)
{
InteriorResTrigger *pITrigger = mInteriorRes->getTrigger(pSource->mTriggerIds[k]);
// Add the object...
Trigger* pTrigger = new Trigger;
pTrigger->setField("dataBlock", pITrigger->mDataBlock);
for(U32 i = 0; i < pITrigger->mDictionary.size(); i++)
pTrigger->setDataField(StringTable->insert(pITrigger->mDictionary[i].name), NULL, pITrigger->mDictionary[i].value);
MatrixF newXForm;
createTriggerTransform(pITrigger, &newXForm);
pTrigger->setTriggerPolyhedron(pITrigger->mPolyhedron);
pTrigger->setScale(mObjScale);
pTrigger->setTransform(newXForm);
pTrigger->registerObject();
doorGroup->addObject(pTrigger, pITrigger->mName);
}
// 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;
doorGroup->addObject(child, child->mName);
if (!child->registerObject())
{
Con::warnf(ConsoleLogEntry::General, "Warning, could not register door. Door skipped!");
delete child;
continue;
}
}
}
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(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
Stream* pStream = ResourceManager->openStream(mInteriorFileName);
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;
}