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_decal.h"
29 # include "../drawer.h" /* VRenderLevelPublic */
33 extern VCvarB r_decals
;
34 extern VCvarB r_decals_flat
;
35 extern VCvarB r_decals_wall
;
36 extern VCvarB k8gore_enabled
;
37 extern VCvarI k8gore_enabled_override
;
38 extern VCvarI k8gore_enabled_override_decal
;
40 static VCvarB
r_decal_switch_special("r_decal_switch_special", true, "Make decals more translucent on switch textures?", CVAR_Archive
|CVAR_NoShadow
);
41 static VCvarF
r_decal_switch_blood_alpha("r_decal_switch_blood_alpha", "0.36", "Force this transparency for blood decals on switches.", CVAR_Archive
|CVAR_NoShadow
);
42 static VCvarF
r_decal_switch_boot_alpha("r_decal_switch_boot_alpha", "0.36", "Force this transparency for boot decals on switches.", CVAR_Archive
|CVAR_NoShadow
);
43 static VCvarF
r_decal_switch_other_alpha("r_decal_switch_other_alpha", "0.56", "Force this transparency for decals on switches.", CVAR_Archive
|CVAR_NoShadow
);
45 // make renderer life somewhat easier by not allowing a lot of decals
46 // main work is done by `VLevel->CleanupSegDecals()`
47 VCvarI
gl_bigdecal_limit("gl_bigdecal_limit", "16", "Limit for big decals on one seg (usually produced by gore mod).", /*CVAR_PreInit|*/CVAR_Archive
|CVAR_NoShadow
);
48 VCvarI
gl_smalldecal_limit("gl_smalldecal_limit", "64", "Limit for small decals on one seg (usually produced by shots).", /*CVAR_PreInit|*/CVAR_Archive
|CVAR_NoShadow
);
50 VCvarI
gl_flatdecal_limit("gl_flatdecal_limit", "16", "Limit for overlapping decals on floor/ceiling.", /*CVAR_PreInit|*/CVAR_Archive
|CVAR_NoShadow
);
53 TMapNC
<VName
, bool> VLevel::baddecals
;
54 TArrayNC
<int> VLevel::dcLineTouchMark
;
55 TArrayNC
<int> VLevel::dcSegTouchMark
;
56 int VLevel::dcLineTouchCounter
= 0;
59 // sorry for this static
60 // it is used in decal cleanup code
61 static TArrayNC
<decal_t
*> dc2kill
;
64 //==========================================================================
68 //==========================================================================
69 static VVA_FORCEINLINE
bool isGoreEnabled () {
70 const int ovd
= k8gore_enabled_override_decal
.asInt();
71 if (ovd
< 0) return false;
72 if (ovd
> 0) return true;
73 const int ov
= k8gore_enabled_override
.asInt();
74 if (ov
< 0) return false;
75 if (ov
> 0) return true;
76 return k8gore_enabled
.asBool();
80 //==========================================================================
82 // decal_t::calculateBBox
84 //==========================================================================
85 void decal_t::calculateBBox () noexcept
{
86 return (void)CalculateTextureBBox(bbox2d
, texture
.id
, worldx
, worldy
, angle
, scaleX
, scaleY
);
90 //==========================================================================
92 // VLevel::CalcDecalAlpha
94 //==========================================================================
95 float VLevel::CalcDecalAlpha (const VDecalDef
*dec
, const float alpha
) noexcept
{
97 float dcalpha
= dec
->alpha
.value
;
99 //GCon->Logf(NAME_Debug, "decal '%s': orig alpha=%g (forced alpha=%g)", *dec->name, dcalpha, alpha);
100 if (alpha
< 1000.0f
) dcalpha
= alpha
; else dcalpha
*= alpha
-1000.0f
;
101 //GCon->Logf(NAME_Debug, "decal '%s': final alpha=%g", *dec->name, dcalpha);
103 if (dcalpha
< 0.004f
) return 0.0f
;
104 return min2(1.0f
, dcalpha
);
108 //==========================================================================
110 // VLevel::CalcSwitchDecalAlpha
112 // switches should be more visible, so make decals almost transparent
114 //==========================================================================
115 float VLevel::CalcSwitchDecalAlpha (const VDecalDef
*dec
, const float ovralpha
) {
117 const float alpha
= CalcDecalAlpha(dec
, ovralpha
);
118 if (!r_decal_switch_special
.asBool() || alpha
<= 0.004f
) return alpha
;
120 if (dec
->bloodSplat
) return clampval(min2(alpha
, r_decal_switch_blood_alpha
.asFloat()), 0.0f
, 1.0f
);
121 if (dec
->bootPrint
) return clampval(min2(alpha
, r_decal_switch_boot_alpha
.asFloat()), 0.0f
, 1.0f
);
123 return clampval(min2(alpha
, r_decal_switch_other_alpha
.asFloat()), 0.0f
, 1.0f
);
127 //==========================================================================
129 // VLevel::IncLineTouchCounter
131 //==========================================================================
132 void VLevel::IncLineTouchCounter () noexcept
{
133 if (dcLineTouchMark
.length() == NumLines
&& dcSegTouchMark
.length() == NumSegs
) {
134 if (++dcLineTouchCounter
!= MAX_VINT32
) return;
136 dcLineTouchMark
.setLength(NumLines
);
137 dcSegTouchMark
.setLength(NumSegs
);
139 for (auto &&v
: dcLineTouchMark
) v
= 0;
140 for (auto &&v
: dcSegTouchMark
) v
= 0;
141 dcLineTouchCounter
= 1;
145 //==========================================================================
147 // VLevel::IncSubTouchCounter
149 //==========================================================================
150 void VLevel::IncSubTouchCounter () noexcept
{
151 dcPobjTouchedReset
= false;
152 if (dcSubTouchMark
.length() == NumSubsectors
) {
153 if (++dcSubTouchCounter
!= MAX_VINT32
) return;
155 dcSubTouchMark
.setLength(NumSubsectors
);
157 for (auto &&v
: dcSubTouchMark
) v
= 0;
158 dcSubTouchCounter
= 1;
162 //==========================================================================
164 // VLevel::AddAnimatedDecal
166 //==========================================================================
167 void VLevel::AddAnimatedDecal (decal_t
*dc
) {
168 if (!dc
|| !dc
->animator
) return;
169 vassert(!dc
->prevanimated
);
170 vassert(!dc
->nextanimated
);
171 vassert(decanimlist
!= dc
);
172 if (decanimlist
) decanimlist
->prevanimated
= dc
;
173 dc
->nextanimated
= decanimlist
;
178 //==========================================================================
180 // VLevel::RemoveDecalAnimator
182 // this will also delete animator
183 // safe to call on decals without an animator
185 //==========================================================================
186 void VLevel::RemoveDecalAnimator (decal_t
*dc
) {
187 if (!dc
|| !dc
->animator
) return;
188 vassert(dc
->prevanimated
|| dc
->nextanimated
|| decanimlist
== dc
);
189 if (dc
->prevanimated
) dc
->prevanimated
->nextanimated
= dc
->nextanimated
; else decanimlist
= dc
->nextanimated
;
190 if (dc
->nextanimated
) dc
->nextanimated
->prevanimated
= dc
->prevanimated
;
192 dc
->animator
= nullptr;
193 dc
->prevanimated
= dc
->nextanimated
= nullptr;
197 //==========================================================================
199 // VLevel::AllocSegDecal
201 //==========================================================================
202 decal_t
*VLevel::AllocSegDecal (seg_t
*seg
, VDecalDef
*dec
, float alpha
, VDecalAnim
*animator
) {
205 //alpha = clampval((alpha >= 0.0f ? alpha : dec->alpha.value), 0.0f, 1.0f);
206 alpha
= CalcDecalAlpha(dec
, alpha
);
207 decal_t
*decal
= new decal_t
;
208 memset((void *)decal
, 0, sizeof(decal_t
));
209 //decal->dectype = dec->name;
211 decal
->texture
= dec
->texid
;
212 decal
->shadeclr
= decal
->origshadeclr
= dec
->shadeclr
;
213 //decal->translation = translation;
214 //decal->orgz = decal->curz = orgz;
215 //decal->xdist = lineofs;
216 decal
->angle
= AngleMod(dec
->angleWall
.value
);
217 decal
->ofsX
= decal
->ofsY
= 0.0f
;
218 decal
->scaleX
= decal
->origScaleX
= dec
->scaleX
.value
;
219 decal
->scaleY
= decal
->origScaleY
= dec
->scaleY
.value
;
220 decal
->alpha
= decal
->origAlpha
= alpha
;
222 (dec
->fullbright
? decal_t::Fullbright
: decal_t::FlagNothingZero
)|
223 (dec
->fuzzy
? decal_t::Fuzzy
: decal_t::FlagNothingZero
)|
224 (dec
->bloodSplat
? decal_t::BloodSplat
: decal_t::FlagNothingZero
)|
225 (dec
->bootPrint
? decal_t::BootPrint
: decal_t::FlagNothingZero
)|
226 (dec
->additive
? decal_t::Additive
: decal_t::FlagNothingZero
);
228 decal
->boottime
= 0.0f
; //dec->boottime.value;
229 decal
->bootanimator
= NAME_None
; //dec->bootanimator;
230 decal
->bootshade
= -2;
231 decal
->boottranslation
= -1;
232 decal
->bootalpha
= -1.0f
;
234 decal
->animator
= (animator
? animator
: dec
->animator
);
235 //if (decal->animator) GCon->Logf(NAME_Debug, "setting animator for '%s': <%s> (%s : %d)", *dec->name, *decal->animator->name, decal->animator->getTypeName(), (int)decal->animator->isEmpty());
236 if (decal
->animator
&& decal
->animator
->isEmpty()) decal
->animator
= nullptr;
237 if (decal
->animator
) decal
->animator
= decal
->animator
->clone();
238 //if (decal->animator) GCon->Logf(NAME_Debug, "set animator for '%s': <%s> (%s : %d)", *dec->name, *decal->animator->name, decal->animator->getTypeName(), (int)decal->animator->isEmpty());
239 seg
->appendDecal(decal
);
240 if (decal
->animator
) AddAnimatedDecal(decal
);
246 //==========================================================================
250 //==========================================================================
251 static int decalAreaCompare (const void *aa
, const void *bb
, void * /*udata*/) {
252 if (aa
== bb
) return 0;
253 const decal_t
*adc
= *(const decal_t
**)aa
;
254 const decal_t
*bdc
= *(const decal_t
**)bb
;
255 const float areaA
= BBox2DArea(adc
->bbox2d
);
256 const float areaB
= BBox2DArea(bdc
->bbox2d
);
257 // if one decal is more than two times smaller than the other, prefer smaller decal
258 if (areaA
<= areaB
*0.5f
) return -1;
259 if (areaB
<= areaA
*0.5f
) return +1;
260 // if the size is roughly the same, prefer animated
261 if (!!adc
->animator
!= !!bdc
->animator
) {
262 const float prcsz
= min2(areaA
, areaB
)/max2(areaA
, areaB
);
263 if (prcsz
>= 0.88f
) return (adc
->animator
? -1 : +1);
265 // prefer decal that will disappear anyway
266 if (adc
->animator
|| bdc
->animator
) {
267 const bool aWillDie
= (adc
->animator
? adc
->animator
->calcWillDisappear() : false);
268 const bool bWillDie
= (bdc
->animator
? bdc
->animator
->calcWillDisappear() : false);
269 if (aWillDie
!= bWillDie
) return (aWillDie
? -1 : +1);
271 // normal area comparison
272 const float adiff
= areaA
-areaB
;
273 if (adiff
< 0.0f
) return -1;
274 if (adiff
> 0.0f
) return +1;
279 //==========================================================================
281 // VLevel::CleanupSegDecals
283 // permament decals are simply ignored
285 //==========================================================================
286 void VLevel::CleanupSegDecals (seg_t
*seg
) {
288 const int bigLimit
= gl_bigdecal_limit
.asInt();
289 const int smallLimit
= gl_smalldecal_limit
.asInt();
290 if (bigLimit
< 1 && smallLimit
< 1) return; // nothing to do
292 dc2kill
.resetNoDtor();
295 int bigDecalCount
= 0, smallDecalCount
= 0;
296 decal_t
*dc
= seg
->decalhead
;
301 int dcTexId
= cdc
->texture
;
302 auto dtex
= GTextureManager
[dcTexId
];
303 if (!dtex
|| dtex
->Width
< 1 || dtex
->Height
< 1) {
304 // remove this decal (just in case)
309 const int twdt
= (int)(dtex
->GetScaledWidthF()*cdc
->scaleX
);
310 const int thgt
= (int)(dtex
->GetScaledHeightF()*cdc
->scaleY
);
312 if (!cdc
->animator
&& (twdt
< 1 || thgt
< 1 || cdc
->alpha
< 0.004f
)) {
313 // remove this decal (just in case)
318 if (cdc
->isPermanent()) continue;
320 if (twdt
>= BigDecalWidth
|| thgt
>= BigDecalHeight
) ++bigDecalCount
; else ++smallDecalCount
;
324 int toKillBig
= (bigLimit
> 0 ? bigDecalCount
-bigLimit
: 0);
325 int toKillSmall
= (smallLimit
> 0 ? smallDecalCount
-smallLimit
: 0);
326 if (toKillBig
<= 0 && toKillSmall
<= 0) return;
328 if (toKillBig
< 0) toKillBig
= 0;
329 if (toKillSmall
< 0) toKillSmall
= 0;
331 // hack: if we have to remove big decals, limit small decals too
332 if (toKillBig
> 0 && smallDecalCount
> bigLimit
) {
333 int biglim
= bigLimit
+bigLimit
/4;
334 //if (biglim < 1) biglim = 1;
335 int tks
= smallDecalCount
-biglim
;
336 if (tks
> toKillSmall
) {
337 //GCon->Logf(NAME_Debug, "force-kill %d small decals instead of %d", tks, toKillSmall);
342 //if (toKillBig) GCon->Logf(NAME_Debug, "have to kill %d big decals...", toKillBig);
343 //if (toKillSmall) GCon->Logf(NAME_Debug, "have to kill %d small decals...", toKillSmall);
345 // prefer smaller and animated decals
346 int dcCount
= toKillBig
+toKillSmall
;
348 // sort by preference
349 smsort_r(dc2kill
.ptr(), dc2kill
.length(), sizeof(decal_t
*), &decalAreaCompare
, nullptr);
350 for (decal_t
*dcdie
: dc2kill
) {
353 //GCon->Logf(NAME_Debug, "killing wall %sdecal '%s' with area %g", (dcdie->animator ? "animated " : ""), *GTextureManager[dcdie->texture]->Name, BBox2DArea(dcdie->bbox2d));
360 //==========================================================================
362 // VLevel::DestroyFlatDecal
364 // this will also destroy decal and its animator!
366 //==========================================================================
367 void VLevel::DestroyFlatDecal (decal_t
*dc
) {
369 vassert(dc
->isFloor() || dc
->isCeiling());
371 vassert(NumSubsectors
> 0);
372 vassert(subsectorDecalList
);
373 // remove from renderer
375 if (dc
->sreg
&& Renderer
) Renderer
->RemoveFlatDecal(dc
);
377 const int sidx
= (int)(ptrdiff_t)(dc
->sub
-&Subsectors
[0]);
378 if (sidx
< 0 || sidx
>= NumSubsectors
) return;
379 // remove from subsector list
380 VDecalList
*lst
= &subsectorDecalList
[sidx
];
381 DLListRemoveEx(dc
, lst
->head
, lst
->tail
, subprev
, subnext
);
382 // remove from global list
383 DLListRemove(dc
, subdecalhead
, subdecaltail
);
385 RemoveDecalAnimator(dc
);
391 //==========================================================================
393 // VLevel::KillAllSubsectorDecals
395 //==========================================================================
396 void VLevel::KillAllSubsectorDecals () {
397 while (subdecalhead
) DestroyFlatDecal(subdecalhead
);
398 vassert(!subdecalhead
);
399 vassert(!subdecaltail
);
400 if (subsectorDecalList
) {
401 delete[] subsectorDecalList
;
402 subsectorDecalList
= nullptr;
407 //==========================================================================
409 // VLevel::AppendDecalToSubsectorList
411 // permament decals are simply ignored by the limiter
413 //==========================================================================
414 void VLevel::AppendDecalToSubsectorList (decal_t
*dc
) {
417 if (dc
->animator
) AddAnimatedDecal(dc
);
420 if (NumSubsectors
== 0/*just in case*/) {
421 RemoveDecalAnimator(dc
);
427 vassert(dc
->isFloor() || dc
->isCeiling());
430 vassert(!dc
->subprev
);
431 vassert(!dc
->subnext
);
432 vassert(!dc
->sregprev
);
433 vassert(!dc
->sregnext
);
436 vassert(!dc
->slidesec
);
437 vassert(dc
->eregindex
>= 0);
439 // append to global sector decal list
440 DLListAppend(dc
, subdecalhead
, subdecaltail
);
442 // append to list of decals in the given sector
443 if (!subsectorDecalList
) {
444 subsectorDecalList
= new VDecalList
[NumSubsectors
];
445 for (int f
= 0; f
< NumSubsectors
; ++f
) subsectorDecalList
[f
].head
= subsectorDecalList
[f
].tail
= nullptr;
448 VDecalList
*lst
= &subsectorDecalList
[(unsigned)(ptrdiff_t)(dc
->sub
-&Subsectors
[0])];
449 DLListAppendEx(dc
, lst
->head
, lst
->tail
, subprev
, subnext
);
452 if (Renderer
) Renderer
->AppendFlatDecal(dc
);
455 // check subsector decals limit
456 const int dclimit
= gl_flatdecal_limit
.asInt();
457 if (dclimit
> 0 && !dc
->isPermanent()) {
458 // prefer decals with animators (they are usually transient blood or something like that)
459 dc2kill
.resetNoDtor();
460 constexpr float shrinkRatio
= 0.8f
;
462 ShrinkBBox2D(mybbox
, dc
->bbox2d
, shrinkRatio
);
465 decal_t
*prdc
= lst
->head
;
467 decal_t
*curdc
= prdc
;
468 prdc
= prdc
->subnext
;
469 if (curdc
->eregindex
!= dc
->eregindex
) continue;
470 if (curdc
->dcsurf
!= dc
->dcsurf
) continue;
471 if (curdc
->isPermanent()) continue;
472 ShrinkBBox2D(curbbox
, curdc
->bbox2d
, shrinkRatio
);
473 if (Are2DBBoxesOverlap(mybbox
, curbbox
)) {
475 dc2kill
.append(curdc
);
478 // do we need to remove some decals?
479 if (dcCount
> dclimit
) {
480 // prefer smallest decals
481 //GCon->Logf(NAME_Debug, "%d flat decals to remove (%d total)...", dcCount-dclimit, dcCount);
482 dcCount
-= dclimit
; // decals left to remove
483 // sort by preference
484 smsort_r(dc2kill
.ptr(), dc2kill
.length(), sizeof(decal_t
*), &decalAreaCompare
, nullptr);
485 for (decal_t
*dcdie
: dc2kill
) {
488 //GCon->Logf(NAME_Debug, "killing flat %sdecal '%s' with area %g", (dcdie->animator ? "animated " : ""), *GTextureManager[dcdie->texture]->Name, BBox2DArea(dcdie->bbox2d));
489 DestroyFlatDecal(dcdie
);
497 //==========================================================================
499 // VLevel::NewFlatDecal
501 //==========================================================================
502 void VLevel::NewFlatDecal (bool asFloor
, subsector_t
*sub
, const int eregidx
, const float wx
, const float wy
,
503 VDecalDef
*dec
, const DecalParams
¶ms
)
506 vassert(eregidx
>= 0);
509 const float dcalpha
= CalcDecalAlpha(dec
, params
.alpha
);
511 decal_t
*decal
= new decal_t
;
512 memset((void *)decal
, 0, sizeof(decal_t
));
513 //decal->dectype = dec->name;
515 decal
->texture
= dec
->texid
;
516 decal
->translation
= params
.translation
;
517 decal
->shadeclr
= decal
->origshadeclr
= (params
.shadeclr
!= -2 ? params
.shadeclr
: dec
->shadeclr
);
518 decal
->slidesec
= nullptr;
520 decal
->eregindex
= eregidx
;
521 decal
->dcsurf
= (asFloor
? decal_t::Floor
: decal_t::Ceiling
);
524 decal
->angle
= AngleMod(params
.angle
);
525 //decal->orgz = org.z; // doesn't matter
526 //!decal->height = height;
527 //decal->curz = 0.0f; // doesn't matter
528 //decal->xdist = 0.0f; // doesn't matter
529 //decal->ofsX = decal->ofsY = 0.0f;
530 decal
->scaleX
= decal
->origScaleX
= dec
->scaleX
.value
;
531 decal
->scaleY
= decal
->origScaleY
= dec
->scaleY
.value
;
532 decal
->alpha
= decal
->origAlpha
= dcalpha
;
535 (dec
->fullbright
? decal_t::Fullbright
: decal_t::FlagNothingZero
)|
536 (dec
->fuzzy
? decal_t::Fuzzy
: decal_t::FlagNothingZero
)|
537 (dec
->bloodSplat
? decal_t::BloodSplat
: decal_t::FlagNothingZero
)|
538 (dec
->bootPrint
? decal_t::BootPrint
: decal_t::FlagNothingZero
)|
539 (dec
->additive
? decal_t::Additive
: decal_t::FlagNothingZero
);
541 decal
->boottime
= dec
->boottime
.value
;
542 decal
->bootanimator
= dec
->bootanimator
;
543 decal
->bootshade
= dec
->bootshade
;
544 decal
->boottranslation
= dec
->boottranslation
;
545 decal
->bootalpha
= dec
->bootalpha
;
547 decal
->animator
= (params
.animator
? params
.animator
: dec
->animator
);
548 //if (decal->animator) GCon->Logf(NAME_Debug, "anim: %s(%s) (%d)", *decal->animator->name, decal->animator->getTypeName(), (int)decal->animator->isEmpty());
549 if (decal
->animator
&& decal
->animator
->isEmpty()) decal
->animator
= nullptr;
550 //decal->animator = nullptr;
551 if (decal
->animator
) decal
->animator
= decal
->animator
->clone();
553 AppendDecalToSubsectorList(decal
);
557 //==========================================================================
559 // VLevel::DestroyDecal
561 // this will also destroy decal and its animator!
563 //==========================================================================
564 void VLevel::DestroyDecal (decal_t
*dc
) {
567 RemoveDecalAnimator(dc
);
568 dc
->seg
->removeDecal(dc
);
571 return DestroyFlatDecal(dc
);
576 //==========================================================================
580 //==========================================================================
581 void VLevel::AddDecal (TVec org
, VName dectype
, int side
, line_t
*li
, int level
, DecalParams
¶ms
) {
582 if (!r_decals
|| !r_decals_wall
) return;
583 if (!li
|| dectype
== NAME_None
|| VStr::strEquCI(*dectype
, "none")) return; // just in case
585 // replace blood decals
586 if (canReplaceBlood
&& isGoreEnabled()) {
587 if (VStr::strEquCI(*dectype
, "BloodSplat")) dectype
= goreBloodDecalSplat
;
588 else if (VStr::strEquCI(*dectype
, "BloodSmear")) dectype
= goreBloodDecalSmear
;
589 if (VStr::strEquCI(*dectype
, "BloodSplatRadius")) dectype
= goreBloodDecalSplatRadius
;
590 else if (VStr::strEquCI(*dectype
, "BloodSmearRadius")) dectype
= goreBloodDecalSmearRadius
;
593 //GCon->Logf(NAME_Debug, "%s: oorg:(%g,%g,%g); org:(%g,%g,%g); trans=%d", *dectype, org.x, org.y, org.z, li->landAlongNormal(org).x, li->landAlongNormal(org).y, li->landAlongNormal(org).z, translation);
595 VDecalDef
*dec
= VDecalDef::getDecal(dectype
);
596 //if (dec->animator) GCon->Logf(NAME_Debug, " animator: <%s> (%s : %d)", *dec->animator->name, dec->animator->getTypeName(), (int)dec->animator->isEmpty());
597 //if (animator) GCon->Logf(NAME_Debug, " forced animator: <%s> (%s : %d)", *animator->name, animator->getTypeName(), (int)animator->isEmpty());
599 org
= li
->landAlongNormal(org
);
600 //GCon->Logf(NAME_Debug, "DECAL '%s'; name is '%s', texid is %d; org=(%g,%g,%g)", *dectype, *dec->name, dec->texid, org.x, org.y, org.z);
601 AddOneDecal(level
, org
, dec
, side
, li
, params
);
603 if (!baddecals
.put(dectype
, true)) GCon
->Logf(NAME_Warning
, "NO DECAL: '%s'", *dectype
);
608 //==========================================================================
610 // VLevel::AddDecalById
612 //==========================================================================
613 void VLevel::AddDecalById (TVec org
, int id
, int side
, line_t
*li
, int level
, DecalParams
¶ms
) {
614 if (!r_decals
|| !r_decals_wall
) return;
615 if (!li
|| id
< 0) return; // just in case
616 VDecalDef
*dec
= VDecalDef::getDecalById(id
);
618 org
= li
->landAlongNormal(org
);
619 params
.forcePermanent
= true; // always
620 AddOneDecal(level
, org
, dec
, side
, li
, params
);
625 //==========================================================================
627 // VLevel::AddFlatDecal
630 // `height` is from `org.z`
631 // zero height means "take from decal texture"
633 //==========================================================================
634 void VLevel::AddFlatDecal (TVec org
, VName dectype
, float range
, DecalParams
¶ms
) {
635 if (!r_decals
|| !r_decals_flat
) return;
636 if (dectype
== NAME_None
|| VStr::strEquCI(*dectype
, "none")) return; // just in case
638 VDecalDef
*dec
= VDecalDef::getDecal(dectype
);
640 if (!baddecals
.put(dectype
, true)) GCon
->Logf(NAME_Warning
, "NO DECAL: '%s'", *dectype
);
644 range
= max2(2.0f
, fabsf(range
));
645 SpreadFlatDecalEx(org
, range
, dec
, 0, params
);
650 //**************************************************************************
654 //**************************************************************************
656 //native final void AddDecal (TVec org, name dectype, int side, line_t *li, optional int translation,
657 // optional int shadeclr, optional float alpha, optional name animator,
658 // optional bool permanent, optional float angle, optional bool forceFlipX);
659 IMPLEMENT_FUNCTION(VLevel
, AddDecal
) {
664 VOptParamInt
translation(0);
665 VOptParamInt
shadeclr(-2);
666 VOptParamFloat
alpha(-2.0f
);
667 VOptParamName
animator(NAME_None
);
668 VOptParamBool
permanent(false);
669 VOptParamFloat
angle(INFINITY
);
670 VOptParamBool
forceFlipX(false);
671 vobjGetParamSelf(org
, dectype
, side
, li
, translation
, shadeclr
, alpha
, animator
, permanent
, angle
, forceFlipX
);
672 //if (!angle.specified) angle.value = INFINITY;
674 params
.translation
= translation
.value
;
675 params
.shadeclr
= shadeclr
.value
;
676 params
.alpha
= alpha
.value
;
677 params
.animator
= VDecalAnim::GetAnimatorByName(animator
.value
);
678 params
.angle
= angle
.value
;
679 params
.forceFlipX
= forceFlipX
.value
;
680 params
.forcePermanent
= permanent
.value
;
681 Self
->AddDecal(org
, dectype
, side
, li
, 0, params
);
684 //native final void AddDecalById (TVec org, int id, int side, line_t *li, optional int translation,
685 // optional int shadeclr, optional float alpha, optional name animator);
686 IMPLEMENT_FUNCTION(VLevel
, AddDecalById
) {
691 VOptParamInt
translation(0);
692 VOptParamInt
shadeclr(-2);
693 VOptParamFloat
alpha(-2.0f
);
694 VOptParamName
animator(NAME_None
);
695 vobjGetParamSelf(org
, id
, side
, li
, translation
, shadeclr
, alpha
, animator
);
697 params
.translation
= translation
.value
;
698 params
.shadeclr
= shadeclr
.value
;
699 params
.alpha
= alpha
.value
;
700 params
.animator
= VDecalAnim::GetAnimatorByName(animator
.value
);
701 params
.angle
= INFINITY
;
702 params
.forceFlipX
= false;
703 params
.forcePermanent
= true;
704 Self
->AddDecalById(org
, id
, side
, li
, 0, params
);
708 //native final void AddFlatDecal (TVec org, name dectype, float range, optional int translation, optional int shadeclr, optional float alpha,
709 // optional name animator, optional float angle, optional bool forceFlipX, optional bool permanent);
710 IMPLEMENT_FUNCTION(VLevel
, AddFlatDecal
) {
714 VOptParamInt
translation(0);
715 VOptParamInt
shadeclr(-2);
716 VOptParamFloat
alpha(-2.0f
);
717 VOptParamName
animator(NAME_None
);
718 VOptParamFloat
angle(INFINITY
);
719 VOptParamBool
forceFlipX(false);
720 VOptParamBool
permanent(false);
721 vobjGetParamSelf(org
, dectype
, range
, translation
, shadeclr
, alpha
, animator
, angle
, forceFlipX
, permanent
);
722 //if (!angle.specified) angle = INFINITY;
724 params
.translation
= translation
.value
;
725 params
.shadeclr
= shadeclr
.value
;
726 params
.alpha
= alpha
.value
;
727 params
.animator
= VDecalAnim::GetAnimatorByName(animator
.value
);
728 params
.angle
= angle
.value
;
729 params
.forceFlipX
= forceFlipX
.value
;
730 params
.forcePermanent
= permanent
.value
;
731 Self
->AddFlatDecal(org
, dectype
, range
, params
);
735 // check what kind of bootprint decal is required at `org`
736 // returns `false` if none (params are undefined)
737 // native /*final*/ bool CheckBootPrints (TVec org, subsector_t *sub, out VBootPrintDecalParams params);
738 IMPLEMENT_FUNCTION(VLevel
, CheckBootPrints
) {
741 VBootPrintDecalParams
*params
;
742 vobjGetParamSelf(org
, sub
, params
);
743 RET_BOOL(Self
->CheckBootPrints(org
, sub
, *params
));
747 // native /*final*/ void CheckFloorDecalDamage (bool isPlayer, TVec org, subsector_t *sub, void delegate (int damage, name damageType) dg);
748 IMPLEMENT_FUNCTION(VLevel
, CheckFloorDecalDamage
) {
754 vobjGetParamSelf(isPlayer
, org
, sub
, dgSelf
, dgFunc
);
755 if (dgFunc
) Self
->CheckFloorDecalDamage(isPlayer
, org
, sub
, dgSelf
, dgFunc
);
759 COMMAND(RemoveAllDecals
) {
760 VLevel
*lvl
= (GLevel
? GLevel
: GClLevel
);
762 GCon
->Log(NAME_Error
, "no level loaded");
765 lvl
->KillAllMapDecals();
766 GCon
->Log("removed all decals");