raze/source/core/rendering/scene/hw_sprites.cpp
Christoph Oelckers 71ca2c4677 - eliminate the last remaining references to the tile manager in the core renderer files.
The only thing left is the sky which needs more refactoring first and the tileUpdatePicnum call for sprites which cannot be resolved by the texture manager because Blood has a randomization factor included here.
2022-12-11 19:44:01 +01:00

542 lines
15 KiB
C++

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
/*
** gl_sprite.cpp
** Sprite/Particle rendering
**
*/
#include "matrix.h"
//#include "models.h"
#include "vectors.h"
#include "texturemanager.h"
#include "basics.h"
//#include "hw_models.h"
#include "hw_drawstructs.h"
#include "hw_drawinfo.h"
#include "hw_portal.h"
#include "flatvertices.h"
#include "hw_cvars.h"
#include "hw_clock.h"
#include "hw_material.h"
#include "hw_dynlightdata.h"
#include "hw_lightbuffer.h"
#include "hw_renderstate.h"
#include "hw_models.h"
#include "hw_viewpointbuffer.h"
#include "hw_voxels.h"
//==========================================================================
//
//
//
//==========================================================================
void HWSprite::DrawSprite(HWDrawInfo* di, FRenderState& state, bool translucent)
{
bool additivefog = false;
bool foglayer = false;
if (translucent)
{
// The translucent pass requires special setup for the various modes.
// for special render styles brightmaps would not look good - especially for subtractive.
if (RenderStyle.BlendOp != STYLEOP_Add)
{
state.EnableBrightmap(false);
}
state.SetRenderStyle(RenderStyle);
state.SetTextureMode(RenderStyle);
state.AlphaFunc(Alpha_Greater, alphaThreshold);
if (RenderStyle.BlendOp == STYLEOP_Add && RenderStyle.DestAlpha == STYLEALPHA_One)
{
additivefog = true;
}
}
#if 0
if (dynlightindex == -1) // only set if we got no light buffer index. This covers all cases where sprite lighting is used.
{
float out[3] = {};
//di->GetDynSpriteLight(gl_light_sprites ? actor : nullptr, gl_light_particles ? particle : nullptr, out);
//state.SetDynLight(out[0], out[1], out[2]);
}
#endif
if (RenderStyle.Flags & STYLEF_FadeToBlack)
{
fade = 0;
additivefog = true;
}
if (RenderStyle.BlendOp == STYLEOP_RevSub || RenderStyle.BlendOp == STYLEOP_Sub)
{
if (!modelframe)
{
// non-black fog with subtractive style needs special treatment
if (fade != 0)
{
foglayer = true;
// Due to the two-layer approach we need to force an alpha test that lets everything pass
state.AlphaFunc(Alpha_Greater, 0);
}
}
else RenderStyle.BlendOp = STYLEOP_Fuzz; // subtractive with models is not going to work.
}
SetLightAndFog(di, state, fade, palette, shade, visibility, alpha);
if (modelframe == 0)
{
state.SetMaterial(texture, UF_Texture, 0, CLAMP_XY, TRANSLATION(Translation_Remap + curbasepal, palette), -1);
state.SetNormal(0, 0, 0);
if (screen->BuffersArePersistent())
{
CreateVertices(di);
}
state.SetLightIndex(-1);
state.Draw(DT_TriangleStrip, vertexindex, 4);
if (foglayer)
{
bool foggy = (GlobalMapFog || (fade & 0xffffff));
auto ShadeDiv = lookups.tables[palette].ShadeFactor;
if (ShadeDiv >= 1 / 1000.f && foggy)
{
// If we get here we know that we have colored fog and no fixed colormap.
float density = GlobalMapFog ? GlobalFogDensity : 350.f - Scale(numshades - shade, 150, numshades);
state.SetFog((GlobalMapFog) ? GlobalMapFog : fade, density * hw_density);
state.SetTextureMode(TM_FOGLAYER);
state.SetRenderStyle(STYLE_Translucent);
state.Draw(DT_TriangleStrip, vertexindex, 4);
state.SetTextureMode(TM_NORMAL);
}
}
}
else
{
FHWModelRenderer mr(state, dynlightindex);
if (modelframe < 0)
{
state.mModelMatrix = rotmat;
auto model = voxel->model;
state.SetDepthFunc(DF_LEqual);
state.EnableTexture(true);
model->BuildVertexBuffer(&mr);
bool mirrored = ((Sprite->cstat & CSTAT_SPRITE_XFLIP) != 0) ^ ((Sprite->cstat & CSTAT_SPRITE_YFLIP) != 0) ^ portalState.isMirrored();
mr.BeginDrawModel(RenderStyle, nullptr, rotmat, mirrored);
TArray<VSMatrix> a;
mr.SetupFrame(model, 0, 0, 0, a, 0);
model->RenderFrame(&mr, TexMan.GetGameTexture(model->GetPaletteTexture()), 0, 0, 0.f, TRANSLATION(Translation_Remap + curbasepal, palette), nullptr, a, 0);
mr.EndDrawModel(RenderStyle, nullptr);
state.SetDepthFunc(DF_Less);
state.SetVertexBuffer(screen->mVertexData);
}
else
{
//RenderModel(&renderer, x, y, z, modelframe, actor, di->Viewpoint.TicFrac);
}
state.SetObjectColor(0xffffffff);
state.SetVertexBuffer(screen->mVertexData);
}
if (translucent)
{
state.EnableBrightmap(true);
state.SetRenderStyle(STYLE_Translucent);
state.SetTextureMode(TM_NORMAL);
}
state.SetObjectColor(0xffffffff);
state.SetAddColor(0);
state.EnableTexture(true);
state.SetDynLight(0, 0, 0);
}
//==========================================================================
//
//
//
//==========================================================================
void HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp)
{
const auto& HWAngles = di->Viewpoint.HWAngles;
#if 0 // maybe later, it's a bit more tricky with Build.
// [BB] Billboard stuff
const bool drawWithXYBillboard = false;
const bool drawBillboardFacingCamera = false;
// [Nash] check for special sprite drawing modes
if (drawWithXYBillboard || drawBillboardFacingCamera)
{
// Compute center of sprite
float xcenter = (x1 + x2) * 0.5;
float ycenter = (y1 + y2) * 0.5;
float zcenter = (z1 + z2) * 0.5;
float xx = -xcenter + x;
float zz = -zcenter + z;
float yy = -ycenter + y;
Matrix3x4 mat;
mat.MakeIdentity();
mat.Translate(xcenter, zcenter, ycenter); // move to sprite center
// Order of rotations matters. Perform yaw rotation (Y, face camera) before pitch (X, tilt up/down).
if (drawBillboardFacingCamera)
{
// [CMB] Rotate relative to camera XY position, not just camera direction,
// which is nicer in VR
float xrel = xcenter - vp->X;
float yrel = ycenter - vp->Y;
float absAngleDeg = RAD2DEG(atan2(-yrel, xrel));
float counterRotationDeg = 270. - HWAngles.Yaw.Degrees; // counteracts existing sprite rotation
float relAngleDeg = counterRotationDeg + absAngleDeg;
mat.Rotate(0, 1, 0, relAngleDeg);
}
// [fgsfds] calculate yaw vectors
float yawvecX = 0, yawvecY = 0, rollDegrees = 0;
float angleRad = (270. - HWAngles.Yaw).Radians();
if (drawWithXYBillboard)
{
// Rotate the sprite about the vector starting at the center of the sprite
// triangle strip and with direction orthogonal to where the player is looking
// in the x/y plane.
mat.Rotate(-sin(angleRad), 0, cos(angleRad), -HWAngles.Pitch.Degrees);
}
mat.Translate(-xcenter, -zcenter, -ycenter); // retreat from sprite center
v[0] = mat * FVector3(x1, z1, y1);
v[1] = mat * FVector3(x2, z1, y2);
v[2] = mat * FVector3(x1, z2, y1);
v[3] = mat * FVector3(x2, z2, y2);
}
else // traditional "Y" billboard mode
#endif
{
v[0] = FVector3(x1, z1, y1);
v[1] = FVector3(x2, z1, y2);
v[2] = FVector3(x1, z2, y1);
v[3] = FVector3(x2, z2, y2);
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWSprite::CreateVertices(HWDrawInfo* di)
{
if (modelframe == 0)
{
FVector3 v[4];
CalculateVertices(di, v, &di->Viewpoint.Pos);
auto vert = screen->mVertexData->AllocVertices(4);
auto vp = vert.first;
vertexindex = vert.second;
vp[0].Set(v[0][0], v[0][1], v[0][2], ul, vt);
vp[1].Set(v[1][0], v[1][1], v[1][2], ur, vt);
vp[2].Set(v[2][0], v[2][1], v[2][2], ul, vb);
vp[3].Set(v[3][0], v[3][1], v[3][2], ur, vb);
}
}
//==========================================================================
//
//
//
//==========================================================================
inline void HWSprite::PutSprite(HWDrawInfo* di, bool translucent)
{
if (translucent && texture && (hw_int_useindexedcolortextures || !checkTranslucentReplacement(texture->GetID(), palette))) alphaThreshold = texture->alphaThreshold;
else alphaThreshold = 0;
/*
if (modelframe == 1 && gl_light_sprites)
{
hw_GetDynModelLight(actor, lightdata);
dynlightindex = screen->mLights->UploadLights(lightdata);
}
else*/
dynlightindex = -1;
rendered_sprites++;
vertexindex = -1;
if (!screen->BuffersArePersistent())
{
CreateVertices(di);
}
di->AddSprite(this, translucent);
}
//==========================================================================
//
//
//
//==========================================================================
void HWSprite::Process(HWDrawInfo* di, tspritetype* spr, sectortype* sector, int thruportal)
{
if (spr == nullptr)
return;
auto tex = TexMan.GetGameTexture(spr->spritetexture());
if (!tex || !tex->isValid()) return;
texture = tex;
Sprite = spr;
modelframe = 0;
dynlightindex = -1;
shade = spr->shade;
palette = spr->pal;
fade = lookups.getFade(sector->floorpal); // fog is per sector.
visibility = sectorVisibility(sector);
SetSpriteTranslucency(spr, alpha, RenderStyle);
x = spr->pos.X;
z = -spr->pos.Z;
y = -spr->pos.Y;
auto vp = di->Viewpoint;
if ((vp.Pos.XY() - DVector2(x, y)).LengthSquared() < 0.125) return;
if (modelframe == 0)
{
int flags = spr->cstat;
int tilenum = spr->picnum;
int xsize, ysize, tilexoff, tileyoff;
if (hw_hightile && TileFiles.tiledata[tilenum].hiofs.xsize)
{
xsize = TileFiles.tiledata[tilenum].hiofs.xsize;
ysize = TileFiles.tiledata[tilenum].hiofs.ysize;
tilexoff = TileFiles.tiledata[tilenum].hiofs.xoffs;
tileyoff = TileFiles.tiledata[tilenum].hiofs.yoffs;
}
else
{
xsize = (int)tex->GetDisplayWidth();
ysize = (int)tex->GetDisplayHeight();
tilexoff = (int)tex->GetDisplayLeftOffset();
tileyoff = (int)tex->GetDisplayTopOffset();
}
int heinum = 0; // tspriteGetSlope(tspr); // todo: slope sprites
if (heinum == 0)
{
tilexoff += spr->xoffset;
tileyoff += spr->yoffset;
}
// convert to render space.
float sx = (float)spr->scale.X * 0.8f; // weird Build fuckery. Face sprites are rendered at 80% width only.
float sy = (float)spr->scale.Y;
float width = xsize * sx;
float height = ysize * sy;
float xoff = tilexoff * sx;
float yoff = tileyoff * sy;
if (xsize & 1) xoff -= sx * 0.5f; // Odd xspans (taken from polymost as-is)
if (spr->cstat & CSTAT_SPRITE_YCENTER)
{
yoff -= height * 0.5f;
if (ysize & 1) yoff -= sy * 0.5f; // Odd yspans (taken from polymost as-is)
}
if (flags & CSTAT_SPRITE_XFLIP) xoff = -xoff;
//if (flags & CSTAT_SPRITE_YFLIP) yoff = -yoff; According to Polymost this must not be done.
ul = (spr->cstat & CSTAT_SPRITE_XFLIP) ? 0.f : 1.f;
ur = (spr->cstat & CSTAT_SPRITE_XFLIP) ? 1.f : 0.f;
vt = (spr->cstat & CSTAT_SPRITE_YFLIP) ? 0.f : 1.f;
vb = (spr->cstat & CSTAT_SPRITE_YFLIP) ? 1.f : 0.f;
if (tex->isHardwareCanvas()) std::swap(vt, vb);
float viewvecX = vp.ViewVector.X;
float viewvecY = vp.ViewVector.Y;
x = spr->pos.X;
y = -spr->pos.Y;
z = -spr->pos.Z;
x1 = x - viewvecY * (xoff - (width * 0.5f));
x2 = x - viewvecY * (xoff + (width * 0.5f));
y1 = y + viewvecX * (xoff - (width * 0.5f));
y2 = y + viewvecX * (xoff + (width * 0.5f));
z1 = z + yoff;
z2 = z + height + yoff;
if (z1 < z2)
{
// Make sure that z1 is the higher one. Some utilities expect it to be oriented this way.
std::swap(z1, z2);
std::swap(vt, vb);
}
}
else
{
x1 = x2 = x;
y1 = y2 = y;
z1 = z2 = z;
texture = nullptr;
}
depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin);
#if 0 // huh?
if ((texture && texture->GetTranslucency()) || (RenderStyle.Flags & STYLEF_RedIsAlpha) || (modelframe && spr->RenderStyle != DefaultRenderStyle()))
{
if (hw_styleflags == STYLEHW_Solid)
{
RenderStyle.SrcAlpha = STYLEALPHA_Src;
RenderStyle.DestAlpha = STYLEALPHA_InvSrc;
}
hw_styleflags = STYLEHW_NoAlphaTest;
}
#endif
PutSprite(di, true);
}
//==========================================================================
//
//
//
//==========================================================================
bool HWSprite::ProcessVoxel(HWDrawInfo* di, voxmodel_t* vox, tspritetype* spr, sectortype* sector, bool rotate)
{
Sprite = spr;
auto ownerActor = spr->ownerActor;
texture = nullptr;
modelframe = -1;
dynlightindex = -1;
shade = spr->shade;
palette = spr->pal;
fade = lookups.getFade(sector->floorpal); // fog is per sector.
visibility = sectorVisibility(sector);
voxel = vox;
auto ang = spr->Angles.Yaw + ownerActor->sprext.rot.Yaw;
if ((spr->clipdist & TSPR_MDLROTATE) || rotate)
{
int myclock = (PlayClock << 3) + MulScale(4 << 3, (int)di->Viewpoint.TicFrac, 16);
ang += DAngle::fromBuild(myclock);
}
if (!vox || (spr->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FLOOR) return false;
SetSpriteTranslucency(spr, alpha, RenderStyle);
FVector3 scalevec = { voxel->scale, voxel->scale, voxel->scale };
FVector3 translatevec = { 0, 0, voxel->zadd * voxel->scale };
float basescale = voxel->bscale;
float sprxscale = (float)spr->scale.X * 0.8f * basescale;
if ((spr->ownerActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_WALL)
{
sprxscale *= 1.25f;
auto rvec = ownerActor->sprext.rot.Yaw.ToVector();
translatevec.Y -= spr->xoffset * rvec.X;
translatevec.X += spr->xoffset * rvec.Y;
}
if (spr->cstat & CSTAT_SPRITE_YFLIP)
{
scalevec.Z = -scalevec.Z;
translatevec.Z = -translatevec.Z;
}
if (spr->cstat & CSTAT_SPRITE_XFLIP)
{
scalevec.X = -scalevec.X;
translatevec.X = -translatevec.X;
translatevec.Y = -translatevec.Y;
}
scalevec.X *= sprxscale;
translatevec.X *= sprxscale;
scalevec.Y *= sprxscale;
translatevec.Y *= sprxscale;
float sprzscale = (float)spr->scale.Y * basescale;
scalevec.Z *= sprzscale;
translatevec.Z *= sprzscale;
float zpos = (float)(spr->pos.Z + ownerActor->sprext.position_offset.Z);
float zscale = ((spr->cstat & CSTAT_SPRITE_YFLIP) && (spr->ownerActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != 0) ? -1.f : 1.f;
zpos -= (spr->yoffset * spr->scale.Y) * zscale * basescale;
x = (spr->pos.X + ownerActor->sprext.position_offset.X);
z = -zpos;
y = -(spr->pos.Y + ownerActor->sprext.position_offset.Y);
float zoff = voxel->siz.Z * .5f;
if (!(spr->cstat & CSTAT_SPRITE_YCENTER))
zoff += voxel->piv.Z;
else if ((spr->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != CSTAT_SPRITE_ALIGNMENT_SLAB)
{
zoff += voxel->piv.Z;
zoff -= voxel->siz.Z * .5f;
}
if (spr->cstat & CSTAT_SPRITE_YFLIP) zoff = voxel->siz.Z - zoff;
rotmat.loadIdentity();
rotmat.translate(x + translatevec.X, z - translatevec.Z, y - translatevec.Y);
rotmat.rotate(ang.Degrees() - 90., 0, 1, 0);
rotmat.scale(scalevec.X, scalevec.Z, scalevec.Y);
// Apply pivot last
rotmat.translate(-voxel->piv.X, zoff, voxel->piv.Y);
auto vp = di->Viewpoint;
depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin);
PutSprite(di, spriteHasTranslucency(Sprite));
return true;
}