Files
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

567 lines
12 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "caminst.h"
#include "notation.h"
//##########################################################################
//############################# CameraInstance #######################
//##########################################################################
//##########################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::CameraInstance(
int camera_ID,
const Origin &camera_origin,
Enumeration camera_type
)
{
Str_Copy(cameraName, "camera", sizeof(cameraName));
itoa(camera_ID, cameraName+6, 10);
cameraID = camera_ID;
cameraDataType = camera_type;
SetLocalOrigin(camera_origin);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::CameraInstance(StreamedInstance *model)
{
cameraID = model->cameraID;
localOrigin = model->localOrigin;
cameraToWorld = localOrigin;
cameraDataType = model->cameraType;
for (int i=0; i<4; ++i)
{
clampValues[i] = model->clampValues[i];
}
Str_Copy(cameraName, "camera", sizeof(cameraName));
itoa(cameraID, cameraName+6, 10);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define READ_CAMERA_ENTRY(name,value)\
if (\
!cam_file->GetEntry(\
camera_page_name,\
name,\
&value\
)\
)\
{ \
DEBUG_STREAM << camera_page_name << " missing " name "!\n" << std::flush;\
Fail("Bad cameras!");\
}
CameraInstance::CameraInstance(
NotationFile *cam_file,
const char *camera_page_name
)
{
Check(this);
Check(cam_file);
Check_Pointer(camera_page_name);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// PageName represents the cameraName
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Str_Copy(cameraName, camera_page_name, sizeof(cameraName));
//
//~~~~~~~~~~~~~~~~~~~~~
// Read in the position
//~~~~~~~~~~~~~~~~~~~~~
//
READ_CAMERA_ENTRY("tranx", localOrigin.linearPosition.x);
READ_CAMERA_ENTRY("trany", localOrigin.linearPosition.y);
READ_CAMERA_ENTRY("tranz", localOrigin.linearPosition.z);
//
//~~~~~~~~~~~~~~~~~~~~~~~~
// Read in the Orientation
//~~~~~~~~~~~~~~~~~~~~~~~~
//
READ_CAMERA_ENTRY("quatx", localOrigin.angularPosition.x);
READ_CAMERA_ENTRY("quaty", localOrigin.angularPosition.y);
READ_CAMERA_ENTRY("quatz", localOrigin.angularPosition.z);
READ_CAMERA_ENTRY("quatw", localOrigin.angularPosition.w);
cameraToWorld = localOrigin;
//
//---------------------
// Read in camera clamp
//---------------------
//
clampValues[0] = -PI;
cam_file->GetEntry(
camera_page_name,
"minYawClamp",
&clampValues[0].angle
);
clampValues[1] = PI;
cam_file->GetEntry(
camera_page_name,
"maxYawClamp",
&clampValues[1].angle
);
clampValues[2] = -PI_OVER_2;
cam_file->GetEntry(
camera_page_name,
"minPitchClamp",
&clampValues[2].angle
);
clampValues[3] = PI_OVER_2;
cam_file->GetEntry(
camera_page_name,
"maxPitchClamp",
&clampValues[3].angle
);
//
// Overwrite default cameraID
//
const char *camera_data_entry;
if(
!cam_file->GetEntry(
camera_page_name,
"cameraType",
&camera_data_entry
)
)
{
cameraDataType = DefaultCameraType;
}
else
{
Check_Pointer(camera_data_entry);
cameraDataType = FindCameraType(camera_data_entry);
}
READ_CAMERA_ENTRY("cameraID", cameraID);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::~CameraInstance()
{
}
//##########################################################################
// Data Access Funtions
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
CameraInstance::FindCameraType(const char *camera_data_type)
{
if(strcmp("DefaultCameraType", camera_data_type) == 0)
{
return DefaultCameraType;
}
else if(strcmp("AlwaysSeesCameraType", camera_data_type) == 0)
{
return AlwaysSeesCameraType;
}
return ErrorCameraType;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::GetCameraTypeString(char *camera_type_string)
{
switch(cameraDataType)
{
case DefaultCameraType:
Str_Copy(
camera_type_string,
"DefaultCameraType",
(sizeof(char) * 128)
);
break;
case AlwaysSeesCameraType:
Str_Copy(
camera_type_string,
"AlwaysSeesCameraType",
(sizeof(char) * 128)
);
break;
default:
Tell(cameraDataType);
Warn(" Invalid cameraType !\n");
break;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::CalculateCameraRotation(
YawPitchRoll *result,
const Point3D &world
)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Given a Vector this function finds a rotation involving only
// pitch and yaw, roll will not be affected. In addition the rotation
// chosed will always be in the direction of least rotation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check(this);
Check(result);
Check(&world);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// find the vector from where we are to where we want to point
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Point3D local;
local.MultiplyByInverse(world, cameraToWorld);
if (Small_Enough(local.LengthSquared()))
{
*result = YawPitchRoll::Identity;
return;
}
//
//~~~~~~~~~~~~~~~~~~~~~
// Calculate the angles
//~~~~~~~~~~~~~~~~~~~~~
//
result->yaw = Radian::Normalize(Arctan(-local.x, -local.z));
Scalar xz_direction = Sqrt((local.x * local.x) + (local.z *local.z));
result->pitch = Radian::Normalize(Arctan(local.y, xz_direction));
result->roll = 0.0f;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::SetLocalOrigin(
const Origin &new_origin
)
{
Check(this);
Check(&new_origin);
YawPitchRoll ypr;
ypr = new_origin.angularPosition;
clampValues[0] = clampValues[1] = 0.0f;
clampValues[2] = clampValues[3] = ypr.pitch;
ypr.pitch = 0.0f;
localOrigin.linearPosition = new_origin.linearPosition;
localOrigin.angularPosition = ypr;
cameraToWorld = localOrigin;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::LookAt(
Origin *result,
const Point3D &world_point
)
{
result->linearPosition = localOrigin.linearPosition;
YawPitchRoll first,local;
first = localOrigin.angularPosition;
CalculateCameraRotation(&local, world_point);
Clamp(local.yaw, clampValues[0], clampValues[1]);
Clamp(local.pitch, clampValues[2], clampValues[3]);
first.yaw += local.yaw;
first.pitch = local.pitch;
result->angularPosition = first;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::AllowLookingAt(const Point3D &world)
{
YawPitchRoll local;
CalculateCameraRotation(&local, world);
if (local.yaw < clampValues[0])
{
clampValues[0] = local.yaw;
}
else if (local.yaw > clampValues[1])
{
clampValues[1] = local.yaw;
}
if (local.pitch < clampValues[2])
{
clampValues[2] = local.pitch;
}
else if (local.pitch > clampValues[3])
{
clampValues[3] = local.pitch;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define YAW_SLOP (PI/12.0)
#define PITCH_SLOP (0.75*(YAW_SLOP))
Logical
CameraInstance::CanCameraSee(const Point3D &world_point)
{
YawPitchRoll ypr;
CalculateCameraRotation(&ypr, world_point);
return
clampValues[0]-YAW_SLOP <= ypr.yaw
&& clampValues[1]+YAW_SLOP >= ypr.yaw
&& clampValues[2]-PITCH_SLOP <= ypr.pitch
&& clampValues[3]+PITCH_SLOP >= ypr.pitch;
}
//##########################################################################
// Test Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
CameraInstance::TestInstance() const
{
return True;
}
//##########################################################################
// Tool Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
CameraInstance::WriteNotationPage(NotationFile *camera_stream)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Output this camera's information to the given notation file
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check(this);
Check(camera_stream);
camera_stream->SetEntry(
cameraName,
"cameraID",
cameraID
);
char camera_type[128];
GetCameraTypeString(camera_type);
camera_stream->SetEntry(
cameraName,
"cameraType",
camera_type
);
camera_stream->SetEntry(
cameraName,
"tranx",
localOrigin.linearPosition.x
);
camera_stream->SetEntry(
cameraName,
"trany",
localOrigin.linearPosition.y
);
camera_stream->SetEntry(
cameraName,
"tranz",
localOrigin.linearPosition.z
);
camera_stream->SetEntry(
cameraName,
"quatx",
localOrigin.angularPosition.x
);
camera_stream->SetEntry(
cameraName,
"quaty",
localOrigin.angularPosition.y
);
camera_stream->SetEntry(
cameraName,
"quatz",
localOrigin.angularPosition.z
);
camera_stream->SetEntry(
cameraName,
"quatw",
localOrigin.angularPosition.w
);
camera_stream->SetEntry(
cameraName,
"minYawClamp",
clampValues[0]
);
camera_stream->SetEntry(
cameraName,
"maxYawClamp",
clampValues[1]
);
camera_stream->SetEntry(
cameraName,
"minPitchClamp",
clampValues[2]
);
camera_stream->SetEntry(
cameraName,
"maxPitchClamp",
clampValues[3]
);
camera_stream->AppendEntry(cameraName, NULL, (char *)NULL);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#undef READ_CAMERA_ENTRY
#define READ_CAMERA_ENTRY(name,value)\
if (\
!cam_file->GetEntry(\
camera_page_name,\
name,\
&value\
)\
)\
{ \
DEBUG_STREAM << camera_page_name << " missing " name "!\n" << std::flush;\
return False;\
}
Logical
CameraInstance::CreateStreamedInstance(
StreamedInstance *model,
NotationFile *cam_file,
const char *camera_page_name
)
{
Check_Pointer(model);
Check(cam_file);
Check_Pointer(camera_page_name);
//
//~~~~~~~~~~~~~~~~~~~~~
// Read in the position
//~~~~~~~~~~~~~~~~~~~~~
//
READ_CAMERA_ENTRY("tranx", model->localOrigin.linearPosition.x);
READ_CAMERA_ENTRY("trany", model->localOrigin.linearPosition.y);
READ_CAMERA_ENTRY("tranz", model->localOrigin.linearPosition.z);
//
//~~~~~~~~~~~~~~~~~~~~~~~~
// Read in the Orientation
//~~~~~~~~~~~~~~~~~~~~~~~~
//
READ_CAMERA_ENTRY("quatx", model->localOrigin.angularPosition.x);
READ_CAMERA_ENTRY("quaty", model->localOrigin.angularPosition.y);
READ_CAMERA_ENTRY("quatz", model->localOrigin.angularPosition.z);
READ_CAMERA_ENTRY("quatw", model->localOrigin.angularPosition.w);
//
//---------------------
// Read in camera clamp
//---------------------
//
model->clampValues[0] = -PI;
cam_file->GetEntry(
camera_page_name,
"minYawClamp",
&model->clampValues[0].angle
);
model->clampValues[1] = PI;
cam_file->GetEntry(
camera_page_name,
"maxYawClamp",
&model->clampValues[1].angle
);
model->clampValues[2] = -PI_OVER_2;
cam_file->GetEntry(
camera_page_name,
"minPitchClamp",
&model->clampValues[2].angle
);
model->clampValues[3] = PI_OVER_2;
cam_file->GetEntry(
camera_page_name,
"maxPitchClamp",
&model->clampValues[3].angle
);
//
// Overwrite default cameraID
//
const char *camera_data_entry;
if(
!cam_file->GetEntry(
camera_page_name,
"cameraType",
&camera_data_entry
)
)
{
model->cameraType = DefaultCameraType;
}
else
{
Check_Pointer(camera_data_entry);
model->cameraType = FindCameraType(camera_data_entry);
}
READ_CAMERA_ENTRY("cameraID", model->cameraID);
model->instanceSize = sizeof(*model);
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(
int camera_ID,
const Origin &camera_origin,
Enumeration camera_type
):
CameraInstance(camera_ID, camera_origin, camera_type)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(StreamedInstance *model):
CameraInstance(model)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(
NotationFile *cam_file,
const char *camera_page_name
):
CameraInstance(cam_file, camera_page_name)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
AlwaysSeesCameraInstance::CanCameraSee(const Point3D &)
{
return True;
}