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
+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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user