diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index ca7b514b5..a1d668f80 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -43,6 +43,12 @@ typedef struct float z; } polyvertex_t; +typedef struct +{ + float x; + float y; +} f_vector2_t; + // a convex 'plane' polygon, clockwise order typedef struct { @@ -84,6 +90,28 @@ typedef struct gl_vissprite_s angle_t angle; // for splats + // Affine nonsense + struct { + // Vertex points for the affine sprite to be drawn to. + // + // 4--3 + // | /| + // |/ | + // 1--2 + f_vector2_t p1, p2, p3, p4; + + // The "root", or center, of the affine sprite + polyvertex_t root; + + // Bounding point (leftmost and highest point), to resolve clipping + polyvertex_t bounding_point; + + // Sine/cosine multiplier, used for billboarding. + float patchsin, patchcos; + + affine_t transform; // The actual affine transformation. + } affine; + //Hurdler: 25/04/2000: now support colormap in hardware mode UINT8 *colormap; INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 208e65818..e301d99f5 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3343,6 +3343,8 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) // This is expecting a pointer to an array containing 4 wallVerts for a sprite static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts, const boolean precip) { + const boolean affine = ((spr->mobj && !P_MobjWasRemoved(spr->mobj)) && ((spr->mobj->player != NULL) || R_ThingIsAffineSprite(spr->mobj))); + if (cv_glspritebillboarding.value && spr && spr->mobj && !R_ThingIsPaperSprite(spr->mobj) && wallVerts) @@ -3370,20 +3372,42 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts // X, Y, AND Z need to be manipulated for the polys to rotate around the // origin, because of how the origin setting works I believe that should // be mobj->z or mobj->z + mobj->height - wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey; - wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey; - // translate back to be around 0 before translating back - wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; - wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + if (affine) + { + wallVerts[0].y = (spr->affine.p1.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[0].x += (spr->affine.p1.y * gl_viewludcos) * gl_viewcos; + wallVerts[0].z += (spr->affine.p1.y * gl_viewludcos) * gl_viewsin; - wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; - wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[1].y = (spr->affine.p2.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[1].x += (spr->affine.p2.y * gl_viewludcos) * gl_viewcos; + wallVerts[1].z += (spr->affine.p2.y * gl_viewludcos) * gl_viewsin; - wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; - wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[2].y = (spr->affine.p3.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[2].x += (spr->affine.p3.y * gl_viewludcos) * gl_viewcos; + wallVerts[2].z += (spr->affine.p3.y * gl_viewludcos) * gl_viewsin; + + wallVerts[3].y = (spr->affine.p4.y * gl_viewludsin) + spr->affine.root.y; + wallVerts[3].x += (spr->affine.p4.y * gl_viewludcos) * gl_viewcos; + wallVerts[3].z += (spr->affine.p4.y * gl_viewludcos) * gl_viewsin; + } + else + { + wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey; + wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey; + // translate back to be around 0 before translating back + wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; + wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos; + + wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos; + + wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin; + + wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin; + } } } @@ -3468,7 +3492,14 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) HWR_RotateSpritePolyToAim(spr, baseWallVerts, false); // push it toward the camera to mitigate floor-clipping sprites - float sprdist = sqrtf((spr->x1 - gl_viewx)*(spr->x1 - gl_viewx) + (spr->z1 - gl_viewy)*(spr->z1 - gl_viewy) + (spr->gzt - gl_viewz)*(spr->gzt - gl_viewz)); + float sprdist = 0; + + float f_xdiff = (spr->x1 - gl_viewx); + float f_ydiff = (spr->z1 - gl_viewy); + float f_zdiff = (spr->gzt - gl_viewz); + + sprdist = sqrtf((f_xdiff*f_xdiff) + (f_ydiff*f_ydiff) + (f_zdiff*f_zdiff)); + float distfact = ((2.0f*spr->dispoffset) + 20.0f) / sprdist; for (i = 0; i < 4; i++) { @@ -3746,6 +3777,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) patch_t *gpatch; GLPatch_t *hwrpatch; FSurfaceInfo Surf = {}; + const boolean affine = ((spr->mobj && !P_MobjWasRemoved(spr->mobj)) && ((spr->mobj->player != NULL) || R_ThingIsAffineSprite(spr->mobj))); const boolean splat = R_ThingIsFloorSprite(spr->mobj); if (!spr->mobj || !spr->mobj->subsector) @@ -3753,7 +3785,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) if (spr->mobj->subsector->sector->numlights && (spr->mobj->renderflags & RF_ABSOLUTELIGHTLEVEL) == 0 - && !splat) + && !splat && !affine) { HWR_SplitSprite(spr); return; @@ -3886,6 +3918,29 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) wallVerts[i].y = FIXED_TO_FLOAT(spr->gz) + zoffset; } } + else if (affine) + { + // Affines are weird; we've generated the transformations for them, + // so now we need to piece together each wall vertex. + const float _cos = spr->affine.patchcos; + const float _sin = spr->affine.patchsin; + + wallVerts[0].x = spr->affine.root.x + (spr->affine.p1.x * _cos); + wallVerts[0].z = spr->affine.root.z + (spr->affine.p1.x * _sin); + wallVerts[0].y = spr->affine.root.y + spr->affine.p1.y; + + wallVerts[1].x = spr->affine.root.x + (spr->affine.p2.x * _cos); + wallVerts[1].z = spr->affine.root.z + (spr->affine.p2.x * _sin); + wallVerts[1].y = spr->affine.root.y + spr->affine.p2.y; + + wallVerts[2].x = spr->affine.root.x + (spr->affine.p3.x * _cos); + wallVerts[2].z = spr->affine.root.z + (spr->affine.p3.x * _sin); + wallVerts[2].y = spr->affine.root.y + spr->affine.p3.y; + + wallVerts[3].x = spr->affine.root.x + (spr->affine.p4.x * _cos); + wallVerts[3].z = spr->affine.root.z + (spr->affine.p4.x * _sin); + wallVerts[3].y = spr->affine.root.y + spr->affine.p4.y; + } else { // these were already scaled in HWR_ProjectSprite @@ -3939,7 +3994,27 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) HWR_RotateSpritePolyToAim(spr, wallVerts, false); // push it toward the camera to mitigate floor-clipping sprites - sprdist = sqrtf((spr->x1 - gl_viewx)*(spr->x1 - gl_viewx) + (spr->z1 - gl_viewy)*(spr->z1 -gl_viewy)+ (spr->gzt - gl_viewz)*(spr->gzt - gl_viewz)); + polyvertex_t pushpoint = {0}; + + if (affine) + { + pushpoint.x = spr->affine.bounding_point.x; + pushpoint.y = spr->affine.bounding_point.y; + pushpoint.z = spr->affine.bounding_point.z; + } + else + { + pushpoint.x = spr->x1; + pushpoint.y = spr->z1; + pushpoint.z = spr->gzt; + } + + float f_xdiff = (pushpoint.x - gl_viewx); + float f_ydiff = (pushpoint.y - gl_viewy); + float f_zdiff = (pushpoint.z - gl_viewz); + + sprdist = sqrtf((f_xdiff*f_xdiff) + (f_ydiff*f_ydiff) + (f_zdiff*f_zdiff)); + distfact = ((2.0f* spr->dispoffset) + 20.0f) / sprdist; for (i = 0; i < 4; i++) { @@ -3976,9 +4051,21 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) if (!R_ThingIsFullBright(spr->mobj) && !(spr->mobj->renderflags & RF_NOCOLORMAPS)) colormap = sector->extra_colormap; - if (splat && sector->numlights) + if ((splat||affine) && sector->numlights) { - INT32 light = R_GetPlaneLight(sector, spr->mobj->z, false); + // Affine splitting is going to require more advanced tricks + // (GLSL fragment shader, or generating a "split" polygon on the fly). + // I've already spent too much time on this damn thing, so I'm just doing + // what precipitation (and, by extension, MD2) does. + + fixed_t _zoffs = 0; + + if (affine) + { + _zoffs = spr->mobj->height; // Always use the light at the top instead of whatever I was doing before + } + + INT32 light = R_GetPlaneLight(sector, spr->mobj->z + _zoffs, false); if (!lightset) lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel; @@ -4823,6 +4910,14 @@ static void HWR_ProjectSprite(mobj_t *thing) vector2_t visoffs, rotoffset; #endif + // Affines + boolean affinesprite = ((thing->player != NULL) || R_ThingIsAffineSprite(thing)); + affine_t affine_transform = {0}; + vector2_t affine_scale = {0}; + polyvertex_t affine_rootpoint = {0}; + f_vector2_t affine_point[4] = {0}; + float leftbound = 0, backbound = 0, topbound = 0; + // uncapped/interpolation interpmobjstate_t interp = {0}; mobj_t *interptarg; @@ -4991,11 +5086,14 @@ static void HWR_ProjectSprite(mobj_t *thing) spr_offset = spritecachedinfo[lumpoff].offset; spr_topoffset = spritecachedinfo[lumpoff].topoffset; + float base_topoffs = FIXED_TO_FLOAT(spr_topoffset); + #ifdef ROTSPRITE spriterotangle = R_SpriteRotationAngle(thing, NULL, &interp, false); if (spriterotangle != 0 - && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE))) + && !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)) + && (!affinesprite)) // Affines are capable of rotation; this is redundant { rollangle = R_GetRollAngle(papersprite == vflip ? spriterotangle : InvAngle(spriterotangle)); @@ -5036,6 +5134,39 @@ static void HWR_ProjectSprite(mobj_t *thing) } #endif + if (affinesprite) + { + affine_scale.x = FLOAT_TO_FIXED(spritexscale * this_scale); + affine_scale.y = FLOAT_TO_FIXED(spriteyscale * this_scale); + + angle_t angle; + + INT32 flipsign = ((flip && papersprite) ? -1 : 1); // Flip OGL affine papersprites for Software parity + + angle = spriterotangle * flipsign; + + const fixed_t rolloffs_x = FixedDiv(interptarg->rollingxoffset, highresscale) * (((thing->renderflags & RF_FLIPOFFSETS) && flip) ? -1 : 1); + const fixed_t rolloffs_y = FixedDiv(interptarg->rollingyoffset, highresscale); + fixed_t y_piv = spr_topoffset; + + if (vflip) + { + // Flip the upper offset, and use *that* as the pivot + y_piv = spr_height - y_piv; + } + + fixed_t sa = FSIN(angle), ca = FCOS(angle); + affine_transform.a = FixedDiv(ca, affine_scale.x); + affine_transform.b = FixedDiv(-sa, affine_scale.x); + affine_transform.c = FixedDiv(sa, affine_scale.y); + affine_transform.d = FixedDiv(ca, affine_scale.y); + affine_transform.ox = spr_offset - rolloffs_x; + affine_transform.oy = y_piv - rolloffs_y; + + spritexscale = 1.0; + spriteyscale = 1,0; + } + if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = FixedDiv(interp.spritexoffset,highresscale); @@ -5115,23 +5246,150 @@ static void HWR_ProjectSprite(mobj_t *thing) visoffs.x = (FixedDiv((visoffs.x * FRACUNIT), mapobjectscale)); } #endif - if (flip) + if (affinesprite) { + x1 = x2 = z1 = z2 = 0.0f; + + float f_offs, f_width, f_height; + + f_offs = FIXED_TO_FLOAT(spr_offset); + f_width = FIXED_TO_FLOAT(spr_width); + f_height = FIXED_TO_FLOAT(spr_height); + + // From HWR_DrawAffinePatch + + float fa = FIXED_TO_FLOAT(affine_transform.a); + float fd = FIXED_TO_FLOAT(affine_transform.d); + float fc = FIXED_TO_FLOAT(affine_transform.c); + float fb = FIXED_TO_FLOAT(affine_transform.b); + float fx = FIXED_TO_FLOAT(affine_transform.ox); + float fy = FIXED_TO_FLOAT(affine_transform.oy); + + // now, the matrix passed to this function maps screen coordinates to texel coordinates... + // but to translate this from software to GL, we have to figure out where each corner + // (or vertex) of the patch should end up on the screen. + // which means we have to map texel coordinates to screen coordinates. + // which means we have to invert the matrix. + // how do you invert a matrix? + // ... + // i don't fucking know, i spent a day on this and got absolutely nowhere, but this guy knows: + // https://nigeltao.github.io/blog/2021/inverting-3x2-affine-transformation-matrix.html + float determinant = fa * fd - fb * fc; + if (determinant > 0.0f) + { + float ba = fd / determinant; + float bb = -fb / determinant; + float bc = -fc / determinant; + float bd = fa / determinant; + + // set the polygon vertices to the right positions + // 3--2 + // | /| + // |/ | + // 0--1 + affine_point[3].x = (ba * -fx + bb * -fy + fx); + affine_point[3].y = (bc * -fx + bd * -fy + fy); + + affine_point[2].x = ba * (f_width - fx) + bb * -fy + fx; + affine_point[2].y = bc * (f_width - fx) + bd * -fy + fy; + + affine_point[0].x = ba * -fx + bb * (f_height - fy) + fx; + affine_point[0].y = bc * -fx + bd * (f_height - fy) + fy; + + affine_point[1].x = ba * (f_width - fx) + bb * (f_height - fy) + fx; + affine_point[1].y = bc * (f_width - fx) + bd * (f_height - fy) + fy; + + // Focus each point on the center. + affine_point[0].x = fx - affine_point[0].x; + affine_point[1].x = fx - affine_point[1].x; + affine_point[2].x = fx - affine_point[2].x; + affine_point[3].x = fx - affine_point[3].x; + + affine_point[1].y -= fy; + affine_point[2].y -= fy; + affine_point[0].y -= fy; + affine_point[3].y -= fy; + + // ...okay, now comb through all four vertices and set the output bounds based on this. + // "Why not a loop?" According to SM64 programming wizard Kaze Emanuar, + // loops take more processing time. If we can help it, it's better to just cut corners. + float f_left = 0; + float f_bottom = 0; +#define BOUNDCHECK(i) \ + { \ + f_left = min(affine_point[i].x, f_left); \ + f_bottom = max(affine_point[i].y, f_bottom); \ + } + + BOUNDCHECK(0); + BOUNDCHECK(1); + BOUNDCHECK(2); + BOUNDCHECK(3); + +#undef BOUNDCHECK + + backbound = (f_left * rightsin); + leftbound = (f_left * rightcos); + topbound = f_bottom * -1; + + // Invert the Y values. + affine_point[0].y *= -1.0f; + affine_point[1].y *= -1.0f; + affine_point[2].y *= -1.0f; + affine_point[3].y *= -1.0f; + + affine_rootpoint.x = tr_x; + affine_rootpoint.z = tr_y; + #ifdef ROTSPRITE - spr_offset -= visoffs.x; - spr_offset -= rotoffset.x; + const float xrescale = this_xscale / FIXED_TO_FLOAT(mapobjectscale); + float f_visx = (FIXED_TO_FLOAT(FixedMul(visoffs.x, mapobjectscale))) * xrescale; + if (flip) + { + spr_offset -= visoffs.x; + affine_rootpoint.x -= (f_visx * rightcos); + affine_rootpoint.z -= (f_visx * rightsin); + } + else + { + spr_offset += visoffs.x; + affine_rootpoint.x += (f_visx * rightcos); + affine_rootpoint.z += (f_visx * rightsin); + } #endif - x1 = (FIXED_TO_FLOAT((spr_width - spr_offset)) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + } + else + { + goto nodeterminant; + } + } else { +nodeterminant: + if (flip) + { #ifdef ROTSPRITE - spr_offset += visoffs.x; - spr_offset += rotoffset.x; + spr_offset -= visoffs.x; + spr_offset -= rotoffset.x; #endif - x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + x1 = (FIXED_TO_FLOAT((spr_width - spr_offset)) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + } + else + { +#ifdef ROTSPRITE + spr_offset += visoffs.x; + spr_offset += rotoffset.x; +#endif + x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + } + + z1 = tr_y + x1 * rightsin; + z2 = tr_y - x2 * rightsin; + x1 = tr_x + x1 * rightcos; + x2 = tr_x - x2 * rightcos; } // test if too close /* @@ -5145,23 +5403,46 @@ static void HWR_ProjectSprite(mobj_t *thing) } */ - z1 = tr_y + x1 * rightsin; - z2 = tr_y - x2 * rightsin; - x1 = tr_x + x1 * rightcos; - x2 = tr_x - x2 * rightcos; if (thing->terrain && thing->terrain->floorClip) - spr_topoffset -= thing->terrain->floorClip; + spr_topoffset -= thing->terrain->floorClip; + + affine_rootpoint.y = 0; + + const float f_topoffs = FIXED_TO_FLOAT(spr_topoffset); if (vflip) { - gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); + affine_rootpoint.y = FIXED_TO_FLOAT(interp.z + thing->height) - (f_topoffs * this_yscale); } else { - gzt = FIXED_TO_FLOAT(interp.z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + affine_rootpoint.y = FIXED_TO_FLOAT(interp.z) + (f_topoffs * this_yscale); + } + + if (!affinesprite) + { + if (vflip) + { + gz = affine_rootpoint.y; + gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); + } + else + { + gzt = affine_rootpoint.y; + gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + } + } + else + { + if (vflip) + { + affine_rootpoint.y += (base_topoffs * this_yscale); + } + else + { + affine_rootpoint.y -= (base_topoffs * this_yscale); + } } } @@ -5227,10 +5508,50 @@ static void HWR_ProjectSprite(mobj_t *thing) // store information in a vissprite vis = HWR_NewVisSprite(); - vis->x1 = x1; - vis->x2 = x2; - vis->z1 = z1; - vis->z2 = z2; + + if (affinesprite) + { + vis->affine.p1.x = affine_point[0].x; + vis->affine.p1.y = affine_point[0].y; + + vis->affine.p2.x = affine_point[1].x; + vis->affine.p2.y = affine_point[1].y; + + vis->affine.p3.x = affine_point[2].x; + vis->affine.p3.y = affine_point[2].y; + + vis->affine.p4.x = affine_point[3].x; + vis->affine.p4.y = affine_point[3].y; + + vis->affine.root.x = affine_rootpoint.x; + vis->affine.root.y = affine_rootpoint.y; + vis->affine.root.z = affine_rootpoint.z; + + vis->affine.patchcos = rightcos; + vis->affine.patchsin = rightsin; + + vis->affine.transform.a = affine_transform.a; + vis->affine.transform.c = affine_transform.b; + vis->affine.transform.b = affine_transform.c; + vis->affine.transform.d = affine_transform.d; + vis->affine.transform.ox = affine_transform.ox; + vis->affine.transform.oy = affine_transform.oy; + + vis->affine.bounding_point.x = affine_rootpoint.x + leftbound; + vis->affine.bounding_point.y = affine_rootpoint.z + backbound; + vis->affine.bounding_point.z = affine_rootpoint.y + topbound; + + // We're using this info to draw the affine sprite; zero-out "standard" draw info. + vis->x1 = vis->x2 = vis->z1 = vis->z2 = 0; + vis->gz = vis->gzt = 0; + } + else + { + vis->x1 = x1; + vis->x2 = x2; + vis->z1 = z1; + vis->z2 = z2; + } vis->tz = tz; // Keep tz for the simple sprite sorting that happens vis->tracertz = tracertz; @@ -5303,9 +5624,12 @@ static void HWR_ProjectSprite(mobj_t *thing) vis->colormap += COLORMAP_REMAPOFFSET; } - // set top/bottom coords - vis->gzt = gzt; - vis->gz = gz; + if (!affinesprite) + { + // set top/bottom coords + vis->gzt = gzt; + vis->gz = gz; + } //CONS_Debug(DBG_RENDER, "------------------\nH: sprite : %d\nH: frame : %x\nH: type : %d\nH: sname : %s\n\n", // thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);