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.

971 lines
32 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==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plAnimStealthNode - Stealthy hidden INode that represents a single //
// segment's worth of animation info for a material. //
// Stored as an INode so they can be "selected" //
// by components as targets of animation messages. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plAnimStealthNode.h"
#include "plPassMtlBase.h"
#include "resource.h"
#include "../MaxComponent/plMaxAnimUtils.h"
#include "../MaxComponent/plPickNodeBase.h"
#include "iparamm2.h"
extern TCHAR *GetString( int id );
extern HINSTANCE hInstance;
//// Stealthy Class Desc /////////////////////////////////////////////////////
class plStealthClassDesc : public ClassDesc2
{
public:
int IsPublic() { return FALSE; }
void* Create(BOOL loading) { return TRACKED_NEW plAnimStealthNode(loading); }
const TCHAR* ClassName() { return GetString( IDS_STEALTH_NAME ); }
SClass_ID SuperClassID() { return HELPER_CLASS_ID; }
Class_ID ClassID() { return ANIMSTEALTH_CLASSID; }
const TCHAR* Category() { return NULL; }
const TCHAR* InternalName() { return _T("PlasmaAnimStealthInfo"); }
HINSTANCE HInstance() { return hInstance; }
};
static plStealthClassDesc sStealthClassDesc;
ClassDesc2* GetStealthClassDesc() { return &sStealthClassDesc; }
//// plStealthDlgProc /////////////////////////////////////////////////////////
// Dialog proc for the anim stealth child dialog
class plStealthDlgProc : public ParamMap2UserDlgProc
{
protected:
// Combo itemdata values
enum
{
kName, // Name of an animation/loop
kDefault, // Default combo value
kInvalid, // Invalid entry (couldn't find)
};
SegmentMap *fSegMap;
HWND fhWnd;
public:
BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void DeleteThis() { IDeleteSegMap(); }
void SetThing(ReferenceTarget *m);
void Update( TimeValue t, Interval &valid, IParamMap2 *pmap );
protected:
// Set all the controls to their stored value
void IInitControls( plAnimStealthNode *stealth, IParamBlock2 *pb);
// Deletes all the allocated memory
void IDeleteSegMap();
void ILoadLoops(IParamBlock2 *pb);
void ISetSel(HWND hCombo, const char *name);
};
const char *kAnimNameNone = ENTIRE_ANIMATION_NAME;
static plStealthDlgProc sStealthDlgProc;
//// Stealthy ParamBlock Desc ////////////////////////////////////////////////
static plEaseAccessor sEaseAccessor( plAnimStealthNode::kBlockPB, plAnimStealthNode::kPBEaseInMin,
plAnimStealthNode::kPBEaseInMax, plAnimStealthNode::kPBEaseInLength,
plAnimStealthNode::kPBEaseOutMin, plAnimStealthNode::kPBEaseOutMax,
plAnimStealthNode::kPBEaseOutLength );
ParamBlockDesc2 plAnimStealthNode::sAnimStealthPB
(
kBlockPB, _T( "animStealth" ), IDS_STEALTH_NAME, GetStealthClassDesc(),//NULL,
P_AUTO_CONSTRUCT + P_AUTO_UI, kRefParamBlock,
// UI
IDD_STEALTH_ANIM, IDS_STEALTH_NAME, 0, 0, &sStealthDlgProc,
kPBName, _T("animName"), TYPE_STRING, 0, 0,
end,
kPBAutoStart, _T("autoStart"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_AUTO_START,
p_default, FALSE,
end,
kPBLoop, _T("loop"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_LOOP,
p_default, TRUE,
end,
kPBLoopName, _T("loopName"), TYPE_STRING, 0, 0,
end,
// Anim Ease
kPBEaseInType, _T("easeInType"), TYPE_INT, 0, 0,
p_ui, TYPE_RADIO, 3, IDC_PASS_ANIM_EASE_IN_NONE, IDC_PASS_ANIM_EASE_IN_CONST_ACCEL, IDC_PASS_ANIM_EASE_IN_SPLINE,
p_vals, plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
p_default, plAnimEaseTypes::kNoEase,
end,
kPBEaseInLength, _T("easeInLength"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_IN_TIME, IDC_PASS_ANIM_EASE_IN_TIME_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
kPBEaseInMin, _T("easeInMin"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_IN_MIN, IDC_PASS_ANIM_EASE_IN_MIN_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
kPBEaseInMax, _T("easeInMax"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_IN_MAX, IDC_PASS_ANIM_EASE_IN_MAX_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
kPBEaseOutType, _T("easeOutType"), TYPE_INT, 0, 0,
p_ui, TYPE_RADIO, 3, IDC_PASS_ANIM_EASE_OUT_NONE, IDC_PASS_ANIM_EASE_OUT_CONST_ACCEL, IDC_PASS_ANIM_EASE_OUT_SPLINE,
p_vals, plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
p_default, plAnimEaseTypes::kNoEase,
end,
kPBEaseOutLength, _T("easeOutLength"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_OUT_TIME, IDC_PASS_ANIM_EASE_OUT_TIME_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
kPBEaseOutMin, _T("easeOutMin"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_OUT_MIN, IDC_PASS_ANIM_EASE_OUT_MIN_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
kPBEaseOutMax, _T("easeOutMax"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_PASS_ANIM_EASE_OUT_MAX, IDC_PASS_ANIM_EASE_OUT_MAX_SPIN, 1.0,
p_accessor, &sEaseAccessor,
end,
end
);
plAnimStealthNode::plAnimStealthNode( BOOL loading ) : fClassDesc(nil), fParamBlock(nil), fParentMtl(nil)
{
fCachedSegMap = nil;
fClassDesc = &sStealthClassDesc;
fClassDesc->MakeAutoParamBlocks( this );
}
plAnimStealthNode::~plAnimStealthNode()
{
// DeleteAllRefsFromMe();
}
CreateMouseCallBack *plAnimStealthNode::GetCreateMouseCallBack()
{
return nil;
}
void plAnimStealthNode::SetParentMtl( plPassMtlBase *parent )
{
fParentMtl = parent;
}
bool plAnimStealthNode::CanConvertToStealth( INode *objNode )
{
return ( ConvertToStealth( objNode ) != nil );
}
plAnimStealthNode *plAnimStealthNode::ConvertToStealth( INode *objNode )
{
if( objNode == nil )
return nil;
Object *obj = objNode->GetObjectRef();
if( obj == nil )
return nil;
if( obj->CanConvertToType( ANIMSTEALTH_CLASSID ) )
return (plAnimStealthNode *)obj;
return nil;
}
const char *plAnimStealthNode::GetSegmentName( void ) const
{
const char *str = fParamBlock->GetStr( (ParamID)kPBName );
if( str == nil || str[ 0 ] == 0 )
return ENTIRE_ANIMATION_NAME;
return str;
}
void plAnimStealthNode::SetSegment( const char *name )
{
if( name == nil || strcmp(name, ENTIRE_ANIMATION_NAME) == 0 || name[ 0 ] == 0 )
fParamBlock->SetValue( (ParamID)kPBName, 0, "" );
else
fParamBlock->SetValue( (ParamID)kPBName, 0, (char *)name );
}
void plAnimStealthNode::SetNodeName( const char *parentName )
{
INode *node = GetINode();
if( node != nil )
{
char name[ 512 ], newName[ 512 ];
sprintf( name, "%s : %s", parentName, GetSegmentName() );
if( GetCOREInterface()->GetINodeByName( name ) != nil )
{
// For whatever reason, MakeNameUnique() doesn't ACTUALLY make a name unique!
// So we just need to more or less do it ourselves...
int i;
for( i = 1; i < 1024; i++ )
{
sprintf( newName, "%s(%d)", name, i );
if( GetCOREInterface()->GetINodeByName( newName ) == nil )
break;
}
if( i == 1024 )
{
// You've got to be kidding me...
char msg[ 2048 ];
sprintf( msg, "WARNING: For some reason, we cannot find a unique name for the node '%s'. This"
" will most likely cause export problems. Exactly how many of these do we HAVE??",
name );
hsMessageBox( msg, "WARNING!", hsMessageBoxNormal );
}
}
else
strcpy( newName, name );
node->SetName( newName );
}
}
int plAnimStealthNode::NumParamBlocks()
{
return 1;
}
IParamBlock2 *plAnimStealthNode::GetParamBlock( int i )
{
if( i == kRefParamBlock )
return fParamBlock;
return nil;
}
IParamBlock2 *plAnimStealthNode::GetParamBlockByID( BlockID id )
{
if( fParamBlock && fParamBlock->ID() == id )
return fParamBlock;
return nil;
}
RefTargetHandle plAnimStealthNode::Clone(RemapDir &remap)
{
plAnimStealthNode *obj = (plAnimStealthNode *)fClassDesc->Create( false );
// Do the base clone
BaseClone(this, obj, remap);
// Copy our references
if (fParamBlock)
obj->ReplaceReference( kRefParamBlock, fParamBlock->Clone( remap ) );
return obj;
}
void plAnimStealthNode::BuildMesh(TimeValue t)
{
}
void plAnimStealthNode::FreeCaches()
{
}
void plAnimStealthNode::GetLocalBoundBox(TimeValue t, INode *node, ViewExp *vpt, Box3 &box)
{
box.MakeCube(Point3(0,0,0), 0);
}
void plAnimStealthNode::GetWorldBoundBox(TimeValue t, INode *node, ViewExp *vpt, Box3 &box)
{
box.MakeCube(Point3(0,0,0), 0);
}
int plAnimStealthNode::Display(TimeValue t, INode *node, ViewExp *vpt, int flags)
{
return 0;
}
int plAnimStealthNode::HitTest(TimeValue t, INode *node, int type, int crossing, int flags, IPoint2 *p, ViewExp *vpt)
{
return 0;
}
int plAnimStealthNode::NumRefs()
{
return 1;
}
RefTargetHandle plAnimStealthNode::GetReference( int i )
{
if( i == kRefParamBlock )
return fParamBlock;
else if( i == kRefParentMtl )
return fParentMtl;
return nil;
}
void plAnimStealthNode::SetReference( int i, RefTargetHandle rtarg )
{
if( i == kRefParamBlock )
fParamBlock = (IParamBlock2 *)rtarg;
else if( i == kRefParentMtl )
fParentMtl = (plPassMtlBase *)rtarg;
}
RefResult plAnimStealthNode::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message)
{
return REF_SUCCEED;
}
IOResult plAnimStealthNode::Save(ISave* isave)
{
return IO_OK;
}
IOResult plAnimStealthNode::Load(ILoad* iload)
{
return IO_OK;
}
plPassMtlBase *plAnimStealthNode::GetParentMtl( void )
{
return fParentMtl;
}
class plGetRefs : public DependentEnumProc
{
public:
hsTArray<ReferenceMaker *> fList;
plGetRefs() { }
virtual int proc( ReferenceMaker *rmaker )
{
fList.Append( rmaker );
return DEP_ENUM_CONTINUE;
}
};
hsBool plAnimStealthNode::IsParentUsedInScene( void )
{
if( GetParentMtl() == nil )
return false;
// There are two possibilities: either a node uses us and thus has a ref to us,
// or a multi-sub uses us that a node has a ref to us.
// Note: we could do the loop as a helper function, but we only do it twice,
// so it's not *really* worth the effort...
//// NOTE: the following doesn't seem to work, but keeping here in case it ever does.
//// What really actually finds something is the enum dependents loop below
const char *mtlName = GetParentMtl()->GetName();
RefList &refList = GetRefList();
RefListItem *item = refList.FirstItem();
while( item != nil )
{
TSTR s;
item->maker->GetClassName( s );
if( item->maker->SuperClassID() == BASENODE_CLASS_ID && !CanConvertToStealth( (INode *)( item->maker ) ) )
return true; // Horray, a node has a ref to us!
else if( item->maker->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
{
// Multi-sub, run the refs on that guy (we only go one up)
Mtl *multisub = (Mtl *)item->maker;
RefList &refList2 = multisub->GetRefList();
RefListItem *item2 = refList.FirstItem();
while( item2 != nil )
{
if( item2->maker->SuperClassID() == BASENODE_CLASS_ID )
return true; // Horray, a node has a ref to us!
item2 = item2->next;
}
// No go, keep trying
}
else if( item->maker->SuperClassID() == MATERIAL_CLASS_ID )
{
int q = 0;
}
item = item->next;
}
// Enum dependents
int i;
plGetRefs callback;
GetParentMtl()->EnumDependents( &callback );
for( i = 0; i < callback.fList.GetCount(); i++ )
{
ReferenceMaker *maker = callback.fList[ i ];
TSTR s;
maker->GetClassName( s );
if( maker->SuperClassID() == BASENODE_CLASS_ID && !CanConvertToStealth( (INode *)maker ) )
return true; // Horray, a node has a ref to us!
}
return false;
}
INode *plAnimStealthNode::GetINode()
{
// Go through the reflist looking for RefMakers with a ref to this component.
// There should only be one INode in this list.
RefList &refList = GetRefList();
RefListItem *item = refList.FirstItem();
while( item )
{
if( item->maker->SuperClassID() == BASENODE_CLASS_ID )
return (INode *)item->maker;
item = item->next;
}
return nil;
}
void plStealthDlgProc::Update(TimeValue t, Interval& valid, IParamMap2* pmap)
{
// Does the pmap match our pmap?
}
//// plStealthMouseOverrideProc //////////////////////////////////////////////
// Because of wonderful linking problems with the MAX libraries, we can't
// actually use CreateChildMParamMap2 like we should. So instead, we use
// CreateChildCPParamMap2. However, *that* function calls the wrong interface
// to handle untrapped mouse messages, with the result that clicking and
// dragging scrolls the command pane (where components are displayed) instead
// of the material editor pane.
// To override this, we subclass each dialog so that we can capture the mouse
// messages before MAX processes them and then reroute them appropriately.
// Note: because MAX already uses the window long of the given window, we can't
// store the old proc of the window. However, since we always use
// CreateChildCPParamMap2, and because the MAX source code shows us that it
// always uses the same dialog proc for all windows created with that function,
// we can simply store the address of that proc the first time we subclass and
// use it for restoring every time thereafter (see the following DlgProc)
static WNDPROC sOldStealthDlgProc = nil;
static INT_PTR CALLBACK plStealthMouseOverrideProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
IParamMap2 *map = (IParamMap2 *)GetWindowLongPtr( hWnd, GWLP_USERDATA );
switch( msg )
{
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MOUSEMOVE:
{
// We don't want the COREInterface to process our mouse messages with RollupMouseMessage;
// rather, we want IMtlParams to do it just like it would if we could actually call
// CreateChildMParamMap2
IParamBlock2 *pb = map->GetParamBlock();
if( pb != nil )
{
plAnimStealthNode *stealth = (plAnimStealthNode *)pb->GetOwner();
if( stealth != nil )
{
plPassMtlBase *mtl = (plPassMtlBase *)stealth->GetParentMtl();
mtl->fIMtlParams->RollupMouseMessage( hWnd, msg, wParam, lParam );
}
}
return 0;
}
}
if( sOldStealthDlgProc != nil )
return CallWindowProc( sOldStealthDlgProc, hWnd, msg, wParam, lParam );
else
return 0;
}
BOOL plStealthDlgProc::DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
IParamBlock2 *pb = map->GetParamBlock();
plAnimStealthNode *stealth = (plAnimStealthNode *)pb->GetOwner();
switch (msg)
{
case WM_INITDIALOG:
{
// Install our override proc so we can capture mouse messages ourselves.
// Note that the first time, we grab the old proc so we can restore with that
// one every time after, since they should always be the same proc
WNDPROC old = (WNDPROC)SetWindowLongPtr( hWnd, DWLP_DLGPROC, (LONG_PTR)plStealthMouseOverrideProc );
if( sOldStealthDlgProc == nil )
sOldStealthDlgProc = old;
fhWnd = hWnd;
IInitControls( stealth, pb );
return TRUE;
}
case WM_DESTROY:
// Restore our old proc
SetWindowLongPtr( hWnd, DWLP_DLGPROC, (LONG_PTR)sOldStealthDlgProc );
break;
case WM_ENABLE:
// The entire dialog was either enabled or disabled.
break;
case WM_COMMAND:
// Loop selection changed
if( LOWORD( wParam ) == IDC_LOOPS && HIWORD( wParam ) == CBN_SELCHANGE )
{
// If a loop is selected, save it
HWND hCombo = (HWND)lParam;
int sel = SendMessage( hCombo, CB_GETCURSEL, 0, 0 );
if( sel != CB_ERR )
{
if( SendMessage( hCombo, CB_GETITEMDATA, sel, 0 ) == kName )
{
char buf[256];
SendMessage( hCombo, CB_GETLBTEXT, sel, (LPARAM)buf );
pb->SetValue( (ParamID)plAnimStealthNode::kPBLoopName, 0, buf );
}
else
pb->SetValue( (ParamID)plAnimStealthNode::kPBLoopName, 0, "" );
}
return TRUE;
}
// Auto-start or loop checkbox checked
if( LOWORD( wParam ) == IDC_LOOP && HIWORD( wParam ) == BN_CLICKED )
{
BOOL checked = ( SendMessage( (HWND)lParam, BM_GETCHECK, 0, 0 ) == BST_CHECKED );
pb->SetValue( plAnimStealthNode::kPBLoop, 0, checked );
EnableWindow( GetDlgItem( hWnd, IDC_LOOPS ), checked );
return TRUE;
}
// Refresh clicked
else if( LOWORD( wParam ) == IDC_REFRESH_ANIMS && HIWORD( wParam ) == BN_CLICKED )
{
IInitControls( stealth, pb );
return TRUE;
}
break;
}
return FALSE;
}
void plStealthDlgProc::SetThing(ReferenceTarget *m)
{
plAnimStealthNode *stealth = (plAnimStealthNode *)m;
IParamBlock2 *pb = stealth->GetParamBlockByID( plAnimStealthNode::kBlockPB );
IInitControls( stealth, pb );
}
void plStealthDlgProc::IDeleteSegMap()
{
// If we have a segment map, delete the memory associated with it
DeleteSegmentMap( fSegMap );
fSegMap = nil;
}
void plStealthDlgProc::ISetSel(HWND hCombo, const char *name)
{
// If there is a name, try and set that
if( name && strcmp( name, "" ) )
{
int idx = SendMessage( hCombo, CB_FINDSTRINGEXACT, -1, (LPARAM)name );
// If we can't find the saved name add a "not found" entry, so they know what it was
if( idx == -1 )
{
char buf[256];
sprintf( buf, "(not found) %s", name );
idx = SendMessage( hCombo, CB_ADDSTRING, 0, (LPARAM)buf );
SendMessage( hCombo, CB_SETITEMDATA, idx, kInvalid );
}
SendMessage( hCombo, CB_SETCURSEL, idx, 0 );
}
// No name, set it to none
else
{
int count = SendMessage( hCombo, CB_GETCOUNT, 0, 0 );
for( int i = 0; i < count; i++ )
{
if( SendMessage( hCombo, CB_GETITEMDATA, i, 0 ) == kDefault )
SendMessage( hCombo, CB_SETCURSEL, i, 0 );
}
}
}
void plStealthDlgProc::IInitControls( plAnimStealthNode *stealth, IParamBlock2 *pb )
{
IDeleteSegMap();
if( stealth->GetParentMtl() != nil )
{
fSegMap = GetAnimSegmentMap( stealth->GetParentMtl(), nil );
ILoadLoops( pb );
}
else
{
// ?? What should we do?
fSegMap = nil;
hsStatusMessage( "No parent material yet in plStealthDlgProc::IInitControls()...not good..." );
}
// Enable/disable the loop dropdown
EnableWindow( GetDlgItem( fhWnd, IDC_LOOPS ), pb->GetInt( (ParamID)plAnimStealthNode::kPBLoop ) );
}
void plStealthDlgProc::ILoadLoops(IParamBlock2 *pb)
{
HWND hLoops = GetDlgItem( fhWnd, IDC_LOOPS );
SendMessage( hLoops, CB_RESETCONTENT, 0, 0 );
// Add the default option
int defIdx = SendMessage( hLoops, CB_ADDSTRING, 0, (LPARAM)ENTIRE_ANIMATION_NAME );
SendMessage( hLoops, CB_SETITEMDATA, defIdx, kDefault );
const char *segName = pb->GetStr( (ParamID)plAnimStealthNode::kPBName );
if( segName == nil || fSegMap == nil )
{
// Default of "entire animation", no other loop options
SendMessage( hLoops, CB_SETCURSEL, defIdx, 0 );
return;
}
SegmentSpec *animSpec = (*fSegMap)[ segName ];
if( animSpec && fSegMap )
{
// for each segment we found:
for( SegmentMap::iterator i = fSegMap->begin(); i != fSegMap->end(); i++ )
{
SegmentSpec *spec = (*i).second;
if( spec->fType == SegmentSpec::kLoop )
{
// If the loop is contained by the animation, add it
if( (spec->fStart == -1 || spec->fStart >= animSpec->fStart) &&
(spec->fEnd == -1 || spec->fEnd <= animSpec->fEnd) )
{
// Add the name
int idx = SendMessage( hLoops, CB_ADDSTRING, 0, (LPARAM)spec->fName );
SendMessage( hLoops, CB_SETITEMDATA, idx, kName );
}
}
}
}
ISetSel( hLoops, pb->GetStr( (ParamID)plAnimStealthNode::kPBLoopName ) );
}
void plAnimStealthNode::BeginEditParams(IObjParam *ip, ULONG flags, Animatable *prev)
{
fClassDesc->BeginEditParams(ip, this, flags, prev);
}
void plAnimStealthNode::EndEditParams(IObjParam *ip, ULONG flags, Animatable *next)
{
fClassDesc->EndEditParams(ip, this, flags, next);
}
//// ReleaseDlg //////////////////////////////////////////////////////////////
void plAnimStealthNode::ReleaseDlg( void )
{
IParamMap2 *map = fParamBlock->GetMap();
fParamBlock->SetMap( nil );
if( map != nil )
DestroyChildCPParamMap2( map );
}
//// SwitchDlg ///////////////////////////////////////////////////////////////
// Switch underlying objects in the dialog (to avoid unnecessary deletion/
// recreations)
void plAnimStealthNode::SwitchDlg( plAnimStealthNode *toSwitchTo )
{
IParamMap2 *map = fParamBlock->GetMap();
fParamBlock->SetMap( nil );
toSwitchTo->fParamBlock->SetMap( map );
map->SetParamBlock( toSwitchTo->fParamBlock );
map->SetThing( (ReferenceTarget *)toSwitchTo );
map->Invalidate();
map->UpdateUI( 0 );
}
//// CreateAndEmbedDlg ///////////////////////////////////////////////////////
// Create the dialog for this object and place it inside the given dialog,
// centering it in the given control if any.
bool plAnimStealthNode::CreateAndEmbedDlg( IParamMap2 *parentMap, IMtlParams *parentParams, HWND frameCtrl )
{
IParamMap2 *map = CreateChildCPParamMap2( fParamBlock, GetCOREInterface(), hInstance,
parentMap, MAKEINTRESOURCE( IDD_STEALTH_ANIM ),
nil, &sStealthDlgProc );
fParamBlock->SetMap( map );
if( frameCtrl != nil )
{
HWND child = fParamBlock->GetMap()->GetHWnd();
RECT childFrame, centerFrame;
::GetClientRect( child, &childFrame );
::GetWindowRect( frameCtrl, &centerFrame );
::MapWindowPoints( nil, parentMap->GetHWnd(), (POINT *)&centerFrame, 2 );
int frameWidth = centerFrame.right - centerFrame.left;
int frameHeight = centerFrame.bottom - centerFrame.top;
int childWidth = childFrame.right - childFrame.left;
int childHeight = childFrame.bottom - childFrame.top;
::OffsetRect( &childFrame, ( frameWidth - childWidth ) >> 1, ( frameHeight - childHeight ) >> 1 );
::OffsetRect( &childFrame, centerFrame.left, centerFrame.top );
::SetWindowPos( child, nil, childFrame.left, childFrame.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
}
return true;
}
//// GetWinDlg ///////////////////////////////////////////////////////////////
// Get the actual window handle of the currently active dialog displaying us
HWND plAnimStealthNode::GetWinDlg( void ) const
{
IParamMap2 *map = fParamBlock->GetMap();
if( map != nil )
return map->GetHWnd();
return nil;
}
//// Picker Dialog for Restricted Animation Components //////////////////////////////////////////
class plPickAnimStealthNode : public plPickMtlNode
{
protected:
ParamID fTypeID;
void IAddUserType(HWND hList)
{
int type = fPB->GetInt(fTypeID);
int idx = ListBox_AddString( hList, kUseParamBlockNodeString );
if (type == plAnimObjInterface::kUseParamBlockNode && !fPB->GetINode(fNodeParamID))
ListBox_SetCurSel(hList, idx);
idx = ListBox_AddString( hList, kUseOwnerNodeString );
if (type == plAnimObjInterface::kUseOwnerNode)
ListBox_SetCurSel(hList, idx);
}
void ISetUserType(plMaxNode* node, const char* userType)
{
if( hsStrEQ( userType, kUseParamBlockNodeString ) )
{
ISetNodeValue(nil);
fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseParamBlockNode);
}
else if( hsStrEQ(userType, kUseOwnerNodeString ) )
{
ISetNodeValue(nil);
fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseOwnerNode);
}
else
fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseParamBlockNode);
}
public:
plPickAnimStealthNode(IParamBlock2* pb, ParamID nodeParamID, ParamID typeID, Mtl *mtl) :
plPickMtlNode(pb, nodeParamID, mtl), fTypeID(typeID)
{
}
};
//// plAnimObjInterface Functions ////////////////////////////////////////////
void plAnimStealthNode::PickTargetNode( IParamBlock2 *destPB, ParamID destParamID, ParamID typeID )
{
plPickAnimStealthNode pick( destPB, destParamID, typeID, (Mtl *)GetParentMtl() );
pick.DoPick();
}
const char *plAnimStealthNode::GetIfaceSegmentName( hsBool allowNil )
{
// When sending messages to material animations, they're already addressed for the right
// layer, no need for a segment name
return nil;
}
//// Parameter Access Functions //////////////////////////////////////////////
#pragma warning( push )
#pragma warning( disable:4800 ) // Forcing value to bool true or false (go figure, i'm even explicitly casting)
bool plAnimStealthNode::GetAutoStart( void ) const { return (bool)fParamBlock->GetInt( (ParamID)kPBAutoStart ); }
void plAnimStealthNode::SetAutoStart( bool b ) { fParamBlock->SetValue( (ParamID)kPBAutoStart, 0, (int)b ); };
bool plAnimStealthNode::GetLoop( void ) const { return fParamBlock->GetInt( (ParamID)kPBLoop ); }
const char *plAnimStealthNode::GetLoopName( void ) const { return fParamBlock->GetStr( (ParamID)kPBLoopName ); }
void plAnimStealthNode::SetLoop( bool b, const char *name )
{
fParamBlock->SetValue( (ParamID)kPBLoop, 0, (int)b );
if( name == nil )
fParamBlock->SetValue( (ParamID)kPBLoopName, 0, "" );
else
fParamBlock->SetValue( (ParamID)kPBLoopName, 0, (char *)name );
}
UInt8 plAnimStealthNode::GetEaseInType( void ) const { return (UInt8)fParamBlock->GetInt( (ParamID)kPBEaseInType ); }
hsScalar plAnimStealthNode::GetEaseInLength( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInLength ); }
hsScalar plAnimStealthNode::GetEaseInMin( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInMin ); }
hsScalar plAnimStealthNode::GetEaseInMax( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInMax ); }
void plAnimStealthNode::SetEaseIn( UInt8 type, hsScalar length, hsScalar min, hsScalar max )
{
fParamBlock->SetValue( (ParamID)kPBEaseInType, 0, (int)type );
fParamBlock->SetValue( (ParamID)kPBEaseInLength, 0, (float)length );
fParamBlock->SetValue( (ParamID)kPBEaseInMin, 0, (float)min );
fParamBlock->SetValue( (ParamID)kPBEaseInMax, 0, (float)max );
}
UInt8 plAnimStealthNode::GetEaseOutType( void ) const { return (UInt8)fParamBlock->GetInt( (ParamID)kPBEaseOutType ); }
hsScalar plAnimStealthNode::GetEaseOutLength( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutLength ); }
hsScalar plAnimStealthNode::GetEaseOutMin( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutMin ); }
hsScalar plAnimStealthNode::GetEaseOutMax( void ) const { return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutMax ); }
void plAnimStealthNode::SetEaseOut( UInt8 type, hsScalar length, hsScalar min, hsScalar max )
{
fParamBlock->SetValue( (ParamID)kPBEaseOutType, 0, (int)type );
fParamBlock->SetValue( (ParamID)kPBEaseOutLength, 0, (float)length );
fParamBlock->SetValue( (ParamID)kPBEaseOutMin, 0, (float)min );
fParamBlock->SetValue( (ParamID)kPBEaseOutMax, 0, (float)max );
}
#pragma warning( pop ) // Forcing value to bool true or false (go figure, i'm even explicitly casting)
//// Parent Accessor Functions ///////////////////////////////////////////////
plStealthNodeAccessor &plStealthNodeAccessor::GetInstance( void )
{
static plStealthNodeAccessor instance;
return instance;
}
void plStealthNodeAccessor::ISetParent( ReferenceTarget *target, plPassMtlBase *parent )
{
if( target != nil && target->ClassID() == ANIMSTEALTH_CLASSID )
{
( (plAnimStealthNode *)target )->SetParentMtl( parent );
}
}
void plStealthNodeAccessor::TabChanged( tab_changes changeCode, Tab<PB2Value> *tab, ReferenceMaker *owner,
ParamID id, int tabIndex, int count )
{
if( changeCode == tab_insert || changeCode == tab_append )
{
if( owner->SuperClassID() != MATERIAL_CLASS_ID )
return;
plPassMtlBase *mtl = (plPassMtlBase *)owner;
while( count > 0 )
{
ISetParent( (*tab)[ tabIndex ].r, mtl );
tabIndex++;
count--;
}
}
else if( changeCode == tab_delete || changeCode == tab_ref_deleted )
{
// How are we supposed to handle this if we don't even get a stinkin pointer??
}
}
void plStealthNodeAccessor::Set( PB2Value &v, ReferenceMaker *owner, ParamID id, int tabIndex, TimeValue t )
{
// Bit of error checking
if( owner->SuperClassID() != MATERIAL_CLASS_ID )
return;
plPassMtlBase *mtl = (plPassMtlBase *)owner;
IParamBlock2 *pb = mtl->fAnimPB;
// A stealth node paramBlock value just got set. First make sure we
// un-set the old stealth's parent
ISetParent( pb->GetReferenceTarget( id, tabIndex ), nil );
// So make sure that the stealth node that was just added gets its parent mtl set properly
ISetParent( v.r, mtl );
}