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.

635 lines
16 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==*/
#include "HeadSpin.h"
#include "plResponderMtl.h"
#include "plResponderComponentPriv.h"
#include "resource.h"
#include "max.h"
#include "MaxMain/plMaxNode.h"
#include "MaxPlasmaMtls/Materials/plDecalMtl.h"
#include "MaxPlasmaMtls/Materials/plPassMtl.h"
#include "MaxConvert/plConvert.h"
#include "MaxConvert/hsMaterialConverter.h"
#include "plSurface/hsGMaterial.h"
#include "plSurface/plLayerAnimation.h"
#include "plMaxAnimUtils.h"
#include "plNotetrackAnim.h"
#include "plPickMaterialMap.h"
#include "MaxMain/plMtlCollector.h"
#include "plPickNode.h"
// Needed for convert
#include "plMessage/plAnimCmdMsg.h"
#include <set>
#include <vector>
#include <algorithm>
#include "MaxMain/plPlasmaRefMsgs.h"
enum
{
kMtlRef,
kMtlAnim,
kMtlLoop,
kMtlType,
kMtlOwner_DEAD,
kMtlNode,
kMtlNodeType,
};
enum MtlNodeType
{
kNodePB, // Use the node in the PB
kNodeResponder // Use the node the responder is attached to
};
#include "plAnimCompProc.h"
class plResponderMtlProc : public plMtlAnimProc
{
public:
plResponderMtlProc();
protected:
virtual void IOnInitDlg(HWND hWnd, IParamBlock2* pb);
virtual void ILoadUser(HWND hWnd, IParamBlock2* pb);
virtual bool IUserCommand(HWND hWnd, IParamBlock2* pb, int cmd, int resID);
virtual void IPickNode(IParamBlock2* pb);
virtual void ISetNodeButtonText(HWND hWnd, IParamBlock2* pb);
};
static plResponderMtlProc gResponderMtlProc;
ParamBlockDesc2 gResponderMtlBlock
(
kResponderMtlBlk, _T("mtlCmd"), 0, NULL, P_AUTO_UI,
IDD_COMP_RESPOND_MTL, IDS_COMP_CMD_PARAMS, 0, 0, &gResponderMtlProc,
kMtlRef, _T("mtl"), TYPE_REFTARG, 0, 0,
end,
kMtlAnim, _T("anim"), TYPE_STRING, 0, 0,
end,
kMtlLoop, _T("loop"), TYPE_STRING, 0, 0,
end,
kMtlType, _T("type"), TYPE_INT, 0, 0,
end,
kMtlNode, _T("node"), TYPE_INODE, 0, 0,
end,
kMtlNodeType, _T("nodeType"), TYPE_INT, 0, 0,
end,
end
);
plResponderCmdMtl& plResponderCmdMtl::Instance()
{
static plResponderCmdMtl theInstance;
return theInstance;
}
ParamBlockDesc2 *plResponderCmdMtl::GetDesc()
{
return &gResponderMtlBlock;
}
// Use old types for backward compatibility
enum
{
kRespondPlayMat=12,
kRespondStopMat,
kRespondToggleMat,
kRespondLoopMatOn,
kRespondLoopMatOff,
kRespondSetForeMat,
kRespondSetBackMat,
kRespondRewindMat,
kNumTypes=8
};
static int IndexToOldType(int idx)
{
static int oldTypes[] =
{
kRespondPlayMat,
kRespondStopMat,
kRespondToggleMat,
kRespondLoopMatOn,
kRespondLoopMatOff,
kRespondSetForeMat,
kRespondSetBackMat,
kRespondRewindMat
};
hsAssert(idx < kNumTypes, "Bad index");
return oldTypes[idx];
}
int plResponderCmdMtl::NumTypes()
{
return kNumTypes;
}
const char *plResponderCmdMtl::GetCategory(int idx)
{
return "Material";
}
const char *plResponderCmdMtl::GetName(int idx)
{
int type = IndexToOldType(idx);
switch (type)
{
case kRespondPlayMat: return "Play";
case kRespondStopMat: return "Stop";
case kRespondToggleMat: return "Toggle";
case kRespondLoopMatOn: return "Set Looping On";
case kRespondLoopMatOff:return "Set Looping Off";
case kRespondSetForeMat:return "Set Forwards";
case kRespondSetBackMat:return "Set Backwards";
case kRespondRewindMat: return "Rewind";
}
return nil;
}
static const char *GetShortName(int type)
{
switch (type)
{
case kRespondPlayMat: return "Mat Play";
case kRespondStopMat: return "Mat Stop";
case kRespondToggleMat: return "Mat Toggle";
case kRespondLoopMatOn: return "Mat Loop On";
case kRespondLoopMatOff:return "Mat Loop Off";
case kRespondSetForeMat:return "Mat Set Fore";
case kRespondSetBackMat:return "Mat Set Back";
case kRespondRewindMat: return "Mat Rewind";
}
return nil;
}
const char *plResponderCmdMtl::GetInstanceName(IParamBlock2 *pb)
{
static char name[256];
const char *shortName = GetShortName(pb->GetInt(kMtlType));
Mtl *mtl = (Mtl*)pb->GetReferenceTarget(kMtlRef);
sprintf(name, "%s (%s)", shortName, mtl ? mtl->GetName() : "none");
return name;
}
IParamBlock2 *plResponderCmdMtl::CreatePB(int idx)
{
int type = IndexToOldType(idx);
// Create the paramblock and save it's type
IParamBlock2 *pb = CreateParameterBlock2(&gResponderMtlBlock, nil);
pb->SetValue(kMtlType, 0, type);
return pb;
}
Mtl *plResponderCmdMtl::GetMtl(IParamBlock2 *pb)
{
return (Mtl*)pb->GetReferenceTarget(kMtlRef);
}
const char *plResponderCmdMtl::GetAnim(IParamBlock2 *pb)
{
return pb->GetStr(kMtlAnim);
}
void ISearchLayerRecur(plLayerInterface *layer, const char *segName, hsTArray<plKey>& keys)
{
if (!layer)
return;
plLayerAnimation *animLayer = plLayerAnimation::ConvertNoRef(layer);
if (animLayer)
{
char *ID = animLayer->GetSegmentID();
if (ID == nil)
ID = "";
if (!strcmp(ID, segName))
{
if( keys.kMissingIndex == keys.Find(animLayer->GetKey()) )
keys.Append(animLayer->GetKey());
}
}
ISearchLayerRecur(layer->GetAttached(), segName, keys);
}
int ISearchLayerRecur(hsGMaterial* mat, const char *segName, hsTArray<plKey>& keys)
{
if (segName == nil)
segName = "";
int i;
for( i = 0; i < mat->GetNumLayers(); i++ )
ISearchLayerRecur(mat->GetLayer(i), segName, keys);
return keys.GetCount();
}
int GetMatAnimModKey(Mtl* mtl, plMaxNodeBase* node, const char* segName, hsTArray<plKey>& keys)
{
int retVal = 0;
int i;
//if( begin < 0 )
// begin = 0;
if( mtl->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
{
for( i = 0; i < mtl->NumSubMtls(); i++ )
retVal += GetMatAnimModKey(mtl->GetSubMtl(i), node, segName, keys);
}
else
{
hsTArray<hsGMaterial*> matList;
if (node)
hsMaterialConverter::Instance().GetMaterialArray(mtl, (plMaxNode*)node, matList);
else
hsMaterialConverter::Instance().CollectConvertedMaterials(mtl, matList);
for( i = 0; i < matList.GetCount(); i++ )
{
retVal += ISearchLayerRecur(matList[i], segName, keys);
}
}
return retVal;
}
void plResponderCmdMtl::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg, IParamBlock2* pb)
{
plMaxNode* mtlNode;
if (pb->GetInt(kMtlNodeType) == kNodeResponder)
mtlNode = node;
else
mtlNode = (plMaxNode*)pb->GetINode(kMtlNode);
if (mtlNode)
mtlNode->SetForceMaterialCopy(true);
}
plMessage *plResponderCmdMtl::CreateMsg(plMaxNode* node, plErrorMsg *pErrMsg, IParamBlock2 *pb)
{
Mtl *maxMtl = (Mtl*)pb->GetReferenceTarget(kMtlRef);
if (!maxMtl)
throw "No material specified";
const char *animName = pb->GetStr(kMtlAnim);
hsScalar begin=-1.f;
hsScalar end = -1.f;
SegmentMap *segMap = GetAnimSegmentMap(maxMtl, pErrMsg);
hsTArray<plKey> keys;
if( segMap )
{
GetSegMapAnimTime(animName, segMap, SegmentSpec::kAnim, begin, end);
}
plMaxNode* mtlNode;
if (pb->GetInt(kMtlNodeType) == kNodeResponder)
mtlNode = node;
else
mtlNode = (plMaxNode*)pb->GetINode(kMtlNode);
GetMatAnimModKey(maxMtl, mtlNode, animName, keys);
const char *loopName = nil;
loopName = pb->GetStr(kMtlLoop);
if (segMap && loopName)
GetSegMapAnimTime(loopName, segMap, SegmentSpec::kLoop, begin, end);
DeleteSegmentMap(segMap);
if (!keys.GetCount())
{
// We need the check here because "physicals only" export mode means that
// most of the materials won't be there, so we should ignore this warning. -Colin
if (plConvert::Instance().GetConvertSettings()->fPhysicalsOnly)
return nil;
else
throw "Material animation key(s) not found";
}
plAnimCmdMsg *msg = TRACKED_NEW plAnimCmdMsg;
msg->AddReceivers(keys);
switch (pb->GetInt(kMtlType))
{
case kRespondPlayMat:
msg->SetCmd(plAnimCmdMsg::kContinue);
break;
case kRespondStopMat:
msg->SetCmd(plAnimCmdMsg::kStop);
break;
case kRespondToggleMat:
msg->SetCmd(plAnimCmdMsg::kToggleState);
break;
case kRespondLoopMatOn:
msg->SetCmd(plAnimCmdMsg::kSetLooping);
// KLUDGE - We send the loop to play by name here, so anim grouped components
// could have loops with different begin and end points. However, apparently
// that functionality was never implemented, whoops. So, we'll take out the
// stuff that actually tries to set the begin and end points for now, so that
// anims with a loop set in advance will actually work with this. -Colin
//
// This KLUDGE has been copied from where Colin kludged it in plResponderAnim
// in the spirit of consistent hackage. Maybe when one gets unkludged, the
// other one will too. -mf
// msg->SetCmd(plAnimCmdMsg::kSetLoopBegin);
// msg->fLoopBegin = begin;
// msg->SetCmd(plAnimCmdMsg::kSetLoopEnd);
// msg->fLoopEnd = end;
break;
case kRespondLoopMatOff:
msg->SetCmd(plAnimCmdMsg::kUnSetLooping);
break;
case kRespondSetForeMat:
msg->SetCmd(plAnimCmdMsg::kSetForewards);
break;
case kRespondSetBackMat:
msg->SetCmd(plAnimCmdMsg::kSetBackwards);
break;
case kRespondRewindMat:
msg->SetCmd(plAnimCmdMsg::kGoToBegin);
break;
default:
delete msg;
throw "Unknown material command";
}
return msg;
}
bool plResponderCmdMtl::IsWaitable(IParamBlock2 *pb)
{
int type = pb->GetInt(kMtlType);
if (type == kRespondPlayMat ||
type == kRespondToggleMat)
return true;
return false;
}
void plResponderCmdMtl::GetWaitPoints(IParamBlock2 *pb, WaitPoints& waitPoints)
{
Mtl *mtl = GetMtl(pb);
const char *animName = GetAnim(pb);
if (mtl)
{
plNotetrackAnim notetrackAnim(mtl, nil);
plAnimInfo info = notetrackAnim.GetAnimInfo(animName);
while (const char *marker = info.GetNextMarkerName())
waitPoints.push_back(marker);
}
}
void plResponderCmdMtl::CreateWait(plMaxNode* node, plErrorMsg* pErrMsg, IParamBlock2 *pb, ResponderWaitInfo& waitInfo)
{
plAnimCmdMsg *animMsg = plAnimCmdMsg::ConvertNoRef(waitInfo.msg);
if (animMsg)
animMsg->SetCmd(plAnimCmdMsg::kAddCallbacks);
plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg;
eventMsg->AddReceiver(waitInfo.receiver);
eventMsg->fRepeats = 0;
eventMsg->fEvent = kStop;
eventMsg->fUser = waitInfo.callbackUser;
if (waitInfo.point)
{
// FIXME COLIN - Error checking here?
Mtl *mtl = GetMtl(pb);
const char *animName = GetAnim(pb);
plNotetrackAnim notetrackAnim(mtl, nil);
plAnimInfo info = notetrackAnim.GetAnimInfo(animName);
eventMsg->fEvent = kTime;
eventMsg->fEventTime = info.GetMarkerTime(waitInfo.point);
}
else
{
eventMsg->fEvent = kStop;
}
plMessageWithCallbacks *callbackMsg = plMessageWithCallbacks::ConvertNoRef(waitInfo.msg);
callbackMsg->AddCallback(eventMsg);
hsRefCnt_SafeUnRef( eventMsg );
}
////////////////////////////////////////////////////////////////////////////////
plResponderMtlProc::plResponderMtlProc()
{
fMtlButtonID = IDC_MTL_BUTTON;
fMtlParamID = kMtlRef;
fNodeButtonID = IDC_NODE_BUTTON;
fNodeParamID = kMtlNode;
fAnimComboID = IDC_ANIM_COMBO;
fAnimParamID = kMtlAnim;
}
void plResponderMtlProc::IOnInitDlg(HWND hWnd, IParamBlock2* pb)
{
int type = pb->GetInt(kMtlType);
// Show the loop control only if this is a loop
int show = (type == kRespondLoopMatOn) ? SW_SHOW : SW_HIDE;
ShowWindow(GetDlgItem(hWnd, IDC_LOOP_COMBO), show);
ShowWindow(GetDlgItem(hWnd, IDC_LOOP_TEXT), show);
// Resize the dialog if we're not using the loop control
if (type != kRespondLoopMatOn)
{
RECT itemRect, clientRect;
GetWindowRect(GetDlgItem(hWnd, IDC_LOOP_TEXT), &itemRect);
GetWindowRect(hWnd, &clientRect);
SetWindowPos(hWnd, NULL, 0, 0, clientRect.right-clientRect.left,
itemRect.top-clientRect.top, SWP_NOMOVE | SWP_NOZORDER);
}
}
void plResponderMtlProc::ILoadUser(HWND hWnd, IParamBlock2 *pb)
{
HWND hLoop = GetDlgItem(hWnd, IDC_LOOP_COMBO);
const char *savedName = pb->GetStr(kMtlLoop);
if (!savedName)
savedName = "";
ComboBox_ResetContent(hLoop);
int sel = ComboBox_AddString(hLoop, ENTIRE_ANIMATION_NAME);
ComboBox_SetCurSel(hLoop, sel);
// Get the NoteTrack animations off the selected material
Mtl *mtl = (Mtl*)pb->GetReferenceTarget(kMtlRef);
if (!mtl)
{
ComboBox_Enable(hLoop, FALSE);
return;
}
ComboBox_Enable(hLoop, TRUE);
plNotetrackAnim anim(mtl, nil);
const char *animName = pb->GetStr(kMtlAnim);
plAnimInfo info = anim.GetAnimInfo(animName);
while (const char *loopName = info.GetNextLoopName())
{
sel = ComboBox_AddString(hLoop, loopName);
if (!strcmp(loopName, savedName))
ComboBox_SetCurSel(hLoop, sel);
}
}
bool plResponderMtlProc::IUserCommand(HWND hWnd, IParamBlock2* pb, int cmd, int resID)
{
if (cmd == CBN_SELCHANGE && resID == IDC_LOOP_COMBO)
{
HWND hCombo = GetDlgItem(hWnd, IDC_LOOP_COMBO);
int idx = ComboBox_GetCurSel(hCombo);
if (idx != CB_ERR)
{
if (ComboBox_GetItemData(hCombo, idx) == 0)
pb->SetValue(kMtlLoop, 0, "");
else
{
// Get the name of the animation and save it
char buf[256];
ComboBox_GetText(hCombo, buf, sizeof(buf));
pb->SetValue(kMtlLoop, 0, buf);
}
}
return true;
}
return false;
}
#include "plPickNodeBase.h"
static const char* kUserTypeAll = "(All)";
static const char* kResponderNodeName = "(Responder Node)";
class plPickRespMtlNode : public plPickMtlNode
{
protected:
int fTypeID;
void IAddUserType(HWND hList)
{
int type = fPB->GetInt(fTypeID);
int idx = ListBox_AddString(hList, kUserTypeAll);
if (type == kNodePB && !fPB->GetINode(fNodeParamID))
ListBox_SetCurSel(hList, idx);
idx = ListBox_AddString(hList, kResponderNodeName);
if (type == kNodeResponder)
ListBox_SetCurSel(hList, idx);
}
void ISetUserType(plMaxNode* node, const char* userType)
{
if (hsStrEQ(userType, kUserTypeAll))
{
ISetNodeValue(nil);
fPB->SetValue(fTypeID, 0, kNodePB);
}
else if (hsStrEQ(userType, kResponderNodeName))
{
ISetNodeValue(nil);
fPB->SetValue(fTypeID, 0, kNodeResponder);
}
else
fPB->SetValue(fTypeID, 0, kNodePB);
}
public:
plPickRespMtlNode(IParamBlock2* pb, int nodeParamID, int typeID, Mtl* mtl) :
plPickMtlNode(pb, nodeParamID, mtl), fTypeID(typeID)
{
}
};
void plResponderMtlProc::IPickNode(IParamBlock2* pb)
{
plPickRespMtlNode pick(pb, kMtlNode, kMtlNodeType, IGetMtl(pb));
pick.DoPick();
}
void plResponderMtlProc::ISetNodeButtonText(HWND hWnd, IParamBlock2* pb)
{
int type = pb->GetInt(kMtlNodeType);
HWND hButton = GetDlgItem(hWnd, IDC_NODE_BUTTON);
if (type == kNodeResponder)
SetWindowText(hButton, kResponderNodeName);
else if (type == kNodePB && !pb->GetINode(kMtlNode))
SetWindowText(hButton, kUserTypeAll);
else
plMtlAnimProc::ISetNodeButtonText(hWnd, pb);
}