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"
30 # include "../drawer.h"
35 PO_3D_LINK_SEQ
= 9367,
40 TArrayNC
<VEntity
*> VLevel::poRoughEntityList
; // moved to VLevel as static
43 //==========================================================================
45 // CollectPObjTouchingThingsRoughBlockmap
47 // collect all objects from blockmap cells this polyobject may touch
48 // puts it in `entList`
50 // pobj bounding box must be valid
52 //==========================================================================
53 static VVA_OKUNUSED
void CollectTouchingThingsRoughBlockmap (TArrayNC
<VEntity
*> &entList
, VLevel
*XLevel
, const float bbox2d
[4], bool clearList
=true) {
56 // do it anyway, because we may rely on the fact that `validcount` is incremented
57 XLevel
->IncrementValidCount();
60 DeclareMakeBlockMapCoordsBBox2DMaxRadius(bbox2d
, cleft
, cbottom
, cright
, ctop
);
62 const int bwdt
= XLevel
->BlockMapWidth
;
63 if (ctop
< 0 || cright
< 0 || cbottom
>= XLevel
->BlockMapHeight
|| cleft
>= bwdt
) return;
64 const int bhgt
= XLevel
->BlockMapHeight
;
66 const int bottom
= max2(cbottom
, 0);
67 const int top
= min2(ctop
, bhgt
-1);
68 const int left
= max2(cleft
, 0);
69 const int right
= min2(cright
, bwdt
-1);
70 const int visCount
= validcount
;
72 const int ey
= top
*bwdt
;
73 for (int by
= bottom
*bwdt
; by
<= ey
; by
+= bwdt
) {
74 for (int bx
= left
; bx
<= right
; ++bx
) {
75 for (VEntity
*mobj
= XLevel
->BlockLinks
[by
+bx
]; mobj
; mobj
= mobj
->BlockMapNext
) {
76 if (mobj
->IsGoingToDie()) continue;
77 if (mobj
->ValidCount
== visCount
) continue;
78 mobj
->ValidCount
= visCount
;
86 //==========================================================================
88 // CollectPObjTouchingThingsRoughSectors
90 // collect all objects from sectors this polyobject may touch
91 // puts it in `entList`
93 // this is done by checking each sector this pobj possibly touches,
94 // so it should be done after calling `PutPObjInSubsectors()`
96 // this also checks pobj bounding box, to skip mobjs that cannot be touched
98 //==========================================================================
99 static VVA_OKUNUSED
void CollectPObjTouchingThingsRoughSectors (TArrayNC
<VEntity
*> &entList
, VLevel
*XLevel
, polyobj_t
*po
, bool clearList
=true) {
102 // do it anyway, because we may rely on the fact that `validcount` is incremented
103 XLevel
->IncrementValidCount();
106 const int visCount
= validcount
;
109 for (polyobjpart_t
*part
= po
->parts
; part
; part
= part
->nextpobj
) {
110 sector_t
*sec
= part
->sub
->sector
;
111 if (sec
->isAnyPObj()) continue; // just in case
112 if (sec
->validcount
== visCount
) continue;
113 sec
->validcount
= visCount
;
114 // new sector, process things
115 for (msecnode_t
*n
= sec
->TouchingThingList
; n
; n
= n
->SNext
) {
116 VEntity
*mobj
= n
->Thing
;
117 if (mobj
->IsGoingToDie()) continue;
118 if (mobj
->ValidCount
== visCount
) continue;
119 mobj
->ValidCount
= visCount
;
120 // check bounding box
121 if (!IsAABBInside2DBBox(mobj
->Origin
.x
, mobj
->Origin
.y
, mobj
->GetMoveRadius(), po
->bbox2d
)) continue;
122 entList
.append(mobj
);
128 //==========================================================================
132 // `*udata` is `polyobj_t`
134 //==========================================================================
135 static bool pobjAddSubsector (VLevel
*level
, subsector_t
*sub
, void *udata
) {
136 polyobj_t
*po
= (polyobj_t
*)udata
;
137 po
->AddSubsector(sub
);
139 if (level
->Renderer
) level
->Renderer
->InvalidateStaticLightmapsSubs(sub
);
141 //GCon->Logf(NAME_Debug, "linking pobj #%d (%g,%g)-(%g,%g) to subsector #%d", po->tag, po->bbox2d[BOX2D_MINX], po->bbox2d[BOX2D_MINY], po->bbox2d[BOX2D_MAXX], po->bbox2d[BOX2D_MAXY], (int)(ptrdiff_t)(sub-&level->Subsectors[0]));
142 return true; // continue checking
146 //==========================================================================
148 // VLevel::PutPObjInSubsectors
150 //==========================================================================
151 void VLevel::PutPObjInSubsectors (polyobj_t
*po
) noexcept
{
154 //TODO: make this faster by calculating overlapping rectangles or something
155 po
->RemoveAllSubsectors();
157 CheckBSPB2DBox(po
->bbox2d
, &pobjAddSubsector
, po
);
161 //==========================================================================
163 // polyobjpart_t::Free
165 //==========================================================================
166 void polyobjpart_t::Free () noexcept
{
175 //==========================================================================
177 // polyobjpart_t::allocSeg
179 //==========================================================================
180 seg_t
*polyobjpart_t::allocSeg (const seg_t
*oldseg
) noexcept
{
181 if (count
== amount
) {
182 amount
+= 64; // arbitrary number
183 segs
= (seg_t
*)Z_Realloc(segs
, sizeof(seg_t
)*amount
);
184 verts
= (TVec
*)Z_Realloc(verts
, sizeof(TVec
)*(amount
*2));
185 for (unsigned f
= 0; f
< (unsigned)count
; ++f
) {
186 segs
[f
].v1
= &verts
[f
*2u+0];
187 segs
[f
].v2
= &verts
[f
*2u+1];
190 seg_t
*res
= &segs
[count
];
191 memset((void *)res
, 0, sizeof(*res
));
192 if (oldseg
) *res
= *oldseg
;
193 res
->v1
= &verts
[count
*2+0];
194 res
->v2
= &verts
[count
*2+1];
196 *res
->v1
= *oldseg
->v1
;
197 *res
->v2
= *oldseg
->v2
;
204 //==========================================================================
206 // polyobjpart_t::allocInitedSeg
208 //==========================================================================
209 void polyobjpart_t::CreateClipSegs (VLevel
*Level
) {
211 flags
|= CLIPSEGS_CREATED
;
212 const bool is3d
= pobj
->Is3D();
213 // create clip segs for each polyobject part
214 // we can use full lines here, because they will be clipped to destination subsector
215 for (auto &&it
: pobj
->LineFirst()) {
216 // clip pobj seg to subsector
217 line_t
*ld
= it
.line();
218 side_t
*sd
= nullptr;
220 //GCon->Logf(NAME_Debug, "trying pobj #%d, line #%d to subsector of sector #%d", pobj->tag, (int)(ptrdiff_t)(ld-&Level->Lines[0]), (int)(ptrdiff_t)(sub->sector-&Level->Sectors[0]));
223 memset((void *)&newseg
, 0, sizeof(newseg
));
228 // check for valid line, and setup vertices
230 if (ld
->sidenum
[0] >= 0 && ld
->frontsector
== pobj
->posector
) {
232 sd
= &Level
->Sides
[ld
->sidenum
[0]];
235 } else if (ld
->sidenum
[1] >= 0 && ld
->backsector
== pobj
->posector
) {
237 sd
= &Level
->Sides
[ld
->sidenum
[1]];
243 if ((ld
->flags
&ML_TWOSIDED
) != 0) continue; // never clip with 2-sided pobj walls (for now)
244 if (ld
->sidenum
[0] < 0) continue;
246 sd
= &Level
->Sides
[ld
->sidenum
[0]];
250 if (!sd
) continue; // just in case
254 newseg
.frontsector
= sub
->sector
; // just in case
255 newseg
.frontsub
= sub
;
258 //GCon->Logf(NAME_Debug, "...clipping pobj #%d, line #%d to subsector of sector #%d", pobj->tag, (int)(ptrdiff_t)(ld-&Level->Lines[0]), (int)(ptrdiff_t)(sub->sector-&Level->Sectors[0]));
260 if (!Level
->ClipPObjSegToSub(sub
, &newseg
)) continue; // out of this subsector
262 //GCon->Logf(NAME_Debug, "...ADDING clipped line of pobj #%d, line #%d to subsector of sector #%d", pobj->tag, (int)(ptrdiff_t)(ld-&Level->Lines[0]), (int)(ptrdiff_t)(sub->sector-&Level->Sectors[0]));
263 (void)allocSeg(&newseg
);
268 //==========================================================================
272 //==========================================================================
273 void polyobj_t::Free () {
276 delete[] originalPts
;
279 polyobjpart_t
*part
= parts
;
281 polyobjpart_t
*c
= part
;
282 part
= part
->nextpobj
;
288 polyobjpart_t
*c
= part
;
289 part
= part
->nextpobj
;
296 //==========================================================================
298 // polyobj_t::RemoveAllSubsectors
300 //==========================================================================
301 void polyobj_t::RemoveAllSubsectors () {
303 polyobjpart_t
*part
= parts
;
304 parts
= part
->nextpobj
;
306 subsector_t
*sub
= part
->sub
;
307 polyobjpart_t
*prev
= nullptr;
308 polyobjpart_t
*curr
= sub
->polyparts
;
309 while (curr
&& curr
!= part
) { prev
= curr
; curr
= curr
->nextsub
; }
311 if (prev
) prev
->nextsub
= part
->nextsub
; else sub
->polyparts
= part
->nextsub
;
313 // put to free parts pool
314 part
->reset(); // remove clipsegs
316 part
->nextsub
= nullptr;
317 part
->nextpobj
= freeparts
;
320 if (GClLevel
&& GClLevel
->Renderer
) GClLevel
->Renderer
->InvalidateStaticLightmapsSubs(sub
);
326 //==========================================================================
328 // polyobj_t::ResetClipSegs
330 //==========================================================================
331 void polyobj_t::ResetClipSegs () {
332 for (polyobjpart_t
*part
= parts
; part
; part
= part
->nextpobj
) {
338 //==========================================================================
340 // subsector_t::ResetClipSegs
342 //==========================================================================
343 void subsector_t::ResetClipSegs () {
344 for (polyobjpart_t
*part
= polyparts
; part
; part
= part
->nextsub
) {
350 //==========================================================================
352 // polyobj_t::AddSubsector
354 // no need to check for duplicates here, there won't be any (yet)
356 //==========================================================================
357 void polyobj_t::AddSubsector (subsector_t
*sub
) {
359 polyobjpart_t
*pp
= freeparts
;
361 freeparts
= pp
->nextpobj
;
363 pp
= new polyobjpart_t
;
364 memset((void *)pp
, 0, sizeof(*pp
));
368 // add part to our list
369 pp
->nextpobj
= parts
;
373 // add part to subsector list
374 pp
->nextsub
= sub
->polyparts
;
379 //==========================================================================
381 // VLevel::ResetPObjRenderCounts
383 // called from renderer
385 //==========================================================================
386 void VLevel::ResetPObjRenderCounts () noexcept
{
387 for (int i
= 0; i
< NumPolyObjs
; ++i
) PolyObjs
[i
]->rendercount
= 0;
391 //==========================================================================
393 // VLevel::RegisterPolyObj
395 // `poidx` is NOT a tag!
397 //==========================================================================
398 void VLevel::RegisterPolyObj (int poidx
) noexcept
{
399 vassert(poidx
>= 0 && poidx
< NumPolyObjs
);
401 if (!PolyTagMap
->has(PolyObjs
[poidx
]->tag
)) {
402 PolyTagMap
->put(PolyObjs
[poidx
]->tag
, poidx
);
404 GCon
->Logf(NAME_Error
, "duplicate pobj tag #%d", PolyObjs
[poidx
]->tag
);
409 //==========================================================================
411 // VLevel::GetPolyobj
413 //==========================================================================
414 polyobj_t
*VLevel::GetPolyobj (int polyNum
) noexcept
{
416 //FIXME: make this faster!
417 for (int i = 0; i < NumPolyObjs; ++i) {
418 if (PolyObjs[i]->tag == polyNum) return PolyObjs[i];
422 auto pip
= PolyTagMap
->get(polyNum
);
423 return (pip
? PolyObjs
[*pip
] : nullptr);
427 //==========================================================================
429 // VLevel::GetPolyobjMirror
431 //==========================================================================
432 int VLevel::GetPolyobjMirror (int poly
) {
433 polyobj_t
*po
= GetPolyobj(poly
);
434 return (po
? po
->mirror
: 0);
438 //==========================================================================
440 // VLevel::ValidateNormalPolyobj
442 // note that polyobject segs are not collected yet, only lines
444 //==========================================================================
445 void VLevel::ValidateNormalPolyobj (polyobj_t
*po
) {
446 if (po
->numlines
< 3) {
447 GCon
->Logf(NAME_Error
, "pobj #%d has less than 3 lines (%d)", po
->tag
, po
->numlines
);
449 vassert(po
->numlines
);
450 sector_t
*sfront
= po
->lines
[0]->frontsector
;
451 sector_t
*sback
= po
->lines
[0]->backsector
;
452 for (auto &&it
: po
->LineFirst()) {
453 line_t
*ld
= it
.line();
454 if (ld
->frontsector
!= sfront
|| ld
->backsector
!= sback
) {
456 GCon
->Logf(NAME_Debug
, "l0: sfront=%d; sback=%d", (sfront
? (int)(ptrdiff_t)(sfront
-&Sectors
[0]) : -666),
457 (sback
? (int)(ptrdiff_t)(sback
-&Sectors
[0]) : -666));
458 GCon
->Logf(NAME_Debug
, "ld: sfront=%d; sback=%d", (ld
->frontsector
? (int)(ptrdiff_t)(ld
->frontsector
-&Sectors
[0]) : -666),
459 (ld
->backsector
? (int)(ptrdiff_t)(ld
->backsector
-&Sectors
[0]) : -666));
461 GCon
->Logf(NAME_Error
, "invalid pobj #%d configuration -- bad line #%d (invalid sectors) (first line is #%d)",
462 po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]), (int)(ptrdiff_t)(po
->lines
[0]-&Lines
[0]));
468 #define PO3DALLOWEDLFAGS ( \
479 ML_MONSTERSCANACTIVATE| \
481 ML_BLOCKEVERYTHING| \
485 ML_CHECKSWITCHRANGE| \
487 ML_BLOCKPROJECTILE| \
495 //==========================================================================
497 // VLevel::FixPolyobjCachedFlags
499 // called from loader
501 //==========================================================================
502 void VLevel::FixPolyobjCachedFlags (polyobj_t
*po
) {
504 po
->PolyFlags
&= ~polyobj_t::PF_HasTopBlocking
;
505 if (!po
->posector
) return; // non-3d pobjs has no cached flags
506 for (auto &&it
: po
->LineFirst()) {
507 line_t
*ld
= it
.line();
508 if (ld
->flags
&ML_CLIP_MIDTEX
) {
509 po
->PolyFlags
|= polyobj_t::PF_HasTopBlocking
;
516 //==========================================================================
518 // VLevel::Validate3DPolyobj
520 // note that polyobject segs are not collected yet, only lines
522 //==========================================================================
523 void VLevel::Validate3DPolyobj (polyobj_t
*po
) {
524 vassert(po
->numlines
>= 3);
526 po
->PolyFlags
&= ~polyobj_t::PF_HasTopBlocking
;
527 sector_t
*sfront
= po
->lines
[0]->frontsector
;
528 sector_t
*sback
= po
->lines
[0]->backsector
;
529 for (auto &&it
: po
->LineFirst()) {
530 line_t
*ld
= it
.line();
531 if (ld
->frontsector
!= sfront
|| ld
->backsector
!= sback
) {
532 Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (invalid sectors)", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
534 // allow some other flags
536 if (ld->flags&ML_BLOCKEVERYTHING) ld->flags |= ML_BLOCKING;
537 else if (ld->flags&(ML_BLOCKMONSTERS|ML_BLOCKPLAYERS)) ld->flags |= ML_BLOCKING;
540 if (!(ld
->flags
&ML_BLOCKING
)) {
541 Host_Error("3d pobj #%d line #%d should have \"impassable\" flag!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
542 //ld->flags |= ML_BLOCKING;
544 // remove flags we are not interested in
545 //ld->flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCKMONSTERS|ML_BLOCKPLAYERS);
546 // check for some extra flags
547 if (ld
->flags
&~PO3DALLOWEDLFAGS
) Host_Error("3d pobj #%d line #%d have some forbidden flags set!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
548 //if (linedef->alpha != 1.0f) Host_Error("3d pobj #%d line #%d cannot be alpha-blended!", po->tag, (int)(ptrdiff_t)(ld-&Lines[0])); //nope, it is used for top/bottom textures
549 // validate midtex (only the front side)
550 if (ld
->sidenum
[0] < 0) Host_Error("3d pobj #%d line #%d has no front side!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
551 //if (ld->sidenum[1] < 0) Host_Error("3d pobj #%d line #%d has no back side!", po->tag, (int)(ptrdiff_t)(ld-&Lines[0]));
552 const side_t
*sidedef
= &Sides
[ld
->sidenum
[0]];
553 VTexture
*MTex
= GTextureManager(sidedef
->MidTexture
);
554 if (!MTex
|| MTex
->Type
== TEXTYPE_Null
) Host_Error("3d pobj #%d line #%d has invalid front midtex!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
555 if (MTex
->GetScaledWidthF()/sidedef
->Mid
.ScaleY
< 1.0f
) Host_Error("3d pobj #%d line #%d has invalid front midtex width!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
556 if (MTex
->GetScaledHeightF()/sidedef
->Mid
.ScaleY
< 1.0f
) Host_Error("3d pobj #%d line #%d has invalid front midtex height!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
557 // it should not be translucent or masked
558 if (MTex
->isSeeThrough()) Host_Error("3d pobj #%d line #%d has transparent/translucent midtex!", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
560 const side_t
*s0
= (ld
->sidenum
[0] < 0 ? nullptr : &Sides
[ld
->sidenum
[0]]);
561 const side_t
*s1
= (ld
->sidenum
[1] < 0 ? nullptr : &Sides
[ld
->sidenum
[1]]);
562 //if (!s0) Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (no front side)", po->tag, (int)(ptrdiff_t)(ld-&Lines[0]));
563 //if (GTextureManager.IsEmptyTexture(s0->MidTexture)) Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (empty midtex)", po->tag, (int)(ptrdiff_t)(ld-&Lines[0]));
564 // top textures must be the same if they are impassable
565 if (ld
->flags
&ML_CLIP_MIDTEX
) {
566 // top texture is blocking
567 if (GTextureManager
.IsEmptyTexture(s0
->TopTexture
)) Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (toptex is blocking and missing)", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
568 if (!s1
|| GTextureManager
.IsEmptyTexture(s1
->TopTexture
)) Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (inner toptex is blocking and missing)", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
569 // check texture heights
570 VTexture
*TTex0
= GTextureManager(s0
->TopTexture
);
571 if (!TTex0
) TTex0
= GTextureManager
[GTextureManager
.DefaultTexture
];
572 VTexture
*TTex1
= GTextureManager(s1
->TopTexture
);
573 if (!TTex1
) TTex1
= GTextureManager
[GTextureManager
.DefaultTexture
];
574 const float texh0
= TTex0
->GetScaledHeightF()/s0
->Top
.ScaleY
;
575 const float texh1
= TTex1
->GetScaledHeightF()/s1
->Top
.ScaleY
;
576 if (texh0
!= texh1
) Host_Error("invalid 3d pobj #%d configuration -- bad line #%d (blocking toptex has different sizes)", po
->tag
, (int)(ptrdiff_t)(ld
-&Lines
[0]));
577 po
->PolyFlags
|= polyobj_t::PF_HasTopBlocking
;
583 //==========================================================================
585 // VLevel::IsGood3DPolyobj
587 // valid 3d pobj should have properly closed contours without "orphan" lines
588 // all lines should be two-sided, with the same frontsector and same
589 // backsector (and those sectors must be different)
591 // note that polyobject segs are not collected yet, only lines
593 //==========================================================================
594 bool VLevel::IsGood3DPolyobj (polyobj_t
*po
) {
595 if (po
->numlines
< 3) return false; // no wai
596 //po->lines = new line_t*[explines.length()];
599 // check if all lines are two-sided, with valid sectors
600 sector_t
*sfront
= nullptr;
601 sector_t
*sback
= nullptr;
603 // but don't bother if we don't want to spawn 3d pobj
604 for (auto &&it
: po
->LineFirst()) {
605 line_t
*ld
= it
.line();
606 if (!(ld
->flags
&ML_TWOSIDED
)) return false; // found one-sided line
607 if (!ld
->frontsector
|| !ld
->backsector
|| ld
->backsector
== ld
->frontsector
) return false; // invalid sectors
608 if (!sfront
) { sfront
= ld
->frontsector
; sback
= ld
->backsector
; }
609 if (ld
->frontsector
!= sfront
|| ld
->backsector
!= sback
) return false; // invalid sectors
610 // this seems to be a valid line
612 // should have both sectors
613 if (!sback
|| sfront
== sback
) return false;
615 // all lines seems to be valid, check for properly closed contours
616 IncrementValidCount();
617 for (auto &&it
: po
->LineFirst()) {
618 line_t
*ld
= it
.line();
619 if (ld
->validcount
== validcount
) continue; // already processed
620 // walk the contour, marking all lines
622 // first line should be marked as used last
626 line_t
*next
= nullptr;
627 for (auto &&it2
: po
->LineFirst()) {
628 line_t
*l2
= it2
.line();
629 if (l2
== curr
) continue; // just in case
630 if (l2
->validcount
== validcount
) continue; // already used
631 if (l2
->v1
== curr
->v2
) {
632 if (next
) return false; // two connected lines
637 if (!next
) return false; // no contour continuation found, invalid pobj
640 curr
->validcount
= validcount
;
641 } while (curr
!= ld
);
642 if (count
< 3) return false;
645 // now check if we have any unmarked lines
646 for (auto &&it
: po
->LineFirst()) {
647 line_t
*ld
= it
.line();
648 if (ld
->validcount
!= validcount
) return false; // oops
655 //==========================================================================
657 // VLevel::InitPolyBlockMap
659 //==========================================================================
660 void VLevel::InitPolyBlockMap () {
661 PolyBlockMap
= new polyblock_t
*[BlockMapWidth
*BlockMapHeight
];
662 memset((void *)PolyBlockMap
, 0, sizeof(polyblock_t
*)*BlockMapWidth
*BlockMapHeight
);
663 for (int i
= 0; i
< NumPolyObjs
; ++i
) LinkPolyobj(PolyObjs
[i
], true);
667 //==========================================================================
671 // this should be a `sec_plane_t` method, but meh...
673 // this is required to fix [4fdd92dc19]
675 //==========================================================================
676 static inline void pobjCopySecPlaneT (sec_plane_t
*dest
, const sec_plane_t
*src
) {
677 if (!dest
|| !src
|| dest
== src
) return;
678 // as we will change most of the fields, it is easier
679 // to save and restore those that need to be untouched (at least for now)
680 const VTextureID pic
= dest
->pic
;
681 const vuint32 flags
= dest
->flags
;
682 const vuint32 flipFlags
= dest
->flipFlags
;
683 const float Alpha
= dest
->Alpha
;
684 const float MirrorAlpha
= dest
->MirrorAlpha
;
685 const vint32 LightSourceSector
= dest
->LightSourceSector
;
686 VEntity
*SkyBox
= dest
->SkyBox
;
689 // ...and restore something ;-)
692 dest
->flipFlags
= flipFlags
;
694 dest
->MirrorAlpha
= MirrorAlpha
;
695 dest
->LightSourceSector
= LightSourceSector
;
696 dest
->SkyBox
= SkyBox
;
700 //==========================================================================
702 // VLevel::OffsetPolyobjFlats
704 //==========================================================================
705 void VLevel::OffsetPolyobjFlats (polyobj_t
*po
, float z
, bool forceRecreation
) {
708 po
->pofloor
.dist
-= z
; // floor dist is negative
709 po
->pofloor
.TexZ
+= z
;
710 po
->pofloor
.minz
+= z
;
711 po
->pofloor
.maxz
+= z
;
712 po
->pofloor
.TexZ
= po
->pofloor
.minz
;
713 po
->poceiling
.dist
+= z
; // ceiling dist is positive
714 po
->poceiling
.TexZ
+= z
;
715 po
->poceiling
.minz
+= z
;
716 po
->poceiling
.maxz
+= z
;
717 po
->poceiling
.TexZ
= po
->poceiling
.minz
;
719 sector_t
*sec
= po
->posector
;
722 if (!forceRecreation
&& fabsf(z
) != 0.0f
) forceRecreation
= true;
724 if (!forceRecreation
&&
725 (FASI(po
->pofloor
.PObjCX
) != FASI(po
->startSpot
.x
) ||
726 FASI(po
->pofloor
.PObjCY
) != FASI(po
->startSpot
.y
) ||
727 FASI(po
->poceiling
.PObjCX
) != FASI(po
->startSpot
.x
) ||
728 FASI(po
->poceiling
.PObjCY
) != FASI(po
->startSpot
.y
)))
730 forceRecreation
= true;
732 po
->pofloor
.PObjCX
= po
->poceiling
.PObjCX
= po
->startSpot
.x
;
733 po
->pofloor
.PObjCY
= po
->poceiling
.PObjCY
= po
->startSpot
.y
;
736 //sec->floor = po->pofloor;
737 //sec->ceiling = po->poceiling;
738 // use special copy function, because some ACS scripts may change some
739 // sector properties, and we want to retain those changes
740 pobjCopySecPlaneT(&sec
->floor
, &po
->pofloor
);
741 pobjCopySecPlaneT(&sec
->ceiling
, &po
->poceiling
);
743 if (forceRecreation
) {
744 for (subsector_t
*sub
= sec
->subsectors
; sub
; sub
= sub
->seclink
) {
745 for (subregion_t
*reg
= sub
->regions
; reg
; reg
= reg
->next
) {
746 reg
->ForceRecreation();
753 //==========================================================================
755 // VLevel::UpdatePolySegs
757 //==========================================================================
758 void VLevel::UpdatePolySegs (polyobj_t
*po
) {
759 // recalc lines's slope type, bounding box, normal and dist
760 for (auto &&it
: po
->LineFirst()) CalcLine(it
.line());
761 // recalc seg's normal and dist
762 for (auto &&it
: po
->SegFirst()) {
763 CalcSegPlaneDir(it
.seg());
765 // we can check seg vertices in cache checks, but meh
766 for (decal_t
*dc
= it
.seg()->decalhead
; dc
; dc
= dc
->next
) dc
->invalidateCache();
768 // update region heights
769 sector_t
*sec
= po
->posector
;
771 sec
->floor
.normal
= po
->pofloor
.normal
;
772 sec
->floor
.dist
= po
->pofloor
.dist
;
773 sec
->ceiling
.normal
= po
->poceiling
.normal
;
774 sec
->ceiling
.dist
= po
->poceiling
.dist
;
779 //==========================================================================
781 // VLevel::LinkPolyobj
783 //==========================================================================
784 void VLevel::LinkPolyobj (polyobj_t
*po
, bool relinkMObjs
) {
786 relink polyobject to the new subsector.
787 it is not the best way, but at least something.
789 note that if it moves to the sector with a different height, the renderer
790 adjusts it to the height of the new sector.
792 to avoid this, i had to introduce new field to segs, which links back to the
793 pobj. and i had to store the initial floor and ceiling planes in the pobj, so
794 renderer can use them instead of the corresponding sector planes.
797 // calculate the polyobj bbox
799 pobbox
[BOX2D_MINX
] = pobbox
[BOX2D_MINY
] = +99999.0f
;
800 pobbox
[BOX2D_MAXX
] = pobbox
[BOX2D_MAXY
] = -99999.0f
;
802 // update subsector bounding boxes
803 for (subsector_t
*sub
= po
->posector
->subsectors
; sub
; sub
= sub
->seclink
) {
804 if (sub
->numlines
< 1) continue;
805 sub
->bbox2d
[BOX2D_MINX
] = sub
->bbox2d
[BOX2D_MINY
] = +FLT_MAX
;
806 sub
->bbox2d
[BOX2D_MAXX
] = sub
->bbox2d
[BOX2D_MAXY
] = -FLT_MAX
;
807 const seg_t
*seg
= &Segs
[sub
->firstline
];
808 for (int f
= sub
->numlines
; f
--; ++seg
) {
810 sub
->bbox2d
[BOX2D_MINX
] = min2(sub
->bbox2d
[BOX2D_MINX
], seg
->v1
->x
);
811 sub
->bbox2d
[BOX2D_MINY
] = min2(sub
->bbox2d
[BOX2D_MINY
], seg
->v1
->y
);
812 sub
->bbox2d
[BOX2D_MAXX
] = max2(sub
->bbox2d
[BOX2D_MAXX
], seg
->v1
->x
);
813 sub
->bbox2d
[BOX2D_MAXY
] = max2(sub
->bbox2d
[BOX2D_MAXY
], seg
->v1
->y
);
815 sub
->bbox2d
[BOX2D_MINX
] = min2(sub
->bbox2d
[BOX2D_MINX
], seg
->v2
->x
);
816 sub
->bbox2d
[BOX2D_MINY
] = min2(sub
->bbox2d
[BOX2D_MINY
], seg
->v2
->y
);
817 sub
->bbox2d
[BOX2D_MAXX
] = max2(sub
->bbox2d
[BOX2D_MAXX
], seg
->v2
->x
);
818 sub
->bbox2d
[BOX2D_MAXY
] = max2(sub
->bbox2d
[BOX2D_MAXY
], seg
->v2
->y
);
821 // we can use subsector bounding boxes here
822 for (const subsector_t
*sub
= po
->posector
->subsectors
; sub
; sub
= sub
->seclink
) {
823 pobbox
[BOX2D_MINX
] = min2(pobbox
[BOX2D_MINX
], sub
->bbox2d
[BOX2D_MINX
]);
824 pobbox
[BOX2D_MINY
] = min2(pobbox
[BOX2D_MINY
], sub
->bbox2d
[BOX2D_MINY
]);
825 pobbox
[BOX2D_MAXX
] = max2(pobbox
[BOX2D_MAXX
], sub
->bbox2d
[BOX2D_MAXX
]);
826 pobbox
[BOX2D_MAXY
] = max2(pobbox
[BOX2D_MAXY
], sub
->bbox2d
[BOX2D_MAXY
]);
828 // update sector sound origin
829 po
->posector
->soundorg
= TVec((pobbox
[BOX2D_RIGHT
]+pobbox
[BOX2D_LEFT
])*0.5f
, (pobbox
[BOX2D_TOP
]+pobbox
[BOX2D_BOTTOM
])*0.5f
, 0.0f
);
831 // use polyobject lines
832 line_t
* const *ld
= po
->lines
;
833 for (int f
= po
->numlines
; f
--; ++ld
) {
835 pobbox
[BOX2D_MINX
] = min2(pobbox
[BOX2D_MINX
], (*ld
)->v1
->x
);
836 pobbox
[BOX2D_MINY
] = min2(pobbox
[BOX2D_MINY
], (*ld
)->v1
->y
);
837 pobbox
[BOX2D_MAXX
] = max2(pobbox
[BOX2D_MAXX
], (*ld
)->v1
->x
);
838 pobbox
[BOX2D_MAXY
] = max2(pobbox
[BOX2D_MAXY
], (*ld
)->v1
->y
);
840 pobbox
[BOX2D_MINX
] = min2(pobbox
[BOX2D_MINX
], (*ld
)->v2
->x
);
841 pobbox
[BOX2D_MINY
] = min2(pobbox
[BOX2D_MINY
], (*ld
)->v2
->y
);
842 pobbox
[BOX2D_MAXX
] = max2(pobbox
[BOX2D_MAXX
], (*ld
)->v2
->x
);
843 pobbox
[BOX2D_MAXY
] = max2(pobbox
[BOX2D_MAXY
], (*ld
)->v2
->y
);
846 //memcpy(po->bbox2d, pobbox, sizeof(po->bbox2d));
847 CopyBBox2D(po
->bbox2d
, pobbox
);
849 PutPObjInSubsectors(po
);
851 const int xbboxRight
= MapBlock(pobbox
[BOX2D_MAXX
]-BlockMapOrgX
);
852 const int xbboxLeft
= MapBlock(pobbox
[BOX2D_MINX
]-BlockMapOrgX
);
853 const int xbboxTop
= MapBlock(pobbox
[BOX2D_MAXY
]-BlockMapOrgY
);
854 const int xbboxBottom
= MapBlock(pobbox
[BOX2D_MINY
]-BlockMapOrgY
);
856 const int bmxsize
= BlockMapWidth
;
857 const int bmysize
= BlockMapHeight
*bmxsize
;
858 const int bmyend
= xbboxTop
*bmxsize
;
860 po
->bmbbox
[BOX2D_RIGHT
] = xbboxRight
;
861 po
->bmbbox
[BOX2D_LEFT
] = xbboxLeft
;
862 po
->bmbbox
[BOX2D_TOP
] = xbboxTop
;
863 po
->bmbbox
[BOX2D_BOTTOM
] = xbboxBottom
;
865 // add the polyobj to each blockmap section
866 for (int by
= xbboxBottom
*bmxsize
; by
<= bmyend
; by
+= bmxsize
) {
867 if (by
< 0 || by
>= bmysize
) continue;
868 for (int bx
= xbboxLeft
; bx
<= xbboxRight
; ++bx
) {
869 if (bx
>= 0 && bx
< bmxsize
) {
870 polyblock_t
**link
= &PolyBlockMap
[by
+bx
];
872 // create a new link at the current block cell
873 *link
= new polyblock_t
;
874 (*link
)->next
= nullptr;
875 (*link
)->prev
= nullptr;
876 (*link
)->polyobj
= po
;
880 polyblock_t
*tempLink
= *link
;
881 while (tempLink
->next
!= nullptr && tempLink
->polyobj
!= nullptr) tempLink
= tempLink
->next
;
882 if (tempLink
->polyobj
== nullptr) {
883 tempLink
->polyobj
= po
;
885 tempLink
->next
= new polyblock_t
;
886 tempLink
->next
->next
= nullptr;
887 tempLink
->next
->prev
= tempLink
;
888 tempLink
->next
->polyobj
= po
;
894 // relink all mobjs this polyobject may touch (if it is 3d pobj)
895 if (relinkMObjs
&& po
->posector
) {
896 CollectPObjTouchingThingsRoughSectors(poRoughEntityList
, this, po
);
897 // relink all things, so they can get pobj info right
898 for (VEntity
*mobj
: poRoughEntityList
) {
899 //GCon->Logf(NAME_Debug, "pobj #%d: relinking entity %s(%u)", po->tag, mobj->GetClass()->GetName(), mobj->GetUniqueId());
900 mobj
->LinkToWorld(); // relink it
906 //==========================================================================
908 // VLevel::Link3DPolyobjMObjs
910 //==========================================================================
911 void VLevel::Link3DPolyobjMObjs (polyobj_t
*pofirst
, bool skipLink
) {
913 for (polyobj_t
*po
= pofirst
; po
; po
= (skipLink
? nullptr : po
->polink
)) {
914 if (!po
->posector
) continue;
915 CollectPObjTouchingThingsRoughSectors(poRoughEntityList
, this, po
, first
);
918 for (VEntity
*mobj
: poRoughEntityList
) mobj
->LinkToWorld(); // relink
922 //==========================================================================
924 // VLevel::UnlinkPolyobj
926 //==========================================================================
927 void VLevel::UnlinkPolyobj (polyobj_t
*po
) {
928 const int xbboxRight
= po
->bmbbox
[BOX2D_RIGHT
];
929 const int xbboxLeft
= po
->bmbbox
[BOX2D_LEFT
];
930 const int xbboxTop
= po
->bmbbox
[BOX2D_TOP
];
931 const int xbboxBottom
= po
->bmbbox
[BOX2D_BOTTOM
];
933 const int bmxsize
= BlockMapWidth
;
934 const int bmysize
= BlockMapHeight
*bmxsize
;
935 const int bmyend
= xbboxTop
*bmxsize
;
937 // remove the polyobj from each blockmap section
938 for (int by
= xbboxBottom
*bmxsize
; by
<= bmyend
; by
+= bmxsize
) {
939 if (by
< 0 || by
>= bmysize
) continue;
940 for (int bx
= xbboxLeft
; bx
<= xbboxRight
; ++bx
) {
941 if (bx
>= 0 && bx
< bmxsize
) {
942 polyblock_t
*link
= PolyBlockMap
[by
+bx
];
943 while (link
!= nullptr && link
->polyobj
!= po
) link
= link
->next
;
944 if (link
) link
->polyobj
= nullptr;
949 po
->RemoveAllSubsectors();
953 //==========================================================================
955 // VLevel::Add3DPolyobjLink
957 //==========================================================================
958 void VLevel::Add3DPolyobjLink (mthing_t
*thing
, int srcpid
, int destpid
) {
959 if (!thing
) Host_Error("Add3DPolyobjLink(): called without a thing");
963 if (thing
->type
== PO_3D_LINK
) isSeq
= false;
964 else if (thing
->type
== PO_3D_LINK_SEQ
) isSeq
= true;
965 else Host_Error("Add3DPolyobjLink(): called with invalid thing");
969 if (!srcpid
|| !destpid
) return;
972 if (srcpid
== destpid
) return;
975 const int idx
= NumPolyLinks3D
++;
977 VPolyLink3D
*nl
= new VPolyLink3D
[NumPolyLinks3D
];
978 for (int f
= 0; f
< NumPolyLinks3D
-1; ++f
) nl
[f
] = PolyLinks3D
[f
];
979 delete[] PolyLinks3D
;
982 nl
[idx
].srcpid
= srcpid
;
983 nl
[idx
].destpid
= destpid
;
984 nl
[idx
].flags
= (isSeq
? VPolyLink3D::Flag_Sequence
: VPolyLink3D::Flag_NothingZero
);
988 //==========================================================================
990 // VLevel::AddPolyAnchorPoint
992 //==========================================================================
993 void VLevel::AddPolyAnchorPoint (mthing_t
*thing
, float x
, float y
, float height
, int tag
) {
994 if (/*tag == 0 ||*/ tag
== 0x7fffffff) { GCon
->Logf(NAME_Error
, "ignored anchor point invalid tag: %d", tag
); return; }
996 ++NumPolyAnchorPoints
;
998 PolyAnchorPoint_t
*Temp
= PolyAnchorPoints
;
999 PolyAnchorPoints
= new PolyAnchorPoint_t
[NumPolyAnchorPoints
];
1001 for (int i
= 0; i
< NumPolyAnchorPoints
-1; ++i
) PolyAnchorPoints
[i
] = Temp
[i
];
1006 PolyAnchorPoint_t
*A
= &PolyAnchorPoints
[NumPolyAnchorPoints
-1];
1011 A
->thingidx
= (thing
? (int)(ptrdiff_t)(thing
-&Things
[0]) : -1);
1015 //==========================================================================
1017 // VLevel::CalcPolyobjCenter2D
1019 //==========================================================================
1020 TVec
VLevel::CalcPolyobjCenter2D (polyobj_t
*po
) noexcept
{
1022 // find bounding box, and use it's center
1023 if (po
->numlines
== 0) Sys_Error("pobj #%d has no lines (internal engine error)", po
->tag
);
1024 float xmin
= +FLT_MAX
;
1025 float ymin
= +FLT_MAX
;
1026 float xmax
= -FLT_MAX
;
1027 float ymax
= -FLT_MAX
;
1028 for (auto &&it
: po
->LineFirst()) {
1029 for (unsigned f
= 0; f
< 2; ++f
) {
1030 const TVec v
= *(f
? it
.line()->v2
: it
.line()->v1
);
1031 xmin
= min2(xmin
, v
.x
);
1032 ymin
= min2(ymin
, v
.y
);
1033 xmax
= max2(xmax
, v
.x
);
1034 ymax
= max2(ymax
, v
.y
);
1037 return TVec((xmin
+xmax
)*0.5f
, (ymin
+ymax
)*0.5f
);
1039 // ssectors can contain split points on the same line
1040 // if i'll simply sum all vertices, our center could be not right
1041 // so i will use only "turning points" (as it should be)
1043 TVec center
= TVec(0.0f
, 0.0f
);
1044 TVec
pdir(0.0f
, 0.0f
);
1045 TVec
lastv1(0.0f
, 0.0f
);
1046 for (auto &&it
: po
->LineFirst()) {
1047 line_t
*ld
= it
.line();
1051 pdir
= ((*ld
->v2
)-lastv1
);
1052 if (pdir
.length2DSquared() >= 1.0f
) {
1053 pdir
= pdir
.normalise();
1058 // are we making a turn here?
1059 const TVec xdir
= ((*ld
->v2
)-lastv1
).normalise();
1060 if (fabsf(xdir
.dot(pdir
)) < 1.0f
-0.0001f
) {
1061 // looks like we did made a turn
1062 // we have a new point
1065 // add it to the sum
1068 // and remember new direction
1069 pdir
= ((*ld
->v2
)-lastv1
).normalise();
1073 // if we have less than three points, something's gone wrong...
1074 if (count
< 3) return TVec::ZeroVector
;
1075 center
= center
/(float)count
;