You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

661 lines
22 KiB

/*==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/>.
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==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plPassMtlBase - Base class for all Plasma MAX materials //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "hsBitVector.h"
#include "Max.h"
#include "iparamb2.h"
#include "notify.h"
#include "notetrck.h"
#include "plPassMtlBase.h"
#include "plPassBaseParamIDs.h"
#include "plNoteTrackWatcher.h"
#include "plAnimStealthNode.h"
#include "MaxComponent/plMaxAnimUtils.h"
#include "MaxMain/plPlasmaRefMsgs.h"
// For converting from a MAX Mtl
#include "plPassMtl.h"
#include "plBumpMtl.h"
#include "plDecalMtl.h"
using namespace plPassBaseParamIDs;
IMtlParams *plPassMtlBase::fIMtlParams = nil;
//// plPostLoadHandler ///////////////////////////////////////////////////////
// Small class to keep track of all the materials to update after load
class plPostLoadHandler
{
static bool fLoading;
static hsTArray<plPassMtlBase *> fPostLoads;
public:
static bool IsLoading() { return fLoading; }
static void PostLoadFixupFunction( void *param, NotifyInfo *info )
{
fLoading = false;
for( int i = 0; i < fPostLoads.GetCount(); i++ )
fPostLoads[ i ]->PostLoadAnimPBFixup();
fPostLoads.Reset();
UnRegisterNotification( PostLoadFixupFunction, param, NOTIFY_FILE_POST_OPEN );
UnRegisterNotification( PostLoadFixupFunction, param, NOTIFY_FILE_POST_MERGE );
}
static void AddPostLoad( plPassMtlBase *mtl )
{
fLoading = true;
if( fPostLoads.GetCount() == 0 )
{
RegisterNotification( PostLoadFixupFunction, mtl, NOTIFY_FILE_POST_OPEN );
RegisterNotification( PostLoadFixupFunction, mtl, NOTIFY_FILE_POST_MERGE );
}
mtl->SetLoadingFlag( true );
fPostLoads.Append( mtl );
}
static void RemovePostLoad( plPassMtlBase *mtl )
{
for( int i = 0; i < fPostLoads.GetCount(); i++ )
{
if( fPostLoads[ i ] == mtl )
{
fPostLoads.Remove( i );
break;
}
}
}
};
hsTArray<plPassMtlBase *> plPostLoadHandler::fPostLoads;
bool plPostLoadHandler::fLoading = false;
plPassMtlBase::plPassMtlBase( BOOL loading ) : fNTWatcher( nil ), fBasicPB(NULL), fAdvPB(NULL), fLayersPB(NULL), fAnimPB(NULL),
fLoading( loading )
{
fNTWatcher = TRACKED_NEW plNoteTrackWatcher( this );
Reset();
}
plPassMtlBase::~plPassMtlBase()
{
if( fLoading )
plPostLoadHandler::RemovePostLoad( this );
// Force the watcher's parent pointer to nil, otherwise the de-ref will attempt to re-delete us
fNTWatcher->SetReference( plNoteTrackWatcher::kRefParentMtl, nil );
delete fNTWatcher;
fNTWatcher = nil;
// Manually delete our notetrack refs, otherwise there'll be hell to pay
for( int i = 0; i < fNotetracks.GetCount(); i++ )
{
if( fNotetracks[ i ] != nil )
DeleteReference( kRefNotetracks + i );
}
}
void plPassMtlBase::Reset( void )
{
fIValid.SetEmpty();
}
//// Stealth Accessors ///////////////////////////////////////////////////////
int plPassMtlBase::GetNumStealths( void )
{
return IGetNumStealths( true );
}
plAnimStealthNode *plPassMtlBase::GetStealth( int index )
{
return IGetStealth( index, false );
}
int plPassMtlBase::IGetNumStealths( hsBool update )
{
if( update )
IUpdateAnimNodes();
return fAnimPB->Count( (ParamID)kPBAnimStealthNodes );
}
plAnimStealthNode *plPassMtlBase::IGetStealth( int index, hsBool update )
{
if( update )
IUpdateAnimNodes();
return (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, index );
}
plAnimStealthNode *plPassMtlBase::IFindStealth( const char *segmentName )
{
int i;
for( i = 0; i < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); i++ )
{
plAnimStealthNode *node = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, i );
const char *name = node->GetSegmentName();
if( node != nil && strcmp( name, segmentName ) == 0 )
{
return node;
}
}
return nil;
}
//// IVerifyStealthPresent ///////////////////////////////////////////////////
// Ensures that we have a stealth for the given segment.
plAnimStealthNode *plPassMtlBase::IVerifyStealthPresent( const char *animName )
{
// If we're in the middle of loading, don't check
if (plPostLoadHandler::IsLoading())
return nil;
plAnimStealthNode *stealth = IFindStealth( animName );
if( stealth == nil )
{
// New segment, add a new stealth node
stealth = (plAnimStealthNode *)GetCOREInterface()->CreateInstance( HELPER_CLASS_ID, ANIMSTEALTH_CLASSID );
INode *node = GetCOREInterface()->CreateObjectNode( stealth );
stealth->SetSegment( ( strcmp(animName, ENTIRE_ANIMATION_NAME) != 0 ) ? animName : nil );
stealth->SetNodeName( GetName() );
node->Freeze( true );
// Skip the attach, since we might not find a valid INode. This will leave the node attached to the scene
// root, which is fine. Since we just care about it being SOMEWHERE in the scene hierarchy
/*
if( fAnimPB->Count( (ParamID)kPBAnimStealthNodes ) > 0 )
{
plAnimStealthNode *first = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, 0 );
first->GetINode()->AttachChild( node );
}
*/
fAnimPB->Append( (ParamID)kPBAnimStealthNodes, 1, (ReferenceTarget **)&stealth );
const char *realName = stealth->GetSegmentName();
fStealthsChanged = true;
}
else
{
// Exists already, we're ok
stealth->SetParentMtl( this );
}
return stealth;
}
//// Change Callbacks ////////////////////////////////////////////////////////
void plPassMtlBase::RegisterChangeCallback( plMtlChangeCallback *callback )
{
if( fChangeCallbacks.Find( callback ) == fChangeCallbacks.kMissingIndex )
fChangeCallbacks.Append( callback );
}
void plPassMtlBase::UnregisterChangeCallback( plMtlChangeCallback *callback )
{
int idx = fChangeCallbacks.Find( callback );
if( idx != fChangeCallbacks.kMissingIndex )
fChangeCallbacks.Remove( idx );
}
//// IUpdateAnimNodes ////////////////////////////////////////////////////////
// Updates the list of stealth nodes in the anim paramBlock to match our
// list of anim segments.
void plPassMtlBase::IUpdateAnimNodes( void )
{
// Our beautiful hack, to make sure we don't update until we actually are loaded
if( fLoading )
return;
SegmentMap *segMap = GetAnimSegmentMap( this, nil );
hsTArray<plAnimStealthNode *> goodNodes;
// Keep track of whether we change anything
fStealthsChanged = false;
// Verify one for "entire animation"
plAnimStealthNode *stealth = IVerifyStealthPresent( ENTIRE_ANIMATION_NAME );
goodNodes.Append( stealth );
// Verify segment nodes
if( segMap != nil )
{
for( SegmentMap::iterator i = segMap->begin(); i != segMap->end(); i++ )
{
SegmentSpec *spec = (*i).second;
if( spec->fType == SegmentSpec::kAnim )
{
plAnimStealthNode *stealth = IVerifyStealthPresent( spec->fName );
goodNodes.Append( stealth );
}
}
DeleteSegmentMap( segMap );
}
// Remove nodes that no longer have segments
int idx;
for( idx = 0; idx < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); )
{
plAnimStealthNode *node = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, idx );
if( node != nil && goodNodes.Find( node ) == goodNodes.kMissingIndex )
{
fAnimPB->Delete( (ParamID)kPBAnimStealthNodes, idx, 1 );
// GetCOREInterface()->DeleteNode( node->GetINode() );
fStealthsChanged = true;
}
else
idx++;
}
if( fStealthsChanged )
{
// Yup, our list of stealths got updated. Notify everyone of such.
for( idx = 0; idx < fChangeCallbacks.GetCount(); idx++ )
fChangeCallbacks[ idx ]->SegmentListChanged();
}
}
//// NameChanged /////////////////////////////////////////////////////////////
// Notify from NTWatcher so we can update the names of our stealth nodes
void plPassMtlBase::NameChanged( void )
{
for( int idx = 0; idx < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); idx++ )
{
plAnimStealthNode *node = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, idx );
if( node != nil )
node->SetNodeName( GetName() );
}
}
//// NoteTrackAdded/Removed //////////////////////////////////////////////////
// Notifies from NTWatcher so we can update our list of stealths
void plPassMtlBase::NoteTrackAdded( void )
{
int i;
// Make a ref to our new notetrack
for( i = 0; i < NumNoteTracks(); i++ )
{
NoteTrack *track = GetNoteTrack( i );
if( fNotetracks.Find( track ) == fNotetracks.kMissingIndex )
{
ReplaceReference(kRefNotetracks + fNotetracks.GetCount(), track);
break;
}
}
for( i = 0; i < fChangeCallbacks.GetCount(); i++ )
fChangeCallbacks[ i ]->NoteTrackListChanged();
IUpdateAnimNodes();
}
void plPassMtlBase::NoteTrackRemoved( void )
{
int i;
hsBitVector stillThere;
// Make a ref to our new notetrack
for( i = 0; i < NumNoteTracks(); i++ )
{
NoteTrack *track = GetNoteTrack( i );
int idx = fNotetracks.Find( track );
if( idx != fNotetracks.kMissingIndex )
stillThere.Set( idx );
}
for( i = 0; i < fNotetracks.GetCount(); i++ )
{
if( !stillThere.IsBitSet( i ) && fNotetracks[ i ] != nil )
{
// DeleteReference( kRefNotetracks + i );
SetReference( kRefNotetracks + i, nil );
}
}
for( i = 0; i < fChangeCallbacks.GetCount(); i++ )
fChangeCallbacks[ i ]->NoteTrackListChanged();
IUpdateAnimNodes();
}
//////////////////////////////////////////////////////////////////////////////
//// MAX Ref Stuff ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// NumRefs /////////////////////////////////////////////////////////////////
int plPassMtlBase::NumRefs()
{
return 4 + fNotetracks.GetCount();
}
//// GetReference ////////////////////////////////////////////////////////////
RefTargetHandle plPassMtlBase::GetReference( int i )
{
if( i >= kRefNotetracks && i < kRefNotetracks + fNotetracks.GetCount() )
return fNotetracks[ i - kRefNotetracks ];
return NULL;
}
//// SetReference ////////////////////////////////////////////////////////////
void plPassMtlBase::SetReference(int i, RefTargetHandle rtarg)
{
if( i >= kRefNotetracks )
{
fNotetracks.ExpandAndZero(i - kRefNotetracks + 1);
fNotetracks[i - kRefNotetracks] = (NoteTrack*)rtarg;
}
}
//// NotifyRefChanged ////////////////////////////////////////////////////////
RefResult plPassMtlBase::NotifyRefChanged( Interval changeInt, RefTargetHandle hTarget,
PartID &partID, RefMessage message )
{
switch( message )
{
case REFMSG_CHANGE:
fIValid.SetEmpty();
// see if this message came from a changing parameter in the pblock,
// if so, limit rollout update to the changing item
if (hTarget == fBasicPB || hTarget == fAdvPB || hTarget == fLayersPB || hTarget == fAnimPB)
{
IParamBlock2 *pb = (IParamBlock2*)hTarget;
ParamID changingParam = pb->LastNotifyParamID();
pb->GetDesc()->InvalidateUI( changingParam );
// And let the SceneWatcher know that the material on some of it's
// referenced objects changed.
NotifyDependents( FOREVER, PART_ALL, REFMSG_USER_MAT );
}
else
{
// Was it a notetrack ref?
if( fNotetracks.Find( (NoteTrack *)hTarget ) != fNotetracks.kMissingIndex )
{
// Yup, so update our notetrack list
IUpdateAnimNodes();
}
}
break;
case REFMSG_TARGET_DELETED:
NoteTrackRemoved();
break;
}
return REF_SUCCEED;
}
//////////////////////////////////////////////////////////////////////////////
//// Standard IO (or non-standard, as the case may be) ///////////////////////
//////////////////////////////////////////////////////////////////////////////
//// PostLoadAnimPBFixup /////////////////////////////////////////////////////
// Takes the old version of the anim paramblock and translates it into the
// new version.
// Note that there's an interesting (?) side effect of this: for new materials,
// we'll incorrectly detect them as the "old" format and fix-up them as well.
// This means that we'll end up with the same defaults for (Entire Animation)
// that we had for the old materials. We can easily change the defaults by
// changing the defaults for the old paramblock though.
// Also, we go ahead and re-work the stealth parent pointers here, since it
// appears to be the ONLY time we can do it and have it reliably work. ARRGH!
//
// History of the options that we CAN'T do and why (and hence why this):
// - ParamBlock accessor doesn't work. For some reason, the accessor isn't
// always called on load, even with P_CALLSETS_ON_LOAD specified
// - Doing it on Load() doesn't work, because neither the ParamBlocks are
// guaranteed to be fully loaded (with their tabs filled) nor are the
// notetracks necessarily attached yet
// - Notetracks can also possibly be attached BEFORE load, which doesn't
// do us a damned bit of good, so we need to make sure we're fully
// loaded before we run this function. Unfortunately, the only time
// we're guaranteed THAT is by a FILE_POST_OPEN notify. (post-load
// callbacks don't work because they're called right after our object
// is loaded but not necessarily before the notetracks are attached)
void plPassMtlBase::PostLoadAnimPBFixup( void )
{
SetLoadingFlag( false );
#ifdef MCN_UPGRADE_OLD_ANIM_BLOCKS
if( fAnimPB->Count( (ParamID)kPBAnimStealthNodes ) == 0 )
{
// Yup, old style. So our update process looks like this:
// 1) Create stealths for all our segments as we are now
// 2) Set the parameters on all of them to our old defaults (no autostart,
// loop on entire, no ease).
// 3) Copy the old paramblock values to the single stealth indicated by
// the old PB
// Step 1...
IUpdateAnimNodes();
// Step 2...
for( int i = 0; i < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); i++ )
{
plAnimStealthNode *node = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, i );
const char *name = node->GetSegmentName();
node->SetAutoStart( false );
node->SetLoop( true, ENTIRE_ANIMATION_NAME );
node->SetEaseIn( plAnimEaseTypes::kNoEase, 1.f, 1.f, 1.f );
node->SetEaseOut( plAnimEaseTypes::kNoEase, 1.f, 1.f, 1.f );
}
// Step 3...
const char *oldSel = (const char *)fAnimPB->GetStr( (ParamID)kPBAnimName );
if( oldSel == nil )
oldSel = ENTIRE_ANIMATION_NAME;
plAnimStealthNode *myNew = IFindStealth( oldSel );
if( myNew != nil )
{
#pragma warning( push ) // Forcing value to bool true or false (go figure, i'm even explicitly casting)
#pragma warning( disable:4800 ) // Forcing value to bool true or false (go figure, i'm even explicitly casting)
myNew->SetAutoStart( (bool)fAnimPB->GetInt( (ParamID)kPBAnimAutoStart ) );
myNew->SetLoop( (bool)fAnimPB->GetInt( (ParamID)kPBAnimLoop ),
(char *)fAnimPB->GetStr( (ParamID)kPBAnimLoopName ) );
myNew->SetEaseIn( (UInt8)fAnimPB->GetInt( (ParamID)kPBAnimEaseInType ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseInLength ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseInMin ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseInMax ) );
myNew->SetEaseOut( (UInt8)fAnimPB->GetInt( (ParamID)kPBAnimEaseOutType ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseOutLength ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseOutMin ),
(hsScalar)fAnimPB->GetFloat( (ParamID)kPBAnimEaseOutMax ) );
#pragma warning( pop )
}
}
#endif // MCN_UPGRADE_OLD_ANIM_BLOCKS
// Make sure the parent is set tho. Note: we have to do this because, for some *(#$&(* reason,
// when we're loading a file, MAX can somehow add the stealths to our tab list WITHOUT calling
// the accessor for it (and the tab is empty on the CallSetsOnLoad() pass, for some reason).
for( int i = 0; i < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); i++ )
{
plAnimStealthNode *node = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, i );
node->SetParentMtl( this );
}
}
#define MTL_HDR_CHUNK 0x4000
//// Load ////////////////////////////////////////////////////////////////////
// Our actual MAX load function
IOResult plPassMtlBase::Load(ILoad *iload)
{
plPostLoadHandler::AddPostLoad( this );
IOResult res;
int id;
while (IO_OK==(res=iload->OpenChunk()))
{
switch(id = iload->CurChunkID())
{
case MTL_HDR_CHUNK:
res = MtlBase::Load(iload);
break;
}
iload->CloseChunk();
if (res!=IO_OK)
return res;
}
return IO_OK;
}
//// Save ////////////////////////////////////////////////////////////////////
// The MAX flip-side
IOResult plPassMtlBase::Save(ISave *isave)
{
IOResult res;
isave->BeginChunk(MTL_HDR_CHUNK);
res = MtlBase::Save(isave);
if (res!=IO_OK) return res;
isave->EndChunk();
return IO_OK;
}
//// ICloneBase //////////////////////////////////////////////////////////////
void plPassMtlBase::ICloneBase( plPassMtlBase *target, RemapDir &remap )
{
*((MtlBase*)target) = *((MtlBase*)this);
ICloneRefs( target, remap );
for( int idx = 0; idx < fAnimPB->Count( (ParamID)kPBAnimStealthNodes ); idx++ )
{
IParamBlock2 *pb = target->fAnimPB;
plAnimStealthNode *stealth = (plAnimStealthNode *)fAnimPB->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, idx );
pb->SetValue( (ParamID)kPBAnimStealthNodes, 0, remap.CloneRef( stealth ), idx );
stealth = (plAnimStealthNode *)pb->GetReferenceTarget( (ParamID)kPBAnimStealthNodes, 0, idx );
INode *node = GetCOREInterface()->CreateObjectNode( stealth );
stealth->SetNodeName( GetName() );
node->Freeze( true );
}
BaseClone(this, target, remap);
target->fIValid.SetEmpty();
}
//// ConvertToPassMtl ////////////////////////////////////////////////////////
// Static convert to our plPassMtlBase type, if possible
plPassMtlBase *plPassMtlBase::ConvertToPassMtl( Mtl *mtl )
{
if( mtl == nil )
return nil;
if( mtl->ClassID() == PASS_MTL_CLASS_ID
|| mtl->ClassID() == BUMP_MTL_CLASS_ID
|| mtl->ClassID() == DECAL_MTL_CLASS_ID )
{
return (plPassMtlBase *)mtl;
}
return nil;
}
//// SetupProperties /////////////////////////////////////////////////////////
hsBool plPassMtlBase::SetupProperties( plMaxNode *node, plErrorMsg *pErrMsg )
{
hsBool ret = true;
// Call SetupProperties on all our animStealths if we have any
int i, count = IGetNumStealths();
for( i = 0; i < count; i++ )
{
if( !IGetStealth( i, false )->SetupProperties( node, pErrMsg ) )
ret = false;
}
return ret;
}
//// ConvertDeInit ///////////////////////////////////////////////////////////
hsBool plPassMtlBase::ConvertDeInit( plMaxNode *node, plErrorMsg *pErrMsg )
{
hsBool ret = true;
// Call ConvertDeInit on all our animStealths if we have any
int i, count = IGetNumStealths();
for( i = 0; i < count; i++ )
{
if( !IGetStealth( i, false )->ConvertDeInit( node, pErrMsg ) )
ret = false;
}
return ret;
}