|
|
|
/*==LICENSE==*
|
|
|
|
|
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools
|
|
|
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|
|
|
or by snail mail at:
|
|
|
|
Cyan Worlds, Inc.
|
|
|
|
14617 N Newport Hwy
|
|
|
|
Mead, WA 99021
|
|
|
|
|
|
|
|
*==LICENSE==*/
|
|
|
|
|
|
|
|
#include "HeadSpin.h"
|
|
|
|
#include "plDynaDecalMgr.h"
|
|
|
|
#include "plDynaDecal.h"
|
|
|
|
|
|
|
|
#include "plCutter.h"
|
|
|
|
|
|
|
|
#include "plAccessGeometry.h"
|
|
|
|
#include "plAccessSpan.h"
|
|
|
|
|
|
|
|
#include "plDrawableSpans.h"
|
|
|
|
#include "plAuxSpan.h"
|
|
|
|
#include "plSpaceTree.h"
|
|
|
|
|
|
|
|
#include "plPrintShape.h"
|
|
|
|
|
|
|
|
#include "plAvatar/plArmatureMod.h"
|
|
|
|
|
|
|
|
#include "plParticleSystem/plParticleSystem.h"
|
|
|
|
#include "plParticleSystem/plParticleEmitter.h"
|
|
|
|
|
|
|
|
#include "pnSceneObject/plSceneObject.h"
|
|
|
|
#include "pnSceneObject/plDrawInterface.h"
|
|
|
|
#include "plSurface/hsGMaterial.h"
|
|
|
|
#include "plSurface/plLayerInterface.h"
|
|
|
|
#include "plScene/plPageTreeMgr.h"
|
|
|
|
|
|
|
|
#include "plPipeline/plGBufferGroup.h"
|
|
|
|
#include "plPipeline/hsGDeviceRef.h"
|
|
|
|
|
|
|
|
#include "plMessage/plAgeLoadedMsg.h"
|
|
|
|
#include "plMessage/plDynaDecalEnableMsg.h"
|
|
|
|
#include "pnMessage/plRefMsg.h"
|
|
|
|
#include "pnMessage/plTimeMsg.h"
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
|
|
|
|
#include "pnEncryption/plRandom.h"
|
|
|
|
#include "hsFastMath.h"
|
|
|
|
|
|
|
|
#include "hsStream.h"
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
|
|
|
|
#include "pnMessage/plPipeResMakeMsg.h"
|
|
|
|
|
|
|
|
#include "plGImage/plBumpMapGen.h"
|
|
|
|
|
|
|
|
// Stuff for creating a bumpenv decal on demand.
|
|
|
|
#include "plGImage/plMipmap.h"
|
|
|
|
#include "plSurface/plLayer.h"
|
|
|
|
#include "plMessage/plLayRefMsg.h"
|
|
|
|
|
|
|
|
//### Hackage
|
|
|
|
#include "plMessage/plRenderMsg.h"
|
|
|
|
#include "plMessage/plListenerMsg.h"
|
|
|
|
#include "plPipeline.h"
|
|
|
|
|
|
|
|
#include "plTweak.h"
|
|
|
|
|
|
|
|
#include "plProfile.h"
|
|
|
|
|
|
|
|
plProfile_CreateTimerNoReset("Total", "DynaDecal", Total);
|
|
|
|
plProfile_CreateTimerNoReset("Cutter", "DynaDecal", Cutter);
|
|
|
|
plProfile_CreateTimerNoReset("Process", "DynaDecal", Process);
|
|
|
|
plProfile_CreateTimerNoReset("Callback", "DynaDecal", Callback);
|
|
|
|
|
|
|
|
static plRandom sRand;
|
|
|
|
static const int kBinBlockSize = 20;
|
|
|
|
static const uint16_t kDefMaxNumVerts = 1000;
|
|
|
|
static const uint16_t kDefMaxNumIdx = kDefMaxNumVerts;
|
|
|
|
|
|
|
|
static const float kDefLifeSpan = 30.f;
|
|
|
|
static const float kDefDecayStart = kDefLifeSpan * 0.5f;
|
|
|
|
static const float kDefRampEnd = kDefLifeSpan * 0.1f;
|
|
|
|
|
|
|
|
static const float kInitAuxSpans = 5;
|
|
|
|
|
|
|
|
#define MF_NO_INIT_ALLOC
|
|
|
|
#define MF_NEVER_RUN_OUT
|
|
|
|
|
|
|
|
// If we aren't doing an initial alloc, we MUST have NEVER_RUN_OUT
|
|
|
|
// It's also useful to not have initial alloc but do have NEVER_RUN_OUT
|
|
|
|
// So:
|
|
|
|
// MF_NO_INIT_ALLOC && MF_NEVER_RUN_OUT - Okay
|
|
|
|
// !MF_NO_INIT_ALLOC && !MF_NEVER_RUN_OUT - Okay
|
|
|
|
// !MF_NO_INIT_ALLOC && MF_NEVER_RUN_OUT - Okay
|
|
|
|
// MF_NO_INIT_ALLOC && !MF_NEVER_RUN_OUT - Bad (you'll never get any decals)
|
|
|
|
#if defined(MF_NO_INIT_ALLOC) && !defined(MF_NEVER_RUN_OUT)
|
|
|
|
#define MF_NEVER_RUN_OUT
|
|
|
|
#endif // defined(MF_NO_INIT_ALLOC) && !defined(MF_NEVER_RUN_OUT)
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::fDisableAccumulate = false;
|
|
|
|
bool plDynaDecalMgr::fDisableUpdate = false;
|
|
|
|
|
|
|
|
plDynaDecalMgr::plDynaDecalMgr()
|
|
|
|
:
|
|
|
|
fMatPreShade(nil),
|
|
|
|
fMatRTShade(nil),
|
|
|
|
fMaxNumVerts(kDefMaxNumVerts),
|
|
|
|
fMaxNumIdx(kDefMaxNumIdx),
|
|
|
|
fWetLength(0),
|
|
|
|
fRampEnd(kDefRampEnd),
|
|
|
|
fDecayStart(kDefDecayStart),
|
|
|
|
fLifeSpan(kDefLifeSpan),
|
|
|
|
fInitAtten(1.f),
|
|
|
|
fIntensity(1.f),
|
|
|
|
fWaitOnEnable(false),
|
|
|
|
fGridSizeU(2.5f),
|
|
|
|
fGridSizeV(2.5f),
|
|
|
|
fScale(1.f, 1.f, 1.f),
|
|
|
|
fPartyTime(1.f)
|
|
|
|
{
|
|
|
|
fCutter = new plCutter;
|
|
|
|
}
|
|
|
|
|
|
|
|
plDynaDecalMgr::~plDynaDecalMgr()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fDecals.GetCount(); i++ )
|
|
|
|
delete fDecals[i];
|
|
|
|
|
|
|
|
for( i = 0; i < fAuxSpans.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
if( fAuxSpans[i]->fDrawable )
|
|
|
|
{
|
|
|
|
plSpan* span = const_cast<plSpan*>(fAuxSpans[i]->fDrawable->GetSpan(fAuxSpans[i]->fBaseSpanIdx));
|
|
|
|
|
|
|
|
span->RemoveAuxSpan(fAuxSpans[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete fAuxSpans[i]->fGroup;
|
|
|
|
|
|
|
|
delete fAuxSpans[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
delete fCutter;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::SetKey(plKey k)
|
|
|
|
{
|
|
|
|
hsKeyedObject::SetKey(k);
|
|
|
|
if( k )
|
|
|
|
{
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plPipeGeoMakeMsg::Index(), GetKey());
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), GetKey());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::Read(hsStream* stream, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plSynchedObject::Read(stream, mgr);
|
|
|
|
|
|
|
|
mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatRTShade), plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
int n = stream->ReadLE32();
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < n; i++ )
|
|
|
|
{
|
|
|
|
mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefTarget), plRefFlags::kPassiveRef);
|
|
|
|
}
|
|
|
|
// Associated slave particle systems. We read in the scene objects now, and find the associated systems on loaded message.
|
|
|
|
n = stream->ReadLE32();
|
|
|
|
for( i = 0; i < n; i++ )
|
|
|
|
{
|
|
|
|
mgr->ReadKeyNotifyMe(stream, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefPartyObject), plRefFlags::kPassiveRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
fMaxNumVerts = (uint16_t)(stream->ReadLE32());
|
|
|
|
fMaxNumIdx = (uint16_t)(stream->ReadLE32());
|
|
|
|
|
|
|
|
fWaitOnEnable = stream->ReadLE32();
|
|
|
|
|
|
|
|
fIntensity = stream->ReadLEScalar();
|
|
|
|
fInitAtten = fIntensity;
|
|
|
|
|
|
|
|
fWetLength = stream->ReadLEScalar();
|
|
|
|
fRampEnd = stream->ReadLEScalar();
|
|
|
|
fDecayStart = stream->ReadLEScalar();
|
|
|
|
fLifeSpan = stream->ReadLEScalar();
|
|
|
|
|
|
|
|
fGridSizeU = stream->ReadLEScalar();
|
|
|
|
fGridSizeV = stream->ReadLEScalar();
|
|
|
|
|
|
|
|
fScale.Read(stream);
|
|
|
|
|
|
|
|
fPartyTime = stream->ReadLEScalar();
|
|
|
|
|
|
|
|
n = stream->ReadLE32();
|
|
|
|
fNotifies.SetCount(n);
|
|
|
|
for( i = 0; i < n; i++ )
|
|
|
|
fNotifies[i] = mgr->ReadKey(stream);
|
|
|
|
|
|
|
|
// If we need to be creating DynaDecalMgrs on the fly, this should go in the
|
|
|
|
// constructor, or we should call it explicitly on the DynaDecalMgr we create.
|
|
|
|
// But putting it here makes it automatic for normal scene loading, without
|
|
|
|
// popping up during the export conversion process.
|
|
|
|
#ifndef MF_NO_INIT_ALLOC
|
|
|
|
InitAuxSpans();
|
|
|
|
#endif // MF_NO_INIT_ALLOC
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
// ###Things that should be in derived classes follow.
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::Write(hsStream* stream, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plSynchedObject::Write(stream, mgr);
|
|
|
|
|
|
|
|
mgr->WriteKey(stream, fMatPreShade);
|
|
|
|
mgr->WriteKey(stream, fMatRTShade);
|
|
|
|
|
|
|
|
stream->WriteLE32(fTargets.GetCount());
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fTargets.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
mgr->WriteKey(stream, fTargets[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Particle systems (really their associated sceneobjects).
|
|
|
|
stream->WriteLE32(fPartyObjects.GetCount());
|
|
|
|
for( i = 0; i < fPartyObjects.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
mgr->WriteKey(stream, fPartyObjects[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
stream->WriteLE32(fMaxNumVerts);
|
|
|
|
stream->WriteLE32(fMaxNumIdx);
|
|
|
|
|
|
|
|
stream->WriteLE32(fWaitOnEnable);
|
|
|
|
|
|
|
|
stream->WriteLEScalar(fIntensity);
|
|
|
|
|
|
|
|
stream->WriteLEScalar(fWetLength);
|
|
|
|
stream->WriteLEScalar(fRampEnd);
|
|
|
|
stream->WriteLEScalar(fDecayStart);
|
|
|
|
stream->WriteLEScalar(fLifeSpan);
|
|
|
|
|
|
|
|
stream->WriteLEScalar(fGridSizeU);
|
|
|
|
stream->WriteLEScalar(fGridSizeV);
|
|
|
|
|
|
|
|
fScale.Write(stream);
|
|
|
|
|
|
|
|
stream->WriteLEScalar(fPartyTime);
|
|
|
|
|
|
|
|
stream->WriteLE32(fNotifies.GetCount());
|
|
|
|
for( i = 0; i < fNotifies.GetCount(); i++ )
|
|
|
|
mgr->WriteKey(stream, fNotifies[i]);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
// ###Things that should be in derived classes follow.
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IMakeAuxRefs(plPipeline* pipe)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fGroups.GetCount(); i++ )
|
|
|
|
fGroups[i]->PrepForRendering(pipe, false);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const plPrintShape* plDynaDecalMgr::IGetPrintShape(const plKey& objKey) const
|
|
|
|
{
|
|
|
|
const plPrintShape* shape = nil;
|
|
|
|
plSceneObject* part = plSceneObject::ConvertNoRef(objKey->ObjectIsLoaded());
|
|
|
|
if( part )
|
|
|
|
{
|
|
|
|
// This is a safe cast, because GetGenericInterface(type) will only return
|
|
|
|
// either a valid object of that type, or nil.
|
|
|
|
shape = static_cast<plPrintShape*>(part->GetGenericInterface(plPrintShape::Index()));
|
|
|
|
}
|
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
const plPrintShape* plDynaDecalMgr::IGetPrintShape(plArmatureMod* avMod, uint32_t id) const
|
|
|
|
{
|
|
|
|
const plPrintShape* shape = nil;
|
|
|
|
const plSceneObject* part = avMod->FindBone(id);
|
|
|
|
if( part )
|
|
|
|
{
|
|
|
|
// This is a safe cast, because GetGenericInterface(type) will only return
|
|
|
|
// either a valid object of that type, or nil.
|
|
|
|
shape = static_cast<plPrintShape*>(part->GetGenericInterface(plPrintShape::Index()));
|
|
|
|
}
|
|
|
|
return shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IHandleEnableMsg(const plDynaDecalEnableMsg* enaMsg)
|
|
|
|
{
|
|
|
|
IWetParts(enaMsg);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IWetParts(const plDynaDecalEnableMsg* enaMsg)
|
|
|
|
{
|
|
|
|
if( !enaMsg->IsArmature() )
|
|
|
|
{
|
|
|
|
const plPrintShape* shape = IGetPrintShape(enaMsg->GetShapeKey());
|
|
|
|
if( shape )
|
|
|
|
{
|
|
|
|
plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
|
|
|
|
IWetInfo(info, enaMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( enaMsg->GetID() == uint32_t(-1) )
|
|
|
|
{
|
|
|
|
plArmatureMod* avMod = plArmatureMod::ConvertNoRef(enaMsg->GetArmKey()->ObjectIsLoaded());
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fPartIDs.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
const plPrintShape* shape = IGetPrintShape(avMod, fPartIDs[i]);
|
|
|
|
if( shape )
|
|
|
|
{
|
|
|
|
plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
|
|
|
|
IWetInfo(info, enaMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IWetPart(enaMsg->GetID(), enaMsg);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IWetPart(uint32_t id, const plDynaDecalEnableMsg* enaMsg)
|
|
|
|
{
|
|
|
|
plArmatureMod* avMod = plArmatureMod::ConvertNoRef(enaMsg->GetArmKey()->ObjectIsLoaded());
|
|
|
|
|
|
|
|
const plPrintShape* shape = IGetPrintShape(avMod, id);
|
|
|
|
if( shape )
|
|
|
|
{
|
|
|
|
plDynaDecalInfo& info = IGetDecalInfo(uintptr_t(shape), shape->GetKey());
|
|
|
|
IWetInfo(info, enaMsg);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IWetInfo(plDynaDecalInfo& info, const plDynaDecalEnableMsg* enaMsg) const
|
|
|
|
{
|
|
|
|
info.fWetTime = enaMsg->GetContactTime();
|
|
|
|
info.fWetLength = enaMsg->GetWetLength();
|
|
|
|
if( !enaMsg->AtEnd() )
|
|
|
|
info.fFlags |= plDynaDecalInfo::kImmersed;
|
|
|
|
else
|
|
|
|
info.fFlags &= ~plDynaDecalInfo::kImmersed;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
// On eval pulse, update all our active decals, letting old ones die off.
|
|
|
|
plEvalMsg* eval = plEvalMsg::ConvertNoRef(msg);
|
|
|
|
if( eval )
|
|
|
|
{
|
|
|
|
IUpdateDecals(hsTimer::GetSysSeconds());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
plDynaDecalEnableMsg* enaMsg = plDynaDecalEnableMsg::ConvertNoRef(msg);
|
|
|
|
if( enaMsg )
|
|
|
|
{
|
|
|
|
IHandleEnableMsg(enaMsg);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
plPipeGeoMakeMsg* make = plPipeGeoMakeMsg::ConvertNoRef(msg);
|
|
|
|
if( make )
|
|
|
|
{
|
|
|
|
return IMakeAuxRefs(make->Pipeline());
|
|
|
|
}
|
|
|
|
|
|
|
|
plAgeLoadedMsg* ageLoadMsg = plAgeLoadedMsg::ConvertNoRef(msg);
|
|
|
|
if( ageLoadMsg && ageLoadMsg->fLoaded )
|
|
|
|
{
|
|
|
|
IGetParticles();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg);
|
|
|
|
if( refMsg )
|
|
|
|
{
|
|
|
|
switch( refMsg->fType )
|
|
|
|
{
|
|
|
|
case kRefMatPreShade:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fMatPreShade = hsGMaterial::ConvertNoRef(refMsg->GetRef());
|
|
|
|
else
|
|
|
|
fMatPreShade = nil;
|
|
|
|
return true;
|
|
|
|
case kRefMatRTShade:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fMatRTShade = hsGMaterial::ConvertNoRef(refMsg->GetRef());
|
|
|
|
else
|
|
|
|
fMatRTShade = nil;
|
|
|
|
return true;
|
|
|
|
case kRefTarget:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
{
|
|
|
|
fTargets.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int idx = fTargets.Find((plSceneObject*)refMsg->GetRef());
|
|
|
|
if( idx != fTargets.kMissingIndex )
|
|
|
|
fTargets.Remove(idx);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case kRefPartyObject:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
{
|
|
|
|
fPartyObjects.Append(plSceneObject::ConvertNoRef(refMsg->GetRef()));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int idx = fPartyObjects.Find((plSceneObject*)refMsg->GetRef());
|
|
|
|
if( idx != fPartyObjects.kMissingIndex )
|
|
|
|
fPartyObjects.Remove(idx);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case kRefParticles:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
{
|
|
|
|
fParticles.Append(plParticleSystem::ConvertNoRef(refMsg->GetRef()));
|
|
|
|
fParticles[fParticles.GetCount()-1]->fMiscFlags |= plParticleSystem::kParticleSystemAlwaysUpdate;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int idx = fParticles.Find((plParticleSystem*)refMsg->GetRef());
|
|
|
|
if( idx != fParticles.kMissingIndex )
|
|
|
|
fParticles.Remove(idx);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case kRefAvatar:
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnRemove|plRefMsg::kOnDestroy) )
|
|
|
|
IRemoveDecalInfo(uintptr_t(refMsg->GetRef()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return plSynchedObject::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
void plDynaDecalMgr::INotifyActive(plDynaDecalInfo& info, const plKey& armKey, uint32_t id) const
|
|
|
|
{
|
|
|
|
if( !(info.fFlags & plDynaDecalInfo::kActive) )
|
|
|
|
{
|
|
|
|
double secs = hsTimer::GetSysSeconds();
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fNotifies.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
plDynaDecalEnableMsg* enaMsg = new plDynaDecalEnableMsg(fNotifies[i], armKey, secs, fWetLength, false, id);
|
|
|
|
enaMsg->Send();
|
|
|
|
}
|
|
|
|
info.fFlags |= plDynaDecalInfo::kActive;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::INotifyInactive(plDynaDecalInfo& info, const plKey& armKey, uint32_t id) const
|
|
|
|
{
|
|
|
|
if( info.fFlags & plDynaDecalInfo::kActive )
|
|
|
|
{
|
|
|
|
double secs = hsTimer::GetSysSeconds();
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fNotifies.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
plDynaDecalEnableMsg* enaMsg = new plDynaDecalEnableMsg(fNotifies[i], armKey, secs, fWetLength, true, id);
|
|
|
|
enaMsg->Send();
|
|
|
|
}
|
|
|
|
info.fFlags &= ~plDynaDecalInfo::kActive;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plDynaDecalInfo& plDynaDecalInfo::Init(const plKey& key)
|
|
|
|
{
|
|
|
|
fKey = key;
|
|
|
|
|
|
|
|
fLastTime = -1.e33f;
|
|
|
|
fLastPos.Set(-1.e33f, -1.e33f, -1.e33f);
|
|
|
|
fWetTime = -1.e33f;
|
|
|
|
fWetLength = 0;
|
|
|
|
fFlags = kNone;
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
plDynaDecalInfo& plDynaDecalMgr::IGetDecalInfo(uintptr_t id, const plKey& key)
|
|
|
|
{
|
|
|
|
plDynaDecalMap::iterator iter = fDecalMap.find(id);
|
|
|
|
if( iter == fDecalMap.end() )
|
|
|
|
{
|
|
|
|
plDynaDecalInfo decalInfo;
|
|
|
|
decalInfo.Init(key);
|
|
|
|
plGenRefMsg* refMsg = new plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefAvatar);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(plKey(key), refMsg, plRefFlags::kPassiveRef);
|
|
|
|
|
|
|
|
pair<plDynaDecalMap::iterator, bool> iterPair;
|
|
|
|
iterPair = fDecalMap.insert(plDynaDecalMap::value_type(id, decalInfo));
|
|
|
|
iter = iterPair.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
return iter->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IRemoveDecalInfo(uint32_t id)
|
|
|
|
{
|
|
|
|
plDynaDecalMap::iterator iter = fDecalMap.find(id);
|
|
|
|
if( iter != fDecalMap.end() )
|
|
|
|
fDecalMap.erase(iter, iter);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IRemoveDecalInfos(const plKey& key)
|
|
|
|
{
|
|
|
|
plDynaDecalMap::iterator iter = fDecalMap.begin();
|
|
|
|
while( iter != fDecalMap.end() )
|
|
|
|
{
|
|
|
|
if( iter->second.fKey == key )
|
|
|
|
{
|
|
|
|
plDynaDecalMap::iterator nuke0 = iter;
|
|
|
|
plDynaDecalMap::iterator nuke1 = iter;
|
|
|
|
iter++;
|
|
|
|
while( (iter != fDecalMap.end()) && (iter->second.fKey == key) )
|
|
|
|
{
|
|
|
|
nuke1 = iter;
|
|
|
|
iter++;
|
|
|
|
}
|
|
|
|
fDecalMap.erase(nuke0, nuke1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float plDynaDecalMgr::IHowWet(plDynaDecalInfo& info, double t) const
|
|
|
|
{
|
|
|
|
// We aren't playing this wet/dry/enable/disable thing.
|
|
|
|
if( !fWaitOnEnable )
|
|
|
|
return fIntensity;
|
|
|
|
|
|
|
|
// We've been notified we've entered the pool,
|
|
|
|
// and haven't been notified we've left it.
|
|
|
|
if( info.fFlags & plDynaDecalInfo::kImmersed )
|
|
|
|
{
|
|
|
|
info.fWetTime = t;
|
|
|
|
return fIntensity;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've never been enabled.
|
|
|
|
if( info.fWetLength <= 0 )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// We're wet, let's see how wet.
|
|
|
|
float wet = (float)(1.f - (t - info.fWetTime) / info.fWetLength);
|
|
|
|
if( wet > 1.f ) // This should never happen. It means t < info.fWetTime (we get wet in the future).
|
|
|
|
return fIntensity;
|
|
|
|
if( wet < 0 )
|
|
|
|
return 0;
|
|
|
|
return wet * fIntensity;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plAuxSpan* plDynaDecalMgr::IGetAuxSpan(plDrawableSpans* targ, int iSpan, hsGMaterial* mat, uint16_t numVerts, uint16_t numIdx)
|
|
|
|
{
|
|
|
|
// Some of this code just assumes you get the number of verts you ask for.
|
|
|
|
// Which was causing errors when you asked for more than the max and didn't
|
|
|
|
// get it. So now if you ask for too many verts, you just lose.
|
|
|
|
if (numVerts > fMaxNumVerts || numIdx > fMaxNumIdx)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
plSpan* span = const_cast<plSpan*>(targ->GetSpan(iSpan));
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// First, see if we've got an aux span already sitting on this span.
|
|
|
|
// We can use an existing aux span iff
|
|
|
|
// a) we own it
|
|
|
|
// b) there's enough room to append our stuff.
|
|
|
|
//
|
|
|
|
// This is kind of overkill in both respects, because:
|
|
|
|
// a) we don't care if we own it, it just really needs the same material
|
|
|
|
// b) there might be room at the beginning or middle of the span
|
|
|
|
//
|
|
|
|
// Relaxing those brings on the additional bookkeeping with having a span
|
|
|
|
// that requires multiple drawprimitive calls.
|
|
|
|
// Case a) can cause it if we share with a DynaDecalMgr with a different
|
|
|
|
// lifespan than ours. That would allow decals in the middle of the span
|
|
|
|
// to die out.
|
|
|
|
// Case b) won't have random bits dropping out of the middle, but will
|
|
|
|
// create a gap in the middle (and probably end) when we wrap around.
|
|
|
|
//
|
|
|
|
// The simplicity in bookkeeping should make up for any space/speed advantages
|
|
|
|
// in packing more into a single AuxSpan.
|
|
|
|
for( i = 0; i < span->GetNumAuxSpans(); i++ )
|
|
|
|
{
|
|
|
|
plAuxSpan* aux = span->GetAuxSpan(i);
|
|
|
|
if( (aux->fOwner == (void*)this)
|
|
|
|
&&(aux->fVStartIdx + aux->fVLength + numVerts < aux->fVBufferLimit)
|
|
|
|
&&(aux->fIStartIdx + aux->fILength + numIdx < aux->fIBufferLimit) )
|
|
|
|
return aux;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rtLit = span->fProps & plSpan::kLiteVtxNonPreshaded;
|
|
|
|
|
|
|
|
// Now look to see if we've got one sitting around unused that's suitable.
|
|
|
|
// Here the suitable criteria is a little different. We know we are the owner,
|
|
|
|
// and we know there's enough room (because it's sitting idle).
|
|
|
|
for( i = 0; i < fAuxSpans.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
plAuxSpan* aux = fAuxSpans[i];
|
|
|
|
if( !aux->fDrawable
|
|
|
|
&&(aux->fVStartIdx + aux->fVLength + numVerts < aux->fVBufferLimit)
|
|
|
|
&&(aux->fIStartIdx + aux->fILength + numIdx < aux->fIBufferLimit) )
|
|
|
|
{
|
|
|
|
aux->fDrawable = targ;
|
|
|
|
aux->fBaseSpanIdx = iSpan;
|
|
|
|
|
|
|
|
ISetAuxMaterial(aux, mat, rtLit);
|
|
|
|
|
|
|
|
span->AddAuxSpan(aux);
|
|
|
|
|
|
|
|
return aux;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ain't got one. We could allocate another one, or we can just say too bad doo-dad.
|
|
|
|
// If we allocate a new one:
|
|
|
|
// A) we'll need to flush managed memory to load it in.
|
|
|
|
// B) we'll be stuck with that memory (video and system) used up until this age is paged out and reloaded
|
|
|
|
// C) we've got a whole bunch of extra faces to draw.
|
|
|
|
// If we just return nil:
|
|
|
|
// A) opposite of above
|
|
|
|
// B) we stop leaving footprints/ripples for a while.
|
|
|
|
// I'm going to try the latter for a bit.
|
|
|
|
|
|
|
|
#ifdef MF_NEVER_RUN_OUT
|
|
|
|
// Okay, nothing there. Let's get a new one.
|
|
|
|
plAuxSpan* aux = new plAuxSpan;
|
|
|
|
fAuxSpans.Append(aux);
|
|
|
|
|
|
|
|
IAllocAuxSpan(aux, numVerts, numIdx);
|
|
|
|
|
|
|
|
aux->fOwner = (void*)this;
|
|
|
|
aux->fDrawable = targ;
|
|
|
|
aux->fBaseSpanIdx = iSpan;
|
|
|
|
|
|
|
|
ISetAuxMaterial(aux, mat, rtLit);
|
|
|
|
|
|
|
|
span->AddAuxSpan(aux);
|
|
|
|
|
|
|
|
return aux;
|
|
|
|
#else // MF_NEVER_RUN_OUT
|
|
|
|
return nil;
|
|
|
|
#endif // MF_NEVER_RUN_OUT
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::InitAuxSpans()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < kInitAuxSpans; i++ )
|
|
|
|
{
|
|
|
|
plAuxSpan* aux = new plAuxSpan;
|
|
|
|
fAuxSpans.Append(aux);
|
|
|
|
IAllocAuxSpan(aux, fMaxNumVerts, fMaxNumIdx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IAllocAuxSpan(plAuxSpan* aux, uint32_t maxNumVerts, uint32_t maxNumIdx)
|
|
|
|
{
|
|
|
|
int iGrp = fGroups.GetCount();
|
|
|
|
plGBufferGroup* grp = new plGBufferGroup(kDecalVtxFormat, true, false);
|
|
|
|
fGroups.Append(grp);
|
|
|
|
|
|
|
|
grp->ReserveVertStorage(maxNumVerts,
|
|
|
|
&aux->fVBufferIdx,
|
|
|
|
&aux->fCellIdx,
|
|
|
|
&aux->fCellOffset,
|
|
|
|
plGBufferGroup::kReserveInterleaved);
|
|
|
|
|
|
|
|
aux->fFlags = 0;
|
|
|
|
|
|
|
|
aux->fVStartIdx = grp->GetVertStartFromCell(aux->fVBufferIdx, aux->fCellIdx, aux->fCellOffset);
|
|
|
|
aux->fVLength = 0;
|
|
|
|
|
|
|
|
uint16_t* dataPtr = nil;
|
|
|
|
grp->ReserveIndexStorage(maxNumIdx, &aux->fIBufferIdx, &aux->fIStartIdx, &dataPtr);
|
|
|
|
//aux->fIStartIdx /* should be assigning something? */;
|
|
|
|
|
|
|
|
aux->fILength = 0;
|
|
|
|
|
|
|
|
aux->fGroup = grp;
|
|
|
|
|
|
|
|
aux->fVBufferInit = aux->fVStartIdx;
|
|
|
|
aux->fVBufferLimit = aux->fVBufferInit + maxNumVerts;
|
|
|
|
|
|
|
|
aux->fIBufferInit = aux->fIStartIdx;
|
|
|
|
aux->fIBufferLimit = aux->fIBufferInit + maxNumIdx;
|
|
|
|
|
|
|
|
aux->fOrigPos.SetCount(maxNumVerts);
|
|
|
|
aux->fOrigUVW.SetCount(maxNumVerts);
|
|
|
|
|
|
|
|
aux->fOwner = (void*)this;
|
|
|
|
aux->fDrawable = nil;
|
|
|
|
aux->fBaseSpanIdx = 0;
|
|
|
|
|
|
|
|
grp->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
|
|
|
|
grp->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
|
|
|
|
grp->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx);
|
|
|
|
grp->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsGMaterial* plDynaDecalMgr::ISetAuxMaterial(plAuxSpan* aux, hsGMaterial* mat, bool rtLit)
|
|
|
|
{
|
|
|
|
if( !mat )
|
|
|
|
mat = fMatRTShade;
|
|
|
|
|
|
|
|
bool attenColor = 0 != (mat->GetLayer(0)->GetBlendFlags()
|
|
|
|
& (hsGMatState::kBlendAdd
|
|
|
|
| hsGMatState::kBlendMult
|
|
|
|
| hsGMatState::kBlendMADD));
|
|
|
|
bool bump = 0 != (mat->GetLayer(0)->GetMiscFlags() & hsGMatState::kMiscBumpChans);
|
|
|
|
bool hasVS = nil != mat->GetLayer(0)->GetVertexShader();
|
|
|
|
|
|
|
|
if( hasVS )
|
|
|
|
{
|
|
|
|
aux->fFlags |= plAuxSpan::kVertexShader;
|
|
|
|
aux->fFlags &= ~plAuxSpan::kAttenColor;
|
|
|
|
aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kRTLit);
|
|
|
|
aux->fMaterial = mat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( bump )
|
|
|
|
{
|
|
|
|
aux->fFlags &= ~plAuxSpan::kVertexShader;
|
|
|
|
aux->fFlags |= plAuxSpan::kAttenColor;
|
|
|
|
aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kRTLit);
|
|
|
|
aux->fMaterial = mat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( attenColor )
|
|
|
|
{
|
|
|
|
aux->fFlags &= ~plAuxSpan::kVertexShader;
|
|
|
|
aux->fFlags |= plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor;
|
|
|
|
aux->fFlags &= ~plAuxSpan::kRTLit;
|
|
|
|
aux->fMaterial = mat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( rtLit )
|
|
|
|
{
|
|
|
|
aux->fFlags &= ~plAuxSpan::kVertexShader;
|
|
|
|
aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor);
|
|
|
|
aux->fFlags |= plAuxSpan::kRTLit;
|
|
|
|
aux->fMaterial = mat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aux->fFlags &= ~(plAuxSpan::kOverrideLiteModel | plAuxSpan::kAttenColor);
|
|
|
|
aux->fFlags &= ~plAuxSpan::kRTLit;
|
|
|
|
aux->fMaterial = fMatPreShade;
|
|
|
|
}
|
|
|
|
return aux->fMaterial;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plDynaDecal* plDynaDecalMgr::IInitDecal(plAuxSpan* aux, double t, uint16_t numVerts, uint16_t numIdx)
|
|
|
|
{
|
|
|
|
int idx = INewDecal();
|
|
|
|
|
|
|
|
fDecals[idx]->fStartVtx = (uint16_t)(aux->fVStartIdx + aux->fVLength);
|
|
|
|
fDecals[idx]->fNumVerts = numVerts;
|
|
|
|
fDecals[idx]->fStartIdx = (uint16_t)(aux->fIStartIdx + aux->fILength);
|
|
|
|
fDecals[idx]->fNumIdx = numIdx;
|
|
|
|
|
|
|
|
fDecals[idx]->fBirth = t;
|
|
|
|
fDecals[idx]->fFlags = plDynaDecal::kFresh;
|
|
|
|
fDecals[idx]->fInitAtten = fInitAtten;
|
|
|
|
|
|
|
|
fDecals[idx]->fAuxSpan = aux;
|
|
|
|
|
|
|
|
aux->fVLength += numVerts;
|
|
|
|
aux->fGroup->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx + aux->fVLength);
|
|
|
|
aux->fGroup->DirtyVertexBuffer(aux->fVBufferIdx);
|
|
|
|
|
|
|
|
aux->fILength += numIdx;
|
|
|
|
aux->fGroup->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx + aux->fILength);
|
|
|
|
aux->fGroup->DirtyIndexBuffer(aux->fIBufferIdx);
|
|
|
|
|
|
|
|
if( aux->fFlags & plAuxSpan::kVertexShader )
|
|
|
|
fDecals[idx]->fFlags |= plDynaDecal::kVertexShader;
|
|
|
|
else
|
|
|
|
if( aux->fFlags & plAuxSpan::kAttenColor )
|
|
|
|
fDecals[idx]->fFlags |= plDynaDecal::kAttenColor;
|
|
|
|
|
|
|
|
// We should probably assert here that our span hasn't just overrun buffergroup storage.
|
|
|
|
hsAssert(aux->fVStartIdx + aux->fVLength <= aux->fVBufferLimit, "Overrunning allocated storage");
|
|
|
|
hsAssert(aux->fIStartIdx + aux->fILength <= aux->fIBufferLimit, "Overrunning allocated storage");
|
|
|
|
|
|
|
|
hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
|
|
|
|
hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");
|
|
|
|
|
|
|
|
return fDecals[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IKillDecal(int i)
|
|
|
|
{
|
|
|
|
// Update this decal's span.
|
|
|
|
// Since decals die off in the same order they are created, and we always
|
|
|
|
// append a decal to a span, we only need to advance the span's start indices,
|
|
|
|
// and decrement the lengths.
|
|
|
|
plAuxSpan* aux = fDecals[i]->fAuxSpan;
|
|
|
|
aux->fVStartIdx += fDecals[i]->fNumVerts;
|
|
|
|
aux->fGroup->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
|
|
|
|
aux->fVLength -= fDecals[i]->fNumVerts;
|
|
|
|
|
|
|
|
aux->fIStartIdx += fDecals[i]->fNumIdx;
|
|
|
|
aux->fGroup->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
|
|
|
|
aux->fILength -= fDecals[i]->fNumIdx;
|
|
|
|
|
|
|
|
hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
|
|
|
|
hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");
|
|
|
|
|
|
|
|
if( !aux->fVLength )
|
|
|
|
{
|
|
|
|
hsAssert(!aux->fILength, "Ran out of verts before indices");
|
|
|
|
aux->fVStartIdx = aux->fVBufferInit;
|
|
|
|
aux->fIStartIdx = aux->fIBufferInit;
|
|
|
|
|
|
|
|
aux->fGroup->SetVertBufferStart(aux->fVBufferIdx, aux->fVStartIdx);
|
|
|
|
aux->fGroup->SetIndexBufferStart(aux->fIBufferIdx, aux->fIStartIdx);
|
|
|
|
aux->fGroup->SetVertBufferEnd(aux->fVBufferIdx, aux->fVStartIdx);
|
|
|
|
aux->fGroup->SetIndexBufferEnd(aux->fIBufferIdx, aux->fIStartIdx);
|
|
|
|
|
|
|
|
hsAssert(aux->fGroup->GetVertBufferEnd(aux->fVBufferIdx) >= aux->fGroup->GetVertBufferStart(aux->fVBufferIdx), "Going out of range on verts");
|
|
|
|
hsAssert(aux->fGroup->GetIndexBufferEnd(aux->fIBufferIdx) >= aux->fGroup->GetIndexBufferStart(aux->fIBufferIdx), "Going out of range on verts");
|
|
|
|
|
|
|
|
if( aux->fDrawable )
|
|
|
|
const_cast<plSpan*>(aux->fDrawable->GetSpan(aux->fBaseSpanIdx))->RemoveAuxSpan(aux);
|
|
|
|
|
|
|
|
aux->fDrawable = nil;
|
|
|
|
aux->fBaseSpanIdx = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete fDecals[i];
|
|
|
|
int newCount = fDecals.GetCount()-1;
|
|
|
|
if( i < newCount )
|
|
|
|
{
|
|
|
|
memmove(&fDecals[i], &fDecals[i+1], (newCount-i) * sizeof(fDecals[i]));
|
|
|
|
}
|
|
|
|
fDecals.SetCount(newCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IUpdateDecals(double t)
|
|
|
|
{
|
|
|
|
if( fDisableUpdate )
|
|
|
|
return;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for( i = 0; i < fDecals.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
if( fDecals[i]->Age(t, fRampEnd, fDecayStart, fLifeSpan) )
|
|
|
|
{
|
|
|
|
IKillDecal(i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for( i = 0; i < fAuxSpans.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
if( fAuxSpans[i]->fVLength )
|
|
|
|
{
|
|
|
|
plAuxSpan* aux = fAuxSpans[i];
|
|
|
|
aux->fGroup->DirtyVertexBuffer(aux->fVBufferIdx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void plDynaDecalMgr::ICountIncoming(hsTArray<plCutoutPoly>& src, uint16_t& numVerts, uint16_t& numIdx) const
|
|
|
|
{
|
|
|
|
numVerts = 0;
|
|
|
|
numIdx = 0;
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < src.GetCount(); j++ )
|
|
|
|
{
|
|
|
|
if( src[j].fVerts.GetCount() )
|
|
|
|
{
|
|
|
|
numVerts += src[j].fVerts.GetCount();
|
|
|
|
numIdx += src[j].fVerts.GetCount()-2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
numIdx *= 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
plDecalVtxFormat* plDynaDecalMgr::IGetBaseVtxPtr(const plAuxSpan* auxSpan) const
|
|
|
|
{
|
|
|
|
plGBufferGroup* grp = auxSpan->fGroup;
|
|
|
|
plGBufferCell* cell = grp->GetCell(auxSpan->fVBufferIdx, auxSpan->fCellIdx);
|
|
|
|
|
|
|
|
uint8_t* ptr = grp->GetVertBufferData(auxSpan->fVBufferIdx);
|
|
|
|
|
|
|
|
ptr += cell->fVtxStart + auxSpan->fCellOffset;
|
|
|
|
|
|
|
|
return (plDecalVtxFormat*)ptr;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t* plDynaDecalMgr::IGetBaseIdxPtr(const plAuxSpan* auxSpan) const
|
|
|
|
{
|
|
|
|
plGBufferGroup* grp = auxSpan->fGroup;
|
|
|
|
|
|
|
|
return grp->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferInit;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IConvertFlatGrid(plAuxSpan* auxSpan,
|
|
|
|
plDynaDecal* decal,
|
|
|
|
const plFlatGridMesh& grid) const
|
|
|
|
{
|
|
|
|
plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
|
|
|
|
vtx += decal->fStartVtx;
|
|
|
|
decal->fVtxBase = vtx;
|
|
|
|
|
|
|
|
hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
|
|
|
|
hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];
|
|
|
|
|
|
|
|
uint32_t initColor = decal->fFlags & plDynaDecal::kAttenColor
|
|
|
|
? 0xff000000
|
|
|
|
: 0x00ffffff;
|
|
|
|
int iv;
|
|
|
|
for( iv = 0; iv < decal->fNumVerts; iv++ )
|
|
|
|
{
|
|
|
|
*origPos = vtx->fPos = grid.fVerts[iv].fPos;
|
|
|
|
|
|
|
|
vtx->fNorm.Set(0.f, 0.f, 1.f);
|
|
|
|
|
|
|
|
vtx->fDiffuse = initColor;
|
|
|
|
vtx->fSpecular = 0;
|
|
|
|
|
|
|
|
vtx->fUVW[0] = grid.fVerts[iv].fUVW;
|
|
|
|
vtx->fUVW[1].Set(0.f, 0.f, 0.f);
|
|
|
|
*origUVW = grid.fVerts[iv].fUVW;
|
|
|
|
origUVW->fZ = 1.f;
|
|
|
|
|
|
|
|
vtx++;
|
|
|
|
origPos++;
|
|
|
|
origUVW++;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t* idx = IGetBaseIdxPtr(auxSpan);
|
|
|
|
idx += decal->fStartIdx;
|
|
|
|
|
|
|
|
hsAssert(grid.fIdx.GetCount() == decal->fNumIdx, "Mismatch on dynamic indices");
|
|
|
|
|
|
|
|
uint16_t base = decal->fStartVtx;
|
|
|
|
int ii;
|
|
|
|
for( ii = 0; ii < grid.fIdx.GetCount(); ii++ )
|
|
|
|
{
|
|
|
|
hsAssert(grid.fIdx[ii] + base - decal->fStartVtx < decal->fNumVerts, "Index going out of range");
|
|
|
|
hsAssert(grid.fIdx[ii] + base < auxSpan->fIStartIdx + auxSpan->fILength, "Index going out of range.");
|
|
|
|
|
|
|
|
*idx++ = grid.fIdx[ii] + base;
|
|
|
|
}
|
|
|
|
|
|
|
|
auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
|
|
|
|
auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::ISetDepthFalloff()
|
|
|
|
{
|
|
|
|
const float totalDepth = fCutter->GetLengthW();
|
|
|
|
|
|
|
|
// Currently all constants, but these could be set per DecalMgr.
|
|
|
|
plConst(float) kMinFeet(3.f);
|
|
|
|
plConst(float) kMaxFeet(10.f);
|
|
|
|
|
|
|
|
plConst(float) kMinDepth(0.25f);
|
|
|
|
plConst(float) kMaxDepth(0.75f);
|
|
|
|
|
|
|
|
fMinDepth = kMinFeet / totalDepth;
|
|
|
|
if( fMinDepth > kMinDepth )
|
|
|
|
fMinDepth = kMinDepth;
|
|
|
|
|
|
|
|
fMinDepthRange = 1.f / fMinDepth;
|
|
|
|
|
|
|
|
fMaxDepth = 1.f - (kMaxFeet / totalDepth);
|
|
|
|
if( fMaxDepth < kMaxDepth )
|
|
|
|
fMaxDepth = kMaxDepth;
|
|
|
|
|
|
|
|
fMaxDepthRange = 1.f / (1.f - fMaxDepth);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IConvertPolys(plAuxSpan* auxSpan,
|
|
|
|
plDynaDecal* decal,
|
|
|
|
hsTArray<plCutoutPoly>& src)
|
|
|
|
{
|
|
|
|
ISetDepthFalloff();
|
|
|
|
|
|
|
|
if( decal->fFlags & plDynaDecal::kVertexShader )
|
|
|
|
return IConvertPolysVS(auxSpan, decal, src);
|
|
|
|
|
|
|
|
if( decal->fFlags & plDynaDecal::kAttenColor )
|
|
|
|
return IConvertPolysColor(auxSpan, decal, src);
|
|
|
|
|
|
|
|
return IConvertPolysAlpha(auxSpan, decal, src);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IConvertPolysAlpha(plAuxSpan* auxSpan,
|
|
|
|
plDynaDecal* decal,
|
|
|
|
hsTArray<plCutoutPoly>& src)
|
|
|
|
{
|
|
|
|
bool loU = false;
|
|
|
|
bool hiU = false;
|
|
|
|
bool loV = false;
|
|
|
|
bool hiV = false;
|
|
|
|
plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
|
|
|
|
vtx += decal->fStartVtx;
|
|
|
|
decal->fVtxBase = vtx;
|
|
|
|
|
|
|
|
hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
|
|
|
|
hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];
|
|
|
|
|
|
|
|
const hsVector3 backDir = fCutter->GetBackDir();
|
|
|
|
|
|
|
|
int iPoly = 0;
|
|
|
|
int iVert = 0;
|
|
|
|
int iv;
|
|
|
|
for( iv = 0; iv < decal->fNumVerts; iv++ )
|
|
|
|
{
|
|
|
|
*origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;
|
|
|
|
|
|
|
|
vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
|
|
|
|
vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;
|
|
|
|
|
|
|
|
if( vtx->fUVW[0].fX < 0.5f )
|
|
|
|
loU = true;
|
|
|
|
else
|
|
|
|
hiU = true;
|
|
|
|
if( vtx->fUVW[0].fY < 0.5f )
|
|
|
|
loV = true;
|
|
|
|
else
|
|
|
|
hiV = true;
|
|
|
|
|
|
|
|
hsColorRGBA col = src[iPoly].fVerts[iVert].fColor;
|
|
|
|
|
|
|
|
float depth = vtx->fUVW[0].fZ;
|
|
|
|
|
|
|
|
float opac = depth < fMinDepth
|
|
|
|
? depth * fMinDepthRange
|
|
|
|
: depth > fMaxDepth
|
|
|
|
? (1.f - depth) * fMaxDepthRange
|
|
|
|
: 1.f;
|
|
|
|
|
|
|
|
float normOpac = 1.f - vtx->fNorm.InnerProduct(backDir);
|
|
|
|
opac *= 1.f - normOpac * normOpac;
|
|
|
|
if( opac < 0 )
|
|
|
|
opac = 0;
|
|
|
|
|
|
|
|
if( src[iPoly].fBaseHasAlpha )
|
|
|
|
opac *= col.a;
|
|
|
|
col.a = 0;
|
|
|
|
|
|
|
|
origUVW->fX = vtx->fUVW[0].fX;
|
|
|
|
origUVW->fY = vtx->fUVW[0].fY;
|
|
|
|
|
|
|
|
origUVW->fZ = opac;
|
|
|
|
vtx->fUVW[1].Set(0, 0, 0);
|
|
|
|
|
|
|
|
vtx->fDiffuse = col.ToARGB32();
|
|
|
|
vtx->fSpecular = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if( ++iVert >= src[iPoly].fVerts.GetCount() )
|
|
|
|
{
|
|
|
|
iVert = 0;
|
|
|
|
iPoly++;
|
|
|
|
}
|
|
|
|
vtx++;
|
|
|
|
origPos++;
|
|
|
|
origUVW++;
|
|
|
|
}
|
|
|
|
hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");
|
|
|
|
|
|
|
|
uint16_t* idx = IGetBaseIdxPtr(auxSpan);
|
|
|
|
idx += decal->fStartIdx;
|
|
|
|
|
|
|
|
uint16_t base = decal->fStartVtx;
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < src.GetCount(); j++ )
|
|
|
|
{
|
|
|
|
uint16_t next = base+1;
|
|
|
|
int k;
|
|
|
|
for( k = 2; k < src[j].fVerts.GetCount(); k++ )
|
|
|
|
{
|
|
|
|
*idx++ = base;
|
|
|
|
*idx++ = next++;
|
|
|
|
*idx++ = next;
|
|
|
|
}
|
|
|
|
base = ++next;
|
|
|
|
}
|
|
|
|
hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");
|
|
|
|
|
|
|
|
auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
|
|
|
|
auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);
|
|
|
|
|
|
|
|
return loU & hiU & loV & hiV;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IConvertPolysColor(plAuxSpan* auxSpan,
|
|
|
|
plDynaDecal* decal,
|
|
|
|
hsTArray<plCutoutPoly>& src)
|
|
|
|
{
|
|
|
|
bool loU = false;
|
|
|
|
bool hiU = false;
|
|
|
|
bool loV = false;
|
|
|
|
bool hiV = false;
|
|
|
|
plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
|
|
|
|
vtx += decal->fStartVtx;
|
|
|
|
decal->fVtxBase = vtx;
|
|
|
|
|
|
|
|
hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
|
|
|
|
hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];
|
|
|
|
|
|
|
|
const hsVector3 backDir = fCutter->GetBackDir();
|
|
|
|
int iPoly = 0;
|
|
|
|
int iVert = 0;
|
|
|
|
int iv;
|
|
|
|
for( iv = 0; iv < decal->fNumVerts; iv++ )
|
|
|
|
{
|
|
|
|
*origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;
|
|
|
|
|
|
|
|
vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
|
|
|
|
vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;
|
|
|
|
|
|
|
|
if( vtx->fUVW[0].fX < 0.5f )
|
|
|
|
loU = true;
|
|
|
|
else
|
|
|
|
hiU = true;
|
|
|
|
if( vtx->fUVW[0].fY < 0.5f )
|
|
|
|
loV = true;
|
|
|
|
else
|
|
|
|
hiV = true;
|
|
|
|
|
|
|
|
float depth = vtx->fUVW[0].fZ;
|
|
|
|
float opac = depth < fMinDepth
|
|
|
|
? depth * fMinDepthRange
|
|
|
|
: depth > fMaxDepth
|
|
|
|
? (1.f - depth) * fMaxDepthRange
|
|
|
|
: 1.f;
|
|
|
|
|
|
|
|
float normOpac = 1.f - vtx->fNorm.InnerProduct(backDir);
|
|
|
|
opac *= 1.f - normOpac * normOpac;
|
|
|
|
if( opac < 0 )
|
|
|
|
opac = 0;
|
|
|
|
|
|
|
|
origUVW->fX = vtx->fUVW[0].fX;
|
|
|
|
origUVW->fY = vtx->fUVW[0].fY;
|
|
|
|
|
|
|
|
origUVW->fZ = opac;
|
|
|
|
vtx->fUVW[1].Set(0, 0, 0);
|
|
|
|
|
|
|
|
vtx->fDiffuse = 0xff000000;
|
|
|
|
vtx->fSpecular = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if( ++iVert >= src[iPoly].fVerts.GetCount() )
|
|
|
|
{
|
|
|
|
iVert = 0;
|
|
|
|
iPoly++;
|
|
|
|
}
|
|
|
|
vtx++;
|
|
|
|
origPos++;
|
|
|
|
origUVW++;
|
|
|
|
}
|
|
|
|
hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");
|
|
|
|
|
|
|
|
uint16_t* idx = IGetBaseIdxPtr(auxSpan);
|
|
|
|
idx += decal->fStartIdx;
|
|
|
|
|
|
|
|
uint16_t base = decal->fStartVtx;
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < src.GetCount(); j++ )
|
|
|
|
{
|
|
|
|
uint16_t next = base+1;
|
|
|
|
int k;
|
|
|
|
for( k = 2; k < src[j].fVerts.GetCount(); k++ )
|
|
|
|
{
|
|
|
|
*idx++ = base;
|
|
|
|
*idx++ = next++;
|
|
|
|
*idx++ = next;
|
|
|
|
}
|
|
|
|
base = ++next;
|
|
|
|
}
|
|
|
|
hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");
|
|
|
|
|
|
|
|
auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
|
|
|
|
auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);
|
|
|
|
|
|
|
|
return loU & hiU & loV & hiV;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IConvertPolysVS(plAuxSpan* auxSpan,
|
|
|
|
plDynaDecal* decal,
|
|
|
|
hsTArray<plCutoutPoly>& src)
|
|
|
|
{
|
|
|
|
bool loU = false;
|
|
|
|
bool hiU = false;
|
|
|
|
bool loV = false;
|
|
|
|
bool hiV = false;
|
|
|
|
plDecalVtxFormat* vtx = IGetBaseVtxPtr(auxSpan);
|
|
|
|
vtx += decal->fStartVtx;
|
|
|
|
decal->fVtxBase = vtx;
|
|
|
|
|
|
|
|
hsPoint3* origPos = &auxSpan->fOrigPos[decal->fStartVtx];
|
|
|
|
hsPoint3* origUVW = &auxSpan->fOrigUVW[decal->fStartVtx];
|
|
|
|
|
|
|
|
int iPoly = 0;
|
|
|
|
int iVert = 0;
|
|
|
|
int iv;
|
|
|
|
for( iv = 0; iv < decal->fNumVerts; iv++ )
|
|
|
|
{
|
|
|
|
*origPos = vtx->fPos = src[iPoly].fVerts[iVert].fPos;
|
|
|
|
|
|
|
|
vtx->fNorm = src[iPoly].fVerts[iVert].fNorm;
|
|
|
|
vtx->fUVW[0] = src[iPoly].fVerts[iVert].fUVW;
|
|
|
|
|
|
|
|
if( vtx->fUVW[0].fX < 0.5f )
|
|
|
|
loU = true;
|
|
|
|
else
|
|
|
|
hiU = true;
|
|
|
|
if( vtx->fUVW[0].fY < 0.5f )
|
|
|
|
loV = true;
|
|
|
|
else
|
|
|
|
hiV = true;
|
|
|
|
|
|
|
|
origUVW->fX = vtx->fUVW[0].fX;
|
|
|
|
origUVW->fY = vtx->fUVW[0].fY;
|
|
|
|
|
|
|
|
origUVW->fZ = vtx->fUVW[0].fZ = (float)decal->fBirth;
|
|
|
|
|
|
|
|
vtx->fUVW[1].Set(0, 0, 0);
|
|
|
|
|
|
|
|
const hsColorRGBA& col = src[iPoly].fVerts[iVert].fColor;
|
|
|
|
vtx->fDiffuse = col.ToARGB32();
|
|
|
|
vtx->fSpecular = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if( ++iVert >= src[iPoly].fVerts.GetCount() )
|
|
|
|
{
|
|
|
|
iVert = 0;
|
|
|
|
iPoly++;
|
|
|
|
}
|
|
|
|
vtx++;
|
|
|
|
origPos++;
|
|
|
|
origUVW++;
|
|
|
|
}
|
|
|
|
hsAssert(vtx <= IGetBaseVtxPtr(auxSpan) + auxSpan->fVBufferLimit, "Vtx pointer gone wild");
|
|
|
|
|
|
|
|
uint16_t* idx = IGetBaseIdxPtr(auxSpan);
|
|
|
|
idx += decal->fStartIdx;
|
|
|
|
|
|
|
|
uint16_t base = decal->fStartVtx;
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < src.GetCount(); j++ )
|
|
|
|
{
|
|
|
|
uint16_t next = base+1;
|
|
|
|
int k;
|
|
|
|
for( k = 2; k < src[j].fVerts.GetCount(); k++ )
|
|
|
|
{
|
|
|
|
*idx++ = base;
|
|
|
|
*idx++ = next++;
|
|
|
|
*idx++ = next;
|
|
|
|
}
|
|
|
|
base = ++next;
|
|
|
|
}
|
|
|
|
hsAssert(idx <= auxSpan->fGroup->GetIndexBufferData(auxSpan->fIBufferIdx) + auxSpan->fIBufferLimit, "Index ptr gone wild");
|
|
|
|
|
|
|
|
auxSpan->fGroup->DirtyVertexBuffer(auxSpan->fVBufferIdx);
|
|
|
|
auxSpan->fGroup->DirtyIndexBuffer(auxSpan->fIBufferIdx);
|
|
|
|
|
|
|
|
return loU & hiU & loV & hiV;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IHitTestPolys(hsTArray<plCutoutPoly>& src) const
|
|
|
|
{
|
|
|
|
bool loU = false;
|
|
|
|
bool hiU = false;
|
|
|
|
bool loV = false;
|
|
|
|
bool hiV = false;
|
|
|
|
int iPoly = 0;
|
|
|
|
int iVert = 0;
|
|
|
|
while( iPoly < src.GetCount() )
|
|
|
|
{
|
|
|
|
const hsPoint3& uvw = src[iPoly].fVerts[iVert].fUVW;
|
|
|
|
|
|
|
|
if( uvw.fX < 0.5f )
|
|
|
|
loU = true;
|
|
|
|
else
|
|
|
|
hiU = true;
|
|
|
|
if( uvw.fY < 0.5f )
|
|
|
|
loV = true;
|
|
|
|
else
|
|
|
|
hiV = true;
|
|
|
|
|
|
|
|
if( ++iVert >= src[iPoly].fVerts.GetCount() )
|
|
|
|
{
|
|
|
|
iVert = 0;
|
|
|
|
iPoly++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return loU & hiU & loV & hiV;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IProcessPolys(plDrawableSpans* targ, int iSpan, double t, hsTArray<plCutoutPoly>& src)
|
|
|
|
{
|
|
|
|
// Figure out how many verts and idxs are coming in.
|
|
|
|
uint16_t numVerts, numIdx;
|
|
|
|
ICountIncoming(src, numVerts, numIdx);
|
|
|
|
if( !numVerts )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Find a span to put them in. Either the current span, or a new
|
|
|
|
// one if it's full up.
|
|
|
|
plAuxSpan* auxSpan = IGetAuxSpan(targ, iSpan, nil, numVerts, numIdx);
|
|
|
|
|
|
|
|
// If we're full up, just see if we hit anything, but don't
|
|
|
|
// make any more decals. Might be nice to accelerate decay
|
|
|
|
// here, since we definitely aren't keeping up.
|
|
|
|
if( !auxSpan )
|
|
|
|
return IHitTestPolys(src);
|
|
|
|
|
|
|
|
// Get a decal to manage this group's aging.
|
|
|
|
// Update the span to point to enough room.
|
|
|
|
plDynaDecal* decal = IInitDecal(auxSpan, t, numVerts, numIdx);
|
|
|
|
|
|
|
|
// Convert the polys from src into the accessor tris
|
|
|
|
return IConvertPolys(auxSpan, decal, src);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IProcessGrid(plDrawableSpans* targ, int iSpan, hsGMaterial* mat, double t, const plFlatGridMesh& grid)
|
|
|
|
{
|
|
|
|
// Find a span to put them in. Either the current span, or a new
|
|
|
|
// one if it's full up.
|
|
|
|
plAuxSpan* auxSpan = IGetAuxSpan(targ, iSpan, mat, grid.fVerts.GetCount(), grid.fIdx.GetCount());
|
|
|
|
|
|
|
|
// If we're full up, just see if we hit anything, but don't
|
|
|
|
// make any more decals.
|
|
|
|
if( !auxSpan )
|
|
|
|
return IHitTestFlatGrid(grid);
|
|
|
|
|
|
|
|
auxSpan->fFlags |= plAuxSpan::kWorldSpace;
|
|
|
|
|
|
|
|
// Get a decal to manage this group's aging.
|
|
|
|
// Update the span to point to enough room.
|
|
|
|
plDynaDecal* decal = IInitDecal(auxSpan, t, grid.fVerts.GetCount(), grid.fIdx.GetCount());
|
|
|
|
|
|
|
|
// Convert the grid from src into the accessor tris
|
|
|
|
return IConvertFlatGrid(auxSpan, decal, grid);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::IHitTestFlatGrid(const plFlatGridMesh& grid) const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::ICutoutGrid(plDrawableSpans* drawable, int iSpan, hsGMaterial* mat, double secs)
|
|
|
|
{
|
|
|
|
static plFlatGridMesh grid;
|
|
|
|
grid.Reset();
|
|
|
|
|
|
|
|
int nWid = int(fCutter->GetLengthU() / fGridSizeU);
|
|
|
|
int nLen = int(fCutter->GetLengthV() / fGridSizeV);
|
|
|
|
|
|
|
|
fCutter->CutoutGrid(nWid, nLen, grid);
|
|
|
|
|
|
|
|
return IProcessGrid(drawable, iSpan, mat, secs, grid);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::ICutoutObject(plSceneObject* so, double secs)
|
|
|
|
{
|
|
|
|
if( fDisableAccumulate )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool retVal = false;
|
|
|
|
|
|
|
|
if( !so )
|
|
|
|
return retVal;
|
|
|
|
|
|
|
|
const plDrawInterface* di = so->GetDrawInterface();
|
|
|
|
if( !di )
|
|
|
|
return retVal;
|
|
|
|
|
|
|
|
plProfile_BeginTiming(Total);
|
|
|
|
int numGot = 0;
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < di->GetNumDrawables(); j++ )
|
|
|
|
{
|
|
|
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
|
|
|
|
// Nil dr - it hasn't loaded yet or something.
|
|
|
|
if( dr )
|
|
|
|
{
|
|
|
|
plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
|
|
|
|
if( !diIndex.IsMatrixOnly() )
|
|
|
|
{
|
|
|
|
int k;
|
|
|
|
for( k = 0; k < diIndex.GetCount(); k++ )
|
|
|
|
{
|
|
|
|
const plSpan* span = dr->GetSpan(diIndex[k]);
|
|
|
|
if( kVolumeCulled != fCutter->GetIsect().Test(span->fWorldBounds) )
|
|
|
|
{
|
|
|
|
plAccessSpan src;
|
|
|
|
plAccessGeometry::Instance()->OpenRO(dr, diIndex[k], src);
|
|
|
|
|
|
|
|
static hsTArray<plCutoutPoly> dst;
|
|
|
|
dst.SetCount(0);
|
|
|
|
|
|
|
|
plProfile_BeginTiming(Cutter);
|
|
|
|
fCutter->Cutout(src, dst);
|
|
|
|
plProfile_EndTiming(Cutter);
|
|
|
|
|
|
|
|
plProfile_BeginTiming(Process);
|
|
|
|
if( IProcessPolys(dr, diIndex[k], secs, dst) )
|
|
|
|
{
|
|
|
|
plProfile_BeginTiming(Callback);
|
|
|
|
if( src.HasWaterHeight() )
|
|
|
|
ICutoutCallback(dst, true, src.GetWaterHeight());
|
|
|
|
else
|
|
|
|
ICutoutCallback(dst);
|
|
|
|
plProfile_EndTiming(Callback);
|
|
|
|
|
|
|
|
retVal = true;
|
|
|
|
}
|
|
|
|
plProfile_EndTiming(Process);
|
|
|
|
|
|
|
|
plAccessGeometry::Instance()->Close(src);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
plProfile_EndTiming(Total);
|
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::ICutoutList(hsTArray<plDrawVisList>& drawVis, double secs)
|
|
|
|
{
|
|
|
|
if( fDisableAccumulate )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool retVal = false;
|
|
|
|
|
|
|
|
if( !drawVis.GetCount() )
|
|
|
|
return retVal;
|
|
|
|
|
|
|
|
hsTArray<plAccessSpan> src;
|
|
|
|
|
|
|
|
int numSpan = 0;
|
|
|
|
int iDraw;
|
|
|
|
for( iDraw = 0; iDraw < drawVis.GetCount(); iDraw++ )
|
|
|
|
numSpan += drawVis[iDraw].fVisList.GetCount();
|
|
|
|
|
|
|
|
src.SetCount(numSpan);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
iDraw = 0;
|
|
|
|
int iSpan = 0;
|
|
|
|
for( i = 0; i < numSpan; i++ )
|
|
|
|
{
|
|
|
|
static hsTArray<plCutoutPoly> dst;
|
|
|
|
dst.SetCount(0);
|
|
|
|
|
|
|
|
plAccessGeometry::Instance()->OpenRO(drawVis[iDraw].fDrawable, drawVis[iDraw].fVisList[iSpan], src[i]);
|
|
|
|
|
|
|
|
fCutter->Cutout(src[i], dst);
|
|
|
|
|
|
|
|
if( IProcessPolys((plDrawableSpans*)drawVis[iDraw].fDrawable, drawVis[iDraw].fVisList[iSpan], secs, dst) )
|
|
|
|
retVal = true;
|
|
|
|
|
|
|
|
plAccessGeometry::Instance()->Close(src[i]);
|
|
|
|
|
|
|
|
if( ++iSpan >= drawVis[iDraw].fVisList.GetCount() )
|
|
|
|
{
|
|
|
|
iDraw++;
|
|
|
|
iSpan = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plDynaDecalMgr::ICutoutTargets(double secs)
|
|
|
|
{
|
|
|
|
if( fDisableAccumulate )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool retVal = false;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fTargets.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
if( fTargets[i] )
|
|
|
|
retVal |= ICutoutObject(fTargets[i], secs);
|
|
|
|
}
|
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
hsGMaterial* plDynaDecalMgr::IConvertToEnvMap(hsGMaterial* mat, plBitmap* envMap)
|
|
|
|
{
|
|
|
|
if( !mat || !envMap )
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
plLayerInterface* oldLay = mat->GetLayer(0);
|
|
|
|
|
|
|
|
plMipmap* oldMip = plMipmap::ConvertNoRef(oldLay->GetTexture());
|
|
|
|
if( !oldMip )
|
|
|
|
return mat;
|
|
|
|
oldMip->SetCurrLevel(0);
|
|
|
|
|
|
|
|
hsGMaterial* newMat = new hsGMaterial;
|
|
|
|
plString buff = plFormat("{}_EnvMat", GetKey()->GetName());
|
|
|
|
hsgResMgr::ResMgr()->NewKey(buff, newMat, GetKey()->GetUoid().GetLocation());
|
|
|
|
|
|
|
|
static plTweak<float> kSmooth(1.f);
|
|
|
|
plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, plBumpMapGen::kBubbleTest, kSmooth);
|
|
|
|
// plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, plBumpMapGen::kNormalize, kSmooth);
|
|
|
|
// plMipmap* bumpMap = plBumpMapGen::QikNormalMap(nil, oldMip, 0xffffffff, 0, 0);
|
|
|
|
buff = plFormat("{}_BumpMap", GetKey()->GetName());
|
|
|
|
hsgResMgr::ResMgr()->NewKey(buff, bumpMap, GetKey()->GetUoid().GetLocation());
|
|
|
|
|
|
|
|
bumpMap->SetFlags(bumpMap->GetFlags() | plMipmap::kBumpEnvMap | plMipmap::kForceNonCompressed);
|
|
|
|
|
|
|
|
plLayer* bumpLay = new plLayer;
|
|
|
|
buff = plFormat("{}_BumpMap_0", GetKey()->GetName());
|
|
|
|
hsgResMgr::ResMgr()->NewKey(buff, bumpLay, GetKey()->GetUoid().GetLocation());
|
|
|
|
|
|
|
|
bumpLay->SetState(oldLay->GetState());
|
|
|
|
bumpLay->SetBlendFlags(hsGMatState::kBlendAdd | hsGMatState::kBlendEnvBumpNext);
|
|
|
|
bumpLay->SetTransform(hsMatrix44::IdentityMatrix());
|
|
|
|
bumpLay->SetUVWSrc(0);
|
|
|
|
|
|
|
|
hsMatrix44 bumpEnvXfm;
|
|
|
|
bumpEnvXfm.Reset();
|
|
|
|
bumpLay->SetBumpEnvMatrix(bumpEnvXfm);
|
|
|
|
|
|
|
|
bumpLay->SetAmbientColor(oldLay->GetAmbientColor());
|
|
|
|
bumpLay->SetRuntimeColor(oldLay->GetRuntimeColor());
|
|
|
|
bumpLay->SetOpacity(1.f);
|
|
|
|
|
|
|
|
plLayRefMsg* refMsg = new plLayRefMsg(bumpLay->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
|
|
|
|
hsgResMgr::ResMgr()->SendRef(bumpMap->GetKey(), refMsg, plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
newMat->AddLayerViaNotify(bumpLay);
|
|
|
|
|
|
|
|
plLayer* envLay = new plLayer;
|
|
|
|
buff = plFormat("{}_EnvMap_0", GetKey()->GetName());
|
|
|
|
hsgResMgr::ResMgr()->NewKey(buff, envLay, GetKey()->GetUoid().GetLocation());
|
|
|
|
|
|
|
|
envLay->SetBlendFlags(hsGMatState::kBlendMult);
|
|
|
|
envLay->SetClampFlags(0);
|
|
|
|
envLay->SetShadeFlags(bumpLay->GetShadeFlags());
|
|
|
|
envLay->SetZFlags(hsGMatState::kZNoZWrite);
|
|
|
|
envLay->SetMiscFlags(hsGMatState::kMiscUseReflectionXform);
|
|
|
|
envLay->SetUVWSrc(plLayer::kUVWReflect);
|
|
|
|
|
|
|
|
envLay->SetAmbientColor(oldLay->GetAmbientColor());
|
|
|
|
envLay->SetRuntimeColor(oldLay->GetRuntimeColor());
|
|
|
|
envLay->SetOpacity(1.f);
|
|
|
|
|
|
|
|
refMsg = new plLayRefMsg(envLay->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
|
|
|
|
hsgResMgr::ResMgr()->SendRef(envMap->GetKey(), refMsg, plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
newMat->AddLayerViaNotify(envLay);
|
|
|
|
|
|
|
|
return newMat;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::ConvertToEnvMap(plBitmap* envMap)
|
|
|
|
{
|
|
|
|
hsGMaterial* newPreShade = IConvertToEnvMap(fMatPreShade, envMap);
|
|
|
|
if( newPreShade && (newPreShade != fMatPreShade) )
|
|
|
|
hsgResMgr::ResMgr()->SendRef(newPreShade->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatPreShade), plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
hsGMaterial* newRTShade = IConvertToEnvMap(fMatRTShade, envMap);
|
|
|
|
if( newRTShade && (newRTShade != fMatRTShade) )
|
|
|
|
hsgResMgr::ResMgr()->SendRef(newRTShade->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefMatRTShade), plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
const plMipmap* plDynaDecalMgr::GetMipmap() const
|
|
|
|
{
|
|
|
|
plMipmap* mip = nil;
|
|
|
|
if( fMatRTShade )
|
|
|
|
mip = plMipmap::ConvertNoRef(fMatRTShade->GetLayer(0)->GetTexture());
|
|
|
|
|
|
|
|
if( !mip && fMatPreShade )
|
|
|
|
mip = plMipmap::ConvertNoRef(fMatPreShade->GetLayer(0)->GetTexture());
|
|
|
|
|
|
|
|
if( mip )
|
|
|
|
mip->SetCurrLevel(0);
|
|
|
|
|
|
|
|
return mip;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsVector3 plDynaDecalMgr::IRandomUp(hsVector3 dir) const
|
|
|
|
{
|
|
|
|
hsVector3 retVal;
|
|
|
|
|
|
|
|
// Okay, we want a pretty random vector perpindicular to the
|
|
|
|
// direction of fire. So we take that direction and cross product
|
|
|
|
// it with the 3 world axes. Now we have 3 random vectors perpindicular
|
|
|
|
// to the dir (but not necessarily each other). Note that some (but not all)
|
|
|
|
// of these vectors may be zero length, it the direction happens to line up
|
|
|
|
// with an axis. We scale each by a random amount, sum them up, and since
|
|
|
|
// they are all perpindicular to dir, the weighted sum is too.
|
|
|
|
// Only problem here is that our random scalings might wind us up with
|
|
|
|
// a zero vector. Unlikely, which means almost certain to happen. So
|
|
|
|
// we keep trying till we get a non-zero vector.
|
|
|
|
float lenSq(-1.f);
|
|
|
|
do {
|
|
|
|
float ranXx = sRand.RandMinusOneToOne();
|
|
|
|
float ranXy = sRand.RandMinusOneToOne();
|
|
|
|
float ranXz = sRand.RandMinusOneToOne();
|
|
|
|
retVal.fX = -dir.fZ * ranXy + dir.fY * ranXz;
|
|
|
|
retVal.fY = dir.fZ * ranXx + -dir.fX * ranXz;
|
|
|
|
retVal.fZ = -dir.fY * ranXx + dir.fX * ranXy;
|
|
|
|
|
|
|
|
lenSq = retVal.MagnitudeSquared();
|
|
|
|
|
|
|
|
} while( lenSq <= 0 );
|
|
|
|
|
|
|
|
retVal *= hsFastMath::InvSqrtAppr(lenSq);
|
|
|
|
|
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsVector3 plDynaDecalMgr::IReflectDir(hsVector3 dir) const
|
|
|
|
{
|
|
|
|
hsFastMath::NormalizeAppr(dir); // it's been interpolated.
|
|
|
|
|
|
|
|
// parm of zero returns unaffected dir, parm of one returns cutter direction reflected about dir.
|
|
|
|
// Here's the original math.
|
|
|
|
// Here N is dir, B is -cutter back direction (incoming), k is parm.
|
|
|
|
// Reflection R of B is 2*(N dot B)*N + B
|
|
|
|
// Interpolating gives K*R + (1-K)*N
|
|
|
|
// Simplifying gives (2*K*(N dot B) + (1-K)) * N + K*B
|
|
|
|
// Or something.
|
|
|
|
|
|
|
|
plConst(float) parm(0.5f);
|
|
|
|
|
|
|
|
hsVector3 b = -fCutter->GetBackDir();
|
|
|
|
|
|
|
|
float t = dir.InnerProduct(b);
|
|
|
|
t *= -2.f * parm;
|
|
|
|
t += (1.f - parm);
|
|
|
|
|
|
|
|
hsVector3 ret = dir * t;
|
|
|
|
|
|
|
|
ret += b * parm;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsMatrix44 plDynaDecalMgr::IL2WFromHit(hsPoint3 pos, hsVector3 dir) const
|
|
|
|
{
|
|
|
|
dir = IReflectDir(dir);
|
|
|
|
|
|
|
|
// Negate the firing direction before constructing our psys l2w, because
|
|
|
|
// particles fire in the negative Z direction.
|
|
|
|
dir = -dir;
|
|
|
|
|
|
|
|
hsVector3 up = IRandomUp(dir);
|
|
|
|
hsVector3 acc = up % dir;
|
|
|
|
|
|
|
|
hsMatrix44 l2w;
|
|
|
|
l2w.Reset();
|
|
|
|
l2w.fMap[0][0] = acc[0];
|
|
|
|
l2w.fMap[1][0] = acc[1];
|
|
|
|
l2w.fMap[2][0] = acc[2];
|
|
|
|
|
|
|
|
l2w.fMap[0][1] = up[0];
|
|
|
|
l2w.fMap[1][1] = up[1];
|
|
|
|
l2w.fMap[2][1] = up[2];
|
|
|
|
|
|
|
|
l2w.fMap[0][2] = dir[0];
|
|
|
|
l2w.fMap[1][2] = dir[1];
|
|
|
|
l2w.fMap[2][2] = dir[2];
|
|
|
|
|
|
|
|
l2w.fMap[0][3] = pos[0];
|
|
|
|
l2w.fMap[1][3] = pos[1];
|
|
|
|
l2w.fMap[2][3] = pos[2];
|
|
|
|
|
|
|
|
l2w.NotIdentity();
|
|
|
|
|
|
|
|
return l2w;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::ICutoutCallback(const hsTArray<plCutoutPoly>& cutouts, bool hasWaterHeight, float waterHeight)
|
|
|
|
{
|
|
|
|
hsTArray<plCutoutHit> hits;
|
|
|
|
|
|
|
|
if( (fPartyTime > 0) && fParticles.GetCount() )
|
|
|
|
{
|
|
|
|
if( hasWaterHeight )
|
|
|
|
fCutter->FindHitPointsConstHeight(cutouts, hits, waterHeight);
|
|
|
|
else
|
|
|
|
fCutter->FindHitPoints(cutouts, hits);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < hits.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
int j;
|
|
|
|
for( j = 0; j < fParticles.GetCount(); j++ )
|
|
|
|
{
|
|
|
|
plParticleEmitter* emit = fParticles[j]->GetAvailEmitter();
|
|
|
|
if( emit )
|
|
|
|
{
|
|
|
|
hsMatrix44 l2w = IL2WFromHit(hits[i].fPos, hits[i].fNorm);
|
|
|
|
|
|
|
|
emit->OverrideLocalToWorld(l2w);
|
|
|
|
emit->SetTimeToLive(fPartyTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plDynaDecalMgr::IGetParticles()
|
|
|
|
{
|
|
|
|
if( fParticles.GetCount() != fPartyObjects.GetCount() )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fPartyObjects.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
const plParticleSystem *sys = plParticleSystem::ConvertNoRef(fPartyObjects[i]->GetModifierByType(plParticleSystem::Index()));
|
|
|
|
// const_cast here is just to see if it's in our list, make Find happy.
|
|
|
|
if( sys && (fParticles.kMissingIndex == fParticles.Find(const_cast<plParticleSystem*>(sys))) )
|
|
|
|
{
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(sys->GetKey(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefParticles), plRefFlags::kPassiveRef);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|