diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index e76186f21a..116765dc10 100644 --- a/src/common/engine/namedef.h +++ b/src/common/engine/namedef.h @@ -10,6 +10,7 @@ xx(Object) xx(Actor) xx(Class) xx(Thinker) +xx(ZSprite) xx(Crosshairs) xx(Untranslated) diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 58aac058f6..4b6dbddc39 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -427,6 +427,8 @@ public: DThinker *thinker = static_cast(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 Particles; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index d0b58ce9c0..015aace07d 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -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 sprites; LightmapSurface *lightmap[2]; }; diff --git a/src/playsim/dthinker.cpp b/src/playsim/dthinker.cpp index 86ef7918cc..123ec1d326 100644 --- a/src/playsim/dthinker.cpp +++ b/src/playsim/dthinker.cpp @@ -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(self)) + { + ChangeStatNum(self, stat); + } return 0; } diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 4715fced5c..1b89dcc6cf 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -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) { diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 6f92752571..2f9d03781f 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -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(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(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); diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index 3b542c19ec..4ae9c87131 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -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; + +}; diff --git a/src/playsim/statnums.h b/src/playsim/statnums.h index 7450ccbddc..8c84292413 100644 --- a/src/playsim/statnums.h +++ b/src/playsim/statnums.h @@ -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 \ No newline at end of file diff --git a/src/rendering/hwrenderer/scene/hw_bsp.cpp b/src/rendering/hwrenderer/scene/hw_bsp.cpp index c79e341106..59d1784fed 100644 --- a/src/rendering/hwrenderer/scene/hw_bsp.cpp +++ b/src/rendering/hwrenderer/scene/hw_bsp.cpp @@ -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) { diff --git a/src/rendering/hwrenderer/scene/hw_drawstructs.h b/src/rendering/hwrenderer/scene/hw_drawstructs.h index e86d4b57a6..060e5eb04f 100644 --- a/src/rendering/hwrenderer/scene/hw_drawstructs.h +++ b/src/rendering/hwrenderer/scene/hw_drawstructs.h @@ -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); }; diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index d8470925f9..204425ee73 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -58,6 +58,7 @@ #include "hw_dynlightdata.h" #include "hw_lightbuffer.h" #include "hw_renderstate.h" +#include "quaternion.h" extern TArray sprites; extern TArray 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(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::fromDeg(particle->Roll + rvf); - } + if(particle->flags & SPF_ROLL) + { + float rvf = (particle->RollVel) * timefrac; + Angles.Roll = TAngle::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 = §or->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::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; +} + //========================================================================== // // diff --git a/src/scripting/backend/codegen_doom.cpp b/src/scripting/backend/codegen_doom.cpp index 7aa0474c81..189cf5047b 100644 --- a/src/scripting/backend/codegen_doom.cpp +++ b/src/scripting/backend/codegen_doom.cpp @@ -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 diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 04331c388b..3b6334cd1b 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -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" diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 8bdc59b54a..765f3546ed 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -545,6 +545,7 @@ struct LevelLocals native native String GetEpisodeName(); native void SpawnParticle(FSpawnParticleParams p); + native ZSprite SpawnZSprite(Class type); } // a few values of this need to be readable by the play code. diff --git a/wadsrc/static/zscript/zsprite.zs b/wadsrc/static/zscript/zsprite.zs new file mode 100644 index 0000000000..24360b6de9 --- /dev/null +++ b/wadsrc/static/zscript/zsprite.zs @@ -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 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; + } +}