// //--------------------------------------------------------------------------- // // Copyright(C) 2004-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 3 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/ // //-------------------------------------------------------------------------- // /* ** a_dynlight.cpp ** Implements actors representing dynamic lights (hardware independent) ** ** ** all functions marked with [TS] are licensed under **--------------------------------------------------------------------------- ** Copyright 2003 Timothy Stump ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "c_dispatch.h" #include "thingdef.h" #include "r_utility.h" #include "doomstat.h" #include "serializer.h" #include "g_levellocals.h" #include "a_dynlight.h" #include "actorinlines.h" #include "memarena.h" static FMemArena DynLightArena(sizeof(FDynamicLight) * 200); static TArray FreeList; static FRandom randLight; CUSTOM_CVAR (Bool, gl_lights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { for (auto Level : AllLevels()) { if (self) Level->RecreateAllAttachedLights(); else Level->DeleteAllAttachedLights(); } } //========================================================================== // //========================================================================== DEFINE_CLASS_PROPERTY(type, S, DynamicLight) { PROP_STRING_PARM(str, 0); static const char * ltype_names[]={ "Point","Pulse","Flicker","Sector","RandomFlicker", "ColorPulse", "ColorFlicker", "RandomColorFlicker", nullptr}; static const int ltype_values[]={ PointLight, PulseLight, FlickerLight, SectorLight, RandomFlickerLight, ColorPulseLight, ColorFlickerLight, RandomColorFlickerLight }; int style = MatchString(str, ltype_names); if (style < 0) I_Error("Unknown light type '%s'", str); defaults->IntVar(NAME_lighttype) = ltype_values[style]; } //========================================================================== // // // //========================================================================== static FDynamicLight *GetLight(FLevelLocals *Level) { FDynamicLight *ret; if (FreeList.Size()) { FreeList.Pop(ret); } else ret = (FDynamicLight*)DynLightArena.Alloc(sizeof(FDynamicLight)); memset(ret, 0, sizeof(*ret)); ret->next = Level->lights; Level->lights = ret; if (ret->next) ret->next->prev = ret; ret->visibletoplayer = true; ret->mShadowmapIndex = 1024; ret->Level = Level; ret->Pos.X = -10000000; // not a valid coordinate. return ret; } //========================================================================== // // Attaches a dynamic light descriptor to a dynamic light actor. // Owned lights do not use this function. // //========================================================================== void AttachLight(AActor *self) { auto light = GetLight(self->Level); light->pSpotInnerAngle = &self->AngleVar(NAME_SpotInnerAngle); light->pSpotOuterAngle = &self->AngleVar(NAME_SpotOuterAngle); light->pPitch = &self->Angles.Pitch; light->pLightFlags = (LightFlags*)&self->IntVar(NAME_lightflags); light->pArgs = self->args; light->specialf1 = DAngle(double(self->SpawnAngle)).Normalized360().Degrees; light->Sector = self->Sector; light->target = self; light->mShadowmapIndex = 1024; light->m_active = false; light->visibletoplayer = true; light->lighttype = (uint8_t)self->IntVar(NAME_lighttype); self->AttachedLights.Push(light); } DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, AttachLight, AttachLight) { PARAM_SELF_PROLOGUE(AActor); AttachLight(self); return 0; } //========================================================================== // // // //========================================================================== void ActivateLight(AActor *self) { for (auto l : self->AttachedLights) l->Activate(); } DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, ActivateLight, ActivateLight) { PARAM_SELF_PROLOGUE(AActor); ActivateLight(self); return 0; } //========================================================================== // // // //========================================================================== void DeactivateLight(AActor *self) { for (auto l : self->AttachedLights) l->Deactivate(); } DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, DeactivateLight, DeactivateLight) { PARAM_SELF_PROLOGUE(AActor); DeactivateLight(self); return 0; } //========================================================================== // // // //========================================================================== static void SetOffset(AActor *self, double x, double y, double z) { for (auto l : self->AttachedLights) { l->SetOffset(DVector3(x, y, z)); } } DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, SetOffset, SetOffset) { PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(x); PARAM_FLOAT(y); PARAM_FLOAT(z); SetOffset(self, x, y, z); return 0; } //========================================================================== // // // //========================================================================== void FDynamicLight::ReleaseLight() { assert(prev != nullptr || this == Level->lights); if (prev != nullptr) prev->next = next; else Level->lights = next; if (next != nullptr) next->prev = prev; next = prev = nullptr; FreeList.Push(this); } //========================================================================== // // [TS] // //========================================================================== void FDynamicLight::Activate() { m_active = true; m_currentRadius = float(GetIntensity()); m_tickCount = 0; if (lighttype == PulseLight) { float pulseTime = float(specialf1 / TICRATE); m_lastUpdate = Level->maptime; if (!swapped) m_cycler.SetParams(float(GetSecondaryIntensity()), float(GetIntensity()), pulseTime); else m_cycler.SetParams(float(GetIntensity()), float(GetSecondaryIntensity()), pulseTime); m_cycler.ShouldCycle(true); m_cycler.SetCycleType(CYCLE_Sin); m_currentRadius = float(m_cycler.GetVal()); } if (m_currentRadius <= 0) m_currentRadius = 1; } //========================================================================== // // [TS] // //========================================================================== void FDynamicLight::Tick() { if (!target) { // How did we get here? :? ReleaseLight(); return; } if (owned) { if (!target->state) { Deactivate(); return; } if (target->flags & MF_UNMORPHED) { m_active = false; return; } visibletoplayer = target->IsVisibleToPlayer(); // cache this value for the renderer to speed up calculations. } // Don't bother if the light won't be shown if (!IsActive()) return; // I am doing this with a type field so that I can dynamically alter the type of light // without having to create or maintain multiple objects. switch(lighttype) { case PulseLight: { float diff = (Level->maptime - m_lastUpdate) / (float)TICRATE; m_lastUpdate = Level->maptime; m_cycler.Update(diff); m_currentRadius = float(m_cycler.GetVal()); break; } case FlickerLight: { int rnd = randLight(360); m_currentRadius = float((rnd < int(specialf1))? GetIntensity() : GetSecondaryIntensity()); break; } case RandomFlickerLight: { int flickerRange = GetSecondaryIntensity() - GetIntensity(); float amt = randLight() / 255.f; if (m_tickCount > specialf1) { m_tickCount = 0; } if (m_tickCount++ == 0 || m_currentRadius > GetSecondaryIntensity()) { m_currentRadius = float(GetIntensity() + (amt * flickerRange)); } break; } #if 0 // These need some more work elsewhere case ColorFlickerLight: { int rnd = randLight(); float pct = specialf1/360.f; m_currentRadius = m_Radius[rnd >= pct * 255]; break; } case RandomColorFlickerLight: { int flickerRange = GetSecondaryIntensity() - GetIntensity(); float amt = randLight() / 255.f; m_tickCount++; if (m_tickCount > specialf1) { m_currentRadius = GetIntensity() + (amt * flickerRange); m_tickCount = 0; } break; } #endif case SectorLight: { float intensity; float scale = GetIntensity() / 8.f; if (scale == 0.f) scale = 1.f; intensity = Sector->lightlevel * scale; intensity = clamp(intensity, 0.f, 255.f); m_currentRadius = intensity; break; } case PointLight: m_currentRadius = float(GetIntensity()); break; } if (m_currentRadius <= 0) m_currentRadius = 1; UpdateLocation(); } //========================================================================== // // // //========================================================================== void FDynamicLight::UpdateLocation() { double oldx= X(); double oldy= Y(); float oldradius = radius; if (IsActive()) { AActor *target = this->target; // perform the read barrier only once. // Offset is calculated in relation to the owning actor. DAngle angle = target->Angles.Yaw; double s = angle.Sin(); double c = angle.Cos(); Pos = target->Vec3Offset(m_off.X * c + m_off.Y * s, m_off.X * s - m_off.Y * c, m_off.Z + target->GetBobOffset()); Sector = target->subsector->sector; // Get the render sector. target->Sector is the sector according to play logic. // Some z-coordinate fudging to prevent the light from getting too close to the floor or ceiling planes. With proper attenuation this would render them invisible. // A distance of 5 is needed so that the light's effect doesn't become too small. if (Z() < target->floorz + 5.) Pos.Z = target->floorz + 5.; else if (Z() > target->ceilingz - 5.) Pos.Z = target->ceilingz - 5.; // The radius being used here is always the maximum possible with the // current settings. This avoids constant relinking of flickering lights float intensity; if (lighttype == FlickerLight || lighttype == RandomFlickerLight || lighttype == PulseLight) { intensity = float(MAX(GetIntensity(), GetSecondaryIntensity())); } else { intensity = m_currentRadius; } radius = intensity * 2.0f; if (radius < m_currentRadius * 2) radius = m_currentRadius * 2; if (X() != oldx || Y() != oldy || radius != oldradius) { //Update the light lists LinkLight(); } } } //============================================================================= // // These have been copied from the secnode code and modified for the light links // // P_AddSecnode() searches the current list to see if this sector is // already there. If not, it adds a sector node at the head of the list of // sectors this object appears in. This is called when creating a list of // nodes that will get linked in later. Returns a pointer to the new node. // //============================================================================= FLightNode * AddLightNode(FLightNode ** thread, void * linkto, FDynamicLight * light, FLightNode *& nextnode) { FLightNode * node; node = nextnode; while (node) { if (node->targ==linkto) // Already have a node for this sector? { node->lightsource = light; // Yes. Setting m_thing says 'keep it'. return(nextnode); } node = node->nextTarget; } // Couldn't find an existing node for this sector. Add one at the head // of the list. node = new FLightNode; node->targ = linkto; node->lightsource = light; node->prevTarget = &nextnode; node->nextTarget = nextnode; if (nextnode) nextnode->prevTarget = &node->nextTarget; // Add new node at head of sector thread starting at s->touching_thinglist node->prevLight = thread; node->nextLight = *thread; if (node->nextLight) node->nextLight->prevLight=&node->nextLight; *thread = node; return(node); } //============================================================================= // // P_DelSecnode() deletes a sector node from the list of // sectors this object appears in. Returns a pointer to the next node // on the linked list, or nullptr. // //============================================================================= static FLightNode * DeleteLightNode(FLightNode * node) { FLightNode * tn; // next node on thing thread if (node) { *node->prevTarget = node->nextTarget; if (node->nextTarget) node->nextTarget->prevTarget=node->prevTarget; *node->prevLight = node->nextLight; if (node->nextLight) node->nextLight->prevLight=node->prevLight; // Return this node to the freelist tn=node->nextTarget; delete node; return(tn); } return(nullptr); } // phares 3/13/98 //========================================================================== // // Gets the light's distance to a line // //========================================================================== double FDynamicLight::DistToSeg(const DVector3 &pos, vertex_t *start, vertex_t *end) { double u, px, py; double seg_dx = end->fX() - start->fX(); double seg_dy = end->fY() - start->fY(); double seg_length_sq = seg_dx * seg_dx + seg_dy * seg_dy; u = (((pos.X - start->fX()) * seg_dx) + (pos.Y - start->fY()) * seg_dy) / seg_length_sq; if (u < 0.) u = 0.; // clamp the test point to the line segment else if (u > 1.) u = 1.; px = start->fX() + (u * seg_dx); py = start->fY() + (u * seg_dy); px -= pos.X; py -= pos.Y; return (px*px) + (py*py); } //========================================================================== // // Collect all touched sidedefs and subsectors // to sidedefs and sector parts. // //========================================================================== struct LightLinkEntry { FSection *sect; DVector3 pos; }; static TArray collected_ss; void FDynamicLight::CollectWithinRadius(const DVector3 &opos, FSection *section, float radius) { if (!section) return; collected_ss.Clear(); collected_ss.Push({ section, opos }); section->validcount = dl_validcount; bool hitonesidedback = false; for (unsigned i = 0; i < collected_ss.Size(); i++) { auto &pos = collected_ss[i].pos; section = collected_ss[i].sect; touching_sector = AddLightNode(§ion->lighthead, section, this, touching_sector); auto processSide = [&](side_t *sidedef, const vertex_t *v1, const vertex_t *v2) { auto linedef = sidedef->linedef; if (linedef && linedef->validcount != ::validcount) { // light is in front of the seg if ((pos.Y - v1->fY()) * (v2->fX() - v1->fX()) + (v1->fX() - pos.X) * (v2->fY() - v1->fY()) <= 0) { linedef->validcount = ::validcount; touching_sides = AddLightNode(&sidedef->lighthead, sidedef, this, touching_sides); } else if (linedef->sidedef[0] == sidedef && linedef->sidedef[1] == nullptr) { hitonesidedback = true; } } if (linedef) { FLinePortal *port = linedef->getPortal(); if (port && port->mType == PORTT_LINKED) { line_t *other = port->mDestination; if (other->validcount != ::validcount) { subsector_t *othersub = Level->PointInRenderSubsector(other->v1->fPos() + other->Delta() / 2); FSection *othersect = othersub->section; if (othersect->validcount != ::validcount) { othersect->validcount = ::validcount; collected_ss.Push({ othersect, PosRelative(other->frontsector->PortalGroup) }); } } } } }; for (auto &segment : section->segments) { // check distance from x/y to seg and if within radius add this seg and, if present the opposing subsector (lather/rinse/repeat) // If out of range we do not need to bother with this seg. if (DistToSeg(pos, segment.start, segment.end) <= radius) { auto sidedef = segment.sidedef; if (sidedef) { processSide(sidedef, segment.start, segment.end); } auto partner = segment.partner; if (partner) { FSection *sect = partner->section; if (sect != nullptr && sect->validcount != dl_validcount) { sect->validcount = dl_validcount; collected_ss.Push({ sect, pos }); } } } } for (auto side : section->sides) { auto v1 = side->V1(), v2 = side->V2(); if (DistToSeg(pos, v1, v2) <= radius) { processSide(side, v1, v2); } } sector_t *sec = section->sector; if (!sec->PortalBlocksSight(sector_t::ceiling)) { line_t *other = section->segments[0].sidedef->linedef; if (sec->GetPortalPlaneZ(sector_t::ceiling) < Z() + radius) { DVector2 refpos = other->v1->fPos() + other->Delta() / 2 + sec->GetPortalDisplacement(sector_t::ceiling); subsector_t *othersub = Level->PointInRenderSubsector(refpos); FSection *othersect = othersub->section; if (othersect->validcount != dl_validcount) { othersect->validcount = dl_validcount; collected_ss.Push({ othersect, PosRelative(othersub->sector->PortalGroup) }); } } } if (!sec->PortalBlocksSight(sector_t::floor)) { line_t *other = section->segments[0].sidedef->linedef; if (sec->GetPortalPlaneZ(sector_t::floor) > Z() - radius) { DVector2 refpos = other->v1->fPos() + other->Delta() / 2 + sec->GetPortalDisplacement(sector_t::floor); subsector_t *othersub = Level->PointInRenderSubsector(refpos); FSection *othersect = othersub->section; if (othersect->validcount != dl_validcount) { othersect->validcount = dl_validcount; collected_ss.Push({ othersect, PosRelative(othersub->sector->PortalGroup) }); } } } } shadowmapped = hitonesidedback && !DontShadowmap(); } //========================================================================== // // Link the light into the world // //========================================================================== void FDynamicLight::LinkLight() { // mark the old light nodes FLightNode * node; node = touching_sides; while (node) { node->lightsource = nullptr; node = node->nextTarget; } node = touching_sector; while (node) { node->lightsource = nullptr; node = node->nextTarget; } if (radius>0) { // passing in radius*radius allows us to do a distance check without any calls to sqrt FSection *sect = Level->PointInRenderSubsector(Pos)->section; dl_validcount++; ::validcount++; CollectWithinRadius(Pos, sect, float(radius*radius)); } // Now delete any nodes that won't be used. These are the ones where // m_thing is still nullptr. node = touching_sides; while (node) { if (node->lightsource == nullptr) { node = DeleteLightNode(node); } else node = node->nextTarget; } node = touching_sector; while (node) { if (node->lightsource == nullptr) { node = DeleteLightNode(node); } else node = node->nextTarget; } } //========================================================================== // // Deletes the link lists // //========================================================================== void FDynamicLight::UnlinkLight () { while (touching_sides) touching_sides = DeleteLightNode(touching_sides); while (touching_sector) touching_sector = DeleteLightNode(touching_sector); shadowmapped = false; } //========================================================================== // // // //========================================================================== void AActor::AttachLight(unsigned int count, const FLightDefaults *lightdef) { FDynamicLight *light; if (count < AttachedLights.Size()) { light = AttachedLights[count]; assert(light != nullptr); } else { light = GetLight(Level); light->SetActor(this, true); AttachedLights.Push(light); } lightdef->ApplyProperties(light); } //========================================================================== // // per-state light adjustment // //========================================================================== extern TArray StateLights; void AActor::SetDynamicLights() { TArray & LightAssociations = GetInfo()->LightAssociations; unsigned int count = 0; if (state == nullptr) return; if (LightAssociations.Size() > 0) { unsigned int i; for (i = 0; i < LightAssociations.Size(); i++) { if (LightAssociations[i]->Sprite() == sprite && (LightAssociations[i]->Frame()==frame || LightAssociations[i]->Frame()==-1)) { AttachLight(count++, LightAssociations[i]->Light()); } } } if (count == 0 && state->Light > 0) { for(int i= state->Light; StateLights[i] != nullptr; i++) { if (StateLights[i] != (FLightDefaults*)-1) { AttachLight(count++, StateLights[i]); } } } for(;countDeactivate(); } } //========================================================================== // // // //========================================================================== void AActor::DeleteAttachedLights() { for (auto l : AttachedLights) { l->UnlinkLight(); l->ReleaseLight(); } AttachedLights.Clear(); } //========================================================================== // // This is called before saving the game // //========================================================================== void FLevelLocals::DeleteAllAttachedLights() { auto it = GetThinkerIterator(); AActor * a; while ((a=it.Next())) { a->DeleteAttachedLights(); } } //========================================================================== // // // //========================================================================== void FLevelLocals::RecreateAllAttachedLights() { auto it = GetThinkerIterator(); AActor * a; while ((a=it.Next())) { if (a->IsKindOf(NAME_DynamicLight)) { ::AttachLight(a); ::ActivateLight(a); } else { a->SetDynamicLights(); } } } //========================================================================== // // CCMDs // //========================================================================== CCMD(listlights) { int walls, sectors; int allwalls=0, allsectors=0, allsubsecs = 0; int i=0, shadowcount = 0; FDynamicLight * dl; for (auto Level : AllLevels()) { Printf("Lights for %s\n", Level->MapName.GetChars()); for (dl = Level->lights; dl; dl = dl->next) { walls=0; sectors=0; Printf("%s at (%f, %f, %f), color = 0x%02x%02x%02x, radius = %f %s %s", dl->target->GetClass()->TypeName.GetChars(), dl->X(), dl->Y(), dl->Z(), dl->GetRed(), dl->GetGreen(), dl->GetBlue(), dl->radius, dl->IsAttenuated()? "attenuated" : "", dl->shadowmapped? "shadowmapped" : ""); i++; shadowcount += dl->shadowmapped; if (dl->target) { FTextureID spr = sprites[dl->target->sprite].GetSpriteFrame(dl->target->frame, 0, 0., nullptr); Printf(", frame = %s ", TexMan.GetTexture(spr)->GetName().GetChars()); } FLightNode * node; node=dl->touching_sides; while (node) { walls++; allwalls++; node = node->nextTarget; } node = dl->touching_sector; while (node) { allsectors++; sectors++; node = node->nextTarget; } Printf("- %d walls, %d sectors\n", walls, sectors); } Printf("%i dynamic lights, %d shadowmapped, %d walls, %d sectors\n\n\n", i, shadowcount, allwalls, allsectors); } }