diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index 5e68173..cce38ed 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -96,11 +96,23 @@ void BTSetLodEye(float x, float y, float z) // convention = the dpl2d frame: centered origin, +y down, unit = half the // viewport height (see dpl2d.cpp MapX/MapY). // --------------------------------------------------------------------------- +// COORDINATE MODEL: everything talks in the DPL2D/RETICLE frame -- centered +// origin, +y down, unit = half the D3D VIEWPORT height (dpl2d MapX/MapY). +// The windowed present STRETCHES the fixed backbuffer into the client area, +// and the projection's aspect follows the CLIENT (the resize rebuild) -- so +// the viewport frame, the client frame and the NDC frame all differ unless +// the window happens to be exactly backbuffer-shaped. Every conversion below +// goes through the true NDC (per-axis P11/P22), NOT a square-frame shortcut: +// a shortcut is exact at screen centre and drifts outward with a side-flipped +// sign (the "locks left of the mech when I aim right of it" report). static float gBTAimCamPos[3] = {0, 0, 0}; static float gBTAimCamX[3] = {1, 0, 0}; // camera right (LookAtRH xaxis) static float gBTAimCamY[3] = {0, 1, 0}; // camera up (LookAtRH yaxis) static float gBTAimCamZ[3] = {0, 0, 1}; // camera BACK (LookAtRH zaxis; view dir = -Z) -static float gBTAimTanHalf = 0.0f; // tan(fovY/2) = 1 / proj._22 +static float gBTAimP11 = 0.0f; // proj._11 (x scale; carries the aspect) +static float gBTAimP22 = 0.0f; // proj._22 (y scale = 1/tan(fovY/2)) +static float gBTAimVpW = 0.0f; // D3D viewport (backbuffer) size -- +static float gBTAimVpH = 0.0f; // the dpl2d frame's pixel space static int gBTAimCamValid = 0; void BTSetAimCamera(const float pos[3], const float xax[3], @@ -116,24 +128,58 @@ void BTSetAimCamera(const float pos[3], const float xax[3], gBTAimCamValid = 1; } -void BTSetAimProjection(float proj22) +void BTSetAimProjection(float p11, float p22, float vpW, float vpH) { - if (proj22 > 1e-6f) - gBTAimTanHalf = 1.0f / proj22; + if (p11 > 1e-6f && p22 > 1e-6f && vpW > 0.0f && vpH > 0.0f) + { + gBTAimP11 = p11; + gBTAimP22 = p22; + gBTAimVpW = vpW; + gBTAimVpH = vpH; + } } // -// Crosshair (reticle coords) -> world pick ray. RH camera looks down -Z; -// a point at reticle (rx, ry) sits at camera-space (rx*t, -ry*t, -1) -// (reticle +y is DOWN, camera +y is UP; t = tan(fovY/2), and the reticle's -// half-viewport-height unit cancels the aspect term exactly). +// Client-area mouse position -> reticle coords: undo the present stretch +// (client px -> viewport px), then centre/scale by the dpl2d unit. Clamped +// to the visible frame. +// +void BTClientToReticle(float mx, float my, float cw, float ch, + float *rx, float *ry) +{ + if (cw <= 0.0f || ch <= 0.0f || gBTAimVpW <= 0.0f || gBTAimVpH <= 0.0f) + { + *rx = 0.0f; *ry = 0.0f; + return; + } + const float vx = mx * (gBTAimVpW / cw); + const float vy = my * (gBTAimVpH / ch); + float x = (vx - gBTAimVpW * 0.5f) / (gBTAimVpH * 0.5f); + float y = (vy - gBTAimVpH * 0.5f) / (gBTAimVpH * 0.5f); + const float xmax = gBTAimVpW / gBTAimVpH; + if (x < -xmax) x = -xmax; + if (x > xmax) x = xmax; + if (y < -1.0f) y = -1.0f; + if (y > 1.0f) y = 1.0f; + *rx = x; + *ry = y; +} + +// +// Crosshair (reticle coords) -> world pick ray. reticle -> NDC: +// ndc_x = rx * vh/vw (the reticle x unit is half the viewport HEIGHT), +// ndc_y = -ry (reticle +y is down). NDC -> camera: divide by the true +// per-axis projection scales; RH camera looks down -Z. // int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]) { - if (!gBTAimCamValid || gBTAimTanHalf <= 0.0f) + if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f + || gBTAimVpW <= 0.0f) return 0; - const float cx = rx * gBTAimTanHalf; - const float cy = -ry * gBTAimTanHalf; + const float ndcX = rx * (gBTAimVpH / gBTAimVpW); + const float ndcY = -ry; + const float cx = ndcX / gBTAimP11; + const float cy = ndcY / gBTAimP22; float d[3]; float len = 0.0f; for (int i = 0; i < 3; ++i) @@ -159,7 +205,8 @@ int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]) // int BTProjectToReticle(const float world[3], float *rx, float *ry) { - if (!gBTAimCamValid || gBTAimTanHalf <= 0.0f) + if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f + || gBTAimVpH <= 0.0f) { *rx = 0.0f; *ry = 0.0f; return 0; @@ -177,8 +224,10 @@ int BTProjectToReticle(const float world[3], float *rx, float *ry) *ry = 0.0f; return 0; } - *rx = xc / (gBTAimTanHalf * depth); - *ry = -yc / (gBTAimTanHalf * depth); + const float ndcX = (xc * gBTAimP11) / depth; + const float ndcY = (yc * gBTAimP22) / depth; + *rx = ndcX * (gBTAimVpW / gBTAimVpH); + *ry = -ndcY; return 1; } @@ -7254,11 +7303,17 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte // the render lists memset(mRenderLists, 0, sizeof(mRenderLists)); - // aim-ray feed (task #36): publish the live projection's FOV term so the - // game side can build the reticle pick ray / designator projection. + // aim-ray feed (task #36): publish the live projection's per-axis scales + + // the BACKBUFFER size (= the dpl2d reticle frame -- the full-screen + // viewport the 2D layer draws in) so the game side can build the reticle + // pick ray / designator projection through the TRUE NDC. NOTE: do NOT + // poll GetViewport here -- at this point it still holds whatever viewport + // the PREVIOUS frame's last pass set (gauge/MFD passes shrink it), which + // made the pick ray flicker frame to frame. { - extern void BTSetAimProjection(float proj22); - BTSetAimProjection(mProjectionMatrix._22); + extern void BTSetAimProjection(float p11, float p22, float vpW, float vpH); + BTSetAimProjection(mProjectionMatrix._11, mProjectionMatrix._22, + (float)GetWidth(), (float)GetHeight()); } HierarchicalDrawComponent *drawComp; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index f20983c..33517da 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1272,17 +1272,17 @@ void pCur(&cp) && pStc(wnd, &cp) && pCr(wnd, &rc) && rc.right > rc.left && rc.bottom > rc.top) { - const float w = (float)(rc.right - rc.left); - const float h = (float)(rc.bottom - rc.top); - const float hh = h * 0.5f; - float rx = ((float)cp.x - w * 0.5f) / hh; - float ry = ((float)cp.y - h * 0.5f) / hh; - // clamp to the visible frame (aspect-wide in x) - const float xmax = (w / h); - if (rx < -xmax) rx = -xmax; - if (rx > xmax) rx = xmax; - if (ry < -1.0f) ry = -1.0f; - if (ry > 1.0f) ry = 1.0f; + // client px -> reticle coords through the RENDERER + // (it undoes the backbuffer->client present stretch; + // a raw client-height mapping is only exact for a + // backbuffer-shaped window and skews the pick + // sideways otherwise). + extern void BTClientToReticle(float mx, float my, + float cw, float ch, float *rx, float *ry); + float rx = 0.0f, ry = 0.0f; + BTClientToReticle((float)cp.x, (float)cp.y, + (float)(rc.right - rc.left), + (float)(rc.bottom - rc.top), &rx, &ry); gBTAimX = rx; gBTAimY = ry; } @@ -2311,15 +2311,25 @@ void // resolves damage to the zone under the crosshair (aimed fire). Entity *hotTarget = 0; // mech under the crosshair NOW Point3D hotPoint; // picked world point on its hull + static int gAimNoRay = 0, gAimNoPick = 0, gAimHits = 0; // 1Hz diagnostics { extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]); float rs[3], rd[3]; - if (BTGetAimRay(gBTAimX, gBTAimY, rs, rd) && gEnemyMech != 0) + if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd)) + { + ++gAimNoRay; + } + else if (gEnemyMech != 0) { Point3D rayStart(rs[0], rs[1], rs[2]); Vector3D rayDir(rd[0], rd[1], rd[2]); if (((Mech *)gEnemyMech)->PickRayHit(rayStart, rayDir, 4000.0f, &hotPoint)) + { hotTarget = gEnemyMech; + ++gAimHits; + } + else + ++gAimNoPick; } // The Reticle struct (the mech's TargetReticle attribute): position, @@ -2396,7 +2406,10 @@ void : (designated ? " designated (off-crosshair)" : " no target")) << " range=" << range << (range <= kWeaponRange ? " IN RANGE" : "") + << " [hits=" << gAimHits << " noRay=" << gAimNoRay + << " noPick=" << gAimNoPick << "]" << "\n" << std::flush; + gAimHits = 0; gAimNoRay = 0; gAimNoPick = 0; } // --- FIRING (bring-up): on the trigger, with a target in range and the