OGL affines

We lose hardware spritesplits in the process; I do not care
This commit is contained in:
yamamama 2026-03-03 07:27:09 -05:00
parent 307e334e27
commit 7792079fbc
2 changed files with 394 additions and 42 deletions

View file

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

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