Added ZSprites.

- Effectively similar to Actors, but without the excess.
- Can be created with either the `level` function or the static `Spawn` function in ZSprite.
- STAT_SPRITE belongs to ZSprites only; ZSprites cannot be moved out of, nor can anything else be moved in to, this statnum.

Misc:

- Fixed (Sprite)Offset taking roll into account.

Crediting phantombeta, RicardoLuis0 and RaveYard for assistance.
This commit is contained in:
Major Cooke 2023-09-23 21:48:00 -05:00 committed by Rachael Alexanderson
parent 3056786f38
commit b6b1b25035
15 changed files with 601 additions and 136 deletions

View file

@ -10,6 +10,7 @@ xx(Object)
xx(Actor)
xx(Class)
xx(Thinker)
xx(ZSprite)
xx(Crosshairs)
xx(Untranslated)

View file

@ -427,6 +427,8 @@ public:
DThinker *thinker = static_cast<DThinker*>(cls->CreateNew());
assert(thinker->IsKindOf(RUNTIME_CLASS(DThinker)));
thinker->ObjectFlags |= OF_JustSpawned;
if (thinker->IsKindOf(RUNTIME_CLASS(DZSprite))) // [MC] This absolutely must happen for this class!
statnum = STAT_SPRITE;
Thinkers.Link(thinker, statnum);
thinker->Level = this;
return thinker;
@ -665,7 +667,7 @@ public:
DSeqNode *SequenceListHead;
// [RH] particle globals
uint32_t OldestParticle; // [MC] Oldest particle for replacing with PS_REPLACE
uint32_t OldestParticle; // [MC] Oldest particle for replacing with SPF_REPLACE
uint32_t ActiveParticles;
uint32_t InactiveParticles;
TArray<particle_t> Particles;

View file

@ -39,6 +39,7 @@
#include "fcolormap.h"
#include "r_sky.h"
#include "p_terrain.h"
#include "p_effect.h"
#include "hwrenderer/data/buffers.h"
@ -1662,7 +1663,7 @@ struct subsector_t
int Index() const { return subsectornum; }
// 2: has one-sided walls
FPortalCoverage portalcoverage[2];
TArray<DZSprite *> sprites;
LightmapSurface *lightmap[2];
};

View file

@ -807,7 +807,12 @@ DEFINE_ACTION_FUNCTION_NATIVE(DThinker, ChangeStatNum, ChangeStatNum)
{
PARAM_SELF_PROLOGUE(DThinker);
PARAM_INT(stat);
ChangeStatNum(self, stat);
// do not allow ZScript to reposition thinkers in or out of particle ticking.
if (stat != STAT_SPRITE && !dynamic_cast<DZSprite*>(self))
{
ChangeStatNum(self, stat);
}
return 0;
}

View file

@ -1607,18 +1607,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnDebris)
// A_SpawnParticle
//
//===========================================================================
enum SPFflag
{
SPF_FULLBRIGHT = 1,
SPF_RELPOS = 1 << 1,
SPF_RELVEL = 1 << 2,
SPF_RELACCEL = 1 << 3,
SPF_RELANG = 1 << 4,
SPF_NOTIMEFREEZE = 1 << 5,
SPF_ROLL = 1 << 6,
SPF_REPLACE = 1 << 7,
SPF_NO_XY_BILLBOARD = 1 << 8,
};
DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticle)
{

View file

@ -49,6 +49,11 @@
#include "vm.h"
#include "actorinlines.h"
#include "g_game.h"
#include "serializer_doom.h"
#ifdef _MSC_VER
#pragma warning(disable: 6011) // dereference null pointer in thinker iterator
#endif
CVAR (Int, cl_rockettrails, 1, CVAR_ARCHIVE);
CVAR (Bool, r_rail_smartspiral, false, CVAR_ARCHIVE);
@ -202,9 +207,27 @@ void P_ClearParticles (FLevelLocals *Level)
// Group particles by subsectors. Because particles are always
// in motion, there is little benefit to caching this information
// from one frame to the next.
// [MC] ZSprites hitches a ride here
void P_FindParticleSubsectors (FLevelLocals *Level)
{
// [MC] Hitch a ride on particle subsectors since ZSprites are effectively using the same kind of system.
for (uint32_t i = 0; i < Level->subsectors.Size(); i++)
{
Level->subsectors[i].sprites.Clear();
}
// [MC] Not too happy about using an iterator for this but I can't think of another way to handle it.
// At least it's on its own statnum for maximum efficiency.
auto it = Level->GetThinkerIterator<DZSprite>(NAME_None, STAT_SPRITE);
DZSprite* sp;
while (sp = it.Next())
{
if (sp->sub == nullptr)
sp->sub = Level->PointInRenderSubsector(sp->Pos);
sp->sub->sprites.Push(sp);
}
// End ZSprite hitching. Now onto the particles.
if (Level->ParticlesInSubsec.Size() < Level->subsectors.Size())
{
Level->ParticlesInSubsec.Reserve (Level->subsectors.Size() - Level->ParticlesInSubsec.Size());
@ -271,7 +294,7 @@ void P_ThinkParticles (FLevelLocals *Level)
{
particle = &Level->Particles[i];
i = particle->tnext;
if (Level->isFrozen() && !(particle->flags &PT_NOTIMEFREEZE))
if (Level->isFrozen() && !(particle->flags &SPF_NOTIMEFREEZE))
{
prev = particle;
continue;
@ -305,7 +328,7 @@ void P_ThinkParticles (FLevelLocals *Level)
particle->Pos.Z += particle->Vel.Z;
particle->Vel += particle->Acc;
if(particle->flags & PT_DOROLL)
if(particle->flags & SPF_ROLL)
{
particle->Roll += particle->RollVel;
particle->RollVel += particle->RollAcc;
@ -334,19 +357,10 @@ void P_ThinkParticles (FLevelLocals *Level)
}
}
enum PSFlag
{
PS_FULLBRIGHT = 1,
PS_NOTIMEFREEZE = 1 << 5,
PS_ROLL = 1 << 6,
PS_REPLACE = 1 << 7,
PS_NO_XY_BILLBOARD = 1 << 8,
};
void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &vel, const DVector3 &accel, PalEntry color, double startalpha, int lifetime, double size,
double fadestep, double sizestep, int flags, FTextureID texture, ERenderStyle style, double startroll, double rollvel, double rollacc)
{
particle_t *particle = NewParticle(Level, !!(flags & PS_REPLACE));
particle_t *particle = NewParticle(Level, !!(flags & SPF_REPLACE));
if (particle)
{
@ -358,7 +372,7 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v
if (fadestep < 0) particle->fadestep = FADEFROMTTL(lifetime);
else particle->fadestep = float(fadestep);
particle->ttl = lifetime;
particle->bright = !!(flags & PS_FULLBRIGHT);
particle->bright = !!(flags & SPF_FULLBRIGHT);
particle->size = size;
particle->sizestep = sizestep;
particle->texture = texture;
@ -366,18 +380,7 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v
particle->Roll = startroll;
particle->RollVel = rollvel;
particle->RollAcc = rollacc;
if(flags & PS_NOTIMEFREEZE)
{
particle->flags |= PT_NOTIMEFREEZE;
}
if(flags & PS_ROLL)
{
particle->flags |= PT_DOROLL;
}
if(flags & PS_NO_XY_BILLBOARD)
{
particle->flags |= PT_NOXYBILLBOARD;
}
particle->flags = flags;
}
}
@ -979,3 +982,264 @@ void P_DisconnectEffect (AActor *actor)
p->size = 4;
}
}
//===========================================================================
//
// ZScript Sprite (DZSprite)
// Concept by Major Cooke
// Most code borrowed by Actor and particles above
//
//===========================================================================
DZSprite::DZSprite()
{
PT = {};
PT.sprite = this;
Pos = Vel = {0,0,0};
Offset = {0,0};
Scale = {1,1};
Roll = 0.0;
Alpha = 1.0;
LightLevel = -1;
Texture = FTextureID();
Style = STYLE_Normal;
Translation = Flags = 0;
sub = nullptr;
cursector = nullptr;
}
void DZSprite::CallPostBeginPlay()
{
PT.texture = Texture;
Super::CallPostBeginPlay();
}
void DZSprite::OnDestroy()
{
PT.alpha = 0.0; // stops all rendering.
spr = nullptr;
Super::OnDestroy();
}
DZSprite* DZSprite::NewZSprite(FLevelLocals* Level, PClass* type)
{
if (type == nullptr)
return nullptr;
else if (type->bAbstract)
{
Printf("Attempt to spawn an instance of abstract ZSprite class %s\n", type->TypeName.GetChars());
return nullptr;
}
else if (!type->IsDescendantOf(RUNTIME_CLASS(DZSprite)))
{
Printf("Attempt to spawn class not inherent to ZSprite: %s\n", type->TypeName.GetChars());
return nullptr;
}
DZSprite *zs = static_cast<DZSprite*>(Level->CreateThinker(type, STAT_SPRITE));
return zs;
}
static DZSprite* SpawnZSprite(FLevelLocals* Level, PClass* type)
{
return DZSprite::NewZSprite(Level, type);
}
DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, SpawnZSprite, SpawnZSprite)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_CLASS_NOT_NULL(type, DZSprite);
DZSprite* zs = SpawnZSprite(self, type);
ACTION_RETURN_OBJECT(zs);
}
// This runs just like Actor's, make sure to call Super.Tick() in ZScript.
void DZSprite::Tick()
{
if (ObjectFlags & OF_EuthanizeMe)
return;
// There won't be a standard particle for this, it's only for graphics.
if (!Texture.isValid())
{
Printf("No valid texture, destroyed");
Destroy();
return;
}
if (isFrozen())
return;
Prev = Pos;
PrevRoll = Roll;
// Handle crossing a line portal
DVector2 newxy = Level->GetPortalOffsetPosition(Pos.X, Pos.Y, Vel.X, Vel.Y);
Pos.X = newxy.X;
Pos.Y = newxy.Y;
Pos.Z += Vel.Z;
sub = Level->PointInRenderSubsector(Pos);
cursector = sub->sector;
// Handle crossing a sector portal.
if (!cursector->PortalBlocksMovement(sector_t::ceiling))
{
if (Pos.Z > cursector->GetPortalPlaneZ(sector_t::ceiling))
{
Pos += cursector->GetPortalDisplacement(sector_t::ceiling);
sub = nullptr;
cursector = nullptr;
}
}
else if (!cursector->PortalBlocksMovement(sector_t::floor))
{
if (Pos.Z < cursector->GetPortalPlaneZ(sector_t::floor))
{
Pos += cursector->GetPortalDisplacement(sector_t::floor);
sub = nullptr;
cursector = nullptr;
}
}
PT.color = 0xffffff;
PT.Pos = Pos;
PT.Vel = Vel;
PT.Roll = Roll;
PT.size = Scale.X;
PT.alpha = Alpha;
PT.texture = Texture;
PT.style = ERenderStyle(GetRenderStyle());
PT.flags = Flags;
PT.subsector = sub;
PT.sprite = this;
}
int DZSprite::GetLightLevel(sector_t* rendersector) const
{
int lightlevel = rendersector->GetSpriteLight();
if (bAddLightLevel)
{
lightlevel += LightLevel;
}
else if (LightLevel > -1)
{
lightlevel = LightLevel;
}
return lightlevel;
}
FVector3 DZSprite::InterpolatedPosition(double ticFrac) const
{
if (bDontInterpolate) return FVector3(Pos);
DVector3 proc = Prev + (ticFrac * (Pos - Prev));
return FVector3(proc);
}
float DZSprite::InterpolatedRoll(double ticFrac) const
{
if (bDontInterpolate) return Roll;
return float(PrevRoll + (Roll - PrevRoll) * ticFrac);
}
void DZSprite::SetTranslation(FName trname)
{
// There is no constant for the empty name...
if (trname.GetChars()[0] == 0)
{
// '' removes it
Translation = 0;
return;
}
int tnum = R_FindCustomTranslation(trname);
if (tnum >= 0)
{
Translation = tnum;
}
// silently ignore if the name does not exist, this would create some insane message spam otherwise.
}
DEFINE_ACTION_FUNCTION(DZSprite, SetTranslation)
{
PARAM_SELF_PROLOGUE(DZSprite);
PARAM_NAME(trans);
self->SetTranslation(trans);
return 0;
}
bool DZSprite::isFrozen()
{
return (Level->isFrozen() && !(Flags & SPF_NOTIMEFREEZE));
}
DEFINE_ACTION_FUNCTION(DZSprite, IsFrozen)
{
PARAM_SELF_PROLOGUE(DZSprite);
ACTION_RETURN_BOOL(self->isFrozen());
}
DEFINE_ACTION_FUNCTION(DZSprite, SetRenderStyle)
{
PARAM_SELF_PROLOGUE(DZSprite);
PARAM_INT(mode);
self->Style = ERenderStyle(mode);
return 0;
}
int DZSprite::GetRenderStyle()
{
for (unsigned i = 0; i < STYLE_Count; i++)
{
if (Style == LegacyRenderStyles[i]) return i;
}
return -1;
}
void DZSprite::Serialize(FSerializer& arc)
{
Super::Serialize(arc);
arc
("pos", Pos)
("vel", Vel)
("prev", Prev)
("scale", Scale)
("roll", Roll)
("offset", Offset)
("alpha", Alpha)
("texture", Texture)
("style", Style)
("translation", Translation)
("cursector", cursector)
("flipx", bXFlip)
("flipy", bYFlip)
("dontinterpolate", bDontInterpolate)
("addlightlevel", bAddLightLevel)
("flags", Flags);
}
IMPLEMENT_CLASS(DZSprite, false, false);
DEFINE_FIELD(DZSprite, Pos);
DEFINE_FIELD(DZSprite, Vel);
DEFINE_FIELD(DZSprite, Prev);
DEFINE_FIELD(DZSprite, Scale);
DEFINE_FIELD(DZSprite, Offset);
DEFINE_FIELD(DZSprite, Roll);
DEFINE_FIELD(DZSprite, Alpha);
DEFINE_FIELD(DZSprite, Texture);
DEFINE_FIELD(DZSprite, Translation);
DEFINE_FIELD(DZSprite, Flags);
DEFINE_FIELD(DZSprite, LightLevel);
DEFINE_FIELD(DZSprite, cursector);
DEFINE_FIELD(DZSprite, bXFlip);
DEFINE_FIELD(DZSprite, bYFlip);
DEFINE_FIELD(DZSprite, bDontInterpolate);
DEFINE_FIELD(DZSprite, bAddLightLevel);

View file

@ -36,6 +36,7 @@
#include "vectors.h"
#include "doomdef.h"
#include "renderstyle.h"
#include "dthinker.h"
enum
{
@ -52,11 +53,17 @@ struct FLevelLocals;
enum EParticleFlags
{
PT_NOTIMEFREEZE = 1,
PT_DOROLL = 1 << 1,
PT_NOXYBILLBOARD = 1 << 2,
SPF_FULLBRIGHT = 1,
SPF_RELPOS = 1 << 1,
SPF_RELVEL = 1 << 2,
SPF_RELACCEL = 1 << 3,
SPF_RELANG = 1 << 4,
SPF_NOTIMEFREEZE = 1 << 5,
SPF_ROLL = 1 << 6,
SPF_REPLACE = 1 << 7,
SPF_NO_XY_BILLBOARD = 1 << 8,
};
class DZSprite;
struct particle_t
{
DVector3 Pos;
@ -71,8 +78,9 @@ struct particle_t
ERenderStyle style;
double Roll, RollVel, RollAcc;
uint16_t tnext, snext, tprev;
uint8_t bright;
uint8_t flags;
bool bright;
uint16_t flags;
DZSprite *sprite;
};
const uint16_t NO_PARTICLE = 0xffff;
@ -130,3 +138,54 @@ void P_DrawSplash (FLevelLocals *Level, int count, const DVector3 &pos, DAngle a
void P_DrawSplash2 (FLevelLocals *Level, int count, const DVector3 &pos, DAngle angle, int updown, int kind);
void P_DisconnectEffect (AActor *actor);
//===========================================================================
//
// ZSprites
// by Major Cooke
// Credit to phantombeta, RicardoLuis0 & RaveYard for aid
//
//===========================================================================
class HWSprite;
class DZSprite : public DThinker
{
DECLARE_CLASS(DZSprite, DThinker);
public:
DVector3 Pos, Vel, Prev;
DVector2 Scale, Offset;
double Roll, PrevRoll, Alpha;
int16_t LightLevel;
FRenderStyle Style;
FTextureID Texture;
uint32_t Translation;
uint16_t Flags;
sector_t *cursector;
bool bXFlip, bYFlip, // flip the sprite on the x/y axis.
bDontInterpolate, // disable all interpolation
bAddLightLevel; // adds sector light level to 'LightLevel'
// internal only variables
subsector_t *sub;
particle_t PT;
HWSprite *spr; //in an effort to cache the result.
DZSprite();
void CallPostBeginPlay() override;
void OnDestroy() override;
static DZSprite* NewZSprite(FLevelLocals* Level, PClass* type);
void SetTranslation(FName trname);
int GetRenderStyle();
bool isFrozen();
int GetLightLevel(sector_t *rendersector) const;
FVector3 InterpolatedPosition(double ticFrac) const;
float InterpolatedRoll(double ticFrac) const;
void Tick() override;
void Serialize(FSerializer& arc) override;
};

View file

@ -69,6 +69,7 @@ enum
STAT_ACTORMOVER, // actor movers
STAT_SCRIPTS, // The ACS thinker. This is to ensure that it can't tick before all actors called PostBeginPlay
STAT_BOT, // Bot thinker
STAT_SPRITE, // ZSprite Thinker
};
#endif

View file

@ -597,6 +597,23 @@ void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector)
void HWDrawInfo::RenderParticles(subsector_t *sub, sector_t *front)
{
SetupSprite.Clock();
for (uint32_t i = 0; i < sub->sprites.Size(); i++)
{
DZSprite *sp = sub->sprites[i];
if (!sp || sp->ObjectFlags & OF_EuthanizeMe)
continue;
if (mClipPortal)
{
int clipres = mClipPortal->ClipPoint(sp->PT.Pos);
if (clipres == PClip_InFront) continue;
}
if (!sp->spr)
{
HWSprite sprite;
sp->spr = &sprite;
}
sp->spr->ProcessParticle(this, &sp->PT, front);
}
for (int i = Level->ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Level->Particles[i].snext)
{
if (mClipPortal)
@ -669,7 +686,7 @@ void HWDrawInfo::DoSubsector(subsector_t * sub)
}
// [RH] Add particles
if (gl_render_things && Level->ParticlesInSubsec[sub->Index()] != NO_PARTICLE)
if (gl_render_things && (sub->sprites.Size() > 0 || Level->ParticlesInSubsec[sub->Index()] != NO_PARTICLE))
{
if (multithread)
{

View file

@ -382,6 +382,7 @@ public:
float vt,vb;
float x1,y1,z1;
float x2,y2,z2;
float offx, offy;
float trans;
int dynlightindex;
@ -402,6 +403,7 @@ public:
void PutSprite(HWDrawInfo *di, bool translucent);
void Process(HWDrawInfo *di, AActor* thing,sector_t * sector, area_t in_area, int thruportal = false, bool isSpriteShadow = false);
void ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *sector);//, int shade, int fakeside)
void AdjustZSprite(HWDrawInfo *di, DZSprite *spr, sector_t *sector);
void DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent);
};

View file

@ -58,6 +58,7 @@
#include "hw_dynlightdata.h"
#include "hw_lightbuffer.h"
#include "hw_renderstate.h"
#include "quaternion.h"
extern TArray<spritedef_t> sprites;
extern TArray<spriteframe_t> SpriteFrames;
@ -101,9 +102,7 @@ void HWSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent)
bool additivefog = false;
bool foglayer = false;
int rel = fullbright ? 0 : getExtraLight();
auto &vp = di->Viewpoint;
const bool UseActorLight = (actor && actor->LightLevel > -1);
auto &vp = di->Viewpoint;
if (translucent)
{
@ -383,7 +382,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
}
// [BB] Billboard stuff
const bool drawWithXYBillboard = ((particle && gl_billboard_particles && !(particle->flags & PT_NOXYBILLBOARD)) || (!(actor && actor->renderflags & RF_FORCEYBILLBOARD)
const bool drawWithXYBillboard = ((particle && gl_billboard_particles && !(particle->flags & SPF_NO_XY_BILLBOARD)) || (!(actor && actor->renderflags & RF_FORCEYBILLBOARD)
//&& di->mViewActor != nullptr
&& (gl_billboard_mode == 1 || (actor && actor->renderflags & RF_FORCEXYBILLBOARD))));
@ -391,7 +390,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
// [Nash] has +ROLLSPRITE
const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE);
const bool drawRollParticle = (particle != nullptr && particle->flags & PT_DOROLL);
const bool drawRollParticle = (particle != nullptr && particle->flags & SPF_ROLL);
// [fgsfds] check sprite type mask
@ -399,7 +398,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
if (actor != nullptr) spritetype = actor->renderflags & RF_SPRITETYPEMASK;
// [Nash] is a flat sprite
const bool isFlatSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE || spritetype == RF_FLATSPRITE);
const bool isFlatSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE);
const bool useOffsets = (actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER);
// [Nash] check for special sprite drawing modes
@ -416,7 +415,20 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
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).
// [MC] Sprite offsets. These must be calculated separately in their own matrix,
// otherwise "face sprites" would cause some issues whenever enabled. We don't
// want those calculations here. Credit to PhantomBeta for this.
if (offx || offy)
{
FAngle zero = FAngle::fromDeg(0);
FQuaternion quat = FQuaternion::FromAngles(FAngle::fromDeg(270) - di->Viewpoint.HWAngles.Yaw, di->Viewpoint.HWAngles.Pitch, FAngle::fromDeg(0));
FVector3 sideVec = quat * FVector3(0, 1, 0);
FVector3 upVec = quat * FVector3(0, 0, 1);
FVector3 res = sideVec * offx + upVec * offy;
mat.Translate(res.X, res.Z, res.Y);
}
// Order of rotations matters. Perform yaw rotation (Y, face camera) before pitch (X, tilt up/down).
if (drawBillboardFacingCamera && !isFlatSprite)
{
// [CMB] Rotate relative to camera XY position, not just camera direction,
@ -431,18 +443,15 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
}
// [fgsfds] calculate yaw vectors
float yawvecX = 0, yawvecY = 0, rollDegrees = 0;
float rollDegrees = 0;
float angleRad = (FAngle::fromDeg(270.) - HWAngles.Yaw).Radians();
if (actor || drawRollParticle) rollDegrees = Angles.Roll.Degrees();
if (isFlatSprite)
{
yawvecX = Angles.Yaw.Cos();
yawvecY = Angles.Yaw.Sin();
}
// [fgsfds] Rotate the sprite about the sight vector (roll)
if (spritetype == RF_WALLSPRITE)
{
float yawvecX = Angles.Yaw.Cos();
float yawvecY = Angles.Yaw.Sin();
mat.Rotate(0, 1, 0, 0);
if (drawRollSpriteActor)
{
@ -470,6 +479,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp)
}
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);
@ -919,6 +929,9 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
if (!tex || !tex->isValid()) return;
auto& spi = tex->GetSpritePositioning(type == RF_FACESPRITE);
offx = (float)thing->SpriteOffset.X;
offy = (float)thing->SpriteOffset.Y;
vt = spi.GetSpriteVT();
vb = spi.GetSpriteVB();
if (thing->renderflags & RF_YFLIP) std::swap(vt, vb);
@ -941,7 +954,7 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
ur = spi.GetSpriteUL();
}
texture = TexMan.GetGameTexture(patch, false);
texture = tex;
if (!texture || !texture->isValid())
return;
@ -950,10 +963,9 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
r.Scale(sprscale.X, isSpriteShadow ? sprscale.Y * 0.15 : sprscale.Y);
float SpriteOffY = thing->SpriteOffset.Y;
float rightfac = -r.left - thing->SpriteOffset.X;
float rightfac = -r.left;
float leftfac = rightfac - r.width;
z1 = z - r.top - SpriteOffY;
z1 = z - r.top;
z2 = z1 - r.height;
float spriteheight = sprscale.Y * r.height;
@ -979,7 +991,7 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
}
case RF_FLATSPRITE:
{
float bottomfac = -r.top - SpriteOffY;
float bottomfac = -r.top;
float topfac = bottomfac - r.height;
x1 = x + leftfac;
@ -989,8 +1001,8 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
// [MC] Counteract in case of any potential problems. Tests so far haven't
// shown any outstanding issues but that doesn't mean they won't appear later
// when more features are added.
z1 += SpriteOffY;
z2 += SpriteOffY;
z1 += offy;
z2 += offy;
break;
}
case RF_WALLSPRITE:
@ -1245,11 +1257,27 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t
void HWSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *sector)//, int shade, int fakeside)
{
if (particle->alpha==0) return;
if (!particle || particle->alpha <= 0)
return;
lightlevel = hw_ClampLight(sector->GetSpriteLight());
DZSprite *spr = particle->sprite;
if (spr && spr->Texture.isNull())
return;
lightlevel = hw_ClampLight(spr ? spr->GetLightLevel(sector) : sector->GetSpriteLight());
foglevel = (uint8_t)clamp<short>(sector->lightlevel, 0, 255);
trans = particle->alpha;
OverrideShader = 0;
modelframe = nullptr;
texture = nullptr;
topclip = LARGE_VALUE;
bottomclip = -LARGE_VALUE;
index = 0;
actor = nullptr;
this->particle = particle;
fullbright = particle->bright;
if (di->isFullbrightScene())
{
Colormap.Clear();
@ -1284,8 +1312,6 @@ void HWSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *
Colormap.ClearColor();
}
trans=particle->alpha;
if(particle->style != STYLE_None)
{
RenderStyle = particle->style;
@ -1295,91 +1321,85 @@ void HWSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *
RenderStyle = STYLE_Translucent;
}
OverrideShader = 0;
ThingColor = particle->color;
ThingColor.a = 255;
const auto& vp = di->Viewpoint;
modelframe=nullptr;
texture=nullptr;
topclip = LARGE_VALUE;
bottomclip = -LARGE_VALUE;
index = 0;
bool has_texture = !particle->texture.isNull();
int particle_style = has_texture ? 2 : gl_particles_style; // Treat custom texture the same as smooth particles
// [BB] Load the texture for round or smooth particles
if (particle_style)
if (spr)
AdjustZSprite(di, spr, sector);
else
{
FTextureID lump;
if (particle_style == 1)
bool has_texture = !particle->texture.isNull();
int particle_style = has_texture ? 2 : gl_particles_style; // Treat custom texture the same as smooth particles
// [BB] Load the texture for round or smooth particles
if (particle_style)
{
lump = TexMan.glPart2;
}
else if (particle_style == 2)
{
lump = has_texture ? particle -> texture : TexMan.glPart;
}
else lump.SetNull();
FTextureID lump;
if (particle_style == 1)
{
lump = TexMan.glPart2;
}
else if (particle_style == 2)
{
lump = has_texture ? particle->texture : TexMan.glPart;
}
else lump.SetNull();
if (lump.isValid())
{
translation = NO_TRANSLATION;
//auto tex = TexMan.GetGameTexture(lump, false);
ul = 0;
ur = 1;
vt = 0;
vb = 1;
texture = TexMan.GetGameTexture(lump, true);
ul = 0;
ur = 1;
vt = 0;
vb = 1;
texture = TexMan.GetGameTexture(lump, true);
}
}
}
const auto &vp = di->Viewpoint;
double timefrac = vp.TicFrac;
if (paused || di->Level->isFrozen())
timefrac = 0.;
float xvf = (particle->Vel.X) * timefrac;
float yvf = (particle->Vel.Y) * timefrac;
float zvf = (particle->Vel.Z) * timefrac;
double timefrac = vp.TicFrac;
if (paused || di->Level->isFrozen())
timefrac = 0.;
float xvf = (particle->Vel.X) * timefrac;
float yvf = (particle->Vel.Y) * timefrac;
float zvf = (particle->Vel.Z) * timefrac;
x = float(particle->Pos.X) + xvf;
y = float(particle->Pos.Y) + yvf;
z = float(particle->Pos.Z) + zvf;
x = float(particle->Pos.X) + xvf;
y = float(particle->Pos.Y) + yvf;
z = float(particle->Pos.Z) + zvf;
if(particle->flags & PT_DOROLL)
{
float rvf = (particle->RollVel) * timefrac;
Angles.Roll = TAngle<double>::fromDeg(particle->Roll + rvf);
}
if(particle->flags & SPF_ROLL)
{
float rvf = (particle->RollVel) * timefrac;
Angles.Roll = TAngle<double>::fromDeg(particle->Roll + rvf);
}
float factor;
if (particle_style == 1) factor = 1.3f / 7.f;
else if (particle_style == 2) factor = 2.5f / 7.f;
else factor = 1 / 7.f;
float scalefac=particle->size * factor;
float factor;
if (particle_style == 1) factor = 1.3f / 7.f;
else if (particle_style == 2) factor = 2.5f / 7.f;
else factor = 1 / 7.f;
float scalefac=particle->size * factor;
float viewvecX = vp.ViewVector.X;
float viewvecY = vp.ViewVector.Y;
float viewvecX = vp.ViewVector.X * scalefac;
float viewvecY = vp.ViewVector.Y * scalefac;
x1=x+viewvecY*scalefac;
x2=x-viewvecY*scalefac;
y1=y-viewvecX*scalefac;
y2=y+viewvecX*scalefac;
z1=z-scalefac;
z2=z+scalefac;
x1=x+viewvecY;
x2=x-viewvecY;
y1=y-viewvecX;
y2=y+viewvecX;
z1=z-scalefac;
z2=z+scalefac;
depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin);
actor=nullptr;
this->particle=particle;
fullbright = !!particle->bright;
depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin);
// [BB] Translucent particles have to be rendered without the alpha test.
if (particle_style != 2 && trans>=1.0f-FLT_EPSILON) hw_styleflags = STYLEHW_Solid;
else hw_styleflags = STYLEHW_NoAlphaTest;
// [BB] Translucent particles have to be rendered without the alpha test.
if (particle_style != 2 && trans>=1.0f-FLT_EPSILON) hw_styleflags = STYLEHW_Solid;
else hw_styleflags = STYLEHW_NoAlphaTest;
}
if (sector->e->XFloor.lightlist.Size() != 0 && !di->isFullbrightScene() && !fullbright)
lightlist = &sector->e->XFloor.lightlist;
@ -1390,6 +1410,63 @@ void HWSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *
rendered_sprites++;
}
// [MC] ZSprites are to be rendered akin to actor sprites.
void HWSprite::AdjustZSprite(HWDrawInfo* di, DZSprite* spr, sector_t* sector)
{
translation = spr->Translation;
texture = TexMan.GetGameTexture(spr->Texture, true);
const auto& vp = di->Viewpoint;
double timefrac = vp.TicFrac;
if (paused || spr->isFrozen() || spr->bDontInterpolate)
timefrac = 0.;
FVector3 interp = spr->InterpolatedPosition(timefrac);
x = interp.X;
y = interp.Y;
z = interp.Z;
offx = (float)spr->Offset.X;
offy = (float)spr->Offset.Y;
if (spr->Flags & SPF_ROLL)
Angles.Roll = TAngle<double>::fromDeg(spr->InterpolatedRoll(timefrac));
auto& spi = texture->GetSpritePositioning(0);
vt = spi.GetSpriteVT();
vb = spi.GetSpriteVB();
ul = spi.GetSpriteUR();
ur = spi.GetSpriteUL();
auto r = spi.GetSpriteRect();
r.Scale(spr->Scale.X, spr->Scale.Y);
if (spr->bXFlip)
{
std::swap(ul,ur);
r.left = -r.width - r.left; // mirror the sprite's x-offset
}
if (spr->bYFlip) std::swap(vt,vb);
float viewvecX = vp.ViewVector.X;
float viewvecY = vp.ViewVector.Y;
float rightfac = -r.left;
float leftfac = rightfac - r.width;
x1 = x - viewvecY * leftfac;
x2 = x - viewvecY * rightfac;
y1 = y + viewvecX * leftfac;
y2 = y + viewvecX * rightfac;
z1 = z - r.top;
z2 = z1 - r.height;
depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin);
// [BB] Translucent particles have to be rendered without the alpha test.
hw_styleflags = STYLEHW_NoAlphaTest;
}
//==========================================================================
//
//

View file

@ -926,6 +926,11 @@ static DObject *BuiltinNewDoom(PClass *cls, int outerside, int backwardscompatib
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
return nullptr;
}
if (cls->IsDescendantOf(NAME_ZSprite)) // Same for ZSprites.
{
ThrowAbortException(X_OTHER, "Cannot create ZSprite or inheriting classes with 'new'. Use 'ZSprite.Spawn' instead.");
return nullptr;
}
if ((vm_warnthinkercreation || !backwardscompatible) && cls->IsDescendantOf(NAME_Thinker))
{
// This must output a diagnostic warning

View file

@ -42,6 +42,7 @@ version "4.12"
#include "zscript/destructible.zs"
#include "zscript/level_postprocessor.zs"
#include "zscript/level_compatibility.zs"
#include "zscript/zsprite.zs"
#include "zscript/actors/actor.zs"
#include "zscript/actors/checks.zs"

View file

@ -545,6 +545,7 @@ struct LevelLocals native
native String GetEpisodeName();
native void SpawnParticle(FSpawnParticleParams p);
native ZSprite SpawnZSprite(Class<ZSprite> type);
}
// a few values of this need to be readable by the play code.

View file

@ -0,0 +1,41 @@
Class ZSprite : Thinker native
{
native Vector3 Pos, Vel, Prev;
native Vector2 Scale, Offset;
native double Roll, Alpha;
native TextureID Texture;
native uint Translation;
native uint16 Flags;
native int16 LightLevel;
native bool bXFlip, bYFlip,
bDontInterpolate,
bAddLightLevel;
native Sector CurSector; // can be null!
native void SetTranslation(Name trans);
native void SetRenderStyle(int mode); // see ERenderStyle
native bool IsFrozen();
static ZSprite Spawn(Class<ZSprite> type, TextureID tex, Vector3 pos, Vector3 vel, double alpha = 1.0, int flags = 0,
double roll = 0.0, Vector2 scale = (1,1), Vector2 offset = (0,0), int style = STYLE_Normal, int trans = 0)
{
if (!Level) return null;
let p = level.SpawnZSprite(type);
if (p)
{
p.Texture = tex;
p.Pos = pos;
p.Vel = vel;
p.Alpha = alpha;
p.Roll = roll;
p.Scale = scale;
p.Offset = offset;
p.SetRenderStyle(style);
p.Translation = trans;
p.Flags = flags;
}
return p;
}
}