tge/engine/interior/interiorLightAnim.cc
2017-04-17 06:17:10 -06:00

405 lines
16 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 "math/mRandom.h"
void InteriorInstance::echoTriggerableLights()
{
// DMMFIX: Only the first detail for now...
Interior* pInterior = mInteriorRes->getDetailLevel(0);
Con::printf("Interior: %s", mInteriorFileName);
Con::printf(" %d Triggerable lights:", pInterior->mNumTriggerableLights);
// Triggerable lights are always the first in the array...
for (U32 i = 0; i < pInterior->mNumTriggerableLights; i++) {
const char* pName = pInterior->getName(pInterior->mAnimatedLights[i].nameIndex);
U32 type = pInterior->mAnimatedLights[i].flags & Interior::AnimationTypeMask;
float duration = pInterior->mAnimatedLights[i].duration;
U32 numStates = pInterior->mAnimatedLights[i].stateCount;
Con::printf(" - %s [%s, Duration: %g, NumStates: %d]",
pName, Interior::getLightTypeString(Interior::LightType(type)),
duration, numStates);
}
}
void InteriorInstance::activateLight(const char* pLightName)
{
if (bool(mInteriorRes) == false) {
AssertWarn(false, "Activating a light on an unloaded interior!");
return;
}
// Now, it's a real pain in the ass to try to keep track of light states on detail
// changes as we did in tribes 1. There, we analyzed the state on a detail change
// and tried to duplicate that state on the detail level we were switching to.
// Inspiration: forget that, and just animate the lights on all the details all
// the time. Unless the detail is rendering, the lightmap data will never be
// downloaded, and the amount of time necessary to keep the lights updated on
// a detail level is absolutely miniscule. Much easier.
//
for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
Interior* pInterior = mInteriorRes->getDetailLevel(i);
for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) {
const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex);
if (dStricmp(pLightName, pILightName) == 0) {
activateLight(i, j);
break;
}
}
}
}
void InteriorInstance::deactivateLight(const char* pLightName)
{
if (bool(mInteriorRes) == false) {
AssertWarn(false, "Deactivating a light on an unloaded interior!");
return;
}
for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
Interior* pInterior = mInteriorRes->getDetailLevel(i);
for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) {
const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex);
if (dStricmp(pLightName, pILightName) == 0) {
deactivateLight(i, j);
break;
}
}
}
}
void InteriorInstance::updateAllLights(const U32 ms)
{
if (bool(mInteriorRes) == false)
return;
for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
LightInfo& rLightInfo = mLightInfo[i];
for (U32 j = 0; j < rLightInfo.mLights.size(); j++) {
if (mAlarmState == Normal) {
if (rLightInfo.mLights[j].active == true && rLightInfo.mLights[j].alarm == false)
updateLightTime(i, j, ms);
} else {
if (rLightInfo.mLights[j].alarm == true)
updateLightTime(i, j, ms);
}
}
}
}
//--------------------------------------------------------------------------
void InteriorInstance::activateLight(const U32 detail, const U32 lightIndex)
{
AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");
LightInfo& rLightInfo = mLightInfo[detail];
LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];
if (rLight.active == false) {
rLight.active = true;
rLight.curState = 0;
rLight.curTime = 0;
Interior* pInterior = mInteriorRes->getDetailLevel(detail);
Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex];
rLight.curColor.set(rState.red, rState.green, rState.blue);
installLight(detail, lightIndex);
if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) {
U32 key = makeUpdateKey(detail, lightIndex);
U32 mask = mUpdateGrouper->getKeyMask(key);
setMaskBits(mask);
}
} else {
// Light is already active, no need to play around further...
//
}
}
//--------------------------------------------------------------------------
void InteriorInstance::deactivateLight(const U32 detail, const U32 lightIndex)
{
AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");
LightInfo& rLightInfo = mLightInfo[detail];
LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];
if (rLight.active == true) {
// DMMFIX
rLight.active = false;
rLight.curState = 0;
rLight.curTime = 0;
Interior* pInterior = mInteriorRes->getDetailLevel(detail);
Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex];
rLight.curColor.set(rState.red, rState.green, rState.blue);
installLight(detail, lightIndex);
if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) {
U32 key = makeUpdateKey(detail, lightIndex);
U32 mask = mUpdateGrouper->getKeyMask(key);
setMaskBits(mask);
}
} else {
// Light is already inactive, no need to play around further...
//
}
}
//--------------------------------------------------------------------------
void InteriorInstance::updateLightTime(const U32 detail, const U32 lightIndex, const U32 ms)
{
AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");
LightInfo& rLightInfo = mLightInfo[detail];
Interior* pInterior = mInteriorRes->getDetailLevel(detail);
LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];
Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex];
U32 oldState = rLight.curState;
ColorI oldColor = rLight.curColor;
// Ok, now we need to break this down a bit. We pass the update along to
// the specialized updating functions based on lightType.
switch (rILight.flags & Interior::AnimationTypeMask) {
case Interior::AmbientLooping:
case Interior::TriggerableLoop:
updateLoopingLight(pInterior, rLight, lightIndex, ms);
break;
case Interior::AmbientFlicker:
case Interior::TriggerableFlicker:
updateFlickerLight(pInterior, rLight, lightIndex, ms);
break;
case Interior::TriggerableRamp:
updateRampLight(pInterior, rLight, lightIndex, ms);
break;
default:
AssertFatal(false, "Bad light type in updateLightTime");
}
if (rLight.curState != oldState ||
rLight.curColor != oldColor) {
// Need to reinstall the light
installLight(detail, lightIndex);
}
}
//--------------------------------------------------------------------------
void InteriorInstance::updateLoopingLight(Interior* interior, LightInfo::Light& light,
const U32 lightIndex, const U32 ms)
{
AssertISV( lightIndex < interior->mAnimatedLights.size( ), "out of bounds array access in InteriorInstance::updateLoopingLight" );
Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];
light.curTime += ms;
light.curTime %= rILight.duration;
// Find the last state that has a active time below this new time...
light.curState = 0;
for (U32 i = 1; i < rILight.stateCount; i++) {
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
if (rState.activeTime <= light.curTime)
light.curState = i;
else
break;
}
// interpolate the color
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
Interior::LightState* pNextState;
U32 msIntoState = light.curTime - rState.activeTime;
U32 msTotal;
if (light.curState != (rILight.stateCount - 1)) {
// Have one more good state
pNextState = &interior->mLightStates[rILight.stateIndex + light.curState + 1];
msTotal = pNextState->activeTime - rState.activeTime;
} else {
// Have to interpolate against the first state...
pNextState = &interior->mLightStates[rILight.stateIndex];
msTotal = rILight.duration - rState.activeTime;
}
F32 interp = F32(msIntoState) / F32(msTotal);
F32 red = F32(rState.red) * (1.0f - interp) + F32(pNextState->red) * interp;
F32 green = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp;
F32 blue = F32(rState.blue) * (1.0f - interp) + F32(pNextState->blue) * interp;
light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f));
}
//--------------------------------------------------------------------------
void InteriorInstance::updateFlickerLight(Interior* interior, LightInfo::Light& light,
const U32 lightIndex, const U32 ms)
{
Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];
U32 switchPeriod = interior->mLightStates[interior->mAnimatedLights[lightIndex].stateIndex + 1].activeTime;
U32 oldTime = light.curTime;
light.curTime += ms;
if (light.curTime < switchPeriod)
return;
light.curTime = 0;
// Ok, pick a random number from 0 to the light duration, and find the state that
// it falls in.
static MRandomLCG randomGen;
U32 pickedTime = randomGen.randI(0, rILight.duration);
light.curState = 0;
for (U32 i = 1; i < rILight.stateCount; i++) {
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
if (rState.activeTime <= pickedTime)
light.curState = i;
else
break;
}
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
light.curColor.set(rState.red, rState.green, rState.blue);
}
//--------------------------------------------------------------------------
void InteriorInstance::updateRampLight(Interior* interior, LightInfo::Light& light,
const U32 lightIndex, const U32 ms)
{
Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];
light.curTime += ms;
if (light.curTime > rILight.duration)
light.curTime = rILight.duration;
// Find the last state that has a active time below this new time...
light.curState = 0;
for (U32 i = 1; i < rILight.stateCount; i++) {
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
if (rState.activeTime <= light.curTime)
light.curState = i;
else
break;
}
// interpolate the color
Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
Interior::LightState* pNextState;
U32 msIntoState = light.curTime - rState.activeTime;
U32 msTotal;
if (light.curState != (rILight.stateCount - 1)) {
// Have one more good state
pNextState = &interior->mLightStates[rILight.stateIndex + light.curState + 1];
msTotal = pNextState->activeTime - rState.activeTime;
} else {
// A ramp light does NOT NOT NOT interp against the first state
pNextState = &rState;
msTotal = msIntoState;
}
F32 interp = F32(msIntoState) / F32(msTotal);
F32 red = F32(rState.red) * (1.0f - interp) + F32(pNextState->red) * interp;
F32 green = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp;
F32 blue = F32(rState.blue) * (1.0f - interp) + F32(pNextState->blue) * interp;
light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f));
}
//--------------------------------------------------------------------------
void InteriorInstance::installLight(const U32 detail, const U32 lightIndex)
{
AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");
LightInfo& rLightInfo = mLightInfo[detail];
LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];
// All we are allowed to assume is that the light time, color, and state are
// correct here. We must install all statedata, and invalidate all surfaces.
// First, let's retrieve the actual light from the Interior
//
Interior* pInterior = mInteriorRes->getDetailLevel(detail);
Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex];
Interior::LightState& rIState = pInterior->mLightStates[rILight.stateIndex + rLight.curState];
// Ok. Now, cycle through the light's state data, and install it
for (U32 i = rIState.dataIndex; i < (rIState.dataIndex + rIState.dataCount); i++) {
Interior::LightStateData& rIData = pInterior->mStateData[i];
LightInfo::StateDataInfo& rData = rLightInfo.mStateDataInfo[rIData.lightStateIndex];
if (rIData.mapIndex != 0xFFFFFFFF) {
rData.curMap = &pInterior->mStateDataBuffer[rIData.mapIndex];
} else {
rData.curMap = NULL;
}
rData.curColor = rLight.curColor;
rData.alarm = (rILight.flags & Interior::AlarmLight) != 0;
rLightInfo.mSurfaceInvalid.set(rIData.surfaceIndex);
}
}
//--------------------------------------------------------------------------
void InteriorInstance::intensityMapMerge(U8* lightMap,
const U32 width,
const U32 height,
const U8* intensityMap,
const ColorI& color)
{
// lightmap is a 24bit RGB texture, intensitymap is an 8 bit intensity
// map. We want lightmap = [lightmap + (intensityMap * color)]
// DMMFIX: SLOWSLOWSLOW! Need MMX version of this at the very least,
// this version is only for clarity;
for (U32 y = 0; y < height; y++) {
for (U32 x = 0; x < width; x++) {
U8* data = &lightMap[(y * width + x) * 3];
U32 intensity = intensityMap[(y * width + x)];
U32 newRed = data[0];
U32 newGreen = data[1];
U32 newBlue = data[2];
U32 addRed = (U32(color.red) * intensity + 0x80) >> 8;
U32 addGreen = (U32(color.green) * intensity + 0x80) >> 8;
U32 addBlue = (U32(color.blue) * intensity + 0x80) >> 8;
newRed += addRed;
newGreen += addGreen;
newBlue += addBlue;
data[0] = (newRed <= 255) ? U8(newRed) : 0xFF;
data[1] = (newGreen <= 255) ? U8(newGreen) : 0xFF;
data[2] = (newBlue <= 255) ? U8(newBlue) : 0xFF;
}
}
}