mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-29 23:42:33 +00:00
659 lines
18 KiB
C++
659 lines
18 KiB
C++
/*
|
|
*
|
|
** 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;
|
|
RemoveRange(prev->next);
|
|
return true;
|
|
}
|
|
ValidateList();
|
|
return false;
|
|
}
|
|
else if (prev->next && node->end >= prev->next->start && prev->next->topclip <= prev->next->bottomclip)
|
|
{
|
|
prev->next->start = node->start;
|
|
Free(node);
|
|
ValidateList();
|
|
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(angle_t rangestart)
|
|
{
|
|
ClipNode *node = cliphead;
|
|
ClipNode *temp;
|
|
|
|
while (node != nullptr)
|
|
{
|
|
temp = node;
|
|
node = node->next;
|
|
Free(temp);
|
|
}
|
|
|
|
cliphead = nullptr;
|
|
|
|
if (visibleStart != 0 || visibleEnd != 0)
|
|
{
|
|
int vstart = int(visibleStart - rangestart);
|
|
if (vstart > 1) AddClipRange(0, vstart);
|
|
|
|
int vend = int(visibleEnd - rangestart);
|
|
if (vend > 0 && vend < INT_MAX) AddClipRange(vend, 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)
|
|
{
|
|
int nodestart = node->start, nodeend = node->end;
|
|
if (node->topclip > node->bottomclip) // we only need to make adjustments to the old node if it is not closed.
|
|
{
|
|
float mtopclip = topclip, mbottomclip = bottomclip;
|
|
mergeClip(node, mtopclip, mbottomclip, viewz);
|
|
// if the new window is closed, we must remove the old range and insert a closed one, so that it gets merged with its neighbours.
|
|
if (mtopclip <= mbottomclip)
|
|
{
|
|
auto prev = node->prev;
|
|
RemoveRange(node);
|
|
auto mynode = NewRange(nodestart, nodeend, 0, 0);
|
|
InsertRange(prev, mynode);
|
|
}
|
|
else
|
|
{
|
|
// in all other cases we must adjust the node's top and bottom
|
|
node->topclip = mtopclip;
|
|
node->bottomclip = mbottomclip;
|
|
}
|
|
}
|
|
// 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)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
// cliphead *can* be null here if a sole existing older range got removed because this one covers it entirely.
|
|
if (cliphead)
|
|
{
|
|
// 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);
|
|
node->end = start;
|
|
InsertRange(node, temp);
|
|
break;
|
|
}
|
|
node = node->next;
|
|
}
|
|
}
|
|
ValidateList();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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", DAngle::fromBam(node->start).Degrees(), DAngle::fromBam(node->end).Degrees(), node->topclip, node->bottomclip);
|
|
}
|
|
}
|