/* * ** gl_clipper.cpp ** ** Handles visibility checks. ** Loosely based on the JDoom clipper. ** **--------------------------------------------------------------------------- ** Copyright 2003 Tim Stump ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "hw_clipper.h" #include "basics.h" #include "build.h" #include "printf.h" //----------------------------------------------------------------------------- // // // //----------------------------------------------------------------------------- void Clipper::ValidateList() { #ifdef _DEBUG // Make sure we catch ordering issues right away in debug mode. auto check = cliphead; while (check) { assert(!check->next || check->end <= check->next->start); check = check->next; } #endif } //----------------------------------------------------------------------------- // // RemoveRange // //----------------------------------------------------------------------------- void Clipper::RemoveRange(ClipNode * range, bool free) { if (range == cliphead) { cliphead = cliphead->next; if (cliphead) cliphead->prev = nullptr; } else { if (range->prev) range->prev->next = range->next; if (range->next) range->next->prev = range->prev; } if (free) Free(range); ValidateList(); } //----------------------------------------------------------------------------- // // InsertRange // //----------------------------------------------------------------------------- bool Clipper::InsertRange(ClipNode* prev, ClipNode* node) { if (node->start == node->end) { Free(node); return false; } assert(node->start <= node->end); assert(!prev || prev->end <= node->start); assert(!prev || !prev->next || prev->next->start >= node->end); if (node->topclip <= node->bottomclip) { if (prev) { if (prev->topclip <= prev->bottomclip && prev->end >= node->start) { prev->end = node->end; Free(node); if (prev->next && prev->end >= prev->next->start && prev->next->topclip <= prev->next->bottomclip) { prev->end = prev->next->end; Free(prev->next); return true; } return false; } else if (prev->next && node->end >= prev->next->start && prev->next->topclip <= prev->next->bottomclip) { prev->next->start = node->start; Free(node); return false; } } } 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; ValidateList(); return false; } //----------------------------------------------------------------------------- // // SplitRange // //----------------------------------------------------------------------------- void Clipper::SplitRange(ClipNode* node, int start, int end, float topclip, float bottomclip) { assert(start < end); int clones = 0; if (end < node->end) { int nodeend = node->end; node->end = end; auto endnode = NewRange(end, nodeend, node->topclip, node->bottomclip); InsertRange(node, endnode); } if (start > node->start) { node->end = start; auto startnode = NewRange(start, end, topclip, bottomclip); InsertRange(node, startnode); } else { // remove and reinsert to do proper checks of the clipping window. auto prev = node->prev; RemoveRange(node, false); node->topclip = topclip; node->bottomclip = bottomclip; InsertRange(prev, node); } } //----------------------------------------------------------------------------- // // Clear // //----------------------------------------------------------------------------- void Clipper::Clear(binangle rangestart) { ClipNode *node = cliphead; ClipNode *temp; while (node != nullptr) { temp = node; node = node->next; Free(temp); } cliphead = nullptr; if (visibleStart.asbam() != 0 || visibleEnd.asbam() != 0) { int vstart = int(visibleStart.asbam() - rangestart.asbam()); if (vstart > 1) AddClipRange(0, vstart - 1); int vend = int(visibleEnd.asbam() - rangestart.asbam()); if (vend > 0 && vend < INT_MAX - 1) AddClipRange(vend + 1, INT_MAX); } } //----------------------------------------------------------------------------- // // IsRangeVisible // //----------------------------------------------------------------------------- bool Clipper::IsRangeVisible(int startAngle, int endAngle) { ClipNode *ci; ci = cliphead; if (endAngle == 0 && ci && ci->start==0) return false; while (ci != nullptr && ci->start < endAngle) { if (startAngle >= ci->start && endAngle <= ci->end && ci->topclip <= ci->bottomclip) { return false; } ci = ci->next; } return true; } //----------------------------------------------------------------------------- // // AddClipRange // //----------------------------------------------------------------------------- void Clipper::AddClipRange(int start, int end) { if (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) { auto temp = node; node = node->next; RemoveRange(temp); } // check if the new range lies fully within an existing range. else if (node->start <= start && node->end >= end) { // if the existing range is closed, we're done. // Other split up the old window to insert the new range in the middle. if (node->topclip > node->bottomclip) { SplitRange(node, start, end, 0, 0); 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) { 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. ValidateList(); return; } else { // if the old node is a window, restrict its size to the left of the new range and continue checking node->end = start; ValidateList(); } } // 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; ValidateList(); 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; } //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, 0, 0); InsertRange(prevNode, mynode); } else { cliphead = NewRange(start, end, 0, 0); } } //----------------------------------------------------------------------------- // // AddWindowRange // //----------------------------------------------------------------------------- void Clipper::AddWindowRange(int start, int end, float topclip, float bottomclip, float viewz) { auto mergeClip = [](ClipNode* node, float& topclip, float& bottomclip, float viewz) { // If the node is already closed, return a closed range. if (node->topclip <= node->bottomclip) { topclip = bottomclip = 0; return; } float mintopclip = min(node->topclip, topclip); float maxbotclip = max(node->bottomclip, bottomclip); if (mintopclip > max(viewz, maxbotclip)) topclip = FLT_MAX; else topclip = mintopclip; if (maxbotclip < min(viewz, mintopclip)) bottomclip = -FLT_MAX; else bottomclip = maxbotclip; }; ClipNode* prevNode = nullptr; 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 (node->topclip > node->bottomclip) // shortcut the common case where the old node is already closed. { float mtopclip = topclip, mbottomclip = bottomclip; mergeClip(node, mtopclip, mbottomclip, viewz); // if old range is a window, make some adjustments. if (mtopclip <= mbottomclip || (mtopclip <= node->topclip && mbottomclip >= 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. node->topclip = mtopclip; node->bottomclip = mbottomclip; } 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, // We must ensure the the new ranges' length are > 0. if (start < nodestart) AddWindowRange(start, nodestart, topclip, bottomclip, viewz); if (end > nodeend) AddWindowRange(nodeend, end, topclip, bottomclip, viewz); return; } //----------------------------------------------------------------------------- // // check if the new range lies fully within an existing range. // //----------------------------------------------------------------------------- else if (node->start <= start && node->end >= end) { // Shortcut if existing range is closed. In this case there's nothing to do. if (node->topclip <= node->bottomclip) { return; } float mtopclip = topclip, mbottomclip = bottomclip; mergeClip(node, mtopclip, mbottomclip, viewz); // existing range is a more narrow window on both sides, we're done. if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip) { return; } else { // adapt the window for the intersection. SplitRange(node, start, end, mtopclip, mbottomclip); 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) { auto next = node->next; // get this before making any edits. //----------------------------------------------------------------------------- // // node overlaps the start of the new range // //----------------------------------------------------------------------------- if (node->start < start && node->end > start) { // if the old range is closed, just shorten the new one and continue. if (node->topclip < node->bottomclip) { start = node->end; ValidateList(); if (start >= end) return; // may have been crushed to a single point. } else { float mtopclip = topclip, mbottomclip = bottomclip; mergeClip(node, mtopclip, mbottomclip, viewz); // if the old range is more narrow than the new one, just shorten the new one and continue. if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip) { start = node->end; ValidateList(); if (start >= end) return; // may have been crushed to a single point. } // the unaltered new range is more narrow than the old one - just shorten the old one and go on. else if (topclip <= bottomclip || (topclip < node->topclip && bottomclip > node->bottomclip)) { node->end = start; ValidateList(); assert(node->end > node->start); // ensured by initial condition. } // if the intersection needs to take properties of both old and new we need to make a split. else { int nodeend = node->end; SplitRange(node, start, nodeend, mtopclip, mbottomclip); start = nodeend; if (start >= end) return; // may have been crushed to a single point. if (mtopclip <= mbottomclip) next = cliphead; // list may have become out of sync. } } } //----------------------------------------------------------------------------- // // nosw overlaps at the end of the new range. // //----------------------------------------------------------------------------- else if (node->start >= start && node->start < end) { auto next = node->next; // get this before making any edits. // if the old range is closed, just shorten the new one and continue. if (node->topclip < node->bottomclip) { end = node->start; ValidateList(); if (start >= end) return; // may have been crushed to a single point. } else { float mtopclip = topclip, mbottomclip = bottomclip; mergeClip(node, mtopclip, mbottomclip, viewz); // if the old range is more narrow than the new one, just shorten the new one and continue. if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip) { end = node->start; ValidateList(); if (start >= end) return; // may have been crushed to a single point. } // the unaltered new range is more narrow than the old one - just shorten the old one and go on. else if (topclip <= bottomclip || (topclip < node->topclip && bottomclip > node->bottomclip)) { node->start = end; ValidateList(); assert(node->end > node->start); } // if the intersection needs to take properties of both old and new we need to make a split. else { int nodestart = node->start; SplitRange(node, nodestart, end, mtopclip, mbottomclip); end = nodestart; if (start >= end) return; // may have been crushed to a single point. if (mtopclip <= mbottomclip) next = cliphead; // list may have become out of sync. } } } node = next; } // we get here if a new range needs to be inserted. node = cliphead; // advance to the place where this can be inserted. while (node != nullptr && node->start < end) { prevNode = node; node = node->next; } assert(!prevNode || prevNode->end <= start); assert(!prevNode || !prevNode->next || prevNode->next->start >= end); assert(prevNode || (cliphead && (!cliphead->next || cliphead->next->start >= end))); } if (topclip > viewz) topclip = FLT_MAX; if (bottomclip < viewz) bottomclip = -FLT_MAX; // only insert a new node if it restricts the window. if (topclip != FLT_MAX || bottomclip != -FLT_MAX) { auto mynode = NewRange(start, end, topclip, bottomclip); InsertRange(prevNode, mynode); } } //----------------------------------------------------------------------------- // // RemoveClipRange // //----------------------------------------------------------------------------- void Clipper::RemoveClipRange(int start, int end) { ClipNode *node, *temp; if (cliphead) { //check to see if range contains any old ranges node = cliphead; while (node != nullptr && node->start < end) { if (node->start >= start && node->end <= end) { temp = node; node = node->next; RemoveRange(temp); } else { node = node->next; } } //check to see if range overlaps a range (or possibly 2) node = cliphead; while (node != nullptr) { if (node->start >= start && node->start <= end) { node->start = end; break; } else if (node->end >= start && node->end <= end) { node->end=start; } else if (node->start < start && node->end > end) { temp = NewRange(end, node->end, node->topclip, node->bottomclip); InsertRange(node, temp); break; } node = node->next; } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void Clipper::DumpClipper() { for (auto node = cliphead; node; node = node->next) { 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); } }