Cockpit: authentic eyepoint + canopy lattice render (task #55)

The first-person cockpit now shows by default and matches gameplay footage
(verified on the Madcat). Two decomp-verified reconstructions:

EYEPOINT (FUN_004579a8 + caller part_014.c:5525, FUN_004c22c4):
- eye offset = siteeyepoint GetBaseOffset (not GetSegmentToEntity, not the
  upright hack), parented on the PARENT segment DCS (btl4vid.cpp)
- live view = affine INVERSE of the eye world matrix (L4VIDRND Execute) --
  replaces the hand-rolled LookAtRH whose +Z-forward/+Y-up row guess aimed
  some mechs into the canopy; combine order fixed to baseOffset * R
- chase camera basis + aim boresight re-expressed for the inverse convention
  (row2 = back); boresight now derives from the view matrix (the stale rows
  aimed the pick ray at the sky = no target, no discharge)

CANOPY (*_COP.BGF, dpl_Punchize geogroups):
- every shell is an open strut lattice (38-59% boundary edges, all 12 mechs);
  rendered single-sided with per-face INWARD winding (l/r torso patches are
  mirrored, so no global winding works) the struts are the dark frame and the
  openings show the world
- frame colour = the 'softer' ramp near its dark end (texture-less unlit ramp;
  BLXSKIN.BMF has no texture -- the old "punch texels" theory was wrong)
- scoped by filename (meshIsCop); wrong drop-punch + view-cut paths removed
- BT_FORCE_MODEL=<name> forces the player mech for per-mech bring-up

KB: new context/cockpit-view.md topic; punch split corrected in bgf-format;
gotchas 14 (LookAt axis guess) + 15 (per-patch edge namespaces); env gates +
eye/punch addresses in decomp-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 06:46:54 -05:00
co-authored by Claude Fable 5
parent 7b0b4f64ab
commit abaa145b6f
9 changed files with 349 additions and 53 deletions
+25 -23
View File
@@ -401,25 +401,18 @@ HierarchicalDrawComponent*
EulerAngles *eye_rot =
(EulerAngles *)entity->GetAttributePointer("EyepointRotation"); // @0051d29d
//
// Mount the eye at the eyepoint's REST position but with a clean
// UPRIGHT forward basis, parented on the tree ROOT: riding the
// live joint chain fed the camera the site's authored tilt + the
// torso pose (a permanently canted horizon). The authentic pitch
// /yaw came from the gyro-driven eye-joint chain (deferred); until
// that lands, the cockpit view = eyepoint position + mech yaw.
// AUTHENTIC eye (decomp FUN_004579a8 @part_007.c:9274-9325; caller part_014.c:5525-5566):
// the eye offset matrix is the siteeyepoint segment's LOCAL rest transform (GetBaseOffset,
// segment+0x74 -- already in offset_matrix above), and the eye is parented on its PARENT
// segment's draw component (parent_DCS) -- NOT the root, NOT the full GetSegmentToEntity,
// NOT an upright hack. World orientation + all live motion (torso twist, gait) come from
// the parent-chain composition, exactly as the decomp's dpl_AddDCSToDCS hierarchy does;
// EyepointRotation is combined in Execute as baseOffset * R, and VIEW = inverse(eyeWorld)
// (FUN_004c22c4 @part_013.c:11788) -- so the look/up axes fall out of the eye's own basis
// with no per-mech forward-axis assumption.
//
AffineMatrix restToEntity = segment->GetSegmentToEntity();
Point3D eyePos;
eyePos = restToEntity; // rest translation (W row)
LinearMatrix eyeBasis(True);
eyeBasis(0,0) = -1.0f; eyeBasis(0,1) = 0.0f; eyeBasis(0,2) = 0.0f; // X row
eyeBasis(1,0) = 0.0f; eyeBasis(1,1) = 1.0f; eyeBasis(1,2) = 0.0f; // Y row (up)
eyeBasis(2,0) = 0.0f; eyeBasis(2,1) = 0.0f; eyeBasis(2,2) = -1.0f; // Z row (look = mech forward)
eyeBasis(3,0) = (Scalar)eyePos.x;
eyeBasis(3,1) = (Scalar)eyePos.y;
eyeBasis(3,2) = (Scalar)eyePos.z;
mEyeCockpit = new DPLEyeRenderable(
entity, eyeBasis, this_root, eye_rot);
entity, offset_matrix, parent_DCS, eye_rot);
if (type == insideEntity) // true inside build: it IS the camera
mCamera = mEyeCockpit;
dbg_eye = 1;
@@ -633,10 +626,16 @@ HierarchicalDrawComponent*
// recomputed up (local +Y) = forward x right
float yx = zy*xz - zz*xy, yy = zz*xx - zx*xz, yz = zx*xy - zy*xx;
// CAMERA-BASIS CONVENTION (task #56 follow-through): the view is now the
// authentic VIEW = inverse(eyeWorld) (DPLEyeRenderable::Execute), under
// which the camera looks along -Z of its own basis -- so the basis rows
// are right/up/BACK, not right/up/look. This chase matrix was authored
// for the old LookAt (row2 = look); convert by a 180-degree turn about
// local Y (negate the X and Z rows -- determinant stays +1).
LinearMatrix debugOffset; // identity
debugOffset(0,0) = xx; debugOffset(0,1) = xy; debugOffset(0,2) = xz; // X row
debugOffset(0,0) = -xx; debugOffset(0,1) = -xy; debugOffset(0,2) = -xz; // X row (right, flipped)
debugOffset(1,0) = yx; debugOffset(1,1) = yy; debugOffset(1,2) = yz; // Y row (up)
debugOffset(2,0) = zx; debugOffset(2,1) = zy; debugOffset(2,2) = zz; // Z row (look)
debugOffset(2,0) = -zx; debugOffset(2,1) = -zy; debugOffset(2,2) = -zz; // Z row (BACK = -look)
debugOffset(3,0) = camPx; debugOffset(3,1) = camPy; debugOffset(3,2) = camPz; // W row (pos)
mEyeChase = new DPLEyeRenderable(entity, debugOffset, this_root, NULL);
@@ -2037,6 +2036,7 @@ BTL4VideoRenderer::BTL4VideoRenderer(
mViewInside = 0;
}
//
//
// ApplyViewSkeleton -- select + load each body segment's displayed mesh for the
// given view (inside = SkeletonType_A / outside = the mech's own skeletonType),
@@ -2083,11 +2083,13 @@ int
gstate = viewpoint->damageZones[zone_index]->GetGraphicState();
CString *nm = segment->GetVideoObjectName(
(EntitySegment::SkeletonType)render_tree.viewSkeleton, gstate);
// The cockpit interior shell (blx_cop -- the canopy frame around the
// eyepoint) currently renders as a black enclosure; keep it hidden until
// its interior rendering is sorted (BT_INSIDE_COCKPIT=1 shows it).
// The cockpit canopy shell (*_cop -- the frame around the eyepoint) SHOWS by
// default: with the authentic eye (baseOffset + parent-segment DCS + inverse
// view) and the loader's single-sided dark-ramp treatment it renders as the
// dark frame with the world through the openings (task #55, verified vs
// gameplay footage). BT_HIDE_COCKPIT=1 hides it (diagnostic).
if (inside && nm != NULL && strstr((const char *)*nm, "_cop") != NULL
&& !getenv("BT_INSIDE_COCKPIT"))
&& getenv("BT_HIDE_COCKPIT"))
nm = NULL;
d3d_OBJECT *obj = NULL;
if (nm != NULL)