Files
firestorm/Gameleap/code/mw4/Tools/3DSPlugins/MaxControls/FOLLOWU.CPP
T
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

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());
}