1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
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.
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.
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/>.
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"
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 { \
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); \
76 (_dl)->ownerUId = 0; \
80 static VClass
*eexCls
= nullptr;
81 static VField
*mflFld
= nullptr;
83 static VClass
*invCls
= nullptr;
84 static VField
*invFld
= nullptr;
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;
98 eexCls
= VThinker::FindClassChecked("EntityEx");
99 mflFld
= VThinker::FindTypedField(eexCls
, "bMeIsMuzzleFlash", TYPE_Bool
);
101 invCls
= VThinker::FindClassChecked("Inventory");
102 invFld
= VThinker::FindTypedField(eexCls
, "Inventory", TYPE_Reference
, invCls
);
106 VEntity
*ee
= Level
->GetEntityBySUId(invUId
);
107 //if (!ee->IsA(invCls)) return false;
108 return (ee
&& (ee
->Owner
== owner
|| mflFld
->GetBool(ee
)));
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;
122 //==========================================================================
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
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
{
148 if (!Drawer
|| !isFiniteF(LightRadius
) || LightRadius
< 8.0f
) return false;
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;
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
) {
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;
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
{
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
) {
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
);
265 //==========================================================================
267 // VRenderLevelShared::RefilterStaticLights
269 //==========================================================================
270 void VRenderLevelShared::RefilterStaticLights () {
274 //==========================================================================
276 // VRenderLevelShared::ResetStaticLights
278 //==========================================================================
279 void VRenderLevelShared::ResetStaticLights () {
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
);
306 L
.coneDirection
= lpar
.coneDirection
;
307 L
.coneAngle
= lpar
.coneAngle
;
308 L
.levelSector
= lpar
.LevelSector
;
309 L
.levelScale
= lpar
.LevelScale
;
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
);
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
);
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
)
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
));
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);
362 if (r_lmap_recalc_moved_static
) InvalidateStaticLightmaps(sl
->origin
, sl
->radius
, false, (sl
->flags
&dlight_t::NoGeoClip
));
366 StOwners
.del(sl
->ownerUId
);
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
);
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
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) {
411 if (l->Owner && l->Owner->IsRefToCleanup()) {
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;
434 ss->dlightbits |= bit;
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;
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;
468 l
->origin
= l
->origOrigin
;
469 //if (l->Owner && l->Owner->IsA(VEntity::StaticClass())) l->origin += ((VEntity *)l->Owner)->GetDrawDelta();
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);
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?
521 if (Owner
&& Owner
->IsA(VEntity::StaticClass())) {
522 isPlr
= ((VEntity
*)Owner
)->IsPlayer();
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));
542 // look for any free slot (or free one if necessary)
544 bool skipVisCheck
= false;
546 // first try to find owned light to replace
549 auto idxp
= dlowners
.get(Owner
->ServerUId
);
551 dlowner
= &DLights
[*idxp
];
552 vassert(dlowner
->ownerUId
== Owner
->ServerUId
);
555 //FIXME: make this faster!
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*/) {
566 // look for any free slot (or free one if necessary)
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
;
573 if (dl
->radius
< 2.0f
) {
575 if (!dldying
) dldying
= dl
;
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
) {
586 // check if we already have dynamic light around new origin
588 const float dd
= (dl
->origin
-lorg
).lengthSquared();
589 if (dd
<= 6.0f
*6.0f
) {
590 if (radius
> 0 && dl
->radius
>= radius
) return nullptr;
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
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
608 // remove replaced light
609 //if (dlreplace && dlreplace != dlowner) memset((void *)dlreplace, 0, sizeof(*dlreplace));
610 vassert(dlowner
->ownerUId
== Owner
->ServerUId
);
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");
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);
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();
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
;
674 //==========================================================================
676 // VRenderLevelShared::DecayLights
678 //==========================================================================
679 void VRenderLevelShared::DecayLights (float timeDelta
) {
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
) {
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
) {
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
);
699 frustum
.setup(cb
, TFrustumParam(cl
->ViewOrg
, cl
->ViewAngles
), true, GetLightMaxDistDef());
700 frustumState
= (frustum
.isValid() ? 1 : -1);
705 if (frustumState
> 0 && !frustum
.checkSphere(dl
->origin
, dl
->radius
)) {
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
) {
732 // remove dynamic light
733 auto idxp
= dlowners
.get(Owner
->ServerUId
);
735 dlight_t
*dl
= &DLights
[*idxp
];
736 vassert(dl
->ownerUId
== Owner
->ServerUId
);
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
) {
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
);
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
);
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
);
799 bbox
[0+0] = p
.x
-radius
*0.4f
;
800 bbox
[0+1] = p
.y
-radius
*0.4f
;
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
);
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
);
827 //==========================================================================
831 //==========================================================================
832 static inline float getFPlaneDist (const sec_surface_t
*floor
, const TVec
&p
) {
833 //const float d = floor->PointDistance(p);
835 const float z
= floor
->esecplane
.GetPointZClamped(p
);
836 if (floor
->esecplane
.GetNormalZ() > 0.0f
) {
839 GCon
->Logf(NAME_Debug
, "skip floor: p.z=%g; fz=%g", p
.z
, z
);
842 GCon
->Logf(NAME_Debug
, "floor: p.z=%g; fz=%g; d=%g", p
.z
, z
, p
.z
-z
);
847 GCon
->Logf(NAME_Debug
, "skip ceiling: p.z=%g; cz=%g", p
.z
, z
);
850 GCon
->Logf(NAME_Debug
, "floor: p.z=%g; cz=%g; d=%g", p
.z
, z
, z
-p
.z
);
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;
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) {
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) {
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) {
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) {
919 int s = (int)(p.dot(rfloor->texinfo.saxisLM));
920 int t = (int)(p.dot(rfloor->texinfo.taxisLM));
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);
938 int ltmp = surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
939 GCon->Logf(NAME_Debug, " (%d)", ltmp);
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
);
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);
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
);
978 if (add
<= 1.0f
) return;
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
);
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
);
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
);
1011 if (add
<= 1.0f
) return;
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
) {
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
);
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
);
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;
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;
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
);
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
);
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;
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
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;
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);
1204 const float pz = min2(ceilz-hadd, p.z+height-hadd);
1205 const float hgt = ceilz-pz;
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
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
;
1232 // advanced lighting
1233 //vuint32 floorLightLevel, ceilingLightLevel;
1234 //vuint32 floorLightColor, ceilingLightColor;
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
;
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
;
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
;
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
1308 if (!IsShadowVolumeRenderer()) {
1309 sec_surface_t *rfloor = GetNearestFloor(sub, p);
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));
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)];
1333 int ltmp = surf->lightmap[(ds>>4)+(dt>>4)*((surf->extents[0]>>4)+1)];
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) { \
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); \
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
;
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
;
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
);
1394 const float pz
= min2(ceilz
-hadd
, p
.z
+height
-hadd
);
1395 const float hgt
= ceilz
-pz
;
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
);
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
);
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
1485 //==========================================================================
1486 void VRenderLevelShared::CalcBSPNodeLMaps (int slindex
, light_t
&sl
, int bspnum
/*, const float *bbox*/) {
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];
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];
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
1513 //return CalcBSPNodeLMaps(slindex, sl, bsp->children[side], bsp->bbox[side]);
1514 bspnum
= bsp
->children
[side
];
1515 //!bbox = bsp->bbox[side];
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;
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!
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);
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
|
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
)|
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
;