View: the INI viewangle is the HORIZONTAL field -- authentic 60x47 frustum (task #55)

Community reports said the camera looked too far back and the cockpit frames
were never this revealed; the user's hypothesis (our projection widens the
view) was exactly right.  The original Tesla projection call, preserved in a
comment at DPLRenderer::SetView, is

  dpl_SetViewProjection(view, -1, -aspect, +1, +aspect, 1/tan(viewangle/2))

i.e. BTDPL.INI's viewangle=60.0 is the HORIZONTAL field of view with the
vertical derived by aspect: the pod's frustum was 60 x 46.8 deg at 4:3.  The
port fed the 60 to D3DXMatrixPerspectiveFovRH as the VERTICAL fov -> 75 x 60
at 4:3 (worse on widescreen): the world rendered ~25% farther away and far
more canopy was visible than the pod ever showed.

Fix: BTFovYFromHorizontal(viewangle, aspect) at all five projection sites
(main, per-frame resize, sky pass, WM_SIZE rebuild, fallback) -- the
horizontal field stays the authentic 60 deg at any window shape.  The aim
boresight reads the live projection (gBTAimP22) and adapts; combat
re-verified (kill chain, no NaN).  Cockpits now show the frames at the
pod's framing: viewports dominant, frames at the periphery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 09:17:07 -05:00
co-authored by Claude Fable 5
parent 96b8892e7c
commit 27959990ea
2 changed files with 48 additions and 11 deletions
+13
View File
@@ -147,6 +147,19 @@ Raptor / Firestarter / Blackjack have `_COP` meshes, SKLs and gauge configs but
names: `avatar bhk1 loki madcat owens sunder thor vulture` (+ variants ava1/lok1/lok2/mad1/mad2/
own1/snd1/thr1/vul1/blkhawk).
## The authentic view field (FOV) — viewangle is HORIZONTAL [T1]
`BTDPL.INI [dpl_defaults] viewangle=60.0` is the **horizontal** field of view: the original Tesla
call (preserved in the DPLRenderer::SetView comment, L4VIDEO.cpp) is
`dpl_SetViewProjection(view, -1, -aspect, +1, +aspect, 1/tan(viewangle/2))` — horizontal
half-extent 1.0, vertical scaled by aspect. Authentic frustum at 4:3 = **60° × 46.8°**. The
port had fed the 60 to D3DX as the VERTICAL fov (75° × 60° at 4:3, worse on widescreen) — the
world read ~25% farther away ("camera too far back" reports) and the canopy frames were
massively over-revealed. Fixed (`BTFovYFromHorizontal`, all 5 projection sites incl. sky +
resize): the horizontal field stays 60° at any window shape. The aim/pick feed reads the live
projection (`gBTAimP22`) so targeting adapts automatically (combat re-verified). Credit: a
user-community report ("frames were never this revealed") + the resolution/FOV hypothesis.
## Diagnostics / env gates
`BT_FORCE_MODEL=<name>` (force the player mech: madcat/owens/sunder/…; btl4mssn.cpp),
+35 -11
View File
@@ -111,6 +111,23 @@ static float gBTAimCamY[3] = {0, 1, 0}; // camera up (LookAtRH yaxis)
static float gBTAimCamZ[3] = {0, 0, 1}; // camera BACK (LookAtRH zaxis; view dir = -Z)
static float gBTAimP11 = 0.0f; // proj._11 (x scale; carries the aspect)
static float gBTAimP22 = 0.0f; // proj._22 (y scale = 1/tan(fovY/2))
// AUTHENTIC VIEW FIELD (task #55 FOV fix): the INI 'viewangle' (BTDPL.INI
// [dpl_defaults] viewangle=60.0) is the HORIZONTAL field of view -- the
// original Tesla call (kept in the comment at DPLRenderer::SetView below) is
// dpl_SetViewProjection(view, -1, -aspect, +1, +aspect, 1/tan(viewangle/2))
// i.e. horizontal half-extent 1.0 at plane distance 1/tan(viewangle/2), with
// the VERTICAL extent scaled by aspect. Our port fed the 60 to D3DX as the
// VERTICAL fov -> ~75x60 deg instead of the pod's 60x46.8 (4:3): the world
// read ~25%% farther away ("camera too far back") and the cockpit canopy was
// massively over-revealed. Derive fovY from the horizontal angle at the live
// aspect so the horizontal field stays the authentic 60 at any window shape.
static float BTFovYFromHorizontal(float viewAngleDeg, float aspect)
{
if (aspect <= 0.0f)
aspect = 4.0f / 3.0f;
return 2.0f * atanf(tanf(viewAngleDeg * 0.5f * (PI / 180.0f)) / aspect);
}
static float gBTAimVpW = 0.0f; // D3D viewport (backbuffer) size --
static float gBTAimVpH = 0.0f; // the dpl2d frame's pixel space
static int gBTAimCamValid = 0;
@@ -4132,8 +4149,11 @@ void
// Use the matching RH projection so the mech becomes visible.
// BT (task #20): honor the live window aspect if the window has been resized
// (gWindowAspect, top of file; set by L4NotifyWindowResized on WM_SIZE).
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix, viewAngle * (PI/180.0f),
gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size, clipNear, clipFar);
{
const float projAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, projAspect), projAspect, clipNear, clipFar);
}
//mProjectionMatrix(0, 0) *= -1;
mDecalEpsilon = 0.0000005f;
@@ -7371,7 +7391,7 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
{
appliedAspect = gWindowAspect;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
viewAngle * (PI / 180.0f), appliedAspect, clipNear, clipFar);
BTFovYFromHorizontal(viewAngle, appliedAspect), appliedAspect, clipNear, clipFar);
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
@@ -7669,9 +7689,11 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&currentFogNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&currentFogFar)));
D3DXMATRIX skyProj;
D3DXMatrixPerspectiveFovRH(&skyProj, viewAngle * (PI / 180.0f),
gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size,
clipNear, 9000.0f);
{
const float skyAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&skyProj,
BTFovYFromHorizontal(viewAngle, skyAspect), skyAspect, clipNear, 9000.0f);
}
mDevice->SetTransform(D3DTS_PROJECTION, &skyProj);
}
else
@@ -8564,8 +8586,8 @@ void
<< viewAngle << ")\n" << std::flush;
return; // projection not built yet; the builder below picks it up
}
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix, viewAngle * (PI / 180.0f),
gWindowAspect, clipNear, clipFar);
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, gWindowAspect), gWindowAspect, clipNear, clipFar);
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
if (mDevice != NULL)
@@ -8611,9 +8633,11 @@ void
viewRatio = (float)tan(viewAngle * 0.5f * (PI / 180.0f));
D3DXMatrixIdentity(&mProjectionMatrix);
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix, viewAngle * (PI / 180.0f),
gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size,
clipNear, clipFar);
{
const float fbAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, fbAspect), fbAspect, clipNear, clipFar);
}
mDecalEpsilon = 0.0000005f;
mDecalProjectionMatrix = mProjectionMatrix;