//===========================================================================// // File: dpl2d.cpp // // Project: BattleTech port (WinTesla / btl4) // //---------------------------------------------------------------------------// // libDPL 2D vector display-list layer, re-hosted over Direct3D 9. // // // // Replaces the inert dpl2d_* stubs that used to live in btstubs.cpp. The // // recorder calls (dpl2d_NewDisplayList / Begin / SetColor / Circle / // // PushMatrix / PopMatrix / MoveTo / End / Compile -- prototypes in // // btl4vid.hpp) build a small command list; dpl2d_ExecuteList (dpl2d.hpp) // // draws a compiled list with screen-space 2D primitives. // // // // The recorded calls match what the reconstructed reticle/PIP builder in // // btl4vid.cpp actually issues, e.g. // // dpl2d_Begin(list, 1); // // dpl2d_SetColor(list, r, g, b); // // dpl2d_Circle(list, x, y, 0.012f, 1); // filled blip // // dpl2d_SetColor(list, 0, 0, 0); // // dpl2d_Circle(list, x, y, 0.014f, 0); // dark outline ring // // dpl2d_PushMatrix(list); dpl2d_MoveTo(list, x, y); dpl2d_PopMatrix(); // // dpl2d_End(list); dpl2d_Compile(list); // //===========================================================================// #include // Scalar + engine prelude #pragma hdrstop #if !defined(BTL4VID_HPP) # include // dpl2d_DISPLAY (opaque) + dpl2d_* prototypes #endif #if !defined(DPL2D_HPP) # include #endif #include #include #include //===========================================================================// // Pre-transformed 2D vertex -- mirrors MUNGA_L4/L4D3D.h L4VERTEX_2D and its // L4VERTEX_2D_FVF, redeclared locally so this TU does not need the MUNGA_L4 // source dir on the include path (the engine lib uses the identical layout). //===========================================================================// namespace { struct Vertex2D { float x, y, z, rhw; DWORD color; }; const DWORD kVertex2DFVF = (D3DFVF_XYZRHW | D3DFVF_DIFFUSE); const int kCircleSegments = 32; // tessellation of dpl2d_Circle const float kPi = 3.14159265358979323846f; inline float ClampUnit(float v) { return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v); } // // One recorded drawing command. The dpl2d_ API the port uses only ever // emits coloured circles (filled disc or outline ring); the matrix stack // and pen move are tracked as a translation so any future MoveTo-relative // geometry lands in the right place. // struct Command { enum Kind { kCircleFill, kCircleOutline } kind; DWORD color; float x, y; // normalised view coords (already translated) float radius; // normalised (fraction of viewport width) }; struct Vec2 { float x, y; }; } // // The concrete display list. dpl2d_DISPLAY is an opaque empty class // (DPLSTUB.h), so the recorder allocates one of these and hands the caller a // reinterpret_cast handle; every dpl2d_ call casts it straight back. // struct Dpl2dList { bool recording; bool compiled; int mode; DWORD currentColor; float penX, penY; // current pen (MoveTo) float transX, transY; // current translation std::vector matrixStack; // saved translations (Push/Pop) std::vector commands; Dpl2dList() : recording(false), compiled(false), mode(0), currentColor(0xFFFFFFFF), penX(0.0f), penY(0.0f), transX(0.0f), transY(0.0f) {} }; static inline Dpl2dList *AsList(dpl2d_DISPLAY *handle) { return reinterpret_cast(handle); } //===========================================================================// // Recorder -- the dpl2d_* entry points the game calls (declared in btl4vid.hpp) //===========================================================================// dpl2d_DISPLAY *dpl2d_NewDisplayList() { return reinterpret_cast(new Dpl2dList); } void dpl2d_Begin(dpl2d_DISPLAY *list, int mode) { Dpl2dList *self = AsList(list); if (self == 0) return; self->recording = true; self->compiled = false; self->mode = mode; self->currentColor= 0xFFFFFFFF; self->transX = self->transY = 0.0f; self->penX = self->penY = 0.0f; self->matrixStack.clear(); self->commands.clear(); } void dpl2d_SetColor(dpl2d_DISPLAY *list, Scalar red, Scalar green, Scalar blue) { Dpl2dList *self = AsList(list); if (self == 0) return; self->currentColor = D3DCOLOR_COLORVALUE( ClampUnit((float)red), ClampUnit((float)green), ClampUnit((float)blue), 1.0f); } void dpl2d_Circle(dpl2d_DISPLAY *list, Scalar x, Scalar y, Scalar radius, int fill) { Dpl2dList *self = AsList(list); if (self == 0) return; Command cmd; cmd.kind = fill ? Command::kCircleFill : Command::kCircleOutline; cmd.color = self->currentColor; cmd.x = (float)x + self->transX; cmd.y = (float)y + self->transY; cmd.radius = (float)radius; self->commands.push_back(cmd); } void dpl2d_PushMatrix(dpl2d_DISPLAY *list) { Dpl2dList *self = AsList(list); if (self == 0) return; Vec2 saved = { self->transX, self->transY }; self->matrixStack.push_back(saved); } void dpl2d_PopMatrix(dpl2d_DISPLAY *list) { Dpl2dList *self = AsList(list); if (self == 0 || self->matrixStack.empty()) return; Vec2 saved = self->matrixStack.back(); self->matrixStack.pop_back(); self->transX = saved.x; self->transY = saved.y; } // // MoveTo: position the pen. In the recovered usage it is bracketed by // Push/Pop with no draw in between (a no-op marker stamp), so it only updates // the pen + the current translation; nothing is emitted. // void dpl2d_MoveTo(dpl2d_DISPLAY *list, Scalar x, Scalar y) { Dpl2dList *self = AsList(list); if (self == 0) return; self->penX = (float)x; self->penY = (float)y; self->transX = (float)x; self->transY = (float)y; } void dpl2d_End(dpl2d_DISPLAY *list) { Dpl2dList *self = AsList(list); if (self == 0) return; self->recording = false; } void dpl2d_Compile(dpl2d_DISPLAY *list) { Dpl2dList *self = AsList(list); if (self == 0) return; self->compiled = true; // geometry is tessellated lazily at execute time } //===========================================================================// // Executor -- rasterise a compiled list on the supplied device. //===========================================================================// void dpl2d_ExecuteList(dpl2d_DISPLAY *list, IDirect3DDevice9 *device) { Dpl2dList *self = AsList(list); if (self == 0 || device == 0 || self->commands.empty()) return; D3DVIEWPORT9 vp; if (FAILED(device->GetViewport(&vp))) return; const float ox = (float)vp.X; const float oy = (float)vp.Y; const float vw = (float)vp.Width; const float vh = (float)vp.Height; // --- save the render states we touch --------------------------------- DWORD oldLighting, oldZ, oldCull, oldAlpha, oldSrc, oldDst, oldFog; device->GetRenderState(D3DRS_LIGHTING, &oldLighting); device->GetRenderState(D3DRS_ZENABLE, &oldZ); device->GetRenderState(D3DRS_CULLMODE, &oldCull); device->GetRenderState(D3DRS_ALPHABLENDENABLE, &oldAlpha); device->GetRenderState(D3DRS_SRCBLEND, &oldSrc); device->GetRenderState(D3DRS_DESTBLEND, &oldDst); device->GetRenderState(D3DRS_FOGENABLE, &oldFog); IDirect3DBaseTexture9 *oldTex = 0; device->GetTexture(0, &oldTex); device->SetTexture(0, 0); device->SetRenderState(D3DRS_LIGHTING, FALSE); device->SetRenderState(D3DRS_ZENABLE, FALSE); device->SetRenderState(D3DRS_FOGENABLE, FALSE); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device->SetFVF(kVertex2DFVF); Vertex2D ring[kCircleSegments + 2]; for (size_t i = 0; i < self->commands.size(); ++i) { const Command &cmd = self->commands[i]; // normalised view coords -> pixels. radius scales with viewport width // so it stays round on a non-square viewport. const float cx = ox + cmd.x * vw; const float cy = oy + cmd.y * vh; const float rx = cmd.radius * vw; const float ry = cmd.radius * vw; if (cmd.kind == Command::kCircleFill) { // triangle fan: centre + rim, closed back to the first rim vertex. ring[0].x = cx; ring[0].y = cy; ring[0].z = 0.0f; ring[0].rhw = 1.0f; ring[0].color = cmd.color; for (int s = 0; s <= kCircleSegments; ++s) { float a = (2.0f * kPi * s) / kCircleSegments; ring[s + 1].x = cx + cosf(a) * rx; ring[s + 1].y = cy + sinf(a) * ry; ring[s + 1].z = 0.0f; ring[s + 1].rhw = 1.0f; ring[s + 1].color = cmd.color; } device->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, kCircleSegments, ring, sizeof(Vertex2D)); } else // kCircleOutline { // line strip around the rim, closed. for (int s = 0; s <= kCircleSegments; ++s) { float a = (2.0f * kPi * s) / kCircleSegments; ring[s].x = cx + cosf(a) * rx; ring[s].y = cy + sinf(a) * ry; ring[s].z = 0.0f; ring[s].rhw = 1.0f; ring[s].color = cmd.color; } device->DrawPrimitiveUP( D3DPT_LINESTRIP, kCircleSegments, ring, sizeof(Vertex2D)); } } // --- restore ---------------------------------------------------------- device->SetTexture(0, oldTex); if (oldTex) oldTex->Release(); device->SetRenderState(D3DRS_LIGHTING, oldLighting); device->SetRenderState(D3DRS_ZENABLE, oldZ); device->SetRenderState(D3DRS_FOGENABLE, oldFog); device->SetRenderState(D3DRS_CULLMODE, oldCull); device->SetRenderState(D3DRS_ALPHABLENDENABLE, oldAlpha); device->SetRenderState(D3DRS_SRCBLEND, oldSrc); device->SetRenderState(D3DRS_DESTBLEND, oldDst); }