Files
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
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.
2026-06-24 21:28:16 -05:00

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> &times,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> &times,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;
}