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.
390 lines
9.7 KiB
C++
390 lines
9.7 KiB
C++
/**********************************************************************
|
|
*<
|
|
FILE: followu.cpp
|
|
|
|
DESCRIPTION: A utility that generates path follow
|
|
|
|
CREATED BY: Rolf Berteig
|
|
|
|
HISTORY: created 1/27/97
|
|
|
|
*> Copyright (c) 1996, All Rights Reserved.
|
|
**********************************************************************/
|
|
#include "ctrl.h"
|
|
#include "utilapi.h"
|
|
|
|
|
|
#define FOLLOW_CLASS_ID Class_ID(0x4557a2c4,0x00186d34)
|
|
#define FOLLOW_CNAME GetString(IDS_RB_FOLLOWUTIL)
|
|
|
|
class FollowUtil : public UtilityObj {
|
|
public:
|
|
IUtil *iu;
|
|
Interface *ip;
|
|
HWND hPanel;
|
|
|
|
BOOL bank, flip;
|
|
float bankAmount, tracking;
|
|
TimeValue start, end;
|
|
int samples;
|
|
|
|
FollowUtil();
|
|
void BeginEditParams(Interface *ip,IUtil *iu);
|
|
void EndEditParams(Interface *ip,IUtil *iu);
|
|
void DeleteThis() {}
|
|
void SelectionSetChanged(Interface *ip,IUtil *iu);
|
|
|
|
void Init(HWND hWnd);
|
|
void Destroy(HWND hWnd);
|
|
void SetStates(HWND hWnd);
|
|
|
|
Matrix3 CalcRefFrame(TimeValue t,INode *node);
|
|
Point3 GetPosition(INode *node,TimeValue t);
|
|
void DoFollow();
|
|
};
|
|
static FollowUtil theFollowUtil;
|
|
|
|
//--- Class Descriptor ------------------------------------------
|
|
|
|
class FollowUtilClassDesc:public ClassDesc {
|
|
public:
|
|
int IsPublic() {return 1;}
|
|
void * Create(BOOL loading = FALSE) {return &theFollowUtil;}
|
|
const TCHAR * ClassName() {return FOLLOW_CNAME;}
|
|
SClass_ID SuperClassID() {return UTILITY_CLASS_ID;}
|
|
Class_ID ClassID() {return FOLLOW_CLASS_ID;}
|
|
const TCHAR* Category() {return _T("");}
|
|
void ResetClassParams(BOOL fileReset);
|
|
};
|
|
|
|
void FollowUtilClassDesc::ResetClassParams(BOOL fileReset)
|
|
{
|
|
if (fileReset) {
|
|
theFollowUtil = FollowUtil();
|
|
}
|
|
}
|
|
|
|
static FollowUtilClassDesc followUtilDesc;
|
|
ClassDesc* GetFollowUtilDesc() {return &followUtilDesc;}
|
|
|
|
#define BANKSCALE 100.0f
|
|
#define FromBankUI(a) ((a)*BANKSCALE)
|
|
#define ToBankUI(a) ((a)/BANKSCALE)
|
|
|
|
#define TRACKSCALE 0.04f
|
|
#define FromTrackUI(a) ((a)*TRACKSCALE)
|
|
#define ToTrackUI(a) ((a)/TRACKSCALE)
|
|
|
|
|
|
///--- Panel Proc ------------------------------------------------
|
|
|
|
static BOOL CALLBACK FollowUtilDlgProc(
|
|
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
theFollowUtil.Init(hWnd);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
theFollowUtil.Destroy(hWnd);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam)) {
|
|
case IDC_PATH_BANK:
|
|
theFollowUtil.bank = IsDlgButtonChecked(hWnd,IDC_PATH_BANK);
|
|
theFollowUtil.SetStates(hWnd);
|
|
break;
|
|
|
|
case IDC_PATH_ALLOWFLIP:
|
|
theFollowUtil.flip = IsDlgButtonChecked(hWnd,IDC_PATH_ALLOWFLIP);
|
|
break;
|
|
|
|
case IDC_FOLLOW_APPLY:
|
|
theFollowUtil.DoFollow();
|
|
break;
|
|
|
|
case IDOK:
|
|
theFollowUtil.iu->CloseUtility();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CC_SPINNER_CHANGE: {
|
|
ISpinnerControl *spin = (ISpinnerControl*)lParam;
|
|
switch (LOWORD(wParam)) {
|
|
case IDC_FOLLOW_BANKSPIN:
|
|
theFollowUtil.bankAmount = FromBankUI(spin->GetFVal());
|
|
break;
|
|
|
|
case IDC_FOLLOW_TRACKSPIN:
|
|
theFollowUtil.tracking = FromTrackUI(spin->GetFVal());
|
|
break;
|
|
|
|
case IDC_FOLLOW_STARTSPIN:
|
|
theFollowUtil.start = spin->GetIVal();
|
|
break;
|
|
|
|
case IDC_FOLLOW_ENDSPIN:
|
|
theFollowUtil.end = spin->GetIVal();
|
|
break;
|
|
|
|
case IDC_FOLLOW_SAMPSPIN:
|
|
theFollowUtil.samples = spin->GetIVal();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
case WM_MOUSEMOVE:
|
|
theFollowUtil.ip->RollupMouseMessage(hWnd,msg,wParam,lParam);
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// --- FollowUtil methods -----------------------------------------
|
|
|
|
FollowUtil::FollowUtil()
|
|
{
|
|
bank = FALSE;
|
|
flip = FALSE;
|
|
bankAmount = FromBankUI(0.5f);
|
|
tracking = FromTrackUI(0.5f);
|
|
start = 0;
|
|
end = 16000;
|
|
samples = 25;
|
|
}
|
|
|
|
void FollowUtil::BeginEditParams(Interface *ip,IUtil *iu)
|
|
{
|
|
this->iu = iu;
|
|
this->ip = ip;
|
|
hPanel = ip->AddRollupPage(
|
|
hInstance,
|
|
MAKEINTRESOURCE(IDD_FOLLOW_PANEL),
|
|
FollowUtilDlgProc,
|
|
GetString(IDS_RB_FOLLOWUTIL),
|
|
0);
|
|
}
|
|
|
|
void FollowUtil::EndEditParams(Interface *ip,IUtil *iu)
|
|
{
|
|
this->iu = NULL;
|
|
this->ip = NULL;
|
|
ip->DeleteRollupPage(hPanel);
|
|
hPanel = NULL;
|
|
}
|
|
|
|
|
|
void FollowUtil::SelectionSetChanged(Interface *ip,IUtil *iu)
|
|
{
|
|
SetStates(hPanel);
|
|
if (ip->GetSelNodeCount()==1) {
|
|
SetDlgItemText(hPanel,IDC_SEL_NAME,ip->GetSelNode(0)->GetName());
|
|
} else if (ip->GetSelNodeCount()) {
|
|
SetDlgItemText(hPanel,IDC_SEL_NAME,GetString(IDS_RB_MULTISEL));
|
|
} else {
|
|
SetDlgItemText(hPanel,IDC_SEL_NAME,GetString(IDS_RB_NONESEL));
|
|
}
|
|
}
|
|
|
|
void FollowUtil::Init(HWND hWnd)
|
|
{
|
|
hPanel = hWnd;
|
|
|
|
CheckDlgButton(hWnd,IDC_PATH_BANK,bank);
|
|
CheckDlgButton(hWnd,IDC_PATH_ALLOWFLIP,flip);
|
|
|
|
ISpinnerControl * spin;
|
|
spin = GetISpinner(GetDlgItem(hWnd,IDC_FOLLOW_STARTSPIN));
|
|
spin->SetLimits(TIME_NegInfinity,TIME_PosInfinity,FALSE);
|
|
spin->SetScale(10.0f);
|
|
spin->LinkToEdit(GetDlgItem(hWnd,IDC_FOLLOW_START),EDITTYPE_TIME);
|
|
spin->SetValue(start,FALSE);
|
|
ReleaseISpinner(spin);
|
|
|
|
spin = GetISpinner(GetDlgItem(hWnd,IDC_FOLLOW_ENDSPIN));
|
|
spin->SetLimits(TIME_NegInfinity,TIME_PosInfinity,FALSE);
|
|
spin->SetScale(10.0f);
|
|
spin->LinkToEdit(GetDlgItem(hWnd,IDC_FOLLOW_END),EDITTYPE_TIME);
|
|
spin->SetValue(end,FALSE);
|
|
ReleaseISpinner(spin);
|
|
|
|
spin = GetISpinner(GetDlgItem(hWnd,IDC_FOLLOW_SAMPSPIN));
|
|
spin->SetLimits(1,999999999,FALSE);
|
|
spin->SetScale(0.1f);
|
|
spin->LinkToEdit(GetDlgItem(hWnd,IDC_FOLLOW_SAMP),EDITTYPE_INT);
|
|
spin->SetValue(samples,FALSE);
|
|
ReleaseISpinner(spin);
|
|
|
|
spin = GetISpinner(GetDlgItem(hWnd,IDC_FOLLOW_BANKSPIN));
|
|
spin->SetLimits(-999999999,999999999,FALSE);
|
|
spin->SetScale(0.01f);
|
|
spin->LinkToEdit(GetDlgItem(hWnd,IDC_FOLLOW_BANK),EDITTYPE_FLOAT);
|
|
spin->SetValue(ToBankUI(bankAmount),FALSE);
|
|
ReleaseISpinner(spin);
|
|
|
|
spin = GetISpinner(GetDlgItem(hWnd,IDC_FOLLOW_TRACKSPIN));
|
|
spin->SetLimits(0.01f,10.0f,FALSE);
|
|
spin->SetScale(0.01f);
|
|
spin->LinkToEdit(GetDlgItem(hWnd,IDC_FOLLOW_TRACK),EDITTYPE_FLOAT);
|
|
spin->SetValue(ToTrackUI(tracking),FALSE);
|
|
ReleaseISpinner(spin);
|
|
|
|
SelectionSetChanged(ip,iu);
|
|
}
|
|
|
|
void FollowUtil::Destroy(HWND hWnd)
|
|
{
|
|
hPanel = NULL;
|
|
}
|
|
|
|
void FollowUtil::SetStates(HWND hWnd)
|
|
{
|
|
if (ip->GetSelNodeCount()) {
|
|
EnableWindow(GetDlgItem(hPanel,IDC_FOLLOW_APPLY),TRUE);
|
|
} else {
|
|
EnableWindow(GetDlgItem(hPanel,IDC_FOLLOW_APPLY),FALSE);
|
|
}
|
|
|
|
if (bank) {
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_BANKLABEL),TRUE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_BANKSPIN),TRUE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_TRACKLABEL),TRUE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_TRACKSPIN),TRUE);
|
|
} else {
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_BANKLABEL),FALSE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_BANKSPIN),FALSE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_TRACKLABEL),FALSE);
|
|
EnableWindow(GetDlgItem(hWnd,IDC_FOLLOW_TRACKSPIN),FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
Point3 FollowUtil::GetPosition(INode *node,TimeValue t)
|
|
{
|
|
Matrix3 tm = node->GetNodeTM(t);
|
|
return tm.GetTrans();
|
|
}
|
|
|
|
#define DT 1
|
|
#define NUMSTEPS 5
|
|
|
|
Matrix3 FollowUtil::CalcRefFrame(TimeValue t,INode *node)
|
|
{
|
|
Interval valid;
|
|
Matrix3 tm(1);
|
|
TimeValue s = start, e = end;
|
|
if (s==e) return tm;
|
|
if (s>e) {
|
|
int temp = e;
|
|
e = s;
|
|
s = temp;
|
|
}
|
|
float u = float(t-s)/float(e);
|
|
int length = e-s;
|
|
|
|
Point3 pt0, pt1;
|
|
pt0 = GetPosition(node,t-DT);
|
|
pt1 = GetPosition(node,t+DT);
|
|
|
|
Point3 pathNorm(0,0,0);
|
|
if (flip) {
|
|
// Compute the normal to the plane of the path by sampling points on the path
|
|
#define NUM_SAMPLES 20
|
|
Point3 v[NUM_SAMPLES], cent(0,0,0);
|
|
for (int i=0; i<NUM_SAMPLES; i++) {
|
|
TimeValue tt = int((float(i)/float(NUM_SAMPLES))*float(length)+s);
|
|
v[i] = GetPosition(node,tt);
|
|
cent += v[i];
|
|
}
|
|
cent /= float(NUM_SAMPLES);
|
|
for (i=1; i<NUM_SAMPLES; i++) {
|
|
pathNorm += Normalize((v[i]-cent)^(v[i-1]-cent));
|
|
}
|
|
pathNorm = Normalize(pathNorm);
|
|
}
|
|
|
|
// X
|
|
tm.SetRow(0,Normalize(pt1-pt0));
|
|
|
|
if (flip) {
|
|
// Choose Z in the plane of the path
|
|
// Z
|
|
tm.SetRow(2,Normalize(tm.GetRow(0)^pathNorm));
|
|
|
|
// Y
|
|
tm.SetRow(1,tm.GetRow(2)^tm.GetRow(0));
|
|
} else {
|
|
// Choose Y in the world XY plane
|
|
// Y
|
|
tm.SetRow(1,Normalize(Point3(0,0,1)^tm.GetRow(0)));
|
|
|
|
// Z
|
|
tm.SetRow(2,tm.GetRow(0)^tm.GetRow(1));
|
|
}
|
|
|
|
|
|
if (bank) {
|
|
// Average over NUMSTEPS samples
|
|
Point3 pt0, pt1, pt2, v0, v1;
|
|
float cv = 0.0f;
|
|
u -= float(NUMSTEPS/2+1)*tracking;
|
|
|
|
if (u+(NUMSTEPS+2)*tracking > 1.0f) u = 1.0f - (NUMSTEPS+2)*tracking;
|
|
if (u<0.0f) u=0.0f;
|
|
|
|
pt1 = GetPosition(node,int(u*length)+s);
|
|
u += tracking;
|
|
pt2 = GetPosition(node,int(u*length)+s);
|
|
u += tracking;
|
|
for (int i=0; i<NUMSTEPS; i++) {
|
|
pt0 = pt1;
|
|
pt1 = pt2;
|
|
if (u>1.0f) {
|
|
break;
|
|
} else {
|
|
pt2 = GetPosition(node,int(u*length)+s);
|
|
}
|
|
v0 = Normalize(pt2-pt1);
|
|
v1 = Normalize(pt1-pt0);
|
|
v0.z = v1.z = 0.0f; // remove Z component.
|
|
cv += (v0^v1).z * bankAmount / Length(pt1-pt0);
|
|
u += tracking;
|
|
}
|
|
if (i) tm.PreRotateX(cv/float(i));
|
|
}
|
|
|
|
return tm;
|
|
}
|
|
|
|
void FollowUtil::DoFollow()
|
|
{
|
|
theHold.Begin();
|
|
SuspendAnimate();
|
|
AnimateOn();
|
|
for (int j=0; j<ip->GetSelNodeCount(); j++) {
|
|
INode *node = ip->GetSelNode(j);
|
|
Control *cont=node->GetTMController()->GetRotationController();
|
|
if (cont && cont->IsKeyable()) {
|
|
cont->DeleteTime(Interval(start,end),TIME_INCLEFT|TIME_INCRIGHT|TIME_NOSLIDE);
|
|
for (int i=0; i<=samples; i++) {
|
|
TimeValue t = int((float(i)/float(samples))*(end-start)) + start;
|
|
Matrix3 tm = CalcRefFrame(t,node);
|
|
Quat q(tm);
|
|
cont->SetValue(t,&q,1,CTRL_ABSOLUTE);
|
|
}
|
|
}
|
|
}
|
|
ResumeAnimate();
|
|
theHold.Accept(GetString(IDS_RB_APPLYFOLLOW));
|
|
ip->RedrawViews(ip->GetTime());
|
|
}
|