ericw-tools/vis/flow.cc

505 lines
15 KiB
C++

#include <common/threads.hh>
#include <vis/vis.hh>
#include <vis/leafbits.hh>
unsigned long c_chains;
int c_vistest, c_mighttest;
static int c_portalskip;
static int c_leafskip;
/*
==============
ClipToSeperators
Source, pass, and target are an ordering of portals.
Generates seperating planes canidates by taking two points from source and
one point from pass, and clips target by them.
If target is totally clipped away, that portal can not be seen through.
Normal clip keeps target on the same side as pass, which is correct
if the order goes source, pass, target. If the order goes pass,
source, target then we flip the clipping plane. Test levels 0 and 2
use the 'normal', while 1 and 3 require the separating plane flip.
Note that when passing in the 'source' plane, taking a copy, rather than a
pointer, was measurably faster
==============
*/
static winding_t *
ClipToSeperators(const winding_t *source,
const plane_t src_pl,
const winding_t *pass,
winding_t *target, unsigned int test,
pstack_t *stack)
{
int i, j, k, l;
plane_t sep;
vec3_t v1, v2;
vec_t d;
int count;
qboolean fliptest;
vec_t len_sq;
// check all combinations
for (i = 0; i < source->numpoints; i++) {
l = (i + 1) % source->numpoints;
VectorSubtract(source->points[l], source->points[i], v1);
// find a vertex of pass that makes a plane that puts all of the
// vertexes of pass on the front side and all of the vertexes of
// source on the back side
for (j = 0; j < pass->numpoints; j++) {
// Which side of the source portal is this point?
// This also tells us which side of the seperating plane has
// the source portal.
d = DotProduct(pass->points[j], src_pl.normal) - src_pl.dist;
if (d < -ON_EPSILON)
fliptest = true;
else if (d > ON_EPSILON)
fliptest = false;
else
continue; // Point lies in source plane
// Make a plane with the three points
VectorSubtract(pass->points[j], source->points[i], v2);
CrossProduct(v1, v2, sep.normal);
len_sq = sep.normal[0] * sep.normal[0]
+ sep.normal[1] * sep.normal[1]
+ sep.normal[2] * sep.normal[2];
// If points don't make a valid plane, skip it.
if (len_sq < ON_EPSILON)
continue;
VectorScale(sep.normal, 1.0 / sqrt(len_sq), sep.normal);
sep.dist = DotProduct(pass->points[j], sep.normal);
//
// flip the plane if the source portal is backwards
//
if (fliptest) {
VectorSubtract(vec3_origin, sep.normal, sep.normal);
sep.dist = -sep.dist;
}
//
// if all of the pass portal points are now on the positive side,
// this is the seperating plane
//
count = 0;
for (k = 0; k < pass->numpoints; k++) {
if (k == j)
continue;
d = DotProduct(pass->points[k], sep.normal) - sep.dist;
if (d < -ON_EPSILON)
break;
else if (d > ON_EPSILON)
++count;
}
if (k != pass->numpoints)
continue; // points on negative side, not a seperating plane
if (!count)
continue; // planar with seperating plane
//
// flip the normal if we want the back side (tests 1 and 3)
//
if (test & 1) {
VectorSubtract(vec3_origin, sep.normal, sep.normal);
sep.dist = -sep.dist;
}
/* Cache separating planes for tests 0, 1 */
if (test < 2) {
if (stack->numseparators[test] == MAX_SEPARATORS)
Error("MAX_SEPARATORS");
stack->separators[test][stack->numseparators[test]] = sep;
stack->numseparators[test]++;
}
target = ClipStackWinding(target, stack, &sep);
if (!target)
return NULL; // target is not visible
break;
}
}
return target;
}
static int
CheckStack(leaf_t *leaf, threaddata_t *thread)
{
pstack_t *p;
for (p = thread->pstack_head.next; p; p = p->next)
if (p->leaf == leaf)
return 1;
return 0;
}
/*
==================
RecursiveLeafFlow
Flood fill through the leafs
If src_portal is NULL, this is the originating leaf
==================
*/
static void
RecursiveLeafFlow(int leafnum, threaddata_t *thread, pstack_t *prevstack)
{
pstack_t stack;
portal_t *p;
plane_t backplane;
leaf_t *leaf;
int i, j, err, numblocks;
leafblock_t *test, *might, *vis, more;
++c_chains;
leaf = &leafs[leafnum];
/*
* Check we haven't recursed into a leaf already on the stack
*/
err = CheckStack(leaf, thread);
if (err) {
// ericw -- this seems harmless and the fix for https://github.com/ericwa/ericw-tools/issues/261
// causes it to happen a lot.
//logprint("WARNING: %s: recursion on leaf %d\n", __func__, leafnum);
//LogLeaf(leaf);
return;
}
// mark the leaf as visible
if (!TestLeafBit(thread->leafvis, leafnum)) {
SetLeafBit(thread->leafvis, leafnum);
thread->base->numcansee++;
}
prevstack->next = &stack;
stack.next = NULL;
stack.leaf = leaf;
stack.portal = NULL;
stack.numseparators[0] = 0;
stack.numseparators[1] = 0;
for (i = 0; i < STACK_WINDINGS; i++)
stack.freewindings[i] = 1;
stack.mightsee = static_cast<leafbits_t *>(malloc(LeafbitsSize(portalleafs)));
might = stack.mightsee->bits;
vis = thread->leafvis->bits;
// check all portals for flowing into other leafs
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
if (!TestLeafBit(prevstack->mightsee, p->leaf)) {
c_leafskip++;
continue; // can't possibly see it
}
// if the portal can't see anything we haven't allready seen, skip it
if (p->status == pstat_done) {
c_vistest++;
test = p->visbits->bits;
} else {
c_mighttest++;
test = p->mightsee->bits;
}
more = 0;
numblocks = (portalleafs + LEAFMASK) >> LEAFSHIFT;
for (j = 0; j < numblocks; j++) {
might[j] = prevstack->mightsee->bits[j] & test[j];
more |= (might[j] & ~vis[j]);
}
if (!more) {
// can't see anything new
c_portalskip++;
continue;
}
// get plane of portal, point normal into the neighbor leaf
stack.portalplane = p->plane;
VectorSubtract(vec3_origin, p->plane.normal, backplane.normal);
backplane.dist = -p->plane.dist;
if (VectorCompare(prevstack->portalplane.normal, backplane.normal, EQUAL_EPSILON))
continue; // can't go out a coplanar face
c_portalcheck++;
stack.portal = p;
stack.next = NULL;
/*
* Testing visibility of a target portal, from a source portal,
* looking through a pass portal.
*
* source portal => pass portal => target portal
* stack.source => prevstack->pass => stack.pass
*
* If we can see part of the target portal, we use that clipped portal
* as the pass portal into the next leaf.
*/
/* Clip any part of the target portal behind the source portal */
stack.pass = ClipStackWinding(p->winding, &stack,
&thread->pstack_head.portalplane);
if (!stack.pass)
continue;
if (!prevstack->pass) {
// the second leaf can only be blocked if coplanar
stack.source = prevstack->source;
RecursiveLeafFlow(p->leaf, thread, &stack);
FreeStackWinding(stack.pass, &stack);
continue;
}
/* Clip any part of the target portal behind the pass portal */
stack.pass = ClipStackWinding(stack.pass, &stack,
&prevstack->portalplane);
if (!stack.pass)
continue;
/* Clip any part of the source portal in front of the target portal */
stack.source = ClipStackWinding(prevstack->source, &stack,
&backplane);
if (!stack.source) {
FreeStackWinding(stack.pass, &stack);
continue;
}
c_portaltest++;
/* TEST 0 :: source -> pass -> target */
if (testlevel > 0) {
if (stack.numseparators[0]) {
for (j = 0; j < stack.numseparators[0]; j++) {
stack.pass = ClipStackWinding(stack.pass, &stack,
&stack.separators[0][j]);
if (!stack.pass)
break;
}
} else {
/* Using prevstack source for separator cache correctness */
stack.pass = ClipToSeperators(prevstack->source,
thread->pstack_head.portalplane,
prevstack->pass, stack.pass, 0,
&stack);
}
if (!stack.pass) {
FreeStackWinding(stack.source, &stack);
continue;
}
}
/* TEST 1 :: pass -> source -> target */
if (testlevel > 1) {
if (stack.numseparators[1]) {
for (j = 0; j < stack.numseparators[1]; j++) {
stack.pass = ClipStackWinding(stack.pass, &stack,
&stack.separators[1][j]);
if (!stack.pass)
break;
}
} else {
/* Using prevstack source for separator cache correctness */
stack.pass = ClipToSeperators(prevstack->pass,
prevstack->portalplane,
prevstack->source, stack.pass, 1,
&stack);
}
if (!stack.pass) {
FreeStackWinding(stack.source, &stack);
continue;
}
}
/* TEST 2 :: target -> pass -> source */
if (testlevel > 2) {
stack.source = ClipToSeperators(stack.pass, stack.portalplane,
prevstack->pass, stack.source, 2,
&stack);
if (!stack.source) {
FreeStackWinding(stack.pass, &stack);
continue;
}
}
/* TEST 3 :: pass -> target -> source */
if (testlevel > 3) {
stack.source = ClipToSeperators(prevstack->pass,
prevstack->portalplane, stack.pass,
stack.source, 3, &stack);
if (!stack.source) {
FreeStackWinding(stack.pass, &stack);
continue;
}
}
c_portalpass++;
// flow through it for real
RecursiveLeafFlow(p->leaf, thread, &stack);
FreeStackWinding(stack.source, &stack);
FreeStackWinding(stack.pass, &stack);
}
free(stack.mightsee);
}
/*
===============
PortalFlow
===============
*/
void
PortalFlow(portal_t *p)
{
threaddata_t data;
if (p->status != pstat_working)
Error("%s: reflowed", __func__);
p->visbits = static_cast<leafbits_t *>(malloc(LeafbitsSize(portalleafs)));
memset(p->visbits, 0, LeafbitsSize(portalleafs));
memset(&data, 0, sizeof(data));
data.leafvis = p->visbits;
data.base = p;
data.pstack_head.portal = p;
data.pstack_head.source = p->winding;
data.pstack_head.portalplane = p->plane;
data.pstack_head.mightsee = p->mightsee;
RecursiveLeafFlow(p->leaf, &data, &data.pstack_head);
}
/*
============================================================================
This is a rough first-order aproximation that is used to trivially reject
some of the final calculations.
============================================================================
*/
static void
SimpleFlood(portal_t *srcportal, int leafnum, byte *portalsee)
{
int i;
leaf_t *leaf;
portal_t *p;
if (TestLeafBit(srcportal->mightsee, leafnum))
return;
SetLeafBit(srcportal->mightsee, leafnum);
srcportal->nummightsee++;
leaf = &leafs[leafnum];
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
if (!portalsee[p - portals])
continue;
SimpleFlood(srcportal, p->leaf, portalsee);
}
}
/*
==============
BasePortalVis
==============
*/
static void *
BasePortalThread(void *dummy)
{
int i, j, portalnum;
portal_t *p, *tp;
winding_t *w, *tw;
float d;
byte *portalsee;
portalsee = static_cast<byte *>(malloc(sizeof(*portalsee) * numportals * 2));
if (!portalsee)
Error("%s: Out of Memory", __func__);
while (1) {
portalnum = GetThreadWork();
if (portalnum == -1)
break;
p = portals + portalnum;
w = p->winding;
p->mightsee = static_cast<leafbits_t *>(malloc(LeafbitsSize(portalleafs)));
memset(p->mightsee, 0, LeafbitsSize(portalleafs));
memset(portalsee, 0, numportals * 2);
for (i = 0, tp = portals; i < numportals * 2; i++, tp++) {
tw = tp->winding;
if (tp == p)
continue;
// Quick test - completely at the back?
d = DotProduct(tw->origin, p->plane.normal) - p->plane.dist;
if (d < -tw->radius)
continue;
for (j = 0; j < tw->numpoints; j++) {
d = DotProduct(tw->points[j], p->plane.normal) - p->plane.dist;
if (d > -ON_EPSILON) // ericw -- changed from > ON_EPSILON for https://github.com/ericwa/ericw-tools/issues/261
break;
}
if (j == tw->numpoints)
continue; // no points on front
// Quick test - completely on front?
d = DotProduct(w->origin, tp->plane.normal) - tp->plane.dist;
if (d > w->radius)
continue;
for (j = 0; j < w->numpoints; j++) {
d = DotProduct(w->points[j], tp->plane.normal) - tp->plane.dist;
if (d < ON_EPSILON) // ericw -- changed from < -ON_EPSILON for https://github.com/ericwa/ericw-tools/issues/261
break;
}
if (j == w->numpoints)
continue; // no points on back
portalsee[i] = 1;
}
p->nummightsee = 0;
SimpleFlood(p, p->leaf, portalsee);
}
free(portalsee);
return NULL;
}
/*
==============
BasePortalVis
==============
*/
void
BasePortalVis(void)
{
RunThreadsOn(0, numportals * 2, BasePortalThread, NULL);
}