mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 19:54:46 +00:00
1966 lines
66 KiB
C++
1966 lines
66 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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<InteriorInstance*>(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<InteriorInstance*>(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<InteriorInstance*>(obj) != NULL,
|
|
"Error, how did a non-interior get here?");
|
|
|
|
InteriorInstance* interior = static_cast<InteriorInstance*>(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<InteriorInstance*>(obj) != NULL,
|
|
"Error, how did a non-interior get here?");
|
|
|
|
InteriorInstance* interior = static_cast<InteriorInstance*>(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<InteriorInstance*>(obj) != NULL,
|
|
"Error, how did a non-interior get here?");
|
|
|
|
InteriorInstance* interior = static_cast<InteriorInstance*>(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<InteriorInstance*>(obj) != NULL,
|
|
"Error, how did a non-interior get here?");
|
|
|
|
InteriorInstance* interior = static_cast<InteriorInstance*>(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<InteriorInstance*>(obj) != NULL,
|
|
"Error, how did a non-interior get here?");
|
|
|
|
InteriorInstance* interior = static_cast<InteriorInstance*>(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<InteriorInstance *>(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<InteriorInstance*>(obj);
|
|
return(instance->getNumDetailLevels());
|
|
}
|
|
|
|
static void cInteriorSetDetailLevel(SimObject * obj, S32, const char ** argv)
|
|
{
|
|
InteriorInstance * instance = static_cast<InteriorInstance*>(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<InteriorInstance*>(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(<basename>)", 3, 3);
|
|
|
|
// Lighting
|
|
Con::addCommand("InteriorInstance", "setAlarmMode", cInteriorSetAlarmMode, "[InteriorObject].setAlarmMode(\"On\"|\"Off\")", 3, 3);
|
|
Con::addCommand("InteriorInstance", "activateLight", cInteriorActivateLight, "[InteriorObject].activateLight(<LightName>)", 3, 3);
|
|
Con::addCommand("InteriorInstance", "deactivateLight", cInteriorDeactivateLight, "[InteriorObject].deactivateLight(<LightName>)", 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<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);
|
|
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<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);
|
|
|
|
// 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<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
|
|
// 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<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;
|
|
}
|
|
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 = 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;
|
|
}
|
|
|
|
|