diff --git a/src/deh_tables.c b/src/deh_tables.c index 16fc5b4f0..b4abaf850 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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}, diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index ca7b514b5..39d1af744 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -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 diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 636d6d852..ee11ce15d 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,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]); diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 42aff1e76..0431b8875 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1678,7 +1678,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) fixedAngY = 0; { - fixed_t anglef = AngleFixed(R_SpriteRotationAngle(spr->mobj, NULL, &interp)); + fixed_t anglef = AngleFixed(R_SpriteRotationAngle(spr->mobj, NULL, &interp, false)); p.rollangle = 0.0f; diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 6cdec7b59..87b65fca8 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -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; diff --git a/src/m_fixed.h b/src/m_fixed.h index 74b2f29b6..d97f3747f 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -351,6 +351,13 @@ struct vector2_t fixed_t y; }; +// Floating-point version, used sparingly, primarily in rendering +struct f_vector2_t +{ + float x; + float y; +}; + vector2_t *FV2_Load(vector2_t *vec, fixed_t x, fixed_t y); vector2_t *FV2_UnLoad(vector2_t *vec, fixed_t *x, fixed_t *y); vector2_t *FV2_Copy(vector2_t *a_o, const vector2_t *a_i); diff --git a/src/p_mobj.h b/src/p_mobj.h index 371cbe1db..bc8b55854 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -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; diff --git a/src/p_pspr.h b/src/p_pspr.h index a42363388..12fbb3b95 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -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) diff --git a/src/p_saveg.c b/src/p_saveg.c index 6e8779124..c65649570 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2046,7 +2046,7 @@ static void DiffMobj(const mobj_t *mobj, UINT32 diff[]) DIFF(mobj->mirrored, MD2_MIRRORED); DIFF(mobj->rollangle, MD2_ROLLANGLE); DIFF(mobj->shadowscale, MD2_SHADOWSCALE); - DIFF(mobj->renderflags, 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); diff --git a/src/p_user.c b/src/p_user.c index 2c9044be7..cd150c864 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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 :) diff --git a/src/r_defs.h b/src/r_defs.h index 375884378..e5bfd2771 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1004,6 +1004,7 @@ typedef enum #ifdef HWRENDER RF_NOMODEL = 0x00040000, // do not draw a model for this mobj in opengl, use its sprite instead #endif + RF_NOAFFINE = 0x00080000, // Disables affine drawing for this sprite RF_HIDESHIFT = (20), RF_DONTDRAW = 0x0F << RF_HIDESHIFT, // --Don't generate a vissprite @@ -1021,6 +1022,8 @@ typedef enum RF_MODULATE = ((AST_MODULATE-1)< @@ -100,6 +101,72 @@ FUNCINLINE static ATTRINLINE constexpr UINT8 R_DrawColumnPixel(drawcolumndata_t* return R_GetColumnTranslucent(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 +FUNCINLINE static ATTRINLINE constexpr UINT16 R_DrawColumnAffinePixel(drawcolumndata_t* dc, UINT8 *dest, INT32 bit) +{ + const UINT16 pixel = reinterpret_cast(dc->source)[bit]; + + UINT8 col = (UINT8)(pixel & 0xff); + + if (pixel < 0xff00) + { + return 0; + } + + if constexpr (Type & DrawColumnType::DC_HOLES) + { + if (col == TRANSPARENTPIXEL) + { + return 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(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 +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(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(ypx); + + if (mosaic_on && ymosaic_on) + { + y_real = (static_cast(ypx / y_mosaic) * y_mosaic); + } + + ux = FLOAT_TO_FIXED(ux_base + (f_b * y_real)); + uy = FLOAT_TO_FIXED(uy_base + (f_d * y_real)); + + if (srcx < 0 || srcx >= pw || srcy < 0 || srcy >= ph) + { + continue; + } + + R_DrawColumnAffinePixel(dc, dest, srcy * pw + srcx); + } + } +} + +#define DEFINE_AFFINE_COLUMN_FUNC(name, flags) \ + void name(drawcolumndata_t *dc) \ + { \ + ZoneScoped; \ + constexpr DrawColumnType opt = static_cast(flags); \ + R_DrawAffineColumnTemplate(dc); \ + } + +#define DEFINE_AFFINE_COLUMN_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); diff --git a/src/r_main.cpp b/src/r_main.cpp index e1192ad93..46a367007 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -197,6 +197,14 @@ consvar_t cv_sliptidetilt = CVAR_INIT ("sliptidetilt", "On", CV_SAVE, CV_OnOff, consvar_t cv_nulldriftefx = CVAR_INIT ("nulldriftefx", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff, NULL); +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); diff --git a/src/r_main.h b/src/r_main.h index 57889b115..26bcecf97 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -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 { diff --git a/src/r_patch.h b/src/r_patch.h index 3a66462ba..4ae365f58 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -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 diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index bef1215bf..ca854dcaf 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -56,7 +56,7 @@ angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_ return rollOrPitch; } -static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) +static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer, boolean fliptilt) { angle_t viewingAngle = R_PointToAnglePlayer(viewPlayer, player->mo->x, player->mo->y); angle_t angleDelta = (viewingAngle - player->mo->angle); @@ -70,6 +70,12 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) // Sliptide tilt: Nulldrifts take priority if and ONLY if they're the larger value compared to sliptides. angle_t sliptideLift = max(abs(aiztilt), abs(nulltilt)) * liftsign; + if (fliptilt) + { + // Vertical flip affine hack: invert the angle so it still looks right. + sliptideLift *= -1; + } + angle_t rollAngle = 0; if (sliptideLift) @@ -97,23 +103,23 @@ boolean R_IsOverlayingSMonitorPlayer(mobj_t* mobj) (mobj->target->player->smonitortimer)); } -angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer) +angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer, boolean fliptilt) { angle_t rollAngle = mobj->rollangle; if (mobj->player) { - rollAngle += R_PlayerSpriteRotation(mobj->player, viewPlayer); + rollAngle += R_PlayerSpriteRotation(mobj->player, viewPlayer, fliptilt); } else if (R_IsOverlayingSMonitorPlayer(mobj)) { - rollAngle += R_PlayerSpriteRotation(mobj->target->player, viewPlayer); + rollAngle += R_PlayerSpriteRotation(mobj->target->player, viewPlayer, fliptilt); } return rollAngle; } -angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp) +angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstate_t *interp, boolean fliptilt) { angle_t rollOrPitch; @@ -125,7 +131,7 @@ angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer, interpmobjstat { rollOrPitch = R_GetPitchRollAngle(mobj, viewPlayer, interp); } - return (rollOrPitch + R_ModelRotationAngle(mobj, viewPlayer)); + return (rollOrPitch + R_ModelRotationAngle(mobj, viewPlayer, fliptilt)); } INT32 R_GetRollAngle(angle_t rollangle) @@ -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 = diff --git a/src/r_things.cpp b/src/r_things.cpp index 08042ba65..f6c06d746 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -39,6 +39,7 @@ #include "d_netfil.h" // blargh. for nameonly(). #include "m_cheat.h" // objectplace #include "p_local.h" // stplyr +#include "v_video.h" // V_GetAffineBounds #include "core/thread_pool.h" #ifdef HWRENDER #include "hardware/hw_md2.h" @@ -946,6 +947,25 @@ UINT32 R_GetThingTransTable(fixed_t alpha, UINT32 transmap) return (20*(FRACUNIT - ((alpha * (10 - transmap))/10) - 1) + FRACUNIT) >> (FRACBITS+1); } +static void R_CopyAffineBounds(affine_bounding_t *src, affine_bounding_t *dest) +{ + // Bounding points + dest->l = src->l; + dest->r = src->r; + dest->t = src->t; + dest->b = src->b; + + // Differences from pivot point + dest->xleft = src->xleft; + dest->xright = src->xright; + dest->yup = src->yup; + dest->ydown = src->ydown; + + // Length values + dest->xlen = src->xlen; + dest->ylen = src->ylen; +} + // // R_DrawVisSprite // mfloorclip and mceilingclip should also be set. @@ -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(0)); + dc.source = static_cast(patch->flats[0]); + dc.sourcelength = patch->width; + + if (bmpatch) + { + Patch_GenerateFlat(bmpatch, static_cast(0)); + + // The column is a flat because fuck you + dc.brightmap = static_cast(bmpatch->flats[0]); + } + + dc.affine.a = vis->affine.transform.a; + dc.affine.b = vis->affine.transform.b; + dc.affine.c = vis->affine.transform.c; + 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(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(frac >> FRACBITS, 0, patch->width - 1); - // Non-paper drawing loop - for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan) - { - texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1); + column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); - column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn])); + if (bmpatch) + bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - if (bmpatch) - bmcol = (column_t *)((UINT8 *)bmpatch->columns + (bmpatch->columnofs[texturecolumn])); - - localcolfunc (&dc, column, bmcol, baseclip); - } + localcolfunc (&dc, column, bmcol, baseclip); + } } R_SetColumnFunc(BASEDRAWFUNC, false); @@ -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(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(affine_bounds.xlen) / static_cast(patch->width); + const float f_affineyscale = static_cast(affine_bounds.ylen) / static_cast(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(affine_bounds.yup) - pivydiff) / affineyscale) + thingyoffset; + const fixed_t real_height = FLOAT_TO_FIXED(static_cast(affine_bounds.ylen) / affineyscale); + + useoffset = FixedMul(real_topoffset, FixedMul(rawyscale, this_scale)); + 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); //<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(vis->cut | SC_VFLIP); if (splat) vis->cut = static_cast(vis->cut | SC_SPLAT); // I like ya cut g + if (affinesprite) + vis->cut = static_cast(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 // diff --git a/src/r_things.h b/src/r_things.h index 4157a9a05..2914058c1 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -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; diff --git a/src/screen.c b/src/screen.c index dd376f7c1..bead6e2f8 100644 --- a/src/screen.c +++ b/src/screen.c @@ -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; diff --git a/src/typedef.h b/src/typedef.h index af50e9342..c5f78200a 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -232,6 +232,7 @@ TYPEDEF (mdllistitem_t); // m_fixed.h TYPEDEF (vector2_t); +TYPEDEF (f_vector2_t); TYPEDEF (vector3_t); TYPEDEF (vector4_t); TYPEDEF (matrix_t); @@ -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); diff --git a/src/v_video.c b/src/v_video.c index ec1f477ab..cc4f93ae2 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -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; diff --git a/src/v_video.h b/src/v_video.h index 61264c092..b4d8cf3a4 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -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);