diff --git a/engine/MUNGA_L4/L4VIDRND.cpp b/engine/MUNGA_L4/L4VIDRND.cpp index 71e92b5..bc728e2 100644 --- a/engine/MUNGA_L4/L4VIDRND.cpp +++ b/engine/MUNGA_L4/L4VIDRND.cpp @@ -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) diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index a3d6bed..6c29c62 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -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::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::iterator r = + render_tree.segRenderable.find(slot); + if (r == render_tree.segRenderable.end() || r->second == NULL) + continue; + int gstate = 0; + std::map::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() diff --git a/game/reconstructed/btl4vid.hpp b/game/reconstructed/btl4vid.hpp index 3f94129..d6857f7 100644 --- a/game/reconstructed/btl4vid.hpp +++ b/game/reconstructed/btl4vid.hpp @@ -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 dbr hulk DPLStaticChildRenderable *wreckHulk; // the 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 //===========================================================================// diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index cd05c11..8f0d4af 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -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) -- diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index f03079c..f645d3c 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -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] diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 48affcd..079c669 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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; } }