From bd1e0fc0135967a797a3a78291a03ec8641f0c4f Mon Sep 17 00:00:00 2001 From: yamamama Date: Sat, 7 Feb 2026 00:53:14 -0500 Subject: [PATCH 01/42] Prelim Add affine bounding struct, move affine_t to r_defs, add affine and bounding to drawcolumndata_t --- src/r_defs.h | 24 ++++++++++++++++++++++++ src/typedef.h | 3 ++- src/v_video.h | 10 ---------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index 375884378..dc24fda3d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1090,6 +1090,27 @@ struct spritedef_t spriteframe_t *spriteframes; }; +// Affine transformation bounding positions, in pixels. Used for Software to determine +// the dimensions of an affine patch. +struct affine_bounding_t +{ + INT32 l; // Leftmost bounding + INT32 r; // Rightmost bounding + + INT32 t; // Highest bounding + INT32 b; // Lowest bounding +}; + +// 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) +}; + // Column and span drawing data bundles typedef struct @@ -1123,6 +1144,9 @@ typedef struct INT32 sourcelength; UINT8 r8_flatcolor; + + affine_bounding_t affinebound; + affine_t affine; } drawcolumndata_t; extern drawcolumndata_t g_dc; diff --git a/src/typedef.h b/src/typedef.h index af50e9342..c7590d27d 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -358,6 +358,8 @@ TYPEDEF (msecnode_t); TYPEDEF (mprecipsecnode_t); TYPEDEF (lightmap_t); TYPEDEF (seg_t); +TYPEDEF (affine_t); +TYPEDEF (affine_bounding_t); // r_fps.h TYPEDEF (viewvars_t); @@ -419,7 +421,6 @@ 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.h b/src/v_video.h index 61264c092..a49d1b622 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -218,16 +218,6 @@ 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); From 40d9ce7e7117929d496fb2191ced7288ac623fcd Mon Sep 17 00:00:00 2001 From: yamamama Date: Sat, 14 Feb 2026 21:26:27 -0500 Subject: [PATCH 02/42] Make affine patch drawing use dynamic clipping The whole thing can actually draw in Software now! --- src/r_defs.h | 16 ++++-- src/v_video.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++---- src/v_video.h | 1 + 3 files changed, 148 insertions(+), 13 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index dc24fda3d..27b89a930 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1094,11 +1094,19 @@ struct spritedef_t // the dimensions of an affine patch. struct affine_bounding_t { - INT32 l; // Leftmost bounding - INT32 r; // Rightmost bounding + vector2_t pivot; - INT32 t; // Highest bounding - INT32 b; // Lowest bounding + // Differences between the "pivot" of the bound and each corner position. + INT32 xleft, yup, xright, ydown; + + // General length between each bounding position + INT32 xlen, ylen; + + INT32 l; // Leftmost bounding + INT32 r; // Rightmost bounding + + INT32 t; // Highest bounding + INT32 b; // Lowest bounding }; // an affine transformation matrix. maps screen coordinates to texture coordinates diff --git a/src/v_video.c b/src/v_video.c index ec1f477ab..18fd336a4 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -810,6 +810,106 @@ static int sortcoords(const void *a, const void *b) } */ +affine_bounding_t* V_GetAffineBounds(const affine_t* transform, + patch_t* patch, + affine_bounding_t* out) +{ + // A decent chunk of this code is just fixedpointizing stuff + // from HWR_GetAffinePatch; That system's near flawless, why fix what isn't broken? + // Kudos to Generic for making my life easier 🥹 + + // First, let's set our output to a bunch of dummy values. + + out->l = INT32_MAX; + out->t = INT32_MAX; + out->r = INT32_MIN; + out->b = INT32_MIN; + + fixed_t fa = (transform->a / vid.dup); + fixed_t fd = (transform->d / vid.dup); + fixed_t fc = (transform->c / vid.dup); + fixed_t fb = (transform->b / vid.dup); + fixed_t fx = (transform->ox); + fixed_t fy = (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 + + // ...so I thought this would be standard fare order-of-operations stuff + // NOPE! + // The compiled assembly code does THIS: + // (fa * fd) - (fb * fc) + fixed_t determinant = FixedMul(fa, fd) - FixedMul(fb, fc); + + if (determinant == 0) + return out; + + fixed_t ba = FixedDiv(fd, determinant); + fixed_t bb = FixedDiv(-fb, determinant); + fixed_t bc = FixedDiv(-fc, determinant); + fixed_t bd = FixedDiv(fa, determinant); + + // set the polygon vertices to the right positions + // 3--2 + // | /| + // |/ | + // 0--1 + fixed_t pw = (patch->width << FRACBITS); + fixed_t ph = (patch->height << FRACBITS); + vector2_t v[4] = { + [3] = {.x = (FixedMul(ba, -fx) - FixedMul(bb, fy)) + fx, + .y = (FixedMul(bc, -fx) - FixedMul(bd, fy)) + fy }, + [2] = { .x = (FixedMul(ba, (pw - fx)) - FixedMul(bb, fy)) + fx, + .y = (FixedMul(bc, (pw - fx)) - FixedMul(bd, fy)) + fy }, + [0] = { .x = (FixedMul(ba, -fx) + FixedMul(bb, (ph - fy))) + fx, + .y = (FixedMul(bc, -fx) + FixedMul(bd, (ph - fy))) + fy }, + [1] = { .x = (FixedMul(ba, (pw - fx)) + FixedMul(bb, (ph - fy))) + fx, + .y = (FixedMul(bc, (pw - fx)) + FixedMul(bd, (ph - fy))) + fy }, + }; + + // Get the leftmost and uppermost bounds of the current resolution. + const INT32 vw = vid.width, vh = vid.height; + const INT32 leftmost = ((BASEVIDWIDTH - vw) / 2), uppermost = ((BASEVIDHEIGHT - vh) / 2); + + // ...okay, now comb through all four vertices and set the output bounds based on this. + // "Why not a loop?" According to SM64 programming wizard Kaze Emanuar, + // loops take more processing time. If we can help it, it's better to just cut corners. +#define BOUNDCHECK(i) \ + { \ + out->l = max(leftmost, min(v[i].x >> FRACBITS, out->l)); \ + out->r = min(vw, max(v[i].x >> FRACBITS, out->r)); \ + out->t = max(uppermost, min(v[i].y >> FRACBITS, out->t)); \ + out->b = min(vh, max(v[i].y >> FRACBITS, out->b)); \ + } + + BOUNDCHECK(0); + BOUNDCHECK(1); + BOUNDCHECK(2); + BOUNDCHECK(3); + +#undef BOUNDCHECK + + // Cool, we have our bounds. Get the diffs so the loops have something to reference. + out->xlen = abs(out->r - out->l); + out->ylen = abs(out->b - out->t); + + out->xleft = (fx >> FRACBITS) - out->l; + out->xright = out->r - (fx >> FRACBITS); + + out->yup = (fy >> FRACBITS) - out->t; + out->ydown = out->b - (fy >> FRACBITS); + + return out; +} + 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) @@ -858,6 +958,9 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc } #endif + affine_bounding_t bounds = {0}; + V_GetAffineBounds(transform, patch, &bounds); + Patch_GenerateFlat(patch, 0); const UINT16 *src = patch->flats[0]; if (src == NULL) @@ -867,8 +970,8 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc const fixed_t b = transform->b / dup; const fixed_t c = transform->c / dup; const fixed_t d = transform->d / dup; - const fixed_t cx = transform->ox; - const fixed_t cy = transform->oy; + fixed_t cx = transform->ox; + fixed_t cy = transform->oy; const INT32 scrwidth = vid.width; const INT32 pw = patch->width, ph = patch->height; @@ -892,20 +995,43 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc 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; + INT32 ydiff = (bounds.yup - (transform->oy >> FRACBITS)); + INT32 xdiff = (bounds.xleft - (transform->ox >> FRACBITS)); - for (dy = 0; dy < ph; dy++) + // Get the clipping values, since that can vary per-resolution. + INT32 yclip = min(y - ydiff, 0) * -1; + INT32 xclip = min(x - xdiff, 0) * -1; + + ydiff -= yclip; + xdiff -= xclip; + + // Get the leftmost and uppermost bounds of the current resolution. + const INT32 vw = vid.width, vh = vid.height; + + INT32 yy = CLAMP(y - ydiff, 0, vh); + INT32 xx = CLAMP(x - xdiff, 0, vw); + + INT32 xmax = max(bounds.xlen - xclip, pw), ymax = max(bounds.ylen - yclip, ph); + + // Offset our X and Y positions by the bounding differences. + fixed_t cxx = cx + (xdiff * FRACUNIT); + fixed_t cyy = cy + (ydiff * FRACUNIT); + + intptr_t dest_y = (intptr_t)(vid.screens[0]) + yy * vw; + + UINT8 * const destbase = (UINT8 * const)(dest_y + xx); + INT32 dx = 0, dy = 0; + for (dy = 0; dy < ymax; 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; + fixed_t ux = (FixedMul(a, -cxx) + FixedMul(b, -cyy) + b*(dy) + cx); + fixed_t uy = FixedMul(c, -cxx) + FixedMul(d, -cyy) + d*(dy) + cy; + UINT8 *dest = (destbase) + (dy) * scrwidth; - for (dx = 0; dx < pw; dx++, dest++) + for (dx = 0; dx < xmax; dx++, dest++) { const INT32 srcx = ux >> FRACBITS; const INT32 srcy = uy >> FRACBITS; diff --git a/src/v_video.h b/src/v_video.h index a49d1b622..fb40f6afc 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -218,6 +218,7 @@ 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); +affine_bounding_t *V_GetAffineBounds(const affine_t *transform, patch_t *patch, affine_bounding_t *out); 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); From 1996395de8f5de31a0c20cad8295fec03a32ce61 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 15 Feb 2026 13:09:45 -0500 Subject: [PATCH 03/42] Affine sprite drawing Kudos to Generic --- src/r_defs.h | 1 + src/r_draw.h | 2 + src/r_draw_column.cpp | 89 ++++++++++++++++++++++++++++++ src/r_things.cpp | 122 +++++++++++++++++++++++++++++++++--------- src/r_things.h | 1 + src/screen.c | 2 + 6 files changed, 191 insertions(+), 26 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index 27b89a930..03992f22d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1155,6 +1155,7 @@ typedef struct affine_bounding_t affinebound; affine_t affine; + fixed_t frac; } drawcolumndata_t; extern drawcolumndata_t g_dc; diff --git a/src/r_draw.h b/src/r_draw.h index 6a742c609..332413160 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -74,6 +74,7 @@ enum COLDRAWFUNC_TWOSMULTIPATCHTRANS, COLDRAWFUNC_FOG, COLDRAWFUNC_DROPSHADOW, + COLDRAWFUNC_AFFINE, COLDRAWFUNC_MAX }; @@ -201,6 +202,7 @@ void R_Draw2sMultiPatchTranslucentColumn_Flush(drawcolumndata_t* dc); void R_DrawFogColumn(drawcolumndata_t* dc); void R_DrawColumnShadowed(drawcolumndata_t* dc); +void R_DrawAffineColumn(drawcolumndata_t* dc); void R_DrawColumn_Brightmap(drawcolumndata_t* dc); void R_DrawTranslucentColumn_Brightmap(drawcolumndata_t* dc); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 5fb70446d..d32cfaf95 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -483,3 +483,92 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc) } while (--count); } + +void R_DrawAffineColumn(drawcolumndata_t *dc) +{ + INT32 count; + const INT32 vidheight = vid.height; + + // leban 1/17/99: + // removed the + 1 here, adjusted the if test, and added an increment + // later. this helps a compiler pipeline a bit better. the x86 + // assembler also does this. + count = dc->yh - dc->yl; + + // leban 1/17/99: + // this case isn't executed too often. depending on how many instructions + // there are between here and the second if test below, this case could + // be moved down and might save instructions overall. since there are + // probably different wads that favor one way or the other, i'll leave + // this alone for now. + if (count < 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if ((unsigned)dc->x >= (unsigned)vid.width || dc->yl < 0 || dc->yh >= vidheight) + { + return; + } + + { + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + + intptr_t frac; + // Looks familiar. + const intptr_t fracstep = dc->iscale; + const intptr_t heightmask = dc->sourcelength-1; // CPhipps - specify type + constexpr INT32 npow2min = -1; + const INT32 npow2max = dc->sourcelength; + + // Framebuffer destination address. + // SoM: MAGIC + UINT8 *restrict dest = R_Address(dc->x, dc->yl); + + const INT32 stride = vid.width; // SoM: Oh, Oh it's MAGIC! You know... + + count++; + + // Determine scaling, which is the only mapping to be done. + frac = (dc->texturemid + FixedMul((dc->yl << FRACBITS) - centeryfrac, fracstep)); + + const affine_t *transform = &dc->affine; + + const fixed_t a = transform->a; + const fixed_t b = transform->b; + const fixed_t c = transform->c; + const fixed_t d = transform->d; + fixed_t cx = transform->ox; + fixed_t cy = transform->oy; + + const INT32 pw = dc->sourcelength, ph = dc->texheight; + + // 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(b, -cx) + FixedMul(a, -cy) + FixedMul(a, dc->frac) + cx; + fixed_t uy = FixedMul(d, -cx) + FixedMul(c, -cy) + FixedMul(c, dc->frac) + cy; + + for (; count > 0; dest += stride, --count) + { + const INT32 srcx = ux >> FRACBITS; + const INT32 srcy = uy >> FRACBITS; + ux += b; + uy += d; + + if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) + continue; + + const UINT16 pixel = reinterpret_cast(dc->source)[srcy * pw + srcx]; + if (pixel < 0xff00) + continue; + + *dest = (UINT8)(pixel & 0xff); + } + } +} diff --git a/src/r_things.cpp b/src/r_things.cpp index 08042ba65..381f692e9 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1010,10 +1010,14 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.fullbright = colormaps; dc.translation = R_GetSpriteTranslation(vis); + if (vis->cut & SC_AFFINE) + { + R_SetColumnFunc(COLDRAWFUNC_AFFINE, false); + } // Hack: Use a special column function for drop shadows that bypasses // invalid memory access crashes caused by R_ProjectDropShadow putting wrong values // in dc_texturemid and dc_iscale when the shadow is sloped. - if (vis->cut & SC_SHADOW) + else if (vis->cut & SC_SHADOW) { R_SetColumnFunc(COLDRAWFUNC_DROPSHADOW, false); dc.transmap = vis->transmap; @@ -1112,8 +1116,74 @@ static void R_DrawVisSprite(vissprite_t *vis) localcolfunc = (vis->cut & SC_VFLIP) ? R_DrawFlippedMaskedColumn : R_DrawMaskedColumn; lengthcol = patch->height; + if (vis->cut & SC_AFFINE) + { + Patch_GenerateFlat(patch, static_cast(0)); + dc.source = static_cast(patch->flats[0]); + dc.sourcelength = patch->width; + + // Non-paper drawing loop + for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) + { + //texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); + + //column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); + + //if (bmpatch) + //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); + + dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = ((sprtopscreen + spryscale*patch->height)-1)>>FRACBITS; + + INT32 topscreen = sprtopscreen + spryscale*0; + INT32 bottomscreen = topscreen + spryscale*patch->height; + + dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = (bottomscreen-1)>>FRACBITS; + + if (windowtop != INT32_MAX && windowbottom != INT32_MAX) + { + if (windowtop > topscreen) + dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; + if (windowbottom < bottomscreen) + dc.yh = (windowbottom - 1)>>FRACBITS; + } + + if (dc.yh >= mfloorclip[dc.x]) + dc.yh = mfloorclip[dc.x]-1; + if (dc.yl <= mceilingclip[dc.x]) + dc.yl = mceilingclip[dc.x]+1; + + if (dc.yl < 0) + dc.yl = 0; + if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop + dc.yh = vid.height - 1; + + if (dc.yh >= baseclip && baseclip != -1) + dc.yh = baseclip; + + dc.affine.a = FRACUNIT; + dc.affine.b = 0; + dc.affine.c = 0; + dc.affine.d = FRACUNIT; + dc.affine.ox = 0; + dc.affine.oy = 0; + dc.frac = frac; + + angle_t angle = leveltime*ANG2; + fixed_t sa = FSIN(angle), ca = FCOS(angle); + dc.affine.a = FixedDiv(ca, FRACUNIT); + dc.affine.b = FixedDiv(-sa, FRACUNIT); + dc.affine.c = FixedDiv(sa, FRACUNIT); + dc.affine.d = FixedDiv(ca, FRACUNIT); + dc.affine.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; + dc.affine.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; + + R_DrawAffineColumn(&dc); + } + } // Split drawing loops for paper and non-paper to reduce conditional checks per sprite - if (vis->scalestep) + else if (vis->scalestep) { fixed_t horzscale = FixedMul(vis->spritexscale, this_scale); fixed_t scalestep = FixedMul(vis->scalestep, vis->spriteyscale); @@ -1162,40 +1232,38 @@ static void R_DrawVisSprite(vissprite_t *vis) } else { - - #if 0 +#if 0 if (vis->x1test && vis->x2test) { - INT32 x1test = vis->x1test; - INT32 x2test = vis->x2test; + INT32 x1test = vis->x1test; + INT32 x2test = vis->x2test; - if (x1test < 0) - x1test = 0; + if (x1test < 0) + x1test = 0; - if (x2test >= vidwidth) - x2test = vidwidth-1; + if (x2test >= vidwidth) + x2test = vidwidth-1; - const INT32 t = (vis->startfrac + (vis->xiscale * (x2test - x1test))) >> FRACBITS; + const INT32 t = (vis->startfrac + (vis->xiscale * (x2test - x1test))) >> FRACBITS; - if (x1test <= x2test && (t < 0 || t >= patch->width)) + if (x1test <= x2test && (t < 0 || t >= patch->width)) + { + CONS_Printf("THE GAME WOULD HAVE CRASHED, %d (old) vs %d (new)\n", (x2test - x1test), (vis->x2 - vis->x1)); + } + } +#endif + // Non-paper drawing loop + for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) { - CONS_Printf("THE GAME WOULD HAVE CRASHED, %d (old) vs %d (new)\n", (x2test - x1test), (vis->x2 - vis->x1)); - } - } - #endif + texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); - // Non-paper drawing loop - for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) - { - texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); + column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); - column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); + if (bmpatch) + bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - if (bmpatch) - bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - - localcolfunc (&dc, column, bmcol, baseclip); - } + localcolfunc (&dc, column, bmcol, baseclip); + } } R_SetColumnFunc(BASEDRAWFUNC, false); @@ -2632,6 +2700,8 @@ static void R_ProjectSprite(mobj_t *thing) vis->cut = static_cast(vis->cut | SC_VFLIP); if (splat) vis->cut = static_cast(vis->cut | SC_SPLAT); // I like ya cut g + if (thing->player != NULL) + vis->cut = static_cast(vis->cut | SC_AFFINE); // *smack* AIEEEEEEEEEEEE vis->patch = patch; vis->bright = R_CacheSpriteBrightMap(sprinfo, frame); diff --git a/src/r_things.h b/src/r_things.h index 4157a9a05..dfbc51323 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -159,6 +159,7 @@ typedef enum SC_SEMIBRIGHT = 1<<12, SC_BBOX = 1<<13, SC_CULL = 1<<14, + SC_AFFINE = 1<<15, // masks SC_CUTMASK = SC_TOP|SC_BOTTOM, SC_FLAGMASK = ~SC_CUTMASK diff --git a/src/screen.c b/src/screen.c index dd376f7c1..565c73972 100644 --- a/src/screen.c +++ b/src/screen.c @@ -132,6 +132,7 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn; colfuncs[COLDRAWFUNC_DROPSHADOW] = R_DrawDropShadowColumn; + colfuncs[COLDRAWFUNC_AFFINE] = R_DrawAffineColumn; colfuncs_bm[BASEDRAWFUNC] = R_DrawColumn_Brightmap; colfuncs_bm[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_Brightmap; @@ -142,6 +143,7 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs_bm[COLDRAWFUNC_TWOSMULTIPATCHTRANS] = R_Draw2sMultiPatchTranslucentColumn_Brightmap; colfuncs_bm[COLDRAWFUNC_FOG] = NULL; // Not needed colfuncs_bm[COLDRAWFUNC_DROPSHADOW] = NULL; // Not needed + colfuncs_bm[COLDRAWFUNC_AFFINE] = NULL; // Is needed but I don't care spanfuncs[BASEDRAWFUNC] = R_DrawSpan; spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan; From 3c961033ca17fe15ce7b8e3a2881bb55fb8992dc Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 22 Feb 2026 01:02:07 -0500 Subject: [PATCH 04/42] patrick_mouth --- src/r_draw_column.cpp | 48 ++++++++++-- src/r_things.cpp | 168 +++++++++++++++++++++++++++++++----------- src/r_things.h | 6 ++ src/v_video.c | 11 +-- src/v_video.h | 5 +- 5 files changed, 182 insertions(+), 56 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index d32cfaf95..efbb69066 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -484,6 +484,9 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc) while (--count); } +// Purely for debug; fills all columns in the drawing area with a pair of "background colors" +#define FILL_DRAW_AREA + void R_DrawAffineColumn(drawcolumndata_t *dc) { INT32 count; @@ -537,22 +540,35 @@ void R_DrawAffineColumn(drawcolumndata_t *dc) frac = (dc->texturemid + FixedMul((dc->yl << FRACBITS) - centeryfrac, fracstep)); const affine_t *transform = &dc->affine; + const affine_bounding_t *bounds = &dc->affinebound; const fixed_t a = transform->a; const fixed_t b = transform->b; const fixed_t c = transform->c; const fixed_t d = transform->d; - fixed_t cx = transform->ox; - fixed_t cy = transform->oy; + fixed_t cx = (bounds->xlen * FRACHALF); //transform->ox; + fixed_t cy = (bounds->ylen * FRACHALF); //transform->oy; const INT32 pw = dc->sourcelength, ph = dc->texheight; - // yoinked from NovaSquirrel's mode 7 preview + INT32 ydiff = (bounds->yup - (transform->oy >> FRACBITS)); + INT32 xdiff = (bounds->xleft - (transform->ox >> FRACBITS)); + + vector2_t centering; + + centering.x = (bounds->xlen * FRACHALF); + centering.y = (bounds->ylen * FRACHALF); + + // Offset our X and Y positions by the bounding differences. + fixed_t cxx = (xdiff * FRACUNIT); + fixed_t cyy = 0; //+ (ydiff * FRACUNIT); + + // yoinked from NovaSquirrel's mode 7 0preview // ...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(b, -cx) + FixedMul(a, -cy) + FixedMul(a, dc->frac) + cx; - fixed_t uy = FixedMul(d, -cx) + FixedMul(c, -cy) + FixedMul(c, dc->frac) + cy; + fixed_t ux = FixedMul(b, -cxx) + FixedMul(a, -cyy) + FixedMul(a, dc->frac);// + cx; + fixed_t uy = FixedMul(d, -cxx) + FixedMul(c, -cyy) + FixedMul(c, dc->frac) + cy; for (; count > 0; dest += stride, --count) { @@ -561,12 +577,30 @@ void R_DrawAffineColumn(drawcolumndata_t *dc) ux += b; uy += d; - if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) - continue; +#ifdef FILL_DRAW_AREA + UINT16 pixel; +#endif + if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) + { +#ifdef FILL_DRAW_AREA + pixel = 65461; + *dest = (UINT8)(pixel & 0xff); +#endif + continue; + } + +#ifdef FILL_DRAW_AREA + pixel = reinterpret_cast(dc->source)[srcy * pw + srcx]; + if (pixel < 0xff00) + { + pixel = 65315; // Uhhhhhhhh + } +#else const UINT16 pixel = reinterpret_cast(dc->source)[srcy * pw + srcx]; if (pixel < 0xff00) continue; +#endif *dest = (UINT8)(pixel & 0xff); } diff --git a/src/r_things.cpp b/src/r_things.cpp index 381f692e9..12d668517 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -39,6 +39,7 @@ #include "d_netfil.h" // blargh. for nameonly(). #include "m_cheat.h" // objectplace #include "p_local.h" // stplyr +#include "v_video.h" // V_GetAffineBounds #include "core/thread_pool.h" #ifdef HWRENDER #include "hardware/hw_md2.h" @@ -946,6 +947,25 @@ UINT32 R_GetThingTransTable(fixed_t alpha, UINT32 transmap) return (20*(FRACUNIT - ((alpha * (10 - transmap))/10) - 1) + FRACUNIT) >> (FRACBITS+1); } +static void R_CopyAffineBounds(affine_bounding_t *src, affine_bounding_t *dest) +{ + // Bounding points + dest->l = src->l; + dest->r = src->r; + dest->t = src->t; + dest->b = src->b; + + // Differences from pivot point + dest->xleft = src->xleft; + dest->xright = src->xright; + dest->yup = src->yup; + dest->ydown = src->ydown; + + // Length values + dest->xlen = src->xlen; + dest->ylen = src->ylen; +} + // // R_DrawVisSprite // mfloorclip and mceilingclip should also be set. @@ -1122,8 +1142,19 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.source = static_cast(patch->flats[0]); dc.sourcelength = patch->width; + dc.affine.a = vis->affine.transform.a; + dc.affine.b = vis->affine.transform.b; + dc.affine.c = vis->affine.transform.c; + dc.affine.d = vis->affine.transform.d; + dc.affine.ox = vis->affine.transform.ox; + dc.affine.oy = vis->affine.transform.oy; + + R_CopyAffineBounds(&vis->affine.bounds, &dc.affinebound); + + fixed_t xstep = intsign(vis->xiscale) * FRACUNIT; + // Non-paper drawing loop - for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) + for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += xstep, sprtopscreen += vis->shear.tan) { //texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); @@ -1133,52 +1164,37 @@ static void R_DrawVisSprite(vissprite_t *vis) //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = ((sprtopscreen + spryscale*patch->height)-1)>>FRACBITS; + dc.yh = ((sprtopscreen + spryscale*dc.affinebound.ylen)-1)>>FRACBITS; INT32 topscreen = sprtopscreen + spryscale*0; - INT32 bottomscreen = topscreen + spryscale*patch->height; + INT32 bottomscreen = topscreen + spryscale*dc.affinebound.ylen; - dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = (bottomscreen-1)>>FRACBITS; + dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = (bottomscreen-1)>>FRACBITS; - if (windowtop != INT32_MAX && windowbottom != INT32_MAX) - { - if (windowtop > topscreen) - dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; - if (windowbottom < bottomscreen) - dc.yh = (windowbottom - 1)>>FRACBITS; - } + if (windowtop != INT32_MAX && windowbottom != INT32_MAX) + { + if (windowtop > topscreen) + dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; + if (windowbottom < bottomscreen) + dc.yh = (windowbottom - 1)>>FRACBITS; + } - if (dc.yh >= mfloorclip[dc.x]) - dc.yh = mfloorclip[dc.x]-1; - if (dc.yl <= mceilingclip[dc.x]) - dc.yl = mceilingclip[dc.x]+1; + if (dc.yh >= mfloorclip[dc.x]) + dc.yh = mfloorclip[dc.x]-1; + if (dc.yl <= mceilingclip[dc.x]) + dc.yl = mceilingclip[dc.x]+1; - if (dc.yl < 0) - dc.yl = 0; - if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop - dc.yh = vid.height - 1; + if (dc.yl < 0) + dc.yl = 0; + if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop + dc.yh = vid.height - 1; - if (dc.yh >= baseclip && baseclip != -1) - dc.yh = baseclip; + if (dc.yh >= baseclip && baseclip != -1) + dc.yh = baseclip; - dc.affine.a = FRACUNIT; - dc.affine.b = 0; - dc.affine.c = 0; - dc.affine.d = FRACUNIT; - dc.affine.ox = 0; - dc.affine.oy = 0; dc.frac = frac; - angle_t angle = leveltime*ANG2; - fixed_t sa = FSIN(angle), ca = FCOS(angle); - dc.affine.a = FixedDiv(ca, FRACUNIT); - dc.affine.b = FixedDiv(-sa, FRACUNIT); - dc.affine.c = FixedDiv(sa, FRACUNIT); - dc.affine.d = FixedDiv(ca, FRACUNIT); - dc.affine.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; - dc.affine.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; - R_DrawAffineColumn(&dc); } } @@ -1906,6 +1922,12 @@ static void R_ProjectSprite(mobj_t *thing) interpmobjstate_t interp = {0}; mobj_t *interptarg = thing; + // Affines + boolean affinesprite = (thing->player != NULL); + affine_t affine_transform = {0}; + affine_bounding_t affine_bounds = {0}; + vector2_t affine_scale = {0}; + if (R_IsOverlayingSMonitorPlayer(thing)) { // Kill overlay misalignment @@ -1944,8 +1966,22 @@ static void R_ProjectSprite(mobj_t *thing) return; // aspect ratio stuff - xscale = FixedDiv(projection[viewssnum], tz); - sortscale = FixedDiv(projectiony[viewssnum], tz); + + + if (affinesprite) + { + xscale = FRACUNIT; + sortscale = FRACUNIT; + + affine_scale.x = FixedDiv(projection[viewssnum], tz); + affine_scale.y = FixedDiv(projectiony[viewssnum], tz); + } + else + { + xscale = FixedDiv(projection[viewssnum], tz); + sortscale = FixedDiv(projectiony[viewssnum], tz); + } + // decide which patch to use for sprite relative to player #ifdef RANGECHECK @@ -2071,7 +2107,8 @@ static void R_ProjectSprite(mobj_t *thing) spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp); if (spriterotangle - && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) + && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)) + && (!affinesprite)) // Affines are capable of rotation; this is redundant { if ((papersprite && ang >= ANGLE_180) != vflip) { @@ -2184,8 +2221,37 @@ static void R_ProjectSprite(mobj_t *thing) } #endif - offset = FixedMul(offset, FixedMul(spritexscale, this_scale)); - offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale)); + if (affinesprite) + { + affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); + + angle_t angle = spriterotangle; //leveltime*ANG2; + fixed_t sa = FSIN(angle), ca = FCOS(angle); + affine_transform.a = FixedDiv(ca, FRACUNIT); + affine_transform.b = FixedDiv(-sa, FRACUNIT); + affine_transform.c = FixedDiv(sa, FRACUNIT); + affine_transform.d = FixedDiv(ca, FRACUNIT); + affine_transform.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; + affine_transform.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; + + V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); + + spr_width = (affine_bounds.xlen * FRACUNIT); + spr_height = (affine_bounds.ylen * FRACUNIT); + + INT32 xdiff = (affine_bounds.xleft - (affine_transform.ox >> FRACBITS)); + + offset -= (xdiff * FRACUNIT); + offset2 = spr_width; + + spritexscale = FRACUNIT; + spriteyscale = FRACUNIT; + } + else + { + offset = FixedMul(offset, FixedMul(spritexscale, this_scale)); + offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale)); + } if (papersprite) { @@ -2607,6 +2673,22 @@ static void R_ProjectSprite(mobj_t *thing) vis->viewpoint.z = viewz; vis->viewpoint.angle = viewangle; + vis->affine = {0}; + + if (affinesprite) + { + vis->affine.rollangle = spriterotangle; + + vis->affine.transform.a = affine_transform.a; + vis->affine.transform.b = affine_transform.b; + vis->affine.transform.c = affine_transform.c; + vis->affine.transform.d = affine_transform.d; + vis->affine.transform.ox = affine_transform.ox; + vis->affine.transform.oy = affine_transform.oy; + + R_CopyAffineBounds(&affine_bounds, &vis->affine.bounds); + } + vis->mobj = thing; // Easy access! Tails 06-07-2002 vis->x1 = x1 < portalclipstart ? portalclipstart : x1; @@ -2700,7 +2782,7 @@ static void R_ProjectSprite(mobj_t *thing) vis->cut = static_cast(vis->cut | SC_VFLIP); if (splat) vis->cut = static_cast(vis->cut | SC_SPLAT); // I like ya cut g - if (thing->player != NULL) + if (affinesprite) vis->cut = static_cast(vis->cut | SC_AFFINE); // *smack* AIEEEEEEEEEEEE vis->patch = patch; diff --git a/src/r_things.h b/src/r_things.h index dfbc51323..fb9e91f62 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -207,6 +207,12 @@ struct vissprite_t INT32 offset; // The center of the shearing location offset from x1 } shear; + struct { + angle_t rollangle; // Affine rotation angle + affine_t transform; // The actual affine transformation. + affine_bounding_t bounds; // The "bounding box" (draw area) of the affine sprite. + } affine; + fixed_t texturemid; patch_t *patch; patch_t *bright; diff --git a/src/v_video.c b/src/v_video.c index 18fd336a4..48bb5c734 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -812,6 +812,7 @@ static int sortcoords(const void *a, const void *b) affine_bounding_t* V_GetAffineBounds(const affine_t* transform, patch_t* patch, + fixed_t divisor, affine_bounding_t* out) { // A decent chunk of this code is just fixedpointizing stuff @@ -825,10 +826,10 @@ affine_bounding_t* V_GetAffineBounds(const affine_t* transform, out->r = INT32_MIN; out->b = INT32_MIN; - fixed_t fa = (transform->a / vid.dup); - fixed_t fd = (transform->d / vid.dup); - fixed_t fc = (transform->c / vid.dup); - fixed_t fb = (transform->b / vid.dup); + fixed_t fa = FixedDiv(transform->a, divisor); + fixed_t fd = FixedDiv(transform->d, divisor); + fixed_t fc = FixedDiv(transform->c, divisor); + fixed_t fb = FixedDiv(transform->b, divisor); fixed_t fx = (transform->ox); fixed_t fy = (transform->oy); @@ -959,7 +960,7 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc #endif affine_bounding_t bounds = {0}; - V_GetAffineBounds(transform, patch, &bounds); + V_GetAffineBounds(transform, patch, vid.dup * FRACUNIT, &bounds); Patch_GenerateFlat(patch, 0); const UINT16 *src = patch->flats[0]; diff --git a/src/v_video.h b/src/v_video.h index fb40f6afc..264ff55b1 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -218,7 +218,10 @@ 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); -affine_bounding_t *V_GetAffineBounds(const affine_t *transform, patch_t *patch, affine_bounding_t *out); +affine_bounding_t* V_GetAffineBounds(const affine_t* transform, + patch_t* patch, + fixed_t divisor, + affine_bounding_t* out); 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); From b86e3416e53c663e6bf77fcd17eb275d58093f76 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 22 Feb 2026 15:06:11 -0500 Subject: [PATCH 05/42] Draw area system works, need to fix alignment --- src/p_tick.c | 8 ++++ src/r_draw_column.cpp | 25 +++++------ src/r_main.cpp | 13 ++++++ src/r_main.h | 4 ++ src/r_things.cpp | 97 ++++++++++++++++++++++++++----------------- src/r_things.h | 2 + 6 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index 929615fa2..3b47f7c03 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -884,6 +884,14 @@ void P_Ticker(boolean run) { leveltime++; +#ifdef AFFINEROT_TESTER + if (cv_affinerottest.value == 2) + { + fixed_t newaffineangle = (cv_affineangle.value + (FRACUNIT)) % (360 * FRACUNIT); + CV_StealthSet(&cv_affineangle, va("%f", FIXED_TO_FLOAT(newaffineangle))); + } +#endif + if (starttime > introtime && leveltime == starttime) { ACS_RunRaceStartScript(); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index efbb69066..ff11428b3 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -21,6 +21,7 @@ // a has a constant z depth from top to bottom. // +#include "r_main.h" #include "r_draw.h" #include @@ -546,29 +547,29 @@ void R_DrawAffineColumn(drawcolumndata_t *dc) const fixed_t b = transform->b; const fixed_t c = transform->c; const fixed_t d = transform->d; - fixed_t cx = (bounds->xlen * FRACHALF); //transform->ox; - fixed_t cy = (bounds->ylen * FRACHALF); //transform->oy; + fixed_t cx = transform->ox; + fixed_t cy = transform->oy; const INT32 pw = dc->sourcelength, ph = dc->texheight; - INT32 ydiff = (bounds->yup - (transform->oy >> FRACBITS)); - INT32 xdiff = (bounds->xleft - (transform->ox >> FRACBITS)); + fixed_t ydiff = (bounds->yup * FRACUNIT) - cy; + fixed_t xdiff = (bounds->xleft * FRACUNIT) - cx; - vector2_t centering; - - centering.x = (bounds->xlen * FRACHALF); - centering.y = (bounds->ylen * FRACHALF); + xdiff -= (xdiff ? FRACUNIT : 0); + ydiff -= (ydiff ? FRACUNIT : 0); // Offset our X and Y positions by the bounding differences. - fixed_t cxx = (xdiff * FRACUNIT); - fixed_t cyy = 0; //+ (ydiff * FRACUNIT); + fixed_t cxx = cx + xdiff; + fixed_t cyy = cy + ydiff; + + //I_OutputMsg("xdiff: %f, ydiff: %f\n", FIXED_TO_FLOAT(xdiff), FIXED_TO_FLOAT(ydiff)); // yoinked from NovaSquirrel's mode 7 0preview // ...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(b, -cxx) + FixedMul(a, -cyy) + FixedMul(a, dc->frac);// + cx; - fixed_t uy = FixedMul(d, -cxx) + FixedMul(c, -cyy) + FixedMul(c, dc->frac) + cy; + fixed_t ux = FixedMul(b, -cyy) + FixedMul(a, -cxx) + FixedMul(a, dc->frac) + cx; + fixed_t uy = FixedMul(d, -cyy) + FixedMul(c, -cxx) + FixedMul(c, dc->frac) + cy; for (; count > 0; dest += stride, --count) { diff --git a/src/r_main.cpp b/src/r_main.cpp index e1192ad93..1da915fcd 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -197,6 +197,14 @@ consvar_t cv_sliptidetilt = CVAR_INIT ("sliptidetilt", "On", CV_SAVE, CV_OnOff, consvar_t cv_nulldriftefx = CVAR_INIT ("nulldriftefx", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff, NULL); +static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; +static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; + +#ifdef AFFINEROT_TESTER +consvar_t cv_affineangle = CVAR_INIT ("affineangle", "0", CV_FLOAT, affineangle_cons_t, NULL); +consvar_t cv_affinerottest = CVAR_INIT ("affinerottest", "Off", CV_FLOAT, affinetest_cons_t, NULL); +#endif + void SplitScreen_OnChange(void) { UINT8 i; @@ -1812,6 +1820,11 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_playerfade); +#ifdef AFFINEROT_TESTER + CV_RegisterVar(&cv_affineangle); + CV_RegisterVar(&cv_affinerottest); +#endif + CV_RegisterVar(&cv_movebob); // Frame interpolation/uncapped diff --git a/src/r_main.h b/src/r_main.h index 57889b115..115017f34 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -161,6 +161,10 @@ extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; +#ifdef AFFINEROT_TESTER +extern consvar_t cv_affineangle, cv_affinerottest; +#endif + // debugging typedef enum { diff --git a/src/r_things.cpp b/src/r_things.cpp index 12d668517..e45d8d441 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -986,6 +986,8 @@ static void R_DrawVisSprite(vissprite_t *vis) drawcolumndata_t dc {0}; const INT32 vidwidth = vid.width; + fixed_t maxpatchwidth = (patch->width << FRACBITS); + if (!patch) return; @@ -1001,10 +1003,16 @@ static void R_DrawVisSprite(vissprite_t *vis) if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) return; // ditto } + if (vis->cut & SC_AFFINE) + { + // Resize the max horizontal bounds so we can draw the whole affine patch. + maxpatchwidth = std::max(maxpatchwidth, vis->affine.bounds.xlen * FRACUNIT); + } + // TODO This check should not be necessary. But Papersprites near to the camera will sometimes create invalid values // for the vissprite's startfrac. This happens because they are not depth culled like other sprites. // Someone who is more familiar with papersprites pls check and try to fix <3 - if (vis->startfrac < 0 || vis->startfrac > (patch->width << FRACBITS)) + if (vis->startfrac < 0 || vis->startfrac > maxpatchwidth) { // never draw vissprites with startfrac out of patch range return; @@ -1104,9 +1112,11 @@ static void R_DrawVisSprite(vissprite_t *vis) spryscale = vis->scale; + fixed_t ymovescale = (vis->cut & SC_AFFINE) ? vis->affine.scaling.y : spryscale; + if (!(vis->scalestep)) { - sprtopscreen = centeryfrac - FixedMul(dc.texturemid, spryscale); + sprtopscreen = centeryfrac - FixedMul(dc.texturemid, ymovescale); sprtopscreen += vis->shear.tan * vis->shear.offset; dc.iscale = FixedDiv(FRACUNIT, vis->scale); } @@ -1970,11 +1980,11 @@ static void R_ProjectSprite(mobj_t *thing) if (affinesprite) { - xscale = FRACUNIT; - sortscale = FRACUNIT; - affine_scale.x = FixedDiv(projection[viewssnum], tz); affine_scale.y = FixedDiv(projectiony[viewssnum], tz); + + xscale = FRACUNIT; + sortscale = affine_scale.y; } else { @@ -2180,6 +2190,44 @@ static void R_ProjectSprite(mobj_t *thing) } #endif + if (affinesprite) + { + affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); + affine_scale.y = FixedMul(affine_scale.y, FixedMul(spriteyscale, this_scale)); + + angle_t angle; + + INT32 flipsign = ((flip) ? -1 : 1); + +#ifdef AFFINEROT_TESTER + if (!cv_affinerottest.value) + angle = spriterotangle * flipsign; + else + angle = FixedAngle(cv_affineangle.value); +#else + angle = spriterotangle * flipsign; +#endif + + fixed_t sa = FSIN(angle), ca = FCOS(angle); + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.x); + affine_transform.c = FixedDiv(sa, affine_scale.y); + affine_transform.d = FixedDiv(ca, affine_scale.y); + affine_transform.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; + affine_transform.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; + + V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); + + spr_width = (affine_bounds.xlen * FRACUNIT); + spr_offset = (affine_bounds.xleft * FRACUNIT); + + //spr_height = (affine_bounds.ylen * FRACUNIT); + //spr_topoffset = (affine_bounds.yup * FRACUNIT); + + spritexscale = FRACUNIT; + spriteyscale = FRACUNIT; + } + if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = FixedDiv(interp.spritexoffset, highresscale); @@ -2221,37 +2269,8 @@ static void R_ProjectSprite(mobj_t *thing) } #endif - if (affinesprite) - { - affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); - - angle_t angle = spriterotangle; //leveltime*ANG2; - fixed_t sa = FSIN(angle), ca = FCOS(angle); - affine_transform.a = FixedDiv(ca, FRACUNIT); - affine_transform.b = FixedDiv(-sa, FRACUNIT); - affine_transform.c = FixedDiv(sa, FRACUNIT); - affine_transform.d = FixedDiv(ca, FRACUNIT); - affine_transform.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; - affine_transform.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; - - V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); - - spr_width = (affine_bounds.xlen * FRACUNIT); - spr_height = (affine_bounds.ylen * FRACUNIT); - - INT32 xdiff = (affine_bounds.xleft - (affine_transform.ox >> FRACBITS)); - - offset -= (xdiff * FRACUNIT); - offset2 = spr_width; - - spritexscale = FRACUNIT; - spriteyscale = FRACUNIT; - } - else - { - offset = FixedMul(offset, FixedMul(spritexscale, this_scale)); - offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale)); - } + offset = FixedMul(offset, FixedMul(spritexscale, this_scale)); + offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale)); if (papersprite) { @@ -2356,7 +2375,7 @@ static void R_ProjectSprite(mobj_t *thing) else { scalestep = 0; - yscale = sortscale; + yscale = (affinesprite) ? FRACUNIT : sortscale; tx += offset; x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; @@ -2677,6 +2696,8 @@ static void R_ProjectSprite(mobj_t *thing) if (affinesprite) { + vis->affine.scaling.x = affine_scale.x; + vis->affine.scaling.y = affine_scale.y; vis->affine.rollangle = spriterotangle; vis->affine.transform.a = affine_transform.a; @@ -2708,7 +2729,7 @@ static void R_ProjectSprite(mobj_t *thing) vis->xscale = FixedMul(spritexscale, xscale); //SoM: 4/17/2000 vis->scale = FixedMul(spriteyscale, yscale); //<thingscale = this_scale; + vis->thingscale = (affinesprite) ? FRACUNIT : this_scale; vis->spritexscale = spritexscale; vis->spriteyscale = spriteyscale; diff --git a/src/r_things.h b/src/r_things.h index fb9e91f62..961ae8cb8 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -186,6 +186,7 @@ struct vissprite_t fixed_t startfrac; // horizontal position of x1 fixed_t xscale, scale; // projected horizontal and vertical scales + fixed_t ypush_scale; // Scale for downwards Y movement. Likely only relevant for affines. fixed_t thingscale; // the object's scale fixed_t sortscale; // sortscale only differs from scale for paper sprites and floor sprites fixed_t sortsplat; // the sortscale from behind the floor sprite @@ -208,6 +209,7 @@ struct vissprite_t } shear; struct { + vector2_t scaling; // Affine scaling angle_t rollangle; // Affine rotation angle affine_t transform; // The actual affine transformation. affine_bounding_t bounds; // The "bounding box" (draw area) of the affine sprite. From f3287a8e385e1a6339d83aed429d88fe517c0f78 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 22 Feb 2026 20:32:57 -0500 Subject: [PATCH 06/42] Fixed alignment --- src/r_main.h | 2 ++ src/r_things.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/r_main.h b/src/r_main.h index 115017f34..45c0558c6 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -161,6 +161,8 @@ extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; +#define AFFINEROT_TESTER + #ifdef AFFINEROT_TESTER extern consvar_t cv_affineangle, cv_affinerottest; #endif diff --git a/src/r_things.cpp b/src/r_things.cpp index e45d8d441..d09a95708 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1983,7 +1983,7 @@ static void R_ProjectSprite(mobj_t *thing) affine_scale.x = FixedDiv(projection[viewssnum], tz); affine_scale.y = FixedDiv(projectiony[viewssnum], tz); - xscale = FRACUNIT; + xscale = affine_scale.x; sortscale = affine_scale.y; } else @@ -2376,14 +2376,17 @@ static void R_ProjectSprite(mobj_t *thing) { scalestep = 0; yscale = (affinesprite) ? FRACUNIT : sortscale; - tx += offset; + + // This is, frankly, a ridiculous solution to a problem that never existed until I touched the renderer + // We blow up the scale of the offsets so that the RESCALED positions are still 1:1 + tx += (affinesprite) ? FixedDiv(offset,xscale) : offset; x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; // off the right side? if (x1 > viewwidth) return; - tx += offset2; + tx += (affinesprite) ? FixedDiv(offset2,xscale) : offset2; x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--; // off the left side From 48ec1c977db3518815bc99cde448a228106e8760 Mon Sep 17 00:00:00 2001 From: yamamama Date: Mon, 23 Feb 2026 06:33:07 -0500 Subject: [PATCH 07/42] Mostly fix vertical alignment spryscale still has alignment issues --- src/r_things.cpp | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index d09a95708..42802efb2 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1112,11 +1112,9 @@ static void R_DrawVisSprite(vissprite_t *vis) spryscale = vis->scale; - fixed_t ymovescale = (vis->cut & SC_AFFINE) ? vis->affine.scaling.y : spryscale; - if (!(vis->scalestep)) { - sprtopscreen = centeryfrac - FixedMul(dc.texturemid, ymovescale); + sprtopscreen = centeryfrac - FixedMul(dc.texturemid, spryscale); sprtopscreen += vis->shear.tan * vis->shear.offset; dc.iscale = FixedDiv(FRACUNIT, vis->scale); } @@ -1174,10 +1172,10 @@ static void R_DrawVisSprite(vissprite_t *vis) //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = ((sprtopscreen + spryscale*dc.affinebound.ylen)-1)>>FRACBITS; + dc.yh = ((sprtopscreen + FRACUNIT*dc.affinebound.ylen)-1)>>FRACBITS; INT32 topscreen = sprtopscreen + spryscale*0; - INT32 bottomscreen = topscreen + spryscale*dc.affinebound.ylen; + INT32 bottomscreen = topscreen + FRACUNIT*dc.affinebound.ylen; dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; dc.yh = (bottomscreen-1)>>FRACBITS; @@ -2375,18 +2373,18 @@ static void R_ProjectSprite(mobj_t *thing) else { scalestep = 0; - yscale = (affinesprite) ? FRACUNIT : sortscale; + yscale = sortscale; // This is, frankly, a ridiculous solution to a problem that never existed until I touched the renderer // We blow up the scale of the offsets so that the RESCALED positions are still 1:1 - tx += (affinesprite) ? FixedDiv(offset,xscale) : offset; + tx += (affinesprite) ? FixedDiv(offset,FixedMul(xscale, this_scale)) : offset; x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; // off the right side? if (x1 > viewwidth) return; - tx += (affinesprite) ? FixedDiv(offset2,xscale) : offset2; + tx += (affinesprite) ? FixedDiv(offset2,FixedMul(xscale, this_scale)) : offset2; x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--; // off the left side @@ -2541,18 +2539,40 @@ static void R_ProjectSprite(mobj_t *thing) if (!shadowskew) { //SoM: 3/17/2000: Disregard sprites that are out of view.. + + fixed_t useoffset = 0, useheight = 0; + + if (affinesprite) + { + // This is an equally terrible hack, but it gets the job done with alignment. + // I'm writing this after pulling an all nighter and trying tons of other solutions; + // I cannot be assed to care about how elegant a solution is. + + const float affineyscale = FIXED_TO_FLOAT(affine_scale.y); + const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup) / affineyscale); + const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); + + useoffset = FixedMul(real_topoffset, FixedMul(spriteyscale, this_scale)); + useheight = FixedMul(real_height, FixedMul(spriteyscale, this_scale)); + } + else + { + useoffset = FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale)); + useheight = FixedMul(spr_height, FixedMul(spriteyscale, this_scale)); + } + if (vflip) { // When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned. // sprite height - sprite topoffset is the proper inverse of the vertical offset, of course. // remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes! - gz = interp.z + interp.height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale)); - gzt = gz + FixedMul(spr_height, FixedMul(spriteyscale, this_scale)); + gz = interp.z + interp.height - useoffset; + gzt = gz + useheight; } else { - gzt = interp.z + FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale)); - gz = gzt - FixedMul(spr_height, FixedMul(spriteyscale, this_scale)); + gzt = interp.z + useoffset; + gz = gzt - useheight; } } From 1b90be290197310d45fc97453cf377a7d8fd47aa Mon Sep 17 00:00:00 2001 From: yamamama Date: Mon, 23 Feb 2026 06:33:19 -0500 Subject: [PATCH 08/42] Disable drawarea fills --- src/r_draw_column.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index ff11428b3..029d73787 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -486,7 +486,7 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc) } // Purely for debug; fills all columns in the drawing area with a pair of "background colors" -#define FILL_DRAW_AREA +//#define FILL_DRAW_AREA void R_DrawAffineColumn(drawcolumndata_t *dc) { From 05508fbb58aac2c70b81de3d6a87c5ee783fd729 Mon Sep 17 00:00:00 2001 From: yamamama Date: Mon, 23 Feb 2026 06:50:12 -0500 Subject: [PATCH 09/42] Fix spryscale alignment --- src/r_things.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index 42802efb2..7bd43c449 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2548,12 +2548,16 @@ static void R_ProjectSprite(mobj_t *thing) // I'm writing this after pulling an all nighter and trying tons of other solutions; // I cannot be assed to care about how elegant a solution is. + // spriteyscale is only ever 1.0 with affines; we have to recursively find the true + // spriteyscale by doing this. + fixed_t rawyscale = FixedDiv(affine_scale.y, FixedMul(this_scale, sortscale)); + const float affineyscale = FIXED_TO_FLOAT(affine_scale.y); const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup) / affineyscale); const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); - useoffset = FixedMul(real_topoffset, FixedMul(spriteyscale, this_scale)); - useheight = FixedMul(real_height, FixedMul(spriteyscale, this_scale)); + useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale)); + useheight = FixedMul(real_height, FixedMul(rawyscale, this_scale)); } else { From def663e56688424c9fe6b60c88aa08bbd5035bb7 Mon Sep 17 00:00:00 2001 From: yamamama Date: Mon, 23 Feb 2026 14:37:09 -0500 Subject: [PATCH 10/42] Extremely basic templating prelim It builds and *doesn't* segfault; I can finally sleep! --- src/r_draw_column.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 029d73787..9796487c3 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -488,7 +488,8 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc) // Purely for debug; fills all columns in the drawing area with a pair of "background colors" //#define FILL_DRAW_AREA -void R_DrawAffineColumn(drawcolumndata_t *dc) +template +static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) { INT32 count; const INT32 vidheight = vid.height; @@ -531,7 +532,19 @@ void R_DrawAffineColumn(drawcolumndata_t *dc) // Framebuffer destination address. // SoM: MAGIC - UINT8 *restrict dest = R_Address(dc->x, dc->yl); + UINT8 * restrict dest; + + if constexpr (Type & DrawColumnType::DC_DIRECT) + dest = R_Address(dc->x, dc->yl); + else if constexpr ((Type & (DrawColumnType::DC_COLORMAP | DrawColumnType::DC_TRANSMAP)) + == (DrawColumnType::DC_COLORMAP | DrawColumnType::DC_TRANSMAP)) + dest = R_GetBufferColormapTrans(dc); + else if constexpr (Type & DrawColumnType::DC_TRANSMAP) + dest = R_GetBufferTrans(dc); + else if constexpr (Type & DrawColumnType::DC_COLORMAP) + dest = R_GetBufferColormap(dc); + else + dest = R_GetBufferOpaque(dc); const INT32 stride = vid.width; // SoM: Oh, Oh it's MAGIC! You know... @@ -607,3 +620,22 @@ void R_DrawAffineColumn(drawcolumndata_t *dc) } } } + +#define DEFINE_AFFINE_COLUMN_FUNC(name, flags) \ + void name(drawcolumndata_t *dc) \ + { \ + ZoneScoped; \ + constexpr DrawColumnType opt = static_cast(flags); \ + R_DrawAffineColumnTemplate(dc); \ + } + +#define DEFINE_AFFINE_COLUMN_COMBO(name, flags) \ + DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) \ + DEFINE_AFFINE_COLUMN_FUNC(name ## _Brightmap, flags|DC_DIRECT|DC_BRIGHTMAP) \ + DEFINE_AFFINE_COLUMN_FUNC(name ## _Flush, flags) + +// Replace with DEFINE_AFFINE_COLUMN_COMBO down the line +#define DEFINE_AFFINE_COLUMN_SETUP(name, flags) \ + DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) + +DEFINE_AFFINE_COLUMN_SETUP(R_DrawAffineColumn, DC_BASIC); From e802de23dfa0ad1b263883079d369c65498fc8d5 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 26 Feb 2026 23:27:54 -0500 Subject: [PATCH 11/42] Move affine pixel lookups to R_DrawColumnAffinePixel Turns out the secret sauce is a... recursive function Colormap translations work, jury's still out on if translucency and brightmaps do --- src/r_draw.h | 2 ++ src/r_draw_column.cpp | 48 ++++++++++++++++++++++++++++++++++++++++--- src/r_things.cpp | 11 ++++++++-- src/screen.c | 10 ++++++++- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/r_draw.h b/src/r_draw.h index 332413160..b320500c6 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -75,6 +75,7 @@ enum COLDRAWFUNC_FOG, COLDRAWFUNC_DROPSHADOW, COLDRAWFUNC_AFFINE, + COLDRAWFUNC_AFFINETRANS, COLDRAWFUNC_MAX }; @@ -203,6 +204,7 @@ void R_Draw2sMultiPatchTranslucentColumn_Flush(drawcolumndata_t* dc); void R_DrawFogColumn(drawcolumndata_t* dc); void R_DrawColumnShadowed(drawcolumndata_t* dc); void R_DrawAffineColumn(drawcolumndata_t* dc); +void R_DrawTranslatedAffineColumn(drawcolumndata_t* dc); void R_DrawColumn_Brightmap(drawcolumndata_t* dc); void R_DrawTranslucentColumn_Brightmap(drawcolumndata_t* dc); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 9796487c3..d940697b3 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -101,6 +101,46 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnPixel(drawcolumndata_t* return R_GetColumnTranslucent(dc, dest, bit, col); } +// Function flow: Translation -> Brightmap -> Translucency +// "Why are these in nested functions for standard columns?" Uhhhhhh iunno lol +// I make-a da code-a + +template +FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnAffinePixel(drawcolumndata_t* dc, UINT8 *dest, INT32 bit) +{ + const UINT16 pixel = reinterpret_cast(dc->source)[bit]; + + if (pixel < 0xff00) + { + return TRANSPARENTPIXEL; + } + + UINT8 col = (UINT8)(pixel & 0xff); + + if constexpr (Type & DrawColumnType::DC_COLORMAP) + { + // Remap to the current translation + col = dc->translation[col]; + } + + if constexpr (Type & DrawColumnType::DC_BRIGHTMAP) + { + if (dc->brightmap[bit] == BRIGHTPIXEL) + { + // Pixel is part of the brightmap + col = dc->fullbright[col]; + } + } + + if constexpr (Type & DrawColumnType::DC_TRANSMAP) + { + // Pixel is translucent + col = *(dc->transmap + (col << 8) + (*dest)); + } + + return col; +} + /** \brief The R_DrawColumn function Experiment to make software go faster. Taken from the Boom source */ @@ -611,12 +651,13 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) pixel = 65315; // Uhhhhhhhh } #else - const UINT16 pixel = reinterpret_cast(dc->source)[srcy * pw + srcx]; - if (pixel < 0xff00) + const UINT8 pixel = R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); + + if (pixel == TRANSPARENTPIXEL) continue; #endif - *dest = (UINT8)(pixel & 0xff); + *dest = pixel; } } } @@ -639,3 +680,4 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) DEFINE_AFFINE_COLUMN_SETUP(R_DrawAffineColumn, DC_BASIC); +DEFINE_AFFINE_COLUMN_SETUP(R_DrawTranslatedAffineColumn, DC_COLORMAP); diff --git a/src/r_things.cpp b/src/r_things.cpp index 7bd43c449..7d8b1d4ae 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1040,7 +1040,12 @@ static void R_DrawVisSprite(vissprite_t *vis) if (vis->cut & SC_AFFINE) { - R_SetColumnFunc(COLDRAWFUNC_AFFINE, false); + // Specialized affine drawing functions, primarily for player sprites. + + if (dc.translation) // translate green skin to another color + R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, false); + else + R_SetColumnFunc(COLDRAWFUNC_AFFINE, false); } // Hack: Use a special column function for drop shadows that bypasses // invalid memory access crashes caused by R_ProjectDropShadow putting wrong values @@ -1203,7 +1208,9 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.frac = frac; - R_DrawAffineColumn(&dc); + drawcolumndata_t dc_copy = dc; + coldrawfunc_t* colfunccopy = colfunc; + colfunccopy(&dc); } } // Split drawing loops for paper and non-paper to reduce conditional checks per sprite diff --git a/src/screen.c b/src/screen.c index 565c73972..73b6c4ac9 100644 --- a/src/screen.c +++ b/src/screen.c @@ -132,7 +132,11 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn; colfuncs[COLDRAWFUNC_DROPSHADOW] = R_DrawDropShadowColumn; + + // Affine functions + colfuncs[COLDRAWFUNC_AFFINE] = R_DrawAffineColumn; + colfuncs[COLDRAWFUNC_AFFINETRANS] = R_DrawTranslatedAffineColumn; colfuncs_bm[BASEDRAWFUNC] = R_DrawColumn_Brightmap; colfuncs_bm[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_Brightmap; @@ -143,7 +147,11 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs_bm[COLDRAWFUNC_TWOSMULTIPATCHTRANS] = R_Draw2sMultiPatchTranslucentColumn_Brightmap; colfuncs_bm[COLDRAWFUNC_FOG] = NULL; // Not needed colfuncs_bm[COLDRAWFUNC_DROPSHADOW] = NULL; // Not needed - colfuncs_bm[COLDRAWFUNC_AFFINE] = NULL; // Is needed but I don't care + + // Affine brightmap functions; needed but I don't care at the moment + + colfuncs_bm[COLDRAWFUNC_AFFINE] = NULL; + colfuncs_bm[COLDRAWFUNC_AFFINETRANS] = NULL; spanfuncs[BASEDRAWFUNC] = R_DrawSpan; spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan; From 402b48342b7590cf3e7e1bc4cbda718c9f67bd4a Mon Sep 17 00:00:00 2001 From: yamamama Date: Fri, 27 Feb 2026 01:38:12 -0500 Subject: [PATCH 12/42] Translucent affine functions Up next is... column splitting. Dear god. --- src/r_draw.h | 4 ++++ src/r_draw_column.cpp | 2 ++ src/r_things.cpp | 19 ++++++++++++++++++- src/screen.c | 7 ++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/r_draw.h b/src/r_draw.h index b320500c6..2533ff7df 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -76,6 +76,8 @@ enum COLDRAWFUNC_DROPSHADOW, COLDRAWFUNC_AFFINE, COLDRAWFUNC_AFFINETRANS, + COLDRAWFUNC_AFFINEFUZZY, + COLDRAWFUNC_AFFINETRANSTRANS, COLDRAWFUNC_MAX }; @@ -205,6 +207,8 @@ void R_DrawFogColumn(drawcolumndata_t* dc); void R_DrawColumnShadowed(drawcolumndata_t* dc); void R_DrawAffineColumn(drawcolumndata_t* dc); void R_DrawTranslatedAffineColumn(drawcolumndata_t* dc); +void R_DrawTranslucentAffineColumn(drawcolumndata_t* dc); +void R_DrawTranslatedTranslucentAffineColumn(drawcolumndata_t* dc); void R_DrawColumn_Brightmap(drawcolumndata_t* dc); void R_DrawTranslucentColumn_Brightmap(drawcolumndata_t* dc); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index d940697b3..483dce065 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -681,3 +681,5 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) DEFINE_AFFINE_COLUMN_SETUP(R_DrawAffineColumn, DC_BASIC); DEFINE_AFFINE_COLUMN_SETUP(R_DrawTranslatedAffineColumn, DC_COLORMAP); +DEFINE_AFFINE_COLUMN_SETUP(R_DrawTranslucentAffineColumn, DC_TRANSMAP); +DEFINE_AFFINE_COLUMN_SETUP(R_DrawTranslatedTranslucentAffineColumn, DC_COLORMAP|DC_TRANSMAP); diff --git a/src/r_things.cpp b/src/r_things.cpp index 7d8b1d4ae..eb5a9c806 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1043,9 +1043,26 @@ static void R_DrawVisSprite(vissprite_t *vis) // Specialized affine drawing functions, primarily for player sprites. if (dc.translation) // translate green skin to another color - R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, false); + { + if (vis->transmap) + { + R_SetColumnFunc(COLDRAWFUNC_AFFINETRANSTRANS, false); + dc.transmap = vis->transmap; + } + else + { + R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, false); + } + } + else if (vis->transmap) + { + R_SetColumnFunc(COLDRAWFUNC_AFFINEFUZZY, false); + dc.transmap = vis->transmap; + } else + { R_SetColumnFunc(COLDRAWFUNC_AFFINE, false); + } } // Hack: Use a special column function for drop shadows that bypasses // invalid memory access crashes caused by R_ProjectDropShadow putting wrong values diff --git a/src/screen.c b/src/screen.c index 73b6c4ac9..158b4313e 100644 --- a/src/screen.c +++ b/src/screen.c @@ -133,10 +133,13 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn; colfuncs[COLDRAWFUNC_DROPSHADOW] = R_DrawDropShadowColumn; - // Affine functions + // Affine functions; the ones here are only relevant for sprites. + // We can't rotate texture columns normally, can we? colfuncs[COLDRAWFUNC_AFFINE] = R_DrawAffineColumn; colfuncs[COLDRAWFUNC_AFFINETRANS] = R_DrawTranslatedAffineColumn; + colfuncs[COLDRAWFUNC_AFFINEFUZZY] = R_DrawTranslucentAffineColumn; + colfuncs[COLDRAWFUNC_AFFINETRANSTRANS] = R_DrawTranslatedTranslucentAffineColumn; colfuncs_bm[BASEDRAWFUNC] = R_DrawColumn_Brightmap; colfuncs_bm[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_Brightmap; @@ -152,6 +155,8 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) colfuncs_bm[COLDRAWFUNC_AFFINE] = NULL; colfuncs_bm[COLDRAWFUNC_AFFINETRANS] = NULL; + colfuncs_bm[COLDRAWFUNC_AFFINEFUZZY] = NULL; + colfuncs_bm[COLDRAWFUNC_AFFINETRANSTRANS] = NULL; spanfuncs[BASEDRAWFUNC] = R_DrawSpan; spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan; From aeadb21ad3567bb78ab35b0e8e269a8dd614a5c6 Mon Sep 17 00:00:00 2001 From: yamamama Date: Fri, 27 Feb 2026 01:52:20 -0500 Subject: [PATCH 13/42] Add affine renderflag Guest starring: even MORE flag variables! --- src/lua_mobjlib.c | 8 ++++++++ src/p_mobj.h | 4 ++-- src/p_saveg.c | 3 ++- src/r_defs.h | 5 +++++ src/r_things.cpp | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 6cdec7b59..f8521fbe7 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -62,6 +62,7 @@ enum mobj_e { mobj_flags2, mobj_eflags, mobj_renderflags, + mobj_renderflags2, mobj_skin, mobj_voice, mobj_color, @@ -160,6 +161,7 @@ static const char *const mobj_opt[] = { "flags2", "eflags", "renderflags", + "renderflags2", "skin", "voice", "color", @@ -429,6 +431,9 @@ static int mobj_get(lua_State *L) case mobj_renderflags: lua_pushinteger(L, mo->renderflags); break; + case mobj_renderflags2: + lua_pushinteger(L, mo->renderflags2); + break; case mobj_skin: // skin name or nil, not struct if (!mo->skin) return 0; @@ -901,6 +906,9 @@ static int mobj_set(lua_State *L) case mobj_renderflags: mo->renderflags = (UINT32)luaL_checkinteger(L, 3); break; + case mobj_renderflags2: + mo->renderflags2 = (UINT32)luaL_checkinteger(L, 3); + break; case mobj_skin: // set skin by name { INT32 i; diff --git a/src/p_mobj.h b/src/p_mobj.h index 371cbe1db..004508530 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -305,7 +305,7 @@ struct mobj_t UINT8 sprite2; // player sprites UINT16 anim_duration; // for FF_ANIMATE states - UINT32 renderflags; // render flags + UINT32 renderflags, renderflags2; // render flags fixed_t spritexscale, spriteyscale; fixed_t spritexoffset, spriteyoffset; fixed_t old_spritexscale, old_spriteyscale; @@ -494,7 +494,7 @@ struct precipmobj_t UINT8 sprite2; // player sprites UINT16 anim_duration; // for FF_ANIMATE states - UINT32 renderflags; // render flags + UINT32 renderflags, renderflags2; // render flags fixed_t spritexscale, spriteyscale; fixed_t spritexoffset, spriteyoffset; fixed_t old_spritexscale, old_spriteyscale; diff --git a/src/p_saveg.c b/src/p_saveg.c index 6e8779124..235e68dc1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2046,7 +2046,7 @@ static void DiffMobj(const mobj_t *mobj, UINT32 diff[]) DIFF(mobj->mirrored, MD2_MIRRORED); DIFF(mobj->rollangle, MD2_ROLLANGLE); DIFF(mobj->shadowscale, MD2_SHADOWSCALE); - DIFF(mobj->renderflags, MD2_RENDERFLAGS); + DIFF(mobj->renderflags || mobj->renderflags2, MD2_RENDERFLAGS); DIFF(mobj->tid != 0, MD2_TID); DIFF(mobj->spritexscale != FRACUNIT || mobj->spriteyscale != FRACUNIT, MD2_SPRITESCALE); DIFF(mobj->spritexoffset || mobj->spriteyoffset || mobj->rollingxoffset || mobj->rollingyoffset, MD2_SPRITEOFFSET); @@ -2324,6 +2324,7 @@ static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinke mobj->renderflags = READUINT32(save->p); } } + SYNCF(MD2_RENDERFLAGS, mobj->renderflags2); SYNCF(MD2_TID, mobj->tid); SYNCDEF(MD2_SPRITESCALE, mobj->spritexscale, FRACUNIT); SYNCDEF(MD2_SPRITESCALE, mobj->spriteyscale, FRACUNIT); diff --git a/src/r_defs.h b/src/r_defs.h index 03992f22d..6b2fffdb6 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1036,6 +1036,11 @@ typedef enum RF_GHOSTLYMASK = (RF_TRANSMASK | RF_FULLBRIGHT), } renderflags_t; +typedef enum +{ + RF2_AFFINE = 0x00000001, // Affine sprite; draws scaling and rotation using a Mode 7 matrix +} renderflags2_t; + typedef enum { SRF_SINGLE = 0, // 0-angle for all rotations diff --git a/src/r_things.cpp b/src/r_things.cpp index eb5a9c806..b11570a3e 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1955,7 +1955,7 @@ static void R_ProjectSprite(mobj_t *thing) mobj_t *interptarg = thing; // Affines - boolean affinesprite = (thing->player != NULL); + boolean affinesprite = ((thing->player != NULL) || ((thing->renderflags2 & RF2_AFFINE) == RF2_AFFINE)); affine_t affine_transform = {0}; affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; From 3a81063d7fed5a78b40297c77afb66c2e4fe554f Mon Sep 17 00:00:00 2001 From: yamamama Date: Fri, 27 Feb 2026 05:14:36 -0500 Subject: [PATCH 14/42] Colormaps and splitting Papersprites are all that remain --- src/r_defs.h | 1 + src/r_draw_column.cpp | 14 +++++++++++--- src/r_main.cpp | 2 ++ src/r_main.h | 2 +- src/r_things.cpp | 10 ++++++++++ src/r_things.h | 1 + 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/r_defs.h b/src/r_defs.h index 6b2fffdb6..bf1b23663 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1160,6 +1160,7 @@ typedef struct affine_bounding_t affinebound; affine_t affine; + vector2_t affineoffset; fixed_t frac; } drawcolumndata_t; diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 483dce065..1d02a12ae 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -101,7 +101,7 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnPixel(drawcolumndata_t* return R_GetColumnTranslucent(dc, dest, bit, col); } -// Function flow: Translation -> Brightmap -> Translucency +// Function flow: Translation -> Brightmap -> Colormap -> Translucency // "Why are these in nested functions for standard columns?" Uhhhhhh iunno lol // I make-a da code-a @@ -123,15 +123,23 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnAffinePixel(drawcolumnd col = dc->translation[col]; } + boolean was_brightmapped = false; + if constexpr (Type & DrawColumnType::DC_BRIGHTMAP) { if (dc->brightmap[bit] == BRIGHTPIXEL) { // Pixel is part of the brightmap col = dc->fullbright[col]; + was_brightmapped = true; } } + if (!was_brightmapped) + { + col = dc->colormap[col]; + } + if constexpr (Type & DrawColumnType::DC_TRANSMAP) { // Pixel is translucent @@ -612,8 +620,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) ydiff -= (ydiff ? FRACUNIT : 0); // Offset our X and Y positions by the bounding differences. - fixed_t cxx = cx + xdiff; - fixed_t cyy = cy + ydiff; + fixed_t cxx = cx + xdiff + dc->affineoffset.x; + fixed_t cyy = cy + ydiff + dc->affineoffset.y; //I_OutputMsg("xdiff: %f, ydiff: %f\n", FIXED_TO_FLOAT(xdiff), FIXED_TO_FLOAT(ydiff)); diff --git a/src/r_main.cpp b/src/r_main.cpp index 1da915fcd..698472381 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -202,6 +202,7 @@ static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Aut #ifdef AFFINEROT_TESTER consvar_t cv_affineangle = CVAR_INIT ("affineangle", "0", CV_FLOAT, affineangle_cons_t, NULL); +consvar_t cv_affinecuttest = CVAR_INIT ("affinecuttest", "8.0", CV_FLOAT, CV_Signed, NULL); consvar_t cv_affinerottest = CVAR_INIT ("affinerottest", "Off", CV_FLOAT, affinetest_cons_t, NULL); #endif @@ -1822,6 +1823,7 @@ void R_RegisterEngineStuff(void) #ifdef AFFINEROT_TESTER CV_RegisterVar(&cv_affineangle); + CV_RegisterVar(&cv_affinecuttest); CV_RegisterVar(&cv_affinerottest); #endif diff --git a/src/r_main.h b/src/r_main.h index 45c0558c6..7c979da0a 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -164,7 +164,7 @@ extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; #define AFFINEROT_TESTER #ifdef AFFINEROT_TESTER -extern consvar_t cv_affineangle, cv_affinerottest; +extern consvar_t cv_affineangle, cv_affinecuttest, cv_affinerottest; #endif // debugging diff --git a/src/r_things.cpp b/src/r_things.cpp index b11570a3e..185c2a05a 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1178,6 +1178,8 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.affine.d = vis->affine.transform.d; dc.affine.ox = vis->affine.transform.ox; dc.affine.oy = vis->affine.transform.oy; + dc.affineoffset.x = vis->affine.offset.x; + dc.affineoffset.y = vis->affine.offset.y; R_CopyAffineBounds(&vis->affine.bounds, &dc.affinebound); @@ -1434,6 +1436,14 @@ static void R_SplitSprite(vissprite_t *sprite) newsprite->gzt = sprite->gz; + if (sprite->cut & SC_AFFINE) + { + // Affines: Fix splitting alignment errors by applying a pixel offset + fixed_t szt_diff = (newsprite->sz - cutfrac + 9) << FRACBITS; + newsprite->affine.offset.y = -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff); + } + + sprite->sz = cutfrac; newsprite->szt = (INT16)(sprite->sz - 1); diff --git a/src/r_things.h b/src/r_things.h index 961ae8cb8..7268d3709 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -210,6 +210,7 @@ struct vissprite_t struct { vector2_t scaling; // Affine scaling + vector2_t offset; // Per-pixel offset angle_t rollangle; // Affine rotation angle affine_t transform; // The actual affine transformation. affine_bounding_t bounds; // The "bounding box" (draw area) of the affine sprite. From 649653908489858e5e7fa5d26282f4293065db38 Mon Sep 17 00:00:00 2001 From: yamamama Date: Fri, 27 Feb 2026 19:19:43 -0500 Subject: [PATCH 15/42] Expose RF2_AFFINE to Lua --- src/deh_tables.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index 16fc5b4f0..8f21c6f59 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1057,6 +1057,7 @@ struct int_const_s const INT_CONST[] = { {"RF_TRANS90",RF_TRANS90}, {"RF_GHOSTLY",RF_GHOSTLY}, {"RF_GHOSTLYMASK",RF_GHOSTLYMASK}, + {"RF2_AFFINE",RF2_AFFINE}, // Level flags {"LF_SCRIPTISFILE",LF_SCRIPTISFILE}, From 9f3c2596d13210f054f37b62c22a81f87490008d Mon Sep 17 00:00:00 2001 From: yamamama Date: Fri, 27 Feb 2026 19:20:11 -0500 Subject: [PATCH 16/42] More crap Trying to fix a horizontal screen clip bug --- src/r_things.cpp | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index 185c2a05a..e3f2f06e8 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1440,7 +1440,7 @@ static void R_SplitSprite(vissprite_t *sprite) { // Affines: Fix splitting alignment errors by applying a pixel offset fixed_t szt_diff = (newsprite->sz - cutfrac + 9) << FRACBITS; - newsprite->affine.offset.y = -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff); + newsprite->affine.offset.y += -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff); } @@ -1969,6 +1969,7 @@ static void R_ProjectSprite(mobj_t *thing) affine_t affine_transform = {0}; affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; + vector2_t affine_offset = {0}; if (R_IsOverlayingSMonitorPlayer(thing)) { @@ -2753,6 +2754,28 @@ static void R_ProjectSprite(mobj_t *thing) vis->viewpoint.z = viewz; vis->viewpoint.angle = viewangle; + vis->mobj = thing; // Easy access! Tails 06-07-2002 + + const boolean cliptoleft = (x1 < portalclipstart); + + vis->x1 = x1 < portalclipstart ? portalclipstart : x1; + vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; + + + if (affinesprite && cliptoleft) + { + // If our draw length is shorter than usual, push the sprite to the left by that much. + const INT32 _xlen = (vis->x2 - vis->x1); + affine_offset.x = (affine_bounds.xlen - _xlen); + } + + vis->sector = thing->subsector->sector; + + // Using linkscale here improves cut detection for LINKDRAW. + vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, linkscale))>>FRACBITS); + vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, linkscale))>>FRACBITS); + vis->cut = cut; + vis->affine = {0}; if (affinesprite) @@ -2768,21 +2791,12 @@ static void R_ProjectSprite(mobj_t *thing) vis->affine.transform.ox = affine_transform.ox; vis->affine.transform.oy = affine_transform.oy; + vis->affine.offset.x = affine_offset.x; + vis->affine.offset.y = affine_offset.y; + R_CopyAffineBounds(&affine_bounds, &vis->affine.bounds); } - vis->mobj = thing; // Easy access! Tails 06-07-2002 - - vis->x1 = x1 < portalclipstart ? portalclipstart : x1; - vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; - - vis->sector = thing->subsector->sector; - - // Using linkscale here improves cut detection for LINKDRAW. - vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, linkscale))>>FRACBITS); - vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, linkscale))>>FRACBITS); - vis->cut = cut; - if (thing->subsector->sector->numlights) vis->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap; else From 0e05031fb7fadbd851dcd616564bcfd9991ea6f6 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sat, 28 Feb 2026 06:03:57 -0500 Subject: [PATCH 17/42] Fix that weird cutoff alignment issue --- src/r_main.cpp | 4 +++- src/r_main.h | 2 +- src/r_things.cpp | 51 ++++++++++++++++++++++-------------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/r_main.cpp b/src/r_main.cpp index 698472381..509491b8e 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -202,7 +202,8 @@ static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Aut #ifdef AFFINEROT_TESTER consvar_t cv_affineangle = CVAR_INIT ("affineangle", "0", CV_FLOAT, affineangle_cons_t, NULL); -consvar_t cv_affinecuttest = CVAR_INIT ("affinecuttest", "8.0", CV_FLOAT, CV_Signed, NULL); +consvar_t cv_affinecuttest = CVAR_INIT ("affinecuttest", "1.0", CV_FLOAT, CV_Signed, NULL); +consvar_t cv_affinedebugx = CVAR_INIT ("affinedebugx", "0.0", CV_FLOAT, CV_Signed, NULL); consvar_t cv_affinerottest = CVAR_INIT ("affinerottest", "Off", CV_FLOAT, affinetest_cons_t, NULL); #endif @@ -1824,6 +1825,7 @@ void R_RegisterEngineStuff(void) #ifdef AFFINEROT_TESTER CV_RegisterVar(&cv_affineangle); CV_RegisterVar(&cv_affinecuttest); + CV_RegisterVar(&cv_affinedebugx); CV_RegisterVar(&cv_affinerottest); #endif diff --git a/src/r_main.h b/src/r_main.h index 7c979da0a..a80c86c0e 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -164,7 +164,7 @@ extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; #define AFFINEROT_TESTER #ifdef AFFINEROT_TESTER -extern consvar_t cv_affineangle, cv_affinecuttest, cv_affinerottest; +extern consvar_t cv_affineangle, cv_affinecuttest, cv_affinedebugx, cv_affinerottest; #endif // debugging diff --git a/src/r_things.cpp b/src/r_things.cpp index e3f2f06e8..66b576755 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1440,7 +1440,7 @@ static void R_SplitSprite(vissprite_t *sprite) { // Affines: Fix splitting alignment errors by applying a pixel offset fixed_t szt_diff = (newsprite->sz - cutfrac + 9) << FRACBITS; - newsprite->affine.offset.y += -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff); + newsprite->affine.offset.y = std::min(0, -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff)); } @@ -1969,7 +1969,6 @@ static void R_ProjectSprite(mobj_t *thing) affine_t affine_transform = {0}; affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; - vector2_t affine_offset = {0}; if (R_IsOverlayingSMonitorPlayer(thing)) { @@ -2754,28 +2753,6 @@ static void R_ProjectSprite(mobj_t *thing) vis->viewpoint.z = viewz; vis->viewpoint.angle = viewangle; - vis->mobj = thing; // Easy access! Tails 06-07-2002 - - const boolean cliptoleft = (x1 < portalclipstart); - - vis->x1 = x1 < portalclipstart ? portalclipstart : x1; - vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; - - - if (affinesprite && cliptoleft) - { - // If our draw length is shorter than usual, push the sprite to the left by that much. - const INT32 _xlen = (vis->x2 - vis->x1); - affine_offset.x = (affine_bounds.xlen - _xlen); - } - - vis->sector = thing->subsector->sector; - - // Using linkscale here improves cut detection for LINKDRAW. - vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, linkscale))>>FRACBITS); - vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, linkscale))>>FRACBITS); - vis->cut = cut; - vis->affine = {0}; if (affinesprite) @@ -2791,12 +2768,21 @@ static void R_ProjectSprite(mobj_t *thing) vis->affine.transform.ox = affine_transform.ox; vis->affine.transform.oy = affine_transform.oy; - vis->affine.offset.x = affine_offset.x; - vis->affine.offset.y = affine_offset.y; - R_CopyAffineBounds(&affine_bounds, &vis->affine.bounds); } + vis->mobj = thing; // Easy access! Tails 06-07-2002 + + vis->x1 = x1 < portalclipstart ? portalclipstart : x1; + vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2; + + vis->sector = thing->subsector->sector; + + // Using linkscale here improves cut detection for LINKDRAW. + vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, linkscale))>>FRACBITS); + vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, linkscale))>>FRACBITS); + vis->cut = cut; + if (thing->subsector->sector->numlights) vis->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap; else @@ -2835,7 +2821,16 @@ static void R_ProjectSprite(mobj_t *thing) if (vis->x1 > x1) { - vis->startfrac += FixedDiv(vis->xiscale, this_scale) * (vis->x1 - x1); + fixed_t xpush = FixedDiv(vis->xiscale, this_scale); + + if (affinesprite) + { + // Affines are, code-wise, "1-to-1 scale", so we always move in whole movements for them. + xpush = intsign(vis->xiscale) * FRACUNIT; + } + + + vis->startfrac += xpush * (vis->x1 - x1); vis->scale += FixedMul(scalestep, spriteyscale) * (vis->x1 - x1); } From 1e06866a9ed647bd36220f81b526eb243bdb4e02 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 1 Mar 2026 12:06:25 -0500 Subject: [PATCH 18/42] FF_AFFINESPRITE and affine papersprites --- src/deh_tables.c | 1 + src/p_pspr.h | 3 + src/r_defs.h | 1 + src/r_draw_column.cpp | 6 +- src/r_things.cpp | 178 +++++++++++++++++++++++++++++++----------- src/r_things.h | 2 + 6 files changed, 145 insertions(+), 46 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 8f21c6f59..1e7794af7 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -948,6 +948,7 @@ struct int_const_s const INT_CONST[] = { {"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP}, {"FF_PAPERSPRITE",FF_PAPERSPRITE}, {"FF_FLOORSPRITE",FF_FLOORSPRITE}, + {"FF_AFFINESPRITE",FF_AFFINESPRITE}, {"FF_BLENDMASK",FF_BLENDMASK}, {"FF_BLENDSHIFT",FF_BLENDSHIFT}, {"FF_ADD",FF_ADD}, diff --git a/src/p_pspr.h b/src/p_pspr.h index a42363388..faece777a 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -90,6 +90,9 @@ extern "C" { /// \brief Frame flags: Flip sprite horizontally #define FF_HORIZONTALFLIP 0x02000000 +/// \brief Frame flags: Mode 7 affines! +#define FF_AFFINESPRITE 0x04000000 + /// \brief Frame flags - Animate: Simple stateless animation #define FF_ANIMATE 0x10000000 /// \brief Frame flags - Animate: Sync animation to global timer (mutually exclusive with below, currently takes priority) diff --git a/src/r_defs.h b/src/r_defs.h index bf1b23663..ccc5dca1d 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1161,6 +1161,7 @@ typedef struct affine_bounding_t affinebound; affine_t affine; vector2_t affineoffset; + fixed_t affineystep; fixed_t frac; } drawcolumndata_t; diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 1d02a12ae..01b4e643d 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -613,6 +613,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) const INT32 pw = dc->sourcelength, ph = dc->texheight; + const fixed_t ystep_delta = dc->affineystep; + fixed_t ydiff = (bounds->yup * FRACUNIT) - cy; fixed_t xdiff = (bounds->xleft * FRACUNIT) - cx; @@ -636,8 +638,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) { const INT32 srcx = ux >> FRACBITS; const INT32 srcy = uy >> FRACBITS; - ux += b; - uy += d; + ux += FixedMul(b, ystep_delta); + uy += FixedMul(d, ystep_delta); #ifdef FILL_DRAW_AREA UINT16 pixel; diff --git a/src/r_things.cpp b/src/r_things.cpp index 66b576755..ec06703fd 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1185,52 +1185,120 @@ static void R_DrawVisSprite(vissprite_t *vis) fixed_t xstep = intsign(vis->xiscale) * FRACUNIT; - // Non-paper drawing loop - for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += xstep, sprtopscreen += vis->shear.tan) + dc.affineystep = FRACUNIT; + + if (vis->scalestep) { - //texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); + // Convert these to floats, and feed them into the affine drawer + float f_yscale_const = FIXED_TO_FLOAT(spryscale); + float f_yscale = FIXED_TO_FLOAT(spryscale); + float f_scalestep = FIXED_TO_FLOAT(FixedMul(vis->scalestep, vis->spriteyscale)); - //column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); - - //if (bmpatch) - //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - - dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = ((sprtopscreen + FRACUNIT*dc.affinebound.ylen)-1)>>FRACBITS; - - INT32 topscreen = sprtopscreen + spryscale*0; - INT32 bottomscreen = topscreen + FRACUNIT*dc.affinebound.ylen; - - dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = (bottomscreen-1)>>FRACBITS; - - if (windowtop != INT32_MAX && windowbottom != INT32_MAX) + // Papersprite drawing loop + for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, f_yscale += f_scalestep) { - if (windowtop > topscreen) - dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; - if (windowbottom < bottomscreen) - dc.yh = (windowbottom - 1)>>FRACBITS; + float _ysc = (f_yscale / f_yscale_const); + + if (_ysc < 0.001f) + { + continue; + } + + dc.affineystep = FLOAT_TO_FIXED(1.0f / _ysc); + + angle_t angle = ((vis->centerangle + xtoviewangle[viewssnum][dc.x]) >> ANGLETOFINESHIFT) & 0xFFF; + dc.frac = FixedMul((vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)), + vis->affine.distscale.x); + + sprtopscreen = (centeryfrac - FixedMul(dc.texturemid, FLOAT_TO_FIXED(f_yscale))); + float f_ylen = static_cast(dc.affinebound.ylen); + + dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = ((sprtopscreen + FLOAT_TO_FIXED(f_ylen * _ysc))-1)>>FRACBITS; + + INT32 topscreen = sprtopscreen + spryscale*0; + INT32 bottomscreen = topscreen + FLOAT_TO_FIXED(f_ylen * _ysc); + + dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = (bottomscreen-1)>>FRACBITS; + + if (windowtop != INT32_MAX && windowbottom != INT32_MAX) + { + if (windowtop > topscreen) + dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; + if (windowbottom < bottomscreen) + dc.yh = (windowbottom - 1)>>FRACBITS; + } + + if (dc.yh >= mfloorclip[dc.x]) + dc.yh = mfloorclip[dc.x]-1; + if (dc.yl <= mceilingclip[dc.x]) + dc.yl = mceilingclip[dc.x]+1; + + if (dc.yl < 0) + dc.yl = 0; + if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop + dc.yh = vid.height - 1; + + if (dc.yh >= baseclip && baseclip != -1) + dc.yh = baseclip; + + drawcolumndata_t dc_copy = dc; + coldrawfunc_t* colfunccopy = colfunc; + colfunccopy(&dc); } - - if (dc.yh >= mfloorclip[dc.x]) - dc.yh = mfloorclip[dc.x]-1; - if (dc.yl <= mceilingclip[dc.x]) - dc.yl = mceilingclip[dc.x]+1; - - if (dc.yl < 0) - dc.yl = 0; - if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop - dc.yh = vid.height - 1; - - if (dc.yh >= baseclip && baseclip != -1) - dc.yh = baseclip; - - dc.frac = frac; - - drawcolumndata_t dc_copy = dc; - coldrawfunc_t* colfunccopy = colfunc; - colfunccopy(&dc); } + else + { + // Non-paper drawing loop + for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += xstep, sprtopscreen += vis->shear.tan) + { + //texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); + + //column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); + + //if (bmpatch) + //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); + + dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = ((sprtopscreen + FRACUNIT*dc.affinebound.ylen)-1)>>FRACBITS; + + INT32 topscreen = sprtopscreen + spryscale*0; + INT32 bottomscreen = topscreen + FRACUNIT*dc.affinebound.ylen; + + dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc.yh = (bottomscreen-1)>>FRACBITS; + + if (windowtop != INT32_MAX && windowbottom != INT32_MAX) + { + if (windowtop > topscreen) + dc.yl = (windowtop + FRACUNIT - 1)>>FRACBITS; + if (windowbottom < bottomscreen) + dc.yh = (windowbottom - 1)>>FRACBITS; + } + + if (dc.yh >= mfloorclip[dc.x]) + dc.yh = mfloorclip[dc.x]-1; + if (dc.yl <= mceilingclip[dc.x]) + dc.yl = mceilingclip[dc.x]+1; + + if (dc.yl < 0) + dc.yl = 0; + if (dc.yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop + dc.yh = vid.height - 1; + + if (dc.yh >= baseclip && baseclip != -1) + dc.yh = baseclip; + + dc.frac = frac; + + drawcolumndata_t dc_copy = dc; + coldrawfunc_t* colfunccopy = colfunc; + colfunccopy(&dc); + } + } + + } // Split drawing loops for paper and non-paper to reduce conditional checks per sprite else if (vis->scalestep) @@ -1965,10 +2033,11 @@ static void R_ProjectSprite(mobj_t *thing) mobj_t *interptarg = thing; // Affines - boolean affinesprite = ((thing->player != NULL) || ((thing->renderflags2 & RF2_AFFINE) == RF2_AFFINE)); + boolean affinesprite = ((thing->player != NULL) || R_ThingIsAffineSprite(thing)); affine_t affine_transform = {0}; affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; + vector2_t affine_distscale = {0}; if (R_IsOverlayingSMonitorPlayer(thing)) { @@ -2012,8 +2081,8 @@ static void R_ProjectSprite(mobj_t *thing) if (affinesprite) { - affine_scale.x = FixedDiv(projection[viewssnum], tz); - affine_scale.y = FixedDiv(projectiony[viewssnum], tz); + affine_distscale.x = affine_scale.x = FixedDiv(projection[viewssnum], tz); + affine_distscale.y = affine_scale.y = FixedDiv(projectiony[viewssnum], tz); xscale = affine_scale.x; sortscale = affine_scale.y; @@ -2177,6 +2246,12 @@ static void R_ProjectSprite(mobj_t *thing) flip = 0; } } + + if (affinesprite && ((papersprite && ang >= ANGLE_180) != vflip)) + { + // Parity with the standard rollangle system + spriterotangle = InvAngle(spriterotangle); + } #endif flip = !flip != !hflip; @@ -2309,6 +2384,12 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t xscale2, yscale2, cosmul, sinmul, tx2, tz2; INT32 range; + if (affinesprite) + { + offset = FixedDiv(offset, xscale); + offset2 = FixedDiv(offset2, xscale); + } + if (ang >= ANGLE_180) { offset *= -1; @@ -2320,6 +2401,7 @@ static void R_ProjectSprite(mobj_t *thing) tr_x += FixedMul(offset, cosmul); tr_y += FixedMul(offset, sinmul); + tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); tx = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos); @@ -2336,6 +2418,7 @@ static void R_ProjectSprite(mobj_t *thing) tr_x += FixedMul(offset2, cosmul); tr_y += FixedMul(offset2, sinmul); + tz2 = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); tx2 = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos); @@ -2759,6 +2842,8 @@ static void R_ProjectSprite(mobj_t *thing) { vis->affine.scaling.x = affine_scale.x; vis->affine.scaling.y = affine_scale.y; + vis->affine.distscale.x = affine_distscale.x; + vis->affine.distscale.y = affine_distscale.y; vis->affine.rollangle = spriterotangle; vis->affine.transform.a = affine_transform.a; @@ -4182,6 +4267,11 @@ boolean R_ThingIsPaperSprite(mobj_t *thing) return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE); } +boolean R_ThingIsAffineSprite(mobj_t *thing) +{ + return (thing->frame & FF_AFFINESPRITE || thing->renderflags2 & RF2_AFFINE); +} + boolean R_ThingIsFloorSprite(mobj_t *thing) { return (thing->flags2 & MF2_SPLAT || thing->frame & FF_FLOORSPRITE || thing->renderflags & RF_FLOORSPRITE); diff --git a/src/r_things.h b/src/r_things.h index 7268d3709..c3acd679d 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -91,6 +91,7 @@ boolean R_ThingVerticallyFlipped (mobj_t *thing); boolean R_ThingIsPaperSprite (mobj_t *thing); boolean R_ThingIsFloorSprite (mobj_t *thing); +boolean R_ThingIsAffineSprite (mobj_t *thing); boolean R_ThingIsFullBright (mobj_t *thing); boolean R_ThingIsSemiBright (mobj_t *thing); @@ -210,6 +211,7 @@ struct vissprite_t struct { vector2_t scaling; // Affine scaling + vector2_t distscale; // X/Y scale based on camera distance vector2_t offset; // Per-pixel offset angle_t rollangle; // Affine rotation angle affine_t transform; // The actual affine transformation. From 7ad5ce13db60f1ecb52660dad9e729fee582d90a Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 1 Mar 2026 16:09:10 -0500 Subject: [PATCH 19/42] Make player ghostmobjs affine --- src/p_user.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index 2c9044be7..6e874393c 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1261,12 +1261,18 @@ mobj_t *P_SpawnGhostMobjEX(mobj_t *mobj, boolean legacy) if (!(mobj->flags & MF_DONTENCOREMAP)) ghost->flags &= ~MF_DONTENCOREMAP; - if (mobj->player && mobj->player->followmobj) + if (mobj->player) { - mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj); - P_SetTarget(&ghost2->tracer, ghost); - P_SetTarget(&ghost->tracer, ghost2); - ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW); + // Players use affine rendering, so match that + ghost->renderflags2 |= RF2_AFFINE; + + if(mobj->player->followmobj) + { + mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj); + P_SetTarget(&ghost2->tracer, ghost); + P_SetTarget(&ghost->tracer, ghost2); + ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW); + } } // Copy interpolation data :) From 5437aa32425cea0ba7c004b00d0ce8e024649e87 Mon Sep 17 00:00:00 2001 From: yamamama Date: Sun, 1 Mar 2026 16:09:48 -0500 Subject: [PATCH 20/42] Use the OpenGL method for highresscale Why are we scaling it down *twice?* --- src/r_things.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index ec06703fd..46e3e1d46 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2049,8 +2049,6 @@ static void R_ProjectSprite(mobj_t *thing) R_InterpolateMobjState(interptarg, R_GetTimeFrac(RTF_LEVEL), &interp); this_scale = interp.scale; - if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) - this_scale = FixedMul(this_scale, ((skin_t *)thing->skin)->highresscale); // sprite offset interp.x += thing->sprxoff; @@ -2263,8 +2261,7 @@ static void R_ProjectSprite(mobj_t *thing) if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) { fixed_t high_res = ((skin_t *)thing->skin)->highresscale; - spritexscale = FixedMul(spritexscale, high_res); - spriteyscale = FixedMul(spriteyscale, high_res); + this_scale = FixedMul(this_scale, high_res); highresscale = high_res; } else From 307e334e274d995d17e1fdc672dc006312a3c70a Mon Sep 17 00:00:00 2001 From: yamamama Date: Mon, 2 Mar 2026 03:45:50 -0500 Subject: [PATCH 21/42] And the rest - Vertical flips - A proper fix for ceiling offsets/sprite splits - Baked offsets, rolling offsets, offsets in general (saltyhop works again!) - Did a few races, can't find any visual bugs of note OpenGL implementation (hopefully) soon --- src/hardware/hw_main.c | 3 +- src/hardware/hw_md2.c | 2 +- src/r_draw_column.cpp | 5 +- src/r_patch.h | 5 +- src/r_patchrotation.c | 27 +++++++--- src/r_things.cpp | 119 ++++++++++++++++++++++++++++------------- 6 files changed, 112 insertions(+), 49 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 636d6d852..208e65818 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4992,7 +4992,7 @@ static void HWR_ProjectSprite(mobj_t *thing) spr_topoffset = spritecachedinfo[lumpoff].topoffset; #ifdef ROTSPRITE - spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp); + spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, false); if (spriterotangle != 0 && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) @@ -5027,6 +5027,7 @@ static void HWR_ProjectSprite(mobj_t *thing) R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, + false, &interp, &visoffs, &rotoffset); diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 42aff1e76..0431b8875 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1678,7 +1678,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) fixedAngY = 0; { - fixed_t anglef = AngleFixed(R_SpriteRotationAngle(spr->mobj, NULL, &interp)); + fixed_t anglef = AngleFixed(R_SpriteRotationAngle(spr->mobj, NULL, &interp, false)); p.rollangle = 0.0f; diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 01b4e643d..63d1362c6 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -613,7 +613,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) const INT32 pw = dc->sourcelength, ph = dc->texheight; - const fixed_t ystep_delta = dc->affineystep; + const boolean vflip = (dc->affineystep < 0); + const fixed_t ystep_delta = abs(dc->affineystep); fixed_t ydiff = (bounds->yup * FRACUNIT) - cy; fixed_t xdiff = (bounds->xleft * FRACUNIT) - cx; @@ -637,7 +638,7 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) for (; count > 0; dest += stride, --count) { const INT32 srcx = ux >> FRACBITS; - const INT32 srcy = uy >> FRACBITS; + const INT32 srcy = (vflip) ? (ph - (uy >> FRACBITS)) : (uy >> FRACBITS); ux += FixedMul(b, ystep_delta); uy += FixedMul(d, ystep_delta); diff --git a/src/r_patch.h b/src/r_patch.h index 3a66462ba..d23280c91 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -48,12 +48,13 @@ patch_t *Patch_GetRotatedSprite( INT32 R_GetRollAngle(angle_t rollangle); boolean R_IsOverlayingSMonitorPlayer(mobj_t* mobj); angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp); -angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer); -angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp); +angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer, boolean fliptilt); +angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp, boolean fliptilt); vector2_t* R_RotateSpriteOffsetsByPitchRoll( mobj_t* mobj, boolean vflip, boolean hflip, + boolean affine, interpmobjstate_t *interp, vector2_t* out, vector2_t* rolloffs); diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index bef1215bf..e12e720ac 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -56,7 +56,7 @@ angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_ return rollOrPitch; } -static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) +static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer, boolean fliptilt) { angle_t viewingAngle = R_PointToAnglePlayer(viewPlayer, player->mo->x, player->mo->y); angle_t angleDelta = (viewingAngle - player->mo->angle); @@ -70,6 +70,12 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) // Sliptide tilt: Nulldrifts take priority if and ONLY if they're the larger value compared to sliptides. angle_t sliptideLift = max(abs(aiztilt), abs(nulltilt)) * liftsign; + if (fliptilt) + { + // Vertical flip affine hack: invert the angle so it still looks right. + sliptideLift *= -1; + } + angle_t rollAngle = 0; if (sliptideLift) @@ -97,23 +103,23 @@ boolean R_IsOverlayingSMonitorPlayer(mobj_t* mobj) (mobj->target->player->smonitortimer)); } -angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer) +angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer, boolean fliptilt) { angle_t rollAngle = mobj->rollangle; if (mobj->player) { - rollAngle += R_PlayerSpriteRotation(mobj->player, viewPlayer); + rollAngle += R_PlayerSpriteRotation(mobj->player, viewPlayer, fliptilt); } else if (R_IsOverlayingSMonitorPlayer(mobj)) { - rollAngle += R_PlayerSpriteRotation(mobj->target->player, viewPlayer); + rollAngle += R_PlayerSpriteRotation(mobj->target->player, viewPlayer, fliptilt); } return rollAngle; } -angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp) +angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp, boolean fliptilt) { angle_t rollOrPitch; @@ -125,7 +131,7 @@ angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstat { rollOrPitch = R_GetPitchRollAngle(mobj, viewPlayer, interp); } - return (rollOrPitch + R_ModelRotationAngle(mobj, viewPlayer)); + return (rollOrPitch + R_ModelRotationAngle(mobj, viewPlayer, fliptilt)); } INT32 R_GetRollAngle(angle_t rollangle) @@ -145,6 +151,7 @@ vector2_t* R_RotateSpriteOffsetsByPitchRoll( mobj_t* mobj, boolean vflip, boolean hflip, + boolean affine, interpmobjstate_t *interp, vector2_t* out, vector2_t* rolloffs) @@ -166,7 +173,13 @@ vector2_t* R_RotateSpriteOffsetsByPitchRoll( angle_t viewingAngle = R_PointToAngle(mobj->x, mobj->y); // rotate ourselves entirely by the sprite's own rotation angle - angle_t visrot = R_SpriteRotationAngle(mobj, NULL, interp); + angle_t visrot = R_SpriteRotationAngle(mobj, NULL, interp, false); + + if (vflip) + { + // Invert the angle for vertically flipped sprites. + visrot = InvAngle(visrot); + } // xoffs = (-cos(xoff) + sin(yoff)) xoffs = diff --git a/src/r_things.cpp b/src/r_things.cpp index 46e3e1d46..2f416c14b 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1178,14 +1178,27 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.affine.d = vis->affine.transform.d; dc.affine.ox = vis->affine.transform.ox; dc.affine.oy = vis->affine.transform.oy; +#ifdef AFFINEROT_TESTER + dc.affineoffset.x = vis->affine.offset.x + cv_affinedebugx.value; +#else dc.affineoffset.x = vis->affine.offset.x; +#endif dc.affineoffset.y = vis->affine.offset.y; R_CopyAffineBounds(&vis->affine.bounds, &dc.affinebound); + fixed_t fixed_ylen = (FRACUNIT*dc.affinebound.ylen); + + if (vis->floorclip) + { + sprbotscreen = sprtopscreen + fixed_ylen; + } + fixed_t xstep = intsign(vis->xiscale) * FRACUNIT; - dc.affineystep = FRACUNIT; + dc.affineystep = (vis->cut & SC_VFLIP) ? -FRACUNIT : FRACUNIT; + + const fixed_t beforeloopoffset = dc.affineoffset.y; if (vis->scalestep) { @@ -1193,6 +1206,7 @@ static void R_DrawVisSprite(vissprite_t *vis) float f_yscale_const = FIXED_TO_FLOAT(spryscale); float f_yscale = FIXED_TO_FLOAT(spryscale); float f_scalestep = FIXED_TO_FLOAT(FixedMul(vis->scalestep, vis->spriteyscale)); + float f_pixel = (vis->cut & SC_VFLIP) ? -1.0f : 1.0f; // Papersprite drawing loop for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, f_yscale += f_scalestep) @@ -1204,7 +1218,7 @@ static void R_DrawVisSprite(vissprite_t *vis) continue; } - dc.affineystep = FLOAT_TO_FIXED(1.0f / _ysc); + dc.affineystep = FLOAT_TO_FIXED(f_pixel / _ysc); angle_t angle = ((vis->centerangle + xtoviewangle[viewssnum][dc.x]) >> ANGLETOFINESHIFT) & 0xFFF; dc.frac = FixedMul((vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)), @@ -1216,8 +1230,9 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; dc.yh = ((sprtopscreen + FLOAT_TO_FIXED(f_ylen * _ysc))-1)>>FRACBITS; - INT32 topscreen = sprtopscreen + spryscale*0; - INT32 bottomscreen = topscreen + FLOAT_TO_FIXED(f_ylen * _ysc); + INT32 topscreen = sprtopscreen; + INT32 bottomscreen = sprbotscreen == INT32_MAX ? topscreen + FLOAT_TO_FIXED(f_ylen * _ysc) + : sprbotscreen + FLOAT_TO_FIXED(f_ylen * _ysc); dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; dc.yh = (bottomscreen-1)>>FRACBITS; @@ -1233,7 +1248,16 @@ static void R_DrawVisSprite(vissprite_t *vis) if (dc.yh >= mfloorclip[dc.x]) dc.yh = mfloorclip[dc.x]-1; if (dc.yl <= mceilingclip[dc.x]) - dc.yl = mceilingclip[dc.x]+1; + { + // Prevent nasty shearing by getting the difference between yl and the ceiling clip. + const INT32 clipdiff = (mceilingclip[dc.x]+1) - dc.yl; + dc.yl += clipdiff; + dc.affineoffset.y = beforeloopoffset - (clipdiff * dc.affineystep); + } + else + { + dc.affineoffset.y = beforeloopoffset; + } if (dc.yl < 0) dc.yl = 0; @@ -1253,18 +1277,9 @@ static void R_DrawVisSprite(vissprite_t *vis) // Non-paper drawing loop for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += xstep, sprtopscreen += vis->shear.tan) { - //texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); - - //column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); - - //if (bmpatch) - //bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - - dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS; - dc.yh = ((sprtopscreen + FRACUNIT*dc.affinebound.ylen)-1)>>FRACBITS; - INT32 topscreen = sprtopscreen + spryscale*0; - INT32 bottomscreen = topscreen + FRACUNIT*dc.affinebound.ylen; + INT32 bottomscreen = sprbotscreen == INT32_MAX ? topscreen + fixed_ylen + : sprbotscreen + fixed_ylen; dc.yl = (topscreen+FRACUNIT-1)>>FRACBITS; dc.yh = (bottomscreen-1)>>FRACBITS; @@ -1280,7 +1295,16 @@ static void R_DrawVisSprite(vissprite_t *vis) if (dc.yh >= mfloorclip[dc.x]) dc.yh = mfloorclip[dc.x]-1; if (dc.yl <= mceilingclip[dc.x]) - dc.yl = mceilingclip[dc.x]+1; + { + // Prevent nasty shearing by getting the difference between yl and the ceiling clip. + const INT32 clipdiff = (mceilingclip[dc.x]+1) - dc.yl; + dc.yl += clipdiff; + dc.affineoffset.y = beforeloopoffset - (clipdiff * FRACUNIT); + } + else + { + dc.affineoffset.y = beforeloopoffset; + } if (dc.yl < 0) dc.yl = 0; @@ -1290,11 +1314,14 @@ static void R_DrawVisSprite(vissprite_t *vis) if (dc.yh >= baseclip && baseclip != -1) dc.yh = baseclip; - dc.frac = frac; + if (dc.yl <= dc.yh && dc.yh > 0 && fixed_ylen != 0) + { + dc.frac = frac; - drawcolumndata_t dc_copy = dc; - coldrawfunc_t* colfunccopy = colfunc; - colfunccopy(&dc); + drawcolumndata_t dc_copy = dc; + coldrawfunc_t* colfunccopy = colfunc; + colfunccopy(&dc); + } } } @@ -1504,12 +1531,12 @@ static void R_SplitSprite(vissprite_t *sprite) newsprite->gzt = sprite->gz; - if (sprite->cut & SC_AFFINE) + /*if (sprite->cut & SC_AFFINE) { // Affines: Fix splitting alignment errors by applying a pixel offset fixed_t szt_diff = (newsprite->sz - cutfrac + 9) << FRACBITS; newsprite->affine.offset.y = std::min(0, -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff)); - } + }*/ sprite->sz = cutfrac; @@ -1797,8 +1824,6 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + patch->height * shadowyscale / 2; shadow->gz = shadow->gzt - patch->height * shadowyscale; shadow->texturemid = FixedMul(interp.scale, FixedDiv(shadow->gzt - viewz, shadowyscale)); - if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) - shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale); shadow->scalestep = 0; shadow->shear.tan = shadowskew; // repurposed variable @@ -2213,7 +2238,7 @@ static void R_ProjectSprite(mobj_t *thing) patch = static_cast(W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE)); #ifdef ROTSPRITE - spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp); + spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, (affinesprite && vflip)); if (spriterotangle && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)) @@ -2248,7 +2273,7 @@ static void R_ProjectSprite(mobj_t *thing) if (affinesprite && ((papersprite && ang >= ANGLE_180) != vflip)) { // Parity with the standard rollangle system - spriterotangle = InvAngle(spriterotangle); + //spriterotangle = InvAngle(spriterotangle); } #endif @@ -2279,6 +2304,8 @@ static void R_ProjectSprite(mobj_t *thing) rotoffset.x = 0; rotoffset.y = 0; + const fixed_t visoffs_xsc = (affinesprite) ? xscale : FixedDiv(FRACUNIT, mapobjectscale); + const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); if (R_ThingIsUsingBakedOffsets(interptarg)) @@ -2286,6 +2313,7 @@ static void R_ProjectSprite(mobj_t *thing) R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, + affinesprite, &interp, &visoffs, &rotoffset); @@ -2312,13 +2340,23 @@ static void R_ProjectSprite(mobj_t *thing) angle = spriterotangle * flipsign; #endif + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); + fixed_t y_piv = ((patch->pivot.y + patch->topoffset) * FRACUNIT); + + if (vflip) + { + // Flip the upper offset, and use *that* as the pivot + y_piv = (patch->height * FRACUNIT) - y_piv; + } + fixed_t sa = FSIN(angle), ca = FCOS(angle); affine_transform.a = FixedDiv(ca, affine_scale.x); affine_transform.b = FixedDiv(-sa, affine_scale.x); affine_transform.c = FixedDiv(sa, affine_scale.y); affine_transform.d = FixedDiv(ca, affine_scale.y); - affine_transform.ox = (patch->pivot.x + patch->leftoffset) * FRACUNIT; - affine_transform.oy = (patch->pivot.y + patch->topoffset) * FRACUNIT; + affine_transform.ox = ((patch->pivot.x + patch->leftoffset) * FRACUNIT) - rolloffs_x; + affine_transform.oy = y_piv - rolloffs_y; V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); @@ -2332,11 +2370,13 @@ static void R_ProjectSprite(mobj_t *thing) spriteyscale = FRACUNIT; } + fixed_t thingyoffset = (FixedDiv(interp.spriteyoffset, highresscale) + FixedDiv((visoffs.y * visoffymul), mapobjectscale) + ((affinesprite) ? 0 : (rotoffset.y * visoffymul))); + if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = FixedDiv(interp.spritexoffset, highresscale); #ifdef ROTSPRITE - spr_topoffset = (FixedDiv(interp.spriteyoffset, highresscale) + FixedDiv((visoffs.y * visoffymul), mapobjectscale) + (rotoffset.y * visoffymul)); + spr_topoffset = thingyoffset; #else spr_topoffset = FixedDiv(interp.spriteyoffset, highresscale); #endif @@ -2350,7 +2390,12 @@ static void R_ProjectSprite(mobj_t *thing) spr_offset += FixedDiv(interp.spritexoffset, highresscale) * flipoffset; #ifdef ROTSPRITE - spr_topoffset += (FixedDiv(interp.spriteyoffset, highresscale) + FixedDiv((visoffs.y * visoffymul), mapobjectscale) + (rotoffset.y * visoffymul)) * flipoffset; + thingyoffset *= flipoffset; + + if (vflip && affinesprite) + thingyoffset *= -1; + + spr_topoffset += thingyoffset; #else spr_topoffset += FixedDiv(interp.spriteyoffset, highresscale) * flipoffset; #endif @@ -2364,10 +2409,10 @@ static void R_ProjectSprite(mobj_t *thing) #ifdef ROTSPRITE if (visoffs.x) { - offset -= FixedDiv((visoffs.x * FRACUNIT), mapobjectscale); + offset -= (visoffs.x * visoffs_xsc); } - if (rotoffset.x) + if ((rotoffset.x) && (!affinesprite)) { offset -= rotoffset.x; } @@ -2667,7 +2712,7 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t rawyscale = FixedDiv(affine_scale.y, FixedMul(this_scale, sortscale)); const float affineyscale = FIXED_TO_FLOAT(affine_scale.y); - const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup) / affineyscale); + const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup) / affineyscale) + thingyoffset; const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale)); @@ -2679,7 +2724,9 @@ static void R_ProjectSprite(mobj_t *thing) useheight = FixedMul(spr_height, FixedMul(spriteyscale, this_scale)); } - if (vflip) + const boolean vflipaffine = (vflip && (affinesprite)); + + if (vflip && (!affinesprite)) { // When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned. // sprite height - sprite topoffset is the proper inverse of the vertical offset, of course. @@ -2689,7 +2736,7 @@ static void R_ProjectSprite(mobj_t *thing) } else { - gzt = interp.z + useoffset; + gzt = interp.z + useoffset + ((vflipaffine) ? interp.height : 0); gz = gzt - useheight; } } From 7792079fbcdf62b4b5c8640c147cf8d899af3b00 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 07:27:09 -0500 Subject: [PATCH 22/42] OGL affines We lose hardware spritesplits in the process; I do not care --- src/hardware/hw_glob.h | 28 +++ src/hardware/hw_main.c | 408 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 394 insertions(+), 42 deletions(-) diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index ca7b514b5..a1d668f80 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -43,6 +43,12 @@ typedef struct float z; } polyvertex_t; +typedef struct +{ + float x; + float y; +} f_vector2_t; + // a convex 'plane' polygon, clockwise order typedef struct { @@ -84,6 +90,28 @@ typedef struct gl_vissprite_s angle_t angle; // for splats + // Affine nonsense + struct { + // Vertex points for the affine sprite to be drawn to. + // + // 4--3 + // | /| + // |/ | + // 1--2 + f_vector2_t p1, p2, p3, p4; + + // The "root", or center, of the affine sprite + polyvertex_t root; + + // Bounding point (leftmost and highest point), to resolve clipping + polyvertex_t bounding_point; + + // Sine/cosine multiplier, used for billboarding. + float patchsin, patchcos; + + affine_t transform; // The actual affine transformation. + } affine; + //Hurdler: 25/04/2000: now support colormap in hardware mode UINT8 *colormap; INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 208e65818..e301d99f5 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3343,6 +3343,8 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) // This is expecting a pointer to an array containing 4 wallVerts for a sprite static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts, const boolean precip) { + const boolean affine = ((spr->mobj && !P_MobjWasRemoved(spr->mobj)) && ((spr->mobj->player != NULL) || R_ThingIsAffineSprite(spr->mobj))); + if (cv_glspritebillboarding.value && spr && spr->mobj && !R_ThingIsPaperSprite(spr->mobj) && wallVerts) @@ -3370,20 +3372,42 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts // X, Y, AND Z need to be manipulated for the polys to rotate around the // origin, because of how the origin setting works I believe that should // be mobj->z or mobj->z + mobj->height - wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey; - wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey; - // translate back to be around 0 before translating back - wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + if (affine) + { + wallVerts[0].y = (spr->affine.p1.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[0].x += (spr->affine.p1.y * gl_viewludcos) * gl_viewcos; + wallVerts[0].z += (spr->affine.p1.y * gl_viewludcos) * gl_viewsin; - wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; - wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[1].y = (spr->affine.p2.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[1].x += (spr->affine.p2.y * gl_viewludcos) * gl_viewcos; + wallVerts[1].z += (spr->affine.p2.y * gl_viewludcos) * gl_viewsin; - wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; - wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[2].y = (spr->affine.p3.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[2].x += (spr->affine.p3.y * gl_viewludcos) * gl_viewcos; + wallVerts[2].z += (spr->affine.p3.y * gl_viewludcos) * gl_viewsin; + + wallVerts[3].y = (spr->affine.p4.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[3].x += (spr->affine.p4.y * gl_viewludcos) * gl_viewcos; + wallVerts[3].z += (spr->affine.p4.y * gl_viewludcos) * gl_viewsin; + } + else + { + wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey; + wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey; + // translate back to be around 0 before translating back + wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; + wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; + + wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + + wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + + wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + } } } @@ -3468,7 +3492,14 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) HWR_RotateSpritePolyToAim(spr, baseWallVerts, false); // push it toward the camera to mitigate floor-clipping sprites - float sprdist = sqrtf((spr->x1 - gl_viewx)*(spr->x1 - gl_viewx) + (spr->z1 - gl_viewy)*(spr->z1 - gl_viewy) + (spr->gzt - gl_viewz)*(spr->gzt - gl_viewz)); + float sprdist = 0; + + float f_xdiff = (spr->x1 - gl_viewx); + float f_ydiff = (spr->z1 - gl_viewy); + float f_zdiff = (spr->gzt - gl_viewz); + + sprdist = sqrtf((f_xdiff*f_xdiff) + (f_ydiff*f_ydiff) + (f_zdiff*f_zdiff)); + float distfact = ((2.0f*spr->dispoffset) + 20.0f) / sprdist; for (i = 0; i < 4; i++) { @@ -3746,6 +3777,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) patch_t *gpatch; GLPatch_t *hwrpatch; FSurfaceInfo Surf = {}; + const boolean affine = ((spr->mobj && !P_MobjWasRemoved(spr->mobj)) && ((spr->mobj->player != NULL) || R_ThingIsAffineSprite(spr->mobj))); const boolean splat = R_ThingIsFloorSprite(spr->mobj); if (!spr->mobj || !spr->mobj->subsector) @@ -3753,7 +3785,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) if (spr->mobj->subsector->sector->numlights && (spr->mobj->renderflags & RF_ABSOLUTELIGHTLEVEL) == 0 - && !splat) + && !splat && !affine) { HWR_SplitSprite(spr); return; @@ -3886,6 +3918,29 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) wallVerts[i].y = FIXED_TO_FLOAT(spr->gz) + zoffset; } } + else if (affine) + { + // Affines are weird; we've generated the transformations for them, + // so now we need to piece together each wall vertex. + const float _cos = spr->affine.patchcos; + const float _sin = spr->affine.patchsin; + + wallVerts[0].x = spr->affine.root.x + (spr->affine.p1.x * _cos); + wallVerts[0].z = spr->affine.root.z + (spr->affine.p1.x * _sin); + wallVerts[0].y = spr->affine.root.y + spr->affine.p1.y; + + wallVerts[1].x = spr->affine.root.x + (spr->affine.p2.x * _cos); + wallVerts[1].z = spr->affine.root.z + (spr->affine.p2.x * _sin); + wallVerts[1].y = spr->affine.root.y + spr->affine.p2.y; + + wallVerts[2].x = spr->affine.root.x + (spr->affine.p3.x * _cos); + wallVerts[2].z = spr->affine.root.z + (spr->affine.p3.x * _sin); + wallVerts[2].y = spr->affine.root.y + spr->affine.p3.y; + + wallVerts[3].x = spr->affine.root.x + (spr->affine.p4.x * _cos); + wallVerts[3].z = spr->affine.root.z + (spr->affine.p4.x * _sin); + wallVerts[3].y = spr->affine.root.y + spr->affine.p4.y; + } else { // these were already scaled in HWR_ProjectSprite @@ -3939,7 +3994,27 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) HWR_RotateSpritePolyToAim(spr, wallVerts, false); // push it toward the camera to mitigate floor-clipping sprites - sprdist = sqrtf((spr->x1 - gl_viewx)*(spr->x1 - gl_viewx) + (spr->z1 - gl_viewy)*(spr->z1 -gl_viewy)+ (spr->gzt - gl_viewz)*(spr->gzt - gl_viewz)); + polyvertex_t pushpoint = {0}; + + if (affine) + { + pushpoint.x = spr->affine.bounding_point.x; + pushpoint.y = spr->affine.bounding_point.y; + pushpoint.z = spr->affine.bounding_point.z; + } + else + { + pushpoint.x = spr->x1; + pushpoint.y = spr->z1; + pushpoint.z = spr->gzt; + } + + float f_xdiff = (pushpoint.x - gl_viewx); + float f_ydiff = (pushpoint.y - gl_viewy); + float f_zdiff = (pushpoint.z - gl_viewz); + + sprdist = sqrtf((f_xdiff*f_xdiff) + (f_ydiff*f_ydiff) + (f_zdiff*f_zdiff)); + distfact = ((2.0f* spr->dispoffset) + 20.0f) / sprdist; for (i = 0; i < 4; i++) { @@ -3976,9 +4051,21 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) if (!R_ThingIsFullBright(spr->mobj) && !(spr->mobj->renderflags & RF_NOCOLORMAPS)) colormap = sector->extra_colormap; - if (splat && sector->numlights) + if ((splat||affine) && sector->numlights) { - INT32 light = R_GetPlaneLight(sector, spr->mobj->z, false); + // Affine splitting is going to require more advanced tricks + // (GLSL fragment shader, or generating a "split" polygon on the fly). + // I've already spent too much time on this damn thing, so I'm just doing + // what precipitation (and, by extension, MD2) does. + + fixed_t _zoffs = 0; + + if (affine) + { + _zoffs = spr->mobj->height; // Always use the light at the top instead of whatever I was doing before + } + + INT32 light = R_GetPlaneLight(sector, spr->mobj->z + _zoffs, false); if (!lightset) lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel; @@ -4823,6 +4910,14 @@ static void HWR_ProjectSprite(mobj_t *thing) vector2_t visoffs, rotoffset; #endif + // Affines + boolean affinesprite = ((thing->player != NULL) || R_ThingIsAffineSprite(thing)); + affine_t affine_transform = {0}; + vector2_t affine_scale = {0}; + polyvertex_t affine_rootpoint = {0}; + f_vector2_t affine_point[4] = {0}; + float leftbound = 0, backbound = 0, topbound = 0; + // uncapped/interpolation interpmobjstate_t interp = {0}; mobj_t *interptarg; @@ -4991,11 +5086,14 @@ static void HWR_ProjectSprite(mobj_t *thing) spr_offset = spritecachedinfo[lumpoff].offset; spr_topoffset = spritecachedinfo[lumpoff].topoffset; + float base_topoffs = FIXED_TO_FLOAT(spr_topoffset); + #ifdef ROTSPRITE spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, false); if (spriterotangle != 0 - && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) + && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)) + && (!affinesprite)) // Affines are capable of rotation; this is redundant { rollangle = R_GetRollAngle(papersprite == vflip ? spriterotangle : InvAngle(spriterotangle)); @@ -5036,6 +5134,39 @@ static void HWR_ProjectSprite(mobj_t *thing) } #endif + if (affinesprite) + { + affine_scale.x = FLOAT_TO_FIXED(spritexscale * this_scale); + affine_scale.y = FLOAT_TO_FIXED(spriteyscale * this_scale); + + angle_t angle; + + INT32 flipsign = ((flip && papersprite) ? -1 : 1); // Flip OGL affine papersprites for Software parity + + angle = spriterotangle * flipsign; + + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); + fixed_t y_piv = spr_topoffset; + + if (vflip) + { + // Flip the upper offset, and use *that* as the pivot + y_piv = spr_height - y_piv; + } + + fixed_t sa = FSIN(angle), ca = FCOS(angle); + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.x); + affine_transform.c = FixedDiv(sa, affine_scale.y); + affine_transform.d = FixedDiv(ca, affine_scale.y); + affine_transform.ox = spr_offset - rolloffs_x; + affine_transform.oy = y_piv - rolloffs_y; + + spritexscale = 1.0; + spriteyscale = 1,0; + } + if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = FixedDiv(interp.spritexoffset,highresscale); @@ -5115,23 +5246,150 @@ static void HWR_ProjectSprite(mobj_t *thing) visoffs.x = (FixedDiv((visoffs.x * FRACUNIT), mapobjectscale)); } #endif - if (flip) + if (affinesprite) { + x1 = x2 = z1 = z2 = 0.0f; + + float f_offs, f_width, f_height; + + f_offs = FIXED_TO_FLOAT(spr_offset); + f_width = FIXED_TO_FLOAT(spr_width); + f_height = FIXED_TO_FLOAT(spr_height); + + // From HWR_DrawAffinePatch + + float fa = FIXED_TO_FLOAT(affine_transform.a); + float fd = FIXED_TO_FLOAT(affine_transform.d); + float fc = FIXED_TO_FLOAT(affine_transform.c); + float fb = FIXED_TO_FLOAT(affine_transform.b); + float fx = FIXED_TO_FLOAT(affine_transform.ox); + float fy = FIXED_TO_FLOAT(affine_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) + { + 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 + affine_point[3].x = (ba * -fx + bb * -fy + fx); + affine_point[3].y = (bc * -fx + bd * -fy + fy); + + affine_point[2].x = ba * (f_width - fx) + bb * -fy + fx; + affine_point[2].y = bc * (f_width - fx) + bd * -fy + fy; + + affine_point[0].x = ba * -fx + bb * (f_height - fy) + fx; + affine_point[0].y = bc * -fx + bd * (f_height - fy) + fy; + + affine_point[1].x = ba * (f_width - fx) + bb * (f_height - fy) + fx; + affine_point[1].y = bc * (f_width - fx) + bd * (f_height - fy) + fy; + + // Focus each point on the center. + affine_point[0].x = fx - affine_point[0].x; + affine_point[1].x = fx - affine_point[1].x; + affine_point[2].x = fx - affine_point[2].x; + affine_point[3].x = fx - affine_point[3].x; + + affine_point[1].y -= fy; + affine_point[2].y -= fy; + affine_point[0].y -= fy; + affine_point[3].y -= fy; + + // ...okay, now comb through all four vertices and set the output bounds based on this. + // "Why not a loop?" According to SM64 programming wizard Kaze Emanuar, + // loops take more processing time. If we can help it, it's better to just cut corners. + float f_left = 0; + float f_bottom = 0; +#define BOUNDCHECK(i) \ + { \ + f_left = min(affine_point[i].x, f_left); \ + f_bottom = max(affine_point[i].y, f_bottom); \ + } + + BOUNDCHECK(0); + BOUNDCHECK(1); + BOUNDCHECK(2); + BOUNDCHECK(3); + +#undef BOUNDCHECK + + backbound = (f_left * rightsin); + leftbound = (f_left * rightcos); + topbound = f_bottom * -1; + + // Invert the Y values. + affine_point[0].y *= -1.0f; + affine_point[1].y *= -1.0f; + affine_point[2].y *= -1.0f; + affine_point[3].y *= -1.0f; + + affine_rootpoint.x = tr_x; + affine_rootpoint.z = tr_y; + #ifdef ROTSPRITE - spr_offset -= visoffs.x; - spr_offset -= rotoffset.x; + const float xrescale = this_xscale / FIXED_TO_FLOAT(mapobjectscale); + float f_visx = (FIXED_TO_FLOAT(FixedMul(visoffs.x, mapobjectscale))) * xrescale; + if (flip) + { + spr_offset -= visoffs.x; + affine_rootpoint.x -= (f_visx * rightcos); + affine_rootpoint.z -= (f_visx * rightsin); + } + else + { + spr_offset += visoffs.x; + affine_rootpoint.x += (f_visx * rightcos); + affine_rootpoint.z += (f_visx * rightsin); + } #endif - x1 = (FIXED_TO_FLOAT((spr_width - spr_offset)) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + } + else + { + goto nodeterminant; + } + } else { +nodeterminant: + if (flip) + { #ifdef ROTSPRITE - spr_offset += visoffs.x; - spr_offset += rotoffset.x; + spr_offset -= visoffs.x; + spr_offset -= rotoffset.x; #endif - x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + x1 = (FIXED_TO_FLOAT((spr_width - spr_offset)) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + } + else + { +#ifdef ROTSPRITE + spr_offset += visoffs.x; + spr_offset += rotoffset.x; +#endif + x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + } + + z1 = tr_y + x1 * rightsin; + z2 = tr_y - x2 * rightsin; + x1 = tr_x + x1 * rightcos; + x2 = tr_x - x2 * rightcos; } // test if too close /* @@ -5145,23 +5403,46 @@ static void HWR_ProjectSprite(mobj_t *thing) } */ - z1 = tr_y + x1 * rightsin; - z2 = tr_y - x2 * rightsin; - x1 = tr_x + x1 * rightcos; - x2 = tr_x - x2 * rightcos; if (thing->terrain && thing->terrain->floorClip) - spr_topoffset -= thing->terrain->floorClip; + spr_topoffset -= thing->terrain->floorClip; + + affine_rootpoint.y = 0; + + const float f_topoffs = FIXED_TO_FLOAT(spr_topoffset); if (vflip) { - gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); + affine_rootpoint.y = FIXED_TO_FLOAT(interp.z + thing->height) - (f_topoffs * this_yscale); } else { - gzt = FIXED_TO_FLOAT(interp.z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + affine_rootpoint.y = FIXED_TO_FLOAT(interp.z) + (f_topoffs * this_yscale); + } + + if (!affinesprite) + { + if (vflip) + { + gz = affine_rootpoint.y; + gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); + } + else + { + gzt = affine_rootpoint.y; + gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + } + } + else + { + if (vflip) + { + affine_rootpoint.y += (base_topoffs * this_yscale); + } + else + { + affine_rootpoint.y -= (base_topoffs * this_yscale); + } } } @@ -5227,10 +5508,50 @@ static void HWR_ProjectSprite(mobj_t *thing) // store information in a vissprite vis = HWR_NewVisSprite(); - vis->x1 = x1; - vis->x2 = x2; - vis->z1 = z1; - vis->z2 = z2; + + if (affinesprite) + { + vis->affine.p1.x = affine_point[0].x; + vis->affine.p1.y = affine_point[0].y; + + vis->affine.p2.x = affine_point[1].x; + vis->affine.p2.y = affine_point[1].y; + + vis->affine.p3.x = affine_point[2].x; + vis->affine.p3.y = affine_point[2].y; + + vis->affine.p4.x = affine_point[3].x; + vis->affine.p4.y = affine_point[3].y; + + vis->affine.root.x = affine_rootpoint.x; + vis->affine.root.y = affine_rootpoint.y; + vis->affine.root.z = affine_rootpoint.z; + + vis->affine.patchcos = rightcos; + vis->affine.patchsin = rightsin; + + vis->affine.transform.a = affine_transform.a; + vis->affine.transform.c = affine_transform.b; + vis->affine.transform.b = affine_transform.c; + vis->affine.transform.d = affine_transform.d; + vis->affine.transform.ox = affine_transform.ox; + vis->affine.transform.oy = affine_transform.oy; + + vis->affine.bounding_point.x = affine_rootpoint.x + leftbound; + vis->affine.bounding_point.y = affine_rootpoint.z + backbound; + vis->affine.bounding_point.z = affine_rootpoint.y + topbound; + + // We're using this info to draw the affine sprite; zero-out "standard" draw info. + vis->x1 = vis->x2 = vis->z1 = vis->z2 = 0; + vis->gz = vis->gzt = 0; + } + else + { + vis->x1 = x1; + vis->x2 = x2; + vis->z1 = z1; + vis->z2 = z2; + } vis->tz = tz; // Keep tz for the simple sprite sorting that happens vis->tracertz = tracertz; @@ -5303,9 +5624,12 @@ static void HWR_ProjectSprite(mobj_t *thing) vis->colormap += COLORMAP_REMAPOFFSET; } - // set top/bottom coords - vis->gzt = gzt; - vis->gz = gz; + if (!affinesprite) + { + // set top/bottom coords + vis->gzt = gzt; + vis->gz = gz; + } //CONS_Debug(DBG_RENDER, "------------------\nH: sprite : %d\nH: frame : %x\nH: type : %d\nH: sname : %s\n\n", // thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]); From 1c932778db070e83fccc259ac7f8cb02f4c57645 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 07:27:18 -0500 Subject: [PATCH 23/42] r_things cleanup --- src/r_things.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index 2f416c14b..34e53afed 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2100,8 +2100,6 @@ static void R_ProjectSprite(mobj_t *thing) return; // aspect ratio stuff - - if (affinesprite) { affine_distscale.x = affine_scale.x = FixedDiv(projection[viewssnum], tz); From 98efe198acc6ae3202fb5ffacbbe3d822ab099a1 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 07:27:44 -0500 Subject: [PATCH 24/42] Make S-Monitor's aura affine --- src/k_kart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_kart.c b/src/k_kart.c index e2c79049e..dd41ada6c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5115,6 +5115,7 @@ void K_DoSMonitor(player_t *player, tic_t time) aura->destscale = player->mo->scale; P_SetScale(aura, player->mo->scale); aura->extravalue2 = 1; + aura->renderflags2 |= RF2_AFFINE; } // Rim suggestion: Don't allow already invincible players to chain. From d650014fd6ae73fe4bd8964f3c1c191200ccfc01 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 07:31:28 -0500 Subject: [PATCH 25/42] Get rid of software affine debug values --- src/p_tick.c | 8 -------- src/r_draw_column.cpp | 20 -------------------- src/r_main.cpp | 14 -------------- src/r_main.h | 6 ------ src/r_things.cpp | 12 +----------- 5 files changed, 1 insertion(+), 59 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index 3b47f7c03..929615fa2 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -884,14 +884,6 @@ void P_Ticker(boolean run) { leveltime++; -#ifdef AFFINEROT_TESTER - if (cv_affinerottest.value == 2) - { - fixed_t newaffineangle = (cv_affineangle.value + (FRACUNIT)) % (360 * FRACUNIT); - CV_StealthSet(&cv_affineangle, va("%f", FIXED_TO_FLOAT(newaffineangle))); - } -#endif - if (starttime > introtime && leveltime == starttime) { ACS_RunRaceStartScript(); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 63d1362c6..61b9f7615 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -533,9 +533,6 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc) while (--count); } -// Purely for debug; fills all columns in the drawing area with a pair of "background colors" -//#define FILL_DRAW_AREA - template static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) { @@ -642,31 +639,14 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) ux += FixedMul(b, ystep_delta); uy += FixedMul(d, ystep_delta); -#ifdef FILL_DRAW_AREA - UINT16 pixel; -#endif - if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) { -#ifdef FILL_DRAW_AREA - pixel = 65461; - *dest = (UINT8)(pixel & 0xff); -#endif continue; } - -#ifdef FILL_DRAW_AREA - pixel = reinterpret_cast(dc->source)[srcy * pw + srcx]; - if (pixel < 0xff00) - { - pixel = 65315; // Uhhhhhhhh - } -#else const UINT8 pixel = R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); if (pixel == TRANSPARENTPIXEL) continue; -#endif *dest = pixel; } diff --git a/src/r_main.cpp b/src/r_main.cpp index 509491b8e..77f004feb 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -200,13 +200,6 @@ consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; -#ifdef AFFINEROT_TESTER -consvar_t cv_affineangle = CVAR_INIT ("affineangle", "0", CV_FLOAT, affineangle_cons_t, NULL); -consvar_t cv_affinecuttest = CVAR_INIT ("affinecuttest", "1.0", CV_FLOAT, CV_Signed, NULL); -consvar_t cv_affinedebugx = CVAR_INIT ("affinedebugx", "0.0", CV_FLOAT, CV_Signed, NULL); -consvar_t cv_affinerottest = CVAR_INIT ("affinerottest", "Off", CV_FLOAT, affinetest_cons_t, NULL); -#endif - void SplitScreen_OnChange(void) { UINT8 i; @@ -1822,13 +1815,6 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_playerfade); -#ifdef AFFINEROT_TESTER - CV_RegisterVar(&cv_affineangle); - CV_RegisterVar(&cv_affinecuttest); - CV_RegisterVar(&cv_affinedebugx); - CV_RegisterVar(&cv_affinerottest); -#endif - CV_RegisterVar(&cv_movebob); // Frame interpolation/uncapped diff --git a/src/r_main.h b/src/r_main.h index a80c86c0e..57889b115 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -161,12 +161,6 @@ extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; -#define AFFINEROT_TESTER - -#ifdef AFFINEROT_TESTER -extern consvar_t cv_affineangle, cv_affinecuttest, cv_affinedebugx, cv_affinerottest; -#endif - // debugging typedef enum { diff --git a/src/r_things.cpp b/src/r_things.cpp index 34e53afed..ff37761f5 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1178,11 +1178,8 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.affine.d = vis->affine.transform.d; dc.affine.ox = vis->affine.transform.ox; dc.affine.oy = vis->affine.transform.oy; -#ifdef AFFINEROT_TESTER - dc.affineoffset.x = vis->affine.offset.x + cv_affinedebugx.value; -#else + dc.affineoffset.x = vis->affine.offset.x; -#endif dc.affineoffset.y = vis->affine.offset.y; R_CopyAffineBounds(&vis->affine.bounds, &dc.affinebound); @@ -2329,14 +2326,7 @@ static void R_ProjectSprite(mobj_t *thing) INT32 flipsign = ((flip) ? -1 : 1); -#ifdef AFFINEROT_TESTER - if (!cv_affinerottest.value) - angle = spriterotangle * flipsign; - else - angle = FixedAngle(cv_affineangle.value); -#else angle = spriterotangle * flipsign; -#endif const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); From ac5cc2eaaa39156b027e64d4a37352f4cacb3715 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 07:38:58 -0500 Subject: [PATCH 26/42] Experiment - make ALL sprites use affines Per internal talks --- src/deh_tables.c | 4 ++-- src/k_kart.c | 1 - src/p_pspr.h | 4 ++-- src/p_user.c | 3 --- src/r_defs.h | 2 +- src/r_things.cpp | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 1e7794af7..5f7c4f28f 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -948,7 +948,7 @@ struct int_const_s const INT_CONST[] = { {"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP}, {"FF_PAPERSPRITE",FF_PAPERSPRITE}, {"FF_FLOORSPRITE",FF_FLOORSPRITE}, - {"FF_AFFINESPRITE",FF_AFFINESPRITE}, + {"FF_NOAFFINE",FF_NOAFFINE}, {"FF_BLENDMASK",FF_BLENDMASK}, {"FF_BLENDSHIFT",FF_BLENDSHIFT}, {"FF_ADD",FF_ADD}, @@ -1058,7 +1058,7 @@ struct int_const_s const INT_CONST[] = { {"RF_TRANS90",RF_TRANS90}, {"RF_GHOSTLY",RF_GHOSTLY}, {"RF_GHOSTLYMASK",RF_GHOSTLYMASK}, - {"RF2_AFFINE",RF2_AFFINE}, + {"RF2_NOAFFINE",RF2_NOAFFINE}, // Level flags {"LF_SCRIPTISFILE",LF_SCRIPTISFILE}, diff --git a/src/k_kart.c b/src/k_kart.c index dd41ada6c..e2c79049e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5115,7 +5115,6 @@ void K_DoSMonitor(player_t *player, tic_t time) aura->destscale = player->mo->scale; P_SetScale(aura, player->mo->scale); aura->extravalue2 = 1; - aura->renderflags2 |= RF2_AFFINE; } // Rim suggestion: Don't allow already invincible players to chain. diff --git a/src/p_pspr.h b/src/p_pspr.h index faece777a..d508f909e 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -90,8 +90,8 @@ extern "C" { /// \brief Frame flags: Flip sprite horizontally #define FF_HORIZONTALFLIP 0x02000000 -/// \brief Frame flags: Mode 7 affines! -#define FF_AFFINESPRITE 0x04000000 +/// \brief Frame flags: Turns off Mode 7-style scaling and rotation +#define FF_NOAFFINE 0x04000000 /// \brief Frame flags - Animate: Simple stateless animation #define FF_ANIMATE 0x10000000 diff --git a/src/p_user.c b/src/p_user.c index 6e874393c..cd150c864 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1263,9 +1263,6 @@ mobj_t *P_SpawnGhostMobjEX(mobj_t *mobj, boolean legacy) if (mobj->player) { - // Players use affine rendering, so match that - ghost->renderflags2 |= RF2_AFFINE; - if(mobj->player->followmobj) { mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj); diff --git a/src/r_defs.h b/src/r_defs.h index ccc5dca1d..d0db384f8 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1038,7 +1038,7 @@ typedef enum typedef enum { - RF2_AFFINE = 0x00000001, // Affine sprite; draws scaling and rotation using a Mode 7 matrix + RF2_NOAFFINE = 0x00000001, // Disables affine drawing for this sprite } renderflags2_t; typedef enum diff --git a/src/r_things.cpp b/src/r_things.cpp index ff37761f5..9408877df 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -4301,7 +4301,7 @@ boolean R_ThingIsPaperSprite(mobj_t *thing) boolean R_ThingIsAffineSprite(mobj_t *thing) { - return (thing->frame & FF_AFFINESPRITE || thing->renderflags2 & RF2_AFFINE); + return (!(thing->frame & FF_NOAFFINE || thing->renderflags2 & RF2_NOAFFINE)); } boolean R_ThingIsFloorSprite(mobj_t *thing) From afacf5506743644e0c44a0200529505266007af2 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 08:08:04 -0500 Subject: [PATCH 27/42] FF_AFFINEPAPER and RF2_AFFINEPAPER He made a BANDAID so TRASH even his TEAM clowned him --- src/deh_tables.c | 2 ++ src/p_pspr.h | 3 +++ src/r_defs.h | 1 + src/r_things.cpp | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 5f7c4f28f..b8b55e9cb 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -949,6 +949,7 @@ struct int_const_s const INT_CONST[] = { {"FF_PAPERSPRITE",FF_PAPERSPRITE}, {"FF_FLOORSPRITE",FF_FLOORSPRITE}, {"FF_NOAFFINE",FF_NOAFFINE}, + {"FF_AFFINEPAPER",FF_AFFINEPAPER}, {"FF_BLENDMASK",FF_BLENDMASK}, {"FF_BLENDSHIFT",FF_BLENDSHIFT}, {"FF_ADD",FF_ADD}, @@ -1059,6 +1060,7 @@ struct int_const_s const INT_CONST[] = { {"RF_GHOSTLY",RF_GHOSTLY}, {"RF_GHOSTLYMASK",RF_GHOSTLYMASK}, {"RF2_NOAFFINE",RF2_NOAFFINE}, + {"RF2_AFFINEPAPER",RF2_AFFINEPAPER}, // Level flags {"LF_SCRIPTISFILE",LF_SCRIPTISFILE}, diff --git a/src/p_pspr.h b/src/p_pspr.h index d508f909e..12fbb3b95 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -93,6 +93,9 @@ extern "C" { /// \brief Frame flags: Turns off Mode 7-style scaling and rotation #define FF_NOAFFINE 0x04000000 +/// \brief Frame flags: Enables "affine papersprites" +#define FF_AFFINEPAPER 0x08000000 + /// \brief Frame flags - Animate: Simple stateless animation #define FF_ANIMATE 0x10000000 /// \brief Frame flags - Animate: Sync animation to global timer (mutually exclusive with below, currently takes priority) diff --git a/src/r_defs.h b/src/r_defs.h index d0db384f8..c84bf1bad 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1039,6 +1039,7 @@ typedef enum typedef enum { RF2_NOAFFINE = 0x00000001, // Disables affine drawing for this sprite + RF2_AFFINEPAPER = 0x00000002, // Enables "affine papersprites" } renderflags2_t; typedef enum diff --git a/src/r_things.cpp b/src/r_things.cpp index 9408877df..7ce247153 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -4296,12 +4296,22 @@ boolean R_ThingVerticallyFlipped(mobj_t *thing) boolean R_ThingIsPaperSprite(mobj_t *thing) { - return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE); + boolean affinepaper = (thing->frame & FF_AFFINEPAPER || thing->renderflags2 & RF2_AFFINEPAPER); + + return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE || affinepaper); } boolean R_ThingIsAffineSprite(mobj_t *thing) { - return (!(thing->frame & FF_NOAFFINE || thing->renderflags2 & RF2_NOAFFINE)); + boolean affinepaper = (thing->frame & FF_AFFINEPAPER || thing->renderflags2 & RF2_AFFINEPAPER); + + // Affine papersprites are messy in software rendering; let modders enable them at their own discretion. + // Yes, I'm lazy; I've been at this for TWO WEEKS. + boolean papersprite = (R_ThingIsPaperSprite(thing) && !affinepaper); + + boolean notaffine = ((thing->frame & FF_NOAFFINE || thing->renderflags2 & RF2_NOAFFINE) || papersprite); + + return (!notaffine); } boolean R_ThingIsFloorSprite(mobj_t *thing) From 3880139febce8a574c27b349283b435510a8dba6 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 09:27:50 -0500 Subject: [PATCH 28/42] "Fake rollangle" rotating --- src/hardware/hw_main.c | 2 +- src/r_main.cpp | 3 +++ src/r_main.h | 2 ++ src/r_patch.h | 1 + src/r_patchrotation.c | 6 ++++++ src/r_things.cpp | 2 +- 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index e301d99f5..8cb71bfe7 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5143,7 +5143,7 @@ static void HWR_ProjectSprite(mobj_t *thing) INT32 flipsign = ((flip && papersprite) ? -1 : 1); // Flip OGL affine papersprites for Software parity - angle = spriterotangle * flipsign; + angle = R_ConvToRollAngle(spriterotangle) * flipsign; const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); diff --git a/src/r_main.cpp b/src/r_main.cpp index 77f004feb..c41e79d3b 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -197,6 +197,8 @@ consvar_t cv_sliptidetilt = CVAR_INIT ("sliptidetilt", "On", CV_SAVE, CV_OnOff, consvar_t cv_nulldriftefx = CVAR_INIT ("nulldriftefx", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_fakerollangle = CVAR_INIT ("fakerollangle", "Off", CV_SAVE, CV_OnOff, NULL); + static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; @@ -1807,6 +1809,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_sliptidetilt); CV_RegisterVar(&cv_nulldriftefx); CV_RegisterVar(&cv_nulldrifttilt); + CV_RegisterVar(&cv_fakerollangle); CV_RegisterVar(&cv_showhud); CV_RegisterVar(&cv_translucenthud); diff --git a/src/r_main.h b/src/r_main.h index 57889b115..b8a62812d 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -161,6 +161,8 @@ extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; +extern consvar_t cv_fakerollangle; + // debugging typedef enum { diff --git a/src/r_patch.h b/src/r_patch.h index d23280c91..4ae365f58 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -58,6 +58,7 @@ vector2_t* R_RotateSpriteOffsetsByPitchRoll( interpmobjstate_t *interp, vector2_t* out, vector2_t* rolloffs); +angle_t R_ConvToRollAngle(angle_t ang); #endif #ifdef __cplusplus diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index e12e720ac..ca854dcaf 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -147,6 +147,12 @@ INT32 R_GetRollAngle(angle_t rollangle) #define VISROTMUL (ANG1 * ROTANGDIFF) +// Simulates "rollangle" angling +angle_t R_ConvToRollAngle(angle_t ang) +{ + return (cv_fakerollangle.value) ? (R_GetRollAngle(ang) * VISROTMUL) : ang; +} + vector2_t* R_RotateSpriteOffsetsByPitchRoll( mobj_t* mobj, boolean vflip, diff --git a/src/r_things.cpp b/src/r_things.cpp index 7ce247153..aa004d90f 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2326,7 +2326,7 @@ static void R_ProjectSprite(mobj_t *thing) INT32 flipsign = ((flip) ? -1 : 1); - angle = spriterotangle * flipsign; + angle = R_ConvToRollAngle(spriterotangle) * flipsign; const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); From de8f2e40daf2966dec2709e8b7dabb71f83e6989 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 11:28:16 -0500 Subject: [PATCH 29/42] Toggle for affine pre-scaling Now you can choose between sane rotation + scaling, or SRB2 rotation + scaling Hooray?! --- src/hardware/hw_main.c | 21 +++++++++++++++++---- src/r_defs.h | 1 + src/r_main.cpp | 3 +++ src/r_main.h | 1 + src/r_things.cpp | 26 ++++++++++++++++++++++---- src/r_things.h | 1 + 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 8cb71bfe7..bed2b5ddf 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5156,10 +5156,23 @@ static void HWR_ProjectSprite(mobj_t *thing) } fixed_t sa = FSIN(angle), ca = FCOS(angle); - affine_transform.a = FixedDiv(ca, affine_scale.x); - affine_transform.b = FixedDiv(-sa, affine_scale.x); - affine_transform.c = FixedDiv(sa, affine_scale.y); - affine_transform.d = FixedDiv(ca, affine_scale.y); + + if (R_AffinePreScale(interptarg)) + { + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.x); + affine_transform.c = FixedDiv(sa, affine_scale.y); + affine_transform.d = FixedDiv(ca, affine_scale.y); + } + else + { + // Simulate shitty SRB2 "scale after rotate" nonsense + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.y); + affine_transform.c = FixedDiv(sa, affine_scale.x); + affine_transform.d = FixedDiv(ca, affine_scale.y); + } + affine_transform.ox = spr_offset - rolloffs_x; affine_transform.oy = y_piv - rolloffs_y; diff --git a/src/r_defs.h b/src/r_defs.h index c84bf1bad..74e95cf2a 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1040,6 +1040,7 @@ typedef enum { RF2_NOAFFINE = 0x00000001, // Disables affine drawing for this sprite RF2_AFFINEPAPER = 0x00000002, // Enables "affine papersprites" + RF2_AFFINEPRESCALE = 0x00000004, // Makes affines scale before rotating, instead of rotating before scaling } renderflags2_t; typedef enum diff --git a/src/r_main.cpp b/src/r_main.cpp index c41e79d3b..be7a995ed 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -199,6 +199,8 @@ consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff consvar_t cv_fakerollangle = CVAR_INIT ("fakerollangle", "Off", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_affineprescale = CVAR_INIT ("affineprescale", "Off", CV_SAVE, CV_OnOff, NULL); + static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; @@ -1810,6 +1812,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_nulldriftefx); CV_RegisterVar(&cv_nulldrifttilt); CV_RegisterVar(&cv_fakerollangle); + CV_RegisterVar(&cv_affineprescale); CV_RegisterVar(&cv_showhud); CV_RegisterVar(&cv_translucenthud); diff --git a/src/r_main.h b/src/r_main.h index b8a62812d..b1f066705 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -162,6 +162,7 @@ extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; extern consvar_t cv_fakerollangle; +extern consvar_t cv_affineprescale; // debugging diff --git a/src/r_things.cpp b/src/r_things.cpp index aa004d90f..8198ca96a 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2339,10 +2339,23 @@ static void R_ProjectSprite(mobj_t *thing) } fixed_t sa = FSIN(angle), ca = FCOS(angle); - affine_transform.a = FixedDiv(ca, affine_scale.x); - affine_transform.b = FixedDiv(-sa, affine_scale.x); - affine_transform.c = FixedDiv(sa, affine_scale.y); - affine_transform.d = FixedDiv(ca, affine_scale.y); + + if (R_AffinePreScale(interptarg)) + { + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.x); + affine_transform.c = FixedDiv(sa, affine_scale.y); + affine_transform.d = FixedDiv(ca, affine_scale.y); + } + else + { + // Simulate shitty SRB2 "scale after rotate" nonsense + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.y); + affine_transform.c = FixedDiv(sa, affine_scale.x); + affine_transform.d = FixedDiv(ca, affine_scale.y); + } + affine_transform.ox = ((patch->pivot.x + patch->leftoffset) * FRACUNIT) - rolloffs_x; affine_transform.oy = y_piv - rolloffs_y; @@ -4340,6 +4353,11 @@ boolean R_ThingIsFullDark(mobj_t *thing) return ((thing->frame & FF_BRIGHTMASK) == FF_FULLDARK); } +boolean R_AffinePreScale(mobj_t *thing) +{ + return (cv_affineprescale.value || thing->renderflags2 & RF2_AFFINEPRESCALE); +} + // // R_DrawMasked // diff --git a/src/r_things.h b/src/r_things.h index c3acd679d..31924f34a 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -96,6 +96,7 @@ boolean R_ThingIsAffineSprite (mobj_t *thing); boolean R_ThingIsFullBright (mobj_t *thing); boolean R_ThingIsSemiBright (mobj_t *thing); boolean R_ThingIsFullDark (mobj_t *thing); +boolean R_AffinePreScale (mobj_t *thing); boolean R_ThingIsFlashing(mobj_t *thing); From 00a9e94161c39cc0a83f00eb229d979a8b6e0291 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 17:00:57 -0500 Subject: [PATCH 30/42] Make affines respect SPRTINFO "Things yama forgets about until the last minute" --- src/hardware/hw_main.c | 44 +++++++++++++++++++++++++++++++----- src/r_things.cpp | 51 ++++++++++++++++++++++++++++++++++++++---- src/r_things.h | 8 ++++++- 3 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index bed2b5ddf..1ecc0929a 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4914,6 +4914,8 @@ static void HWR_ProjectSprite(mobj_t *thing) boolean affinesprite = ((thing->player != NULL) || R_ThingIsAffineSprite(thing)); affine_t affine_transform = {0}; vector2_t affine_scale = {0}; + f_vector2_t affine_pivotoffsetdiff = {0}; + polyvertex_t affine_rootpoint = {0}; f_vector2_t affine_point[4] = {0}; float leftbound = 0, backbound = 0, topbound = 0; @@ -5136,6 +5138,18 @@ static void HWR_ProjectSprite(mobj_t *thing) if (affinesprite) { + vector2_t affine_pivot = {0}; + + vector2_t patch_defaultpivot = {.x = spr_offset, .y = (spr_height / 2)}; + + R_GetPivotVectorFromSpriteInfo(&affine_pivot, + &patch_defaultpivot, + sprinfo, + (thing->frame & FF_FRAMEMASK)); + + affine_pivotoffsetdiff.x = FIXED_TO_FLOAT(affine_pivot.x - spr_offset); + affine_pivotoffsetdiff.y = FIXED_TO_FLOAT(affine_pivot.y - spr_topoffset); + affine_scale.x = FLOAT_TO_FIXED(spritexscale * this_scale); affine_scale.y = FLOAT_TO_FIXED(spriteyscale * this_scale); @@ -5147,7 +5161,7 @@ static void HWR_ProjectSprite(mobj_t *thing) const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); - fixed_t y_piv = spr_topoffset; + fixed_t y_piv = affine_pivot.y; if (vflip) { @@ -5173,11 +5187,11 @@ static void HWR_ProjectSprite(mobj_t *thing) affine_transform.d = FixedDiv(ca, affine_scale.y); } - affine_transform.ox = spr_offset - rolloffs_x; + affine_transform.ox = affine_pivot.x - rolloffs_x; affine_transform.oy = y_piv - rolloffs_y; spritexscale = 1.0; - spriteyscale = 1,0; + spriteyscale = 1.0; } if (thing->renderflags & RF_ABSOLUTEOFFSETS) @@ -5328,10 +5342,18 @@ static void HWR_ProjectSprite(mobj_t *thing) // loops take more processing time. If we can help it, it's better to just cut corners. float f_left = 0; float f_bottom = 0; + float f_right = 0; + float f_top = 0; + float f_xlen = 0; + float f_ylen = 0; #define BOUNDCHECK(i) \ { \ f_left = min(affine_point[i].x, f_left); \ f_bottom = max(affine_point[i].y, f_bottom); \ + f_top = min(affine_point[i].y, f_top); \ + f_right = max(affine_point[i].x, f_right); \ + f_xlen = (f_right - f_left); \ + f_ylen = (f_bottom - f_top); \ } BOUNDCHECK(0); @@ -5354,6 +5376,16 @@ static void HWR_ProjectSprite(mobj_t *thing) affine_rootpoint.x = tr_x; affine_rootpoint.z = tr_y; + // Rescale X and Y so we can multiply the pivot offset differences by them. + const float f_affinexscale = f_xlen / (FIXED_TO_FLOAT(spr_width)); + const float f_affineyscale = f_ylen / (FIXED_TO_FLOAT(spr_height)); + + affine_pivotoffsetdiff.x *= f_affinexscale; + affine_pivotoffsetdiff.y *= f_affineyscale; + + affine_rootpoint.x -= (affine_pivotoffsetdiff.x * rightcos); + affine_rootpoint.z -= (affine_pivotoffsetdiff.x * rightsin); + #ifdef ROTSPRITE const float xrescale = this_xscale / FIXED_TO_FLOAT(mapobjectscale); float f_visx = (FIXED_TO_FLOAT(FixedMul(visoffs.x, mapobjectscale))) * xrescale; @@ -5422,15 +5454,15 @@ nodeterminant: affine_rootpoint.y = 0; - const float f_topoffs = FIXED_TO_FLOAT(spr_topoffset); + const float f_topoffs = (FIXED_TO_FLOAT(spr_topoffset)); if (vflip) { - affine_rootpoint.y = FIXED_TO_FLOAT(interp.z + thing->height) - (f_topoffs * this_yscale); + affine_rootpoint.y = (FIXED_TO_FLOAT(interp.z + thing->height) + affine_pivotoffsetdiff.y) - (f_topoffs * this_yscale); } else { - affine_rootpoint.y = FIXED_TO_FLOAT(interp.z) + (f_topoffs * this_yscale); + affine_rootpoint.y = (FIXED_TO_FLOAT(interp.z) - affine_pivotoffsetdiff.y) + (f_topoffs * this_yscale); } if (!affinesprite) diff --git a/src/r_things.cpp b/src/r_things.cpp index 8198ca96a..ee4783b4c 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1971,6 +1971,28 @@ fixed_t R_GetSpriteDirectionalLighting(angle_t angle) return extralight; } +void R_GetPivotVectorFromSpriteInfo(vector2_t* out, + vector2_t* defaultpiv, + spriteinfo_t* sprinfo, + size_t frame) +{ + if (in_bit_array(sprinfo->available, frame)) + { + out->x = (sprinfo->pivot[frame].x * FRACUNIT); + out->y = (sprinfo->pivot[frame].y * FRACUNIT); + } + else if (in_bit_array(sprinfo->available, SPRINFO_DEFAULT_PIVOT)) + { + out->x = (sprinfo->pivot[SPRINFO_DEFAULT_PIVOT].x * FRACUNIT); + out->y = (sprinfo->pivot[SPRINFO_DEFAULT_PIVOT].y * FRACUNIT); + } + else + { + out->x = defaultpiv->x; + out->y = defaultpiv->y; + } +} + // // R_ProjectSprite // Generates a vissprite for a thing @@ -2060,6 +2082,7 @@ static void R_ProjectSprite(mobj_t *thing) affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; vector2_t affine_distscale = {0}; + vector2_t affine_pivotoffsetdiff = {0}; if (R_IsOverlayingSMonitorPlayer(thing)) { @@ -2319,6 +2342,18 @@ static void R_ProjectSprite(mobj_t *thing) if (affinesprite) { + vector2_t affine_pivot = {0}; + + vector2_t patch_defaultpivot = {.x = (patch->leftoffset * FRACUNIT), .y = (patch->height * FRACHALF)}; + + R_GetPivotVectorFromSpriteInfo(&affine_pivot, + &patch_defaultpivot, + sprinfo, + (thing->frame & FF_FRAMEMASK)); + + affine_pivotoffsetdiff.x = affine_pivot.x - (patch->leftoffset * FRACUNIT); + affine_pivotoffsetdiff.y = affine_pivot.y - (patch->topoffset * FRACUNIT); + affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); affine_scale.y = FixedMul(affine_scale.y, FixedMul(spriteyscale, this_scale)); @@ -2330,7 +2365,7 @@ static void R_ProjectSprite(mobj_t *thing) const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); - fixed_t y_piv = ((patch->pivot.y + patch->topoffset) * FRACUNIT); + fixed_t y_piv = affine_pivot.y; if (vflip) { @@ -2356,13 +2391,20 @@ static void R_ProjectSprite(mobj_t *thing) affine_transform.d = FixedDiv(ca, affine_scale.y); } - affine_transform.ox = ((patch->pivot.x + patch->leftoffset) * FRACUNIT) - rolloffs_x; + affine_transform.ox = affine_pivot.x - rolloffs_x; affine_transform.oy = y_piv - rolloffs_y; V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); + // Rescale X and Y so we can multiply the pivot offset differences by them. + const fixed_t _affinexscale = FixedDiv(affine_bounds.xlen * FRACUNIT, patch->width * FRACUNIT); + const fixed_t _affineyscale = FixedDiv(affine_bounds.ylen * FRACUNIT, patch->height * FRACUNIT); + + affine_pivotoffsetdiff.x = FixedMul(affine_pivotoffsetdiff.x, _affinexscale); + affine_pivotoffsetdiff.y = FixedMul(affine_pivotoffsetdiff.y, _affineyscale); + spr_width = (affine_bounds.xlen * FRACUNIT); - spr_offset = (affine_bounds.xleft * FRACUNIT); + spr_offset = (affine_bounds.xleft * FRACUNIT) - affine_pivotoffsetdiff.x; //spr_height = (affine_bounds.ylen * FRACUNIT); //spr_topoffset = (affine_bounds.yup * FRACUNIT); @@ -2713,7 +2755,8 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t rawyscale = FixedDiv(affine_scale.y, FixedMul(this_scale, sortscale)); const float affineyscale = FIXED_TO_FLOAT(affine_scale.y); - const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup) / affineyscale) + thingyoffset; + const float pivydiff = FIXED_TO_FLOAT(affine_pivotoffsetdiff.y) * ((vflip) ? -1.0f : 1.0f); + const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup - pivydiff) / affineyscale) + thingyoffset; const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale)); diff --git a/src/r_things.h b/src/r_things.h index 31924f34a..977c2a211 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -91,12 +91,18 @@ boolean R_ThingVerticallyFlipped (mobj_t *thing); boolean R_ThingIsPaperSprite (mobj_t *thing); boolean R_ThingIsFloorSprite (mobj_t *thing); -boolean R_ThingIsAffineSprite (mobj_t *thing); + boolean R_ThingIsFullBright (mobj_t *thing); boolean R_ThingIsSemiBright (mobj_t *thing); boolean R_ThingIsFullDark (mobj_t *thing); + +boolean R_ThingIsAffineSprite (mobj_t *thing); boolean R_AffinePreScale (mobj_t *thing); +void R_GetPivotVectorFromSpriteInfo(vector2_t* out, + vector2_t* defaultpiv, + spriteinfo_t* sprinfo, + size_t frame); boolean R_ThingIsFlashing(mobj_t *thing); From 354564ee9b7ffc329b4ea0f841d59cf023b25815 Mon Sep 17 00:00:00 2001 From: yamamama Date: Tue, 3 Mar 2026 17:08:28 -0500 Subject: [PATCH 31/42] Missed a spot --- src/deh_tables.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index b8b55e9cb..30dcd4db7 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1061,6 +1061,7 @@ struct int_const_s const INT_CONST[] = { {"RF_GHOSTLYMASK",RF_GHOSTLYMASK}, {"RF2_NOAFFINE",RF2_NOAFFINE}, {"RF2_AFFINEPAPER",RF2_AFFINEPAPER}, + {"RF2_AFFINEPRESCALE",RF2_AFFINEPRESCALE}, // Level flags {"LF_SCRIPTISFILE",LF_SCRIPTISFILE}, From 143af29983442baa7f05cf43a73b491a8409f2d3 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 04:56:25 -0500 Subject: [PATCH 32/42] I forgot: rolling offsets aren't fixedpoint :holdingbugtears: --- src/hardware/hw_main.c | 4 ++-- src/r_things.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 1ecc0929a..ee11ce15d 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5159,8 +5159,8 @@ static void HWR_ProjectSprite(mobj_t *thing) angle = R_ConvToRollAngle(spriterotangle) * flipsign; - const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); - const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset * FRACUNIT, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset * FRACUNIT, highresscale); fixed_t y_piv = affine_pivot.y; if (vflip) diff --git a/src/r_things.cpp b/src/r_things.cpp index ee4783b4c..76d5774bc 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2363,8 +2363,8 @@ static void R_ProjectSprite(mobj_t *thing) angle = R_ConvToRollAngle(spriterotangle) * flipsign; - const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); - const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset * FRACUNIT, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset * FRACUNIT, highresscale); fixed_t y_piv = affine_pivot.y; if (vflip) From 3d229f37688db355ad77fdc94444219539dc4ac1 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 12:32:21 -0500 Subject: [PATCH 33/42] Fix affine sprites "dissolving" in Software --- src/r_things.cpp | 2 +- src/v_video.c | 15 +++++++++++---- src/v_video.h | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index 76d5774bc..5939b64b7 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2394,7 +2394,7 @@ static void R_ProjectSprite(mobj_t *thing) affine_transform.ox = affine_pivot.x - rolloffs_x; affine_transform.oy = y_piv - rolloffs_y; - V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds); + V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds, true); // Rescale X and Y so we can multiply the pivot offset differences by them. const fixed_t _affinexscale = FixedDiv(affine_bounds.xlen * FRACUNIT, patch->width * FRACUNIT); diff --git a/src/v_video.c b/src/v_video.c index 48bb5c734..cc4f93ae2 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -813,7 +813,8 @@ static int sortcoords(const void *a, const void *b) affine_bounding_t* V_GetAffineBounds(const affine_t* transform, patch_t* patch, fixed_t divisor, - affine_bounding_t* out) + affine_bounding_t* out, + boolean unrestrict_bound) { // A decent chunk of this code is just fixedpointizing stuff // from HWR_GetAffinePatch; That system's near flawless, why fix what isn't broken? @@ -877,8 +878,14 @@ affine_bounding_t* V_GetAffineBounds(const affine_t* transform, }; // Get the leftmost and uppermost bounds of the current resolution. - const INT32 vw = vid.width, vh = vid.height; - const INT32 leftmost = ((BASEVIDWIDTH - vw) / 2), uppermost = ((BASEVIDHEIGHT - vh) / 2); + INT32 vw = vid.width, vh = vid.height; + INT32 leftmost = ((BASEVIDWIDTH - vw) / 2), uppermost = ((BASEVIDHEIGHT - vh) / 2); + + if (unrestrict_bound) + { + vw = vh = INT32_MAX; + leftmost = uppermost = INT32_MIN; + } // ...okay, now comb through all four vertices and set the output bounds based on this. // "Why not a loop?" According to SM64 programming wizard Kaze Emanuar, @@ -960,7 +967,7 @@ void V_DrawAffinePatch(fixed_t x, fixed_t y, const affine_t *transform, INT32 sc #endif affine_bounding_t bounds = {0}; - V_GetAffineBounds(transform, patch, vid.dup * FRACUNIT, &bounds); + V_GetAffineBounds(transform, patch, vid.dup * FRACUNIT, &bounds, false); Patch_GenerateFlat(patch, 0); const UINT16 *src = patch->flats[0]; diff --git a/src/v_video.h b/src/v_video.h index 264ff55b1..b4d8cf3a4 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -221,7 +221,8 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_ affine_bounding_t* V_GetAffineBounds(const affine_t* transform, patch_t* patch, fixed_t divisor, - affine_bounding_t* out); + affine_bounding_t* out, + boolean unrestrict_bound); 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); From 1af1a7290d5c168d20e8c21e6ae405950e202a75 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 12:50:46 -0500 Subject: [PATCH 34/42] Lore-accurate affine columdrawer --- src/r_draw_column.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 61b9f7615..a5d27d412 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -110,12 +110,20 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnAffinePixel(drawcolumnd { const UINT16 pixel = reinterpret_cast(dc->source)[bit]; - if (pixel < 0xff00) + UINT8 col = (UINT8)(pixel & 0xff); + + if constexpr (Type & DrawColumnType::DC_HOLES) { - return TRANSPARENTPIXEL; + if (col == TRANSPARENTPIXEL) + { + return *dest; + } } - UINT8 col = (UINT8)(pixel & 0xff); + if (pixel < 0xff00) + { + return *dest; + } if constexpr (Type & DrawColumnType::DC_COLORMAP) { @@ -643,10 +651,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) { continue; } - const UINT8 pixel = R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); - if (pixel == TRANSPARENTPIXEL) - continue; + const UINT8 pixel = R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); *dest = pixel; } From 066aa94ef404f881b0ddd5d284356d6da8322c04 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 20:48:04 -0500 Subject: [PATCH 35/42] R_DrawColumnAffinePixel *actually* draws the pixels now --- src/r_draw_column.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index a5d27d412..1f5eb2014 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -101,30 +101,30 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnPixel(drawcolumndata_t* return R_GetColumnTranslucent(dc, dest, bit, col); } -// Function flow: Translation -> Brightmap -> Colormap -> Translucency +// Function flow: Holes -> Translation -> Brightmap -> Colormap -> Translucency // "Why are these in nested functions for standard columns?" Uhhhhhh iunno lol // I make-a da code-a template -FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnAffinePixel(drawcolumndata_t* dc, UINT8 *dest, INT32 bit) +FUNCINLINE static ATTRINLINE constexpr UINT16 R_DrawColumnAffinePixel(drawcolumndata_t* dc, UINT8 *dest, INT32 bit) { const UINT16 pixel = reinterpret_cast(dc->source)[bit]; UINT8 col = (UINT8)(pixel & 0xff); + if (pixel < 0xff00) + { + return 0; + } + if constexpr (Type & DrawColumnType::DC_HOLES) { if (col == TRANSPARENTPIXEL) { - return *dest; + return 0; } } - if (pixel < 0xff00) - { - return *dest; - } - if constexpr (Type & DrawColumnType::DC_COLORMAP) { // Remap to the current translation @@ -154,7 +154,9 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnAffinePixel(drawcolumnd col = *(dc->transmap + (col << 8) + (*dest)); } - return col; + *dest = col; + + return (0xff00 | col); } /** \brief The R_DrawColumn function @@ -652,9 +654,7 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) continue; } - const UINT8 pixel = R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); - - *dest = pixel; + R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); } } } From 7ef15619d6a0f64506bd408718272a5a0b79e5d3 Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 20:48:36 -0500 Subject: [PATCH 36/42] Kill AFFINEPAPER and move the affine RF2s to standard renderflags --- src/deh_tables.c | 5 ++--- src/lua_mobjlib.c | 8 ++++++++ src/p_mobj.h | 10 ++++++++-- src/p_saveg.c | 4 +++- src/r_defs.h | 8 ++++++-- src/r_things.cpp | 14 +++++--------- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 30dcd4db7..b4abaf850 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1034,6 +1034,7 @@ struct int_const_s const INT_CONST[] = { #else {"RF_NOMODEL",0}, #endif + {"RF_NOAFFINE",RF_NOAFFINE}, {"RF_DONTDRAW",RF_DONTDRAW}, {"RF_DONTDRAWP1",RF_DONTDRAWP1}, {"RF_DONTDRAWP2",RF_DONTDRAWP2}, @@ -1046,6 +1047,7 @@ struct int_const_s const INT_CONST[] = { {"RF_REVERSESUBTRACT",RF_REVERSESUBTRACT}, {"RF_MODULATE",RF_MODULATE}, {"RF_OVERLAY",RF_OVERLAY}, + {"RF_AFFINEPRESCALE",RF_AFFINEPRESCALE}, {"RF_TRANSMASK",RF_TRANSMASK}, {"RF_TRANSSHIFT",RF_TRANSSHIFT}, {"RF_TRANS10",RF_TRANS10}, @@ -1059,9 +1061,6 @@ struct int_const_s const INT_CONST[] = { {"RF_TRANS90",RF_TRANS90}, {"RF_GHOSTLY",RF_GHOSTLY}, {"RF_GHOSTLYMASK",RF_GHOSTLYMASK}, - {"RF2_NOAFFINE",RF2_NOAFFINE}, - {"RF2_AFFINEPAPER",RF2_AFFINEPAPER}, - {"RF2_AFFINEPRESCALE",RF2_AFFINEPRESCALE}, // Level flags {"LF_SCRIPTISFILE",LF_SCRIPTISFILE}, diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index f8521fbe7..87b65fca8 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -62,7 +62,9 @@ enum mobj_e { mobj_flags2, mobj_eflags, mobj_renderflags, +#if 0 mobj_renderflags2, +#endif mobj_skin, mobj_voice, mobj_color, @@ -161,7 +163,9 @@ static const char *const mobj_opt[] = { "flags2", "eflags", "renderflags", +#if 0 "renderflags2", +#endif "skin", "voice", "color", @@ -431,9 +435,11 @@ static int mobj_get(lua_State *L) case mobj_renderflags: lua_pushinteger(L, mo->renderflags); break; +#if 0 case mobj_renderflags2: lua_pushinteger(L, mo->renderflags2); break; +#endif case mobj_skin: // skin name or nil, not struct if (!mo->skin) return 0; @@ -906,9 +912,11 @@ static int mobj_set(lua_State *L) case mobj_renderflags: mo->renderflags = (UINT32)luaL_checkinteger(L, 3); break; +#if 0 case mobj_renderflags2: mo->renderflags2 = (UINT32)luaL_checkinteger(L, 3); break; +#endif case mobj_skin: // set skin by name { INT32 i; diff --git a/src/p_mobj.h b/src/p_mobj.h index 004508530..bc8b55854 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -305,7 +305,10 @@ struct mobj_t UINT8 sprite2; // player sprites UINT16 anim_duration; // for FF_ANIMATE states - UINT32 renderflags, renderflags2; // render flags + UINT32 renderflags; // render flags +#if 0 + UINT32 renderflags2; // More render flags +#endif fixed_t spritexscale, spriteyscale; fixed_t spritexoffset, spriteyoffset; fixed_t old_spritexscale, old_spriteyscale; @@ -494,7 +497,10 @@ struct precipmobj_t UINT8 sprite2; // player sprites UINT16 anim_duration; // for FF_ANIMATE states - UINT32 renderflags, renderflags2; // render flags + UINT32 renderflags; // render flags +#if 0 + UINT32 renderflags2; // More render flags +#endif fixed_t spritexscale, spriteyscale; fixed_t spritexoffset, spriteyoffset; fixed_t old_spritexscale, old_spriteyscale; diff --git a/src/p_saveg.c b/src/p_saveg.c index 235e68dc1..c65649570 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2046,7 +2046,7 @@ static void DiffMobj(const mobj_t *mobj, UINT32 diff[]) DIFF(mobj->mirrored, MD2_MIRRORED); DIFF(mobj->rollangle, MD2_ROLLANGLE); DIFF(mobj->shadowscale, MD2_SHADOWSCALE); - DIFF(mobj->renderflags || mobj->renderflags2, MD2_RENDERFLAGS); + DIFF(mobj->renderflags /*|| mobj->renderflags2*/, MD2_RENDERFLAGS); DIFF(mobj->tid != 0, MD2_TID); DIFF(mobj->spritexscale != FRACUNIT || mobj->spriteyscale != FRACUNIT, MD2_SPRITESCALE); DIFF(mobj->spritexoffset || mobj->spriteyoffset || mobj->rollingxoffset || mobj->rollingyoffset, MD2_SPRITEOFFSET); @@ -2324,7 +2324,9 @@ static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinke mobj->renderflags = READUINT32(save->p); } } +#if 0 SYNCF(MD2_RENDERFLAGS, mobj->renderflags2); +#endif SYNCF(MD2_TID, mobj->tid); SYNCDEF(MD2_SPRITESCALE, mobj->spritexscale, FRACUNIT); SYNCDEF(MD2_SPRITESCALE, mobj->spriteyscale, FRACUNIT); diff --git a/src/r_defs.h b/src/r_defs.h index 74e95cf2a..7856a1473 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1004,6 +1004,7 @@ typedef enum #ifdef HWRENDER RF_NOMODEL = 0x00040000, // do not draw a model for this mobj in opengl, use its sprite instead #endif + RF_NOAFFINE = 0x00080000, // Disables affine drawing for this sprite RF_HIDESHIFT = (20), RF_DONTDRAW = 0x0F << RF_HIDESHIFT, // --Don't generate a vissprite @@ -1021,6 +1022,8 @@ typedef enum RF_MODULATE = ((AST_MODULATE-1)<frame & FF_AFFINEPAPER || thing->renderflags2 & RF2_AFFINEPAPER); - - return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE || affinepaper); + return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE); } boolean R_ThingIsAffineSprite(mobj_t *thing) { - boolean affinepaper = (thing->frame & FF_AFFINEPAPER || thing->renderflags2 & RF2_AFFINEPAPER); - - // Affine papersprites are messy in software rendering; let modders enable them at their own discretion. + // Affine papersprites are messy in software rendering. // Yes, I'm lazy; I've been at this for TWO WEEKS. - boolean papersprite = (R_ThingIsPaperSprite(thing) && !affinepaper); + boolean papersprite = (R_ThingIsPaperSprite(thing)); - boolean notaffine = ((thing->frame & FF_NOAFFINE || thing->renderflags2 & RF2_NOAFFINE) || papersprite); + boolean notaffine = ((thing->frame & FF_NOAFFINE || thing->renderflags & RF_NOAFFINE) || papersprite); return (!notaffine); } @@ -4398,7 +4394,7 @@ boolean R_ThingIsFullDark(mobj_t *thing) boolean R_AffinePreScale(mobj_t *thing) { - return (cv_affineprescale.value || thing->renderflags2 & RF2_AFFINEPRESCALE); + return (cv_affineprescale.value || thing->renderflags & RF_AFFINEPRESCALE); } // From cb578f423d3351ebd7f2b249784580dacc0be28c Mon Sep 17 00:00:00 2001 From: yamamama Date: Wed, 4 Mar 2026 21:33:00 -0500 Subject: [PATCH 37/42] Remove extraneous dc_copy checks, fix rolling offsets --- src/r_things.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index 402b57bce..db92a857f 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1264,7 +1264,6 @@ static void R_DrawVisSprite(vissprite_t *vis) if (dc.yh >= baseclip && baseclip != -1) dc.yh = baseclip; - drawcolumndata_t dc_copy = dc; coldrawfunc_t* colfunccopy = colfunc; colfunccopy(&dc); } @@ -1315,7 +1314,6 @@ static void R_DrawVisSprite(vissprite_t *vis) { dc.frac = frac; - drawcolumndata_t dc_copy = dc; coldrawfunc_t* colfunccopy = colfunc; colfunccopy(&dc); } @@ -2363,8 +2361,9 @@ static void R_ProjectSprite(mobj_t *thing) angle = R_ConvToRollAngle(spriterotangle) * flipsign; - const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset * FRACUNIT, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); - const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset * FRACUNIT, highresscale); + const boolean renderflip = ((thing->renderflags & RF_FLIPOFFSETS) == RF_FLIPOFFSETS); + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset * FRACUNIT, highresscale) * (((!renderflip) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset * FRACUNIT, highresscale) * (((!renderflip) && vflip) ? -1 : 1); fixed_t y_piv = affine_pivot.y; if (vflip) From 951c43d1e8fe0340337f154209845d5afcb72dcb Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 5 Mar 2026 00:39:19 -0500 Subject: [PATCH 38/42] Add an option for mosaic affine rendering Currently software only; would require a shader or more "involved" solution for OpenGL --- src/hardware/hw_glob.h | 6 ------ src/m_fixed.h | 7 +++++++ src/r_defs.h | 1 + src/r_draw_column.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/r_main.cpp | 2 ++ src/r_main.h | 2 +- src/r_things.cpp | 10 ++++++++++ src/r_things.h | 1 + src/typedef.h | 1 + 9 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index a1d668f80..39d1af744 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -43,12 +43,6 @@ typedef struct float z; } polyvertex_t; -typedef struct -{ - float x; - float y; -} f_vector2_t; - // a convex 'plane' polygon, clockwise order typedef struct { diff --git a/src/m_fixed.h b/src/m_fixed.h index 74b2f29b6..d97f3747f 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -351,6 +351,13 @@ struct vector2_t fixed_t y; }; +// Floating-point version, used sparingly, primarily in rendering +struct f_vector2_t +{ + float x; + float y; +}; + vector2_t *FV2_Load(vector2_t *vec, fixed_t x, fixed_t y); vector2_t *FV2_UnLoad(vector2_t *vec, fixed_t *x, fixed_t *y); vector2_t *FV2_Copy(vector2_t *a_o, const vector2_t *a_i); diff --git a/src/r_defs.h b/src/r_defs.h index 7856a1473..e5bfd2771 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1168,6 +1168,7 @@ typedef struct affine_t affine; vector2_t affineoffset; fixed_t affineystep; + f_vector2_t affinemosaic; // Truncates how columndrawers "move" across the screen fixed_t frac; } drawcolumndata_t; diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 1f5eb2014..ca90caf41 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -635,19 +635,50 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) //I_OutputMsg("xdiff: %f, ydiff: %f\n", FIXED_TO_FLOAT(xdiff), FIXED_TO_FLOAT(ydiff)); + // If we're smaller than usual, mosaic isn't necessary. + + const float y_mosaic = std::max(1.0f, dc->affinemosaic.y); + const float x_mosaic = std::max(1.0f, dc->affinemosaic.x); + + const boolean mosaic_on = cv_affinemosaic.value; + const boolean xmosaic_on = (FLOAT_TO_FIXED(x_mosaic) > FRACUNIT); + const boolean ymosaic_on = (FLOAT_TO_FIXED(y_mosaic) > FRACUNIT); + + fixed_t usefrac = dc->frac; + + if (mosaic_on && xmosaic_on) + { + usefrac = FLOAT_TO_FIXED(static_cast(FIXED_TO_FLOAT(dc->frac) / x_mosaic) * x_mosaic); + } + + float ypx = 0.0f; + // yoinked from NovaSquirrel's mode 7 0preview // ...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(b, -cyy) + FixedMul(a, -cxx) + FixedMul(a, dc->frac) + cx; - fixed_t uy = FixedMul(d, -cyy) + FixedMul(c, -cxx) + FixedMul(c, dc->frac) + cy; + fixed_t ux = FixedMul(b, -cyy) + FixedMul(a, -cxx) + FixedMul(a, usefrac) + cx; + fixed_t uy = FixedMul(d, -cyy) + FixedMul(c, -cxx) + FixedMul(c, usefrac) + cy; + float ux_base = FIXED_TO_FLOAT(ux); + float uy_base = FIXED_TO_FLOAT(uy); + float f_b = FIXED_TO_FLOAT(FixedMul(b, ystep_delta)); + float f_d = FIXED_TO_FLOAT(FixedMul(d, ystep_delta)); for (; count > 0; dest += stride, --count) { + ypx += 1.0f; + const INT32 srcx = ux >> FRACBITS; const INT32 srcy = (vflip) ? (ph - (uy >> FRACBITS)) : (uy >> FRACBITS); - ux += FixedMul(b, ystep_delta); - uy += FixedMul(d, ystep_delta); + INT32 y_real = static_cast(ypx); + + if (mosaic_on && ymosaic_on) + { + y_real = (static_cast(ypx / y_mosaic) * y_mosaic); + } + + ux = FLOAT_TO_FIXED(ux_base + (f_b * y_real)); + uy = FLOAT_TO_FIXED(uy_base + (f_d * y_real)); if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) { diff --git a/src/r_main.cpp b/src/r_main.cpp index be7a995ed..46a367007 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -200,6 +200,7 @@ consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff consvar_t cv_fakerollangle = CVAR_INIT ("fakerollangle", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_affineprescale = CVAR_INIT ("affineprescale", "Off", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_affinemosaic = CVAR_INIT ("affinemosaic", "Off", CV_SAVE, CV_OnOff, NULL); static CV_PossibleValue_t affineangle_cons_t[] = {{0, "MIN"}, {360 * FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t affinetest_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Auto"}, {0, NULL}}; @@ -1813,6 +1814,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_nulldrifttilt); CV_RegisterVar(&cv_fakerollangle); CV_RegisterVar(&cv_affineprescale); + CV_RegisterVar(&cv_affinemosaic); CV_RegisterVar(&cv_showhud); CV_RegisterVar(&cv_translucenthud); diff --git a/src/r_main.h b/src/r_main.h index b1f066705..26bcecf97 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -162,7 +162,7 @@ extern consvar_t cv_sliptidetilt; extern consvar_t cv_nulldriftefx, cv_nulldrifttilt; extern consvar_t cv_fakerollangle; -extern consvar_t cv_affineprescale; +extern consvar_t cv_affineprescale, cv_affinemosaic; // debugging diff --git a/src/r_things.cpp b/src/r_things.cpp index db92a857f..fcf796998 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1182,6 +1182,9 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.affineoffset.x = vis->affine.offset.x; dc.affineoffset.y = vis->affine.offset.y; + dc.affinemosaic.x = vis->affine.mosaic.x; + dc.affinemosaic.y = vis->affine.mosaic.y; + R_CopyAffineBounds(&vis->affine.bounds, &dc.affinebound); fixed_t fixed_ylen = (FRACUNIT*dc.affinebound.ylen); @@ -2081,6 +2084,7 @@ static void R_ProjectSprite(mobj_t *thing) vector2_t affine_scale = {0}; vector2_t affine_distscale = {0}; vector2_t affine_pivotoffsetdiff = {0}; + f_vector2_t affine_mosaic = {.x = 1.0f, .y = 1.0f}; if (R_IsOverlayingSMonitorPlayer(thing)) { @@ -2355,6 +2359,9 @@ static void R_ProjectSprite(mobj_t *thing) affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); affine_scale.y = FixedMul(affine_scale.y, FixedMul(spriteyscale, this_scale)); + affine_mosaic.x = FIXED_TO_FLOAT(FixedMul(affine_distscale.x, this_scale)); + affine_mosaic.y = FIXED_TO_FLOAT(FixedMul(affine_distscale.y, this_scale)); + angle_t angle; INT32 flipsign = ((flip) ? -1 : 1); @@ -2940,6 +2947,9 @@ static void R_ProjectSprite(mobj_t *thing) vis->affine.transform.ox = affine_transform.ox; vis->affine.transform.oy = affine_transform.oy; + vis->affine.mosaic.x = affine_mosaic.x; + vis->affine.mosaic.y = affine_mosaic.y; + R_CopyAffineBounds(&affine_bounds, &vis->affine.bounds); } diff --git a/src/r_things.h b/src/r_things.h index 977c2a211..2914058c1 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -220,6 +220,7 @@ struct vissprite_t vector2_t scaling; // Affine scaling vector2_t distscale; // X/Y scale based on camera distance vector2_t offset; // Per-pixel offset + f_vector2_t mosaic; // Truncates how columndrawers "move" across the screen angle_t rollangle; // Affine rotation angle affine_t transform; // The actual affine transformation. affine_bounding_t bounds; // The "bounding box" (draw area) of the affine sprite. diff --git a/src/typedef.h b/src/typedef.h index c7590d27d..c5f78200a 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -232,6 +232,7 @@ TYPEDEF (mdllistitem_t); // m_fixed.h TYPEDEF (vector2_t); +TYPEDEF (f_vector2_t); TYPEDEF (vector3_t); TYPEDEF (vector4_t); TYPEDEF (matrix_t); From a23fb11a4525dfc6352d0850a6ab0415c5489050 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 5 Mar 2026 06:50:48 -0500 Subject: [PATCH 39/42] Remove old debug print --- src/r_draw_column.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index ca90caf41..3975436af 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -633,8 +633,6 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) fixed_t cxx = cx + xdiff + dc->affineoffset.x; fixed_t cyy = cy + ydiff + dc->affineoffset.y; - //I_OutputMsg("xdiff: %f, ydiff: %f\n", FIXED_TO_FLOAT(xdiff), FIXED_TO_FLOAT(ydiff)); - // If we're smaller than usual, mosaic isn't necessary. const float y_mosaic = std::max(1.0f, dc->affinemosaic.y); From 5a648dd91abfaf4959614cf5ea0632c9445a2716 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 5 Mar 2026 13:24:17 -0500 Subject: [PATCH 40/42] Make Software use OpenGL's pivot-differencing Software is a renderer I fully believe was crafted by the deranged --- src/r_things.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/r_things.cpp b/src/r_things.cpp index fcf796998..f9d93da36 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -2083,7 +2083,7 @@ static void R_ProjectSprite(mobj_t *thing) affine_bounding_t affine_bounds = {0}; vector2_t affine_scale = {0}; vector2_t affine_distscale = {0}; - vector2_t affine_pivotoffsetdiff = {0}; + f_vector2_t affine_pivotoffsetdiff = {0}; f_vector2_t affine_mosaic = {.x = 1.0f, .y = 1.0f}; if (R_IsOverlayingSMonitorPlayer(thing)) @@ -2346,15 +2346,15 @@ static void R_ProjectSprite(mobj_t *thing) { vector2_t affine_pivot = {0}; - vector2_t patch_defaultpivot = {.x = (patch->leftoffset * FRACUNIT), .y = (patch->height * FRACHALF)}; + vector2_t patch_defaultpivot = {.x = spr_offset, .y = (spr_height / 2)}; R_GetPivotVectorFromSpriteInfo(&affine_pivot, &patch_defaultpivot, sprinfo, (thing->frame & FF_FRAMEMASK)); - affine_pivotoffsetdiff.x = affine_pivot.x - (patch->leftoffset * FRACUNIT); - affine_pivotoffsetdiff.y = affine_pivot.y - (patch->topoffset * FRACUNIT); + affine_pivotoffsetdiff.x = FIXED_TO_FLOAT(affine_pivot.x - spr_offset); + affine_pivotoffsetdiff.y = FIXED_TO_FLOAT(affine_pivot.y - spr_topoffset); affine_scale.x = FixedMul(affine_scale.x, FixedMul(spritexscale, this_scale)); affine_scale.y = FixedMul(affine_scale.y, FixedMul(spriteyscale, this_scale)); @@ -2403,14 +2403,14 @@ static void R_ProjectSprite(mobj_t *thing) V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds, true); // Rescale X and Y so we can multiply the pivot offset differences by them. - const fixed_t _affinexscale = FixedDiv(affine_bounds.xlen * FRACUNIT, patch->width * FRACUNIT); - const fixed_t _affineyscale = FixedDiv(affine_bounds.ylen * FRACUNIT, patch->height * FRACUNIT); + const float f_affinexscale = static_cast(affine_bounds.xlen) / static_cast(patch->width); + const float f_affineyscale = static_cast(affine_bounds.ylen) / static_cast(patch->height); - affine_pivotoffsetdiff.x = FixedMul(affine_pivotoffsetdiff.x, _affinexscale); - affine_pivotoffsetdiff.y = FixedMul(affine_pivotoffsetdiff.y, _affineyscale); + affine_pivotoffsetdiff.x *= f_affinexscale; + affine_pivotoffsetdiff.y *= f_affineyscale; spr_width = (affine_bounds.xlen * FRACUNIT); - spr_offset = (affine_bounds.xleft * FRACUNIT) - affine_pivotoffsetdiff.x; + spr_offset = (affine_bounds.xleft * FRACUNIT) - FLOAT_TO_FIXED(affine_pivotoffsetdiff.x); //spr_height = (affine_bounds.ylen * FRACUNIT); //spr_topoffset = (affine_bounds.yup * FRACUNIT); @@ -2761,8 +2761,8 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t rawyscale = FixedDiv(affine_scale.y, FixedMul(this_scale, sortscale)); const float affineyscale = FIXED_TO_FLOAT(affine_scale.y); - const float pivydiff = FIXED_TO_FLOAT(affine_pivotoffsetdiff.y) * ((vflip) ? -1.0f : 1.0f); - const fixed_t real_topoffset = FLOAT_TO_FIXED(static_cast(affine_bounds.yup - pivydiff) / affineyscale) + thingyoffset; + const float pivydiff = affine_pivotoffsetdiff.y * ((vflip) ? -1.0f : 1.0f); + const fixed_t real_topoffset = FLOAT_TO_FIXED((static_cast(affine_bounds.yup) - pivydiff) / affineyscale) + thingyoffset; const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale)); From 0ebe6f2f4dd42968915512263a7b7f09b2973ff8 Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 5 Mar 2026 14:29:07 -0500 Subject: [PATCH 41/42] Affine brightmaps --- src/r_draw.h | 5 +++++ src/r_draw_column.cpp | 19 ++++++++++++++----- src/r_things.cpp | 17 +++++++++++++---- src/screen.c | 8 ++++---- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/r_draw.h b/src/r_draw.h index 2533ff7df..b5b10a75c 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -210,6 +210,11 @@ void R_DrawTranslatedAffineColumn(drawcolumndata_t* dc); void R_DrawTranslucentAffineColumn(drawcolumndata_t* dc); void R_DrawTranslatedTranslucentAffineColumn(drawcolumndata_t* dc); +void R_DrawAffineColumn_Brightmap(drawcolumndata_t* dc); +void R_DrawTranslatedAffineColumn_Brightmap(drawcolumndata_t* dc); +void R_DrawTranslucentAffineColumn_Brightmap(drawcolumndata_t* dc); +void R_DrawTranslatedTranslucentAffineColumn_Brightmap(drawcolumndata_t* dc); + void R_DrawColumn_Brightmap(drawcolumndata_t* dc); void R_DrawTranslucentColumn_Brightmap(drawcolumndata_t* dc); void R_DrawTranslatedColumn_Brightmap(drawcolumndata_t* dc); diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 3975436af..65ba577bf 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -135,11 +135,19 @@ FUNCINLINE static ATTRINLINE constexpr UINT16 R_DrawColumnAffinePixel(drawcolumn if constexpr (Type & DrawColumnType::DC_BRIGHTMAP) { - if (dc->brightmap[bit] == BRIGHTPIXEL) + // For affine drawers, dc->brightmap points to a *flat*, and not a *column*. + // Keep that in mind before you do something that causes a bunch of segfaults! + + if (dc->brightmap) { - // Pixel is part of the brightmap - col = dc->fullbright[col]; - was_brightmapped = true; + const UINT16 bmpixel = reinterpret_cast(dc->brightmap)[bit]; + + if ((bmpixel & 0xff) == BRIGHTPIXEL) + { + // Pixel is part of the brightmap + col = dc->fullbright[col]; + was_brightmapped = true; + } } } @@ -703,7 +711,8 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) // Replace with DEFINE_AFFINE_COLUMN_COMBO down the line #define DEFINE_AFFINE_COLUMN_SETUP(name, flags) \ - DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) + DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) \ + DEFINE_AFFINE_COLUMN_FUNC(name ## _Brightmap, flags|DC_DIRECT|DC_BRIGHTMAP) DEFINE_AFFINE_COLUMN_SETUP(R_DrawAffineColumn, DC_BASIC); DEFINE_AFFINE_COLUMN_SETUP(R_DrawTranslatedAffineColumn, DC_COLORMAP); diff --git a/src/r_things.cpp b/src/r_things.cpp index f9d93da36..ab59b994f 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1041,27 +1041,28 @@ static void R_DrawVisSprite(vissprite_t *vis) if (vis->cut & SC_AFFINE) { // Specialized affine drawing functions, primarily for player sprites. + boolean affinebrightmap = (bmpatch != NULL); if (dc.translation) // translate green skin to another color { if (vis->transmap) { - R_SetColumnFunc(COLDRAWFUNC_AFFINETRANSTRANS, false); + R_SetColumnFunc(COLDRAWFUNC_AFFINETRANSTRANS, affinebrightmap); dc.transmap = vis->transmap; } else { - R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, false); + R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, affinebrightmap); } } else if (vis->transmap) { - R_SetColumnFunc(COLDRAWFUNC_AFFINEFUZZY, false); + R_SetColumnFunc(COLDRAWFUNC_AFFINEFUZZY, affinebrightmap); dc.transmap = vis->transmap; } else { - R_SetColumnFunc(COLDRAWFUNC_AFFINE, false); + R_SetColumnFunc(COLDRAWFUNC_AFFINE, affinebrightmap); } } // Hack: Use a special column function for drop shadows that bypasses @@ -1172,6 +1173,14 @@ static void R_DrawVisSprite(vissprite_t *vis) dc.source = static_cast(patch->flats[0]); dc.sourcelength = patch->width; + if (bmpatch) + { + Patch_GenerateFlat(bmpatch, static_cast(0)); + + // The column is a flat because fuck you + dc.brightmap = static_cast(bmpatch->flats[0]); + } + dc.affine.a = vis->affine.transform.a; dc.affine.b = vis->affine.transform.b; dc.affine.c = vis->affine.transform.c; diff --git a/src/screen.c b/src/screen.c index 158b4313e..bead6e2f8 100644 --- a/src/screen.c +++ b/src/screen.c @@ -153,10 +153,10 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext) // Affine brightmap functions; needed but I don't care at the moment - colfuncs_bm[COLDRAWFUNC_AFFINE] = NULL; - colfuncs_bm[COLDRAWFUNC_AFFINETRANS] = NULL; - colfuncs_bm[COLDRAWFUNC_AFFINEFUZZY] = NULL; - colfuncs_bm[COLDRAWFUNC_AFFINETRANSTRANS] = NULL; + colfuncs_bm[COLDRAWFUNC_AFFINE] = R_DrawAffineColumn_Brightmap; + colfuncs_bm[COLDRAWFUNC_AFFINETRANS] = R_DrawTranslatedAffineColumn_Brightmap; + colfuncs_bm[COLDRAWFUNC_AFFINEFUZZY] = R_DrawTranslucentAffineColumn_Brightmap; + colfuncs_bm[COLDRAWFUNC_AFFINETRANSTRANS] = R_DrawTranslatedTranslucentAffineColumn_Brightmap; spanfuncs[BASEDRAWFUNC] = R_DrawSpan; spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan; From ea505f942872e25e30ea1721549b6fee065e7ace Mon Sep 17 00:00:00 2001 From: yamamama Date: Thu, 5 Mar 2026 23:47:28 -0500 Subject: [PATCH 42/42] Some quick cleanup --- src/r_draw_column.cpp | 6 ------ src/r_things.cpp | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 65ba577bf..5aa3b9195 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -704,12 +704,6 @@ static void R_DrawAffineColumnTemplate(drawcolumndata_t *dc) R_DrawAffineColumnTemplate(dc); \ } -#define DEFINE_AFFINE_COLUMN_COMBO(name, flags) \ - DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) \ - DEFINE_AFFINE_COLUMN_FUNC(name ## _Brightmap, flags|DC_DIRECT|DC_BRIGHTMAP) \ - DEFINE_AFFINE_COLUMN_FUNC(name ## _Flush, flags) - -// Replace with DEFINE_AFFINE_COLUMN_COMBO down the line #define DEFINE_AFFINE_COLUMN_SETUP(name, flags) \ DEFINE_AFFINE_COLUMN_FUNC(name, flags|DC_DIRECT) \ DEFINE_AFFINE_COLUMN_FUNC(name ## _Brightmap, flags|DC_DIRECT|DC_BRIGHTMAP) diff --git a/src/r_things.cpp b/src/r_things.cpp index ab59b994f..f6c06d746 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1538,14 +1538,6 @@ static void R_SplitSprite(vissprite_t *sprite) newsprite->gzt = sprite->gz; - /*if (sprite->cut & SC_AFFINE) - { - // Affines: Fix splitting alignment errors by applying a pixel offset - fixed_t szt_diff = (newsprite->sz - cutfrac + 9) << FRACBITS; - newsprite->affine.offset.y = std::min(0, -((sprite->affine.bounds.ylen * FRACUNIT) - szt_diff)); - }*/ - - sprite->sz = cutfrac; newsprite->szt = (INT16)(sprite->sz - 1); @@ -2421,9 +2413,6 @@ static void R_ProjectSprite(mobj_t *thing) spr_width = (affine_bounds.xlen * FRACUNIT); spr_offset = (affine_bounds.xleft * FRACUNIT) - FLOAT_TO_FIXED(affine_pivotoffsetdiff.x); - //spr_height = (affine_bounds.ylen * FRACUNIT); - //spr_topoffset = (affine_bounds.yup * FRACUNIT); - spritexscale = FRACUNIT; spriteyscale = FRACUNIT; }