From bcef031c85514712a66a4e6cd3a5e841fe1850e8 Mon Sep 17 00:00:00 2001 From: GenericHeroGuy Date: Wed, 31 Dec 2025 14:49:36 +0100 Subject: [PATCH] WIP affine patch drawing will probably invert the matrix --- src/hardware/hw_draw.c | 125 ++++++++++++++++++++++++++++++ src/hardware/hw_main.h | 1 + src/lua_hudlib.c | 52 +++++++++++++ src/typedef.h | 1 + src/v_video.c | 168 +++++++++++++++++++++++++++++++++++++++++ src/v_video.h | 13 ++++ 6 files changed, 360 insertions(+) diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index 2c9fb6b75..be962b161 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -265,6 +265,131 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p HWD.pfnDrawPolygon(NULL, v, 4, flags|PF_Translucent); } +void HWR_DrawAffinePatch(patch_t *gpatch, fixed_t x, fixed_t y, const affine_t *transform, INT32 option, const UINT8 *colormap) +{ + //const cliprect_t *clip = V_GetClipRect(); + + // make patch ready in hardware cache + if (!colormap) + HWR_GetPatch(gpatch); + else + HWR_GetMappedPatch(gpatch, colormap); + + // positions of the x, y, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1 + float fwidth = vid.width;// / vid.dupx; + float fheight = vid.height;// / vid.dupy; + float cx = -1.0f + (x / (fwidth/2)); + float cy = 1.0f - (y / (fheight/2)); + + float fa = FIXED_TO_FLOAT(transform->a) / vid.dupx; + float fd = FIXED_TO_FLOAT(transform->d) / vid.dupx; + float fc = FIXED_TO_FLOAT(transform->c) / vid.dupy; + float fb = FIXED_TO_FLOAT(transform->b) / vid.dupy; + float fx = FIXED_TO_FLOAT(transform->ox); + float fy = FIXED_TO_FLOAT(transform->oy); + + // now, the matrix passed to this function maps screen coordinates to texel coordinates... + // but to translate this from software to GL, we have to figure out where each corner + // (or vertex) of the patch should end up on the screen. + // which means we have to map texel coordinates to screen coordinates. + // which means we have to invert the matrix. + // how do you invert a matrix? + // ... + // i don't fucking know, i spent a day on this and got absolutely nowhere, but this guy knows: + // https://nigeltao.github.io/blog/2021/inverting-3x2-affine-transformation-matrix.html + float determinant = fa * fd - fb * fc; + if (determinant == 0.0f) + return; + float ba = fd / determinant; + float bb = -fb / determinant; + float bc = -fc / determinant; + float bd = fa / determinant; + + // set the polygon vertices to the right positions + // 3--2 + // | /| + // |/ | + // 0--1 + FOutVector v[4] = { + [3] = { .x = ba * -fx + bb * -fy + fx, + .y = bc * -fx + bd * -fy + fy }, + [2] = { .x = ba * (gpatch->width - fx) + bb * -fy + fx, + .y = bc * (gpatch->width - fx) + bd * -fy + fy }, + [0] = { .x = ba * -fx + bb * (gpatch->height - fy) + fx, + .y = bc * -fx + bd * (gpatch->height - fy) + fy }, + [1] = { .x = ba * (gpatch->width - fx) + bb * (gpatch->height - fy) + fx, + .y = bc * (gpatch->width - fx) + bd * (gpatch->height - fy) + fy }, + }; + + // normalize to -1,1 + v[0].x = cx + (v[0].x / fwidth*2); + v[1].x = cx + (v[1].x / fwidth*2); + v[2].x = cx + (v[2].x / fwidth*2); + v[3].x = cx + (v[3].x / fwidth*2); + v[0].y = cy - (v[0].y / fheight*2); + v[1].y = cy - (v[1].y / fheight*2); + v[2].y = cy - (v[2].y / fheight*2); + v[3].y = cy - (v[3].y / fheight*2); + + v[0].z = v[1].z = v[2].z = v[3].z = 1.0f; + + const GLPatch_t *hwrPatch = ((GLPatch_t *)gpatch->hardware); + float s_min = 0.f, t_min = 0.f; + float s_max = hwrPatch->max_s, t_max = hwrPatch->max_t; + + if (option & V_FLIP) + { + v[0].s = v[3].s = s_max; + v[2].s = v[1].s = s_min; + } + else + { + v[0].s = v[3].s = s_min; + v[2].s = v[1].s = s_max; + } + + if (option & V_VFLIP) + { + v[0].t = v[1].t = t_min; + v[2].t = v[3].t = t_max; + } + else + { + v[0].t = v[1].t = t_max; + v[2].t = v[3].t = t_min; + } + + FBITFIELD flags = PF_NoDepthTest; + + // clip it since it is used for bunny scroll in doom I + UINT32 alphalevel = (option & V_ALPHAMASK) >> V_ALPHASHIFT; + UINT32 blendmode = (option & V_BLENDMASK) >> V_BLENDSHIFT; + if (blendmode) + blendmode++; // realign to constants + if (alphalevel || blendmode) + { + FSurfaceInfo Surf; + Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff; + + flags |= HWR_GetBlendModeFlag(blendmode); + + if (alphalevel == 13) + Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency]; + else if (alphalevel == 14) + Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency]; + else if (alphalevel == 15) + Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency]; + else if (alphalevel < 10) + Surf.PolyColor.s.alpha = softwaretranstogl[min(max((10 - alphalevel), 0), 10)]; + else + Surf.PolyColor.s.alpha = 0; + + HWD.pfnDrawPolygon(&Surf, v, 4, flags|PF_Modulated); + } + else + HWD.pfnDrawPolygon(NULL, v, 4, flags|PF_Translucent); +} + void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h) { FOutVector v[4]; diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index f60ec4a33..2b72607ad 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -43,6 +43,7 @@ void HWR_InitTextureMapping(void); void HWR_SetViewSize(void); void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap); void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h); +void HWR_DrawAffinePatch(patch_t *gpatch, fixed_t x, fixed_t y, const affine_t *transform, INT32 option, const UINT8 *colormap); void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap); void HWR_CreatePlanePolygons(INT32 bspnum); void HWR_CreateStaticLightmaps(INT32 bspnum); diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 7350ff873..66995993d 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -705,6 +705,57 @@ static int libd_drawStretched(lua_State *L) return 0; } +static int libd_drawAffine(lua_State *L) +{ + HUDONLY + fixed_t x = luaL_checkfixed(L, 1); + fixed_t y = luaL_checkfixed(L, 2); + affine_t transform = {}; + luaL_checktype(L, 3, LUA_TTABLE); + patch_t *patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH)); + INT32 flags = luaL_optinteger(L, 5, 0); + UINT8 *colormap = NULL; + if (!lua_isnoneornil(L, 6)) + colormap = *((UINT8 **)luaL_checkudata(L, 6, META_COLORMAP)); + + lua_pushnil(L); + while (lua_next(L, 3)) + { + INT32 i = 0; + const char *k = NULL; + if (lua_isnumber(L, -2)) + i = lua_tointeger(L, -2); + else if (lua_isstring(L, -2)) + k = lua_tostring(L, -2); + + if (i == 1 || (k && fasticmp(k, "a"))) + transform.a = luaL_checkfixed(L, -1); + else if (i == 2 || (k && fasticmp(k, "b"))) + transform.b = luaL_checkfixed(L, -1); + else if (i == 3 || (k && fasticmp(k, "c"))) + transform.c = luaL_checkfixed(L, -1); + else if (i == 4 || (k && fasticmp(k, "d"))) + transform.d = luaL_checkfixed(L, -1); + else if (i == 5 || (k && fasticmp(k, "ox"))) + transform.ox = luaL_checkfixed(L, -1); + else if (i == 6 || (k && fasticmp(k, "oy"))) + transform.oy = luaL_checkfixed(L, -1); + + lua_pop(L, 1); + } + lua_pop(L, 1); + + lua_getfield(L, LUA_REGISTRYINDEX, "HUD_DRAW_LIST"); + huddrawlist_h list = lua_touserdata(L, -1); + lua_pop(L, 1); + + //if (LUA_HUD_IsDrawListValid(list)) + //LUA_HUD_AddDrawStretched(list, x, y, hscale, vscale, patch, flags, colormap); + //else + V_DrawAffinePatch(x, y, &transform, flags, patch, colormap); + return 0; +} + // KART: draw patch on minimap from x, y coordinates on the map static int libd_drawOnMinimap(lua_State *L) { @@ -1536,6 +1587,7 @@ static luaL_Reg lib_draw[] = { {"draw", libd_draw}, {"drawScaled", libd_drawScaled}, {"drawStretched", libd_drawStretched}, + {"drawAffine", libd_drawAffine}, {"drawNum", libd_drawNum}, {"drawPaddedNum", libd_drawPaddedNum}, {"drawPingNum", libd_drawPingNum}, diff --git a/src/typedef.h b/src/typedef.h index bbae58272..1d8d326bc 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -419,6 +419,7 @@ TYPEDEF (taggroup_t); // v_video.h TYPEDEF (colorlookup_t); TYPEDEF (cliprect_t); +TYPEDEF (affine_t); // w_wad.h TYPEDEF (filelump_t); diff --git a/src/v_video.c b/src/v_video.c index 6b39f4021..06940540a 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -31,6 +31,7 @@ #include "m_random.h" #include "doomstat.h" #include "r_fps.h" +#include "qs22j.h" #ifdef HWRENDER #include "hardware/hw_glob.h" @@ -797,6 +798,173 @@ static const UINT8 *v_translevel = NULL; #define TRANSLUCENTDRAW 3 #define TRANSMAPPEDDRAW 4 +/* +static int sortcoords(const void *a, const void *b) +{ + return *(const fixed_t *)a - *(const fixed_t *)b; +} +*/ + +void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 scrn, patch_t *patch, const UINT8 *colormap) +{ + if (rendermode == render_none) + return; + + INT32 dupx, dupy; + switch (scrn & V_SCALEPATCHMASK) + { + case V_NOSCALEPATCH: + dupx = dupy = 1; + break; + case V_SMALLSCALEPATCH: + dupx = vid.smalldupx; + dupy = vid.smalldupy; + break; + case V_MEDSCALEPATCH: + dupx = vid.meddupx; + dupy = vid.meddupy; + break; + default: + dupx = vid.dupx; + dupy = vid.dupy; + break; + } + + if (scrn & V_NOSCALESTART) + { + x >>= FRACBITS; + y >>= FRACBITS; + } + else + { + x = FixedMul(x,dupx<ox * (dupx-1); + y += transform->oy * (dupy-1); + x >>= FRACBITS; + y >>= FRACBITS; + + if (!(scrn & V_SCALEPATCHMASK)) // Center it if necessary + V_AdjustXYWithSnap(&x, &y, scrn, dupx, dupy); + } + +#ifdef HWRENDER + if (rendermode == render_opengl) + { + HWR_DrawAffinePatch(patch, x, y, transform, scrn, colormap); + return; + } +#endif + + Patch_GenerateFlat(patch, 0); + const UINT16 *src = patch->flats[0]; + if (src == NULL) + return; + + const fixed_t a = transform->a / dupx; + const fixed_t b = transform->b / dupx; + const fixed_t c = transform->c / dupy; + const fixed_t d = transform->d / dupy; + const fixed_t cx = transform->ox; + const fixed_t cy = transform->oy; + + const INT32 scrwidth = vid.width; + const INT32 pw = patch->width, ph = patch->height; + + /* + fixed_t determinant = FixedMul(a, d) - FixedMul(b, c); + if (determinant == 0) + return; + fixed_t ba = FixedDiv(d, determinant); + fixed_t bb = FixedDiv(-b, determinant); + fixed_t bc = FixedDiv(-c, determinant); + fixed_t bd = FixedDiv(a, determinant); + + fixed_t x1 = FixedMul(ba, -cx) + FixedMul(bb, -cy) + cx; + fixed_t y1 = FixedMul(bc, -cx) + FixedMul(bd, -cy) + cy; + fixed_t x2 = FixedMul(ba, pw*FRACUNIT - cx) + FixedMul(bb, -cy) + cx; + fixed_t y2 = FixedMul(bc, pw*FRACUNIT - cx) + FixedMul(bd, -cy) + cy; + fixed_t x3 = FixedMul(ba, -cx) + FixedMul(bb, ph*FRACUNIT - cy) + cx; + fixed_t y3 = FixedMul(bc, -cx) + FixedMul(bd, ph*FRACUNIT - cy) + cy; + fixed_t x4 = FixedMul(ba, pw*FRACUNIT - cx) + FixedMul(bb, ph*FRACUNIT - cy) + cx; + fixed_t y4 = FixedMul(bc, pw*FRACUNIT - cx) + FixedMul(bd, ph*FRACUNIT - cy) + cy; + */ + + UINT8 * const destbase = vid.screens[0] + y * vid.width + x; + INT32 dx = 0, dy = 0; + + for (dy = 0; dy < ph; dy++) + { + // yoinked from NovaSquirrel's mode 7 preview + // ...which is in turn yoinked from Mesen's S-PPU code + // i can't do matrix math to save my life :face_holding_back_tears: + // (m7xofs and m7yofs are already factored in by destbase) + fixed_t ux = FixedMul(a, -cx) + FixedMul(b, -cy) + b*dy + cx; + fixed_t uy = FixedMul(c, -cx) + FixedMul(d, -cy) + d*dy + cy; + UINT8 *dest = destbase + dy * scrwidth; + + for (dx = 0; dx < pw; dx++, dest++) + { + const INT32 srcx = ux >> FRACBITS; + const INT32 srcy = uy >> FRACBITS; + ux += a; + uy += c; + + if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) + continue; + + const UINT16 pixel = src[srcy * pw + srcx]; + if (pixel < 0xff00) + continue; + + if (colormap != NULL) + *dest = colormap[pixel & 0xff]; + else + *dest = (UINT8)(pixel & 0xff); + } + } + + /* + fixed_t xsort[4] = { x1, x2, x3, x4 }; + fixed_t ysort[4] = { y1, y2, y3, y4 }; + qs22j(xsort, 4, sizeof(*xsort), sortcoords); + qs22j(ysort, 4, sizeof(*ysort), sortcoords); + fixed_t dx1, dx2; + dx1 = FixedDiv(x1 - x2, y2 - y1); + dx2 = FixedDiv(x1 - x3, y3 - y1); + fixed_t px1 = x*FRACUNIT + x1; + fixed_t px2 = x*FRACUNIT + x2; + fixed_t py = y*FRACUNIT + y1; + for (UINT32 i = 0; i < 10; i++) + { + for (fixed_t A = px1; A < px2; A += FRACUNIT) + V_DrawFixedFill(A, py + i*FRACUNIT, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); + px1 += dx1; + px2 += dx2; + } + V_DrawFixedFill(x*FRACUNIT + x1, y*FRACUNIT + y1, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); + V_DrawFixedFill(x*FRACUNIT + x2, y*FRACUNIT + y2, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); + V_DrawFixedFill(x*FRACUNIT + x3, y*FRACUNIT + y3, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); + V_DrawFixedFill(x*FRACUNIT + x4, y*FRACUNIT + y4, FRACUNIT, FRACUNIT, V_NOSCALESTART | (leveltime&0xff)); + */ +}; + +// draws a patch rotated counter-clockwise by angle degrees +// note that scaling is applied BEFORE rotation! +void V_DrawRotatedPatch(fixed_t x, fixed_t y, angle_t angle, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap) +{ + fixed_t sa = FSIN(angle), ca = FCOS(angle); + affine_t t = { + .a = FixedDiv(ca, pscale), + .b = FixedDiv(-sa, pscale), + .c = FixedDiv(sa, vscale), + .d = FixedDiv(ca, vscale), + .ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT, + .oy = (patch->pivot.y + patch->topoffset) * FRACUNIT, + }; + V_DrawAffinePatch(x, y, &t, scrn, patch, colormap); +} + // Draws a patch scaled to arbitrary size. void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap) { diff --git a/src/v_video.h b/src/v_video.h index f0d2cbfdc..c6cd0fcca 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -215,6 +215,19 @@ void V_ClearClipRect(void); void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap); void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h); +// an affine transformation matrix. maps screen coordinates to texture coordinates +struct affine_t +{ + fixed_t a; // horizontal step per pixel (M7A) + fixed_t b; // horizontal step per scanline (M7B) + fixed_t c; // vertical step per pixel (M7C) + fixed_t d; // vertical step per scanline (M7D) + fixed_t ox, oy; // transform origin in texture coordinates (M7X/M7Y) +}; + +void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 scrn, patch_t *patch, const UINT8 *colormap); +void V_DrawRotatedPatch(fixed_t x, fixed_t y, angle_t angle, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap); + void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT16 skincolor); // Draw a linear block of pixels into the view buffer.