/********************************************************************** *< FILE: linkctrl.cpp DESCRIPTION: An animated link controller CREATED BY: Rolf Berteig HISTORY: 1/25/97 *> Copyright (c) 1996, All Rights Reserved. **********************************************************************/ #include "ctrl.h" #include "units.h" #include "istdplug.h" #define LINKCTRL_NAME GetString(IDS_RB_LINKCTRL) class LinkCtrl; class PickLinkMode : public PickModeCallback, public PickNodeCallback { public: LinkCtrl *cont; PickLinkMode(LinkCtrl *c) {cont=c;} BOOL HitTest(IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags); BOOL Pick(IObjParam *ip,ViewExp *vpt); void EnterMode(IObjParam *ip); void ExitMode(IObjParam *ip); BOOL RightClick(IObjParam *ip,ViewExp *vpt) {return TRUE;} BOOL Filter(INode *node); PickNodeCallback *GetFilter() {return this;} }; class LinkCtrl : public ILinkCtrl { public: Control *tmControl; // ref 0 Tab nodes; // ref 1-n Tab times; static HWND hWnd; static IObjParam *ip; static LinkCtrl *editCont; static ISpinnerControl *iTime; static ICustButton *iPickOb, *iDelOb; static PickLinkMode *pickLinkMode; static BOOL valid; LinkCtrl(BOOL loading=FALSE); // Animatable methods void DeleteThis() {delete this;} int GetNumTargets() {return 0;} Class_ID ClassID() {return LINKCTRL_CLASSID;} SClass_ID SuperClassID() {return CTRL_MATRIX3_CLASS_ID;} void GetClassName(TSTR& s) {s = LINKCTRL_NAME;} void BeginEditParams(IObjParam *ip, ULONG flags,Animatable *prev); void EndEditParams(IObjParam *ip, ULONG flags,Animatable *next); int NumSubs() {return 1;} Animatable* SubAnim(int i) {return tmControl;} TSTR SubAnimName(int i) {return GetString(IDS_RB_TRANSFORM);} // Reference methods RefResult NotifyRefChanged(Interval, RefTargetHandle, PartID&, RefMessage); IOResult Save(ISave *isave); IOResult Load(ILoad *iload); RefTargetHandle Clone(RemapDir &remap = NoRemap()); int NumRefs() {return 1+nodes.Count();} RefTargetHandle GetReference(int i); void SetReference(int i, RefTargetHandle rtarg); BOOL AssignController(Animatable *control,int subAnim); // Control methods void Copy(Control *from); BOOL IsLeaf() {return FALSE;} void CommitValue(TimeValue t) {} void RestoreValue(TimeValue t) {} void GetValue(TimeValue t, void *val, Interval &valid, GetSetMethod method=CTRL_ABSOLUTE); void SetValue(TimeValue t, void *val, int commit=1, GetSetMethod method=CTRL_ABSOLUTE); BOOL InheritsParentTransform() {return FALSE;} BOOL CanInstanceController() {return FALSE;} // Animatable methods void CopyKeysFromTime(TimeValue src,TimeValue dst,DWORD flags) {tmControl->CopyKeysFromTime(src,dst,flags);} BOOL IsKeyAtTime(TimeValue t,DWORD flags) {return tmControl->IsKeyAtTime(t,flags);} BOOL GetNextKeyTime(TimeValue t,DWORD flags,TimeValue &nt) {return tmControl->GetNextKeyTime(t,flags,nt);} int GetKeyTimes(Tab ×,Interval range,DWORD flags) {return tmControl->GetKeyTimes(times,range,flags);} int GetKeySelState(BitArray &sel,Interval range,DWORD flags) {return tmControl->GetKeySelState(sel,range,flags);} Control *GetPositionController() {return tmControl->GetPositionController();} Control *GetRotationController() {return tmControl->GetRotationController();} Control *GetScaleController() {return tmControl->GetScaleController();} BOOL SetPositionController(Control *c) {return tmControl->SetPositionController(c);} BOOL SetRotationController(Control *c) {return tmControl->SetRotationController(c);} BOOL SetScaleController(Control *c) {return tmControl->SetScaleController(c);} void MapKeys(TimeMap *map,DWORD flags); // From ILinkCtrl int GetParentCount() {return nodes.Count();} TimeValue GetLinkTime(int i) {return times[i];} void SetLinkTime(int i,TimeValue t) {SetTime(t,i);} void LinkTimeChanged() {SortNodes(-1);} BOOL SetFrameNumber(int targetNumber, int frameNumber) {return TRUE;} BOOL SetTarget(INode *target, int targetNumber) {return TRUE;} BOOL AddTarget(INode *target, int frameNo) {return TRUE;} INode* GetNode(int targetNumber) {return NULL;} BOOL DeleteTarget(int selection) {return TRUE;} int GetFrameNumber(int targetNumber) {return 0;} // Local methods Matrix3 GetParentTM(TimeValue t,Interval *valid=NULL); Matrix3 CompTM(TimeValue t, int i); void AddNewLink(INode *node,TimeValue t); void DeleteLink(int i); void SetTime(TimeValue t,int i); void SortNodes(int sel); void SetupDialog(HWND hWnd); void DestroyDialog(); void SetupList(int sel=-1); void ListSelChanged(); void Invalidate(); void Update(); }; class LinksRestore : public RestoreObj { public: LinkCtrl *cont; Tab unodes; Tab rnodes; Tab utimes; Tab rtimes; LinksRestore(LinkCtrl *c) { cont = c; unodes = cont->nodes; utimes = cont->times; } void Restore(int isUndo) { if (isUndo) { rnodes = cont->nodes; rtimes = cont->times; } cont->nodes = unodes; cont->times = utimes; cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE); cont->NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED); cont->Invalidate(); } void Redo() { cont->nodes = rnodes; cont->times = rtimes; cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE); cont->NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED); cont->Invalidate(); } void EndHold() {cont->ClearAFlag(A_HELD);} }; //--- Class Descriptor ---------------------------------------------- HWND LinkCtrl::hWnd = NULL; IObjParam *LinkCtrl::ip = NULL; LinkCtrl *LinkCtrl::editCont = NULL; ISpinnerControl *LinkCtrl::iTime = NULL; ICustButton *LinkCtrl::iPickOb = NULL; ICustButton *LinkCtrl::iDelOb = NULL; PickLinkMode *LinkCtrl::pickLinkMode = NULL; BOOL LinkCtrl::valid = FALSE; class LinkCtrlClassDesc:public ClassDesc { public: int IsPublic() {return 1;} void * Create(BOOL loading) {return new LinkCtrl(loading);} const TCHAR * ClassName() {return LINKCTRL_NAME;} SClass_ID SuperClassID() {return CTRL_MATRIX3_CLASS_ID; } Class_ID ClassID() {return LINKCTRL_CLASSID;} const TCHAR* Category() {return _T("");} }; static LinkCtrlClassDesc linkCtrlCD; ClassDesc* GetLinkCtrlDesc() {return &linkCtrlCD;} BOOL CALLBACK LinkParamDialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); //--- LinkCtrl methods ----------------------------------------------- LinkCtrl::LinkCtrl(BOOL loading) { tmControl = NULL; if (!loading) { Control *cont; ClassDesc *desc = GetDefaultController(CTRL_MATRIX3_CLASS_ID); if (desc && desc->ClassID()==ClassID()) { cont = (Control*)CreateInstance(CTRL_MATRIX3_CLASS_ID, Class_ID(PRS_CONTROL_CLASS_ID,0)); } else { cont = NewDefaultMatrix3Controller(); } ReplaceReference(0,cont); } } void LinkCtrl::BeginEditParams( IObjParam *ip, ULONG flags,Animatable *prev) { if (flags&BEGIN_EDIT_MOTION) { this->ip = ip; editCont = this; hWnd = ip->AddRollupPage( hInstance, MAKEINTRESOURCE(IDD_LINK_PARAMS), LinkParamDialogProc, GetString(IDS_RB_LINKPARAMS), (LPARAM)this); pickLinkMode = new PickLinkMode(this); tmControl->BeginEditParams(ip,flags,NULL); } } void LinkCtrl::EndEditParams( IObjParam *ip, ULONG flags,Animatable *next) { if (editCont) { ip->ClearPickMode(); tmControl->EndEditParams(ip,flags,NULL); ip->DeleteRollupPage(hWnd); hWnd = NULL; ip = NULL; editCont = NULL; delete pickLinkMode; pickLinkMode = NULL; } } RefResult LinkCtrl::NotifyRefChanged( Interval iv, RefTargetHandle hTarg, PartID& partID, RefMessage msg) { switch (msg) { case REFMSG_TARGET_DELETED: { for (int i=nodes.Count()-1; i>=0; i--) { if (nodes[i]==hTarg) DeleteLink(i); } break; } } return REF_SUCCEED; } #define LINKCOUNT_CHUNK 0x0100 #define TIMES_CHUNK 0x0110 IOResult LinkCtrl::Save(ISave *isave) { ULONG nb; int ct = nodes.Count(); isave->BeginChunk(LINKCOUNT_CHUNK); isave->Write(&ct,sizeof(ct),&nb); isave->EndChunk(); if (ct) { isave->BeginChunk(TIMES_CHUNK); isave->Write(times.Addr(0),sizeof(TimeValue)*ct,&nb); isave->EndChunk(); } return IO_OK; } IOResult LinkCtrl::Load(ILoad *iload) { ULONG nb; int ct; IOResult res; while (IO_OK==(res=iload->OpenChunk())) { switch (iload->CurChunkID()) { case LINKCOUNT_CHUNK: { iload->Read(&ct,sizeof(ct),&nb); nodes.SetCount(ct); times.SetCount(ct); for (int i=0; iRead(times.Addr(0),sizeof(TimeValue)*ct,&nb); break; } iload->CloseChunk(); if (res!=IO_OK) return res; } return IO_OK; } RefTargetHandle LinkCtrl::Clone(RemapDir &remap) { LinkCtrl *ctrl = new LinkCtrl(); ctrl->ReplaceReference(0,remap.CloneRef(tmControl)); ctrl->nodes.SetCount(nodes.Count()); ctrl->times.SetCount(times.Count()); for (int i=0; inodes[i] = NULL; ctrl->ReplaceReference(i+1,nodes[i]); ctrl->times[i] = times[i]; } return ctrl; } RefTargetHandle LinkCtrl::GetReference(int i) { if (i==0) return tmControl; return nodes[i-1]; } void LinkCtrl::SetReference(int i, RefTargetHandle rtarg) { if (i==0) tmControl = (Control*)rtarg; else nodes[i-1] = (INode*)rtarg; } BOOL LinkCtrl::AssignController(Animatable *control,int subAnim) { if (control->ClassID()==ClassID()) return FALSE; ReplaceReference(0,(ReferenceTarget*)control); NotifyDependents(FOREVER,0,REFMSG_CHANGE); NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED); return TRUE; } void LinkCtrl::Copy(Control *from) { if (from->ClassID()==ClassID()) { LinkCtrl *lc = (LinkCtrl*)from; ReplaceReference(0,(ReferenceTarget*)lc->tmControl->Clone()); } else { ReplaceReference(0,(ReferenceTarget*)from->Clone()); } NotifyDependents(FOREVER,0,REFMSG_CHANGE); NotifyDependents(FOREVER,0,REFMSG_SUBANIM_STRUCTURE_CHANGED); } void LinkCtrl::GetValue( TimeValue t, void *val, Interval &valid, GetSetMethod method) { Matrix3 *ptm = (Matrix3*)val; *ptm = GetParentTM(t,&valid); tmControl->GetValue(t,val,valid,method); } void LinkCtrl::SetValue( TimeValue t, void *val, int commit, GetSetMethod method) { SetXFormPacket *pckt = (SetXFormPacket*)val; pckt->tmParent = GetParentTM(t); tmControl->SetValue(t,val,commit,method); } Matrix3 LinkCtrl::CompTM(TimeValue t, int i) { Matrix3 rtm(1); if (i) { // Evaluate the tm of the previous node the instant of the switch Matrix3 pptm = CompTM(times[i],i-1); // Evaluate the tm of this node the instant of the switch Matrix3 ptm = nodes[i]->GetNodeTM(times[i]); rtm = pptm*Inverse(ptm); } return rtm*nodes[i]->GetNodeTM(t); } Matrix3 LinkCtrl::GetParentTM(TimeValue t,Interval *valid) { if (!nodes.Count()) return Matrix3(1); for (int i=0; it) { if (i) i--; break; } } if (i>nodes.Count()-1) i = nodes.Count()-1; // Compute a matrix that adjusts the transitions between links so // as to maintain continuity Matrix3 rtm(1); if (i) { // Evaluate (recursively) the tm of the previous node the instant of the switch //Matrix3 pptm = nodes[i-1]->GetNodeTM(times[i]); Matrix3 pptm = CompTM(times[i],i-1); // Evaluate the tm of this node the instant of the switch Matrix3 ptm = nodes[i]->GetNodeTM(times[i]); rtm = pptm*Inverse(ptm); } // Validity is limited to the duration of this link if (valid) { Interval iv = FOREVER; if (i) iv.SetStart(times[i]); if (iGetNodeTM(t,valid); } void LinkCtrl::AddNewLink(INode *node,TimeValue t) { if (theHold.Holding() && !TestAFlag(A_HELD)) theHold.Put(new LinksRestore(this)); if (!nodes.Count()) { Matrix3 tm(1), ntm = node->GetNodeTM(t), itm(1); tmControl->GetValue(t,&tm,FOREVER,CTRL_RELATIVE); tmControl->ChangeParents(t,itm,ntm,tm); } for (int i=0; it) break; } nodes.Insert(i,1,&node); times.Insert(i,1,&t); MakeRefByID(FOREVER,i+1,node); NotifyDependents(FOREVER,0,REFMSG_CHANGE); SetupList(i); } void LinkCtrl::DeleteLink(int i) { if (nodes.Count()==1) { TimeValue t = GetCOREInterface()->GetTime(); Matrix3 ptm = GetParentTM(t); Matrix3 itm(1), tm = ptm; tmControl->GetValue(t,&tm,FOREVER,CTRL_RELATIVE); tmControl->ChangeParents(t,ptm,itm,tm); } DeleteReference(i+1); if (theHold.Holding() && !TestAFlag(A_HELD)) theHold.Put(new LinksRestore(this)); nodes.Delete(i,1); times.Delete(i,1); NotifyDependents(FOREVER,0,REFMSG_CHANGE); if (hWnd && editCont==this) SetupList(); } void LinkCtrl::SetTime(TimeValue t,int i) { if (theHold.Holding() && !TestAFlag(A_HELD)) { theHold.Put(new LinksRestore(this)); SetAFlag(A_HELD); } times[i] = t; SortNodes(i); NotifyDependents(FOREVER,0,REFMSG_CHANGE); } void LinkCtrl::MapKeys(TimeMap *map,DWORD flags) { if (theHold.Holding() && !TestAFlag(A_HELD)) { theHold.Put(new LinksRestore(this)); SetAFlag(A_HELD); } for (int i=0; imap(times[i]); } if (hWnd && editCont==this) SetupList(); NotifyDependents(FOREVER,0,REFMSG_CHANGE); } static LinkCtrl *theSortCtrl = NULL; static int __cdecl CompareNodes(const void *a, const void *b) { int *aa = (int*)a; int *bb = (int*)b; assert(theSortCtrl); return theSortCtrl->times[*aa] - theSortCtrl->times[*bb]; } void LinkCtrl::SortNodes(int sel) { // Setup a map for a shell sort Tab map; map.SetCount(nodes.Count()); for (int i=0; i onodes = nodes; Tab otimes = times; for (i=0; i=0) { for (i=0; iSetupDialog(hWnd); break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_LINK_PICK: cont->ip->SetPickMode(cont->pickLinkMode); break; case IDC_LINK_DELETE: { int sel = SendDlgItemMessage(hWnd,IDC_LINK_LIST,LB_GETCURSEL,0,0); if (sel!=LB_ERR) { theHold.Begin(); cont->DeleteLink(sel); theHold.Accept(GetString(IDS_RB_DELETELINK)); cont->ip->RedrawViews(cont->ip->GetTime()); } break; } case IDC_LINK_LIST: if (HIWORD(wParam)==LBN_SELCHANGE) { cont->ListSelChanged(); } break; } break; case CC_SPINNER_BUTTONDOWN: theHold.Begin(); break; case CC_SPINNER_CHANGE: { ISpinnerControl *spin = (ISpinnerControl*)lParam; int sel = SendDlgItemMessage(hWnd,IDC_LINK_LIST,LB_GETCURSEL,0,0); if (sel!=LB_ERR) cont->SetTime(spin->GetIVal(),sel); cont->ip->RedrawViews(cont->ip->GetTime()); break; } case CC_SPINNER_BUTTONUP: if (HIWORD(wParam)) { theHold.Accept(GetString(IDS_RB_SETLINKTIME)); } else { theHold.Cancel(); } cont->ip->RedrawViews(cont->ip->GetTime()); break; case WM_PAINT: cont->Update(); return FALSE; case WM_DESTROY: cont->DestroyDialog(); break; default: return FALSE; } return TRUE; } void LinkCtrl::SetupDialog(HWND hWnd) { this->hWnd = hWnd; iTime = GetISpinner(GetDlgItem(hWnd,IDC_LINK_TIMESPIN)); iTime->SetLimits(TIME_NegInfinity,TIME_PosInfinity,FALSE); iTime->SetScale(10.0f); iTime->LinkToEdit(GetDlgItem(hWnd,IDC_LINK_TIME),EDITTYPE_TIME); iPickOb = GetICustButton(GetDlgItem(hWnd,IDC_LINK_PICK)); iDelOb = GetICustButton(GetDlgItem(hWnd,IDC_LINK_DELETE)); iPickOb->SetType(CBT_CHECK); iPickOb->SetHighlightColor(GREEN_WASH); SetupList(); } void LinkCtrl::DestroyDialog() { ReleaseISpinner(iTime); ReleaseICustButton(iPickOb); ReleaseICustButton(iDelOb); iTime = NULL; iPickOb = NULL; iDelOb = NULL; } void LinkCtrl::SetupList(int sel) { if (!hWnd || editCont!=this) return; int csel = SendDlgItemMessage(hWnd,IDC_LINK_LIST,LB_GETCURSEL,0,0); if (sel>=0) csel = sel; SendDlgItemMessage(hWnd,IDC_LINK_LIST,LB_RESETCONTENT,0,0); for (int i=0; iGetName()); } if (csel!=LB_ERR && cselEnable(); iTime->Enable(); iTime->SetValue(times[sel],FALSE); EnableWindow(GetDlgItem(hWnd,IDC_LINK_TIMELABEL),TRUE); } else { iDelOb->Disable(); iTime->Disable(); EnableWindow(GetDlgItem(hWnd,IDC_LINK_TIMELABEL),FALSE); } } void LinkCtrl::Invalidate() { if (hWnd && editCont==this) { valid = FALSE; InvalidateRect(hWnd,NULL,FALSE); } } void LinkCtrl::Update() { if (!valid) { valid = TRUE; SetupList(); } } //--- PickLinkMode ------------------------------------------------ BOOL PickLinkMode::Filter(INode *node) { if (node) { return (node->TestForLoop(FOREVER,cont)==REF_SUCCEED); } return FALSE; } BOOL PickLinkMode::HitTest( IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags) { INode *node = cont->ip->PickNode(hWnd,m); return node?TRUE:FALSE; } BOOL PickLinkMode::Pick(IObjParam *ip,ViewExp *vpt) { INode *node = vpt->GetClosestHit(); if (node) { if (node->TestForLoop(FOREVER,cont)==REF_SUCCEED) { theHold.Begin(); cont->AddNewLink(node,ip->GetTime()); theHold.Accept(GetString(IDS_RB_ADDLINK)); cont->ip->RedrawViews(cont->ip->GetTime()); } } return FALSE; } void PickLinkMode::EnterMode(IObjParam *ip) {cont->iPickOb->SetCheck(TRUE);} void PickLinkMode::ExitMode(IObjParam *ip) {cont->iPickOb->SetCheck(FALSE);}