render: added "r_sprite_lighting_type" variable to control sprite lighting with light...
[k8vavoom.git] / source / render / r_light.cpp
blobdbc9ffd392c32f66a3a30e45981e8644a94f69d8
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../psim/p_entity.h"
28 #include "../psim/p_levelinfo.h"
29 #include "../psim/p_player.h"
30 #include "../client/client.h"
31 #include "r_local.h"
34 // ////////////////////////////////////////////////////////////////////////// //
35 VCvarI r_light_mode("r_light_mode", "1", "Lighting mode (0:Vavoom; 1:Doom; 2:DoomBanded)?", CVAR_Archive);
36 VCvarF r_doomlight_min("r_doomlight_min", "0", "Doom Lighting mode minimum lighting, [0..31]?", CVAR_Archive);
37 VCvarF r_light_globvis("r_light_globvis", "8", "Dark lighting mode visibility.", CVAR_Archive|CVAR_NoShadow);
38 static VCvarI r_sprite_lighting_type("r_sprite_lighting_type", "1", "Sprite lighting mode (0:Boom; 1:Advanced)?", CVAR_Archive);
40 VCvarB r_darken("r_darken", true, "Darken level to better match original Doom?", CVAR_Archive);
41 VCvarI r_ambient_min("r_ambient_min", "0", "Minimal ambient light.", CVAR_Archive|CVAR_NoShadow);
42 VCvarB r_allow_ambient("r_allow_ambient", true, "Allow ambient lights?", CVAR_Archive|CVAR_NoShadow);
43 VCvarB r_dynamic_lights("r_dynamic_lights", true, "Allow dynamic lights?", CVAR_Archive);
44 VCvarB r_dynamic_clip("r_dynamic_clip", true, "Clip dynamic lights?", CVAR_Archive);
45 VCvarB r_static_lights("r_static_lights", true, "Allow static lights?", CVAR_Archive);
46 VCvarB r_light_opt_shadow("r_light_opt_shadow", false, "Check if light can potentially cast a shadow.", CVAR_Archive|CVAR_NoShadow);
47 VCvarF r_light_filter_dynamic_coeff("r_light_filter_dynamic_coeff", "0.2", "How close dynamic lights should be to be filtered out?\n(0.2-0.4 is usually ok).", CVAR_Archive);
48 VCvarB r_allow_dynamic_light_filter("r_allow_dynamic_light_filter", true, "Allow filtering of dynamic lights?", CVAR_Archive);
50 // currently affects only advanced renderer
51 VCvarI r_light_shadow_min_proj_dimension("r_light_shadow_min_proj_dimension", "112", "Do not render shadows for lights smaller than this screen size.", CVAR_Archive);
53 VCvarB r_shadowmaps("r_shadowmaps", false, "Use shadowmaps instead of shadow volumes?", /*CVAR_PreInit|*/CVAR_Archive|CVAR_NoShadow);
55 static VCvarB r_dynamic_light_better_vis_check("r_dynamic_light_better_vis_check", true, "Do better (but slower) dynlight visibility checking on spawn?", CVAR_Archive|CVAR_NoShadow);
57 extern VCvarB r_glow_flat;
58 extern VCvarB r_lmap_recalc_moved_static;
60 static VCvarB r_lmap_texture_check_static("r_lmap_texture_check_static", true, "Check textures of two-sided lines in lightmap tracer?", CVAR_Archive);
61 static VCvarB r_lmap_texture_check_dynamic("r_lmap_texture_check_dynamic", true, "Check textures of two-sided lines in lightmap tracer?", CVAR_Archive);
62 static VCvarI r_lmap_texture_check_radius_dynamic("r_lmap_texture_check_radius_dynamic", "300", "Disable texture check for dynamic lights with radius lower than this.", CVAR_Archive);
65 static TFrustum frustumDLight;
66 static TFrustumParam fpDLight;
69 #define RL_CLEAR_DLIGHT(_dl) do { \
70 (_dl)->radius = 0; \
71 (_dl)->flags = 0; \
72 /* lights with lightid aren't added to the ownership map, because we may have many of them for one owner */ \
73 /* note that they will not be deleted when their owner is going away */ \
74 if ((_dl)->ownerUId && !(_dl)->lightid) dlowners.del((_dl)->ownerUId); \
75 (_dl)->lightid = 0; \
76 (_dl)->ownerUId = 0; \
77 } while (0)
80 static VClass *eexCls = nullptr;
81 static VField *mflFld = nullptr;
82 #if 0
83 static VClass *invCls = nullptr;
84 static VField *invFld = nullptr;
85 #endif
88 //==========================================================================
90 // VRenderLevelShared::IsInInventory
92 //==========================================================================
93 bool VRenderLevelShared::IsInInventory (VEntity *owner, vuint32 invUId) const {
94 if (!owner || !invUId) return false;
95 if (owner->ServerUId == invUId) return true;
97 if (!eexCls) {
98 eexCls = VThinker::FindClassChecked("EntityEx");
99 mflFld = VThinker::FindTypedField(eexCls, "bMeIsMuzzleFlash", TYPE_Bool);
100 #if 0
101 invCls = VThinker::FindClassChecked("Inventory");
102 invFld = VThinker::FindTypedField(eexCls, "Inventory", TYPE_Reference, invCls);
103 #endif
106 VEntity *ee = Level->GetEntityBySUId(invUId);
107 //if (!ee->IsA(invCls)) return false;
108 return (ee && (ee->Owner == owner || mflFld->GetBool(ee)));
110 #if 0
111 // loop over inventory
112 for (VEntity *inv = (VEntity *)invFld->GetObjectValue(owner); inv; inv = (VEntity *)invFld->GetObjectValue(inv)) {
113 //GCon->Logf(NAME_Debug, "owner:%s(%u); invUId=%u; inv=%s(%u) with uid=%u", owner->GetClass()->GetName(), owner->GetUniqueId(), invUId, inv->GetClass()->GetName(), inv->GetUniqueId(), inv->ServerUId);
114 if (inv->ServerUId == invUId && inv->Owner == owner) return true;
117 return false;
118 #endif
122 //==========================================================================
124 // calcLightPoint
126 // raise a point to 1/2 of the height
128 //==========================================================================
129 static inline TVec calcLightPoint (const TVec &pt, float height) noexcept {
130 return TVec(pt.x, pt.y, pt.z+max2(0.0f, height)*0.5f);
134 //==========================================================================
136 // VRenderLevelShared::CalcScreenLightDimensions
138 // use drawer's vieworg, so it can be called only when rendering a scene
139 // it's not exact!
140 // returns `false` if the light is invisible (or too small, with radius < 8)
141 // in this case, `w`, and `h` are zeroes
142 // both `w` and `h` can be `nullptr`
144 //==========================================================================
145 bool VRenderLevelShared::CalcScreenLightDimensions (const TVec &LightPos, const float LightRadius, int *w, int *h) noexcept {
146 if (w) *w = 0;
147 if (h) *h = 0;
148 if (!Drawer || !isFiniteF(LightRadius) || LightRadius < 8.0f) return false;
149 // just in case
150 if (!Drawer->vpmats.vport.isValid()) return false;
152 // transform into world coords
153 TVec inworld = Drawer->vpmats.toWorld(LightPos);
155 //GCon->Logf(NAME_Debug, "LightPos=(%g,%g,%g); LightRadius=%g; wpos=(%g,%g,%g)", LightPos.x, LightPos.y, LightPos.z, LightRadius, inworld.x, inworld.y, inworld.z);
157 // the thing that should not be (completely behind)
158 if (inworld.z-LightRadius > -1.0f) return false;
160 // create light bbox
161 float bbox[6];
162 bbox[0+0] = inworld.x-LightRadius;
163 bbox[0+1] = inworld.y-LightRadius;
164 bbox[0+2] = inworld.z-LightRadius;
166 bbox[3+0] = inworld.x+LightRadius;
167 bbox[3+1] = inworld.y+LightRadius;
168 bbox[3+2] = min2(-1.0f, inworld.z+LightRadius); // clamp to znear
170 const int scrx0 = Drawer->vpmats.vport.x0;
171 const int scry0 = Drawer->vpmats.vport.y0;
172 const int scrx1 = Drawer->vpmats.vport.getX1();
173 const int scry1 = Drawer->vpmats.vport.getY1();
175 int minx = scrx1+64, miny = scry1+64;
176 int maxx = -(scrx0-64), maxy = -(scry0-64);
178 // transform points, get min and max
179 for (unsigned f = 0; f < MAX_BBOX3D_CORNERS; ++f) {
180 int winx, winy;
181 Drawer->vpmats.project(GetBBox3DCorner(f, bbox), &winx, &winy);
183 if (minx > winx) minx = winx;
184 if (miny > winy) miny = winy;
185 if (maxx < winx) maxx = winx;
186 if (maxy < winy) maxy = winy;
189 if (minx > scrx1 || miny > scry1 || maxx < scrx0 || maxy < scry0) return false;
191 minx = midval(scrx0, minx, scrx1);
192 miny = midval(scry0, miny, scry1);
193 maxx = midval(scrx0, maxx, scrx1);
194 maxy = midval(scry0, maxy, scry1);
196 //GCon->Logf(" LightRadius=%f; (%d,%d)-(%d,%d)", LightRadius, minx, miny, maxx, maxy);
197 const int wdt = maxx-minx+1;
198 const int hgt = maxy-miny+1;
199 //GCon->Logf(" LightRadius=%f; (%dx%d)", LightRadius, wdt, hgt);
201 if (wdt < 1 || hgt < 1) return false;
203 if (w) *w = wdt;
204 if (h) *h = hgt;
206 return true;
210 //==========================================================================
212 // VRenderLevelShared::CalcBBox3DScreenPosition
214 // does very primitive Z clipping
215 // returns `false` if fully out of screen
217 //==========================================================================
218 bool VRenderLevelShared::CalcBBox3DScreenPosition (const float bbox3d[6], int *x0, int *y0, int *x1, int *y1) noexcept {
219 // just in case
220 if (!Drawer->vpmats.vport.isValid()) return false;
222 TVec vbbox[8]; // transformed bbox points
223 bool seenOkZ = false;
224 // transform into world coords
225 for (unsigned f = 0; f < MAX_BBOX3D_CORNERS; ++f) {
226 vbbox[f] = Drawer->vpmats.toWorld(GetBBox3DCorner(f, bbox3d));
227 if (vbbox[f].z > -1.0f) vbbox[f].z = -1.0f; else seenOkZ = true;
229 if (!seenOkZ) return false;
231 const int scrx0 = Drawer->vpmats.vport.x0;
232 const int scry0 = Drawer->vpmats.vport.y0;
233 const int scrx1 = Drawer->vpmats.vport.getX1();
234 const int scry1 = Drawer->vpmats.vport.getY1();
236 int minx = scrx1+64, miny = scry1+64;
237 int maxx = -(scrx0-64), maxy = -(scry0-64);
239 // transform points, get min and max
240 for (unsigned f = 0; f < 8; ++f) {
241 int winx, winy;
242 Drawer->vpmats.project(vbbox[f], &winx, &winy);
244 if (minx > winx) minx = winx;
245 if (miny > winy) miny = winy;
246 if (maxx < winx) maxx = winx;
247 if (maxy < winy) maxy = winy;
250 if (minx > scrx1 || miny > scry1 || maxx < scrx0 || maxy < scry0) return false;
252 minx = midval(scrx0, minx, scrx1);
253 miny = midval(scry0, miny, scry1);
254 maxx = midval(scrx0, maxx, scrx1);
255 maxy = midval(scry0, maxy, scry1);
257 *x0 = minx;
258 *y0 = miny;
259 *x1 = maxx;
260 *y1 = maxy;
261 return true;
265 //==========================================================================
267 // VRenderLevelShared::RefilterStaticLights
269 //==========================================================================
270 void VRenderLevelShared::RefilterStaticLights () {
274 //==========================================================================
276 // VRenderLevelShared::ResetStaticLights
278 //==========================================================================
279 void VRenderLevelShared::ResetStaticLights () {
280 StOwners.reset();
281 Lights.reset();
282 //GCon->Log(NAME_Debug, "VRenderLevelShared::ResetStaticLights");
283 // clear touching subs
284 for (auto &&ssl : SubStaticLights) {
285 ssl.touchedStatic.reset();
286 ssl.invalidateFrame = 0;
291 //==========================================================================
293 // VRenderLevelShared::AddStaticLightRGB
295 //==========================================================================
296 void VRenderLevelShared::AddStaticLightRGB (vuint32 OwnerUId, const VLightParams &lpar, const vuint32 flags) {
297 staticLightsFiltered = false;
298 light_t &L = Lights.Alloc();
299 L.origin = lpar.Origin;
300 L.radius = lpar.Radius;
301 L.color = lpar.Color;
302 //L.dynowner = nullptr;
303 L.ownerUId = OwnerUId;
304 L.leafnum = (int)(ptrdiff_t)(Level->PointInSubsector(lpar.Origin)-Level->Subsectors);
305 L.active = true;
306 L.coneDirection = lpar.coneDirection;
307 L.coneAngle = lpar.coneAngle;
308 L.levelSector = lpar.LevelSector;
309 L.levelScale = lpar.LevelScale;
310 L.flags = flags;
311 if (lpar.LevelSector) {
312 L.sectorLightLevel = lpar.LevelSector->params.lightlevel;
313 const float intensity = clampval(L.sectorLightLevel*(fabsf(lpar.LevelScale)*0.125f), 0.0f, 255.0f);
314 L.radius = VLevelInfo::GZSizeToRadius(intensity, (lpar.LevelScale < 0.0f), 2.0f);
316 if (OwnerUId) {
317 auto osp = StOwners.get(OwnerUId);
318 if (osp) Lights[*osp].ownerUId = 0;
319 StOwners.put(OwnerUId, Lights.length()-1);
321 CalcStaticLightTouchingSubs(Lights.length()-1, L);
322 //GCon->Logf(NAME_Debug, "VRenderLevelShared::AddStaticLightRGB: count=%d", Lights.length());
326 //==========================================================================
328 // VRenderLevelShared::MoveStaticLightByOwner
330 //==========================================================================
331 void VRenderLevelShared::MoveStaticLightByOwner (vuint32 OwnerUId, const TVec &origin) {
332 if (!OwnerUId) return;
333 auto stp = StOwners.get(OwnerUId);
334 if (!stp) return;
335 light_t &sl = Lights[*stp];
336 if (fabsf(sl.origin.x-origin.x) <= 4.0f &&
337 fabsf(sl.origin.y-origin.y) <= 4.0f &&
338 fabsf(sl.origin.z-origin.z) <= 4.0f)
340 return;
342 //GCon->Logf(NAME_Debug, "moving static light #%d (owner uid=%u)", *stp, sl.ownerUId);
343 //if (sl.origin == origin) return;
344 if (sl.active && r_lmap_recalc_moved_static) InvalidateStaticLightmaps(sl.origin, sl.radius, false, (sl.flags&dlight_t::NoGeoClip));
345 sl.origin = origin;
346 sl.leafnum = (int)(ptrdiff_t)(Level->PointInSubsector(sl.origin)-Level->Subsectors);
347 CalcStaticLightTouchingSubs(*stp, sl);
348 if (sl.active && r_lmap_recalc_moved_static) InvalidateStaticLightmaps(sl.origin, sl.radius, true, (sl.flags&dlight_t::NoGeoClip));
352 //==========================================================================
354 // VRenderLevelShared::RemoveStaticLightByIndex
356 //==========================================================================
357 void VRenderLevelShared::RemoveStaticLightByIndex (int slidx) {
358 if (slidx < 0 || slidx >= Lights.length()) return;
359 light_t *sl = Lights.ptr()+slidx;
360 //GCon->Logf(NAME_Debug, "removing static light #%d (owner uid=%u)", slidx, sl->ownerUId);
361 if (sl->active) {
362 if (r_lmap_recalc_moved_static) InvalidateStaticLightmaps(sl->origin, sl->radius, false, (sl->flags&dlight_t::NoGeoClip));
363 sl->active = false;
365 if (sl->ownerUId) {
366 StOwners.del(sl->ownerUId);
367 sl->ownerUId = 0;
368 CalcStaticLightTouchingSubs(slidx, *sl);
373 //==========================================================================
375 // VRenderLevelShared::RemoveStaticLightByOwner
377 //==========================================================================
378 void VRenderLevelShared::RemoveStaticLightByOwner (vuint32 OwnerUId) {
379 if (!OwnerUId) return;
380 auto stp = StOwners.get(OwnerUId);
381 if (!stp) return;
382 //GCon->Logf(NAME_Debug, "removing static light with owner uid %u (%d)", OwnerUId, *stp);
383 RemoveStaticLightByIndex(*stp);
387 //==========================================================================
389 // VRenderLevelShared::InvalidateStaticLightmaps
391 //==========================================================================
392 void VRenderLevelShared::InvalidateStaticLightmaps (const TVec &/*org*/, float /*radius*/, bool /*relight*/, bool /*noGeoClip*/) {
396 //==========================================================================
398 // VRenderLevelShared::ClearReferences
400 //==========================================================================
401 void VRenderLevelShared::ClearReferences () {
402 // no need to do anything here, the renderer will be notified about thinker add/remove events
403 // dynlights
405 dlight_t *l = DLights;
406 for (unsigned i = 0; i < MAX_DLIGHTS; ++i, ++l) {
407 if (l->die < Level->Time || l->radius < 1.0f) {
408 RL_CLEAR_DLIGHT(l);
409 continue;
411 if (l->Owner && l->Owner->IsRefToCleanup()) {
412 RL_CLEAR_DLIGHT(l);
419 //==========================================================================
421 // VRenderLevelShared::MarkLights
423 //==========================================================================
425 void VRenderLevelShared::MarkLights (dlight_t *light, vuint32 bit, int bspnum, int lleafnum) {
426 if (BSPIDX_IS_LEAF(bspnum)) {
427 const int num = (bspnum != -1 ? BSPIDX_LEAF_SUBSECTOR(bspnum) : 0);
428 subsector_t *ss = &Level->Subsectors[num];
430 if (ss->dlightframe != currDLightFrame) {
431 ss->dlightbits = bit;
432 ss->dlightframe = currDLightFrame;
433 } else {
434 ss->dlightbits |= bit;
436 } else {
437 node_t *node = &Level->Nodes[bspnum];
438 const float dist = node->PointDistance(light->origin);
439 if (dist > -light->radius+light->minlight) MarkLights(light, bit, node->children[0], lleafnum);
440 if (dist < light->radius-light->minlight) MarkLights(light, bit, node->children[1], lleafnum);
446 //==========================================================================
448 // VRenderLevelShared::PushDlights
450 //==========================================================================
451 void VRenderLevelShared::PushDlights () {
452 //???:if (GGameInfo->IsPaused() || (Level->LevelInfo->LevelInfoFlags2&VLevelInfo::LIF2_Frozen)) return;
453 IncDLightFrameCount();
455 if (!r_dynamic_lights) return;
457 // lightvis params
458 LitCalcBBox = false;
459 CurrLightCalcUnstuck = (r_shadowmaps.asBool() && Drawer->CanRenderShadowMaps() && r_shadowmap_fix_light_dist);
461 dlight_t *l = DLights;
462 for (unsigned i = 0; i < MAX_DLIGHTS; ++i, ++l) {
463 if (l->radius < 1.0f || l->die < Level->Time) {
464 dlinfo[i].needTrace = 0;
465 dlinfo[i].leafnum = -1;
466 continue;
468 l->origin = l->origOrigin;
469 //if (l->Owner && l->Owner->IsA(VEntity::StaticClass())) l->origin += ((VEntity *)l->Owner)->GetDrawDelta();
470 if (l->ownerUId) {
472 auto ownpp = suid2ent.get(l->ownerUId);
473 if (ownpp) l->origin += (*ownpp)->GetDrawDelta();
475 VEntity *own = Level->GetEntityBySUId(l->ownerUId);
476 if (own) l->origin += own->GetDrawDelta();
478 if (dlinfo[i].leafnum < 0) dlinfo[i].leafnum = (int)(ptrdiff_t)(Level->PointInSubsector(l->origin)-Level->Subsectors);
479 //dlinfo[i].needTrace = (r_dynamic_clip && Level->NeedProperLightTraceAt(l->origin, l->radius) ? 1 : -1);
480 //MarkLights(l, 1U<<i, Level->NumNodes-1, dlinfo[i].leafnum);
481 //FIXME: this has one frame latency; meh for now
482 LitCalcBBox = false; // we don't need any lists
483 CurrLightNoGeoClip = (l->flags&dlight_t::NoGeoClip);
484 if (CalcPointLightVis(l->origin, l->radius, (int)i)) {
485 dlinfo[i].needTrace = (doShadows ? 1 : -1);
486 } else {
487 // this one is invisible
488 dlinfo[i].needTrace = 0;
489 dlinfo[i].leafnum = -1;
495 //==========================================================================
497 // VRenderLevelShared::AllocDlight
499 //==========================================================================
500 dlight_t *VRenderLevelShared::AllocDlight (VThinker *Owner, const TVec &lorg, float radius, int lightid) {
501 dlight_t *dlowner = nullptr;
502 dlight_t *dldying = nullptr;
503 dlight_t *dlreplace = nullptr;
504 dlight_t *dlbestdist = nullptr;
506 if (radius <= 0.0f) radius = 0.0f; else if (radius < 2.0f) return nullptr; // ignore too small lights
507 if (lightid < 0) lightid = 0;
509 float bestdist = (lorg-cl->ViewOrg).lengthSquared();
511 // even if filtering is disabled, filter them anyway
512 const float coeff = (r_allow_dynamic_light_filter.asBool() ? clampval(r_light_filter_dynamic_coeff.asFloat(), 0.1f, 1.0f) : 0.02f);
514 float radsq = (radius < 1.0f ? 64*64 : radius*radius*coeff);
515 if (radsq < 32.0f*32.0f) radsq = 32.0f*32.0f;
516 const float radsqhalf = radsq*0.25f;
518 // if this is player's dlight, never drop it
519 //TODO: check for Voodoo Dolls?
520 bool isPlr = false;
521 if (Owner && Owner->IsA(VEntity::StaticClass())) {
522 isPlr = ((VEntity *)Owner)->IsPlayer();
525 int leafnum = -1;
527 if (!isPlr) {
528 // if the light is behind a view, drop it if it is further than the light radius
529 if (bestdist >= (radius > 0.0f ? radius*radius : 64.0f*64.0f)) {
530 if (fpDLight.needUpdate(cl->ViewOrg, cl->ViewAngles)) {
531 fpDLight.setup(cl->ViewOrg, cl->ViewAngles);
532 // this also drops too far-away lights
533 frustumDLight.setup(clip_base, fpDLight, true, GetLightMaxDistDef());
535 if (!frustumDLight.checkSphere(lorg, (radius > 0.0f ? radius : 64.0f))) {
536 //GCon->Logf(" DROPPED; radius=%f; dist=%f", radius, sqrtf(bestdist));
537 return nullptr;
542 // look for any free slot (or free one if necessary)
543 dlight_t *dl;
544 bool skipVisCheck = false;
546 // first try to find owned light to replace
547 if (Owner) {
548 if (lightid == 0) {
549 auto idxp = dlowners.get(Owner->ServerUId);
550 if (idxp) {
551 dlowner = &DLights[*idxp];
552 vassert(dlowner->ownerUId == Owner->ServerUId);
554 } else {
555 //FIXME: make this faster!
556 dl = DLights;
557 for (int i = 0; i < MAX_DLIGHTS; ++i, ++dl) {
558 if (dl->ownerUId == Owner->ServerUId && dl->lightid == lightid /*&& dl->die >= Level->Time && dl->radius > 0.0f*/) {
559 dlowner = dl;
560 break;
566 // look for any free slot (or free one if necessary)
567 if (!dlowner) {
568 dl = DLights;
569 for (int i = 0; i < MAX_DLIGHTS; ++i, ++dl) {
570 // remove dead lights (why not?)
571 if (dl->die < Level->Time) dl->radius = 0.0f;
572 // unused light?
573 if (dl->radius < 2.0f) {
574 RL_CLEAR_DLIGHT(dl);
575 if (!dldying) dldying = dl;
576 continue;
578 // don't replace player's lights
579 if (dl->flags&dlight_t::PlayerLight) continue;
580 // replace furthest light
581 const float dist = (dl->origin-cl->ViewOrg).lengthSquared();
582 if (dist > bestdist) {
583 bestdist = dist;
584 dlbestdist = dl;
586 // check if we already have dynamic light around new origin
587 if (!isPlr) {
588 const float dd = (dl->origin-lorg).lengthSquared();
589 if (dd <= 6.0f*6.0f) {
590 if (radius > 0 && dl->radius >= radius) return nullptr;
591 dlreplace = dl;
592 skipVisCheck = true; // it is so near that we don't need to do any checks
593 break; // stop searching, we have the perfect candidate
594 } else if (dd < radsqhalf) {
595 // if existing light radius is greater than a new radius, drop new light, 'cause
596 // we have too much lights around one point (prolly due to several things at one place)
597 if (radius > 0.0f && dl->radius >= radius) return nullptr;
598 // otherwise, replace this light
599 dlreplace = dl;
600 skipVisCheck = true; // it is so near that we don't need to do any checks (i hope)
601 // keep checking, we may find a better candidate
607 if (dlowner) {
608 // remove replaced light
609 //if (dlreplace && dlreplace != dlowner) memset((void *)dlreplace, 0, sizeof(*dlreplace));
610 vassert(dlowner->ownerUId == Owner->ServerUId);
611 dl = dlowner;
612 } else {
613 dl = dlreplace;
614 if (!dl) {
615 dl = dldying;
616 if (!dl) {
617 dl = dlbestdist;
618 if (!dl) return nullptr;
623 // floodfill visibility check
624 if (!skipVisCheck && !isPlr && /*!IsShadowVolumeRenderer() &&*/ r_dynamic_light_better_vis_check) {
625 if (leafnum < 0) leafnum = (int)(ptrdiff_t)(Level->PointInSubsector(lorg)-Level->Subsectors);
626 if (!IsBspVisSector(leafnum) && !CheckBSPVisibilityBox(lorg, (radius > 0.0f ? radius : 64.0f), &Level->Subsectors[leafnum])) {
627 //GCon->Logf("DYNAMIC DROP: visibility check");
628 return nullptr;
632 // tagged lights are not in the map
633 if (dl->ownerUId && !dl->lightid) dlowners.del(dl->ownerUId);
635 // clean new light, and return it
636 memset((void *)dl, 0, sizeof(*dl));
637 dl->ownerUId = (Owner ? Owner->ServerUId : 0);
638 dl->origin = lorg;
639 dl->radius = radius;
640 dl->type = DLTYPE_Point;
641 dl->lightid = lightid;
642 if (isPlr) dl->flags |= dlight_t::PlayerLight;
644 dlinfo[(ptrdiff_t)(dl-DLights)].leafnum = leafnum;
646 // tagged lights are not in the map
647 if (!lightid && dl->ownerUId) dlowners.put(dl->ownerUId, (vuint32)(ptrdiff_t)(dl-&DLights[0]));
649 dl->origOrigin = lorg;
650 if (Owner && Owner->IsA(VEntity::StaticClass())) {
651 dl->origin += ((VEntity *)Owner)->GetDrawDelta();
654 return dl;
658 //==========================================================================
660 // VRenderLevelShared::FindDlightById
662 //==========================================================================
663 dlight_t *VRenderLevelShared::FindDlightById (int lightid) {
664 if (lightid <= 0) return nullptr;
665 dlight_t *dl = DLights;
666 for (int i = MAX_DLIGHTS; i--; ++dl) {
667 if (dl->die < Level->Time || dl->radius <= 0.0f) continue;
668 if (dl->lightid == lightid) return dl;
670 return nullptr;
674 //==========================================================================
676 // VRenderLevelShared::DecayLights
678 //==========================================================================
679 void VRenderLevelShared::DecayLights (float timeDelta) {
680 TFrustum frustum;
681 int frustumState = 0; // <0: don't check; >1: inited
682 if (!cl) frustumState = -1;
683 dlight_t *dl = DLights;
684 for (int i = 0; i < MAX_DLIGHTS; ++i, ++dl) {
685 if (dl->radius <= 0.0f || dl->die < Level->Time) {
686 RL_CLEAR_DLIGHT(dl);
687 continue;
689 //dl->radius -= timeDelta*(dl->decay/1000.0f);
690 dl->radius -= timeDelta*dl->decay;
691 // remove small lights too
692 if (dl->radius < 2.0f) {
693 RL_CLEAR_DLIGHT(dl);
694 } else {
695 // check if light is out of frustum, and remove it if it is invisible
696 if (frustumState == 0) {
697 TClipBase cb(refdef.fovx, refdef.fovy);
698 if (cb.isValid()) {
699 frustum.setup(cb, TFrustumParam(cl->ViewOrg, cl->ViewAngles), true, GetLightMaxDistDef());
700 frustumState = (frustum.isValid() ? 1 : -1);
701 } else {
702 frustumState = -1;
705 if (frustumState > 0 && !frustum.checkSphere(dl->origin, dl->radius)) {
706 RL_CLEAR_DLIGHT(dl);
713 //==========================================================================
715 // VRenderLevelShared::ThinkerAdded
717 //==========================================================================
718 void VRenderLevelShared::ThinkerAdded (VThinker * /*Owner*/) {
719 //if (!Owner) return;
720 //if (!Owner->IsA(VEntity::StaticClass())) return;
721 //suid2ent.put(Owner->ServerUId, (VEntity *)Owner);
725 //==========================================================================
727 // VRenderLevelShared::ThinkerDestroyed
729 //==========================================================================
730 void VRenderLevelShared::ThinkerDestroyed (VThinker *Owner) {
731 if (!Owner) return;
732 // remove dynamic light
733 auto idxp = dlowners.get(Owner->ServerUId);
734 if (idxp) {
735 dlight_t *dl = &DLights[*idxp];
736 vassert(dl->ownerUId == Owner->ServerUId);
737 dl->radius = 0;
738 dl->flags = 0;
739 dl->ownerUId = 0;
740 dlowners.del(Owner->ServerUId);
742 // remove static light
743 auto stxp = StOwners.get(Owner->ServerUId);
744 if (stxp) RemoveStaticLightByIndex(*stxp);
745 //suid2ent.del(Owner->ServerUId);
749 //==========================================================================
751 // VRenderLevelShared::FreeSurfCache
753 //==========================================================================
754 void VRenderLevelShared::FreeSurfCache (surfcache_t *&) {
758 //==========================================================================
760 // VRenderLevelShared::ProcessCachedSurfaces
762 //==========================================================================
763 void VRenderLevelShared::ProcessCachedSurfaces () {
767 //==========================================================================
769 // VRenderLevelShared::CheckLightPointCone
771 //==========================================================================
772 float VRenderLevelShared::CheckLightPointCone (VEntity *lowner, const TVec &p, const float radius, const float height, const TVec &coneOrigin, const TVec &coneDir, const float coneAngle) {
773 (void)lowner;
774 TPlane pl;
775 pl.SetPointNormal3D(coneOrigin, coneDir);
777 if ((p-coneOrigin).lengthSquared() <= 8.0f) return 1.0f;
779 //if (checkSpot && dl.coneAngle > 0.0f && dl.coneAngle < 360.0f)
780 if (radius <= 0.0f) {
781 if (height <= 0.0f) {
782 if (pl.PointOnSide(p)) return 0.0f;
783 return p.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
784 } else {
785 const TVec p1(p.x, p.y, p.z+height);
786 if (pl.PointOnSide(p)) {
787 if (pl.PointOnSide(p1)) return 0.0f;
788 return p1.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
789 } else {
790 const float att0 = p.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
791 if (att0 == 1.0f || pl.PointOnSide(p1)) return att0;
792 const float att1 = p1.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
793 return max2(att0, att1);
798 float bbox[6];
799 bbox[0+0] = p.x-radius*0.4f;
800 bbox[0+1] = p.y-radius*0.4f;
801 bbox[0+2] = p.z;
802 bbox[3+0] = p.x+radius*0.4f;
803 bbox[3+1] = p.y+radius*0.4f;
804 bbox[3+2] = p.z+(height > 0.0f ? height : 0.0f);
805 if (!pl.checkBox3D(bbox)) return 0.0f;
806 float res = calcLightPoint(p, height).calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
807 if (res == 1.0f) return res;
808 for (unsigned bi = 0; bi < MAX_BBOX3D_CORNERS; ++bi) {
809 const TVec vv(GetBBox3DCorner(bi, bbox));
810 if (pl.PointOnSide(vv)) continue;
811 const float attn = vv.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
812 if (attn > res) {
813 res = attn;
814 if (res == 1.0f) return 1.0f; // it can't be higher than this
817 // check box midpoint
819 const TVec cv((bbox[0+0]+bbox[3+0])*0.5f, (bbox[0+1]+bbox[3+1])*0.5f, (bbox[0+2]+bbox[3+2])*0.5f);
820 const float attn = cv.calcSpotlightAttMult(coneOrigin, coneDir, coneAngle);
821 res = max2(res, attn);
823 return res;
827 //==========================================================================
829 // getFPlaneDist
831 //==========================================================================
832 static inline float getFPlaneDist (const sec_surface_t *floor, const TVec &p) {
833 //const float d = floor->PointDistance(p);
834 if (floor) {
835 const float z = floor->esecplane.GetPointZClamped(p);
836 if (floor->esecplane.GetNormalZ() > 0.0f) {
837 // floor
838 if (p.z < z) {
839 GCon->Logf(NAME_Debug, "skip floor: p.z=%g; fz=%g", p.z, z);
840 return -1.0f;
842 GCon->Logf(NAME_Debug, "floor: p.z=%g; fz=%g; d=%g", p.z, z, p.z-z);
843 return p.z-z;
844 } else {
845 // ceiling
846 if (p.z >= z) {
847 GCon->Logf(NAME_Debug, "skip ceiling: p.z=%g; cz=%g", p.z, z);
848 return -1.0f;
850 GCon->Logf(NAME_Debug, "floor: p.z=%g; cz=%g; d=%g", p.z, z, z-p.z);
851 return z-p.z;
853 } else {
854 return -1.0f;
859 //==========================================================================
861 // VRenderLevelShared::GetNearestFloor
863 // used to find a lightmap
864 // slightly wrong (we should process ceilings too, and use nearest lmap)
865 // also note that to process floors/ceilings it is better to use
866 // sprite height center
868 //==========================================================================
870 sec_surface_t *VRenderLevelShared::GetNearestFloor (const subsector_t *sub, const TVec &p) {
871 if (!sub) return nullptr;
872 sec_surface_t *rfloor = nullptr;
873 float bestdist = 999999.0f;
874 for (subregion_t *r = sub->regions; r; r = r->next) {
875 sec_surface_t *floor;
876 // floors
877 floor = r->fakefloor;
878 if (floor && floor->esecplane.GetNormalZ() > 0.0f) {
879 const float z = floor->esecplane.GetPointZClamped(p);
880 const float d = p.z-z;
881 if (d >= 0.0f && d <= bestdist) {
882 bestdist = d;
883 rfloor = floor;
886 floor = r->realfloor;
887 if (floor && floor->esecplane.GetNormalZ() > 0.0f) {
888 const float z = floor->esecplane.GetPointZClamped(p);
889 const float d = p.z-z;
890 if (d >= 0.0f && d <= bestdist) {
891 bestdist = d;
892 rfloor = floor;
895 // ceilings
896 floor = r->fakeceil;
897 if (floor && floor->esecplane.GetNormalZ() > 0.0f) {
898 const float z = floor->esecplane.GetPointZClamped(p);
899 const float d = p.z-z;
900 if (d >= 0.0f && d <= bestdist) {
901 bestdist = d;
902 rfloor = floor;
905 floor = r->realceil;
906 if (floor && floor->esecplane.GetNormalZ() > 0.0f) {
907 const float z = floor->esecplane.GetPointZClamped(p);
908 const float d = p.z-z;
909 if (d >= 0.0f && d <= bestdist) {
910 bestdist = d;
911 rfloor = floor;
915 return rfloor;
918 // get lightmap
919 int s = (int)(p.dot(rfloor->texinfo.saxisLM));
920 int t = (int)(p.dot(rfloor->texinfo.taxisLM));
921 int ds, dt;
922 for (surface_t *surf = rfloor->surfs; surf; surf = surf->next) {
923 if (surf->lightmap == nullptr) continue;
924 if (surf->count < 3) continue; // wtf?!
925 //if (s < surf->texturemins[0] || t < surf->texturemins[1]) continue;
927 ds = s-surf->texturemins[0];
928 dt = t-surf->texturemins[1];
930 if (ds < 0 || dt < 0 || ds > surf->extents[0] || dt > surf->extents[1]) continue;
932 GCon->Logf(NAME_Debug, " lightmap hit! (%d,%d)", ds, dt);
933 if (surf->lightmap_rgb) {
934 //l += surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
935 const rgb_t *rgbtmp = &surf->lightmap_rgb[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
936 GCon->Logf(NAME_Debug, " (%d,%d,%d)", rgbtmp->r, rgbtmp->g, rgbtmp->b);
937 } else {
938 int ltmp = surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
939 GCon->Logf(NAME_Debug, " (%d)", ltmp);
941 break;
947 //==========================================================================
949 // VRenderLevelShared::CalcEntityStaticLightingFromOwned
951 //==========================================================================
952 void VRenderLevelShared::CalcEntityStaticLightingFromOwned (VEntity *lowner, const TVec &pt, float radius, float height, float &l, float &lr, float &lg, float &lb, unsigned flags) {
953 // fullight by owned lights
954 if (!lowner || (flags&LP_IgnoreSelfLights)) return;
955 if (!r_static_lights.asBool()) return;
957 auto stpp = StOwners.get(lowner->ServerUId);
958 if (!stpp) return;
960 const light_t *stl = &Lights[*stpp];
961 //GCon->Logf(NAME_Debug, "000: own static light for '%s' (%u): flags=0x%04x", lowner->GetClass()->GetName(), lowner->ServerUId, stl->flags);
962 if (stl->flags&(dlight_t::Disabled|dlight_t::Subtractive|dlight_t::NoActorLight|dlight_t::NoSelfLight)) return; // check "no self/actor light" flags
963 //GCon->Logf(NAME_Debug, "001: own static light for '%s' (%u): flags=0x%04x", lowner->GetClass()->GetName(), lowner->ServerUId, stl->flags);
965 const TVec p = calcLightPoint(pt, height);
966 const float distSq = (p-stl->origin).lengthSquared();
967 if (distSq >= stl->radius*stl->radius) return; // too far away
968 GCon->Logf(NAME_Debug, "002: own static light for '%s' (%u): flags=0x%04x; dist=%g", lowner->GetClass()->GetName(), lowner->ServerUId, stl->flags, sqrtf(distSq));
971 //float add = stl->radius-sqrtf(distSq);
972 float add = stl->radius;
973 //GCon->Logf(NAME_Debug, "003: own static light for '%s' (%u): flags=0x%04x; add=%g", lowner->GetClass()->GetName(), lowner->ServerUId, stl->flags, add);
974 if (add > 1.0f) {
975 if (stl->coneAngle > 0.0f && stl->coneAngle < 360.0f) {
976 const float attn = CheckLightPointCone(lowner, pt, radius, height, stl->origin, stl->coneDirection, stl->coneAngle);
977 add *= attn;
978 if (add <= 1.0f) return;
980 l += add;
981 lr += add*((stl->color>>16)&255)/255.0f;
982 lg += add*((stl->color>>8)&255)/255.0f;
983 lb += add*(stl->color&255)/255.0f;
988 //==========================================================================
990 // VRenderLevelShared::CalcEntityDynamicLightingFromOwned
992 //==========================================================================
993 void VRenderLevelShared::CalcEntityDynamicLightingFromOwned (VEntity *lowner, const TVec &pt, float radius, float height, float &l, float &lr, float &lg, float &lb, unsigned flags) {
994 // fullight by owned lights
995 if (!lowner || (flags&LP_IgnoreSelfLights)) return;
996 if (!r_dynamic_lights.asBool()) return;
998 auto dlpp = dlowners.get(lowner->ServerUId);
999 if (!dlpp) return;
1001 const dlight_t &dl = DLights[*dlpp];
1002 if (dl.flags&(dlight_t::Disabled|dlight_t::Subtractive|dlight_t::NoActorLight|dlight_t::NoSelfLight)) return; // check "no self/actor light" flags
1003 const TVec p = calcLightPoint(pt, height);
1004 const float distSq = (p-dl.origin).lengthSquared();
1005 if (distSq >= dl.radius*dl.radius) return; // too far away
1006 float add = (dl.radius-dl.minlight)-sqrtf(distSq);
1007 if (add > 1.0f) {
1008 if (dl.coneAngle > 0.0f && dl.coneAngle < 360.0f) {
1009 const float attn = CheckLightPointCone(lowner, pt, radius, height, dl.origin, dl.coneDirection, dl.coneAngle);
1010 add *= attn;
1011 if (add <= 1.0f) return;
1013 l += add;
1014 lr += add*((dl.color>>16)&255)/255.0f;
1015 lg += add*((dl.color>>8)&255)/255.0f;
1016 lb += add*(dl.color&255)/255.0f;
1021 //==========================================================================
1023 // VRenderLevelShared::CalculateDynLightSub
1025 // this is common code for light point calculation
1026 // pass light values from ambient pass
1028 //==========================================================================
1029 void VRenderLevelShared::CalculateDynLightSub (VEntity *lowner, float &l, float &lr, float &lg, float &lb, const subsector_t *sub, const TVec &pt, float radius, float height, unsigned flags) {
1030 //WARNING! if `MAX_DLIGHTS` is not 32, remove the last check
1031 if (r_dynamic_lights && sub->dlightframe == currDLightFrame && sub->dlightbits) {
1032 const TVec p = calcLightPoint(pt, height);
1033 const bool dynclip = r_dynamic_clip.asBool();
1034 const int snum = (int)(ptrdiff_t)(sub-&Level->Subsectors[0]);
1035 static_assert(sizeof(unsigned) >= sizeof(vuint32), "`unsigned` should be at least of `vuint32` size");
1036 const unsigned dlbits = (unsigned)sub->dlightbits;
1037 const bool texCheck = r_lmap_texture_check_dynamic.asBool();
1038 const float texCheckRadus = r_lmap_texture_check_radius_dynamic.asInt(); // float, to avoid conversions
1039 for (unsigned i = 0; i < MAX_DLIGHTS; ++i) {
1040 // check visibility
1041 if (!(dlbits&(1U<<i))) continue;
1042 if (!dlinfo[i].isVisible()) continue;
1043 // reject owned light, because they are processed by another method
1044 const dlight_t &dl = DLights[i];
1045 if (lowner && lowner->ServerUId == dl.ownerUId) continue;
1046 if (dl.flags&(dlight_t::Disabled|dlight_t::Subtractive|dlight_t::NoActorLight)) continue; // check "no self/actor light" flags
1047 // special processing for API light queries
1048 if ((flags&LP_IgnoreSelfLights) && lowner && IsInInventory(lowner, dl.ownerUId)) continue;
1049 if (dl.type&DLTYPE_Subtractive) continue;
1050 //if (!dl.radius || dl.die < Level->Time) continue; // this is not needed here
1051 const float distSq = (p-dl.origin).lengthSquared();
1052 if (distSq >= dl.radius*dl.radius) continue; // too far away
1053 float add = (dl.radius-dl.minlight)-sqrtf(distSq);
1054 if (add > 1.0f) {
1055 // owned light will light the whole object
1056 //const bool isowned = (lowner && lowner->ServerUId == dl.ownerUId);
1057 // check potential visibility
1058 if (dl.coneAngle > 0.0f && dl.coneAngle < 360.0f) {
1059 const float attn = CheckLightPointCone(lowner, pt, radius, height, dl.origin, dl.coneDirection, dl.coneAngle);
1060 add *= attn;
1061 if (add <= 1.0f) continue;
1063 // trace light that needs shadows
1064 const int leafnum = dlinfo[i].leafnum;
1065 if (dynclip && !(dl.flags&dlight_t::NoShadow) && leafnum != snum && dlinfo[i].isNeedTrace()) {
1066 // trace *to* light, because this is slightly faster for closets and such
1067 if (!RadiusCastRay((texCheck && dl.radius > texCheckRadus), sub, p, (leafnum >= 0 ? &Level->Subsectors[leafnum] : nullptr), dl.origin, radius)) continue;
1069 //!if (dl.type&DLTYPE_Subtractive) add = -add;
1070 l += add;
1071 lr += add*((dl.color>>16)&255)/255.0f;
1072 lg += add*((dl.color>>8)&255)/255.0f;
1073 lb += add*(dl.color&255)/255.0f;
1080 //==========================================================================
1082 // VRenderLevelShared::CalculateSubStatic
1084 // calculate subsector's light from static light sources
1085 // (light variables must be initialized)
1087 //==========================================================================
1088 void VRenderLevelShared::CalculateSubStatic (VEntity *lowner, float &l, float &lr, float &lg, float &lb, const subsector_t *sub, const TVec &pt, float radius, float height, unsigned flags) {
1089 if (flags&LP_StrictDynamic) {
1090 if ((flags&(LP_IgnoreDynLights|LP_IgnoreStatLights)) == (LP_IgnoreDynLights|LP_IgnoreStatLights)) return;
1091 } else {
1092 if (flags&LP_IgnoreStatLights) return;
1094 if (r_static_lights && sub) {
1095 if (!staticLightsFiltered) RefilterStaticLights();
1096 const TVec p = calcLightPoint(pt, height);
1097 const bool dynclip = true; //r_dynamic_clip.asBool();
1098 const bool texCheck = r_lmap_texture_check_static.asBool();
1099 // we know for sure what static light may affect the subsector, so there is no need to check 'em all
1100 const int snum = (int)(ptrdiff_t)(sub-&Level->Subsectors[0]);
1101 SubStaticLigtInfo *subslinfo = &SubStaticLights[snum];
1102 for (auto it : subslinfo->touchedStatic.first()) {
1103 const int idx = it.getKey();
1104 if (idx < 0 || idx >= Lights.length()) continue;
1105 const light_t *stl = Lights.ptr()+idx;
1106 if (!stl->active) continue;
1107 // ignore owned lights, because they are processed in another method
1108 if (lowner && lowner->ServerUId == stl->ownerUId) continue;
1109 if (stl->flags&(dlight_t::Disabled|dlight_t::Subtractive|dlight_t::NoActorLight)) continue; // check "no self/actor light" flags
1110 // check for "converted"
1111 if (flags&LP_StrictDynamic) {
1112 if (flags&(stl->flags&dlight_t::LightSource ? LP_IgnoreStatLights : LP_IgnoreDynLights)) continue;
1114 // check potential visibility
1115 const float distSq = (p-stl->origin).lengthSquared();
1116 if (distSq >= stl->radius*stl->radius) continue; // too far away
1117 float add = stl->radius-sqrtf(distSq);
1118 if (add > 1.0f) {
1119 if (stl->coneAngle > 0.0f && stl->coneAngle < 360.0f) {
1120 const float attn = CheckLightPointCone(lowner, pt, radius, height, stl->origin, stl->coneDirection, stl->coneAngle);
1121 add *= attn;
1122 if (add <= 1.0f) continue;
1124 if (dynclip && stl->leafnum != snum) {
1125 // trace *to* light, because this is slightly faster for closets and such
1126 if (!RadiusCastRay(texCheck, sub, p, (stl->leafnum >= 0 ? &Level->Subsectors[stl->leafnum] : nullptr), stl->origin, radius)) continue;
1128 l += add;
1129 lr += add*((stl->color>>16)&255)/255.0f;
1130 lg += add*((stl->color>>8)&255)/255.0f;
1131 lb += add*(stl->color&255)/255.0f;
1138 //===========================================================================
1140 // Doom-like lighting equation
1142 // level: [0..255]
1144 //===========================================================================
1145 static inline float DoomLightingEquation (float llev, float zdist) {
1146 //const lm = r_light_mode.asInt();
1147 //if (lm <= 0) return llev;
1149 // L is the integer llev level used in the game
1150 const float L = llev; //*255.0;
1152 const float z = max2(fabsf(zdist), 0.001f);
1154 const float LightGlobVis = R_CalcGlobVis();
1156 // more-or-less Doom light level equation
1157 const float vis = min2(LightGlobVis/z, 24.0f/32.0f);
1158 const float shade = 2.0f-(L+12.0f)/128.0f;
1159 float lightscale = shade-vis;
1161 if (r_light_mode.asInt() >= 2) lightscale = (-floorf(-lightscale*31.0f)-0.5f)/31.0f; // banded
1163 const float lmin = 31.0f-clampval(r_doomlight_min.asFloat(), 0.0f, 31.0f);
1164 lightscale = clampval(lightscale, 0.0f, /*31.0f*/lmin/32.0f);
1165 // `lightscale` is the normalized colormap index (0 bright .. 1 dark)
1167 return 255.0f*(1.0f-lightscale);
1171 //==========================================================================
1173 // VRenderLevelShared::CalculateSubAmbient
1175 // calculate subsector's ambient light
1176 // (light variables must be initialized)
1178 //==========================================================================
1179 void VRenderLevelShared::CalculateSubAmbient (VEntity *lowner, float &l, float &lr, float &lg, float &lb,
1180 const subsector_t *sub, const TVec &p,
1181 float radius, float height, unsigned flags)
1183 (void)lowner; (void)radius;
1184 unsigned glowFlags = 3u; // bit 0: floor glow allowed; bit 1: ceiling glow allowed
1185 if (height < 0.0f) height = 0.0f;
1188 if ((glowFlags&1u) && sec->floor.pic) {
1189 VTexture *gtex = GTextureManager(sec->floor.pic);
1190 if (gtex && gtex->Type != TEXTYPE_Null && gtex->glowing) {
1191 const float floorz = sub->sector->floor.GetPointZClamped(p);
1192 if (p.z >= floorz) {
1193 const float pz = p.z+hadd;
1194 const float hgt = pz-floorz;
1195 MIX_FLAT_GLOW
1199 if ((glowFlags&2u) && sec->ceiling.pic) {
1200 VTexture *gtex = GTextureManager(sec->ceiling.pic);
1201 if (gtex && gtex->Type != TEXTYPE_Null && gtex->glowing) {
1202 const float ceilz = sub->sector->ceiling.GetPointZClamped(p);
1203 if (p.z <= ceilz) {
1204 const float pz = min2(ceilz-hadd, p.z+height-hadd);
1205 const float hgt = ceilz-pz;
1206 MIX_FLAT_GLOW
1212 //const TVec p = calcLightPoint(pt, height);
1213 //FIXME: this is slightly wrong (and slow)
1214 if (/*!skipAmbient &&*/ sub->regions && !(flags&LP_IgnoreAmbLight)) {
1215 //sec_region_t *regbase;
1216 sec_region_t *reglight = Level->PointRegionLight(sub, p, &glowFlags);
1218 sec_params_t *fparam = reglight->params;
1219 sec_params_t *cparam = reglight->params;
1221 // allow glow only for bottom regions
1222 //FIXME: this is not right, we should calculate glow for translucent/transparent floors too!
1223 //glowAllowed = !!(regbase->regflags&sec_region_t::RF_BaseRegion);
1225 // region's base light
1226 int SecLightColor;
1227 if (r_sprite_lighting_type.asInt() <= 0) {
1228 // Boom sprite lighting
1229 l = R_GetLightLevel(0, fparam->lightlevel+(flags&LP_IgnoreExtraLight ? 0 : ExtraLight));
1230 SecLightColor = fparam->LightColor;
1231 } else {
1232 // advanced lighting
1233 //vuint32 floorLightLevel, ceilingLightLevel;
1234 //vuint32 floorLightColor, ceilingLightColor;
1236 int lss;
1237 lss = reglight->efloor.splane->LightSourceSector;
1238 if (lss >= 0 && lss < Level->NumSectors) {
1239 fparam = &Level->Sectors[lss].params;
1242 lss = reglight->eceiling.splane->LightSourceSector;
1243 if (lss >= 0 && lss < Level->NumSectors) {
1244 cparam = &Level->Sectors[lss].params;
1247 float fz = reglight->efloor.minZ;
1248 float cz = reglight->eceiling.maxZ;
1249 // ignore other things
1250 if ((fparam->lightlevel == cparam->lightlevel && fparam->LightColor == cparam->LightColor) ||
1251 (fz >= cz || cz - fz < 4.0f || p.z <= fz || p.z >= cz))
1253 l = R_GetLightLevel(0, fparam->lightlevel+(flags&LP_IgnoreExtraLight ? 0 : ExtraLight));
1254 SecLightColor = fparam->LightColor;
1255 } else {
1256 const float vdist = 1.0f / (cz - fz);
1257 float fdist = 1.0f - (p.z - fz) * vdist;
1258 float cdist = 1.0f - (cz - p.z) * vdist;
1260 float flr = ((fparam->LightColor>>16)&255) * fdist;
1261 float flg = ((fparam->LightColor>>8)&255) * fdist;
1262 float flb = (fparam->LightColor&255) * fdist;
1264 float clr = ((cparam->LightColor>>16)&255) * cdist;
1265 float clg = ((cparam->LightColor>>8)&255) * cdist;
1266 float clb = (cparam->LightColor&255) * cdist;
1268 vuint32 mixr = clampToByte((int)((flr + clr) * 0.5f));
1269 vuint32 mixg = clampToByte((int)((flg + clg) * 0.5f));
1270 vuint32 mixb = clampToByte((int)((flb + clb) * 0.5f));
1271 SecLightColor = (mixr << 16) | (mixg << 8) | mixb;
1273 float lfloor = (fparam->lightFCFlags&sec_params_t::LFC_FloorLight_Abs
1274 ? (float)fparam->lightFloor : (float)fparam->lightlevel);
1275 float lceil = (cparam->lightFCFlags&sec_params_t::LFC_FloorLight_Abs
1276 ? (float)cparam->lightFloor : (float)cparam->lightlevel);
1278 float llvl = lfloor * fdist + lceil * cdist;
1279 if (llvl < 0.0f) llvl = 0.0f; else if (llvl > 255.0f) llvl = 255.0f;
1280 l = R_GetLightLevel(0, (int)llvl+(flags&LP_IgnoreExtraLight ? 0 : ExtraLight));
1284 if (r_light_mode.asInt() <= 0 || (flags&LP_NoLightFade)) {
1285 lr = ((SecLightColor>>16)&255)*l/255.0f;
1286 lg = ((SecLightColor>>8)&255)*l/255.0f;
1287 lb = (SecLightColor&255)*l/255.0f;
1288 } else {
1289 // calculate light fading
1290 // this transforms point into camera space
1291 // as we are interested only in distance, it is enough to get transformed `z`
1292 const TVec mp = p-Drawer->vieworg;
1293 //TVec pp;
1294 //pp.x = mp.dot(Drawer->viewright);
1295 //pp.y = mp.dot(Drawer->viewup);
1296 //pp.z = mp.dot(Drawer->viewforward);
1297 const float ppz = mp.dot(Drawer->viewforward);
1298 const float nl = DoomLightingEquation(l, ppz);
1299 lr = ((SecLightColor>>16)&255)*nl/255.0f;
1300 lg = ((SecLightColor>>8)&255)*nl/255.0f;
1301 lb = (SecLightColor&255)*nl/255.0f;
1304 // light from floor's lightmap
1305 //k8: nope, we'll use light sources to calculate this
1306 #if 0
1308 if (!IsShadowVolumeRenderer()) {
1309 sec_surface_t *rfloor = GetNearestFloor(sub, p);
1310 if (rfloor) {
1311 //int s = (int)(p.dot(rfloor->texinfo.saxis)+rfloor->texinfo.soffs);
1312 //int t = (int)(p.dot(rfloor->texinfo.taxis)+rfloor->texinfo.toffs);
1313 int s = (int)(p.dot(rfloor->texinfo.saxisLM));
1314 int t = (int)(p.dot(rfloor->texinfo.taxisLM));
1315 int ds, dt;
1316 for (surface_t *surf = rfloor->surfs; surf; surf = surf->next) {
1317 if (surf->lightmap == nullptr) continue;
1318 if (surf->count < 3) continue; // wtf?!
1319 //if (s < surf->texturemins[0] || t < surf->texturemins[1]) continue;
1321 ds = s-surf->texturemins[0];
1322 dt = t-surf->texturemins[1];
1324 if (ds < 0 || dt < 0 || ds > surf->extents[0] || dt > surf->extents[1]) continue;
1326 if (surf->lightmap_rgb) {
1327 l += surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
1328 const rgb_t *rgbtmp = &surf->lightmap_rgb[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
1329 lr += rgbtmp->r;
1330 lg += rgbtmp->g;
1331 lb += rgbtmp->b;
1332 } else {
1333 int ltmp = surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
1334 l += ltmp;
1335 lr += ltmp;
1336 lg += ltmp;
1337 lb += ltmp;
1339 break;
1344 #endif
1347 //TODO: glow height
1348 #define MIX_FLAT_GLOW \
1349 if (hgt >= 0.0f && hgt < 120.0f) { \
1350 /* if (lr == l && lg == l && lb == l) lr = lg = lb = 255; l = 255; */ \
1351 /*return gtex->glowing|0xff000000u;*/ \
1352 /*skipAmbient = true;*/ \
1353 /*glowL = 255.0f;*/ \
1354 float glowL = (120.0f-hgt)*255.0f/120.0f; \
1355 float glowR = (gtex->glowing>>16)&0xff; \
1356 float glowG = (gtex->glowing>>8)&0xff; \
1357 float glowB = gtex->glowing&0xff; \
1358 /* mix with glow */ \
1359 /*glowL *= 0.8f;*/ \
1360 if (glowL > 1.0f) { \
1361 /*l *= 0.8f;*/ \
1362 const float llfrac = 0.8f; /*(l/255.0f)*0.8f;*/ \
1363 const float glfrac = (glowL/255.0f)*0.9f; \
1364 lr = clampval(lr*llfrac+glowR*glfrac, 0.0f, 255.0f); \
1365 lg = clampval(lg*llfrac+glowG*glfrac, 0.0f, 255.0f); \
1366 lb = clampval(lb*llfrac+glowB*glfrac, 0.0f, 255.0f); \
1367 l = clampval(l+glowL, 0.0f, 255.0f); \
1372 // glowing flats
1373 if (glowFlags && r_glow_flat && sub->sector && !(flags&LP_IgnoreGlowLights)) {
1374 //const float hadd = height*(1.0f/8.0f);
1375 const float hadd = 6.0f;
1376 const sector_t *sec = sub->sector;
1377 // fuckin' pasta!
1378 if ((glowFlags&1u) && sec->floor.pic) {
1379 VTexture *gtex = GTextureManager(sec->floor.pic);
1380 if (gtex && gtex->Type != TEXTYPE_Null && gtex->glowing) {
1381 const float floorz = sub->sector->floor.GetPointZClamped(p);
1382 if (p.z >= floorz) {
1383 const float pz = p.z+hadd;
1384 const float hgt = pz-floorz;
1385 MIX_FLAT_GLOW
1389 if ((glowFlags&2u) && sec->ceiling.pic) {
1390 VTexture *gtex = GTextureManager(sec->ceiling.pic);
1391 if (gtex && gtex->Type != TEXTYPE_Null && gtex->glowing) {
1392 const float ceilz = sub->sector->ceiling.GetPointZClamped(p);
1393 if (p.z <= ceilz) {
1394 const float pz = min2(ceilz-hadd, p.z+height-hadd);
1395 const float hgt = ceilz-pz;
1396 MIX_FLAT_GLOW
1402 #undef MIX_FLAT_GLOW
1406 //==========================================================================
1408 // VRenderLevelShared::LightPoint
1410 // used to calculate lighting for objects
1411 // can be used without `lowner` to calculate lighting in the given point:
1412 // in this case, the lighting is assumed to be for non-actor
1414 //==========================================================================
1415 vuint32 VRenderLevelShared::LightPoint (VEntity *lowner, const TVec p, float radius, float height, const subsector_t *psub, unsigned flags) {
1416 if (FixedLight && !(flags&LP_IgnoreFixedLight)) return FixedLight|(FixedLight<<8)|(FixedLight<<16)|(FixedLight<<24);
1418 const subsector_t *sub = (psub ? psub : Level->PointInSubsector(p));
1420 float l = 0.0f, lr = 0.0f, lg = 0.0f, lb = 0.0f;
1422 // calculate ambient light level
1423 CalculateSubAmbient(lowner, l, lr, lg, lb, sub, p, radius, height, flags);
1425 // fullight by owned lights
1426 if (lowner && !(flags&LP_IgnoreSelfLights)) {
1427 CalcEntityStaticLightingFromOwned(lowner, p, radius, height, l, lr, lg, lb, flags);
1428 CalcEntityDynamicLightingFromOwned(lowner, p, radius, height, l, lr, lg, lb, flags);
1431 // add static lights (flags are checked in the callee)
1432 /*if (IsShadowVolumeRenderer())*/ CalculateSubStatic(lowner, l, lr, lg, lb, sub, p, radius, height, flags);
1434 // add dynamic lights
1435 if (!(flags&LP_IgnoreDynLights)) {
1436 CalculateDynLightSub(lowner, l, lr, lg, lb, sub, p, radius, height, flags);
1439 return
1440 (((vuint32)clampToByte((int)l))<<24)|
1441 (((vuint32)clampToByte((int)lr))<<16)|
1442 (((vuint32)clampToByte((int)lg))<<8)|
1443 ((vuint32)clampToByte((int)lb));
1447 //==========================================================================
1449 // VRenderLevelShared::LightPointAmbient
1451 // used in advrender to calculate model lighting
1453 //==========================================================================
1454 vuint32 VRenderLevelShared::LightPointAmbient (VEntity *lowner, const TVec p, float radius, float height, const subsector_t *psub, unsigned flags) {
1455 if (FixedLight && !(flags&LP_IgnoreFixedLight)) return FixedLight|(FixedLight<<8)|(FixedLight<<16)|(FixedLight<<24);
1457 const subsector_t *sub = (psub ? psub : Level->PointInSubsector(p));
1458 float l = 0.0f, lr = 0.0f, lg = 0.0f, lb = 0.0f;
1460 CalculateSubAmbient(lowner, l, lr, lg, lb, sub, p, radius, height, flags);
1462 // fullight by owned lights
1463 if (lowner && !(flags&LP_IgnoreSelfLights)) {
1464 CalcEntityStaticLightingFromOwned(lowner, p, radius, height, l, lr, lg, lb, flags);
1465 CalcEntityDynamicLightingFromOwned(lowner, p, radius, height, l, lr, lg, lb, flags);
1468 return
1469 (((vuint32)clampToByte((int)l))<<24)|
1470 (((vuint32)clampToByte((int)lr))<<16)|
1471 (((vuint32)clampToByte((int)lg))<<8)|
1472 ((vuint32)clampToByte((int)lb));
1475 #undef RL_CLEAR_DLIGHT
1479 //==========================================================================
1481 // VRenderLevelShared::CalcBSPNodeLMaps
1483 // FIXME:POBJ:
1485 //==========================================================================
1486 void VRenderLevelShared::CalcBSPNodeLMaps (int slindex, light_t &sl, int bspnum /*, const float *bbox*/) {
1487 tailcall:
1488 //if (!CheckSphereVsAABBIgnoreZ(bbox, sl.origin, sl.radius)) return;
1490 // found a subsector?
1491 if (BSPIDX_IS_NON_LEAF(bspnum)) {
1492 const node_t *bsp = &Level->Nodes[bspnum];
1493 // decide which side the light is on
1494 const float dist = bsp->PointDistance(sl.origin);
1495 if (dist >= sl.radius) {
1496 // light is completely on the front side
1497 //return CalcBSPNodeLMaps(slindex, sl, bsp->children[0], bsp->bbox[0]);
1498 bspnum = bsp->children[0];
1499 //!bbox = bsp->bbox[0];
1500 goto tailcall;
1501 } else if (dist <= -sl.radius) {
1502 // light is completely on the back side
1503 //return CalcBSPNodeLMaps(slindex, sl, bsp->children[1], bsp->bbox[1]);
1504 bspnum = bsp->children[1];
1505 //!bbox = bsp->bbox[1];
1506 goto tailcall;
1507 } else {
1508 unsigned side = (unsigned)(dist <= 0.0f);
1509 // recursively divide front space
1510 CalcBSPNodeLMaps(slindex, sl, bsp->children[side]/*, bsp->bbox[side]*/);
1511 // possibly divide back space
1512 side ^= 1;
1513 //return CalcBSPNodeLMaps(slindex, sl, bsp->children[side], bsp->bbox[side]);
1514 bspnum = bsp->children[side];
1515 //!bbox = bsp->bbox[side];
1516 goto tailcall;
1518 } else {
1519 //subsector_t *sub = &Level->Subsectors[BSPIDX_LEAF_SUBSECTOR(bspnum)];
1520 //CalcSubsectorLMaps(slindex, sl, BSPIDX_LEAF_SUBSECTOR(bspnum));
1521 const int num = BSPIDX_LEAF_SUBSECTOR(bspnum);
1522 subsector_t *sub = &Level->Subsectors[num];
1523 if (sub->isOriginalPObj()) return;
1524 if (!CheckSphereVs2dAABB(sub->bbox2d, sl.origin, sl.radius)) return;
1525 // polyobj
1527 if (sub->HasPObjs()) {
1528 for (auto &&it : sub->PObjFirst()) {
1529 polyobj_t *pobj = it.value();
1530 sl.touchedPolys.append(pobj);
1534 sl.touchedSubs.append(sub);
1535 SubStaticLights[num].touchedStatic.put(slindex, true);
1540 //==========================================================================
1542 // VRenderLevelShared::CalcStaticLightTouchingSubs
1544 // FIXME: make this faster!
1545 // FIXME:POBJ:
1547 //==========================================================================
1548 void VRenderLevelShared::CalcStaticLightTouchingSubs (int slindex, light_t &sl) {
1549 // remove from all subsectors
1550 if (SubStaticLights.length() < Level->NumSubsectors) SubStaticLights.setLength(Level->NumSubsectors);
1551 for (auto &&it : sl.touchedSubs) {
1552 const int snum = (int)(ptrdiff_t)(it-&Level->Subsectors[0]);
1553 SubStaticLights[snum].touchedStatic.del(slindex);
1556 sl.touchedSubs.resetNoDtor();
1557 if (!sl.active || sl.radius < 2.0f) return;
1559 //sl.touchedPolys.reset();
1561 if (Level->NumSubsectors < 2) {
1562 if (Level->NumSubsectors == 1) {
1563 subsector_t *sub = &Level->Subsectors[0];
1564 if (!sub->isOriginalPObj()) {
1565 sl.touchedSubs.append(sub);
1566 SubStaticLights[0].touchedStatic.put(slindex, true);
1569 } else {
1570 //!const float bbox[6] = { -999999.0f, -999999.0f, -999999.0f, +999999.0f, +999999.0f, +999999.0f };
1571 CalcBSPNodeLMaps(slindex, sl, Level->NumNodes-1/*, bbox*/);
1576 //==========================================================================
1578 // VRenderLevelShared::InvalidateStaticLightmapsSubs
1580 //==========================================================================
1581 void VRenderLevelShared::InvalidateStaticLightmapsSubs (subsector_t * /*sub*/) {
1585 //==========================================================================
1587 // VRenderLevelShared::InvalidatePObjLMaps
1589 //==========================================================================
1590 void VRenderLevelShared::InvalidatePObjLMaps (polyobj_t * /*po*/) {
1594 //==========================================================================
1596 // VRenderLevelShared::CalcEntityLight
1598 // `flags` is `VDrawer::ELFlag_XXX` set
1599 // returns 0 for unknown
1601 //==========================================================================
1602 vuint32 VRenderLevelShared::CalcEntityLight (VEntity *lowner, unsigned dflags) {
1603 if (!lowner) return 0;
1604 const unsigned lflags =
1605 LP_IgnoreFixedLight|
1606 LP_IgnoreExtraLight|
1607 LP_NoLightFade|
1608 LP_StrictDynamic| // consider "converted" dynlights as dynamic
1609 (dflags&VDrawer::ELFlag_AllowSelfLights ? LP_NothingZero : LP_IgnoreSelfLights)|
1610 (dflags&VDrawer::ELFlag_IgnoreDynLights ? LP_IgnoreDynLights : LP_NothingZero)|
1611 (dflags&VDrawer::ELFlag_IgnoreStaticLights ? LP_IgnoreStatLights : LP_NothingZero)|
1612 (dflags&VDrawer::ELFlag_IgnoreAmbLights ? LP_IgnoreAmbLight : LP_NothingZero)|
1613 (dflags&VDrawer::ELFlag_IgnoreGlowLights ? LP_IgnoreGlowLights : LP_NothingZero)|
1614 LP_NothingZero;
1615 // we need real light
1616 //const int oldRM = r_light_mode.asInt();
1617 //r_light_mode.ForceSet(0); // switch to real ambient lighting
1618 vuint32 res = LightPoint(lowner, lowner->Origin, max2(1.0f, lowner->Radius), max2(0.0f, lowner->Height), lowner->SubSector, lflags);
1619 //r_light_mode.ForceSet(oldRM); // restore lighting mode
1620 if (res == 0u) res = 0x01000000u;
1621 return res;