Cockpit view live: V toggle, camera gate, real EyepointRotation, inside skeleton

- V toggles the authentic cockpit eyepoint <-> the chase camera.  Both eyes
  coexist; DPLEyeRenderable::Execute now writes the VIEW only when it IS
  mCamera (unconditional writes let the last-executed eye stomp the toggle).
- EyepointRotation is a real zeroed EulerAngles member on Mech (was bound to
  the shared junk attrPad -- the eye composes this attribute into the view
  EVERY frame, so the cockpit camera was rotated by garbage: the canted
  horizon, then the black screen).  The eye-slew systems write it later.
- The cockpit eye mounts at the eyepoint's REST position with a clean upright
  forward basis on the tree root (the live joint chain fed it the site tilt +
  torso pose; authentic pitch/yaw is the deferred gyro eye chain).
- The inside view swaps the player to the INSIDE skeleton mesh set
  (SkeletonType_A): 19 body segments hide, exactly one mesh remains --
  blx_cop.bgf, the authentic cockpit canopy shell around the eyepoint.  It
  currently renders as a black enclosure (the black-screen report), so it is
  HIDDEN pending its interior/punch-material rendering (BT_INSIDE_COCKPIT=1
  shows it for that work).  Damage gstates respected in both directions;
  RemakeEntity keys off the DISPLAYED skeleton.
- Dev: BT_START_INSIDE=1 starts in the cockpit view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 19:23:07 -05:00
co-authored by Claude Fable 5
parent c2f70f6348
commit 56f02b2176
6 changed files with 186 additions and 11 deletions
+5 -1
View File
@@ -5571,7 +5571,11 @@ void
++dbgEyeExec;
}
myDevice->SetTransform(D3DTS_VIEW, &view);
// Only the ACTIVE camera writes the view: the BT view toggle builds
// BOTH the cockpit and chase eyes; an unconditional write here let
// whichever eye executed LAST stomp the toggled camera every frame.
if (myRenderer->mCamera == this)
myDevice->SetTransform(D3DTS_VIEW, &view);
myRenderer->GetMatrixStack()->Pop();
}
else if (dbgEyeExec < 8)
+140 -8
View File
@@ -302,6 +302,7 @@ HierarchicalDrawComponent*
MechRenderTree &render_tree = mMechRenderTrees[entity];
render_tree = MechRenderTree();
render_tree.skeletonType = (int)skeletonType;
render_tree.viewSkeleton = (int)skeletonType;
render_tree.rootRenderable = this_root;
render_tree.wrecked = 0;
if (getenv("BT_DEATH_LOG"))
@@ -363,17 +364,37 @@ HierarchicalDrawComponent*
//
if (segment->IsSiteSegment() /* [0x10] */ != 0)
{
if (type == insideEntity &&
// The authentic COCKPIT EYEPOINT. Built for the true inside view
// AND for the player's chase build (buildDebugChaseCamera), so the
// V-key toggle can switch to it -- the pod's only view was this
// eyepoint; the chase camera is the port's usability addition.
if ((type == insideEntity || buildDebugChaseCamera) &&
strcmp((const char *)segment->GetName() /* [0x11c] */, "siteeyepoint") == 0) // @0051d290
{
EulerAngles *eye_rot =
(EulerAngles *)entity->GetAttributePointer("EyepointRotation"); // @0051d29d
//
// DPLEyeRenderable installs itself as the renderer's camera
// (mCamera) so the main view tracks the cockpit eyepoint.
// 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.
//
mCamera = new DPLEyeRenderable(
entity, offset_matrix, parent_DCS, eye_rot);
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);
if (type == insideEntity) // true inside build: it IS the camera
mCamera = mEyeCockpit;
dbg_eye = 1;
}
continue;
@@ -504,6 +525,11 @@ HierarchicalDrawComponent*
SwapToWreck(entity);
}
// DEV: BT_START_INSIDE=1 begins in the cockpit view (also exercises the
// inside-skeleton swap headlessly).
if (buildDebugChaseCamera && getenv("BT_START_INSIDE"))
SetViewInside(1);
//
// TODO(bring-up): inside-view targeting reticle (BTReticleRenderable +
// AddWeapon pips) and the per-subsystem weapon/effect renderables (PPC/
@@ -572,10 +598,12 @@ HierarchicalDrawComponent*
debugOffset(2,0) = zx; debugOffset(2,1) = zy; debugOffset(2,2) = zz; // Z row (look)
debugOffset(3,0) = camPx; debugOffset(3,1) = camPy; debugOffset(3,2) = camPz; // W row (pos)
mCamera = new DPLEyeRenderable(entity, debugOffset, this_root, NULL);
mEyeChase = new DPLEyeRenderable(entity, debugOffset, this_root, NULL);
mCamera = mEyeChase; // default view: chase (V toggles cockpit)
DEBUG_STREAM << "[BTrender] external debug chase camera installed at ("
<< camPx << "," << camPy << "," << camPz << ") looking at ("
<< tgtX << "," << tgtY << "," << tgtZ << ")\n" << std::flush;
<< tgtX << "," << tgtY << "," << tgtZ << ") -- V toggles the cockpit eyepoint"
<< (mEyeCockpit ? "" : " (COCKPIT EYE MISSING)") << "\n" << std::flush;
}
return this_root;
@@ -614,7 +642,7 @@ void
JointedMover *jointed_mover = (JointedMover *)entity;
EntitySegment::SkeletonType skeletonType =
(EntitySegment::SkeletonType)render_tree.skeletonType;
(EntitySegment::SkeletonType)render_tree.viewSkeleton; // the DISPLAYED set
EntitySegment::SegmentTableIterator segment_iterator(jointed_mover->segmentTable);
EntitySegment *segment;
@@ -1233,6 +1261,110 @@ BTL4VideoRenderer::BTL4VideoRenderer(
DPLRenderer(::GetActiveWindow(), 800, 600, false, interest_type, depth_calibration)
{
Check_Pointer(this);
mEyeCockpit = 0;
mEyeChase = 0;
}
//
// The V-key view toggle: switch the live camera between the authentic cockpit
// eyepoint and the port's external chase camera. A missing eye (e.g. the
// cockpit eye on a mech with no siteeyepoint) leaves the current view.
//
void
BTL4VideoRenderer::SetViewInside(int inside)
{
if (inside && mEyeCockpit != 0)
mCamera = mEyeCockpit;
else if (!inside && mEyeChase != 0)
mCamera = mEyeChase;
//
// Swap the player's DISPLAYED skeleton with the view: the INSIDE view uses
// the inside-skeleton mesh set (SkeletonType_A -- most body segments have
// no inside mesh, so the pilot isn't wrapped in his own torso textures;
// the authentic pod view worked exactly this way), the chase view restores
// the full outside set. Damage graphic states are respected per segment.
//
Entity *viewpoint = (application != 0) ? application->GetViewpointEntity() : 0;
std::map<Entity*, MechRenderTree>::iterator tree_it =
mMechRenderTrees.find(viewpoint);
if (tree_it != mMechRenderTrees.end() && !tree_it->second.wrecked)
{
MechRenderTree &render_tree = tree_it->second;
render_tree.viewSkeleton = inside
? (int)EntitySegment::SkeletonType_A
: render_tree.skeletonType;
JointedMover *jm = (JointedMover *)viewpoint;
EntitySegment::SegmentTableIterator it(jm->segmentTable);
EntitySegment *segment;
int shown = 0, hidden = 0;
while ((segment = it.ReadAndNext()) != NULL)
{
if (segment->IsSiteSegment() != 0)
continue;
int slot = segment->GetIndex();
std::map<int, HierarchicalDrawComponent*>::iterator r =
render_tree.segRenderable.find(slot);
if (r == render_tree.segRenderable.end() || r->second == NULL)
continue;
int gstate = 0;
std::map<int, int>::iterator g = render_tree.segGState.find(slot);
if (g != render_tree.segGState.end())
gstate = g->second;
CString *nm = segment->GetVideoObjectName(
(EntitySegment::SkeletonType)render_tree.viewSkeleton,
(Enumeration)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 for that investigation).
if (inside && nm != NULL && strstr((const char *)*nm, "_cop") != NULL
&& !getenv("BT_INSIDE_COCKPIT"))
nm = NULL;
d3d_OBJECT *obj = NULL;
if (nm != NULL)
{
char filename[44];
strcpy(filename, (const char *)*nm);
int len = (int)strlen(filename);
if (len >= 4)
filename[len - 4] = '\0';
strcat(filename, ".bgf");
obj = d3d_OBJECT::LoadObject(GetDevice(), filename);
}
r->second->SetDrawObj(obj);
if (obj)
{
++shown;
DEBUG_STREAM << "[view] shown: seg '"
<< (const char *)segment->GetName() << "' mesh '"
<< (nm ? (const char *)*nm : "?") << "'\n" << std::flush;
}
else ++hidden;
}
DEBUG_STREAM << "[view] skeleton "
<< (inside ? "A (inside)" : "N (outside)") << ": "
<< shown << " segment mesh(es) shown, " << hidden
<< " hidden\n" << std::flush;
}
DEBUG_STREAM << "[view] " << (inside ? "COCKPIT eyepoint" : "external chase")
<< (mCamera == mEyeCockpit ? " (cockpit live)" : " (chase live)")
<< "\n" << std::flush;
}
//
// Sim-side bridge (the mech4 keyboard poll drives it).
//
void BTSetViewInside(int inside)
{
if (application == NULL)
return;
BTL4VideoRenderer *renderer =
(BTL4VideoRenderer *)application->GetVideoRenderer();
if (renderer != NULL)
renderer->SetViewInside(inside);
}
BTL4VideoRenderer::~BTL4VideoRenderer()
+21
View File
@@ -564,6 +564,7 @@ class BTReticleRenderable:
struct MechRenderTree
{
int skeletonType; // EntitySegment::SkeletonType used at build
int viewSkeleton; // the skeleton currently DISPLAYED (view toggle)
HierarchicalDrawComponent *rootRenderable;// the tree root (wreck hulk parent)
int wrecked; // 1 = swapped to the <mech>dbr hulk
DPLStaticChildRenderable *wreckHulk; // the <mech>dbr piece (sinks)
@@ -586,6 +587,20 @@ class BTReticleRenderable:
int
TickWreck(Entity *victim, float dt);
//
// VIEW TOGGLE: the player's mech builds BOTH cameras -- the authentic
// cockpit eyepoint (DPLEyeRenderable at 'siteeyepoint', the pod's only
// view) and the port's external chase camera (a usability addition for
// the windowed build). SetViewInside switches the renderer's live
// camera between them (V key via the BTSetViewInside bridge).
//
void
SetViewInside(int inside);
protected:
DPLEyeRenderable *mEyeCockpit; // the authentic cockpit eyepoint
DPLEyeRenderable *mEyeChase; // the external chase camera
public:
//
// The death-wreck swap (ExplosionScripts effect 104, reconstructed): the
@@ -683,6 +698,12 @@ extern void BTSwapMechToWreck(Entity *victim);
//
extern int BTWreckSinkTick(Entity *victim, float dt);
//
// View-mode bridge (the V key): 1 = the authentic cockpit eyepoint,
// 0 = the external chase camera.
//
extern void BTSetViewInside(int inside);
#endif // BTL4VID_HPP
//===========================================================================//
+3 -2
View File
@@ -531,7 +531,7 @@ const Mech::IndexEntry
ATTRIBUTE_ENTRY(Mech, CollisionMaterialType, attrPad), // 0x19
ATTRIBUTE_ENTRY(Mech, CurrentSpeed, legCycleSpeed), // 0x1a (existing @0x348)
ATTRIBUTE_ENTRY(Mech, MaxRunSpeed, reverseStrideLength), // 0x1b (existing @0x34c = run/top speed)
ATTRIBUTE_ENTRY(Mech, EyepointRotation, attrPad), // 0x1c
ATTRIBUTE_ENTRY(Mech, EyepointRotation, eyepointRotation), // 0x1c (real member -- the eye reads it per frame)
ATTRIBUTE_ENTRY(Mech, TargetReticle, attrPad), // 0x1d
ATTRIBUTE_ENTRY(Mech, FootStep, attrPad), // 0x1e
ATTRIBUTE_ENTRY(Mech, AnimationState, attrPad), // 0x1f
@@ -1318,7 +1318,8 @@ Mech::Mech(
deathHandler = (int)new MechDeathHandler(this); // FUN_0042a984
critRes->Unlock();
}
wreckSmokeTimer = 0.0f;
wreckSmokeTimer = 0.0f;
eyepointRotation = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f));
//
// Cylinder hit-location table (resource type 0x1d = DamageLookupTableStream) --
+6
View File
@@ -501,6 +501,12 @@ protected:
// smoke plume (psfx 1, DDTHSMK) every 10s (its authored emission window)
// while the mech is a destroyed wreck, so a dead mech keeps smoking.
Scalar wreckSmokeTimer;
// The cockpit eye-slew angles (the "EyepointRotation" attribute the
// binary's DPLEyeRenderable composes into the view every frame). Was
// bound to the shared junk attrPad -> the cockpit camera got rotated by
// garbage (the canted-horizon / black-screen views). Zero until the
// eye-slew system (HUD freeAimSlew / gyro eye chain) writes it.
EulerAngles eyepointRotation;
// Three ref-counted creation-name objects (badge/color/insignia).
void *resourceNameA; // @0x844 this[0x211]
+11
View File
@@ -1207,6 +1207,17 @@ void
const int xNow = focused && (pAsync('X') & dn) ? 1 : 0;
if (xNow && !sPrevX) gBTDrive.allStop = 1; // edge -> one all-stop
sPrevX = xNow;
// V: toggle the view between the authentic COCKPIT eyepoint
// (the pod's only view) and the external chase camera.
static int sPrevV = 0, sViewInside = 0;
const int vNow = focused && (pAsync('V') & dn) ? 1 : 0;
if (vNow && !sPrevV)
{
sViewInside = !sViewInside;
extern void BTSetViewInside(int inside);
BTSetViewInside(sViewInside);
}
sPrevV = vNow;
}
}