From 213ed28398a47a08c937be6a66d0af00831d5b45 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 19 Dec 2021 18:53:53 +0100 Subject: [PATCH] - WindowClipper WIP --- .../core/rendering/scene/hw_bunchdrawer.cpp | 50 ++- source/core/rendering/scene/hw_bunchdrawer.h | 3 +- source/core/rendering/scene/hw_clipper.cpp | 344 ++++++++++++++---- source/core/rendering/scene/hw_clipper.h | 16 +- 4 files changed, 330 insertions(+), 83 deletions(-) diff --git a/source/core/rendering/scene/hw_bunchdrawer.cpp b/source/core/rendering/scene/hw_bunchdrawer.cpp index a67dd62a9..108237c47 100644 --- a/source/core/rendering/scene/hw_bunchdrawer.cpp +++ b/source/core/rendering/scene/hw_bunchdrawer.cpp @@ -62,6 +62,7 @@ void BunchDrawer::Init(HWDrawInfo *_di, Clipper* c, vec2_t& view, binangle a1, b clipper = c; viewx = view.x * (1/ 16.f); viewy = view.y * -(1/ 16.f); + viewz = (float)di->Viewpoint.Pos.Z; iview = view; StartScene(); @@ -149,16 +150,12 @@ void BunchDrawer::DeleteBunch(int index) Bunches.Pop(); } -bool BunchDrawer::CheckClip(walltype* wal) +bool BunchDrawer::CheckClip(walltype* wal, float* topclip, float* bottomclip) { auto pt2 = wal->point2Wall(); sectortype* backsector = wal->nextSector(); sectortype* frontsector = wal->sectorp(); - // if one plane is sky on both sides, the line must not clip. - if (frontsector->ceilingstat & backsector->ceilingstat & CSTAT_SECTOR_SKY) return false; - if (frontsector->floorstat & backsector->floorstat & CSTAT_SECTOR_SKY) return false; - float bs_floorheight1; float bs_floorheight2; float bs_ceilingheight1; @@ -177,7 +174,27 @@ bool BunchDrawer::CheckClip(walltype* wal) PlanesAtPoint(backsector, wal->x, wal->y, &bs_ceilingheight1, &bs_floorheight1); PlanesAtPoint(backsector, pt2->x, pt2->y, &bs_ceilingheight2, &bs_floorheight2); - // now check for closed sectors! No idea if we really need the sky checks. We'll see. + *bottomclip = max(max(bs_floorheight1, bs_floorheight2), max(fs_floorheight1, fs_floorheight2)); + if (*bottomclip < viewz) *bottomclip = -FLT_MAX; + + // if one plane is sky on both sides, the line must not clip. + if (frontsector->ceilingstat & backsector->ceilingstat & CSTAT_SECTOR_SKY) + { + // save some processing with outside areas - no need to add to the clipper if back sector is higher. + if (fs_ceilingheight1 <= bs_floorheight1 && fs_ceilingheight2 <= bs_floorheight2) *bottomclip = -FLT_MAX; + *topclip = FLT_MAX; + return false; + } + *topclip = min(min(bs_ceilingheight1, bs_ceilingheight2), min(fs_ceilingheight1, fs_ceilingheight2)); + if (*topclip > viewz) *topclip = FLT_MAX; + + if (frontsector->floorstat & backsector->floorstat & CSTAT_SECTOR_SKY) + { + *bottomclip = -FLT_MAX; + return false; + } + + // now check for closed sectors. if (bs_ceilingheight1 <= fs_floorheight1 && bs_ceilingheight2 <= fs_floorheight2) { // backsector's ceiling is below frontsector's floor. @@ -258,15 +275,30 @@ int BunchDrawer::ClipLine(int aline, bool portal) if (line < 0) return CL_Pass; - if (cline->partner == -1 || (wall[line].cstat & CSTAT_WALL_1WAY) || CheckClip(&wall[line])) + float topclip = 0, bottomclip = 0; + if (cline->partner == -1 || (wall[line].cstat & CSTAT_WALL_1WAY) || CheckClip(&wall[line], &topclip, &bottomclip)) { // one-sided - if (!portal && !dontclip) clipper->AddClipRange(startAngle, endAngle); + if (!portal && !dontclip) + { + clipper->AddClipRange(startAngle, endAngle); + Printf("\nWall %d from %2.3f - %2.3f (blocking)\n", line, bamang(startAngle).asdeg(), bamang(endAngle).asdeg()); + clipper->DumpClipper(); + } return CL_Draw; } else { if (portal) clipper->RemoveClipRange(startAngle, endAngle); + else + { + if (topclip < viewz || bottomclip > viewz) + { + clipper->AddWindowRange(startAngle, endAngle, topclip, bottomclip); + Printf("\nWall %d from %2.3f - %2.3f, (%2.3f, %2.3f) (passing)\n", line, bamang(startAngle).asdeg(), bamang(endAngle).asdeg(), topclip, bottomclip); + clipper->DumpClipper(); + } + } // set potentially visible viewing range for this line's back sector. int nsection = cline->partnersection; @@ -648,7 +680,7 @@ void BunchDrawer::ProcessSection(int sectionnum, bool portal) void BunchDrawer::RenderScene(const int* viewsectors, unsigned sectcount, bool portal) { - //Printf("----------------------------------------- \nstart at sector %d\n", viewsectors[0]); + Printf("----------------------------------------- \nstart at sector %d\n", viewsectors[0]); auto process = [&]() { clipper->Clear(ang1); diff --git a/source/core/rendering/scene/hw_bunchdrawer.h b/source/core/rendering/scene/hw_bunchdrawer.h index 7466cd063..9822d8b3a 100644 --- a/source/core/rendering/scene/hw_bunchdrawer.h +++ b/source/core/rendering/scene/hw_bunchdrawer.h @@ -32,6 +32,7 @@ class BunchDrawer BitArray gotwall; BitArray blockwall; binangle ang1, ang2, angrange; + float viewz; TArray sectionstartang, sectionendang; @@ -49,7 +50,7 @@ private: bool StartBunch(int sectnum, int linenum, binangle startan, binangle endan, bool portal); bool AddLineToBunch(int line, binangle newan); void DeleteBunch(int index); - bool CheckClip(walltype* wal); + bool CheckClip(walltype* wal, float* topclip, float* bottomclip); int ClipLine(int line, bool portal); void ProcessBunch(int bnch); int WallInFront(int wall1, int wall2); diff --git a/source/core/rendering/scene/hw_clipper.cpp b/source/core/rendering/scene/hw_clipper.cpp index 0a7eafd86..405087ec5 100644 --- a/source/core/rendering/scene/hw_clipper.cpp +++ b/source/core/rendering/scene/hw_clipper.cpp @@ -52,6 +52,7 @@ void Clipper::RemoveRange(ClipNode * range) if (range == cliphead) { cliphead = cliphead->next; + if (cliphead) cliphead->prev = nullptr; } else { @@ -62,6 +63,22 @@ void Clipper::RemoveRange(ClipNode * range) Free(range); } +void Clipper::InsertRange(ClipNode* prev, ClipNode* node) +{ + if (prev) + { + node->next = prev->next; + prev->next = node; + } + else + { + node->next = cliphead; + cliphead = node; + } + node->prev = prev; + if (node->next) node->next->prev = node; +} + //----------------------------------------------------------------------------- // // Clear @@ -109,7 +126,7 @@ bool Clipper::IsRangeVisible(int startAngle, int endAngle) while (ci != nullptr && ci->start < endAngle) { - if (startAngle >= ci->start && endAngle <= ci->end) + if (startAngle >= ci->start && endAngle <= ci->end && ci->topclip <= ci->bottomclip) { return false; } @@ -127,99 +144,300 @@ bool Clipper::IsRangeVisible(int startAngle, int endAngle) void Clipper::AddClipRange(int start, int end) { - ClipNode *node, *temp, *prevNode; - if (cliphead) { - //check to see if range contains any old ranges - node = cliphead; + auto node = cliphead; while (node != nullptr && node->start < end) { + // check to see if range contains any old ranges. + // These can be removed, regardless whether they are a window or fully closed. if (node->start >= start && node->end <= end) { - temp = node; + auto temp = node; node = node->next; RemoveRange(temp); } - else if (node->start<=start && node->end>=end) + // check if the new range lies fully within an existing range. + else if (node->start <= start && node->end >= end) { - return; + // existing range is closed, we're done. + if (node->topclip <= node->bottomclip) + { + return; + } + // this splits up a window node so we got to insert two new nodes + else + { + int nodeend = node->end; + node->end = start; + + auto mynode = NewRange(start, end, 0, 0); + InsertRange(node, mynode); + + if (end != nodeend) + { + auto afternode = NewRange(end, nodeend, node->topclip, node->bottomclip); + InsertRange(mynode, afternode); + } + + // We can only delete the old node after being done with its data. + if (node->end == node->start) RemoveRange(node); + + return; + } } else { node = node->next; } } - - //check to see if range overlaps a range (or possibly 2) + + // at this point we know that overlaps can only be at one side because all full overlaps have been resolved already. + // so what follows can at most intersect two other nodes - one at the left and one at the right node = cliphead; while (node != nullptr && node->start <= end) { - if (node->end >= start) + // node overlaps at the start. + if (node->start < start && node->end >= start) { - // we found the first overlapping node - if (node->start > start) - { - // the new range overlaps with this node's start point - node->start = start; - } - - if (node->end < end) + if (node->topclip <= node->bottomclip) { node->end = end; + auto next = node->next; + if (next && next->start <= end) + { + // check if the following range overlaps. We know already that it will go past the end of the newly added range + // (otherwise the first loop above would have taken care of it) so we can skip any checks for the full inclusion case. + if (next->topclip <= next->bottomclip) + { + // next range is closed, so merge the two. + node->end = next->end; + RemoveRange(next); + } + else + { + next->start = end; + } + } + // we're done and do not need to add a new node. + return; } - - ClipNode *node2 = node->next; - while (node2 && node2->start <= node->end) + else { - if (node2->end > node->end) node->end = node2->end; - ClipNode *delnode = node2; - node2 = node2->next; - RemoveRange(delnode); + // if the old node is a window, restrict its size to the left of the new range and continue checking + node->end = start; } - return; } - node = node->next; + // range overlaps at the end. + else if (node->start >= start && node->start <= end) + { + // node is closed - we can just merge this and exit + if (node->topclip <= node->bottomclip) + { + node->start = start; + return; + } + // node is a window - so restrict its size and insert the new node in front of it, + else + { + node->start = end; + + auto mynode = NewRange(start, end, 0, 0); + InsertRange(node->prev, mynode); + return; + } + } + node = node->next; } - - //just add range + + //found no intersections - just add range node = cliphead; - prevNode = nullptr; - temp = NewRange(start, end); - + ClipNode* prevNode = nullptr; + // advance to the place where this can be inserted. while (node != nullptr && node->start < end) { prevNode = node; node = node->next; } - - temp->next = node; - if (node == nullptr) - { - temp->prev = prevNode; - if (prevNode) prevNode->next = temp; - if (!cliphead) cliphead = temp; - } - else - { - if (node == cliphead) - { - cliphead->prev = temp; - cliphead = temp; - } - else - { - temp->prev = prevNode; - prevNode->next = temp; - node->prev = temp; - } - } + auto mynode = NewRange(start, end, 0, 0); + InsertRange(prevNode, mynode); } else { - temp = NewRange(start, end); - cliphead = temp; - return; + cliphead = NewRange(start, end, 0, 0); + } +} + + +//----------------------------------------------------------------------------- +// +// AddWindowRange +// +//----------------------------------------------------------------------------- + +void Clipper::AddWindowRange(int start, int end, float topclip, float bottomclip) +{ + if (cliphead) + { + auto node = cliphead; + while (node != nullptr && node->start < end) + { + // check to see if range contains any old ranges. + + // existing range is part of new one. + if (node->start >= start && node->end <= end) + { + // if old range is a window, make some adjustments. + if (topclip <= node->topclip && bottomclip >= node->bottomclip) + { + // if the new window is more narrow both on top and bottom, we can remove the old range. + auto temp = node; + node = node->next; + RemoveRange(temp); + continue; + } + + // in all other cases we must adjust the node, and recursively process both sub-ranges. + if (topclip < node->topclip) node->topclip = topclip; + if (bottomclip > node->bottomclip) node->bottomclip = bottomclip; + int nodestart = node->start, nodeend = node->end; + // At this point it is just easier to recursively add the sub-ranges because we'd have to run the full program on both anyway, + if (start < nodestart) AddWindowRange(start, nodestart, topclip, bottomclip); + if (end > nodeend) AddWindowRange(nodeend, end, topclip, bottomclip); + return; + } + // check if the new range lies fully within an existing range. + else if (node->start <= start && node->end >= end) + { + // existing range is closed or a more narrow window on both sides, we're done. + if (node->topclip <= node->bottomclip || (topclip >= node->topclip && bottomclip <= node->bottomclip)) + { + return; + } + // this splits up a window node so we got to insert two new nodes + else + { + // adapt the window for the intersection. + if (topclip > node->topclip) topclip = node->topclip; + if (bottomclip < node->bottomclip) bottomclip = node->bottomclip; + + int nodeend = node->end; + node->end = start; + + auto mynode = NewRange(start, end, topclip, bottomclip); + InsertRange(node, mynode); + + if (end != nodeend) + { + auto afternode = NewRange(end, nodeend, node->topclip, node->bottomclip); + InsertRange(mynode, afternode); + } + + // We can only delete the old node after being done with its data. + if (node->end == node->start) RemoveRange(node); + + return; + } + } + else + { + node = node->next; + } + } + + // at this point we know that overlaps can only be at one side because all full overlaps have been resolved already. + // so what follows can at most intersect two other nodes - one at the left and one at the right + node = cliphead; + while (node != nullptr && node->start <= end) + { + // node overlaps at the start. + if (node->start < start && node->end >= start) + { + struct temprange + { + int start, end; + float top, bottom; + }; + temprange ranges[2]; + memset(ranges, -1, sizeof(ranges)); + // only split if this changes the old range, otherwise just truncate + if (node->topclip > node->bottomclip && (topclip < node->topclip || bottomclip > node->bottomclip)) + { + ranges[0].start = start; + ranges[0].end = node->end; + ranges[0].top = min(topclip, node->topclip); + ranges[0].bottom = max(bottomclip, node->bottomclip); + node->end = start; + } + start = node->end; + + // check if the following range overlaps. We know already that it will go past the end of the newly added range + // (otherwise the first loop above would have taken care of it) so we can skip any checks for the full inclusion case. + auto next = node->next; + if (next && next->start <= end) + { + // only split if this changes the old range, otherwise just truncate + if (next->topclip > next->bottomclip && (topclip < next->topclip || bottomclip > next->bottomclip)) + { + ranges[1].start = next->start; + ranges[1].end = end; + ranges[1].top = min(topclip, next->topclip); + ranges[1].bottom = max(bottomclip, next->bottomclip); + next->start = end; + } + end = next->start; + } + auto after = node; + ClipNode* insert; + if (ranges[0].end != -1) + { + insert = NewRange(ranges[0].start, ranges[0].end, ranges[0].top, ranges[0].bottom); + InsertRange(after, insert); + after = insert; + } + insert = NewRange(start, end, topclip, bottomclip); + InsertRange(after, insert); + after = insert; + if (ranges[1].end != -1) + { + insert = NewRange(ranges[1].start, ranges[1].end, ranges[1].top, ranges[1].bottom); + InsertRange(after, insert); + } + return; + } + // range overlaps at the end. + else if (node->start >= start && node->start <= end) + { + auto prev = node->prev; + if (node->topclip > node->bottomclip && (topclip < node->topclip || bottomclip > node->bottomclip)) + { + auto inode = NewRange(start, node->end, min(topclip, node->topclip), max(bottomclip, node->bottomclip)); + node->end = start; + InsertRange(prev, inode); + } + start = node->end; + auto mynode = NewRange(start, end, topclip, bottomclip); + InsertRange(prev, mynode); + return; + } + node = node->next; + } + + //found no intersections - just add range + node = cliphead; + ClipNode* prevNode = nullptr; + // advance to the place where this can be inserted. + while (node != nullptr && node->start < end) + { + prevNode = node; + node = node->next; + } + auto mynode = NewRange(start, end, topclip, bottomclip); + InsertRange(prevNode, mynode); + } + else + { + cliphead = NewRange(start, end, topclip, bottomclip); } } @@ -267,12 +485,8 @@ void Clipper::RemoveClipRange(int start, int end) } else if (node->start < start && node->end > end) { - temp = NewRange(end, node->end); - node->end=start; - temp->next=node->next; - temp->prev=node; - node->next=temp; - if (temp->next) temp->next->prev=temp; + temp = NewRange(end, node->end, node->topclip, node->bottomclip); + InsertRange(node, temp); break; } node = node->next; @@ -289,6 +503,6 @@ void Clipper::DumpClipper() { for (auto node = cliphead; node; node = node->next) { - Printf("Range from %f to %f\n", bamang(node->start).asdeg(), bamang(node->end).asdeg()); + Printf("Range from %2.3f to %2.3f (top = %2.3f, bottom = %2.3f)\n", bamang(node->start).asdeg(), bamang(node->end).asdeg(), node->topclip, node->bottomclip); } } diff --git a/source/core/rendering/scene/hw_clipper.h b/source/core/rendering/scene/hw_clipper.h index ca991b9b8..1b74b856d 100644 --- a/source/core/rendering/scene/hw_clipper.h +++ b/source/core/rendering/scene/hw_clipper.h @@ -14,11 +14,7 @@ class ClipNode ClipNode *prev, *next; int start, end; - - bool operator== (const ClipNode &other) - { - return other.start == start && other.end == end; - } + float topclip, bottomclip; }; @@ -27,14 +23,13 @@ class Clipper FMemArena nodearena; ClipNode * freelist = nullptr; - ClipNode * clipnodes = nullptr; ClipNode * cliphead = nullptr; - void RemoveRange(ClipNode* cn); binangle visibleStart, visibleEnd; public: bool IsRangeVisible(int startangle, int endangle); void AddClipRange(int startangle, int endangle); + void AddWindowRange(int startangle, int endangle, float topclip, float bottomclip); void RemoveClipRange(int startangle, int endangle); public: @@ -59,16 +54,21 @@ private: else return (ClipNode*)nodearena.Alloc(sizeof(ClipNode)); } - ClipNode * NewRange(int start, int end) + ClipNode * NewRange(int start, int end, float top, float bottom) { ClipNode * c = GetNew(); c->start = start; c->end = end; + c->topclip = top; + c->bottomclip = bottom; c->next = c->prev = NULL; return c; } + void RemoveRange(ClipNode* cn); + void InsertRange(ClipNode* prev, ClipNode* node); + public: void SetVisibleRange(angle_t a1, angle_t a2)