Merge pull request '[FEAT] Affine sprite rendering' (#223) from softwarehell into next

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/223
This commit is contained in:
yamamama 2026-03-06 06:41:00 +01:00
commit 6e99c9b5cd
23 changed files with 1495 additions and 129 deletions

View file

@ -948,6 +948,8 @@ struct int_const_s const INT_CONST[] = {
{"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP},
{"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},
@ -1032,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},
@ -1044,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},

View file

@ -84,6 +84,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

View file

@ -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,16 @@ 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};
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;
// uncapped/interpolation
interpmobjstate_t interp = {0};
mobj_t *interptarg;
@ -4991,11 +5088,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);
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));
@ -5027,6 +5127,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
R_RotateSpriteOffsetsByPitchRoll(interptarg,
vflip,
hflip,
false,
&interp,
&visoffs,
&rotoffset);
@ -5035,6 +5136,64 @@ static void HWR_ProjectSprite(mobj_t *thing)
}
#endif
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);
angle_t angle;
INT32 flipsign = ((flip && papersprite) ? -1 : 1); // Flip OGL affine papersprites for Software parity
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);
fixed_t y_piv = affine_pivot.y;
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);
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 = affine_pivot.x - 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);
@ -5114,23 +5273,168 @@ 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;
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);
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;
// 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
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
/*
@ -5144,23 +5448,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) + affine_pivotoffsetdiff.y) - (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) - affine_pivotoffsetdiff.y) + (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);
}
}
}
@ -5226,10 +5553,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;
@ -5302,9 +5669,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]);

View file

@ -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;

View file

@ -62,6 +62,9 @@ enum mobj_e {
mobj_flags2,
mobj_eflags,
mobj_renderflags,
#if 0
mobj_renderflags2,
#endif
mobj_skin,
mobj_voice,
mobj_color,
@ -160,6 +163,9 @@ static const char *const mobj_opt[] = {
"flags2",
"eflags",
"renderflags",
#if 0
"renderflags2",
#endif
"skin",
"voice",
"color",
@ -429,6 +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;
@ -901,6 +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;

View file

@ -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);

View file

@ -306,6 +306,9 @@ struct mobj_t
UINT16 anim_duration; // for FF_ANIMATE states
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;
@ -495,6 +498,9 @@ struct precipmobj_t
UINT16 anim_duration; // for FF_ANIMATE states
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;

View file

@ -90,6 +90,12 @@ extern "C" {
/// \brief Frame flags: Flip sprite horizontally
#define FF_HORIZONTALFLIP 0x02000000
/// \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)

View file

@ -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,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);

View file

@ -1261,12 +1261,15 @@ 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);
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 :)

View file

@ -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)<<RF_BLENDSHIFT),
RF_OVERLAY = ((AST_OVERLAY-1)<<RF_BLENDSHIFT),
RF_AFFINEPRESCALE = 0x08000000, // Makes affines scale before rotating, instead of rotating before scaling
RF_TRANSMASK = (INT32)0xF0000000, // --Transparency override
RF_TRANSSHIFT = (7*4),
RF_TRANS10 = (1<<RF_TRANSSHIFT), // 10%
@ -1036,6 +1039,14 @@ typedef enum
RF_GHOSTLYMASK = (RF_TRANSMASK | RF_FULLBRIGHT),
} renderflags_t;
#if 0
typedef enum
{
RF2_NOAFFINE = 0x00000001, // Disables affine drawing for this sprite
RF2_AFFINEPRESCALE = 0x00000002, // Makes affines scale before rotating, instead of rotating before scaling
} renderflags2_t;
#endif
typedef enum
{
SRF_SINGLE = 0, // 0-angle for all rotations
@ -1090,6 +1101,35 @@ 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
{
vector2_t pivot;
// 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
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 +1163,13 @@ typedef struct
INT32 sourcelength;
UINT8 r8_flatcolor;
affine_bounding_t affinebound;
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;
extern drawcolumndata_t g_dc;

View file

@ -74,6 +74,10 @@ enum
COLDRAWFUNC_TWOSMULTIPATCHTRANS,
COLDRAWFUNC_FOG,
COLDRAWFUNC_DROPSHADOW,
COLDRAWFUNC_AFFINE,
COLDRAWFUNC_AFFINETRANS,
COLDRAWFUNC_AFFINEFUZZY,
COLDRAWFUNC_AFFINETRANSTRANS,
COLDRAWFUNC_MAX
};
@ -201,6 +205,15 @@ 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_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);

View file

@ -21,6 +21,7 @@
// a has a constant z depth from top to bottom.
//
#include "r_main.h"
#include "r_draw.h"
#include <tracy/tracy/Tracy.hpp>
@ -100,6 +101,72 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnPixel(drawcolumndata_t*
return R_GetColumnTranslucent<Type>(dc, dest, bit, col);
}
// 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<DrawColumnType Type>
FUNCINLINE static ATTRINLINE constexpr UINT16 R_DrawColumnAffinePixel(drawcolumndata_t* dc, UINT8 *dest, INT32 bit)
{
const UINT16 pixel = reinterpret_cast<UINT16 *>(dc->source)[bit];
UINT8 col = (UINT8)(pixel & 0xff);
if (pixel < 0xff00)
{
return 0;
}
if constexpr (Type & DrawColumnType::DC_HOLES)
{
if (col == TRANSPARENTPIXEL)
{
return 0;
}
}
if constexpr (Type & DrawColumnType::DC_COLORMAP)
{
// Remap to the current translation
col = dc->translation[col];
}
boolean was_brightmapped = false;
if constexpr (Type & DrawColumnType::DC_BRIGHTMAP)
{
// 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)
{
const UINT16 bmpixel = reinterpret_cast<UINT16 *>(dc->brightmap)[bit];
if ((bmpixel & 0xff) == 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
col = *(dc->transmap + (col << 8) + (*dest));
}
*dest = col;
return (0xff00 | col);
}
/** \brief The R_DrawColumn function
Experiment to make software go faster. Taken from the Boom source
*/
@ -483,3 +550,165 @@ void R_DrawColumn_Flat(drawcolumndata_t *dc)
}
while (--count);
}
template<DrawColumnType Type>
static void R_DrawAffineColumnTemplate(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;
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...
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 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;
const INT32 pw = dc->sourcelength, ph = dc->texheight;
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;
xdiff -= (xdiff ? FRACUNIT : 0);
ydiff -= (ydiff ? FRACUNIT : 0);
// Offset our X and Y positions by the bounding differences.
fixed_t cxx = cx + xdiff + dc->affineoffset.x;
fixed_t cyy = cy + ydiff + dc->affineoffset.y;
// 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<INT32>(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, 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);
INT32 y_real = static_cast<INT32>(ypx);
if (mosaic_on && ymosaic_on)
{
y_real = (static_cast<INT32>(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)
{
continue;
}
R_DrawColumnAffinePixel<Type>(dc, dest, srcy * pw + srcx);
}
}
}
#define DEFINE_AFFINE_COLUMN_FUNC(name, flags) \
void name(drawcolumndata_t *dc) \
{ \
ZoneScoped; \
constexpr DrawColumnType opt = static_cast<DrawColumnType>(flags); \
R_DrawAffineColumnTemplate<opt>(dc); \
}
#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)
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);

View file

@ -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);
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}};
void SplitScreen_OnChange(void)
{
UINT8 i;
@ -1804,6 +1812,9 @@ void R_RegisterEngineStuff(void)
CV_RegisterVar(&cv_sliptidetilt);
CV_RegisterVar(&cv_nulldriftefx);
CV_RegisterVar(&cv_nulldrifttilt);
CV_RegisterVar(&cv_fakerollangle);
CV_RegisterVar(&cv_affineprescale);
CV_RegisterVar(&cv_affinemosaic);
CV_RegisterVar(&cv_showhud);
CV_RegisterVar(&cv_translucenthud);

View file

@ -161,6 +161,9 @@ extern consvar_t cv_sloperoll;
extern consvar_t cv_sliptidetilt;
extern consvar_t cv_nulldriftefx, cv_nulldrifttilt;
extern consvar_t cv_fakerollangle;
extern consvar_t cv_affineprescale, cv_affinemosaic;
// debugging
typedef enum {

View file

@ -48,15 +48,17 @@ 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);
angle_t R_ConvToRollAngle(angle_t ang);
#endif
#ifdef __cplusplus

View file

@ -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)
@ -141,10 +147,17 @@ 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,
boolean hflip,
boolean affine,
interpmobjstate_t *interp,
vector2_t* out,
vector2_t* rolloffs)
@ -166,7 +179,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 =

View file

@ -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.
@ -966,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;
@ -981,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;
@ -1010,10 +1038,37 @@ static void R_DrawVisSprite(vissprite_t *vis)
dc.fullbright = colormaps;
dc.translation = R_GetSpriteTranslation(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, affinebrightmap);
dc.transmap = vis->transmap;
}
else
{
R_SetColumnFunc(COLDRAWFUNC_AFFINETRANS, affinebrightmap);
}
}
else if (vis->transmap)
{
R_SetColumnFunc(COLDRAWFUNC_AFFINEFUZZY, affinebrightmap);
dc.transmap = vis->transmap;
}
else
{
R_SetColumnFunc(COLDRAWFUNC_AFFINE, affinebrightmap);
}
}
// 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 +1167,175 @@ 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<pictureflags_t>(0));
dc.source = static_cast<UINT8 *>(patch->flats[0]);
dc.sourcelength = patch->width;
if (bmpatch)
{
Patch_GenerateFlat(bmpatch, static_cast<pictureflags_t>(0));
// The column is a flat because fuck you
dc.brightmap = static_cast<UINT8 *>(bmpatch->flats[0]);
}
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;
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);
if (vis->floorclip)
{
sprbotscreen = sprtopscreen + fixed_ylen;
}
fixed_t xstep = intsign(vis->xiscale) * FRACUNIT;
dc.affineystep = (vis->cut & SC_VFLIP) ? -FRACUNIT : FRACUNIT;
const fixed_t beforeloopoffset = dc.affineoffset.y;
if (vis->scalestep)
{
// 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));
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)
{
float _ysc = (f_yscale / f_yscale_const);
if (_ysc < 0.001f)
{
continue;
}
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)),
vis->affine.distscale.x);
sprtopscreen = (centeryfrac - FixedMul(dc.texturemid, FLOAT_TO_FIXED(f_yscale)));
float f_ylen = static_cast<float>(dc.affinebound.ylen);
dc.yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS;
dc.yh = ((sprtopscreen + FLOAT_TO_FIXED(f_ylen * _ysc))-1)>>FRACBITS;
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;
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])
{
// 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;
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;
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)
{
INT32 topscreen = sprtopscreen + spryscale*0;
INT32 bottomscreen = sprbotscreen == INT32_MAX ? topscreen + fixed_ylen
: sprbotscreen + fixed_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])
{
// 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;
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.yl <= dc.yh && dc.yh > 0 && fixed_ylen != 0)
{
dc.frac = frac;
coldrawfunc_t* colfunccopy = colfunc;
colfunccopy(&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 +1384,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<fixed_t>(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<fixed_t>(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);
@ -1603,8 +1823,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
@ -1755,6 +1973,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
@ -1838,6 +2078,15 @@ static void R_ProjectSprite(mobj_t *thing)
interpmobjstate_t interp = {0};
mobj_t *interptarg = thing;
// Affines
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};
f_vector2_t affine_pivotoffsetdiff = {0};
f_vector2_t affine_mosaic = {.x = 1.0f, .y = 1.0f};
if (R_IsOverlayingSMonitorPlayer(thing))
{
// Kill overlay misalignment
@ -1848,8 +2097,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;
@ -1876,8 +2123,20 @@ static void R_ProjectSprite(mobj_t *thing)
return;
// aspect ratio stuff
xscale = FixedDiv(projection[viewssnum], tz);
sortscale = FixedDiv(projectiony[viewssnum], tz);
if (affinesprite)
{
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;
}
else
{
xscale = FixedDiv(projection[viewssnum], tz);
sortscale = FixedDiv(projectiony[viewssnum], tz);
}
// decide which patch to use for sprite relative to player
#ifdef RANGECHECK
@ -2000,10 +2259,11 @@ static void R_ProjectSprite(mobj_t *thing)
patch = static_cast<patch_t*>(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)))
&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))
&& (!affinesprite)) // Affines are capable of rotation; this is redundant
{
if ((papersprite && ang >= ANGLE_180) != vflip)
{
@ -2030,6 +2290,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;
@ -2041,8 +2307,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
@ -2060,6 +2325,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))
@ -2067,6 +2334,7 @@ static void R_ProjectSprite(mobj_t *thing)
R_RotateSpriteOffsetsByPitchRoll(interptarg,
vflip,
hflip,
affinesprite,
&interp,
&visoffs,
&rotoffset);
@ -2075,11 +2343,87 @@ static void R_ProjectSprite(mobj_t *thing)
}
#endif
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 = 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);
angle = R_ConvToRollAngle(spriterotangle) * flipsign;
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)
{
// 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);
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 = affine_pivot.x - rolloffs_x;
affine_transform.oy = y_piv - rolloffs_y;
V_GetAffineBounds(&affine_transform, patch, FRACUNIT, &affine_bounds, true);
// Rescale X and Y so we can multiply the pivot offset differences by them.
const float f_affinexscale = static_cast<float>(affine_bounds.xlen) / static_cast<float>(patch->width);
const float f_affineyscale = static_cast<float>(affine_bounds.ylen) / static_cast<float>(patch->height);
affine_pivotoffsetdiff.x *= f_affinexscale;
affine_pivotoffsetdiff.y *= f_affineyscale;
spr_width = (affine_bounds.xlen * FRACUNIT);
spr_offset = (affine_bounds.xleft * FRACUNIT) - FLOAT_TO_FIXED(affine_pivotoffsetdiff.x);
spritexscale = FRACUNIT;
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
@ -2093,7 +2437,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
@ -2107,10 +2456,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;
}
@ -2124,6 +2473,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;
@ -2135,6 +2490,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);
@ -2151,6 +2507,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);
@ -2223,14 +2580,17 @@ static void R_ProjectSprite(mobj_t *thing)
{
scalestep = 0;
yscale = 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,FixedMul(xscale, this_scale)) : offset;
x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS;
// off the right side?
if (x1 > viewwidth)
return;
tx += offset2;
tx += (affinesprite) ? FixedDiv(offset2,FixedMul(xscale, this_scale)) : offset2;
x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--;
// off the left side
@ -2385,18 +2745,47 @@ static void R_ProjectSprite(mobj_t *thing)
if (!shadowskew)
{
//SoM: 3/17/2000: Disregard sprites that are out of view..
if (vflip)
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.
// 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 float pivydiff = affine_pivotoffsetdiff.y * ((vflip) ? -1.0f : 1.0f);
const fixed_t real_topoffset = FLOAT_TO_FIXED((static_cast<float>(affine_bounds.yup) - pivydiff) / affineyscale) + thingyoffset;
const fixed_t real_height = FLOAT_TO_FIXED(static_cast<float>(affine_bounds.ylen) / affineyscale);
useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale));
useheight = FixedMul(real_height, FixedMul(rawyscale, this_scale));
}
else
{
useoffset = FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
useheight = FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
}
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.
// 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 + ((vflipaffine) ? interp.height : 0);
gz = gzt - useheight;
}
}
@ -2539,6 +2928,29 @@ static void R_ProjectSprite(mobj_t *thing)
vis->viewpoint.z = viewz;
vis->viewpoint.angle = viewangle;
vis->affine = {0};
if (affinesprite)
{
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;
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;
vis->affine.mosaic.x = affine_mosaic.x;
vis->affine.mosaic.y = affine_mosaic.y;
R_CopyAffineBounds(&affine_bounds, &vis->affine.bounds);
}
vis->mobj = thing; // Easy access! Tails 06-07-2002
vis->x1 = x1 < portalclipstart ? portalclipstart : x1;
@ -2558,7 +2970,7 @@ static void R_ProjectSprite(mobj_t *thing)
vis->xscale = FixedMul(spritexscale, xscale); //SoM: 4/17/2000
vis->scale = FixedMul(spriteyscale, yscale); //<<detailshift;
vis->thingscale = this_scale;
vis->thingscale = (affinesprite) ? FRACUNIT : this_scale;
vis->spritexscale = spritexscale;
vis->spriteyscale = spriteyscale;
@ -2589,7 +3001,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);
}
@ -2632,6 +3053,8 @@ static void R_ProjectSprite(mobj_t *thing)
vis->cut = static_cast<spritecut_e>(vis->cut | SC_VFLIP);
if (splat)
vis->cut = static_cast<spritecut_e>(vis->cut | SC_SPLAT); // I like ya cut g
if (affinesprite)
vis->cut = static_cast<spritecut_e>(vis->cut | SC_AFFINE); // *smack* AIEEEEEEEEEEEE
vis->patch = patch;
vis->bright = R_CacheSpriteBrightMap(sprinfo, frame);
@ -3939,6 +4362,17 @@ boolean R_ThingIsPaperSprite(mobj_t *thing)
return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE);
}
boolean R_ThingIsAffineSprite(mobj_t *thing)
{
// Affine papersprites are messy in software rendering.
// Yes, I'm lazy; I've been at this for TWO WEEKS.
boolean papersprite = (R_ThingIsPaperSprite(thing));
boolean notaffine = ((thing->frame & FF_NOAFFINE || thing->renderflags & RF_NOAFFINE) || papersprite);
return (!notaffine);
}
boolean R_ThingIsFloorSprite(mobj_t *thing)
{
return (thing->flags2 & MF2_SPLAT || thing->frame & FF_FLOORSPRITE || thing->renderflags & RF_FLOORSPRITE);
@ -3965,6 +4399,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->renderflags & RF_AFFINEPRESCALE);
}
//
// R_DrawMasked
//

View file

@ -92,10 +92,18 @@ boolean R_ThingVerticallyFlipped (mobj_t *thing);
boolean R_ThingIsPaperSprite (mobj_t *thing);
boolean R_ThingIsFloorSprite (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);
boolean R_ThingIsUsingBakedOffsets(mobj_t *thing);
@ -159,6 +167,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
@ -185,6 +194,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
@ -206,6 +216,16 @@ struct vissprite_t
INT32 offset; // The center of the shearing location offset from x1
} shear;
struct {
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.
} affine;
fixed_t texturemid;
patch_t *patch;
patch_t *bright;

View file

@ -133,6 +133,14 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext)
colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn;
colfuncs[COLDRAWFUNC_DROPSHADOW] = R_DrawDropShadowColumn;
// 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;
colfuncs_bm[COLDRAWFUNC_TRANS] = R_DrawTranslatedColumn_Brightmap;
@ -143,6 +151,13 @@ void SCR_SetDrawFuncs(enum columncontext_e _columncontext)
colfuncs_bm[COLDRAWFUNC_FOG] = NULL; // Not needed
colfuncs_bm[COLDRAWFUNC_DROPSHADOW] = NULL; // Not needed
// Affine brightmap functions; needed but I don't care at the moment
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;
spanfuncs[SPANDRAWFUNC_TILTED] = R_DrawSpan_Tilted;

View file

@ -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);
@ -358,6 +359,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 +422,6 @@ TYPEDEF (taggroup_t);
// v_video.h
TYPEDEF (colorlookup_t);
TYPEDEF (cliprect_t);
TYPEDEF (affine_t);
// w_wad.h
TYPEDEF (filelump_t);

View file

@ -810,6 +810,114 @@ 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,
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?
// 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 = 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);
// 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.
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,
// 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 +966,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, vid.dup * FRACUNIT, &bounds, false);
Patch_GenerateFlat(patch, 0);
const UINT16 *src = patch->flats[0];
if (src == NULL)
@ -867,8 +978,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 +1003,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;

View file

@ -218,16 +218,11 @@ 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)
};
affine_bounding_t* V_GetAffineBounds(const affine_t* transform,
patch_t* patch,
fixed_t divisor,
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);