From 28df53aa311c594fb5e5f63a8b2639d4e75a81cd Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 27 Jul 2026 10:05:45 -0500 Subject: [PATCH] Frame the Winners Circle for the shape it was built for The stand was composed to be looked at from a 4:3 pod monitor. On a 16:9 canvas its platform runs out at the sides and the shot fills up with sky and void, so the podium is now pillarboxed: the scene renders into a centred 4:3 viewport with the surround left black. The projection has to use the cropped shape too, or the scene comes out squashed into the narrower viewport instead of cropped by it. RP412PODIUMASPECT overrides the ratio, 0 turns it off. The camera also came in closer, from 33 units rather than 45, and now aims slightly below the group rather than above it. That tilt is what buys back the sky above the grandstand - aiming above the group tips the camera up instead and walks the winner's spot off the bottom of the frame, which is the one position that has to be in shot. Co-Authored-By: Claude Fable 5 --- MUNGA_L4/L4VIDEO.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++- MUNGA_L4/L4VIDEO.h | 5 ++++ RP_L4/RPL4APP.cpp | 15 ++++++---- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/MUNGA_L4/L4VIDEO.cpp b/MUNGA_L4/L4VIDEO.cpp index 085972e..ae38013 100644 --- a/MUNGA_L4/L4VIDEO.cpp +++ b/MUNGA_L4/L4VIDEO.cpp @@ -1802,6 +1802,7 @@ DPLRenderer::DPLRenderer( // the next race re-reads viewangle from RPDPL.INI. mPresentationCamera = False; mPresentationFog = False; + mPresentationAspect = 0.0f; D3DXMatrixIdentity(&mPresentationView); dplMainView = NULL; dplDeathZone = NULL; @@ -2374,6 +2375,20 @@ void // tells the end-of-mission fade to stand down mPresentationFog = True; + // + // Crop to the shape the stand was composed for. 4:3 unless + // RP412PODIUMASPECT says otherwise; 0 turns it off and lets + // the podium run full width. + // + mPresentationAspect = 4.0f / 3.0f; + { + const char *aspect = getenv("RP412PODIUMASPECT"); + if (aspect != NULL) + { + mPresentationAspect = (float) atof(aspect); + } + } + // unconditional: fogUpdating is off while a vehicle drives its own // headlight fog, and the podium overrides all of that if (mDevice != NULL) @@ -6094,6 +6109,36 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte lastFrameTime = mTargetRenderTime; currentFrameTime = Now(); + // + // Pillarbox: black the whole target first, then narrow the viewport so + // everything after this - clear, scene, reticle - lands inside the crop + // and the surround stays black. Restored after Present. + // + if (mPresentationAspect > 0.0f) + { + D3DVIEWPORT9 full; + full.X = 0; + full.Y = 0; + full.Width = mPresentParams.BackBufferWidth; + full.Height = mPresentParams.BackBufferHeight; + full.MinZ = 0.0f; + full.MaxZ = 1.0f; + mDevice->SetViewport(&full); + mDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); + + DWORD cropped = (DWORD) + (mPresentParams.BackBufferHeight * mPresentationAspect); + if (cropped > mPresentParams.BackBufferWidth) + { + cropped = mPresentParams.BackBufferWidth; + } + + D3DVIEWPORT9 crop = full; + crop.Width = cropped; + crop.X = (mPresentParams.BackBufferWidth - cropped) / 2; + mDevice->SetViewport(&crop); + } + DWORD currentFog; mDevice->GetRenderState(D3DRS_FOGCOLOR, ¤tFog); hr = mDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, currentFog, 1.0f, 0); @@ -6253,6 +6298,19 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL); + // hand the whole target back + if (mPresentationAspect > 0.0f) + { + D3DVIEWPORT9 full; + full.X = 0; + full.Y = 0; + full.Width = mPresentParams.BackBufferWidth; + full.Height = mPresentParams.BackBufferHeight; + full.MinZ = 0.0f; + full.MaxZ = 1.0f; + mDevice->SetViewport(&full); + } + // // RP412RENDERDIAG=1: did the ending frame actually reach the window, and // what colour did it clear to? A black clear with a blue-violet fog set @@ -6889,11 +6947,18 @@ void DPLRenderer::SetViewAngle(Degree new_angle) viewRatio = (float) tan(view_angle / 2.0f); aspectRatio = (float) y_size / (float) x_size; + // pillarboxed shots render into a narrower viewport, so the projection + // has to use that shape or the scene comes out squashed rather than + // cropped + float projection_aspect = (mPresentationAspect > 0.0f) + ? mPresentationAspect + : ((float) x_size / (float) y_size); + D3DXMatrixIdentity(&mProjectionMatrix); D3DXMatrixPerspectiveFovLH( &mProjectionMatrix, viewAngle * (PI / 180.0f), - (float) x_size / (float) y_size, + projection_aspect, clipNear, clipFar); mProjectionMatrix(0, 0) *= -1; // handedness flip - the view is RH diff --git a/MUNGA_L4/L4VIDEO.h b/MUNGA_L4/L4VIDEO.h index 7ea6f04..4a1726c 100644 --- a/MUNGA_L4/L4VIDEO.h +++ b/MUNGA_L4/L4VIDEO.h @@ -468,6 +468,11 @@ private: D3DXMATRIX mPresentationView; Logical mPresentationCamera; Logical mPresentationFog; + // >0 pillarboxes the scene to this aspect. The Winners Circle was + // built to be looked at from a 4:3 pod monitor and its platform runs + // out at the sides of a 16:9 canvas, so the shot is cropped to the + // shape it was composed for and the surround left black. + float mPresentationAspect; void FindBestAdapterIndices(bool isWindowed); diff --git a/RP_L4/RPL4APP.cpp b/RP_L4/RPL4APP.cpp index 9e97a91..3a819b5 100644 --- a/RP_L4/RPL4APP.cpp +++ b/RP_L4/RPL4APP.cpp @@ -246,11 +246,16 @@ void // RP412PODIUMHEIGHT eye height above the group // RP412PODIUMAIM height of the aim point above the group // - // framed off the stand itself: far enough back for all eight bays and - // the numbers above them, high enough to look down the tiers - Scalar standoff = 45.0f; - Scalar height = 18.0f; - Scalar aim_lift = 2.0f; + // + // Framed off the stand itself: close enough that the platform fills the + // crop rather than trailing off into sky, high enough to look down the + // tiers, and aimed slightly below the group so the tilt buys back the + // dead sky above the grandstand. Aiming above the group tips the camera + // up and walks the winner's spot off the bottom of the frame. + // + Scalar standoff = 33.0f; + Scalar height = 20.0f; + Scalar aim_lift = -2.0f; const char *tune = getenv("RP412PODIUMSTANDOFF"); if (tune != NULL && atof(tune) != 0.0) standoff = (Scalar) atof(tune); tune = getenv("RP412PODIUMHEIGHT");