Files
BT412/engine/MUNGA/BOXSORT.cpp
T
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

475 lines
12 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "boxsolid.h"
#include "vector3d.h"
//#############################################################################
//########################### BoxedSolidList ############################
//#############################################################################
enum {
X_Axis_Bit = 1,
Y_Axis_Bit = 2,
Z_Axis_Bit = 4
};
enum {
Min_Side = 0x001,
Inside = 0x002,
Max_Side = 0x004,
Both_Sides = Min_Side|Max_Side,
Shift = 3,
X_Shift = 2 * Shift,
Min_X_Side = Min_Side << X_Shift,
Inside_X = Inside << X_Shift,
Max_X_Side = Max_Side << X_Shift,
X_Mask = Min_X_Side|Inside_X|Max_X_Side,
Y_Shift = Shift,
Min_Y_Side = Min_Side << Y_Shift,
Inside_Y = Inside << Y_Shift,
Max_Y_Side = Max_Side << Y_Shift,
Y_Mask = Min_Y_Side|Inside_Y|Max_Y_Side,
Z_Shift = 0,
Min_Z_Side = Min_Side << Z_Shift,
Inside_Z = Inside << Z_Shift,
Max_Z_Side = Max_Side << Z_Shift,
Z_Mask = Min_Z_Side|Inside_Z|Max_Z_Side
};
int
Legal_To_Fuse[BoxedSolid::SolidTypeCount]=
{
X_Axis_Bit|Y_Axis_Bit|Z_Axis_Bit, 0, 0, 0,
X_Axis_Bit, Z_Axis_Bit, X_Axis_Bit, Z_Axis_Bit,
X_Axis_Bit, Z_Axis_Bit, X_Axis_Bit, Z_Axis_Bit,
Y_Axis_Bit, Y_Axis_Bit, Y_Axis_Bit, Y_Axis_Bit,
X_Axis_Bit, Y_Axis_Bit, Z_Axis_Bit
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
BoundingBoxList::Reduce()
{
Check(this);
BoundingBoxListNode
*i, *j, *previous;
//
//--------------------------------------------------------------------------
// Fuse the collision slices together into the largest possible chunks based
// upon the collision model of each of the involved slices. Repeat until no
// fusings were made in the last pass or only one collision slice remains
//--------------------------------------------------------------------------
//
Logical again = True;
while (again)
{
again = False;
//
//--------------------------------------------------------------------
// Check each collision slice against the remaining slices in the list
//--------------------------------------------------------------------
//
for (i=root; i; i = i->previousNode)
{
Check(i);
previous = i;
j = i->previousNode;
while (j)
{
Check(j);
//
//-------------------------------------------------------------
// If the model types are different, these two slices cannot be
// fused
//-------------------------------------------------------------
//
BoundingBox *first = i->boundingBox;
BoundingBox *second = j->boundingBox;
//
//----------------------------------------------------
// Make sure that the faces on two sets of sides match
//----------------------------------------------------
//
int matches = 0;
int face = -1;
for (int side=0; side<6; side += 2)
{
if (
(*first)[side] == (*second)[side]
&& (*first)[side+1] == (*second)[side+1]
)
{
++matches;
}
else if (face<0)
{
face = side;
}
}
if (matches != 2)
{
Next_Solid:
previous = j;
j = j->previousNode;
continue;
}
//
//----------------------------------------------------------------
// Check to make sure that the two solids have an opposing face in
// common, which will allow the solids to be fused
//----------------------------------------------------------------
//
if (
(*first)[face] != (*second)[face+1]
&& (*first)[face+1] != (*second)[face]
)
{
goto Next_Solid;
}
//
//----------------------------------------------------
// Find the face to fuse, and fuse the blocks together
//----------------------------------------------------
//
if ((*first)[face+1] == (*second)[face])
{
++face;
}
(*first)[face] = (*second)[face];
//
//-----------------------------------------------
// Erase the second solid from the collision list
//-----------------------------------------------
//
--nodeCount;
if (previous)
{
previous->previousNode = j->previousNode;
Unregister_Object(j);
delete(j);
j = previous->previousNode;
}
else
{
root = j->previousNode;
Unregister_Object(j);
delete(j);
j = root;
}
again = True;
}
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
BoundingBoxList::SortForTree()
{
Check(this);
int
i, j;
BoundingBoxListNode
*p, *q;
//
//-----------------------------------------------------------------------
// Find out how many collision solids are in the list, and figure out the
// total extent box so that we can set the traversal order correctly
//-----------------------------------------------------------------------
//
ExtentBox map_extents;
map_extents.minX = 65535.9999f;
map_extents.minY = 65535.9999f;
map_extents.minZ = 65535.9999f;
map_extents.maxX = -65535.9999f;
map_extents.maxY = -65535.9999f;
map_extents.maxZ = -65535.9999f;
for (p=root; p; p = p->previousNode)
{
Check(p);
if (p->boundingBox->minX < map_extents.minX)
{
map_extents.minX = p->boundingBox->minX;
}
if (p->boundingBox->maxX > map_extents.maxX)
{
map_extents.maxX = p->boundingBox->maxX;
}
if (p->boundingBox->minY < map_extents.minY)
{
map_extents.minY = p->boundingBox->minY;
}
if (p->boundingBox->maxY > map_extents.maxY)
{
map_extents.maxY = p->boundingBox->maxY;
}
if (p->boundingBox->minZ < map_extents.minZ)
{
map_extents.minZ = p->boundingBox->minZ;
}
if (p->boundingBox->maxZ > map_extents.maxZ)
{
map_extents.maxZ = p->boundingBox->maxZ;
}
}
if (
map_extents.maxZ - map_extents.minZ
> map_extents.maxX - map_extents.minX
)
{
isXMajorAxis = False;
}
//
//----------------------------------------------------
// Allocate a scoreboard and fill it with sorting info
//----------------------------------------------------
//
Verify(!scoreBoard);
scoreBoard = new int[nodeCount * nodeCount];
Register_Pointer(scoreBoard);
int *score = scoreBoard;
boundingBoxIndex = new BoundingBox* [nodeCount];
Register_Pointer(boundingBoxIndex);
for (p=root,i=0; p; p = p->previousNode,++i)
{
Check(p);
BoundingBox *first = p->boundingBox;
boundingBoxIndex[i] = first;
for (q=root,j=0; q; q = q->previousNode,++j,++score)
{
//
//------------------------------
// Ignore scoring against itself
//------------------------------
//
Check(q);
*score = 0;
if (p == q)
{
continue;
}
BoundingBox *second = q->boundingBox;
//
//--------------------------------------------------------------------
// Step through the three axes and set the flags showing how the
// second solid is split up by the first solid. Make sure that if the
// second solid completely covers the first that this inside bit is
// set correctly
//--------------------------------------------------------------------
//
for (int axis = X_Axis; axis <= Z_Axis; --axis)
{
*score <<= Shift;
int face = axis << 1;
if ((*second)[face] < (*first)[face])
{
*score |= Min_Side;
}
else if ((*second)[face] < (*first)[face+1])
{
*score |= Inside;
}
if ((*second)[face+1] > (*first)[face+1])
{
*score |= Max_Side;
}
else if ((*second)[face+1] > (*first)[face])
{
*score |= Inside;
}
if ((*score & Both_Sides) == Both_Sides)
{
*score |= Inside;
}
}
}
}
//
//--------------------------------------------------------------------------
// Create a new BoxedSolid list for results to go into, and a third in which
// to pass the active list
//--------------------------------------------------------------------------
//
BoundingBoxList active;
active.root = root;
active.nodeCount = nodeCount;
root = NULL;
BoundingBoxTree tree_so_far;
Sort(
active,
map_extents,
tree_so_far
);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
BoundingBoxList::Sort(
BoundingBoxList &,//active,
ExtentBox &,//map_extents,
BoundingBoxTree &//tree_so_far
)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
BoxedSolidList::Reduce()
{
Check(this);
BoundingBoxListNode
*i, *j, *previous;
//
//--------------------------------------------------------------------------
// Fuse the collision slices together into the largest possible chunks based
// upon the collision model of each of the involved slices. Repeat until no
// fusings were made in the last pass or only one collision slice remains
//--------------------------------------------------------------------------
//
Logical again = True;
while (again)
{
again = False;
//
//--------------------------------------------------------------------
// Check each collision slice against the remaining slices in the list
//--------------------------------------------------------------------
//
for (i=root; i; i = i->previousNode)
{
Check(i);
previous = i;
j = i->previousNode;
while (j)
{
Check(j);
//
//-------------------------------------------------------------
// If the model types are different, these two slices cannot be
// fused
//-------------------------------------------------------------
//
BoxedSolid *first = Cast_Object(BoxedSolid*, i->boundingBox);
BoxedSolid *second = Cast_Object(BoxedSolid*, j->boundingBox);
if (first->solidType != second->solidType)
{
Next_Solid:
previous = j;
j = j->previousNode;
continue;
}
//
//----------------------------------------------------
// Make sure that the faces on two sets of sides match
//----------------------------------------------------
//
int matches = 0;
int face = -1;
for (int side=0; side<6; side += 2)
{
if (
(*first)[side] == (*second)[side]
&& (*first)[side+1] == (*second)[side+1]
)
{
++matches;
}
else if (face<0)
{
face = side;
}
}
if (matches != 2)
{
goto Next_Solid;
}
//
//----------------------------------------------------------------
// Check to make sure that the two solids have an opposing face in
// common, which will allow the solids to be fused
//----------------------------------------------------------------
//
if (
(*first)[face] != (*second)[face+1]
&& (*first)[face+1] != (*second)[face]
)
{
goto Next_Solid;
}
//
//---------------------------------------------------------------
// Make sure that this type of solid is legal to be fused in this
// direction
//---------------------------------------------------------------
//
if (!(Legal_To_Fuse[first->solidType] & (face>>1)))
{
goto Next_Solid;
}
//
//----------------------------------------------------
// Find the face to fuse, and fuse the blocks together
//----------------------------------------------------
//
if ((*first)[face+1] == (*second)[face])
{
++face;
}
(*first)[face] = (*second)[face];
//
//-----------------------------------------------
// Erase the second solid from the collision list
//-----------------------------------------------
//
if (previous)
{
previous->previousNode = j->previousNode;
Unregister_Object(j);
delete(j);
j = previous->previousNode;
}
else
{
root = j->previousNode;
Unregister_Object(j);
delete(j);
j = root;
}
again = True;
}
}
}
}