/*==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 "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 #include #include #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 }; class plResponderMtlProc; extern 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& 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& 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& 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 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 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 ); } //////////////////////////////////////////////////////////////////////////////// #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; 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); }