Cockpit surround: composite the gauges around the 3D view (default desktop layout)
Under BT_DEV_GAUGES the DEFAULT is now the pod-faithful cockpit surround: the 3D world CENTERED with the six gauge surfaces composited AROUND it in the single main window at 1/2 native scale, plus clickable RIO button lamps. Iterated as a visual reference with the user, then landed in the engine. - L4VB16.cpp/.h: BTCockpitLayout + BTCockpitComputeLayout (layout single source of truth, computed from the backbuffer canvas); BTDrawCockpitPanels (band fill -> button lamp quads -> 9-entry surface loop, phosphor-green mono / amber palette sec, Eng-sibling slot map -> GDI-atlas flight labels; D3DSBT_ALL state-block isolation = the floating-rocks trap); BTCockpitMouseDown/Up (client->bb hit-test + glass press/release/right-latch contract); BTApplyWorldViewport + DevGaugeDocked cockpit branches; shared BTLampBrightnessOf inline. Hooked at the top of BTDrawGaugeInset (before BT_SHOT, so shots include it). - btl4main.cpp: glass preset (cockpit default, per-display panels opt-in); mode resolution -> gBTGaugeCockpit with work-area-clamped window sizing (-res = view size); WndProc WM_L/RBUTTON routing. - L4VIDEO.cpp: BTWorldAspectOf cockpit branch (view rect on-screen aspect). - L4VIDRND.cpp: CameraShipHUDRenderable::Render made viewport-relative. Buttons = the L4GLASSWIN address banks x0.5 (Heat 0x2F, Mfd2 0x27, Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red; radar rails 0x10-0x1B yellow; flight 0x38-0x47 blue, labeled). Full rect is the hit target; the surface draws over it so only the lamp edge shows. Precedence: BT_GLASS_PANELS=1 stands cockpit down > BT_COCKPIT=1 > _WINDOW/_DOCK opt-out > cockpit default; BT_COCKPIT=0 = dock-bottom. Green tunable via BT_COCKPIT_TINT. Renders in ALL builds; only the PadRIO click/lamp seam is BT_GLASS-gated. Verified (awaiting playtest): both build/ + build-glass/ compile (0 error C); glass BT_SHOT matches the mockup; 500-click storm during a live mission survived (Gitea #18 Receiver-gap fix holds); BT_COCKPIT=0 dock-bottom unregressed; BT_GLASS_PANELS=1 stands cockpit down; pod build renders the panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5ba5697a08
commit
129b27499d
+34
-5
@@ -72,11 +72,40 @@ TestInstance/BecameActive/Execute. **Gotchas that bite EVERY widget:**
|
||||
(`RecomputeCondenserValves`, FUN_0049f788, was a no-op stub). [T2]
|
||||
|
||||
## Dev composite (off-pod)
|
||||
`BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces in a separate 960×384 window (bit-plane masks
|
||||
over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from
|
||||
`SETENV.BAT`/`L4GAUGE` on the real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`,
|
||||
intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a
|
||||
different bit-plane (0x00C0). [T2]
|
||||
`BT_DEV_GAUGES` renders the 6 pod [[MFD]] surfaces (bit-plane masks over one shared `SVGA16`
|
||||
pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they come from `SETENV.BAT`/`L4GAUGE` on the
|
||||
real multi-adapter hardware (`FindBestAdapterIndices`/`BuildWindows`, intact). The `overlay` port
|
||||
(SectorDisplay lives there) shares the `sec` physical surface via a different bit-plane (0x00C0). [T2]
|
||||
|
||||
## Cockpit surround (the DEFAULT desktop layout, 2026-07-20) [T2]
|
||||
Under `BT_DEV_GAUGES`, the DEFAULT is now the **cockpit surround** (`L4VB16.cpp`
|
||||
`BTDrawCockpitPanels`): the 3D world view CENTERED with the six gauge surfaces composited AROUND
|
||||
it in the SINGLE main window at **½ native scale** (MFD 320×240, radar 240×320 portrait), plus
|
||||
clickable RIO button lamps — the pod-faithful arrangement (Coolant UL, Mfd2 upper-center, Comm/Hot
|
||||
Box UR, Mfd1/Mfd3 lower flanks, secondary/radar flush below). The pod monitors physically clip the
|
||||
eyeport, so the corner MFDs + top-center overlap the view edges. Mono MFDs are tinted **phosphor
|
||||
green** (radar keeps its **amber** palette); the world viewport is the centered view rect
|
||||
(`BTApplyWorldViewport` cockpit branch) so the reticle/HUD follow for free (dpl2d maps through the
|
||||
viewport), and `gWindowAspect` = the view rect's on-screen aspect (`BTWorldAspectOf` cockpit branch).
|
||||
- **Layout = single source of truth**: `BTCockpitLayout` / `BTCockpitComputeLayout(canvasW,canvasH)`
|
||||
in `l4vb16.h`/`L4VB16.cpp`, computed FROM the backbuffer (canvas) size — consumed by the window
|
||||
sizing (`btl4main.cpp`), the world viewport, the panel/button draw, the aspect, and the mouse
|
||||
hit-test. Constants: `SCALE=0.5, OVL=44 (corner overlap), LAMP=16 (protruding lamp edge),
|
||||
REDCELL=64 (hidden hit depth), RAILW=26`; `canvas = view + (552, 548)`.
|
||||
- **Buttons** = the L4GLASSWIN geometry ×0.5 with the same address banks (Heat 0x2F, Mfd2 0x27,
|
||||
Comm 0x37, Mfd1 0x0F, Mfd3 0x07 red 8-btn; radar rails 0x10-0x15/0x18-0x1D + bottom
|
||||
{0x16,0x17,0x1F,0x1E} yellow; flight 0x38-0x3F/0x40-0x47 blue, labeled). Full rect = hit target;
|
||||
the surface draws OVER it so only the lamp edge shows (the PaintGlass painter trick). Mouse:
|
||||
main WndProc `WM_L/RBUTTON` → `BTCockpitMouseDown/Up` (client→bb map, glass press/release/right-
|
||||
latch contract) → `PadRIO::SetScreenButton` (`#ifdef BT_GLASS`; dim/no-op in pod builds). Lamp
|
||||
brightness = `BTLampBrightnessOf` (shared inline in l4vb16.h) over `PadRIO::GetLampState`.
|
||||
- **Env / precedence** (resolved once in btl4main → `gBTGaugeCockpit`): `BT_GLASS_PANELS=1`
|
||||
(Cyd's per-display windows) stands cockpit down > explicit `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW`
|
||||
(separate window) / `BT_DEV_GAUGES_DOCK` (legacy inset) opt-out > **cockpit default**.
|
||||
`BT_COCKPIT=0` forces the dock-bottom strip. `-res W H` = the WORLD VIEW size (canvas clamped to
|
||||
the work area). Green tint tunable via `BT_COCKPIT_TINT=RRGGBB` (default `0x27E8`). Labels are a
|
||||
lazy GDI-baked MANAGED atlas (survives device reset). Renders in ALL builds; only the PadRIO
|
||||
click/lamp seam is BT_GLASS-gated. Full detail: `docs/GAUGE_COMPOSITE.md`.
|
||||
|
||||
## MP gauge-window FREEZE = dangling bindings + permanent SEH disable (Gitea #12, 2026-07-19) [T2 log-convicted]
|
||||
The live-MP "dev-gauges window froze entirely mid-session" incident (issue #12) is NOT a
|
||||
|
||||
@@ -807,3 +807,52 @@ screenshots silently omitted the gauge panel (audit runs 1-2 captured cockpit-on
|
||||
Moved the capture after the inset (L4VIDEO.cpp) — dock shots again show what the screen shows.
|
||||
Also noted: BT_DEV_GAUGES_DOCK alone does NOT enable the composite — DevGaugeComposite() gates
|
||||
on BT_DEV_GAUGES (L4VB16.cpp:105); set BOTH.
|
||||
|
||||
---
|
||||
|
||||
## Cockpit surround — the default desktop layout (2026-07-20)
|
||||
|
||||
Replaces dock-bottom as the DEFAULT under `BT_DEV_GAUGES`: the 3D world CENTERED with the six
|
||||
gauge surfaces composited AROUND it in the single main window at ½ native scale, plus clickable
|
||||
RIO button lamps. Iterated as a visual reference with the user (scratchpad/cockpit_pod.py) then
|
||||
landed in the engine.
|
||||
|
||||
**Files.**
|
||||
- `engine/MUNGA_L4/l4vb16.h` — `BTCockpitLayout`/`BTCockpitBtn` structs, the compute/draw/mouse
|
||||
declarations, and `BTLampBrightnessOf` (shared inline lamp decode, dedup of L4GLASSWIN's).
|
||||
- `engine/MUNGA_L4/L4VB16.cpp` — `gBTGaugeCockpit`/`gBTCockpitCanvasW/H`; `BTCockpitCanvasFor` +
|
||||
`BTCockpitComputeLayout` (layout single source of truth); `BTDrawCockpitPanels` (band fill →
|
||||
button lamp quads → 9-entry surface loop with green tint / palette sec + Eng-sibling slot map →
|
||||
GDI-atlas flight labels; wrapped in a `D3DSBT_ALL` state block = the floating-rocks isolation);
|
||||
`BTCockpitMouseDown/Up` (client→bb hit-test + glass press/release/right-latch contract);
|
||||
`BTApplyWorldViewport` + `DevGaugeDocked` cockpit branches. Hooked at the top of
|
||||
`BTDrawGaugeInset` (before BT_SHOT, so shots include it).
|
||||
- `game/btl4main.cpp` — glass preset (cockpit default, panels opt-in), mode resolution +
|
||||
work-area-clamped window sizing (`-res` = the view size), WndProc `WM_L/RBUTTON` routing.
|
||||
- `engine/MUNGA_L4/L4VIDEO.cpp` — `BTWorldAspectOf` cockpit branch (view rect on-screen aspect).
|
||||
- `engine/MUNGA_L4/L4VIDRND.cpp` — `CameraShipHUDRenderable::Render` made viewport-relative
|
||||
(camera-seat banner + ranking window land over the view, not clipped into the surround bands).
|
||||
|
||||
**Layout math.** `SCALE=0.5; MFD 320×240, radar 240×320; OVL=44 (corner overlap into the view);
|
||||
LAMP=16 (visible lamp edge, user asked for larger than glass's 10); REDCELL=64 (hidden hit depth);
|
||||
RAILW=26; sideBand=276, topBand=212, bottomBand=336; canvas=(viewW+552, viewH+548).` Corner MFDs
|
||||
overlap the view corner by OVL both axes; Mfd2 drops 25% of its height into the top; sec flush
|
||||
below (view bottom clean). Buttons: the full rect is the click target, the surface draws over it so
|
||||
only the LAMP edge shows (PaintGlass painter trick). Address banks = L4GLASSWIN
|
||||
BuildMfd/BuildRadar/BuildFlight.
|
||||
|
||||
**Gating precedence** (resolved once → `gBTGaugeCockpit`): `BTGlassPanelsActive` (BT_GLASS_PANELS)
|
||||
> `BT_COCKPIT=1` > `BT_DEV_GAUGES_WINDOW` / `BT_DEV_GAUGES_DOCK` > **cockpit default**. `BT_COCKPIT=0`
|
||||
= dock-bottom. `DevGaugeDocked()` includes cockpit so the separate window stands down. Green tint
|
||||
`BT_COCKPIT_TINT=RRGGBB` (default 0x27E8 ≈ rgb(33,255,66)); radar stays palette amber.
|
||||
|
||||
**Pod-build seam.** Panels + dim lamps render in ALL builds; only `PadRIO::SetScreenButton/
|
||||
GetLampState` are `#ifdef BT_GLASS` (via `CkEmit`/`CkLampState`). Pod build: cockpit draws, clicks
|
||||
no-op. Labels: lazy GDI DIB → MANAGED A8R8G8B8 texture cache (survives device reset).
|
||||
|
||||
**Verified (2026-07-20, awaiting human playtest):** both build/ (BT_GLASS off) and build-glass/
|
||||
compile (0 error C). Glass BT_SHOT matches the mockup (green MFDs, amber radar, red/yellow/blue
|
||||
lamps, labeled flight blocks, reticle centered on target). 500-click PostMessage storm during a
|
||||
live mission SURVIVED (no AV — the Gitea #18 Receiver-gap fix holds); 20 hits logged with correct
|
||||
addresses/coords. BT_COCKPIT=0 dock-bottom pixel-unregressed. BT_GLASS_PANELS=1 stands cockpit
|
||||
down (Cyd's windows up). Pod build renders the panels.
|
||||
|
||||
+516
-2
@@ -8,6 +8,7 @@
|
||||
|
||||
#ifdef BT_GLASS
|
||||
#include "l4glasswin.h" // BTGlassPanelsActive(): the per-display windows own the surfaces
|
||||
#include "l4padrio.h" // PadRIO::GetLampState / SetScreenButton (cockpit lamps + clicks)
|
||||
#endif
|
||||
// BTResolveGaugeRenderer()/BTResolveMainWindow() (l4vb16.h) are DEFINED in
|
||||
// game/btl4main.cpp off the real app/window pointers -- NOT here: an engine TU
|
||||
@@ -139,12 +140,25 @@ int BTGaugeStripHeightFor(int width)
|
||||
// Called by the main renderer right after BeginScene; no-op unless dock-bottom.
|
||||
void BTApplyWorldViewport(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (!gBTGaugeDockBottom || device == NULL) return;
|
||||
if (device == NULL) return;
|
||||
if (!gBTGaugeDockBottom && !gBTGaugeCockpit) return;
|
||||
IDirect3DSurface9 *rt = NULL;
|
||||
if (FAILED(device->GetRenderTarget(0, &rt)) || rt == NULL) return;
|
||||
D3DSURFACE_DESC d;
|
||||
rt->GetDesc(&d);
|
||||
rt->Release();
|
||||
if (gBTGaugeCockpit)
|
||||
{
|
||||
// COCKPIT: the world renders in the CENTERED view rect; the surround
|
||||
// bands + panels draw on top afterwards (BTDrawCockpitPanels). The
|
||||
// reticle/HUD dpl2d pass maps through this viewport for free.
|
||||
BTCockpitLayout L;
|
||||
BTCockpitComputeLayout((int)d.Width, (int)d.Height, &L);
|
||||
if (L.viewW <= 0 || L.viewH <= 0) return;
|
||||
D3DVIEWPORT9 vp = { (DWORD)L.viewX, (DWORD)L.viewY, (DWORD)L.viewW, (DWORD)L.viewH, 0.0f, 1.0f };
|
||||
device->SetViewport(&vp);
|
||||
return;
|
||||
}
|
||||
DWORD stripH = (DWORD)BTGaugeStripHeightFor((int)d.Width);
|
||||
if (stripH == 0 || stripH >= d.Height) return;
|
||||
D3DVIEWPORT9 vp = { 0, 0, d.Width, d.Height - stripH, 0.0f, 1.0f };
|
||||
@@ -158,7 +172,7 @@ static bool DevGaugeDocked()
|
||||
{
|
||||
static int v = -1;
|
||||
if (v < 0) v = (getenv("BT_DEV_GAUGES_DOCK") != NULL) ? 1 : 0;
|
||||
return v != 0 || gBTGaugeDockBottom != 0;
|
||||
return v != 0 || gBTGaugeDockBottom != 0 || gBTGaugeCockpit != 0;
|
||||
}
|
||||
|
||||
// GLASS per-display windows (L4GLASSWIN) own the instrument surfaces in their OWN
|
||||
@@ -173,6 +187,147 @@ static bool GlassPanelsOwnSurfaces()
|
||||
#endif
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// COCKPIT SURROUND (BT_COCKPIT) -- layout single source of truth.
|
||||
//===========================================================================//
|
||||
int gBTGaugeCockpit = 0; // mode select (resolved once by btl4main)
|
||||
int gBTCockpitCanvasW = 0; // backbuffer canvas dims (set by btl4main; the
|
||||
int gBTCockpitCanvasH = 0; // aspect calc needs them after a client resize)
|
||||
|
||||
// Layout constants (backbuffer px). MFD = 640x480 native x 0.5; radar = 480x640
|
||||
// portrait x 0.5. The lamp EDGE (kCkLamp) protrudes past the surface; the FULL
|
||||
// button rect reaches kCkRedCell INTO the display (hit-only, covered by the
|
||||
// surface). User asked for larger/more-clickable buttons than the glass windows'
|
||||
// 10px edge -> kCkLamp 16.
|
||||
enum {
|
||||
kCkMFDW = 320, kCkMFDH = 240,
|
||||
kCkRADW = 240, kCkRADH = 320,
|
||||
kCkOVL = 44, // (int)(0.14f * 320): corner overlap into the view
|
||||
kCkLamp = 16, // visible protruding lamp edge
|
||||
kCkRedCell = 64, // glass RedCellH(128) * 0.5: hidden hit depth into the MFD
|
||||
kCkRailW = 26, // radar side-rail width
|
||||
kCkGap = 2,
|
||||
kCkSideBand = kCkMFDW - kCkOVL, // 276
|
||||
kCkTopBand = (kCkMFDH - kCkOVL) + kCkLamp, // 212
|
||||
kCkBotBand = kCkRADH + kCkLamp // 336 (radar flush below view; lamps protrude)
|
||||
};
|
||||
|
||||
// Canvas = view region + the surround bands.
|
||||
void BTCockpitCanvasFor(int viewW, int viewH, int *canvasW, int *canvasH)
|
||||
{
|
||||
if (canvasW) *canvasW = viewW + 2 * kCkSideBand;
|
||||
if (canvasH) *canvasH = viewH + kCkTopBand + kCkBotBand;
|
||||
}
|
||||
|
||||
// The five MFD banks: {cockpit surface slot, high address}. Top row = bankHi-i
|
||||
// (descending), bottom row = bankHi-4-i (L4GLASSWIN BuildMfd order + the
|
||||
// BTGlassPanels_Create address assignment).
|
||||
static const struct { int slot, bankHi; } kCkMfdBank[5] =
|
||||
{
|
||||
{ 0, 0x2F }, // Heat (UL)
|
||||
{ 1, 0x27 }, // Mfd2 (UC)
|
||||
{ 5, 0x37 }, // Comm (UR)
|
||||
{ 2, 0x0F }, // Mfd1 (LL)
|
||||
{ 3, 0x07 }, // Mfd3 (LR)
|
||||
};
|
||||
|
||||
static void CkPushBtn(BTCockpitLayout *L, int addr, int x, int y, int w, int h,
|
||||
int colorClass, int inert, const char *label)
|
||||
{
|
||||
if (L->buttonCount >= (int)(sizeof(L->buttons) / sizeof(L->buttons[0]))) return;
|
||||
BTCockpitBtn &b = L->buttons[L->buttonCount++];
|
||||
b.address = addr; b.x = x; b.y = y; b.w = w; b.h = h;
|
||||
b.colorClass = colorClass; b.inert = inert; b.label = label;
|
||||
}
|
||||
|
||||
void BTCockpitComputeLayout(int canvasW, int canvasH, BTCockpitLayout *out)
|
||||
{
|
||||
BTCockpitLayout *L = out;
|
||||
memset(L, 0, sizeof(*L));
|
||||
L->canvasW = canvasW;
|
||||
L->canvasH = canvasH;
|
||||
int viewW = canvasW - 2 * kCkSideBand;
|
||||
int viewH = canvasH - kCkTopBand - kCkBotBand;
|
||||
if (viewW < 64) viewW = 64;
|
||||
if (viewH < 64) viewH = 64;
|
||||
int vx = kCkSideBand, vy = kCkTopBand;
|
||||
L->viewX = vx; L->viewY = vy; L->viewW = viewW; L->viewH = viewH;
|
||||
|
||||
// Surface dest rects (slots: 0 Heat, 1 Mfd2, 2 Mfd1, 3 Mfd3, 4 sec, 5 Comm).
|
||||
// Corner MFDs overlap the view corner by kCkOVL both axes; Mfd2 drops 25% of
|
||||
// its height into the top; sec is flush below (view bottom edge clean).
|
||||
int cx = vx + viewW / 2;
|
||||
// Heat TL
|
||||
L->surfX[0] = vx - kCkMFDW + kCkOVL; L->surfY[0] = vy - kCkMFDH + kCkOVL;
|
||||
L->surfW[0] = kCkMFDW; L->surfH[0] = kCkMFDH;
|
||||
// Mfd2 top-center (25% into view)
|
||||
L->surfX[1] = cx - kCkMFDW / 2; L->surfY[1] = vy - (int)(0.75f * kCkMFDH);
|
||||
L->surfW[1] = kCkMFDW; L->surfH[1] = kCkMFDH;
|
||||
// Mfd1 BL
|
||||
L->surfX[2] = vx - kCkMFDW + kCkOVL; L->surfY[2] = vy + viewH - kCkOVL;
|
||||
L->surfW[2] = kCkMFDW; L->surfH[2] = kCkMFDH;
|
||||
// Mfd3 BR
|
||||
L->surfX[3] = vx + viewW - kCkOVL; L->surfY[3] = vy + viewH - kCkOVL;
|
||||
L->surfW[3] = kCkMFDW; L->surfH[3] = kCkMFDH;
|
||||
// sec center, flush below
|
||||
L->surfX[4] = cx - kCkRADW / 2; L->surfY[4] = vy + viewH;
|
||||
L->surfW[4] = kCkRADW; L->surfH[4] = kCkRADH;
|
||||
// Comm TR
|
||||
L->surfX[5] = vx + viewW - kCkOVL; L->surfY[5] = vy - kCkMFDH + kCkOVL;
|
||||
L->surfW[5] = kCkMFDW; L->surfH[5] = kCkMFDH;
|
||||
|
||||
// --- MFD red buttons: 4 top (bankHi-i) + 4 bottom (bankHi-4-i) ---
|
||||
int slotW = kCkMFDW / 4; // 80
|
||||
for (int m = 0; m < 5; m++)
|
||||
{
|
||||
int s = kCkMfdBank[m].slot, hi = kCkMfdBank[m].bankHi;
|
||||
int sx = L->surfX[s], sy = L->surfY[s];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int bx = sx + i * slotW;
|
||||
CkPushBtn(L, hi - i, bx, sy - kCkLamp, slotW - kCkGap, kCkRedCell + kCkLamp, 0, 0, 0);
|
||||
CkPushBtn(L, hi - 4 - i, bx, sy + kCkMFDH - kCkRedCell, slotW - kCkGap, kCkRedCell + kCkLamp, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// --- radar yellow rails (6/side) + 4 bottom cells ---
|
||||
{
|
||||
int rx = L->surfX[4], ry = L->surfY[4];
|
||||
int slotH = kCkRADH / 6;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
int y = ry + i * slotH;
|
||||
CkPushBtn(L, 0x10 + i, rx - kCkLamp, y, kCkLamp + kCkRailW, slotH - kCkGap, 1, 0, 0);
|
||||
CkPushBtn(L, 0x18 + i, rx + kCkRADW - kCkRailW, y, kCkRailW + kCkLamp, slotH - kCkGap, 1, 0, 0);
|
||||
}
|
||||
static const int bottomAddr[4] = { 0x16, 0x17, 0x1F, 0x1E };
|
||||
int botLeft = rx + kCkRailW, botSpan = kCkRADW - 2 * kCkRailW, botW = botSpan / 4;
|
||||
for (int i = 0; i < 4; i++)
|
||||
CkPushBtn(L, bottomAddr[i], botLeft + i * botW, ry + kCkRADH - 14, botW - kCkGap, 14 + kCkLamp, 1, 0, 0);
|
||||
}
|
||||
|
||||
// --- flight blue blocks: THROTTLE/AUX (0x38-0x3F) under Mfd1, JOYSTICK
|
||||
// (0x40-0x47) under Mfd3 -- two 4x2 grids, fully visible + labeled ---
|
||||
{
|
||||
static const char *thrLabel[8] = { 0, 0, 0, 0, 0, "Panic", 0, "Throttle" }; // 0x38..0x3F
|
||||
static const int thrInert[8] = { 1, 1, 1, 1, 1, 0, 1, 0 };
|
||||
static const char *joyLabel[8] = { "Main", "Hat Bk", "Hat Up", "Hat R",
|
||||
"Hat L", "Pinky", "Middle", "Upper" }; // 0x40..0x47
|
||||
const int cw = 68, ch = 30, g = 4;
|
||||
int blockW = 4 * cw + 3 * g; // 284
|
||||
int thrX = L->surfX[2], thrY = L->surfY[2] + kCkMFDH + 6;
|
||||
int joyX = L->surfX[3] + kCkMFDW - blockW, joyY = L->surfY[3] + kCkMFDH + 6;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int c = i % 4, r = i / 4;
|
||||
CkPushBtn(L, 0x38 + i, thrX + c * (cw + g), thrY + r * (ch + g), cw, ch, 2,
|
||||
thrInert[i], thrLabel[i] ? thrLabel[i] : "");
|
||||
CkPushBtn(L, 0x40 + i, joyX + c * (cw + g), joyY + r * (ch + g), cw, ch, 2,
|
||||
0, joyLabel[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// DEV-COMPOSITE (option B) -- draw the woken gauge renderer's SECONDARY/radar
|
||||
// surface as an inset in the MAIN window. The gauge widgets have already
|
||||
@@ -495,6 +650,358 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw,
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// COCKPIT SURROUND -- draw the surround bands + gauge panels + button lamps over
|
||||
// the CENTERED world view. Hooked into BTDrawGaugeInset (before BT_SHOT), so it
|
||||
// draws into the main backbuffer and screenshots include it. Mirrors
|
||||
// BTDrawGaugeSurfaces' device-state isolation (the "floating cavern rocks" trap).
|
||||
//===========================================================================//
|
||||
|
||||
// Shared click state (also written by BTCockpitMouseDown/Up below).
|
||||
static int gCkPressed = -1;
|
||||
static unsigned char gCkLatched[128] = { 0 };
|
||||
|
||||
// The mono MFD phosphor tint (R5G6B5); default green, env BT_COCKPIT_TINT=RRGGBB.
|
||||
static int CkTint()
|
||||
{
|
||||
static int t = -1;
|
||||
if (t < 0)
|
||||
{
|
||||
const char *e = getenv("BT_COCKPIT_TINT");
|
||||
if (e != NULL && strlen(e) >= 6)
|
||||
{
|
||||
unsigned int rgb = (unsigned int)strtoul(e, NULL, 16);
|
||||
int r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF;
|
||||
t = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
||||
}
|
||||
else
|
||||
t = 0x27E8; // ~rgb(33,255,66) phosphor green
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// Glass-only RIO seam: lamp state feeds the draw; a click emits a button. No-op
|
||||
// (dim / ignored) in a pod build, where PadRIO isn't compiled.
|
||||
static int CkLampState(int addr)
|
||||
{
|
||||
#ifdef BT_GLASS
|
||||
return PadRIO::GetLampState(addr);
|
||||
#else
|
||||
(void)addr; return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Colored-quad (untextured DIFFUSE) draw state -- set once, then CkFill per rect.
|
||||
static void CkColorState(LPDIRECT3DDEVICE9 dev)
|
||||
{
|
||||
dev->SetTexture(0, NULL);
|
||||
dev->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
|
||||
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
|
||||
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
|
||||
dev->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
dev->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
}
|
||||
|
||||
static void CkFill(LPDIRECT3DDEVICE9 dev, int x, int y, int w, int h, D3DCOLOR c)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
struct V { float x, y, z, rhw; D3DCOLOR c; };
|
||||
float fx = (float)x, fy = (float)y, fw = (float)w, fh = (float)h;
|
||||
V q[4] = {
|
||||
{ fx, fy, 0.0f, 1.0f, c },
|
||||
{ fx + fw, fy, 0.0f, 1.0f, c },
|
||||
{ fx + fw, fy + fh, 0.0f, 1.0f, c },
|
||||
{ fx, fy + fh, 0.0f, 1.0f, c },
|
||||
};
|
||||
dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, q, sizeof(V));
|
||||
}
|
||||
|
||||
// Lamp fill + border for a button, from the glass PaintGlass color ramps.
|
||||
static void CkLampColors(int colorClass, int shade, int inert, D3DCOLOR *fill, D3DCOLOR *border)
|
||||
{
|
||||
if (colorClass == 1) // yellow (radar)
|
||||
{
|
||||
*border = D3DCOLOR_XRGB(245, 210, 60);
|
||||
*fill = (shade == 3) ? D3DCOLOR_XRGB(245, 210, 60)
|
||||
: (shade != 0) ? D3DCOLOR_XRGB(140, 118, 38)
|
||||
: D3DCOLOR_XRGB(70, 60, 24);
|
||||
}
|
||||
else if (colorClass == 2) // blue (flight)
|
||||
{
|
||||
if (inert)
|
||||
{
|
||||
*border = D3DCOLOR_XRGB(90, 100, 122);
|
||||
*fill = D3DCOLOR_XRGB(44, 48, 58);
|
||||
return;
|
||||
}
|
||||
*border = D3DCOLOR_XRGB(205, 228, 255);
|
||||
*fill = (shade == 3) ? D3DCOLOR_XRGB(205, 228, 255)
|
||||
: (shade != 0) ? D3DCOLOR_XRGB(110, 135, 180)
|
||||
: D3DCOLOR_XRGB(70, 86, 120);
|
||||
}
|
||||
else // red (MFD)
|
||||
{
|
||||
*border = D3DCOLOR_XRGB(230, 70, 70);
|
||||
*fill = (shade == 3) ? D3DCOLOR_XRGB(230, 70, 70)
|
||||
: (shade != 0) ? D3DCOLOR_XRGB(120, 50, 50)
|
||||
: D3DCOLOR_XRGB(64, 40, 40);
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy GDI-baked label textures (MANAGED A8R8G8B8, survive device reset) for the
|
||||
// blue flight blocks -- keyed by string. ~16 short strings.
|
||||
struct CkLabelTex { char str[16]; LPDIRECT3DTEXTURE9 tex; int w, h; };
|
||||
static CkLabelTex gCkLabels[32];
|
||||
static int gCkLabelCount = 0;
|
||||
static HFONT gCkFont = NULL;
|
||||
|
||||
static LPDIRECT3DTEXTURE9 CkGetLabel(LPDIRECT3DDEVICE9 dev, const char *str, int *ow, int *oh)
|
||||
{
|
||||
for (int i = 0; i < gCkLabelCount; i++)
|
||||
if (strcmp(gCkLabels[i].str, str) == 0)
|
||||
{ if (ow) *ow = gCkLabels[i].w; if (oh) *oh = gCkLabels[i].h; return gCkLabels[i].tex; }
|
||||
if (gCkLabelCount >= 32) return NULL;
|
||||
|
||||
if (gCkFont == NULL)
|
||||
gCkFont = CreateFontA(-11, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET,
|
||||
0, 0, ANTIALIASED_QUALITY, 0, "Segoe UI");
|
||||
HDC dc = CreateCompatibleDC(NULL);
|
||||
HFONT oldFont = (HFONT)SelectObject(dc, gCkFont);
|
||||
SIZE sz; GetTextExtentPoint32A(dc, str, (int)strlen(str), &sz);
|
||||
int tw = sz.cx + 2, th = sz.cy + 2;
|
||||
if (tw < 1) tw = 1;
|
||||
if (th < 1) th = 1;
|
||||
|
||||
BITMAPINFO bmi; memset(&bmi, 0, sizeof(bmi));
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = tw; bmi.bmiHeader.biHeight = -th; // top-down
|
||||
bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB;
|
||||
void *bits = NULL;
|
||||
HBITMAP dib = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||
HBITMAP oldBm = (HBITMAP)SelectObject(dc, dib);
|
||||
RECT r = { 0, 0, tw, th };
|
||||
FillRect(dc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SetTextColor(dc, RGB(255, 255, 255));
|
||||
TextOutA(dc, 1, 1, str, (int)strlen(str));
|
||||
GdiFlush();
|
||||
|
||||
LPDIRECT3DTEXTURE9 tex = NULL;
|
||||
dev->CreateTexture(tw, th, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &tex, NULL);
|
||||
if (tex != NULL && bits != NULL)
|
||||
{
|
||||
D3DLOCKED_RECT lr;
|
||||
if (SUCCEEDED(tex->LockRect(0, &lr, NULL, 0)))
|
||||
{
|
||||
for (int y = 0; y < th; y++)
|
||||
{
|
||||
unsigned long *s = (unsigned long*)bits + y * tw;
|
||||
unsigned long *d = (unsigned long*)((char*)lr.pBits + y * lr.Pitch);
|
||||
for (int x = 0; x < tw; x++)
|
||||
d[x] = ((s[x] & 0xFF) << 24) | 0x00FFFFFF; // A = text luminance, RGB white
|
||||
}
|
||||
tex->UnlockRect(0);
|
||||
}
|
||||
}
|
||||
SelectObject(dc, oldBm);
|
||||
DeleteObject(dib);
|
||||
SelectObject(dc, oldFont);
|
||||
DeleteDC(dc);
|
||||
|
||||
CkLabelTex &e = gCkLabels[gCkLabelCount++];
|
||||
strncpy(e.str, str, 15); e.str[15] = 0; e.tex = tex; e.w = tw; e.h = th;
|
||||
if (ow) *ow = tw; if (oh) *oh = th;
|
||||
return tex;
|
||||
}
|
||||
|
||||
static void CkDrawLabel(LPDIRECT3DDEVICE9 dev, int cx, int cy, const char *str, D3DCOLOR color)
|
||||
{
|
||||
if (str == NULL || str[0] == 0) return;
|
||||
int tw = 0, th = 0;
|
||||
LPDIRECT3DTEXTURE9 tex = CkGetLabel(dev, str, &tw, &th);
|
||||
if (tex == NULL) return;
|
||||
float x = (float)(cx - tw / 2), y = (float)(cy - th / 2);
|
||||
struct V { float x, y, z, rhw; D3DCOLOR c; float u, v; };
|
||||
V q[4] = {
|
||||
{ x, y, 0.0f, 1.0f, color, 0.0f, 0.0f },
|
||||
{ x + (float)tw, y, 0.0f, 1.0f, color, 1.0f, 0.0f },
|
||||
{ x + (float)tw, y + (float)th, 0.0f, 1.0f, color, 1.0f, 1.0f },
|
||||
{ x, y + (float)th, 0.0f, 1.0f, color, 0.0f, 1.0f },
|
||||
};
|
||||
dev->SetTexture(0, tex);
|
||||
dev->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
|
||||
dev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
dev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); // label color
|
||||
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); // text mask
|
||||
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, q, sizeof(V));
|
||||
}
|
||||
|
||||
void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (device == NULL) return;
|
||||
IDirect3DSurface9 *rt = NULL;
|
||||
if (FAILED(device->GetRenderTarget(0, &rt)) || rt == NULL) return;
|
||||
D3DSURFACE_DESC dsc; rt->GetDesc(&dsc); rt->Release();
|
||||
BTCockpitLayout L;
|
||||
BTCockpitComputeLayout((int)dsc.Width, (int)dsc.Height, &L);
|
||||
|
||||
// DEVICE-STATE ISOLATION (floating cavern rocks): snapshot + restore.
|
||||
IDirect3DStateBlock9 *sb = NULL;
|
||||
device->CreateStateBlock(D3DSBT_ALL, &sb);
|
||||
|
||||
// Full-target viewport (the world viewport was centered; our quads span the canvas).
|
||||
D3DVIEWPORT9 vp = { 0, 0, dsc.Width, dsc.Height, 0.0f, 1.0f };
|
||||
device->SetViewport(&vp);
|
||||
|
||||
// 1) Surround bands (dark) AROUND the view -- the engine Clear never covers
|
||||
// them, and the gauge renderer may not be up yet, so fill unconditionally.
|
||||
CkColorState(device);
|
||||
D3DCOLOR band = D3DCOLOR_XRGB(6, 7, 9);
|
||||
CkFill(device, 0, 0, L.canvasW, L.viewY, band); // top
|
||||
CkFill(device, 0, L.viewY + L.viewH, L.canvasW, L.canvasH - (L.viewY + L.viewH), band); // bottom
|
||||
CkFill(device, 0, L.viewY, L.viewX, L.viewH, band); // left
|
||||
CkFill(device, L.viewX + L.viewW, L.viewY, L.canvasW - (L.viewX + L.viewW), L.viewH, band); // right
|
||||
|
||||
// 2) Button lamp faces (UNDER the surfaces -- only the protruding edge shows).
|
||||
unsigned long tick = GetTickCount();
|
||||
for (int i = 0; i < L.buttonCount; i++)
|
||||
{
|
||||
const BTCockpitBtn &b = L.buttons[i];
|
||||
int shade = BTLampBrightnessOf(CkLampState(b.address), tick);
|
||||
if (gCkPressed == b.address || gCkLatched[b.address & 0x7F]) shade = 3;
|
||||
D3DCOLOR fill, border;
|
||||
CkLampColors(b.colorClass, shade, b.inert, &fill, &border);
|
||||
CkFill(device, b.x, b.y, b.w, b.h, border);
|
||||
CkFill(device, b.x + 1, b.y + 1, b.w - 2, b.h - 2, fill);
|
||||
}
|
||||
|
||||
// 3) Gauge surfaces on top (green-tinted mono / palette sec). Reuses the
|
||||
// 9-entry loop shape + BlankColor skip of BTDrawGaugeSurfaces; the Eng1-3
|
||||
// planes share their sibling's dest cell (ckSlotOf).
|
||||
GaugeRenderer *gr = BTResolveGaugeRenderer();
|
||||
if (gr != NULL)
|
||||
{
|
||||
SVGA16 *svga = NULL;
|
||||
for (int i = 0; i < 6 && svga == NULL; i++)
|
||||
{
|
||||
L4GraphicsPort *p = static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(kBTGaugeSurfaces[i].portName));
|
||||
if (p != NULL) svga = (SVGA16*) p->graphicsDisplay;
|
||||
}
|
||||
if (svga != NULL)
|
||||
{
|
||||
static const int ckSlotOf[9] = { 0, 1, 2, 3, 4, 5, 1, 2, 3 };
|
||||
int tint = CkTint();
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
const BTGaugeSurfaceDesc &d = kBTGaugeSurfaces[i];
|
||||
L4GraphicsPort *port = static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(d.portName));
|
||||
if (port == NULL) continue;
|
||||
if (d.monoTint >= 0 && port->GetEnableID() == L4GraphicsPort::BlankColor) continue;
|
||||
int rot = d.rotateCCW;
|
||||
if (rot != 0)
|
||||
{
|
||||
static int s_secRot = -1;
|
||||
if (s_secRot < 0) { const char *rv = getenv("BT_GAUGE_SEC_ROT"); s_secRot = (rv != NULL && rv[0] == '1') ? 1 : 3; }
|
||||
rot = s_secRot;
|
||||
}
|
||||
int surf = ckSlotOf[i];
|
||||
int useTint = (d.monoTint < 0) ? -1 : tint;
|
||||
svga->DrawDevSurface(device, i, port->GetBitMask(), port->paletteID, useTint,
|
||||
(float)L.surfX[surf], (float)L.surfY[surf], (float)L.surfW[surf], (float)L.surfH[surf], rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Flight-block labels (on top; the blue faces aren't covered by surfaces).
|
||||
for (int i = 0; i < L.buttonCount; i++)
|
||||
{
|
||||
const BTCockpitBtn &b = L.buttons[i];
|
||||
if (b.label == NULL || b.label[0] == 0) continue;
|
||||
D3DCOLOR lc = b.inert ? D3DCOLOR_ARGB(255, 120, 128, 145) : D3DCOLOR_ARGB(255, 0, 0, 0);
|
||||
CkDrawLabel(device, b.x + b.w / 2, b.y + b.h / 2, b.label, lc);
|
||||
}
|
||||
|
||||
if (sb != NULL) { sb->Apply(); sb->Release(); }
|
||||
}
|
||||
|
||||
// Glass-only RIO emit: a cockpit click injects a button edge through PadRIO's
|
||||
// static entry (safe no-op if no PadRIO -- pod build never links it).
|
||||
static void CkEmit(int addr, int pressed)
|
||||
{
|
||||
#ifdef BT_GLASS
|
||||
PadRIO::SetScreenButton(addr, pressed);
|
||||
#else
|
||||
(void)addr; (void)pressed;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Mouse routing (main-window WndProc -> here). Client coords are mapped into the
|
||||
// fixed backbuffer canvas; hit-test the FULL button rects; mirror the L4GLASSWIN
|
||||
// GlassWndProc contract (left = press / unlatch-if-latched; right = latch toggle).
|
||||
// Returns 1 when a button was hit (the caller SetCaptures on a left press).
|
||||
int BTCockpitMouseDown(int cx, int cy, int clientW, int clientH, int rightButton)
|
||||
{
|
||||
if (!gBTGaugeCockpit) return 0;
|
||||
int bbW = gBTCockpitCanvasW, bbH = gBTCockpitCanvasH;
|
||||
if (bbW <= 0 || bbH <= 0 || clientW <= 0 || clientH <= 0) return 0;
|
||||
int bx = cx * bbW / clientW;
|
||||
int by = cy * bbH / clientH;
|
||||
|
||||
BTCockpitLayout L;
|
||||
BTCockpitComputeLayout(bbW, bbH, &L);
|
||||
int a = -1;
|
||||
for (int i = 0; i < L.buttonCount; i++)
|
||||
{
|
||||
const BTCockpitBtn &b = L.buttons[i];
|
||||
if (bx >= b.x && bx < b.x + b.w && by >= b.y && by < b.y + b.h) { a = b.address; break; }
|
||||
}
|
||||
if (a < 0) return 0;
|
||||
|
||||
// Crash forensics (always-on, flushed BEFORE dispatch): a click that kills the
|
||||
// process leaves its address as the last log line (the Gitea #18 pattern).
|
||||
DEBUG_STREAM << "[cockpit] CLICK addr=0x" << std::hex << a << std::dec
|
||||
<< " at(" << bx << "," << by << ")"
|
||||
<< (rightButton ? " latch-toggle"
|
||||
: (gCkLatched[a & 0x7F] ? " unlatch" : " press"))
|
||||
<< "\n" << std::flush;
|
||||
|
||||
if (rightButton)
|
||||
{
|
||||
gCkLatched[a & 0x7F] = gCkLatched[a & 0x7F] ? 0 : 1;
|
||||
CkEmit(a, gCkLatched[a & 0x7F]);
|
||||
return 1;
|
||||
}
|
||||
if (gCkLatched[a & 0x7F])
|
||||
{
|
||||
gCkLatched[a & 0x7F] = 0;
|
||||
CkEmit(a, 0);
|
||||
return 0; // released -- no capture needed
|
||||
}
|
||||
gCkPressed = a;
|
||||
CkEmit(a, 1);
|
||||
return 1; // pressed -- caller SetCaptures for release-outside
|
||||
}
|
||||
|
||||
void BTCockpitMouseUp(void)
|
||||
{
|
||||
if (gCkPressed >= 0)
|
||||
{
|
||||
CkEmit(gCkPressed, 0);
|
||||
gCkPressed = -1;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Free entry point the MAIN renderer calls (L4VIDEO DPLRenderer::ExecuteImplementation,
|
||||
// just before EndScene). No-op unless BT_DEV_GAUGES is set. Docks the 6-surface panel
|
||||
@@ -505,6 +1012,13 @@ void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (!DevGaugeComposite() || !DevGaugeDocked() || device == NULL) return;
|
||||
if (GlassPanelsOwnSurfaces()) return; // the per-display glass windows draw the surfaces
|
||||
if (gBTGaugeCockpit)
|
||||
{
|
||||
// COCKPIT SURROUND: draw the surround bands + panels + button lamps over
|
||||
// the centered world view (captured before BT_SHOT, so shots include it).
|
||||
BTDrawCockpitPanels(device);
|
||||
return;
|
||||
}
|
||||
if (gBTGaugeDockBottom)
|
||||
{
|
||||
// DOCK-BOTTOM: the strip owns the bottom band of the target (the world
|
||||
|
||||
@@ -17,6 +17,66 @@ class GaugeRenderer;
|
||||
GaugeRenderer *BTResolveGaugeRenderer();
|
||||
void *BTResolveMainWindow(); // HWND (kept void* -- no windows.h dependency)
|
||||
|
||||
//########################################################################
|
||||
//#################### COCKPIT SURROUND (BT_COCKPIT) #####################
|
||||
//########################################################################
|
||||
//
|
||||
// The pod-faithful single-window layout: the 3D world view CENTERED, with the
|
||||
// six live gauge surfaces composited AROUND it at 1/2 native scale (the pod
|
||||
// monitors physically clip the eyeport, so the corner MFDs / top-center / etc.
|
||||
// overlap the view edges) and clickable RIO button lamps. DEFAULT under
|
||||
// BT_DEV_GAUGES (BT_COCKPIT=0 falls back to the dock-bottom strip).
|
||||
//
|
||||
// The layout is the SINGLE SOURCE OF TRUTH -- computed from the backbuffer
|
||||
// (canvas) size and consumed by the window sizing, the world viewport, the
|
||||
// panel/button draw, the aspect calc, and the mouse hit-test. See L4VB16.cpp.
|
||||
//
|
||||
struct BTCockpitBtn
|
||||
{
|
||||
int address; // RIO buttonGroup address (0x00-0x47)
|
||||
int x, y, w, h; // backbuffer-space rect (full hit target; only the
|
||||
// protruding lamp edge shows after the surface draws over)
|
||||
int colorClass; // 0 = red (MFD), 1 = yellow (radar), 2 = blue (flight)
|
||||
int inert; // 1 = no authored .CTL mapping -> drawn greyed
|
||||
const char *label; // non-NULL (blue flight block) -> face is drawn + labeled
|
||||
};
|
||||
|
||||
struct BTCockpitLayout
|
||||
{
|
||||
int canvasW, canvasH;
|
||||
int viewX, viewY, viewW, viewH; // centered world view
|
||||
int surfX[6], surfY[6], surfW[6], surfH[6]; // Heat,Mfd2,Mfd1,Mfd3,sec,Comm dest rects
|
||||
BTCockpitBtn buttons[80]; // 5*8 red + 16 yellow + 16 blue = 72
|
||||
int buttonCount;
|
||||
};
|
||||
|
||||
extern int gBTGaugeCockpit; // mode select; set once by btl4main
|
||||
extern int gBTCockpitCanvasW, gBTCockpitCanvasH; // backbuffer canvas dims (for the aspect calc)
|
||||
|
||||
void BTCockpitCanvasFor(int viewW, int viewH, int *canvasW, int *canvasH);
|
||||
void BTCockpitComputeLayout(int canvasW, int canvasH, BTCockpitLayout *out);
|
||||
void BTDrawCockpitPanels(LPDIRECT3DDEVICE9 device);
|
||||
int BTCockpitMouseDown(int cx, int cy, int clientW, int clientH, int rightButton); // 1 = consumed
|
||||
void BTCockpitMouseUp(void);
|
||||
|
||||
// Lamp brightness decode (RIOBase::LampState) -- shared by the cockpit draw and
|
||||
// the glass per-display windows. level = max of the two 2-bit fields; the low 2
|
||||
// bits pick a flash half-period (500/250/125 ms). Returns 0..3.
|
||||
inline int BTLampBrightnessOf(int state, unsigned long tick)
|
||||
{
|
||||
int level1 = (state >> 2) & 0x3;
|
||||
int level2 = (state >> 4) & 0x3;
|
||||
int level = (level1 > level2) ? level1 : level2;
|
||||
if (level == 0) return 0;
|
||||
int flash = state & 0x3;
|
||||
if (flash != 0)
|
||||
{
|
||||
unsigned long half = (flash == 1) ? 500 : (flash == 2) ? 250 : 125;
|
||||
if ((tick / half) & 1) return 0;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
//######################### Video16BitBuffered ###########################
|
||||
//########################################################################
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "..\munga\eyecandy.h"
|
||||
#include "l4vidrnd.h"
|
||||
#include "l4video.h"
|
||||
#include "l4vb16.h" // BTCockpitLayout / BTCockpitComputeLayout (cockpit aspect)
|
||||
#include "..\munga\matrix.h"
|
||||
#include "..\munga\mover.h"
|
||||
#include "..\munga\jmover.h"
|
||||
@@ -9384,6 +9385,22 @@ static float BTWorldAspectOf(int client_w, int client_h)
|
||||
{
|
||||
extern int gBTGaugeDockBottom;
|
||||
extern int BTGaugeStripHeightFor(int width);
|
||||
// COCKPIT: the world occupies the CENTERED view sub-rect of the fixed-size
|
||||
// backbuffer (canvas), which D3D stretches into the client area. The view's
|
||||
// ON-SCREEN aspect = (viewW/canvasW * client_w) / (viewH/canvasH * client_h).
|
||||
extern int gBTGaugeCockpit;
|
||||
extern int gBTCockpitCanvasW, gBTCockpitCanvasH;
|
||||
if (gBTGaugeCockpit && gBTCockpitCanvasW > 0 && gBTCockpitCanvasH > 0)
|
||||
{
|
||||
BTCockpitLayout L;
|
||||
BTCockpitComputeLayout(gBTCockpitCanvasW, gBTCockpitCanvasH, &L);
|
||||
if (L.viewW > 0 && L.viewH > 0)
|
||||
{
|
||||
float w = (float)L.viewW * (float)client_w / (float)gBTCockpitCanvasW;
|
||||
float h = (float)L.viewH * (float)client_h / (float)gBTCockpitCanvasH;
|
||||
if (h > 0.0f) return w / h;
|
||||
}
|
||||
}
|
||||
float world_h = (float)client_h;
|
||||
if (gBTGaugeDockBottom)
|
||||
{
|
||||
|
||||
@@ -3102,16 +3102,26 @@ void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform)
|
||||
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
|
||||
// COCKPIT/DOCK: the world renders in a SUB-RECT viewport, not the whole
|
||||
// backbuffer -- place the camera-seat HUD relative to the CURRENT viewport
|
||||
// (== full backbuffer in normal mode) so it lands over the world view, not in
|
||||
// the surround bands (where pretransformed verts would be viewport-clipped).
|
||||
D3DVIEWPORT9 cvp;
|
||||
device->GetViewport(&cvp);
|
||||
const float refX = (float)cvp.X, refY = (float)cvp.Y;
|
||||
const float refW = (float)cvp.Width, refH = (float)cvp.Height;
|
||||
|
||||
//
|
||||
// The followed player's callsign banner (bottom center). Execute already
|
||||
// converts the 1-based goalPlayerIndex to the 0-based texture slot.
|
||||
// The followed player's callsign banner (bottom center of the view). Execute
|
||||
// already converts the 1-based goalPlayerIndex to the 0-based texture slot.
|
||||
// (Drawn per-frame in viewport space -- the ctor mVB was backbuffer-fixed.)
|
||||
//
|
||||
if (oldFollowedPlayerIndex >= 0 && oldFollowedPlayerIndex < MAX_PLAYER_NAMES)
|
||||
{
|
||||
device->SetStreamSource(0, mVB, 0, sizeof(L4VERTEX_2D_TEX));
|
||||
device->SetTexture(0,
|
||||
myRenderer->GetNameTexture(oldFollowedPlayerIndex));
|
||||
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
float nameW = (refW * 0.32f) / 2.0f;
|
||||
float nameH = refH * 0.10f;
|
||||
CameraHUDDrawQuad(device, myRenderer->GetNameTexture(oldFollowedPlayerIndex),
|
||||
refX + refW / 2.0f - nameW, refY + refH - nameH * 2.0f, nameW * 2.0f, nameH);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -3135,14 +3145,14 @@ void CameraShipHUDRenderable::Render(int pass, const D3DXMATRIX *viewTransform)
|
||||
if (displayRankingWindow != NULL && *displayRankingWindow
|
||||
&& playerRank != NULL && playerCount > 0)
|
||||
{
|
||||
const float width = myRenderer->GetWidth();
|
||||
const float height = myRenderer->GetHeight();
|
||||
const float width = refW;
|
||||
const float height = refH;
|
||||
const float nameW = width * 0.20f;
|
||||
const float ordW = width * 0.10f;
|
||||
const float rowH = nameW * (32.0f / 128.0f); // bitmap aspect
|
||||
const float pad = width * 0.01f;
|
||||
const float x0 = width * 0.55f;
|
||||
const float y0 = height * 0.16f;
|
||||
const float x0 = refX + width * 0.55f;
|
||||
const float y0 = refY + height * 0.16f;
|
||||
|
||||
for (int bmp = 0; bmp < playerCount && bmp < MAX_PLAYER_NAMES; ++bmp)
|
||||
{
|
||||
|
||||
+97
-24
@@ -106,6 +106,32 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
// GetAsyncKeyState per frame instead (with a foreground guard), which
|
||||
// is immune to message stealing.
|
||||
return 0;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
// COCKPIT SURROUND: route clicks on the composited panel buttons to the
|
||||
// RIO (glass build) via the layout hit-test. No-op in other modes.
|
||||
{
|
||||
extern int gBTGaugeCockpit;
|
||||
extern int BTCockpitMouseDown(int, int, int, int, int);
|
||||
if (gBTGaugeCockpit)
|
||||
{
|
||||
RECT rc; GetClientRect(hWnd, &rc);
|
||||
int consumed = BTCockpitMouseDown(
|
||||
(int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam),
|
||||
(int)(rc.right - rc.left), (int)(rc.bottom - rc.top),
|
||||
uMsg == WM_RBUTTONDOWN);
|
||||
if (consumed && uMsg == WM_LBUTTONDOWN)
|
||||
SetCapture(hWnd); // a release outside the button still clears it
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
extern int gBTGaugeCockpit;
|
||||
extern void BTCockpitMouseUp(void);
|
||||
if (gBTGaugeCockpit) { BTCockpitMouseUp(); ReleaseCapture(); }
|
||||
}
|
||||
return 0;
|
||||
case WM_SIZE:
|
||||
// Window-resize aspect fix (task #20): rebuild the projection for the
|
||||
// new client aspect so the scene doesn't stretch fat/skinny (the D3D9
|
||||
@@ -330,19 +356,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
{
|
||||
// GLASS profile (dev tooling, step 2e): the desktop cockpit --
|
||||
// PadRIO input (XInput+keyboard, content\bindings.txt), the
|
||||
// on-screen clickable button panel, the dev-composited MFD
|
||||
// surfaces (dock-bottom default; BT_DEV_GAUGES_WINDOW=1 separate
|
||||
// window / BT_DEV_GAUGES_DOCK=1 overlay inset), and the desktop
|
||||
// plasma window. Each only when not already set, so a developer
|
||||
// env overrides the preset.
|
||||
// dev-composited MFD surfaces, and the desktop plasma window.
|
||||
// Each only when not already set, so a developer env overrides.
|
||||
if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=PAD,KEYBOARD");
|
||||
if (getenv("BT_DEV_GAUGES") == NULL) putenv("BT_DEV_GAUGES=1");
|
||||
if (getenv("L4PLASMA") == NULL) putenv("L4PLASMA=SCREEN");
|
||||
if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1");
|
||||
// Break the secondary displays into per-display windows (surface + its RIO
|
||||
// bank around it). Set BT_GLASS_PANELS=0 to fall back to the single
|
||||
// combined pad panel + docked gauge strip.
|
||||
if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1");
|
||||
|
||||
// DEFAULT LAYOUT = the COCKPIT SURROUND (single window: the six gauge
|
||||
// surfaces + clickable button lamps composited AROUND the centered 3D
|
||||
// view). Opt out with BT_COCKPIT=0 (dock-bottom strip) or
|
||||
// BT_GLASS_PANELS=1 (the per-display windows). When cockpit is the
|
||||
// default the buttons live IN the main window, so no separate pad/panel
|
||||
// window is created (leave BT_PAD_PANEL / BT_GLASS_PANELS unset).
|
||||
int cockpitDefault = 1;
|
||||
if (getenv("BT_COCKPIT") && getenv("BT_COCKPIT")[0] == '0') cockpitDefault = 0;
|
||||
if (getenv("BT_GLASS_PANELS") && getenv("BT_GLASS_PANELS")[0] != '0') cockpitDefault = 0;
|
||||
if (getenv("BT_DEV_GAUGES_WINDOW") || getenv("BT_DEV_GAUGES_DOCK")) cockpitDefault = 0;
|
||||
if (!cockpitDefault)
|
||||
{
|
||||
if (getenv("BT_PAD_PANEL") == NULL) putenv("BT_PAD_PANEL=1");
|
||||
if (getenv("BT_GLASS_PANELS") == NULL) putenv("BT_GLASS_PANELS=1");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
@@ -562,29 +596,68 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
// restores the old separate MFD window.
|
||||
{
|
||||
extern int gBTGaugeDockBottom;
|
||||
extern int gBTGaugeCockpit;
|
||||
extern int gBTCockpitCanvasW, gBTCockpitCanvasH;
|
||||
extern int BTGaugeStripHeightFor(int width);
|
||||
extern void BTCockpitCanvasFor(int viewW, int viewH, int *cw, int *ch);
|
||||
// BT_GLASS_PANELS breaks the gauges into their own per-display windows, so
|
||||
// the world window keeps its normal size (no bottom strip).
|
||||
// the world window keeps its normal size (no cockpit / bottom strip).
|
||||
int glassOwnsGauges = 0;
|
||||
#ifdef BT_GLASS
|
||||
{ extern int BTGlassPanelsActive(); glassOwnsGauges = BTGlassPanelsActive(); }
|
||||
#endif
|
||||
if (!glassOwnsGauges &&
|
||||
getenv("BT_DEV_GAUGES") != 0 && getenv("BT_DEV_GAUGES_WINDOW") == 0)
|
||||
if (!glassOwnsGauges && getenv("BT_DEV_GAUGES") != 0)
|
||||
{
|
||||
gBTGaugeDockBottom = 1;
|
||||
// Readability default (user-reported: the strip at 800 wide is a
|
||||
// 0.6x downscale, hard to read): a WIDER world region (the
|
||||
// projection is aspect-correct for any shape) buys strip pixels --
|
||||
// 1100x600 world + 1100x400 strip = a 1000-tall window that fits a
|
||||
// 1080p desktop with the panel at 83% of design size. -res still
|
||||
// overrides both.
|
||||
if (strstr(lpCmdLine ? lpCmdLine : "", "-res") == 0)
|
||||
// Mode resolution (precedence): explicit BT_COCKPIT > separate-window /
|
||||
// legacy-inset opt-out > COCKPIT SURROUND default under BT_DEV_GAUGES.
|
||||
int cockpitOn;
|
||||
const char *ck = getenv("BT_COCKPIT");
|
||||
if (ck != NULL)
|
||||
cockpitOn = (ck[0] != '0');
|
||||
else
|
||||
cockpitOn = (getenv("BT_DEV_GAUGES_WINDOW") == 0 &&
|
||||
getenv("BT_DEV_GAUGES_DOCK") == 0);
|
||||
|
||||
if (cockpitOn)
|
||||
{
|
||||
winW = 1100;
|
||||
winH = 600;
|
||||
gBTGaugeCockpit = 1;
|
||||
// -res W H = the WORLD VIEW size in cockpit mode; default 900x500.
|
||||
int viewW = 900, viewH = 500;
|
||||
if (strstr(lpCmdLine ? lpCmdLine : "", "-res") != 0) { viewW = winW; viewH = winH; }
|
||||
// Clamp the CANVAS to the work area (minus the window frame): shrink
|
||||
// viewH (floor 400) then viewW (floor 640) until it fits (1080p tight).
|
||||
RECT wa = { 0, 0, 1920, 1080 };
|
||||
SystemParametersInfo(SPI_GETWORKAREA, 0, &wa, 0);
|
||||
RECT fr = { 0, 0, 1000, 1000 };
|
||||
AdjustWindowRect(&fr, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
int availW = (wa.right - wa.left) - ((fr.right - fr.left) - 1000);
|
||||
int availH = (wa.bottom - wa.top) - ((fr.bottom - fr.top) - 1000);
|
||||
int cw, ch;
|
||||
BTCockpitCanvasFor(viewW, viewH, &cw, &ch);
|
||||
while (ch > availH && viewH > 400) { viewH -= 20; BTCockpitCanvasFor(viewW, viewH, &cw, &ch); }
|
||||
while (cw > availW && viewW > 640) { viewW -= 20; BTCockpitCanvasFor(viewW, viewH, &cw, &ch); }
|
||||
winW = cw; winH = ch;
|
||||
gBTCockpitCanvasW = winW; // windowed backbuffer = client canvas
|
||||
gBTCockpitCanvasH = winH;
|
||||
std::cout << "[cockpit] view " << viewW << "x" << viewH
|
||||
<< " canvas " << winW << "x" << winH << std::endl << std::flush;
|
||||
}
|
||||
else if (getenv("BT_DEV_GAUGES_WINDOW") == 0)
|
||||
{
|
||||
gBTGaugeDockBottom = 1;
|
||||
// Readability default (user-reported: the strip at 800 wide is a
|
||||
// 0.6x downscale, hard to read): a WIDER world region (the
|
||||
// projection is aspect-correct for any shape) buys strip pixels --
|
||||
// 1100x600 world + 1100x400 strip = a 1000-tall window that fits a
|
||||
// 1080p desktop with the panel at 83% of design size. -res still
|
||||
// overrides both.
|
||||
if (strstr(lpCmdLine ? lpCmdLine : "", "-res") == 0)
|
||||
{
|
||||
winW = 1100;
|
||||
winH = 600;
|
||||
}
|
||||
winH += BTGaugeStripHeightFor(winW);
|
||||
}
|
||||
winH += BTGaugeStripHeightFor(winW);
|
||||
}
|
||||
}
|
||||
RECT wr = { 0, 0, winW, winH };
|
||||
|
||||
Reference in New Issue
Block a user