Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2743 lines
68 KiB
C++
2743 lines
68 KiB
C++
/**********************************************************************
|
|
*<
|
|
FILE: attach.cpp
|
|
|
|
DESCRIPTION: Attachment controller
|
|
|
|
CREATED BY: Rolf Berteig
|
|
|
|
HISTORY: 11/18/96
|
|
|
|
*> Copyright (c) 1996, All Rights Reserved.
|
|
**********************************************************************/
|
|
#include "ctrl.h"
|
|
#include "units.h"
|
|
#include "tcbgraph.h"
|
|
|
|
|
|
#define ATTACH_CONTROL_CLASS_ID Class_ID(0xbb27e611,0xa72f43e7)
|
|
#define ATTACH_CONTROL_CNAME GetString(IDS_RB_ATTACHCONTROL)
|
|
|
|
static void ComputeHermiteBasis(float u, float *v);
|
|
static float Ease(float u, float a, float b);
|
|
|
|
class AKey {
|
|
public:
|
|
TimeValue time;
|
|
DWORD flags;
|
|
DWORD face;
|
|
float u0, u1;
|
|
float tens, cont, bias, easeIn, easeOut;
|
|
|
|
Point3 pos, norm, din, dout;
|
|
Quat quat, qa, qb;
|
|
|
|
AKey() {
|
|
flags=0; face=0; u0=u1=tens=cont=bias=easeIn=easeOut=0.0f;
|
|
pos=din=dout=Point3(0,0,0);
|
|
norm=Point3(1,0,0);
|
|
quat=qa=qb=IdentQuat();
|
|
}
|
|
AKey &operator=(AKey &k) {
|
|
time = k.time;
|
|
flags = k.flags;
|
|
face = k.face;
|
|
u0 = k.u0;
|
|
u1 = k.u1;
|
|
tens = k.tens;
|
|
cont = k.cont;
|
|
bias = k.bias;
|
|
easeIn = k.easeIn;
|
|
easeOut = k.easeOut;
|
|
|
|
pos = k.pos;
|
|
din = k.din;
|
|
dout = k.dout;
|
|
norm = k.norm;
|
|
quat = k.quat;
|
|
qa = k.qa;
|
|
qb = k.qb;
|
|
return *this;
|
|
}
|
|
|
|
};
|
|
|
|
#define KEY_SELECTED (1<<0)
|
|
#define KEY_FLAGGED (1<<1)
|
|
|
|
class AttachCtrl;
|
|
|
|
class PickObjectMode :
|
|
public PickModeCallback,
|
|
public PickNodeCallback {
|
|
public:
|
|
AttachCtrl *cont;
|
|
|
|
PickObjectMode(AttachCtrl *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;}
|
|
};
|
|
|
|
#define CID_SETPOS 0x938ff27a
|
|
|
|
class SetPosMouseProc : public MouseCallBack {
|
|
public:
|
|
AttachCtrl *cont;
|
|
IObjParam *ip;
|
|
SetPosMouseProc(AttachCtrl *c,IObjParam *i) {cont=c;ip=i;}
|
|
int proc(HWND hWnd, int msg, int point, int flags, IPoint2 m);
|
|
void SetPos(HWND hWnd,IPoint2 m);
|
|
};
|
|
|
|
class SetPosCMode : public CommandMode {
|
|
public:
|
|
ChangeFGObject fgProc;
|
|
SetPosMouseProc proc;
|
|
IObjParam *ip;
|
|
AttachCtrl *cont;
|
|
|
|
SetPosCMode(AttachCtrl *c,IObjParam *i)
|
|
: fgProc((ReferenceTarget*)c), proc(c,i) {ip=i;cont=c;}
|
|
|
|
int Class() {return MOVE_COMMAND;}
|
|
int ID() {return CID_SETPOS;}
|
|
MouseCallBack *MouseProc(int *numPoints) {*numPoints=2;return &proc;}
|
|
ChangeForegroundCallback *ChangeFGProc() {return &fgProc;}
|
|
BOOL ChangeFG(CommandMode *oldMode) {return oldMode->ChangeFGProc() != &fgProc;}
|
|
void EnterMode();
|
|
void ExitMode();
|
|
};
|
|
|
|
|
|
class AttachCtrl : public StdControl, public TimeChangeCallback {
|
|
public:
|
|
// Controller data
|
|
Tab<AKey> keys;
|
|
INode *node;
|
|
BOOL rangeLinked, align, manUpdate;
|
|
|
|
// Current value cache
|
|
Interval range, valid;
|
|
Point3 val;
|
|
Quat qval;
|
|
BOOL trackValid, doManUpdate;
|
|
|
|
static HWND hWnd;
|
|
static IObjParam *ip;
|
|
static AttachCtrl *editCont;
|
|
static BOOL uiValid;
|
|
static ISpinnerControl *iTime, *iFace, *iA, *iB, *iTens, *iCont, *iBias, *iEaseTo, *iEaseFrom;
|
|
static ICustButton *iPickOb, *iSetPos, *iPrev, *iNext, *iUpdate;
|
|
static ICustStatus *iStat;
|
|
static int index;
|
|
static PickObjectMode *pickObMode;
|
|
static SetPosCMode *setPosMode;
|
|
|
|
AttachCtrl();
|
|
|
|
// Animatable methods
|
|
void DeleteThis() {delete this;}
|
|
int IsKeyable() {return 1;}
|
|
BOOL IsAnimated() {return keys.Count?TRUE:FALSE;}
|
|
Class_ID ClassID() {return ATTACH_CONTROL_CLASS_ID;}
|
|
SClass_ID SuperClassID() {return CTRL_POSITION_CLASS_ID;}
|
|
void GetClassName(TSTR& s) {s = ATTACH_CONTROL_CNAME;}
|
|
void BeginEditParams(IObjParam *ip, ULONG flags,Animatable *prev);
|
|
void EndEditParams(IObjParam *ip, ULONG flags,Animatable *next);
|
|
|
|
// Reference methods
|
|
RefResult NotifyRefChanged(Interval, RefTargetHandle, PartID&, RefMessage);
|
|
IOResult Save(ISave *isave);
|
|
IOResult Load(ILoad *iload);
|
|
RefTargetHandle Clone(RemapDir &remap = NoRemap());
|
|
int NumRefs() {return Control::NumRefs()+1;};
|
|
RefTargetHandle GetReference(int i);
|
|
void SetReference(int i, RefTargetHandle rtarg);
|
|
|
|
// Control methods
|
|
void Copy(Control *from);
|
|
BOOL IsLeaf() {return TRUE;}
|
|
void CommitValue(TimeValue t) {}
|
|
void RestoreValue(TimeValue t) {}
|
|
|
|
// Animatable methods
|
|
Interval GetTimeRange(DWORD flags);
|
|
void EditTimeRange(Interval range,DWORD flags);
|
|
void MapKeys(TimeMap *map,DWORD flags );
|
|
int NumKeys() {return keys.Count();}
|
|
TimeValue GetKeyTime(int index) {return keys[index].time;}
|
|
int GetKeyIndex(TimeValue t);
|
|
void DeleteKeyAtTime(TimeValue t);
|
|
BOOL IsKeyAtTime(TimeValue t,DWORD flags);
|
|
int GetKeyTimes(Tab<TimeValue> ×,Interval range,DWORD flags);
|
|
int GetKeySelState(BitArray &sel,Interval range,DWORD flags);
|
|
BOOL GetNextKeyTime(TimeValue t,DWORD flags,TimeValue &nt);
|
|
void DeleteTime(Interval iv, DWORD flags);
|
|
void ReverseTime(Interval iv, DWORD flags);
|
|
void ScaleTime(Interval iv, float s);
|
|
void InsertTime(TimeValue ins, TimeValue amount);
|
|
BOOL SupportTimeOperations() {return TRUE;}
|
|
void DeleteKeys(DWORD flags);
|
|
void DeleteKeyByIndex(int index);
|
|
void SelectKeys(TrackHitTab& sel, DWORD flags);
|
|
void SelectKeyByIndex(int i,BOOL sel);
|
|
void FlagKey(TrackHitRecord hit);
|
|
int GetFlagKeyIndex();
|
|
int NumSelKeys();
|
|
void CloneSelectedKeys(BOOL offset=FALSE);
|
|
void AddNewKey(TimeValue t,DWORD flags);
|
|
BOOL IsKeySelected(int index);
|
|
BOOL CanCopyTrack(Interval iv, DWORD flags) {return TRUE;}
|
|
BOOL CanPasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags) {return cobj->ClassID()==ClassID();}
|
|
TrackClipObject *CopyTrack(Interval iv, DWORD flags);
|
|
void PasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags);
|
|
int HitTestTrack(
|
|
TrackHitTab& hits,
|
|
Rect& rcHit,
|
|
Rect& rcTrack,
|
|
float zoom,
|
|
int scroll,
|
|
DWORD flags);
|
|
int PaintTrack(
|
|
ParamDimensionBase *dim,
|
|
HDC hdc,
|
|
Rect& rcTrack,
|
|
Rect& rcPaint,
|
|
float zoom,
|
|
int scroll,
|
|
DWORD flags);
|
|
void EditTrackParams(
|
|
TimeValue t,
|
|
ParamDimensionBase *dim,
|
|
TCHAR *pname,
|
|
HWND hParent,
|
|
IObjParam *ip,
|
|
DWORD flags);
|
|
int TrackParamsType() {return TRACKPARAMS_KEY;}
|
|
|
|
// StdControl methods
|
|
void GetValueLocalTime(TimeValue t, void *val, Interval &valid, GetSetMethod method=CTRL_ABSOLUTE) {}
|
|
void GetValue(TimeValue t, void *val, Interval &valid, GetSetMethod method=CTRL_ABSOLUTE);
|
|
void SetValueLocalTime(TimeValue t, void *val, int commit, GetSetMethod method) {}
|
|
void Extrapolate(Interval range,TimeValue t,void *val,Interval &valid,int type) {}
|
|
void *CreateTempValue() {return new Point3;}
|
|
void DeleteTempValue(void *val) {delete (Point3*)val;}
|
|
void ApplyValue(void *val, void *delta) {((Matrix3*)val)->PreTranslate(*((Point3*)delta));}
|
|
void MultiplyValue(void *val, float m) {*((Point3*)val) *= m;}
|
|
|
|
// TimeChangedCallback
|
|
void TimeChanged(TimeValue t) {InvalidateUI();}
|
|
|
|
// Local methods
|
|
void SortKeys();
|
|
void HoldTrack();
|
|
void Invalidate();
|
|
void PrepareTrack(TimeValue t);
|
|
float GetInterpVal(TimeValue t,int &n0, int &n1);
|
|
Point3 PointOnPath(TimeValue t);
|
|
Quat QuatOnPath(TimeValue t);
|
|
|
|
Interval CompValidity(TimeValue t);
|
|
BOOL SetObject(INode *node);
|
|
void SetKeyPos(TimeValue t, DWORD fi, Point3 bary);
|
|
|
|
void CompFirstDeriv();
|
|
void CompLastDeriv();
|
|
void Comp2KeyDeriv();
|
|
void CompMiddleDeriv(int i);
|
|
void CompAB(int i);
|
|
|
|
void InvalidateUI();
|
|
void UpdateUI();
|
|
void UpdateTCBGraph();
|
|
void UpdateBaryGraph();
|
|
void SetupWindow(HWND hWnd);
|
|
void DestroyWindow();
|
|
void SpinnerChange(int id);
|
|
void Command(int id, LPARAM lParam);
|
|
};
|
|
|
|
HWND AttachCtrl::hWnd = NULL;
|
|
IObjParam *AttachCtrl::ip = NULL;
|
|
AttachCtrl *AttachCtrl::editCont = NULL;
|
|
BOOL AttachCtrl::uiValid = FALSE;
|
|
ISpinnerControl *AttachCtrl::iTime = NULL;
|
|
ISpinnerControl *AttachCtrl::iFace = NULL;
|
|
ISpinnerControl *AttachCtrl::iA = NULL;
|
|
ISpinnerControl *AttachCtrl::iB = NULL;
|
|
ISpinnerControl *AttachCtrl::iTens = NULL;
|
|
ISpinnerControl *AttachCtrl::iCont = NULL;
|
|
ISpinnerControl *AttachCtrl::iBias = NULL;
|
|
ISpinnerControl *AttachCtrl::iEaseTo = NULL;
|
|
ISpinnerControl *AttachCtrl::iEaseFrom = NULL;
|
|
ICustButton *AttachCtrl::iPickOb = NULL;
|
|
ICustButton *AttachCtrl::iSetPos = NULL;
|
|
ICustButton *AttachCtrl::iPrev = NULL;
|
|
ICustButton *AttachCtrl::iNext = NULL;
|
|
ICustButton *AttachCtrl::iUpdate = NULL;
|
|
ICustStatus *AttachCtrl::iStat = NULL;
|
|
int AttachCtrl::index = -1;
|
|
PickObjectMode *AttachCtrl::pickObMode = NULL;
|
|
SetPosCMode *AttachCtrl::setPosMode = NULL;
|
|
|
|
class AttachClassDesc:public ClassDesc {
|
|
public:
|
|
int IsPublic() {return 1;}
|
|
void * Create(BOOL loading) {return new AttachCtrl();}
|
|
const TCHAR * ClassName() {return ATTACH_CONTROL_CNAME;}
|
|
SClass_ID SuperClassID() {return CTRL_POSITION_CLASS_ID; }
|
|
Class_ID ClassID() {return ATTACH_CONTROL_CLASS_ID;}
|
|
const TCHAR* Category() {return _T("");}
|
|
};
|
|
static AttachClassDesc attachCD;
|
|
ClassDesc* GetAttachControlDesc() {return &attachCD;}
|
|
|
|
|
|
class AttachCtrlRestore : public RestoreObj {
|
|
public:
|
|
Tab<AKey> undo;
|
|
Tab<AKey> redo;
|
|
Interval urange, rrange;
|
|
AttachCtrl *cont;
|
|
|
|
AttachCtrlRestore(AttachCtrl *c) {
|
|
cont = c;
|
|
undo = c->keys;
|
|
urange = c->range;
|
|
}
|
|
|
|
void Restore(int isUndo) {
|
|
if (isUndo) {
|
|
redo = cont->keys;
|
|
rrange = cont->range;
|
|
}
|
|
cont->keys = undo;
|
|
cont->range = urange;
|
|
cont->Invalidate();
|
|
cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
|
|
}
|
|
|
|
void Redo() {
|
|
cont->keys = redo;
|
|
cont->range = rrange;
|
|
cont->Invalidate();
|
|
cont->NotifyDependents(FOREVER,0,REFMSG_CHANGE);
|
|
}
|
|
|
|
int Size() {return 1;}
|
|
void EndHold() {cont->ClearAFlag(A_HELD);}
|
|
};
|
|
|
|
|
|
class AttachClipObject : public TrackClipObject {
|
|
public:
|
|
Tab<AKey> tab;
|
|
|
|
Class_ID ClassID() {return ATTACH_CONTROL_CLASS_ID;}
|
|
SClass_ID SuperClassID() { return CTRL_FLOAT_CLASS_ID; }
|
|
void DeleteThis() {delete this;}
|
|
|
|
AttachClipObject(Interval iv) : TrackClipObject(iv) {}
|
|
};
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AttachCtrl::AttachCtrl()
|
|
{
|
|
node = NULL;
|
|
rangeLinked = TRUE;
|
|
align = TRUE;
|
|
trackValid = FALSE;
|
|
manUpdate = FALSE;
|
|
doManUpdate = FALSE;
|
|
val = Point3(0,0,0);
|
|
qval = IdentQuat();
|
|
}
|
|
|
|
RefTargetHandle AttachCtrl::Clone(RemapDir &remap)
|
|
{
|
|
AttachCtrl *bc = new AttachCtrl;
|
|
bc->range = range;
|
|
bc->valid = valid;
|
|
bc->keys = keys;
|
|
bc->val = val;
|
|
bc->node = NULL;
|
|
bc->manUpdate = manUpdate;
|
|
CloneControl(bc,remap);
|
|
return bc;
|
|
}
|
|
|
|
#define KEYTIME_CHUNKID 0x0100
|
|
#define KEYFLAGS_CHUNKID 0x0110
|
|
#define NUMKEYS_CHUNKID 0x0130
|
|
#define KEYFACE_CHUNKID 0x0140
|
|
#define KEYU0_CHUNKID 0x0150
|
|
#define KEYU1_CHUNKID 0x0160
|
|
#define KEYTENS_CHUNKID 0x0170
|
|
#define KEYCONT_CHUNKID 0x0180
|
|
#define KEYBIAS_CHUNKID 0x0190
|
|
#define KEYEASEIN_CHUNKID 0x0200
|
|
#define KEYEASEOUT_CHUNKID 0x0210
|
|
#define ALIGN_CHUNKID 0x0220
|
|
#define MANUPDATE_CHUNKID 0x0230
|
|
|
|
IOResult AttachCtrl::Save(ISave *isave)
|
|
{
|
|
ULONG nb;
|
|
int ct = keys.Count();
|
|
|
|
// Save align
|
|
isave->BeginChunk(ALIGN_CHUNKID);
|
|
isave->Write(&align,sizeof(align),&nb);
|
|
isave->EndChunk();
|
|
|
|
// Manual update
|
|
isave->BeginChunk(MANUPDATE_CHUNKID);
|
|
isave->Write(&manUpdate,sizeof(manUpdate),&nb);
|
|
isave->EndChunk();
|
|
|
|
// Save the number of keys
|
|
isave->BeginChunk(NUMKEYS_CHUNKID);
|
|
isave->Write(&ct,sizeof(ct),&nb);
|
|
isave->EndChunk();
|
|
|
|
// Write each key
|
|
for (int i=0; i<ct; i++) {
|
|
isave->BeginChunk(KEYTIME_CHUNKID);
|
|
isave->Write(&keys[i].time,sizeof(keys[i].time),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYFLAGS_CHUNKID);
|
|
isave->Write(&keys[i].flags,sizeof(keys[i].flags),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYFACE_CHUNKID);
|
|
isave->Write(&keys[i].face,sizeof(keys[i].face),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYU0_CHUNKID);
|
|
isave->Write(&keys[i].u0,sizeof(keys[i].u0),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYU1_CHUNKID);
|
|
isave->Write(&keys[i].u1,sizeof(keys[i].u1),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYTENS_CHUNKID);
|
|
isave->Write(&keys[i].tens,sizeof(keys[i].tens),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYCONT_CHUNKID);
|
|
isave->Write(&keys[i].cont,sizeof(keys[i].cont),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYBIAS_CHUNKID);
|
|
isave->Write(&keys[i].bias,sizeof(keys[i].bias),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYEASEIN_CHUNKID);
|
|
isave->Write(&keys[i].easeIn,sizeof(keys[i].easeIn),&nb);
|
|
isave->EndChunk();
|
|
|
|
isave->BeginChunk(KEYEASEOUT_CHUNKID);
|
|
isave->Write(&keys[i].easeOut,sizeof(keys[i].easeOut),&nb);
|
|
isave->EndChunk();
|
|
}
|
|
|
|
return IO_OK;
|
|
}
|
|
|
|
IOResult AttachCtrl::Load(ILoad *iload)
|
|
{
|
|
ULONG nb;
|
|
int ct;
|
|
IOResult res;
|
|
|
|
while (IO_OK==(res=iload->OpenChunk())) {
|
|
switch (iload->CurChunkID()) {
|
|
case ALIGN_CHUNKID:
|
|
iload->Read(&align,sizeof(align),&nb);
|
|
break;
|
|
|
|
case MANUPDATE_CHUNKID:
|
|
iload->Read(&manUpdate,sizeof(manUpdate),&nb);
|
|
break;
|
|
|
|
case NUMKEYS_CHUNKID:
|
|
iload->Read(&ct,sizeof(ct),&nb);
|
|
iload->CloseChunk();
|
|
keys.SetCount(ct);
|
|
|
|
// Key data always follows
|
|
for (int i=0; i<ct; i++) {
|
|
keys[i] = AKey();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYTIME_CHUNKID);
|
|
iload->Read(&keys[i].time,sizeof(keys[i].time),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYFLAGS_CHUNKID);
|
|
iload->Read(&keys[i].flags,sizeof(keys[i].flags),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYFACE_CHUNKID);
|
|
iload->Read(&keys[i].face,sizeof(keys[i].face),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYU0_CHUNKID);
|
|
iload->Read(&keys[i].u0,sizeof(keys[i].u0),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYU1_CHUNKID);
|
|
iload->Read(&keys[i].u1,sizeof(keys[i].u1),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYTENS_CHUNKID);
|
|
iload->Read(&keys[i].tens,sizeof(keys[i].tens),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYCONT_CHUNKID);
|
|
iload->Read(&keys[i].cont,sizeof(keys[i].cont),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYBIAS_CHUNKID);
|
|
iload->Read(&keys[i].bias,sizeof(keys[i].bias),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYEASEIN_CHUNKID);
|
|
iload->Read(&keys[i].easeIn,sizeof(keys[i].easeIn),&nb);
|
|
iload->CloseChunk();
|
|
|
|
iload->OpenChunk();
|
|
assert(iload->CurChunkID()==KEYEASEOUT_CHUNKID);
|
|
iload->Read(&keys[i].easeOut,sizeof(keys[i].easeOut),&nb);
|
|
iload->CloseChunk();
|
|
}
|
|
continue;
|
|
}
|
|
iload->CloseChunk();
|
|
if (res!=IO_OK) return res;
|
|
}
|
|
|
|
return IO_OK;
|
|
}
|
|
|
|
void AttachCtrl::Copy(Control *from)
|
|
{
|
|
}
|
|
|
|
void AttachCtrl::HoldTrack()
|
|
{
|
|
if (theHold.Holding()&&!TestAFlag(A_HELD)) {
|
|
theHold.Put(new AttachCtrlRestore(this));
|
|
SetAFlag(A_HELD);
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::EditTimeRange(Interval range,DWORD flags)
|
|
{
|
|
HoldTrack();
|
|
if (flags&EDITRANGE_LINKTOKEYS && keys.Count()) {
|
|
this->range.Set(keys[0].time,keys[keys.Count()-1].time);
|
|
rangeLinked = TRUE;
|
|
} else {
|
|
rangeLinked = FALSE;
|
|
this->range = range;
|
|
}
|
|
Invalidate();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
Interval AttachCtrl::GetTimeRange(DWORD flags)
|
|
{
|
|
if (rangeLinked && keys.Count())
|
|
return Interval(keys[0].time,keys[keys.Count()-1].time);
|
|
else return range;
|
|
}
|
|
|
|
void AttachCtrl::MapKeys(TimeMap *map,DWORD flags)
|
|
{
|
|
int n = keys.Count();
|
|
BOOL changed = FALSE;
|
|
if (!n) return;
|
|
HoldTrack();
|
|
|
|
if (flags&TRACK_MAPRANGE) {
|
|
TimeValue t0 = map->map(range.Start());
|
|
TimeValue t1 = map->map(range.End());
|
|
range.Set(t0,t1);
|
|
changed = TRUE;
|
|
}
|
|
|
|
if (flags&TRACK_DOALL) {
|
|
for (int i=0; i<n; i++) {
|
|
keys[i].time = map->map(keys[i].time);
|
|
changed = TRUE;
|
|
}
|
|
} else
|
|
if (flags&TRACK_DOSEL) {
|
|
BOOL slide = flags&TRACK_SLIDEUNSEL;
|
|
TimeValue delta = 0, prev;
|
|
int start, end, inc;
|
|
if (flags&TRACK_RIGHTTOLEFT) {
|
|
start = n-1;
|
|
end = -1;
|
|
inc = -1;
|
|
} else {
|
|
start = 0;
|
|
end = n;
|
|
inc = 1;
|
|
}
|
|
for (int i=start; i!=end; i+=inc) {
|
|
if (keys[i].flags & KEY_SELECTED) {
|
|
prev = keys[i].time;
|
|
keys[i].time = map->map(keys[i].time);
|
|
delta = keys[i].time - prev;
|
|
changed = TRUE;
|
|
} else if (slide) {
|
|
keys[i].time += delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
}
|
|
|
|
|
|
int AttachCtrl::GetKeyIndex(TimeValue t)
|
|
{
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time==t) return i;
|
|
if (keys[i].time>t) return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void AttachCtrl::DeleteKeyAtTime(TimeValue t)
|
|
{
|
|
int index = GetKeyIndex(t);
|
|
if (index>=0) {
|
|
HoldTrack();
|
|
keys.Delete(index,1);
|
|
Invalidate();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
}
|
|
|
|
BOOL AttachCtrl::IsKeyAtTime(TimeValue t,DWORD flags)
|
|
{
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time>t) return FALSE;
|
|
if (keys[i].time==t) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int AttachCtrl::GetKeyTimes(
|
|
Tab<TimeValue> ×,Interval range,DWORD flags)
|
|
{
|
|
int skip = 0;
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time >= range.Start() && keys[i].time <= range.End()) {
|
|
times.Append(1,&keys[i].time,10);
|
|
} else {
|
|
if (keys[i].time > range.End()) break;
|
|
skip++;
|
|
}
|
|
}
|
|
times.Shrink();
|
|
return skip;
|
|
}
|
|
|
|
int AttachCtrl::GetKeySelState(
|
|
BitArray &sel,Interval range,DWORD flags)
|
|
{
|
|
int c = 0;
|
|
int skip = 0;
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time >= range.Start() && keys[i].time <= range.End()) {
|
|
sel.Set(c++,keys[i].flags&KEY_SELECTED);
|
|
} else {
|
|
if (keys[i].time > range.End()) break;
|
|
skip++;
|
|
}
|
|
}
|
|
return skip;
|
|
}
|
|
|
|
BOOL AttachCtrl::GetNextKeyTime(
|
|
TimeValue t,DWORD flags,TimeValue &nt)
|
|
{
|
|
if (!keys.Count()) return FALSE;
|
|
if (flags&NEXTKEY_RIGHT) {
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time > t ) {
|
|
nt = keys[i].time;
|
|
return TRUE;
|
|
}
|
|
}
|
|
nt = keys[0].time;
|
|
return TRUE;
|
|
} else {
|
|
for (int i=keys.Count()-1; i>=0; i--) {
|
|
if (keys[i].time < t ) {
|
|
nt = keys[i].time;
|
|
return TRUE;
|
|
}
|
|
}
|
|
nt = keys[keys.Count()-1].time;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
void AttachCtrl::DeleteTime(Interval iv, DWORD flags)
|
|
{
|
|
Interval test = TestInterval(iv,flags);
|
|
int n = keys.Count();
|
|
int d = iv.Duration()-1;
|
|
if (d<0) d = 0;
|
|
HoldTrack();
|
|
|
|
for (int i = n-1; i >= 0; i--) {
|
|
if (test.InInterval(keys[i].time)) {
|
|
keys.Delete(i,1);
|
|
} else
|
|
if (!(flags&TIME_NOSLIDE)) {
|
|
if (keys[i].time > test.End()) {
|
|
keys[i].time -= d;
|
|
}
|
|
}
|
|
}
|
|
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::ReverseTime(Interval iv, DWORD flags)
|
|
{
|
|
Interval test = TestInterval(iv,flags);
|
|
int n = keys.Count();
|
|
HoldTrack();
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
if (test.InInterval(keys[i].time)) {
|
|
TimeValue delta = keys[i].time - iv.Start();
|
|
keys[i].time = iv.End()-delta;
|
|
}
|
|
}
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::ScaleTime(Interval iv, float s)
|
|
{
|
|
int n = keys.Count();
|
|
TimeValue delta = int(s*float(iv.End()-iv.Start())) + iv.Start()-iv.End();
|
|
HoldTrack();
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
if (iv.InInterval(keys[i].time)) {
|
|
keys[i].time =
|
|
int(s*float(keys[i].time - iv.Start())) + iv.Start();
|
|
} else
|
|
if (keys[i].time > iv.End()) {
|
|
keys[i].time += delta;
|
|
}
|
|
}
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::InsertTime(TimeValue ins, TimeValue amount)
|
|
{
|
|
int n = keys.Count();
|
|
HoldTrack();
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
if (keys[i].time >= ins) {
|
|
keys[i].time += amount;
|
|
}
|
|
}
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::DeleteKeys(DWORD flags)
|
|
{
|
|
int n = keys.Count();
|
|
HoldTrack();
|
|
|
|
for (int i = n-1; i >= 0; i--) {
|
|
if (flags&TRACK_DOALL || keys[i].flags&KEY_SELECTED) {
|
|
keys.Delete(i,1);
|
|
}
|
|
}
|
|
Invalidate();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::DeleteKeyByIndex(int index)
|
|
{
|
|
HoldTrack();
|
|
keys.Delete(index,1);
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::SelectKeyByIndex(int i,BOOL sel)
|
|
{
|
|
HoldTrack();
|
|
if (sel) keys[i].flags |= KEY_SELECTED;
|
|
else keys[i].flags &= ~KEY_SELECTED;
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::SelectKeys(TrackHitTab& sel, DWORD flags)
|
|
{
|
|
HoldTrack();
|
|
|
|
if (flags&SELKEYS_CLEARKEYS) {
|
|
int n = keys.Count();
|
|
for (int i = 0; i < n; i++ ) {
|
|
keys[i].flags &= ~KEY_SELECTED;
|
|
}
|
|
}
|
|
|
|
if (flags&SELKEYS_DESELECT) {
|
|
for (int i = 0; i < sel.Count(); i++ ) {
|
|
keys[sel[i].hit].flags &= ~KEY_SELECTED;
|
|
}
|
|
}
|
|
if (flags&SELKEYS_SELECT) {
|
|
for (int i = 0; i < sel.Count(); i++ ) {
|
|
keys[sel[i].hit].flags |= KEY_SELECTED;
|
|
}
|
|
}
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
void AttachCtrl::FlagKey(TrackHitRecord hit)
|
|
{
|
|
int n = keys.Count();
|
|
for (int i = 0; i < n; i++) {
|
|
keys[i].flags &= ~KEY_FLAGGED;
|
|
}
|
|
assert(hit.hit>=0&&hit.hit<(DWORD)n);
|
|
keys[hit.hit].flags |= KEY_FLAGGED;
|
|
}
|
|
|
|
int AttachCtrl::GetFlagKeyIndex()
|
|
{
|
|
int n = keys.Count();
|
|
for (int i = 0; i < n; i++) {
|
|
if (keys[i].flags & KEY_FLAGGED) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int AttachCtrl::NumSelKeys()
|
|
{
|
|
int n = keys.Count();
|
|
int c = 0;
|
|
for ( int i = 0; i < n; i++ ) {
|
|
if (keys[i].flags & KEY_SELECTED) {
|
|
c++;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void AttachCtrl::CloneSelectedKeys(BOOL offset)
|
|
{
|
|
int n = keys.Count();
|
|
HoldTrack();
|
|
BOOL changed = FALSE;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
if (keys[i].flags & KEY_SELECTED) {
|
|
AKey key(keys[i]);
|
|
key.flags |= KEY_SELECTED;
|
|
keys.Append(1,&key,5);
|
|
keys[i].flags &= ~KEY_SELECTED;
|
|
changed = TRUE;
|
|
}
|
|
}
|
|
if (changed) {
|
|
keys.Shrink();
|
|
Invalidate();
|
|
SortKeys();
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::AddNewKey(TimeValue t,DWORD flags)
|
|
{
|
|
HoldTrack();
|
|
AKey key;
|
|
key.time = t;
|
|
key.face = 0;
|
|
key.u0 = key.u1 = 1.0f/3.0f;
|
|
key.tens = key.cont = key.bias = key.easeIn = key.easeOut = 0.0f;
|
|
|
|
// Try to find a nearby key
|
|
if (keys.Count()) {
|
|
int n0, n1;
|
|
GetInterpVal(t,n0,n1);
|
|
key.face = keys[n0].face;
|
|
key.u0 = keys[n0].u0;
|
|
key.u1 = keys[n0].u1;
|
|
}
|
|
|
|
if (flags&ADDKEY_SELECT) {
|
|
key.flags |= KEY_SELECTED;
|
|
}
|
|
if (flags&ADDKEY_FLAGGED) {
|
|
int n = keys.Count();
|
|
for (int i = 0; i < n; i++) {
|
|
keys[i].flags &= ~KEY_FLAGGED;
|
|
}
|
|
key.flags |= KEY_FLAGGED;
|
|
}
|
|
keys.Append(1,&key);
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
BOOL AttachCtrl::IsKeySelected(int index)
|
|
{
|
|
return keys[index].flags & KEY_SELECTED;
|
|
}
|
|
|
|
TrackClipObject *AttachCtrl::CopyTrack(Interval iv, DWORD flags)
|
|
{
|
|
AttachClipObject *cobj = new AttachClipObject(iv);
|
|
Interval test = TestInterval(iv,flags);
|
|
for (int i = 0; i < keys.Count(); i++) {
|
|
if (test.InInterval(keys[i].time)) {
|
|
AKey nk(keys[i]);
|
|
cobj->tab.Append(1,&nk,10);
|
|
}
|
|
}
|
|
cobj->tab.Shrink();
|
|
return cobj;
|
|
}
|
|
|
|
void AttachCtrl::PasteTrack(TrackClipObject *cobj,Interval iv, DWORD flags)
|
|
{
|
|
AttachClipObject *cob = (AttachClipObject*)cobj;
|
|
HoldTrack();
|
|
DeleteTime(iv,flags);
|
|
InsertTime(iv.Start(),cob->clip.Duration()-1);
|
|
for (int i = 0; i < cob->tab.Count(); i++) {
|
|
AKey key(cob->tab[i]);
|
|
key.time -= cob->clip.Start() - iv.Start();
|
|
keys.Append(1,&key);
|
|
}
|
|
Invalidate();
|
|
SortKeys();
|
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
|
}
|
|
|
|
int AttachCtrl::HitTestTrack(
|
|
TrackHitTab& hits,
|
|
Rect& rcHit,
|
|
Rect& rcTrack,
|
|
float zoom,
|
|
int scroll,
|
|
DWORD flags)
|
|
{
|
|
int left = ScreenToTime(rcTrack.left,zoom,scroll) - 4;
|
|
int right = ScreenToTime(rcTrack.right,zoom,scroll) + 4;
|
|
int n = keys.Count();
|
|
int y = (rcTrack.top+rcTrack.bottom)/2;
|
|
|
|
for ( int i = 0; i < n; i++ ) {
|
|
if (flags&HITTRACK_SELONLY &&
|
|
!(keys[i].flags & KEY_SELECTED)) continue;
|
|
if (flags&HITTRACK_UNSELONLY &&
|
|
(keys[i].flags & KEY_SELECTED)) continue;
|
|
|
|
if (keys[i].time > right) {
|
|
break;
|
|
}
|
|
if (keys[i].time > left) {
|
|
int x = TimeToScreen(keys[i].time,zoom,scroll);
|
|
if (rcHit.Contains(IPoint2(x,y))) {
|
|
TrackHitRecord rec(i,0);
|
|
hits.Append(1,&rec);
|
|
if (flags&HITTRACK_ABORTONHIT) return TRACK_DONE;
|
|
}
|
|
}
|
|
}
|
|
return TRACK_DONE;
|
|
}
|
|
|
|
int AttachCtrl::PaintTrack(
|
|
ParamDimensionBase *dim,
|
|
HDC hdc,
|
|
Rect& rcTrack,
|
|
Rect& rcPaint,
|
|
float zoom,
|
|
int scroll,
|
|
DWORD flags)
|
|
{
|
|
int left = ScreenToTime(rcPaint.left-8,zoom,scroll);
|
|
int right = ScreenToTime(rcPaint.right+8,zoom,scroll);
|
|
int n = keys.Count();
|
|
int y = (rcTrack.top+rcTrack.bottom)/2, x = rcPaint.left-2;
|
|
HBRUSH selBrush = CreateSolidBrush(RGB(255,255,255));
|
|
HBRUSH unselBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
|
|
SelectObject(hdc,GetStockObject(BLACK_PEN));
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
if (keys[i].time > right) {
|
|
break;
|
|
}
|
|
if (keys[i].time > left) {
|
|
x = TimeToScreen(keys[i].time,zoom,scroll);
|
|
if ((flags&PAINTTRACK_SHOWSEL) && (keys[i].flags&KEY_SELECTED)) {
|
|
SelectObject(hdc,selBrush);
|
|
} else {
|
|
SelectObject(hdc,unselBrush);
|
|
}
|
|
Ellipse(hdc,x-4,y-5,x+4,y+5);
|
|
}
|
|
}
|
|
|
|
DeleteObject(selBrush);
|
|
return TRACK_DONE;
|
|
}
|
|
|
|
|
|
static int __cdecl CompareAKeys(const AKey *k1, const AKey *k2)
|
|
{
|
|
if (k1->time < k2->time) return -1;
|
|
if (k1->time > k2->time) return 1;
|
|
return 0;
|
|
}
|
|
|
|
void AttachCtrl::SortKeys()
|
|
{
|
|
keys.Sort((CompareFnc)CompareAKeys);
|
|
}
|
|
|
|
|
|
RefResult AttachCtrl::NotifyRefChanged(
|
|
Interval valid, RefTargetHandle targ,
|
|
PartID &partID, RefMessage msg)
|
|
{
|
|
switch (msg) {
|
|
case REFMSG_CHANGE:
|
|
Invalidate();
|
|
break;
|
|
|
|
case REFMSG_TARGET_DELETED:
|
|
node = NULL;
|
|
break;
|
|
}
|
|
return REF_SUCCEED;
|
|
}
|
|
|
|
RefTargetHandle AttachCtrl::GetReference(int i)
|
|
{
|
|
if (i<Control::NumRefs()) {
|
|
return Control::GetReference(i);
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::SetReference(int i, RefTargetHandle rtarg)
|
|
{
|
|
if (i<Control::NumRefs()) {
|
|
Control::SetReference(i,rtarg);
|
|
} else {
|
|
node = (INode*)rtarg;
|
|
}
|
|
}
|
|
|
|
Interval AttachCtrl::CompValidity(TimeValue t)
|
|
{
|
|
Interval v = FOREVER;
|
|
if (keys.Count()) {
|
|
if (t<keys[0].time) {
|
|
v.Set(TIME_NegInfinity,keys[0].time);
|
|
} else
|
|
if (t>keys[keys.Count()-1].time) {
|
|
v.Set(keys[keys.Count()-1].time,TIME_PosInfinity);
|
|
} else {
|
|
v.SetInstant(t);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void AttachCtrl::GetValue(
|
|
TimeValue t, void *val, Interval &valid, GetSetMethod method)
|
|
{
|
|
TimeValue oldTime = t;
|
|
t = ApplyEase(t,valid);
|
|
if (!this->valid.InInterval(t)) {
|
|
this->valid = CompValidity(t);
|
|
PrepareTrack(t);
|
|
this->val = PointOnPath(t);
|
|
if (align) {
|
|
qval = QuatOnPath(t);
|
|
}
|
|
if (this->valid.Empty()) this->valid.Set(oldTime,oldTime);
|
|
}
|
|
if (method==CTRL_RELATIVE) {
|
|
Matrix3 *tm = (Matrix3*)val;
|
|
if (align) qval.MakeMatrix(*tm);
|
|
tm->SetTrans(this->val);
|
|
} else {
|
|
*((Point3*)val) = this->val;
|
|
}
|
|
valid &= this->valid;
|
|
}
|
|
|
|
float AttachCtrl::GetInterpVal(TimeValue t,int &n0, int &n1)
|
|
{
|
|
float u=0.0f;
|
|
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
if (keys[i].time>t) {
|
|
if (i) {
|
|
n0 = i-1;
|
|
n1 = i;
|
|
u = float(t-keys[i-1].time)/
|
|
float(keys[i].time-keys[i-1].time);
|
|
u = Ease(u,keys[i-1].easeOut,keys[i].easeIn);
|
|
return u;
|
|
} else {
|
|
n0 = n1 = 0;
|
|
return u;
|
|
}
|
|
}
|
|
}
|
|
n0 = n1 = keys.Count()-1;
|
|
return u;
|
|
}
|
|
|
|
Point3 AttachCtrl::PointOnPath(TimeValue t)
|
|
{
|
|
Point3 p(0,0,0);
|
|
if (keys.Count()) {
|
|
int n0, n1;
|
|
float u = GetInterpVal(t,n0,n1);
|
|
float v[4];
|
|
ComputeHermiteBasis(u, v);
|
|
p = keys[n0].pos * v[0] + keys[n1].pos * v[1] +
|
|
keys[n0].dout * v[2] + keys[n1].din * v[3];
|
|
}
|
|
return p;
|
|
}
|
|
|
|
Quat AttachCtrl::QuatOnPath(TimeValue t)
|
|
{
|
|
Quat q = IdentQuat();
|
|
if (keys.Count()) {
|
|
int n0, n1;
|
|
float u = GetInterpVal(t,n0,n1);
|
|
Quat qd = keys[n1].quat/keys[n0].quat;
|
|
float ang;
|
|
Point3 axis;
|
|
AngAxisFromQ(qd, &ang, axis);
|
|
q = squadrev(
|
|
ang, axis,
|
|
keys[n0].quat, keys[n0].qa, keys[n1].qb, keys[n1].quat, u);
|
|
}
|
|
return q;
|
|
}
|
|
|
|
void AttachCtrl::Invalidate()
|
|
{
|
|
valid.SetEmpty();
|
|
trackValid = FALSE;
|
|
InvalidateUI();
|
|
}
|
|
|
|
static void MakeAngPositive(float &ang, Point3 &axis)
|
|
{
|
|
if (ang < 0.0f) {
|
|
ang = -ang;
|
|
axis = -axis;
|
|
}
|
|
}
|
|
|
|
static void MakeAngLessThan180(float &ang, Point3 &axis)
|
|
{
|
|
MakeAngPositive(ang,axis);
|
|
while (ang > TWOPI) ang -= TWOPI;
|
|
if (fabs(ang) > PI) ang -= TWOPI;
|
|
MakeAngPositive(ang,axis);
|
|
}
|
|
|
|
static Quat ConstructOrientation(Point3 norm, Point3 dir)
|
|
{
|
|
Matrix3 tm(1);
|
|
tm.SetRow(2,norm);
|
|
tm.SetRow(1,Normalize(norm^dir));
|
|
tm.SetRow(0,tm.GetRow(1)^tm.GetRow(2));
|
|
Quat q(tm);
|
|
float ang;
|
|
Point3 axis;
|
|
AngAxisFromQ(q, &ang, axis);
|
|
MakeAngLessThan180(ang, axis);
|
|
return QFromAngAxis(ang,axis);
|
|
}
|
|
|
|
|
|
void AttachCtrl::PrepareTrack(TimeValue t)
|
|
{
|
|
if (trackValid && keys.Count()!=1) return;
|
|
trackValid = TRUE;
|
|
if (!node) return;
|
|
if (manUpdate && !doManUpdate) return;
|
|
doManUpdate = FALSE;
|
|
|
|
// First compute key positions and normals
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
AKey &k = keys[i];
|
|
|
|
// Evaluate the node
|
|
ObjectState os = node->EvalWorldState(
|
|
keys.Count()==1 ? t : k.time);
|
|
TriObject *obj = NULL;
|
|
BOOL needsDel = FALSE;
|
|
|
|
// Convert it to a tri object
|
|
if (os.obj->IsSubClassOf(triObjectClassID)) {
|
|
obj = (TriObject*)os.obj;
|
|
needsDel = FALSE;
|
|
} else {
|
|
if (os.obj->CanConvertToType(triObjectClassID)) {
|
|
Object *oldObj = os.obj;
|
|
obj = (TriObject*)
|
|
os.obj->ConvertToType(
|
|
keys.Count()==1 ? t : k.time,triObjectClassID);
|
|
needsDel = (obj != oldObj);
|
|
}
|
|
}
|
|
|
|
// Get the position on the face
|
|
if (obj) {
|
|
Interval iv = FOREVER;
|
|
Matrix3 tm = node->GetObjTMAfterWSM(
|
|
keys.Count()==1 ? t : k.time, &iv);
|
|
if (keys.Count()==1) {
|
|
// Special case for only one key
|
|
valid &= iv;
|
|
valid &= obj->ChannelValidity(t,GEOM_CHAN_NUM);
|
|
valid &= obj->ChannelValidity(t,TOPO_CHAN_NUM);
|
|
}
|
|
Mesh &m = obj->mesh;
|
|
if (m.getNumFaces()) {
|
|
DWORD f = k.face % m.getNumFaces();
|
|
k.pos = m.verts[m.faces[f].v[0]] * k.u0 +
|
|
m.verts[m.faces[f].v[1]] * k.u1 +
|
|
m.verts[m.faces[f].v[2]] * (1.0f-k.u0-k.u1);
|
|
k.pos = k.pos * tm;
|
|
|
|
k.norm = Normalize(
|
|
(m.verts[m.faces[f].v[1]]-m.verts[m.faces[f].v[0]])^
|
|
(m.verts[m.faces[f].v[2]]-m.verts[m.faces[f].v[1]]));
|
|
k.norm = VectorTransform(tm,k.norm);
|
|
} else {
|
|
k.pos = Point3(0,0,0) * tm;
|
|
k.norm = Point3(0,0,1);
|
|
}
|
|
|
|
if (needsDel) obj->DeleteThis();
|
|
}
|
|
}
|
|
|
|
// Next compute position derivatives
|
|
switch (keys.Count()) {
|
|
case 0: break;
|
|
case 1:
|
|
keys[0].din = keys[0].dout = Point3(0,0,0);
|
|
break;
|
|
|
|
case 2:
|
|
Comp2KeyDeriv();
|
|
break;
|
|
|
|
default:
|
|
for (i=1; i<keys.Count()-1; i++) CompMiddleDeriv(i);
|
|
CompFirstDeriv();
|
|
CompLastDeriv();
|
|
break;
|
|
}
|
|
|
|
// Compute orientations
|
|
if (align) {
|
|
// Compute orientations at each key
|
|
for (int i=0; i<keys.Count(); i++) {
|
|
Point3 dir;
|
|
if (i<keys.Count()-1) {
|
|
dir = Normalize(
|
|
PointOnPath(keys[i].time+1)-
|
|
PointOnPath(keys[i].time));
|
|
} else {
|
|
dir = Normalize(
|
|
PointOnPath(keys[i].time)-
|
|
PointOnPath(keys[i].time-1));
|
|
}
|
|
keys[i].quat = ConstructOrientation(keys[i].norm, dir);
|
|
}
|
|
|
|
// Now compute quat tangents
|
|
if (keys.Count()==1) keys[0].qa = keys[0].qb = IdentQuat();
|
|
else for (i=0; i<keys.Count(); i++) CompAB(i);
|
|
}
|
|
trackValid = TRUE;
|
|
}
|
|
|
|
void AttachCtrl::CompFirstDeriv()
|
|
{
|
|
float t;
|
|
t = 0.5f * (1.0f - keys[0].tens);
|
|
keys[0].dout =
|
|
t * (3.0f * (keys[1].pos - keys[0].pos) - keys[1].din);
|
|
}
|
|
|
|
void AttachCtrl::CompLastDeriv()
|
|
{
|
|
float t;
|
|
int n = keys.Count();
|
|
t = 0.5f * (1.0f - keys[n-1].tens);
|
|
keys[n-1].din =
|
|
-t * (3.0f * (keys[n-2].pos - keys[n-1].pos) + keys[n-2].dout);
|
|
}
|
|
|
|
void AttachCtrl::Comp2KeyDeriv()
|
|
{
|
|
float t0, t1;
|
|
Point3 dp = keys[1].pos - keys[0].pos;
|
|
t0 = 1.0f - keys[0].tens;
|
|
t1 = 1.0f - keys[1].tens;
|
|
keys[0].dout = t0 * dp;
|
|
keys[1].din = t1 * dp;
|
|
}
|
|
|
|
void AttachCtrl::CompMiddleDeriv(int i)
|
|
{
|
|
float tm,cm,cp,bm,bp,tmcm,tmcp,ksm,ksp,kdm,kdp,c;
|
|
float dt,fp,fn;
|
|
int n = keys.Count();
|
|
|
|
/* fp,fn apply speed correction when continuity is 0.0 */
|
|
dt = .5f * (float)(keys[i+1].time - keys[i-1].time);
|
|
fp = ((float)(keys[i].time - keys[i-1].time)) / dt;
|
|
fn = ((float)(keys[i+1].time - keys[i].time)) / dt;
|
|
|
|
c = float(fabs(keys[i].cont));
|
|
fp = fp + c - c * fp;
|
|
fn = fn + c - c * fn;
|
|
cm = 1.0f - keys[i].cont;
|
|
tm = 0.5f * (1.0f - keys[i].tens);
|
|
cp = 2.0f - cm;
|
|
bm = 1.0f - keys[i].bias;
|
|
bp = 2.0f - bm;
|
|
tmcm = tm*cm; tmcp = tm*cp;
|
|
ksm = tmcm*bp*fp; ksp = tmcp*bm*fp;
|
|
kdm = tmcp*bp*fn; kdp = tmcm*bm*fn;
|
|
|
|
Point3 delm, delp;
|
|
delm = keys[i].pos - keys[i-1].pos;
|
|
delp = keys[i+1].pos - keys[i].pos;
|
|
keys[i].din = ksm*delm + ksp*delp;
|
|
keys[i].dout = kdm*delm + kdp*delp;
|
|
}
|
|
|
|
|
|
static float Ease(float u, float a, float b)
|
|
{
|
|
float k;
|
|
float s = a + b;
|
|
|
|
if (u==0.0f || u==1.0f) return u;
|
|
if (s == 0.0) return u;
|
|
if (s > 1.0f) {
|
|
a = a/s;
|
|
b = b/s;
|
|
}
|
|
k = 1/(2.0f - a - b);
|
|
if (u < a) return ((k/a)*u*u);
|
|
else if (u < 1.0f - b) return (k*(2.0f*u - a));
|
|
else {
|
|
u = 1.0f - u;
|
|
return (1.0f - (k/b) *u*u);
|
|
}
|
|
}
|
|
|
|
static void ComputeHermiteBasis(float u, float *v)
|
|
{
|
|
float u2,u3,a;
|
|
|
|
u2 = u*u;
|
|
u3 = u2*u;
|
|
a = 2.0f*u3 - 3.0f*u2;
|
|
v[0] = 1.0f + a;
|
|
v[1] = -a;
|
|
v[2] = u - 2.0f*u2 + u3;
|
|
v[3] = -u2 + u3;
|
|
}
|
|
|
|
static float qdot(Quat p, Quat q)
|
|
{
|
|
return (q[0]*p[0] + q[1]*p[1] + q[2]*p[2] + q[3]*p[3]);
|
|
}
|
|
|
|
void AttachCtrl::CompAB(int i)
|
|
{
|
|
int n = keys.Count();
|
|
Quat qprev, qnext, q;
|
|
Quat qp, qm, qa, qb, qae, qbe;
|
|
float tm,cm,cp,bm,bp,tmcm,tmcp,ksm,ksp,kdm,kdp,c;
|
|
float dt,fp,fn;
|
|
|
|
if (i!=0) {
|
|
if (qdot(keys[i-1].quat, keys[i].quat) < 0.0f)
|
|
qprev = -keys[i-1].quat;
|
|
else qprev = keys[i-1].quat;
|
|
qm = LnDif(qprev,keys[i].quat);
|
|
}
|
|
if (i<n-1) {
|
|
if (qdot(keys[i+1].quat, keys[i].quat) < 0.0f)
|
|
qnext = -keys[i+1].quat;
|
|
else qnext = keys[i+1].quat;
|
|
qp = LnDif(keys[i].quat, qnext);
|
|
}
|
|
if (i==0) qm = qp;
|
|
if (i==1) qp = qm;
|
|
|
|
fp = fn = 1.0f;
|
|
cm = 1.0f - keys[i].cont;
|
|
|
|
if (i>0 && i<n-1) {
|
|
dt = 0.5f * float(keys[i+1].time - keys[i-1].time);
|
|
fp = float(keys[i].time - keys[i-1].time)/dt;
|
|
fn = float(keys[i+1].time - keys[i].time)/dt;
|
|
c = float(fabs(keys[i].cont));
|
|
fp = fp + c - c * fp;
|
|
fn = fn + c - c * fn;
|
|
}
|
|
|
|
tm = .5f*(1.0f - keys[i].tens);
|
|
cp = 2.0f - cm;
|
|
bm = 1.0f - keys[i].bias;
|
|
bp = 2.0f - bm;
|
|
tmcm = tm * cm;
|
|
tmcp = tm * cp;
|
|
ksm = 1.0f - tmcm * bp * fp;
|
|
ksp = -tmcp * bm * fp;
|
|
kdm = tmcp * bp * fn;
|
|
kdp = tmcm * bm * fn - 1.0f;
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
qa[j] = .5f * (kdm * qm[j] + kdp * qp[j]);
|
|
qb[j] = .5f * (ksm * qm[j] + ksp * qp[j]);
|
|
}
|
|
|
|
qae = Exp(qa);
|
|
qbe = Exp(qb);
|
|
|
|
keys[i].qa = keys[i].quat * qae;
|
|
keys[i].qb = keys[i].quat * qbe;
|
|
}
|
|
|
|
|
|
|
|
BOOL AttachCtrl::SetObject(INode *node)
|
|
{
|
|
if (node->TestForLoop(FOREVER,this)==REF_SUCCEED) {
|
|
ReplaceReference(Control::NumRefs(),(RefTargetHandle)node);
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
Invalidate();
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::SetKeyPos(TimeValue t, DWORD fi, Point3 bary)
|
|
{
|
|
if (manUpdate) doManUpdate = TRUE;
|
|
int index = GetKeyIndex(t);
|
|
if (index<0) {
|
|
AddNewKey(t,0);
|
|
index = GetKeyIndex(t);
|
|
}
|
|
if (index>=0) {
|
|
keys[index].face = fi;
|
|
keys[index].u0 = bary.x;
|
|
keys[index].u1 = bary.y;
|
|
Invalidate();
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
}
|
|
}
|
|
|
|
|
|
//--- UI -------------------------------------------------------------
|
|
|
|
#define WM_GETBARY (WM_USER+0xa38c)
|
|
#define WM_SETBARY (WM_USER+0xa38d)
|
|
|
|
#define BARYGRAPH_UP 1
|
|
#define BARYGRAPH_DOWN 2
|
|
#define BARYGRAPH_MOVE 3
|
|
|
|
static LRESULT CALLBACK BaryGraphWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
static BOOL CALLBACK AttachParamDialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
|
|
#define ToTCBUI(a) (((a)+1.0f)*25.0f)
|
|
#define FromTCBUI(a) (((a)/25.0f)-1.0f)
|
|
#define ToEaseUI(a) ((a)*50.0f)
|
|
#define FromEaseUI(a) ((a)/50.0f)
|
|
|
|
void RegisterClasses()
|
|
{
|
|
static BOOL registered=FALSE;
|
|
if (!registered) {
|
|
InitTCBGraph(hInstance);
|
|
|
|
WNDCLASS wc;
|
|
wc.style = 0;
|
|
wc.hInstance = hInstance;
|
|
wc.hIcon = NULL;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
|
|
wc.lpszMenuName = NULL;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.lpfnWndProc = BaryGraphWindowProc;
|
|
wc.lpszClassName = _T("FacePosGraph");
|
|
RegisterClass(&wc);
|
|
|
|
registered = TRUE;
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::BeginEditParams(
|
|
IObjParam *ip, ULONG flags,Animatable *prev)
|
|
{
|
|
RegisterClasses();
|
|
this->ip = ip;
|
|
editCont = this;
|
|
uiValid = FALSE;
|
|
hWnd = ip->AddRollupPage(
|
|
hInstance,
|
|
MAKEINTRESOURCE(IDD_ATTACH_PARAMS),
|
|
AttachParamDialogProc,
|
|
GetString(IDS_RB_ATTACHPARAMS),
|
|
(LPARAM)this );
|
|
ip->RegisterTimeChangeCallback(this);
|
|
pickObMode = new PickObjectMode(this);
|
|
setPosMode = new SetPosCMode(this,ip);
|
|
}
|
|
|
|
void AttachCtrl::EndEditParams(
|
|
IObjParam *ip, ULONG flags,Animatable *next)
|
|
{
|
|
ip->ClearPickMode();
|
|
ip->DeleteMode(setPosMode);
|
|
ip->UnRegisterTimeChangeCallback(this);
|
|
if (hWnd) ip->DeleteRollupPage(hWnd);
|
|
hWnd = NULL;
|
|
ip = NULL;
|
|
editCont = NULL;
|
|
delete pickObMode;
|
|
pickObMode = NULL;
|
|
delete setPosMode;
|
|
setPosMode = NULL;
|
|
}
|
|
|
|
void AttachCtrl::SpinnerChange(int id)
|
|
{
|
|
HoldTrack();
|
|
assert(index>=0);
|
|
switch (id) {
|
|
case IDC_ATTACH_KEYTIMESPIN:
|
|
keys[index].time = iTime->GetIVal();
|
|
ip->SetTime(keys[index].time);
|
|
break;
|
|
case IDC_ATTACH_KEYFACESPIN:
|
|
keys[index].face = iFace->GetIVal();
|
|
break;
|
|
case IDC_ATTACH_KEYASPIN:
|
|
keys[index].u0 = iA->GetFVal();
|
|
UpdateBaryGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYBSPIN:
|
|
keys[index].u1 = iB->GetFVal();
|
|
UpdateBaryGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYTENSSPIN:
|
|
keys[index].tens = FromTCBUI(iTens->GetFVal());
|
|
UpdateTCBGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYCONTSPIN:
|
|
keys[index].cont = FromTCBUI(iCont->GetFVal());
|
|
UpdateTCBGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYBIASSPIN:
|
|
keys[index].bias = FromTCBUI(iBias->GetFVal());
|
|
UpdateTCBGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYEASETOSPIN:
|
|
keys[index].easeIn = FromEaseUI(iEaseTo->GetFVal());
|
|
UpdateTCBGraph();
|
|
break;
|
|
case IDC_ATTACH_KEYEASEFROMSPIN:
|
|
keys[index].easeOut = FromEaseUI(iEaseFrom->GetFVal());
|
|
UpdateTCBGraph();
|
|
break;
|
|
}
|
|
Invalidate();
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
ip->RedrawViews(ip->GetTime());
|
|
}
|
|
|
|
void AttachCtrl::Command(int id, LPARAM lParam)
|
|
{
|
|
switch (id) {
|
|
case IDC_ATTACH_PICKOB:
|
|
ip->SetPickMode(pickObMode);
|
|
break;
|
|
|
|
case IDC_PREVKEY:
|
|
if (index==0) index = keys.Count()-1;
|
|
else index--;
|
|
ip->SetTime(keys[index].time);
|
|
break;
|
|
|
|
case IDC_NEXTKEY:
|
|
if (index==keys.Count()-1) index = 0;
|
|
else index++;
|
|
ip->SetTime(keys[index].time);
|
|
break;
|
|
|
|
case IDC_ATTACH_SETPOS:
|
|
if (ip->GetCommandMode()==setPosMode) {
|
|
ip->SetStdCommandMode(CID_OBJMOVE);
|
|
} else {
|
|
ip->SetCommandMode(setPosMode);
|
|
}
|
|
break;
|
|
|
|
case IDC_ATTACH_ALIGN:
|
|
align = IsDlgButtonChecked(hWnd,id);
|
|
valid.SetEmpty();
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
ip->RedrawViews(ip->GetTime());
|
|
break;
|
|
|
|
case IDC_ATTACH_UPDATE:
|
|
doManUpdate = TRUE;
|
|
Invalidate();
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
ip->RedrawViews(ip->GetTime());
|
|
break;
|
|
|
|
case IDC_ATTACH_MANUPDATE:
|
|
manUpdate = IsDlgButtonChecked(hWnd,IDC_ATTACH_MANUPDATE);
|
|
if (manUpdate) iUpdate->Enable();
|
|
else iUpdate->Disable();
|
|
break;
|
|
|
|
case IDC_ATTACH_FACEPOSGRAPH:
|
|
switch (LOWORD(lParam)) {
|
|
case BARYGRAPH_DOWN:
|
|
theHold.Begin();
|
|
break;
|
|
|
|
case BARYGRAPH_UP:
|
|
if (HIWORD(lParam)) theHold.Accept(GetString(IDS_RB_EDITATTACHKEY));
|
|
else theHold.Cancel();
|
|
break;
|
|
|
|
case BARYGRAPH_MOVE: {
|
|
HoldTrack();
|
|
Point2 bary;
|
|
SendDlgItemMessage(hWnd,IDC_ATTACH_FACEPOSGRAPH,
|
|
WM_GETBARY, 0, (LPARAM)&bary);
|
|
keys[index].u0 = bary.x;
|
|
keys[index].u1 = bary.y;
|
|
Invalidate();
|
|
NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
UpdateBaryGraph();
|
|
ip->RedrawViews(ip->GetTime());
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::InvalidateUI()
|
|
{
|
|
if (editCont==this) {
|
|
InvalidateRect(hWnd,NULL,FALSE);
|
|
uiValid = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
void AttachCtrl::UpdateTCBGraph()
|
|
{
|
|
if (index>=0 && hWnd) {
|
|
TCBGraphParams tp;
|
|
tp.tens = keys[index].tens;
|
|
tp.cont = keys[index].cont;
|
|
tp.bias = keys[index].bias;
|
|
tp.easeTo = keys[index].easeIn;
|
|
tp.easeFrom = keys[index].easeOut;
|
|
SendDlgItemMessage(hWnd,IDC_TCB_GRAPH,WM_SETTCBGRAPHPARAMS,0,(LPARAM)&tp);
|
|
UpdateWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH));
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::UpdateBaryGraph()
|
|
{
|
|
if (index>=0 && hWnd) {
|
|
Point2 pt(keys[index].u0,keys[index].u1);
|
|
SendDlgItemMessage(hWnd,IDC_ATTACH_FACEPOSGRAPH,WM_SETBARY,0,(LONG)&pt);
|
|
UpdateWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH));
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::UpdateUI()
|
|
{
|
|
if (!uiValid) {
|
|
uiValid = TRUE;
|
|
|
|
index = GetKeyIndex(ip->GetTime());
|
|
if (index<0) {
|
|
iTime->Disable();
|
|
iFace->Disable();
|
|
iA->Disable();
|
|
iB->Disable();
|
|
iTens->Disable();
|
|
iCont->Disable();
|
|
iBias->Disable();
|
|
iEaseFrom->Disable();
|
|
iEaseTo->Disable();
|
|
iStat->SetText(_T(""));
|
|
iPrev->Disable();
|
|
iNext->Disable();
|
|
EnableWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH),FALSE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH),FALSE);
|
|
} else {
|
|
iTime->Enable();
|
|
iFace->Enable();
|
|
iA->Enable();
|
|
iB->Enable();
|
|
iTens->Enable();
|
|
iCont->Enable();
|
|
iBias->Enable();
|
|
iEaseFrom->Enable();
|
|
iEaseTo->Enable();
|
|
iTime->SetLimits(GetAnimStart(),GetAnimEnd(),FALSE);
|
|
iPrev->Enable();
|
|
iNext->Enable();
|
|
|
|
TSTR buf;
|
|
buf.printf(_T("%d"),index+1);
|
|
iStat->SetText(buf);
|
|
|
|
iTime->SetValue(keys[index].time,FALSE);
|
|
iFace->SetValue((int)keys[index].face,FALSE);
|
|
iA->SetValue(keys[index].u0,FALSE);
|
|
iB->SetValue(keys[index].u1,FALSE);
|
|
iTens->SetValue(ToTCBUI(keys[index].tens),FALSE);
|
|
iCont->SetValue(ToTCBUI(keys[index].cont),FALSE);
|
|
iBias->SetValue(ToTCBUI(keys[index].bias),FALSE);
|
|
iEaseTo->SetValue(ToEaseUI(keys[index].easeIn),FALSE);
|
|
iEaseFrom->SetValue(ToEaseUI(keys[index].easeOut),FALSE);
|
|
|
|
UpdateTCBGraph();
|
|
EnableWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH),TRUE);
|
|
|
|
UpdateBaryGraph();
|
|
EnableWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH),TRUE);
|
|
}
|
|
|
|
CheckDlgButton(hWnd,IDC_ATTACH_ALIGN,align);
|
|
if (node) {
|
|
SetDlgItemText(hWnd,IDC_ATTACH_OBNAME,node->GetName());
|
|
} else {
|
|
SetDlgItemText(hWnd,IDC_ATTACH_OBNAME,GetString(IDS_RB_NONE));
|
|
}
|
|
|
|
CheckDlgButton(hWnd,IDC_ATTACH_MANUPDATE,manUpdate);
|
|
if (manUpdate) iUpdate->Enable();
|
|
else iUpdate->Disable();
|
|
}
|
|
}
|
|
|
|
void AttachCtrl::SetupWindow(HWND hWnd)
|
|
{
|
|
this->hWnd = hWnd;
|
|
|
|
iTime = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYTIMESPIN));
|
|
iTime->SetLimits(GetAnimStart(),GetAnimEnd(),FALSE);
|
|
iTime->SetScale(10.0f);
|
|
iTime->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYTIME),EDITTYPE_TIME);
|
|
|
|
iFace = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYFACESPIN));
|
|
iFace->SetLimits(0,0xfffffff,FALSE);
|
|
iFace->SetScale(0.1f);
|
|
iFace->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYFACE),EDITTYPE_INT);
|
|
|
|
iA = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYASPIN));
|
|
iA->SetLimits(-999999999.0f,999999999.0f,FALSE);
|
|
iA->SetScale(0.005f);
|
|
iA->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYA),EDITTYPE_FLOAT);
|
|
|
|
iB = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYBSPIN));
|
|
iB->SetLimits(-999999999.0f,999999999.0f,FALSE);
|
|
iB->SetScale(0.005f);
|
|
iB->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYB),EDITTYPE_FLOAT);
|
|
|
|
iTens = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYTENSSPIN));
|
|
iTens->SetLimits(0.0f,50.0f,FALSE);
|
|
iTens->SetScale(0.1f);
|
|
iTens->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYTENS),EDITTYPE_FLOAT);
|
|
|
|
iCont = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYCONTSPIN));
|
|
iCont->SetLimits(0.0f,50.0f,FALSE);
|
|
iCont->SetScale(0.1f);
|
|
iCont->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYCONT),EDITTYPE_FLOAT);
|
|
|
|
iBias = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYBIASSPIN));
|
|
iBias->SetLimits(0.0f,50.0f,FALSE);
|
|
iBias->SetScale(0.1f);
|
|
iBias->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYBIAS),EDITTYPE_FLOAT);
|
|
|
|
iEaseTo = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYEASETOSPIN));
|
|
iEaseTo->SetLimits(0.0f,50.0f,FALSE);
|
|
iEaseTo->SetScale(0.1f);
|
|
iEaseTo->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYEASETO),EDITTYPE_FLOAT);
|
|
|
|
iEaseFrom = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYEASEFROMSPIN));
|
|
iEaseFrom->SetLimits(0.0f,50.0f,FALSE);
|
|
iEaseFrom->SetScale(0.1f);
|
|
iEaseFrom->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYEASEFROM),EDITTYPE_FLOAT);
|
|
|
|
iPickOb = GetICustButton(GetDlgItem(hWnd,IDC_ATTACH_PICKOB));
|
|
iPickOb->SetType(CBT_CHECK);
|
|
iPickOb->SetHighlightColor(GREEN_WASH);
|
|
|
|
iSetPos = GetICustButton(GetDlgItem(hWnd,IDC_ATTACH_SETPOS));
|
|
iSetPos->SetType(CBT_CHECK);
|
|
iSetPos->SetHighlightColor(GREEN_WASH);
|
|
|
|
iPrev = GetICustButton(GetDlgItem(hWnd,IDC_PREVKEY));
|
|
iNext = GetICustButton(GetDlgItem(hWnd,IDC_NEXTKEY));
|
|
iUpdate = GetICustButton(GetDlgItem(hWnd,IDC_ATTACH_UPDATE));
|
|
|
|
iStat = GetICustStatus(GetDlgItem(hWnd,IDC_KEYNUM));
|
|
iStat->SetTextFormat(STATUSTEXT_CENTERED);
|
|
}
|
|
|
|
void AttachCtrl::DestroyWindow()
|
|
{
|
|
ReleaseICustStatus(iStat);
|
|
ReleaseISpinner(iTime);
|
|
ReleaseISpinner(iFace);
|
|
ReleaseISpinner(iA);
|
|
ReleaseISpinner(iB);
|
|
ReleaseISpinner(iTens);
|
|
ReleaseISpinner(iCont);
|
|
ReleaseISpinner(iBias);
|
|
ReleaseICustButton(iPickOb);
|
|
ReleaseICustButton(iSetPos);
|
|
ReleaseICustButton(iPrev);
|
|
ReleaseICustButton(iNext);
|
|
ReleaseICustButton(iUpdate);
|
|
ReleaseISpinner(iEaseTo);
|
|
ReleaseISpinner(iEaseFrom);
|
|
iEaseTo = iEaseFrom = iTime = iFace = iA =
|
|
iB = iTens = iCont = iBias = NULL;
|
|
iStat = NULL;
|
|
iPickOb = iSetPos = iPrev = iNext = iUpdate = NULL;
|
|
}
|
|
|
|
static BOOL CALLBACK AttachParamDialogProc(
|
|
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
AttachCtrl *ctrl = (AttachCtrl*)GetWindowLong(hWnd,GWL_USERDATA);
|
|
if (!ctrl && msg != WM_INITDIALOG) return FALSE;
|
|
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
ctrl = (AttachCtrl*)lParam;
|
|
SetWindowLong(hWnd, GWL_USERDATA, lParam);
|
|
ctrl->SetupWindow(hWnd);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
ctrl->DestroyWindow();
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
ctrl->UpdateUI();
|
|
return FALSE;
|
|
|
|
case WM_LBUTTONDOWN:case WM_LBUTTONUP: case WM_MOUSEMOVE:
|
|
ctrl->ip->RollupMouseMessage(hWnd,msg,wParam,lParam);
|
|
return FALSE;
|
|
|
|
case CC_SPINNER_BUTTONDOWN:
|
|
theHold.Begin();
|
|
break;
|
|
|
|
case WM_CUSTEDIT_ENTER:
|
|
case CC_SPINNER_BUTTONUP:
|
|
if (HIWORD(wParam)) {
|
|
theHold.Accept(GetString(IDS_RB_EDITATTACHKEY));
|
|
} else {
|
|
theHold.Cancel();
|
|
}
|
|
break;
|
|
|
|
case CC_SPINNER_CHANGE:
|
|
ctrl->SpinnerChange(LOWORD(wParam));
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
ctrl->Command(LOWORD(wParam),lParam);
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//--- PickObjectMode ------------------------------------------------
|
|
|
|
BOOL PickObjectMode::Filter(INode *node)
|
|
{
|
|
if (node) {
|
|
ObjectState os = node->EvalWorldState(0);
|
|
if (os.obj->CanConvertToType(triObjectClassID)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL PickObjectMode::HitTest(
|
|
IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags)
|
|
{
|
|
INode *node = cont->ip->PickNode(hWnd,m);
|
|
return node?TRUE:FALSE;
|
|
}
|
|
|
|
BOOL PickObjectMode::Pick(IObjParam *ip,ViewExp *vpt)
|
|
{
|
|
INode *node = vpt->GetClosestHit();
|
|
if (node) {
|
|
ObjectState os = node->EvalWorldState(0);
|
|
if (os.obj->CanConvertToType(triObjectClassID)) {
|
|
if (cont->SetObject(node)) {
|
|
cont->ip->RedrawViews(cont->ip->GetTime());
|
|
} else {
|
|
TSTR buf1 = GetString(IDS_RB_ATTACHCONTROLLER);
|
|
TSTR buf2 = GetString(IDS_RB_ILLEGALOBJECT);
|
|
MessageBox(ip->GetMAXHWnd(),buf2,buf1,MB_OK|MB_ICONEXCLAMATION);
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void PickObjectMode::EnterMode(IObjParam *ip)
|
|
{cont->iPickOb->SetCheck(TRUE);}
|
|
|
|
void PickObjectMode::ExitMode(IObjParam *ip)
|
|
{cont->iPickOb->SetCheck(FALSE);}
|
|
|
|
|
|
//--- SetPosMode ------------------------------------------------
|
|
|
|
void SetPosMouseProc::SetPos(HWND hWnd,IPoint2 m)
|
|
{
|
|
if (!cont->node) return;
|
|
ViewExp *vpt = ip->GetViewport(hWnd);
|
|
if (!vpt) return;
|
|
|
|
Ray ray, wray;
|
|
float at;
|
|
TimeValue t = ip->GetTime();
|
|
Point3 norm, pt, bary;
|
|
DWORD fi;
|
|
Interval valid;
|
|
|
|
// Calculate a ray from the mouse point
|
|
vpt->MapScreenToWorldRay(float(m.x), float(m.y),wray);
|
|
|
|
// Back transform the ray into object space.
|
|
Matrix3 obtm = cont->node->GetObjTMAfterWSM(t);
|
|
Matrix3 iobtm = Inverse(obtm);
|
|
ray.p = iobtm * wray.p;
|
|
ray.dir = VectorTransform(iobtm, wray.dir);
|
|
|
|
// Evaluate the node
|
|
ObjectState os = cont->node->EvalWorldState(t);
|
|
TriObject *obj = NULL;
|
|
BOOL needsDel = FALSE;
|
|
|
|
// Convert it to a tri object
|
|
if (os.obj->IsSubClassOf(triObjectClassID)) {
|
|
obj = (TriObject*)os.obj;
|
|
needsDel = FALSE;
|
|
} else {
|
|
if (os.obj->CanConvertToType(triObjectClassID)) {
|
|
Object *oldObj = os.obj;
|
|
obj = (TriObject*)
|
|
os.obj->ConvertToType(t,triObjectClassID);
|
|
needsDel = (obj != oldObj);
|
|
}
|
|
}
|
|
if (!obj) return;
|
|
|
|
if (obj->mesh.IntersectRay(ray, at, norm, fi, bary)) {
|
|
cont->HoldTrack();
|
|
cont->SetKeyPos(t, fi, bary);
|
|
}
|
|
|
|
if (needsDel) obj->DeleteThis();
|
|
ip->ReleaseViewport(vpt);
|
|
}
|
|
|
|
int SetPosMouseProc::proc(
|
|
HWND hWnd, int msg, int point, int flags, IPoint2 m)
|
|
{
|
|
static BOOL animated = FALSE;
|
|
switch (msg) {
|
|
case MOUSE_POINT:
|
|
if (point==0) {
|
|
animated = FALSE;
|
|
if (!Animating() && cont->keys.Count()==1) {
|
|
int index = cont->GetKeyIndex(ip->GetTime());
|
|
if (index<0) {
|
|
animated = TRUE;
|
|
}
|
|
}
|
|
theHold.Begin();
|
|
ip->RedrawViews(ip->GetTime(),REDRAW_BEGIN);
|
|
} else {
|
|
if (animated) {
|
|
int res = MessageBox(hWnd,
|
|
_T("Are you sure you want to animate the position of this object?"),
|
|
_T("Attachment Controller"),
|
|
MB_ICONQUESTION|MB_YESNO);
|
|
if (res!=IDYES) {
|
|
theHold.Cancel();
|
|
ip->RedrawViews(ip->GetTime(),REDRAW_END);
|
|
return FALSE;
|
|
}
|
|
}
|
|
theHold.Accept(GetString(IDS_RB_EDITATTACHKEY));
|
|
ip->RedrawViews(ip->GetTime(),REDRAW_END);
|
|
}
|
|
break;
|
|
|
|
case MOUSE_MOVE: {
|
|
theHold.Restore();
|
|
SetPos(hWnd,m);
|
|
ip->RedrawViews(ip->GetTime(),REDRAW_INTERACTIVE);
|
|
break;
|
|
}
|
|
|
|
case MOUSE_ABORT:
|
|
theHold.Cancel();
|
|
ip->RedrawViews(ip->GetTime(),REDRAW_END);
|
|
break;
|
|
|
|
case MOUSE_FREEMOVE:
|
|
SetCursor(ip->GetSysCursor(SYSCUR_SELECT));
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void SetPosCMode::EnterMode()
|
|
{
|
|
if (cont->iSetPos) cont->iSetPos->SetCheck(TRUE);
|
|
}
|
|
|
|
void SetPosCMode::ExitMode()
|
|
{
|
|
if (cont->iSetPos) cont->iSetPos->SetCheck(FALSE);
|
|
}
|
|
|
|
|
|
//--- Barycentric graph ----------------------------------------------
|
|
|
|
static LRESULT CALLBACK BaryGraphWindowProc(
|
|
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
Point2 *bary = (Point2*)GetWindowLong(hWnd,GWL_USERDATA);
|
|
static int mx, my;
|
|
static Point2 oldBary;
|
|
static BOOL mDown = FALSE;
|
|
|
|
switch (msg) {
|
|
case WM_CREATE:
|
|
SetWindowLong(hWnd,GWL_USERDATA,(LONG)new Point2(0.33f,0.33f));
|
|
break;
|
|
|
|
case WM_SETBARY: {
|
|
Point2 *nb = (Point2*)lParam;
|
|
*bary = *nb;
|
|
InvalidateRect(hWnd,NULL,TRUE);
|
|
break;
|
|
}
|
|
|
|
case WM_GETBARY: {
|
|
Point2 *nb = (Point2*)lParam;
|
|
*nb = *bary;
|
|
break;
|
|
}
|
|
|
|
case WM_ENABLE:
|
|
InvalidateRect(hWnd,NULL,TRUE);
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
mDown = TRUE;
|
|
mx = LOWORD(lParam);
|
|
my = HIWORD(lParam);
|
|
oldBary = *bary;
|
|
SetCapture(hWnd);
|
|
SendMessage(GetParent(hWnd),WM_COMMAND,IDC_ATTACH_FACEPOSGRAPH,
|
|
MAKELPARAM(BARYGRAPH_DOWN,0));
|
|
// Fallthrough
|
|
|
|
case WM_MOUSEMOVE:
|
|
if (mDown) {
|
|
Rect rect;
|
|
GetClientRect(hWnd,&rect);
|
|
int x = (short)LOWORD(lParam) - 5;
|
|
int y = (short)HIWORD(lParam) - 5;
|
|
float u = float(x)/float(rect.w()-11);
|
|
float v = 1.0f-float(y)/float(rect.h()-11);
|
|
|
|
bary->x = v;
|
|
bary->y = 1.0f-v-u;
|
|
SendMessage(GetParent(hWnd),WM_COMMAND,IDC_ATTACH_FACEPOSGRAPH,
|
|
MAKELPARAM(BARYGRAPH_MOVE,0));
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
mDown = FALSE;
|
|
ReleaseCapture();
|
|
SendMessage(GetParent(hWnd),WM_COMMAND,IDC_ATTACH_FACEPOSGRAPH,
|
|
MAKELPARAM(BARYGRAPH_UP,TRUE));
|
|
break;
|
|
|
|
case WM_RBUTTONDOWN:
|
|
mDown = FALSE;
|
|
*bary = oldBary;
|
|
ReleaseCapture();
|
|
SendMessage(GetParent(hWnd),WM_COMMAND,IDC_ATTACH_FACEPOSGRAPH,
|
|
MAKELPARAM(BARYGRAPH_UP,FALSE));
|
|
break;
|
|
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd,&ps);
|
|
Rect rect, orect;
|
|
GetClientRect(hWnd,&rect);
|
|
orect = rect;
|
|
|
|
if (IsWindowEnabled(hWnd)) {
|
|
rect.left += 5;
|
|
rect.right -= 5;
|
|
rect.top += 5;
|
|
rect.bottom -= 5;
|
|
IPoint2 pt0(rect.left,rect.top);
|
|
IPoint2 pt1(rect.left,rect.bottom);
|
|
IPoint2 pt2(rect.right,rect.bottom);
|
|
SelectObject(hdc,GetStockObject(BLACK_PEN));
|
|
MoveToEx(hdc,pt0.x, pt0.y,NULL);
|
|
LineTo(hdc,pt1.x, pt1.y);
|
|
LineTo(hdc,pt2.x, pt2.y);
|
|
LineTo(hdc,pt0.x, pt0.y);
|
|
|
|
IPoint2 pt(
|
|
int(pt0.x*bary->x + pt1.x*bary->y + pt2.x*(1.0f-bary->x-bary->y)),
|
|
int(pt0.y*bary->x + pt1.y*bary->y + pt2.y*(1.0f-bary->x-bary->y)));
|
|
|
|
SelectObject(hdc,CreatePen(PS_SOLID,0,RGB(255,0,0)));
|
|
MoveToEx(hdc,pt.x, pt.y-3,NULL);
|
|
LineTo(hdc,pt.x, pt.y+4);
|
|
MoveToEx(hdc,pt.x-3, pt.y,NULL);
|
|
LineTo(hdc,pt.x+4, pt.y);
|
|
DeleteObject(SelectObject(hdc,GetStockObject(BLACK_PEN)));
|
|
}
|
|
|
|
WhiteRect3D(hdc,orect,TRUE);
|
|
EndPaint(hWnd,&ps);
|
|
break;
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
delete bary;
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hWnd,msg,wParam,lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//--- Key Dialog ----------------------------------------------------
|
|
|
|
|
|
static BOOL CALLBACK AttachDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
|
|
#define ATTACHDLG_CLASS_ID Class_ID(0x8736fff2,0xaab37f23)
|
|
|
|
class AttachKeyDlg : public ReferenceMaker {
|
|
public:
|
|
HWND hWnd;
|
|
AttachCtrl *cont;
|
|
IObjParam *ip;
|
|
BOOL valid;
|
|
ISpinnerControl *iTime, *iFace, *iA, *iB, *iTens, *iCont, *iBias, *iEaseTo, *iEaseFrom;
|
|
ICustButton *iPrev, *iNext;
|
|
ICustStatus *iStat;
|
|
|
|
AttachKeyDlg(AttachCtrl *c,TCHAR *pname,IObjParam *ip,HWND hParent);
|
|
~AttachKeyDlg();
|
|
|
|
Class_ID ClassID() {return ATTACHDLG_CLASS_ID;}
|
|
SClass_ID SuperClassID() {return REF_MAKER_CLASS_ID;}
|
|
RefResult NotifyRefChanged(Interval iv, RefTargetHandle hTarg,PartID& partID,RefMessage msg);
|
|
int NumRefs() {return 1;}
|
|
RefTargetHandle GetReference(int i) {return cont;}
|
|
void SetReference(int i, RefTargetHandle rtarg) {cont=(AttachCtrl*)rtarg;}
|
|
|
|
void MaybeCloseWindow();
|
|
void Invalidate();
|
|
void Update();
|
|
void UpdateBaryGraph(float a, float b);
|
|
void UpdateTCBGraph(float t, float c, float b, float easeIn, float easeOut);
|
|
void SetupUI(HWND hWnd);
|
|
void Command(int id, LPARAM lParam);
|
|
void SpinnerChange(int id);
|
|
};
|
|
|
|
void AttachCtrl::EditTrackParams(
|
|
TimeValue t,
|
|
ParamDimensionBase *dim,
|
|
TCHAR *pname,
|
|
HWND hParent,
|
|
IObjParam *ip,
|
|
DWORD flags)
|
|
{
|
|
RegisterClasses();
|
|
new AttachKeyDlg(this,pname,ip,hParent);
|
|
}
|
|
|
|
AttachKeyDlg::AttachKeyDlg(
|
|
AttachCtrl *c,TCHAR *pname,IObjParam *ip,HWND hParent)
|
|
{
|
|
valid = FALSE;
|
|
cont = NULL;
|
|
hWnd = NULL;
|
|
this->ip = ip;
|
|
MakeRefByID(FOREVER,0,c);
|
|
hWnd = CreateDialogParam(
|
|
hInstance,
|
|
MAKEINTRESOURCE(IDD_ATTACH_DIALOG),
|
|
hParent,
|
|
AttachDlgProc,
|
|
(LPARAM)this);
|
|
TSTR title = TSTR(GetString(IDS_RB_ATTACHTITLE)) + TSTR(pname);
|
|
SetWindowText(hWnd,title);
|
|
}
|
|
|
|
AttachKeyDlg::~AttachKeyDlg()
|
|
{
|
|
DeleteAllRefsFromMe();
|
|
ReleaseICustStatus(iStat);
|
|
ReleaseISpinner(iTime);
|
|
ReleaseISpinner(iFace);
|
|
ReleaseISpinner(iA);
|
|
ReleaseISpinner(iB);
|
|
ReleaseISpinner(iTens);
|
|
ReleaseISpinner(iCont);
|
|
ReleaseISpinner(iBias);
|
|
ReleaseICustButton(iPrev);
|
|
ReleaseICustButton(iNext);
|
|
ReleaseISpinner(iEaseTo);
|
|
ReleaseISpinner(iEaseFrom);
|
|
}
|
|
|
|
class CheckForNonAttachDlg : public DependentEnumProc {
|
|
public:
|
|
BOOL non;
|
|
ReferenceMaker *me;
|
|
CheckForNonAttachDlg(ReferenceMaker *m) {non = FALSE;me = m;}
|
|
int proc(ReferenceMaker *rmaker) {
|
|
if (rmaker==me) return 0;
|
|
if (rmaker->SuperClassID()!=REF_MAKER_CLASS_ID &&
|
|
rmaker->ClassID()!=ATTACHDLG_CLASS_ID) {
|
|
non = TRUE;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
void AttachKeyDlg::MaybeCloseWindow()
|
|
{
|
|
CheckForNonAttachDlg check(cont);
|
|
cont->EnumDependents(&check);
|
|
if (!check.non) {
|
|
PostMessage(hWnd,WM_CLOSE,0,0);
|
|
}
|
|
}
|
|
|
|
|
|
void AttachKeyDlg::SpinnerChange(int id)
|
|
{
|
|
cont->HoldTrack();
|
|
|
|
for (int index=0; index<cont->keys.Count(); index++) {
|
|
if (!(cont->keys[index].flags&KEY_SELECTED)) continue;
|
|
switch (id) {
|
|
case IDC_ATTACH_KEYTIMESPIN:
|
|
cont->keys[index].time = iTime->GetIVal();
|
|
break;
|
|
case IDC_ATTACH_KEYFACESPIN:
|
|
cont->keys[index].face = iFace->GetIVal();
|
|
break;
|
|
case IDC_ATTACH_KEYASPIN:
|
|
cont->keys[index].u0 = iA->GetFVal();
|
|
break;
|
|
case IDC_ATTACH_KEYBSPIN:
|
|
cont->keys[index].u1 = iB->GetFVal();
|
|
break;
|
|
case IDC_ATTACH_KEYTENSSPIN:
|
|
cont->keys[index].tens = FromTCBUI(iTens->GetFVal());
|
|
break;
|
|
case IDC_ATTACH_KEYCONTSPIN:
|
|
cont->keys[index].cont = FromTCBUI(iCont->GetFVal());
|
|
break;
|
|
case IDC_ATTACH_KEYBIASSPIN:
|
|
cont->keys[index].bias = FromTCBUI(iBias->GetFVal());
|
|
break;
|
|
case IDC_ATTACH_KEYEASETOSPIN:
|
|
cont->keys[index].easeIn = FromEaseUI(iEaseTo->GetFVal());
|
|
break;
|
|
case IDC_ATTACH_KEYEASEFROMSPIN:
|
|
cont->keys[index].easeOut = FromEaseUI(iEaseFrom->GetFVal());
|
|
break;
|
|
}
|
|
}
|
|
|
|
cont->Invalidate();
|
|
cont->NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
ip->RedrawViews(ip->GetTime());
|
|
UpdateWindow(hWnd);
|
|
}
|
|
|
|
void AttachKeyDlg::Command(int id, LPARAM lParam)
|
|
{
|
|
switch (id) {
|
|
case IDC_PREVKEY: {
|
|
int n=0;
|
|
for (int i=0; i<cont->keys.Count(); i++) {
|
|
if (cont->keys[i].flags&KEY_SELECTED) {
|
|
if (i) n = i-1;
|
|
else n = cont->keys.Count()-1;
|
|
}
|
|
}
|
|
for (i=0; i<cont->keys.Count(); i++) {
|
|
if (i==n) cont->keys[i].flags |= KEY_SELECTED;
|
|
else cont->keys[i].flags &= ~KEY_SELECTED;
|
|
}
|
|
cont->NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
break;
|
|
}
|
|
|
|
case IDC_NEXTKEY: {
|
|
int n=0;
|
|
for (int i=0; i<cont->keys.Count(); i++) {
|
|
if (cont->keys[i].flags&KEY_SELECTED) {
|
|
if (i<cont->keys.Count()-1) n = i+1;
|
|
else n = 0;
|
|
}
|
|
}
|
|
for (i=0; i<cont->keys.Count(); i++) {
|
|
if (i==n) cont->keys[i].flags |= KEY_SELECTED;
|
|
else cont->keys[i].flags &= ~KEY_SELECTED;
|
|
}
|
|
cont->NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
break;
|
|
}
|
|
|
|
case IDC_ATTACH_FACEPOSGRAPH:
|
|
switch (LOWORD(lParam)) {
|
|
case BARYGRAPH_DOWN:
|
|
theHold.Begin();
|
|
break;
|
|
|
|
case BARYGRAPH_UP:
|
|
if (HIWORD(lParam)) theHold.Accept(GetString(IDS_RB_EDITATTACHKEY));
|
|
else theHold.Cancel();
|
|
break;
|
|
|
|
case BARYGRAPH_MOVE: {
|
|
cont->HoldTrack();
|
|
Point2 bary;
|
|
SendDlgItemMessage(hWnd,IDC_ATTACH_FACEPOSGRAPH,
|
|
WM_GETBARY, 0, (LPARAM)&bary);
|
|
|
|
for (int index=0; index<cont->keys.Count(); index++) {
|
|
if (!(cont->keys[index].flags&KEY_SELECTED)) continue;
|
|
cont->keys[index].u0 = bary.x;
|
|
cont->keys[index].u1 = bary.y;
|
|
}
|
|
cont->Invalidate();
|
|
cont->NotifyDependents(FOREVER,PART_ALL,REFMSG_CHANGE);
|
|
ip->RedrawViews(ip->GetTime());
|
|
UpdateWindow(hWnd);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void AttachKeyDlg::Invalidate()
|
|
{
|
|
valid = FALSE;
|
|
InvalidateRect(hWnd,NULL,FALSE);
|
|
}
|
|
|
|
void AttachKeyDlg::UpdateBaryGraph(float a, float b)
|
|
{
|
|
Point2 pt(a,b);
|
|
SendDlgItemMessage(hWnd,IDC_ATTACH_FACEPOSGRAPH,WM_SETBARY,0,(LONG)&pt);
|
|
UpdateWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH));
|
|
}
|
|
|
|
void AttachKeyDlg::UpdateTCBGraph(
|
|
float t, float c, float b, float easeIn, float easeOut)
|
|
{
|
|
TCBGraphParams tp;
|
|
tp.tens = t;
|
|
tp.cont = c;
|
|
tp.bias = b;
|
|
tp.easeTo = easeIn;
|
|
tp.easeFrom = easeOut;
|
|
SendDlgItemMessage(hWnd,IDC_TCB_GRAPH,WM_SETTCBGRAPHPARAMS,0,(LPARAM)&tp);
|
|
UpdateWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH));
|
|
}
|
|
|
|
void AttachKeyDlg::Update()
|
|
{
|
|
if (valid || !hWnd) return;
|
|
valid = TRUE;
|
|
BOOL init = FALSE;
|
|
TimeValue time;
|
|
DWORD face;
|
|
float a=0.0f, b=0.0f, tens=0.0f, cnt=0.0f, bias=0.0f, easeTo=0.0f, easeFrom=0.0f;
|
|
BOOL common[9] = {0,0,0,0,0,0,0,0,0};
|
|
int selIndex = -1, selCount=0;
|
|
|
|
for (int i=0; i<cont->keys.Count(); i++) {
|
|
if (!(cont->keys[i].flags&KEY_SELECTED)) continue;
|
|
selCount++;
|
|
|
|
if (!init) {
|
|
init = TRUE;
|
|
time = cont->keys[i].time;
|
|
a = cont->keys[i].u0;
|
|
b = cont->keys[i].u1;
|
|
tens = cont->keys[i].tens;
|
|
cnt = cont->keys[i].cont;
|
|
bias = cont->keys[i].bias;
|
|
easeTo = cont->keys[i].easeIn;
|
|
easeFrom = cont->keys[i].easeOut;
|
|
face = cont->keys[i].face;
|
|
selIndex = i;
|
|
for (int j=0; j<9; j++) common[j] = TRUE;
|
|
} else {
|
|
if (time !=cont->keys[i].time) common[0] = FALSE;
|
|
if (a !=cont->keys[i].u0) common[1] = FALSE;
|
|
if (b !=cont->keys[i].u1) common[2] = FALSE;
|
|
if (tens !=cont->keys[i].tens) common[3] = FALSE;
|
|
if (cnt !=cont->keys[i].cont) common[4] = FALSE;
|
|
if (bias !=cont->keys[i].bias) common[5] = FALSE;
|
|
if (easeTo !=cont->keys[i].easeIn) common[6] = FALSE;
|
|
if (easeFrom!=cont->keys[i].easeOut) common[7] = FALSE;
|
|
if (face !=cont->keys[i].face) common[8] = FALSE;
|
|
selIndex = -1;
|
|
}
|
|
}
|
|
|
|
UpdateBaryGraph(a,b);
|
|
if (common[3]&&common[4]&&common[5]&&common[6]&&common[7]) {
|
|
UpdateTCBGraph(tens,cnt,bias,easeTo,easeFrom);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH),TRUE);
|
|
} else {
|
|
EnableWindow(GetDlgItem(hWnd,IDC_TCB_GRAPH),FALSE);
|
|
}
|
|
|
|
if (selIndex>=0) {
|
|
TSTR buf;
|
|
buf.printf(_T("%d"),selIndex+1);
|
|
iStat->SetText(buf);
|
|
} else {
|
|
iStat->SetText(_T(""));
|
|
}
|
|
|
|
if (selCount==0) {
|
|
iTime->Disable();
|
|
iA->Disable();
|
|
iB->Disable();
|
|
iTens->Disable();
|
|
iCont->Disable();
|
|
iBias->Disable();
|
|
iEaseTo->Disable();
|
|
iEaseFrom->Disable();
|
|
iFace->Disable();
|
|
EnableWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH),FALSE);
|
|
return;
|
|
} else {
|
|
iTime->Enable();
|
|
iA->Enable();
|
|
iB->Enable();
|
|
iTens->Enable();
|
|
iCont->Enable();
|
|
iBias->Enable();
|
|
iEaseTo->Enable();
|
|
iEaseFrom->Enable();
|
|
iFace->Enable();
|
|
EnableWindow(GetDlgItem(hWnd,IDC_ATTACH_FACEPOSGRAPH),TRUE);
|
|
}
|
|
|
|
if (common[0]) {
|
|
iTime->SetIndeterminate(FALSE);
|
|
iTime->SetValue(time,FALSE);
|
|
} else {
|
|
iTime->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[1]) {
|
|
iA->SetIndeterminate(FALSE);
|
|
iA->SetValue(a,FALSE);
|
|
} else {
|
|
iA->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[2]) {
|
|
iB->SetIndeterminate(FALSE);
|
|
iB->SetValue(b,FALSE);
|
|
} else {
|
|
iB->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[3]) {
|
|
iTens->SetIndeterminate(FALSE);
|
|
iTens->SetValue(ToTCBUI(tens),FALSE);
|
|
} else {
|
|
iTens->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[4]) {
|
|
iCont->SetIndeterminate(FALSE);
|
|
iCont->SetValue(ToTCBUI(cnt),FALSE);
|
|
} else {
|
|
iCont->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[5]) {
|
|
iBias->SetIndeterminate(FALSE);
|
|
iBias->SetValue(ToTCBUI(bias),FALSE);
|
|
} else {
|
|
iBias->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[6]) {
|
|
iEaseTo->SetIndeterminate(FALSE);
|
|
iEaseTo->SetValue(ToEaseUI(easeTo),FALSE);
|
|
} else {
|
|
iEaseTo->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[7]) {
|
|
iEaseFrom->SetIndeterminate(FALSE);
|
|
iEaseFrom->SetValue(ToEaseUI(easeFrom),FALSE);
|
|
} else {
|
|
iEaseFrom->SetIndeterminate(TRUE);
|
|
}
|
|
if (common[8]) {
|
|
iFace->SetIndeterminate(FALSE);
|
|
iFace->SetValue((int)face,FALSE);
|
|
} else {
|
|
iFace->SetIndeterminate(TRUE);
|
|
}
|
|
}
|
|
|
|
void AttachKeyDlg::SetupUI(HWND hWnd)
|
|
{
|
|
this->hWnd = hWnd;
|
|
|
|
iTime = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYTIMESPIN));
|
|
iTime->SetLimits(GetAnimStart(),GetAnimEnd(),FALSE);
|
|
iTime->SetScale(10.0f);
|
|
iTime->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYTIME),EDITTYPE_TIME);
|
|
|
|
iFace = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYFACESPIN));
|
|
iFace->SetLimits(0,0xfffffff,FALSE);
|
|
iFace->SetScale(0.1f);
|
|
iFace->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYFACE),EDITTYPE_INT);
|
|
|
|
iA = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYASPIN));
|
|
iA->SetLimits(-999999999.0f,999999999.0f,FALSE);
|
|
iA->SetScale(0.005f);
|
|
iA->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYA),EDITTYPE_FLOAT);
|
|
|
|
iB = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYBSPIN));
|
|
iB->SetLimits(-999999999.0f,999999999.0f,FALSE);
|
|
iB->SetScale(0.005f);
|
|
iB->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYB),EDITTYPE_FLOAT);
|
|
|
|
iTens = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYTENSSPIN));
|
|
iTens->SetLimits(0.0f,50.0f,FALSE);
|
|
iTens->SetScale(0.1f);
|
|
iTens->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYTENS),EDITTYPE_FLOAT);
|
|
|
|
iCont = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYCONTSPIN));
|
|
iCont->SetLimits(0.0f,50.0f,FALSE);
|
|
iCont->SetScale(0.1f);
|
|
iCont->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYCONT),EDITTYPE_FLOAT);
|
|
|
|
iBias = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYBIASSPIN));
|
|
iBias->SetLimits(0.0f,50.0f,FALSE);
|
|
iBias->SetScale(0.1f);
|
|
iBias->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYBIAS),EDITTYPE_FLOAT);
|
|
|
|
iEaseTo = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYEASETOSPIN));
|
|
iEaseTo->SetLimits(0.0f,50.0f,FALSE);
|
|
iEaseTo->SetScale(0.1f);
|
|
iEaseTo->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYEASETO),EDITTYPE_FLOAT);
|
|
|
|
iEaseFrom = GetISpinner(GetDlgItem(hWnd,IDC_ATTACH_KEYEASEFROMSPIN));
|
|
iEaseFrom->SetLimits(0.0f,50.0f,FALSE);
|
|
iEaseFrom->SetScale(0.1f);
|
|
iEaseFrom->LinkToEdit(GetDlgItem(hWnd,IDC_ATTACH_KEYEASEFROM),EDITTYPE_FLOAT);
|
|
|
|
iPrev = GetICustButton(GetDlgItem(hWnd,IDC_PREVKEY));
|
|
iNext = GetICustButton(GetDlgItem(hWnd,IDC_NEXTKEY));
|
|
|
|
iStat = GetICustStatus(GetDlgItem(hWnd,IDC_KEYNUM));
|
|
iStat->SetTextFormat(STATUSTEXT_CENTERED);
|
|
}
|
|
|
|
RefResult AttachKeyDlg::NotifyRefChanged(
|
|
Interval iv, RefTargetHandle hTarg,
|
|
PartID& partID,RefMessage msg)
|
|
{
|
|
switch (msg) {
|
|
case REFMSG_CHANGE:
|
|
Invalidate();
|
|
break;
|
|
|
|
case REFMSG_REF_DELETED:
|
|
MaybeCloseWindow();
|
|
break;
|
|
}
|
|
return REF_SUCCEED;
|
|
}
|
|
|
|
static BOOL CALLBACK AttachDlgProc(
|
|
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
AttachKeyDlg *dlg = (AttachKeyDlg*)GetWindowLong(hWnd,GWL_USERDATA);
|
|
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
dlg = (AttachKeyDlg*)lParam;
|
|
SetWindowLong(hWnd,GWL_USERDATA,lParam);
|
|
dlg->SetupUI(hWnd);
|
|
CenterWindow(hWnd,GetParent(hWnd));
|
|
ShowWindow(hWnd,SW_SHOW);
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
DestroyWindow(hWnd);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
delete dlg;
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
dlg->Update();
|
|
return FALSE;
|
|
|
|
case CC_SPINNER_BUTTONDOWN:
|
|
theHold.Begin();
|
|
break;
|
|
|
|
case WM_CUSTEDIT_ENTER:
|
|
case CC_SPINNER_BUTTONUP:
|
|
if (HIWORD(wParam)) {
|
|
theHold.Accept(GetString(IDS_RB_EDITATTACHKEY));
|
|
} else {
|
|
theHold.Cancel();
|
|
}
|
|
break;
|
|
|
|
case CC_SPINNER_CHANGE:
|
|
dlg->SpinnerChange(LOWORD(wParam));
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
dlg->Command(LOWORD(wParam),lParam);
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|