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:
arcattack
2026-07-20 20:39:34 -05:00
co-authored by Claude Opus 4.8
parent 5ba5697a08
commit 129b27499d
7 changed files with 793 additions and 41 deletions
+34 -5
View File
@@ -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
+49
View File
@@ -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
View File
@@ -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
+60
View File
@@ -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 ###########################
//########################################################################
+17
View File
@@ -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)
{
+20 -10
View File
@@ -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
View File
@@ -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 };