/*==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 .
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 "resource.h"
#include "hsUtils.h"
#include "plAnimComponent.h"
#include "plComponentProcBase.h"
#include "plPhysicalComponents.h"
#include "plMiscComponents.h"
#include "MaxMain/plPhysicalProps.h"
#include "pnSceneObject/plSceneObject.h"
#include "plInterp/plController.h"
#include "plNotetrackAnim.h"
#include "hsResMgr.h"
#include "plAvatar/plAGModifier.h"
#include "plAvatar/plAGChannel.h"
#include "plAvatar/plAGAnim.h"
#include "plAvatar/plAGMasterMod.h"
#include "plAvatar/plMatrixChannel.h"
#include "plAvatar/plPointChannel.h"
#include "plAvatar/plScalarChannel.h"
#include "MaxMain/plMaxNode.h"
#include "MaxConvert/hsControlConverter.h"
#include "pnKeyedObject/plUoid.h"
#include "plMaxAnimUtils.h"
#include "MaxPlasmaLights/plRealTimeLightBase.h"
#include "pfAnimation/plLightModifier.h"
#include "pnKeyedObject/plMsgForwarder.h"
#include "plSDL/plSDL.h"
#include "plSDL/plSDLDescriptor.h"
#include "plPickNodeBase.h"
// For material animations
#include "MaxPlasmaMtls/Materials/plAnimStealthNode.h"
// So that the linker won't throw this code away, since it doesn't appear to be used
void DummyCodeIncludeFunc() {}
bool HasPhysicalComponent(plMaxNodeBase *node, bool searchChildren)
{
int i;
for (i = 0; i < node->NumAttachedComponents(); i++)
{
if (node->GetAttachedComponent(i)->CanConvertToType(PHYSICS_BASE_CID))
return true;
}
if (searchChildren)
{
for (i = 0; i < node->NumberOfChildren(); i++)
if (HasPhysicalComponent((plMaxNodeBase *)node->GetChildNode(i), searchChildren))
return true;
}
return false;
}
bool HasPhysicalComponent(plComponentBase *comp)
{
int i;
for (i = 0; i < comp->NumTargets(); i++)
{
plMaxNodeBase *node = comp->GetTarget(i);
if (node && HasPhysicalComponent(node, true))
return true;
}
return false;
}
bool plAnimComponentBase::GetAnimKey( plMaxNode *node, hsTArray &outKeys )
{
plComponentBase *comp = node->ConvertToComponent();
if( comp != nil )
{
if( IsAnimComponent( comp ) )
{
plAnimComponentBase *base = (plAnimComponentBase *)comp;
// Grab this guy's key
}
}
// else if( )
{
}
return true;
}
plAnimObjInterface *plAnimComponentBase::GetAnimInterface( INode *inode )
{
if( inode == nil )
return nil;
plMaxNode *node = (plMaxNode *)inode;
plComponentBase *comp = node->ConvertToComponent();
if( comp != nil )
{
if( IsAnimComponent( comp ) )
{
plAnimComponentBase *base = (plAnimComponentBase *)comp;
return (plAnimObjInterface *)base;
}
}
else
{
plAnimStealthNode *stealth = plAnimStealthNode::ConvertToStealth( node );
if( stealth != nil )
return (plAnimObjInterface *)stealth;
}
return nil;
}
void plAnimComponentProc::EnableGlobal(HWND hWnd, hsBool enable)
{
ComboBox_Enable(GetDlgItem(hWnd, IDC_ANIM_GLOBAL_LIST), enable);
ComboBox_Enable(GetDlgItem(hWnd, IDC_ANIM_NAMES), !enable);
ComboBox_Enable(GetDlgItem(hWnd, IDC_LOOP_NAMES), !enable);
Button_Enable(GetDlgItem(hWnd, IDC_COMP_ANIM_AUTOSTART_CKBX), !enable);
Button_Enable(GetDlgItem(hWnd, IDC_COMP_ANIM_LOOP_CKBX), !enable);
}
void plAnimComponentProc::FillAgeGlobalComboBox(HWND box, const char *varName)
{
plStateDescriptor *sd = plSDLMgr::GetInstance()->FindDescriptor(plPageInfoComponent::GetCurrExportAgeName(), plSDL::kLatestVersion);
if (sd)
{
int i;
for (i = 0; i < sd->GetNumVars(); i++)
{
plVarDescriptor *var = sd->GetVar(i);
if (var->GetType() == plVarDescriptor::kFloat ||
var->GetType() == plVarDescriptor::kDouble ||
var->GetType() == plVarDescriptor::kTime ||
var->GetType() == plVarDescriptor::kAgeTimeOfDay)
{
ComboBox_AddString(box, var->GetName());
}
}
}
ComboBox_AddString(box, "(none)");
}
void plAnimComponentProc::SetBoxToAgeGlobal(HWND box, const char *varName)
{
char buff[512];
if (!varName || !strcmp(varName, ""))
varName = "(none)";
ComboBox_SelectString(box, 0, varName);
ComboBox_GetLBText(box, ComboBox_GetCurSel(box), buff);
if (strcmp(varName, buff))
{
// Didn't find our variable in the age SDL file...
// Probably just missing the sdl file,
// so we'll force it in there. It'll export fine.
ComboBox_AddString(box, varName);
ComboBox_SelectString(box, 0, varName);
}
}
BOOL plAnimComponentProc::DlgProc(TimeValue t, IParamMap2 *pMap, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND gWnd = GetDlgItem(hWnd, IDC_ANIM_GLOBAL_LIST);
char buff[512];
switch (msg)
{
case WM_INITDIALOG:
{
fPB = pMap->GetParamBlock();
fNoteTrackDlg.Init(GetDlgItem(hWnd, IDC_ANIM_NAMES),
GetDlgItem(hWnd, IDC_LOOP_NAMES),
kAnimName,
kAnimLoopName,
fPB,
fPB->GetOwner());
fNoteTrackDlg.Load();
EnableWindow(GetDlgItem(hWnd, IDC_LOOP_NAMES), fPB->GetInt(kAnimLoop));
FillAgeGlobalComboBox(gWnd, fPB->GetStr(ParamID(kAnimGlobalName)));
SetBoxToAgeGlobal(gWnd, fPB->GetStr(ParamID(kAnimGlobalName)));
EnableGlobal(hWnd, fPB->GetInt(ParamID(kAnimUseGlobal)));
Button_Enable(GetDlgItem(hWnd, IDC_COMP_ANIM_PHYSANIM),
HasPhysicalComponent((plComponentBase*)fPB->GetOwner()));
}
return TRUE;
case WM_COMMAND:
if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_ANIM_NAMES)
{
fNoteTrackDlg.AnimChanged();
return TRUE;
}
else if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_LOOP_NAMES)
{
// Get the new loop name
fNoteTrackDlg.LoopChanged();
return TRUE;
}
else if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_ANIM_GLOBAL_LIST)
{
ComboBox_GetLBText(gWnd, ComboBox_GetCurSel(gWnd), buff);
fPB->SetValue(ParamID(kAnimGlobalName), 0, _T(buff));
}
// Catch loop button updates
else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_COMP_ANIM_LOOP_CKBX)
EnableWindow(GetDlgItem(hWnd, IDC_LOOP_NAMES), fPB->GetInt(kAnimLoop));
else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_COMP_ANIM_USE_GLOBAL)
{
EnableGlobal(hWnd, fPB->GetInt(ParamID(kAnimUseGlobal)));
}
break;
}
return false;
}
void plAnimComponentProc::Update( TimeValue t, Interval &valid, IParamMap2 *pmap )
{
HWND hWnd = pmap->GetHWnd();
IParamBlock2 *pb = pmap->GetParamBlock();
SetBoxToAgeGlobal(GetDlgItem(hWnd, IDC_ANIM_GLOBAL_LIST), pb->GetStr(ParamID(kAnimGlobalName)));
}
void plAnimComponentProc::DeleteThis()
{
fNoteTrackDlg.DeleteCache();
}
// For the paramblock below.
static plAnimComponentProc gAnimCompProc;
#define WM_ROLLOUT_OPEN WM_USER+1
class plAnimEaseComponentProc : public ParamMap2UserDlgProc
{
protected:
void EnableStopPoints(IParamMap2 *pm, bool enable)
{
pm->Enable(kAnimEaseInMin, enable);
pm->Enable(kAnimEaseInMax, enable);
pm->Enable(kAnimEaseOutMin, enable);
pm->Enable(kAnimEaseOutMax, enable);
}
public:
BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG:
{
IParamBlock2 *pb = map->GetParamBlock();
// Enable the min and max controls (that are only valid with stop points)
// if at least one of the targets has a stop point
plAnimComponent *comp = (plAnimComponent*)pb->GetOwner();
int num = comp->NumTargets();
bool stopPoints = false;
for (int i = 0; i < num; i++)
{
if (DoesHaveStopPoints(comp->GetTarget(i)))
{
stopPoints = true;
break;
}
}
EnableStopPoints(map, stopPoints);
// If we're doing an ease, set the ease rollup to open
if (pb->GetInt(kAnimEaseInType) != plAnimEaseTypes::kNoEase ||
pb->GetInt(kAnimEaseOutType) != plAnimEaseTypes::kNoEase)
PostMessage(hWnd, WM_ROLLOUT_OPEN, 0, 0);
}
return TRUE;
// Max doesn't know about the rollup until after WM_CREATE, so we get
// around it by posting a message
case WM_ROLLOUT_OPEN:
{
IRollupWindow *rollup = GetCOREInterface()->GetCommandPanelRollup();
int idx = rollup->GetPanelIndex(hWnd);
rollup->SetPanelOpen(idx, TRUE);
}
return TRUE;
}
return FALSE;
}
void DeleteThis() {}
};
// For the paramblock below.
static plAnimEaseComponentProc gAnimEaseCompProc;
/*
// Make sure min is less than normal, which is less than max
class EaseAccessor : public PBAccessor
{
protected:
bool fDoingUpdate;
void AdjustMin(IParamBlock2 *pb, ParamID minID, ParamID normalID, ParamID maxID, float value)
{
if (value > pb->GetFloat(normalID))
{
pb->SetValue(normalID, 0, value);
if (value > pb->GetFloat(maxID))
pb->SetValue(maxID, 0, value);
}
}
void AdjustNormal(IParamBlock2 *pb, ParamID minID, ParamID normalID, ParamID maxID, float value)
{
if (value < pb->GetFloat(minID))
pb->SetValue(minID, 0, value);
if (value > pb->GetFloat(maxID))
pb->SetValue(maxID, 0, value);
}
void AdjustMax(IParamBlock2 *pb, ParamID minID, ParamID normalID, ParamID maxID, float value)
{
if (value < pb->GetFloat(normalID))
{
pb->SetValue(normalID, 0, value);
if (value < pb->GetFloat(minID))
pb->SetValue(minID, 0, value);
}
}
public:
EaseAccessor() : fDoingUpdate(false) {}
void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
{
if (fDoingUpdate)
return;
fDoingUpdate = true;
plAnimComponent *comp = (plAnimComponent*)owner;
IParamBlock2 *pb = comp->GetParamBlockByID(plComponentBase::kBlkComp);
if (id == kAnimEaseInMin)
AdjustMin(pb, kAnimEaseInMin, kAnimEaseInLength, kAnimEaseInMin, v.f);
else if (id == kAnimEaseInLength)
AdjustNormal(pb, kAnimEaseInMin, kAnimEaseInLength, kAnimEaseInMax, v.f);
else if (id == kAnimEaseInMax)
AdjustMax(pb, kAnimEaseInMin, kAnimEaseInLength, kAnimEaseInMax, v.f);
else if (id == kAnimEaseOutMin)
AdjustMin(pb, kAnimEaseOutMin, kAnimEaseOutLength, kAnimEaseOutMax, v.f);
else if (id == kAnimEaseOutLength)
AdjustNormal(pb, kAnimEaseOutMin, kAnimEaseOutLength, kAnimEaseOutMax, v.f);
else if (id == kAnimEaseOutMax)
AdjustMax(pb, kAnimEaseOutMin, kAnimEaseOutLength, kAnimEaseOutMax, v.f);
fDoingUpdate = false;
}
};
*/
CLASS_DESC(plAnimComponent, gAnimDesc, "Animation", "Animation", COMP_TYPE_MISC, ANIM_COMP_CID)
CLASS_DESC(plAnimGroupedComponent, gAnimGroupedDesc, "Animation Grouped", "AnimGrouped", COMP_TYPE_MISC, ANIM_GROUP_COMP_CID)
hsBool plAnimComponentBase::IsAnimComponent(plComponentBase *comp)
{
return (comp->ClassID() == ANIM_COMP_CID ||
comp->ClassID() == ANIM_GROUP_COMP_CID);
}
enum { kAnimMain, kAnimEase };
ParamBlockDesc2 gAnimBlock
(
plComponent::kBlkComp, _T("animation"), 0, &gAnimDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp,
// map rollups
2,
kAnimMain, IDD_COMP_ANIM, IDS_COMP_ANIM, 0, 0, &gAnimCompProc,
kAnimEase, IDD_COMP_ANIM_EASE, IDS_COMP_ANIM_EASE, 0, APPENDROLL_CLOSED, &gAnimEaseCompProc,
// Anim Main rollout
kAnimAutoStart, _T("autoStart"), TYPE_BOOL, 0, 0,
p_ui, kAnimMain, TYPE_SINGLECHEKBOX, IDC_COMP_ANIM_AUTOSTART_CKBX,
p_default, FALSE,
end,
kAnimLoop, _T("loop"), TYPE_BOOL, 0, 0,
p_ui, kAnimMain, TYPE_SINGLECHEKBOX, IDC_COMP_ANIM_LOOP_CKBX,
p_default, FALSE,
end,
kAnimName, _T("animName"), TYPE_STRING, 0, 0,
end,
kAnimUseGlobal, _T("UseGlobal"), TYPE_BOOL, 0, 0,
p_default, FALSE,
p_ui, kAnimMain, TYPE_SINGLECHEKBOX, IDC_COMP_ANIM_USE_GLOBAL,
end,
kAnimGlobalName, _T("GlobalName"), TYPE_STRING, 0, 0,
p_default, _T(""),
end,
kAnimLoopName, _T("loopName"), TYPE_STRING, 0, 0,
end,
kAnimPhysAnim, _T("PhysAnim"), TYPE_BOOL, 0, 0,
p_default, TRUE,
p_ui, kAnimMain, TYPE_SINGLECHEKBOX, IDC_COMP_ANIM_PHYSANIM,
end,
// Anim Ease rollout
kAnimEaseInType, _T("easeInType"), TYPE_INT, 0, 0,
p_ui, kAnimEase, TYPE_RADIO, 3, IDC_COMP_ANIM_EASE_IN_NONE, IDC_COMP_ANIM_EASE_IN_CONST_ACCEL, IDC_COMP_ANIM_EASE_IN_SPLINE,
p_vals, plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
p_default, plAnimEaseTypes::kNoEase,
end,
kAnimEaseInLength, _T("easeInLength"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_IN_TIME, IDC_COMP_ANIM_EASE_IN_TIME_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
kAnimEaseInMin, _T("easeInMin"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_IN_MIN, IDC_COMP_ANIM_EASE_IN_MIN_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
kAnimEaseInMax, _T("easeInMax"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_IN_MAX, IDC_COMP_ANIM_EASE_IN_MAX_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
kAnimEaseOutType, _T("easeOutType"), TYPE_INT, 0, 0,
p_ui, kAnimEase, TYPE_RADIO, 3, IDC_COMP_ANIM_EASE_OUT_NONE, IDC_COMP_ANIM_EASE_OUT_CONST_ACCEL, IDC_COMP_ANIM_EASE_OUT_SPLINE,
p_vals, plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
p_default, plAnimEaseTypes::kNoEase,
end,
kAnimEaseOutLength, _T("easeOutLength"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_OUT_TIME, IDC_COMP_ANIM_EASE_OUT_TIME_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
kAnimEaseOutMin, _T("easeOutMin"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_OUT_MIN, IDC_COMP_ANIM_EASE_OUT_MIN_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
kAnimEaseOutMax, _T("easeOutMax"), TYPE_FLOAT, 0, 0,
p_default, 1.0,
p_range, 0.1, 99.0,
p_ui, kAnimEase, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_EASE_OUT_MAX, IDC_COMP_ANIM_EASE_OUT_MAX_SPIN, 1.0,
p_accessor, &gAnimCompEaseAccessor,
end,
end
);
ParamBlockDesc2 gAnimGroupedBlock
(
plComponent::kBlkComp, _T("animGrouped"), 0, &gAnimGroupedDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP + P_INCLUDE_PARAMS, plComponent::kRefComp,
// map rollups
2,
kAnimMain, IDD_COMP_ANIM, IDS_COMP_ANIM_GROUPED, 0, 0, &gAnimCompProc,
kAnimEase, IDD_COMP_ANIM_EASE, IDS_COMP_ANIM_EASE, 0, APPENDROLL_CLOSED, &gAnimEaseCompProc,
// use params from existing descriptor
&gAnimBlock,
end
);
plAnimComponent::plAnimComponent()
{
fClassDesc = &gAnimDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
plKey plAnimComponent::GetModKey(plMaxNode *node)
{
if (fMods.find(node) != fMods.end())
return fMods[node]->GetKey();
return nil;
}
hsBool plAnimComponent::GetKeyList( INode *restrictedNode, hsTArray &outKeys )
{
if( restrictedNode != nil )
{
if( fMods.find( (plMaxNode *)restrictedNode ) != fMods.end() )
{
outKeys.Append( fMods[ (plMaxNode *)restrictedNode ]->GetKey() );
return true;
}
return false;
}
else
{
hsAssert( false, "DO SOMETHING!" );
return false;
}
}
plAnimGroupedComponent::plAnimGroupedComponent() : fForward(nil)
{
fClassDesc = &gAnimGroupedDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
plKey plAnimGroupedComponent::GetModKey(plMaxNode *node)
{
if( fForward )
return fForward->GetKey();
return nil;
}
hsBool plAnimGroupedComponent::GetKeyList( INode *restrictedNode, hsTArray &outKeys )
{
if( fForward )
{
outKeys.Append( fForward->GetKey() );
return true;
}
return false;
}
#include "../pnMessage/plNodeRefMsg.h"
hsBool plAnimGroupedComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
bool needSetMaster = fNeedReset;
if (fNeedReset)
{
fForward = TRACKED_NEW plMsgForwarder;
plKey forwardKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), fForward, node->GetLocation());
plNodeRefMsg *refMsg = TRACKED_NEW plNodeRefMsg(node->GetRoomKey(), plRefMsg::kOnCreate, -1, plNodeRefMsg::kGeneric);
hsgResMgr::ResMgr()->AddViaNotify(forwardKey, refMsg, plRefFlags::kActiveRef);
}
hsBool ret = plAnimComponentBase::PreConvert(node, pErrMsg);
plAGMasterMod *mod = fMods[node];
if (needSetMaster)
mod->SetIsGroupMaster(true, fForward);
mod->SetIsGrouped(true);
fForward->AddForwardKey(mod->GetKey());
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////
plAnimComponentBase::plAnimComponentBase() : fNeedReset(true)
{
}
const char *plAnimComponentBase::GetAnimName()
{
const char *name = fCompPB->GetStr(kAnimName);
if (!name || name[0] == '\0')
return nil;
return name;
}
bool IsSubworld(plMaxNode* node)
{
UInt32 numComps = node->NumAttachedComponents();
for (int i = 0; i < numComps; i++)
{
plComponentBase* comp = node->GetAttachedComponent(i);
if (comp && comp->ClassID() == PHYS_SUBWORLD_CID)
return true;
}
return false;
}
void SetPhysAnimRecurse(plMaxNode *node, plErrorMsg *pErrMsg)
{
// If we hit a subworld, stop. The subworld may be animated, but the
// physicals in it aren't.
if (IsSubworld(node))
return;
if (HasPhysicalComponent(node, false))
{ char* debugName = node->GetName();
node->GetPhysicalProps()->SetPhysAnim(true, node, pErrMsg);
}
int i;
for (i = 0; i < node->NumberOfChildren(); i++)
SetPhysAnimRecurse((plMaxNode *)node->GetChildNode(i), pErrMsg);
}
hsBool plAnimComponentBase::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
if (node->IsTMAnimated())
{
node->SetMovable(true);
node->SetForceLocal(true);
//
// forceLocal on our parent (since keys work in local space)
//
plMaxNode *parent = (plMaxNode *)node->GetParentNode();
if (!parent->IsRootNode())
{
parent->SetForceLocal(true);
//char str[512];
//sprintf(str, "Forcing local on '%s' because of animated child '%s'\n",parent->GetName(),node->GetName() );
//OutputDebugString(str);
}
}
if (fCompPB->GetInt(ParamID(kAnimPhysAnim)))
SetPhysAnimRecurse(node, pErrMsg);
/*
int childCount = node->NumberOfChildren();
for (int i = 0; i < childCount; i++)
{
SetupProperties((plMaxNode *)node->GetChildNode(i), pErrMsg);
}
*/
return true;
}
hsBool plAnimComponentBase::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
// If this is the first time in the preconvert, reset the map
if (fNeedReset)
{
fNeedReset = false;
}
// If this node is animated, create it's modifier and key now so we can give
// it out to anyone that needs it
// if (node->IsTMAnimated() || node->IsAnimatedLight())
// {
const char *name = node->GetName();
plAGMasterMod *mod = node->GetAGMasterMod();
if (mod == nil)
{
if (!node->HasAGMod()) // Need to add this before the MasterMod, if it doesn't have one already.
{
node->AddModifier(new plAGModifier(node->GetName()), IGetUniqueName(node));
}
mod = TRACKED_NEW plAGMasterMod();
plKey modKey = node->AddModifier(mod, IGetUniqueName(node));
}
fMods[node] = mod;
// }
// Small change here. We're setting up the timing specs on the
// plAGAnim object during preconvert, so that the info is available
// when actually converting the anim (and for other components
// that need it, but may or may not actually convert before us.)
// Note: if the component uses the "(Entire Animation)" segment for
// the main start/end, the start/end times won't be valid until
// we've added all keys during convert. Some cleanup might
// be necessary in this case.
const char *animName = fCompPB->GetStr(kAnimName);
if (animName == nil || !strcmp(animName, ""))
animName = ENTIRE_ANIMATION_NAME;
if (fCompPB->GetInt(ParamID(kAnimUseGlobal)))
{
plAgeGlobalAnim *ageAnim = TRACKED_NEW plAgeGlobalAnim(animName, 0, 0);
ageAnim->SetGlobalVarName((char*)fCompPB->GetStr(ParamID(kAnimGlobalName)));
fAnims[node] = ageAnim;
}
else
{
plATCAnim *ATCAnim = TRACKED_NEW plATCAnim(animName, 0, 0);
plNotetrackAnim noteAnim(node, pErrMsg);
plAnimInfo info = noteAnim.GetAnimInfo(animName);
ATCAnim->SetAutoStart(fCompPB->GetInt(kAnimAutoStart));
hsScalar start = info.GetAnimStart();
hsScalar end = info.GetAnimEnd();
hsScalar initial = info.GetAnimInitial();
if (start != -1)
ATCAnim->SetStart(start);
if (end != -1)
ATCAnim->SetEnd(end);
if (initial != -1)
ATCAnim->SetInitial(initial);
if (fCompPB->GetInt(kAnimLoop))
{
ATCAnim->SetLoop(true);
const char *loopName = fCompPB->GetStr(kAnimLoopName);
hsScalar loopStart = info.GetLoopStart(loopName);
hsScalar loopEnd = info.GetLoopEnd(loopName);
ATCAnim->SetLoopStart(loopStart == -1 ? ATCAnim->GetStart() : loopStart);
ATCAnim->SetLoopEnd(loopEnd == -1 ? ATCAnim->GetEnd() : loopEnd);
}
while (const char *loop = info.GetNextLoopName())
ATCAnim->AddLoop(loop, info.GetLoopStart(loop), info.GetLoopEnd(loop));
while (const char *marker = info.GetNextMarkerName())
ATCAnim->AddMarker(marker, info.GetMarkerTime(marker));
float stopPoint = -1;
while ((stopPoint = info.GetNextStopPoint()) != -1)
ATCAnim->AddStopPoint(stopPoint);
ATCAnim->SetEaseInType(fCompPB->GetInt(kAnimEaseInType));
ATCAnim->SetEaseOutType(fCompPB->GetInt(kAnimEaseOutType));
ATCAnim->SetEaseInLength(fCompPB->GetFloat(kAnimEaseInLength));
ATCAnim->SetEaseInMin(fCompPB->GetFloat(kAnimEaseInMin));
ATCAnim->SetEaseInMax(fCompPB->GetFloat(kAnimEaseInMax));
ATCAnim->SetEaseOutLength(fCompPB->GetFloat(kAnimEaseOutLength));
ATCAnim->SetEaseOutMin(fCompPB->GetFloat(kAnimEaseOutMin));
ATCAnim->SetEaseOutMax(fCompPB->GetFloat(kAnimEaseOutMax));
fAnims[node] = ATCAnim;
}
return true;
}
hsBool plAnimComponentBase::IAddTMToAnim(plMaxNode *node, plAGAnim *anim, plErrorMsg *pErrMsg)
{
hsBool result = false;
// Get the affine parts and the TM Controller
plSceneObject *obj = node->GetSceneObject();
hsAffineParts * parts = TRACKED_NEW hsAffineParts;
plController* tmc;
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
tmc = hsControlConverter::Instance().ConvertTMAnim(obj, node, parts);
else
tmc = hsControlConverter::Instance().ConvertTMAnim(obj, node, parts, anim->GetStart(), anim->GetEnd());
if (tmc)
{
plMatrixChannelApplicator *app = TRACKED_NEW plMatrixChannelApplicator();
app->SetChannelName(node->GetName());
plMatrixControllerChannel *channel = TRACKED_NEW plMatrixControllerChannel(tmc, parts);
app->SetChannel(channel);
anim->AddApplicator(app);
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(tmc->GetLength());
result = true;
}
delete parts; // We copy this over, so no need to keep it around
return result;
}
hsBool plAnimComponentBase::IAddLightToAnim(plMaxNode *node, plAGAnim *anim, plErrorMsg *pErrMsg)
{
if (!node->IsAnimatedLight())
return false;
Object *obj = node->GetObjectRef();
Class_ID cid = obj->ClassID();
IParamBlock2 *pb = nil;
if (cid == RTSPOT_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
else if (cid == RTOMNI_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
else if (cid == RTDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
else if (cid == RTPDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkMain);
node->GetRTLightColAnim(pb, anim);
if (cid == RTSPOT_LIGHT_CLASSID || cid == RTOMNI_LIGHT_CLASSID)
node->GetRTLightAttenAnim(pb, anim);
if (cid == RTSPOT_LIGHT_CLASSID)
node->GetRTConeAnim(pb, anim);
return true;
}
hsBool plAnimComponentBase::IConvertNodeSegmentBranch(plMaxNode *node, plAGAnim *anim, plErrorMsg *pErrMsg)
{
hsBool madeAnim = false;
int i;
if (IAddTMToAnim(node, anim, pErrMsg))
madeAnim = true;
if (IAddLightToAnim(node, anim, pErrMsg))
madeAnim = true;
for (i = 0; i < node->NumAttachedComponents(); i++)
{
if (node->GetAttachedComponent(i)->AddToAnim(anim, node))
madeAnim = true;
}
if (madeAnim)
{
// It has an animation, we're going to need a plAGMod when loading the anim
if (!node->HasAGMod())
{
node->AddModifier(new plAGModifier(node->GetName()), IGetUniqueName(node));
}
madeAnim = true;
}
/*
// let's see if the children have any segments specified...
int childCount = node->NumberOfChildren();
for (int i = 0; i < childCount; i++)
{
if (IConvertNodeSegmentBranch((plMaxNode *)(node->GetChildNode(i)), anim, pErrMsg))
madeAnim = true;
}
*/
return madeAnim;
}
hsBool plAnimComponentBase::IMakePersistent(plMaxNode *node, plAGAnim *anim, plErrorMsg *pErrMsg)
{
// anims made by this component are private to the specific AGMasterMod, so we attach them there.
plAGMasterMod *mod = plAGMasterMod::ConvertNoRef(fMods[node]);
hsAssert(mod != nil, "No MasterMod to make animation persistent!");
char buffer[256];
sprintf(buffer, "%s_%s_anim_%d", node->GetName(), anim->GetName(), mod->GetNumPrivateAnimations());
plLocation nodeLoc = node->GetLocation();
plKey animKey = hsgResMgr::ResMgr()->NewKey(buffer, anim, nodeLoc);
plGenRefMsg* refMsg = TRACKED_NEW plGenRefMsg(mod->GetKey(), plRefMsg::kOnCreate, 0, 0);
hsgResMgr::ResMgr()->AddViaNotify(animKey, refMsg, plRefFlags::kActiveRef);
return true;
}
hsBool plAnimComponentBase::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
fNeedReset = true;
if (!IConvertNodeSegmentBranch(node, fAnims[node], pErrMsg))
{
// Either we delete it here, or we make it persistent below and the resMgr handles it
delete fAnims[node];
fAnims[node] = nil;
return false;
}
if (fCompPB->GetInt(ParamID(kAnimUseGlobal)))
{
((plAgeGlobalAnim *)fAnims[node])->SetGlobalVarName((char*)fCompPB->GetStr(ParamID(kAnimGlobalName)));
}
else // It's an ATCAnim
{
// If we're on an "(Entire Animation)" segment. The loops won't know their lengths until
// after the nodes have been converted and added. So we adjust them here if necessary.
((plATCAnim *)fAnims[node])->CheckLoop();
}
IMakePersistent(node, fAnims[node], pErrMsg);
return true;
}
hsBool plAnimComponentBase::DeInit(plMaxNode *node, plErrorMsg *pErrMsg)
{
fMods.clear();
fLightMods.clear();
fAnims.clear();
return true;
}
void plAnimComponentBase::SetupCtl( plAGAnim *anim, plController *ctl, plAGApplicator *app, plMaxNode *node )
{
plScalarControllerChannel *channel = TRACKED_NEW plScalarControllerChannel(ctl);
app->SetChannel(channel);
anim->AddApplicator(app);
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
//// Picker Dialog for Restricted Animation Components //////////////////////////////////////////
class plPickAnimCompNode : public plPickCompNode
{
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:
plPickAnimCompNode(IParamBlock2* pb, ParamID nodeParamID, ParamID typeID, plComponentBase *comp) :
plPickCompNode(pb, nodeParamID, comp), fTypeID(typeID)
{
}
};
void plAnimComponentBase::PickTargetNode( IParamBlock2 *destPB, ParamID destParamID, ParamID destTypeID )
{
plPickAnimCompNode pick( destPB, destParamID, destTypeID, (plComponentBase *)this );
pick.DoPick();
}
const char *plAnimComponentBase::GetIfaceSegmentName( hsBool allowNil )
{
const char *name = GetAnimName();
if( allowNil || name != nil )
return name;
return ENTIRE_ANIMATION_NAME;
}
//// Hit Callback for Animations /////////////////////////////////////////////
class plPlasmaAnimHitCallback : public HitByNameDlgCallback
{
protected:
IParamBlock2* fPB;
ParamID fParamID;
TCHAR fTitle[ 128 ];
public:
plPlasmaAnimHitCallback( IParamBlock2 *pb, ParamID paramID, TCHAR *title = nil )
: fPB( pb ), fParamID( paramID )
{
strcpy( fTitle, title );
}
virtual TCHAR *dialogTitle() { return fTitle; }
virtual TCHAR *buttonText() { return "OK"; }
virtual int filter( INode *node )
{
plComponentBase *comp = ( (plMaxNodeBase *)node )->ConvertToComponent();
if( comp != nil && plAnimComponentBase::IsAnimComponent( comp ) )
{
// Make sure it won't create a cyclical reference (Max doesn't like those)
if( comp->TestForLoop( FOREVER, fPB ) == REF_FAIL )
return FALSE;
return TRUE;
}
else
{
plAnimStealthNode *stealth = plAnimStealthNode::ConvertToStealth( node );
if( stealth != nil )
{
if( stealth->TestForLoop( FOREVER, fPB ) == REF_FAIL )
return FALSE;
if( !stealth->IsParentUsedInScene() )
return FALSE;
return TRUE;
}
}
return FALSE;
}
virtual void proc( INodeTab &nodeTab )
{
fPB->SetValue( fParamID, (TimeValue)0, nodeTab[ 0 ] );
}
virtual BOOL showHiddenAndFrozen() { return TRUE; }
virtual BOOL singleSelect() { return TRUE; }
};
//// Dialog Proc For Anim Selection /////////////////////////////////////////////////////////////
plPlasmaAnimSelectDlgProc::plPlasmaAnimSelectDlgProc( ParamID paramID, int dlgItem, TCHAR *promptTitle, ParamMap2UserDlgProc *chainedDlgProc )
{
fParamID = paramID;
fDlgItem = dlgItem;
fUseNode = false;
strcpy( fTitle, promptTitle );
fChain = chainedDlgProc;
}
plPlasmaAnimSelectDlgProc::plPlasmaAnimSelectDlgProc( ParamID paramID, int dlgItem, ParamID nodeParamID, ParamID typeParamID, int nodeDlgItem,
TCHAR *promptTitle, ParamMap2UserDlgProc *chainedDlgProc )
{
fParamID = paramID;
fDlgItem = dlgItem;
fUseNode = true;
fNodeParamID = nodeParamID;
fTypeParamID = typeParamID;
fNodeDlgItem = nodeDlgItem;
strcpy( fTitle, promptTitle );
fChain = chainedDlgProc;
}
void plPlasmaAnimSelectDlgProc::SetThing( ReferenceTarget *m )
{
if( fChain != nil )
fChain->SetThing( m );
}
void plPlasmaAnimSelectDlgProc::Update( TimeValue t, Interval &valid, IParamMap2 *pmap )
{
if( fChain != nil )
fChain->Update( t, valid, pmap );
}
void plPlasmaAnimSelectDlgProc::IUpdateNodeBtn( HWND hWnd, IParamBlock2 *pb )
{
if( fUseNode )
{
int type = pb->GetInt( fTypeParamID );
if( type == plAnimObjInterface::kUseOwnerNode )
::SetWindowText( ::GetDlgItem( hWnd, fNodeDlgItem ), kUseOwnerNodeString );
else
{
INode *node = pb->GetINode( fNodeParamID );
TSTR newName( node ? node->GetName() : kUseParamBlockNodeString );
::SetWindowText( ::GetDlgItem( hWnd, fNodeDlgItem ), newName );
}
plAnimObjInterface *iface = plAnimComponentBase::GetAnimInterface( pb->GetINode( fParamID ) );
if( iface == nil || !iface->IsNodeRestricted() )
::EnableWindow( ::GetDlgItem( hWnd, fNodeDlgItem ), false );
else
{
::EnableWindow( ::GetDlgItem( hWnd, fNodeDlgItem ), true );
}
}
}
BOOL plPlasmaAnimSelectDlgProc::DlgProc( TimeValue t, IParamMap2 *pmap, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch ( msg )
{
case WM_INITDIALOG:
{
IParamBlock2 *pb = pmap->GetParamBlock();
INode *node = pb->GetINode( fParamID );
TSTR newName( node ? node->GetName() : "Pick" );
::SetWindowText( ::GetDlgItem( hWnd, fDlgItem ), newName );
IUpdateNodeBtn( hWnd, pb );
}
break;
case WM_COMMAND:
if( ( HIWORD( wParam ) == BN_CLICKED ) )
{
if( LOWORD( wParam ) == fDlgItem )
{
IParamBlock2 *pb = pmap->GetParamBlock();
plPlasmaAnimHitCallback hitCB( pb, fParamID, fTitle );
GetCOREInterface()->DoHitByNameDialog( &hitCB );
INode *node = pb->GetINode( fParamID );
TSTR newName( node ? node->GetName() : "Pick" );
::SetWindowText( ::GetDlgItem(hWnd, fDlgItem ), newName );
pmap->Invalidate( fParamID );
::InvalidateRect( hWnd, NULL, TRUE );
IUpdateNodeBtn( hWnd, pb );
return true;
}
else if( fUseNode && LOWORD( wParam ) == fNodeDlgItem )
{
IParamBlock2 *pb = pmap->GetParamBlock();
plAnimObjInterface *iface = plAnimComponentBase::GetAnimInterface( pb->GetINode( fParamID ) );
iface->PickTargetNode( pb, fNodeParamID, fTypeParamID );
IUpdateNodeBtn( hWnd, pb );
return true;
}
}
break;
}
if( fChain != nil )
return fChain->DlgProc( t, pmap, hWnd, msg, wParam, lParam );
return false;
}
void plPlasmaAnimSelectDlgProc::DeleteThis()
{
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
CLASS_DESC(plAnimCompressComp, gAnimCompressDesc, "Anim Compress", "AnimCompress", COMP_TYPE_MISC, ANIM_COMPRESS_COMP_CID)
ParamBlockDesc2 gAnimCompressBk
(
plComponent::kBlkComp, _T("AnimCompress"), 0, &gAnimCompressDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,
IDD_COMP_ANIM_COMPRESS, IDS_COMP_ANIM_COMPRESS_ROLL, 0, 0, nil,
plAnimCompressComp::kAnimCompressLevel, _T("compressLevel"), TYPE_INT, 0, 0,
p_ui, TYPE_RADIO, 3, IDC_COMP_ANIM_COMPRESS_NONE, IDC_COMP_ANIM_COMPRESS_LOW, IDC_COMP_ANIM_COMPRESS_HIGH,
p_vals, plAnimCompressComp::kCompressionNone, plAnimCompressComp::kCompressionLow, plAnimCompressComp::kCompressionHigh,
p_default, plAnimCompressComp::kCompressionLow,
end,
plAnimCompressComp::kAnimCompressThreshold, _T("Threshold"), TYPE_FLOAT, 0, 0,
p_default, 0.01,
p_range, 0.0, 1.0,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_ANIM_COMPRESS_THRESHOLD, IDC_COMP_ANIM_COMPRESS_THRESHOLD_SPIN, 0.001,
end,
end
);
plAnimCompressComp::plAnimCompressComp()
{
fClassDesc = &gAnimCompressDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
hsBool plAnimCompressComp::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
node->SetAnimCompress(fCompPB->GetInt(ParamID(kAnimCompressLevel)));
// We use Max's key reduction code, which doesn't seem to match up with its own UI.
// Manually using Max's "Reduce Keys" option with a threshold of .01 seems to give
// approximately the same results as calling the function ApplyKeyReduction with
// a threshold of .0002. I want the UI to appear consistent to the artist, so we
// shrug our shoulders and scale down by 50.
node->SetKeyReduceThreshold(fCompPB->GetFloat(ParamID(kAnimCompressThreshold)) / 50.f);
return true;
}
hsBool plAnimCompressComp::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
return true;
}