libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / psim / p_polyobj_misc.cpp
blob888b19b469da86635eebf4d1d5657b9a3d94f2ba
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "p_entity.h"
28 #include "p_decal.h"
29 #ifdef CLIENT
30 # include "../drawer.h"
31 #endif
34 enum {
35 PO_3D_LINK_SEQ = 9367,
36 PO_3D_LINK = 9368,
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) {
54 if (clearList) {
55 entList.reset();
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;
79 entList.append(mobj);
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) {
100 if (clearList) {
101 entList.reset();
102 // do it anyway, because we may rely on the fact that `validcount` is incremented
103 XLevel->IncrementValidCount();
106 const int visCount = validcount;
108 // touching sectors
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 //==========================================================================
130 // pobjAddSubsector
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);
138 #ifdef CLIENT
139 if (level->Renderer) level->Renderer->InvalidateStaticLightmapsSubs(sub);
140 #endif
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 {
152 if (!po) return;
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 {
167 Z_Free(segs);
168 Z_Free(verts);
169 segs = nullptr;
170 verts = nullptr;
171 count = amount = 0;
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];
195 if (oldseg) {
196 *res->v1 = *oldseg->v1;
197 *res->v2 = *oldseg->v2;
199 ++count;
200 return res;
204 //==========================================================================
206 // polyobjpart_t::allocInitedSeg
208 //==========================================================================
209 void polyobjpart_t::CreateClipSegs (VLevel *Level) {
210 reset();
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]));
222 seg_t newseg;
223 memset((void *)&newseg, 0, sizeof(newseg));
225 TVec sv1 = *ld->v1;
226 TVec sv2 = *ld->v2;
228 // check for valid line, and setup vertices
229 if (is3d) {
230 if (ld->sidenum[0] >= 0 && ld->frontsector == pobj->posector) {
231 newseg.side = 0;
232 sd = &Level->Sides[ld->sidenum[0]];
233 newseg.v1 = &sv1;
234 newseg.v2 = &sv2;
235 } else if (ld->sidenum[1] >= 0 && ld->backsector == pobj->posector) {
236 newseg.side = 1;
237 sd = &Level->Sides[ld->sidenum[1]];
238 newseg.v1 = &sv2;
239 newseg.v2 = &sv1;
241 } else {
242 // normal pobj
243 if ((ld->flags&ML_TWOSIDED) != 0) continue; // never clip with 2-sided pobj walls (for now)
244 if (ld->sidenum[0] < 0) continue;
245 newseg.side = 0;
246 sd = &Level->Sides[ld->sidenum[0]];
247 newseg.v1 = &sv1;
248 newseg.v2 = &sv2;
250 if (!sd) continue; // just in case
252 newseg.sidedef = sd;
253 newseg.linedef = ld;
254 newseg.frontsector = sub->sector; // just in case
255 newseg.frontsub = sub;
256 newseg.pobj = pobj;
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 //==========================================================================
270 // polyobj_t::Free
272 //==========================================================================
273 void polyobj_t::Free () {
274 delete[] segs;
275 delete[] lines;
276 delete[] originalPts;
277 delete[] segPts;
278 delete[] prevPts;
279 polyobjpart_t *part = parts;
280 while (part) {
281 polyobjpart_t *c = part;
282 part = part->nextpobj;
283 c->Free();
284 delete c;
286 part = freeparts;
287 while (part) {
288 polyobjpart_t *c = part;
289 part = part->nextpobj;
290 c->Free();
291 delete c;
296 //==========================================================================
298 // polyobj_t::RemoveAllSubsectors
300 //==========================================================================
301 void polyobj_t::RemoveAllSubsectors () {
302 while (parts) {
303 polyobjpart_t *part = parts;
304 parts = part->nextpobj;
305 // remove it
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; }
310 if (curr) {
311 if (prev) prev->nextsub = part->nextsub; else sub->polyparts = part->nextsub;
313 // put to free parts pool
314 part->reset(); // remove clipsegs
315 part->sub = nullptr;
316 part->nextsub = nullptr;
317 part->nextpobj = freeparts;
318 freeparts = part;
319 #ifdef CLIENT
320 if (GClLevel && GClLevel->Renderer) GClLevel->Renderer->InvalidateStaticLightmapsSubs(sub);
321 #endif
326 //==========================================================================
328 // polyobj_t::ResetClipSegs
330 //==========================================================================
331 void polyobj_t::ResetClipSegs () {
332 for (polyobjpart_t *part = parts; part; part = part->nextpobj) {
333 part->reset();
338 //==========================================================================
340 // subsector_t::ResetClipSegs
342 //==========================================================================
343 void subsector_t::ResetClipSegs () {
344 for (polyobjpart_t *part = polyparts; part; part = part->nextsub) {
345 part->reset();
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) {
358 vassert(sub);
359 polyobjpart_t *pp = freeparts;
360 if (pp) {
361 freeparts = pp->nextpobj;
362 } else {
363 pp = new polyobjpart_t;
364 memset((void *)pp, 0, sizeof(*pp));
366 // set owner
367 pp->pobj = this;
368 // add part to our list
369 pp->nextpobj = parts;
370 parts = pp;
371 // set subsector
372 pp->sub = sub;
373 // add part to subsector list
374 pp->nextsub = sub->polyparts;
375 sub->polyparts = pp;
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);
400 vassert(PolyTagMap);
401 if (!PolyTagMap->has(PolyObjs[poidx]->tag)) {
402 PolyTagMap->put(PolyObjs[poidx]->tag, poidx);
403 } else {
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];
420 return nullptr;
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) {
455 #if 0
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));
460 #endif
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 ( \
469 ML_BLOCKING| \
470 ML_BLOCKMONSTERS| \
471 ML_TWOSIDED| \
472 /*ML_DONTPEGTOP|*/ \
473 ML_DONTPEGBOTTOM| \
474 /*ML_SOUNDBLOCK|*/ \
475 ML_DONTDRAW| \
476 ML_MAPPED| \
477 ML_REPEAT_SPECIAL| \
478 ML_ADDITIVE| \
479 ML_MONSTERSCANACTIVATE| \
480 ML_BLOCKPLAYERS| \
481 ML_BLOCKEVERYTHING| \
482 ML_BLOCK_FLOATERS| \
483 ML_CLIP_MIDTEX| \
484 ML_WRAP_MIDTEX| \
485 ML_CHECKSWITCHRANGE| \
486 ML_FIRSTSIDEONLY| \
487 ML_BLOCKPROJECTILE| \
488 ML_BLOCKUSE| \
489 ML_BLOCKSIGHT| \
490 ML_BLOCKHITSCAN| \
491 ML_NODECAL| \
495 //==========================================================================
497 // VLevel::FixPolyobjCachedFlags
499 // called from loader
501 //==========================================================================
502 void VLevel::FixPolyobjCachedFlags (polyobj_t *po) {
503 if (!po) return;
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;
510 break;
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;
539 // check flags
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]));
559 // check sides
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()];
597 //po->numlines = 0;
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
621 line_t *curr = ld;
622 // first line should be marked as used last
623 int count = 0;
624 do {
625 // find v2 line
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
633 next = l2;
634 break;
637 if (!next) return false; // no contour continuation found, invalid pobj
638 ++count;
639 curr = next;
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
651 return true;
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 //==========================================================================
669 // pobjCopySecPlaneT
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;
687 // copy everything
688 *dest = *src;
689 // ...and restore something ;-)
690 dest->pic = pic;
691 dest->flags = flags;
692 dest->flipFlags = flipFlags;
693 dest->Alpha = Alpha;
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) {
706 if (!po) return;
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;
720 if (!sec) return;
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;
735 // fix sector
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());
764 // invalidate decals
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;
770 if (sec) {
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) {
785 /* k8 notes
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
798 float pobbox[4];
799 pobbox[BOX2D_MINX] = pobbox[BOX2D_MINY] = +99999.0f;
800 pobbox[BOX2D_MAXX] = pobbox[BOX2D_MAXY] = -99999.0f;
801 if (po->posector) {
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) {
809 // v1
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);
814 // v2
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);
830 } else {
831 // use polyobject lines
832 line_t * const *ld = po->lines;
833 for (int f = po->numlines; f--; ++ld) {
834 // v1
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);
839 // v2
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];
871 if (!(*link)) {
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;
877 continue;
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;
884 } else {
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) {
912 bool first = true;
913 for (polyobj_t *po = pofirst; po; po = (skipLink ? nullptr : po->polink)) {
914 if (!po->posector) continue;
915 CollectPObjTouchingThingsRoughSectors(poRoughEntityList, this, po, first);
916 first = false;
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");
960 vassert(thing);
962 bool isSeq = false;
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");
967 if (!isSeq) {
968 // normal link
969 if (!srcpid || !destpid) return;
970 } else {
971 // sequence link
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;
980 PolyLinks3D = nl;
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];
1000 if (Temp) {
1001 for (int i = 0; i < NumPolyAnchorPoints-1; ++i) PolyAnchorPoints[i] = Temp[i];
1002 delete[] Temp;
1003 Temp = nullptr;
1006 PolyAnchorPoint_t *A = &PolyAnchorPoints[NumPolyAnchorPoints-1];
1007 A->x = x;
1008 A->y = y;
1009 A->height = height;
1010 A->tag = tag;
1011 A->thingidx = (thing ? (int)(ptrdiff_t)(thing-&Things[0]) : -1);
1015 //==========================================================================
1017 // VLevel::CalcPolyobjCenter2D
1019 //==========================================================================
1020 TVec VLevel::CalcPolyobjCenter2D (polyobj_t *po) noexcept {
1021 #if 1
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);
1038 #else
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)
1042 int count = 0;
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();
1048 if (!count) {
1049 // first one
1050 lastv1 = *ld->v1;
1051 pdir = ((*ld->v2)-lastv1);
1052 if (pdir.length2DSquared() >= 1.0f) {
1053 pdir = pdir.normalise();
1054 center += lastv1;
1055 count = 1;
1057 } else {
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
1063 // remember it
1064 lastv1 = *ld->v1;
1065 // add it to the sum
1066 center += lastv1;
1067 ++count;
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;
1076 return center;
1077 #endif