quakeforge/tools/qfvis/source/flow.c

463 lines
12 KiB
C
Raw Normal View History

2002-08-25 23:06:23 +00:00
/*
flow.c
PVS PHS generator tool
Copyright (C) 1996-1997 Id Software, Inc.
2002-08-25 23:06:23 +00:00
Copyright (C) 2002 Colin Thompson
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
2013-03-07 05:31:00 +00:00
Boston, MA 02111-1307, USA
2002-08-25 23:06:23 +00:00
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <getopt.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_IO_H
# include <io.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "QF/alloc.h"
2002-08-25 23:06:23 +00:00
#include "QF/bspfile.h"
#include "QF/cmd.h"
2002-08-25 23:06:23 +00:00
#include "QF/mathlib.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
2002-08-25 23:06:23 +00:00
#include "vis.h"
#include "options.h"
static int
CheckStack (cluster_t *cluster, threaddata_t *thread)
2002-08-25 23:06:23 +00:00
{
2013-03-07 05:31:00 +00:00
pstack_t *portal;
2013-03-07 05:31:00 +00:00
for (portal = thread->pstack_head.next; portal; portal = portal->next)
if (portal->cluster == cluster) {
printf ("CheckStack: cluster recursion\n");
return 1;
}
return 0;
2002-08-25 23:06:23 +00:00
}
static sep_t *
new_separator (threaddata_t *thread)
{
sep_t *sep;
ALLOC (128, sep_t, thread->sep, sep);
return sep;
}
static void
delete_separator (threaddata_t *thread, sep_t *sep)
{
FREE (thread->sep, sep);
}
static void
free_separators (threaddata_t *thread, pstack_t *stack)
{
int i;
for (i = 2; i > 0; i--) {
while (stack->separators[i - 1]) {
sep_t *sep = stack->separators[i - 1];
stack->separators[i - 1] = sep->next;
delete_separator (thread, sep);
}
}
}
2002-08-25 23:06:23 +00:00
/*
ClipToSeparators
2002-08-25 23:06:23 +00:00
Source, pass, and target are an ordering of portals.
2002-08-26 15:05:23 +00:00
Generates seperating planes candidates by taking two points from source and
2002-08-25 23:06:23 +00:00
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
2002-08-26 15:05:23 +00:00
order goes source, pass, target. If the order goes pass, source, target
then test should be odd.
2002-08-25 23:06:23 +00:00
*/
2013-03-07 05:31:00 +00:00
static winding_t *
ClipToSeparators (threaddata_t *thread, pstack_t *stack,
winding_t *source, winding_t *pass, winding_t *target,
int test)
2002-08-25 23:06:23 +00:00
{
2013-03-07 05:31:00 +00:00
float d;
int i, j, k, l;
int counts[3];
qboolean fliptest;
plane_t plane;
vec3_t v1, v2;
vec_t length;
2002-08-25 23:06:23 +00:00
if (test < 2 && stack->separators[test]) {
sep_t *sep;
for (sep = stack->separators[test]; target && sep; sep = sep->next) {
target = ClipWinding (target, &sep->plane, false);
}
return target;
}
// check all combinations
2013-03-07 05:31:00 +00:00
for (i = 0; i < source->numpoints; i++) {
2002-08-25 23:06:23 +00:00
l = (i + 1) % source->numpoints;
VectorSubtract (source->points[l], source->points[i], v1);
2002-09-19 02:37:52 +00:00
// find a vertex of pass that makes a plane that puts all of the
2002-08-25 23:06:23 +00:00
// 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++) {
VectorSubtract (pass->points[j], source->points[i], v2);
plane.normal[0] = v1[1] * v2[2] - v1[2] * v2[1];
plane.normal[1] = v1[2] * v2[0] - v1[0] * v2[2];
plane.normal[2] = v1[0] * v2[1] - v1[1] * v2[0];
2002-08-25 23:06:23 +00:00
length = DotProduct (plane.normal, plane.normal);
2002-08-25 23:06:23 +00:00
// if points don't make a valid plane, skip it
2002-08-25 23:06:23 +00:00
if (length < ON_EPSILON)
continue;
length = 1 / sqrt (length);
VectorScale (plane.normal, length, plane.normal);
2002-08-25 23:06:23 +00:00
plane.dist = DotProduct (pass->points[j], plane.normal);
// find out which side of the generated seperating plane has the
// source portal
fliptest = false;
for (k = 0; k < source->numpoints; k++) {
if (k == i || k == l)
continue;
d = DotProduct (source->points[k], plane.normal) - plane.dist;
if (d < -ON_EPSILON) {
2002-08-25 23:06:23 +00:00
// source is on the negative side, so we want all
// pass and target on the positive side
fliptest = false;
break;
} else if (d > ON_EPSILON) {
2002-08-25 23:06:23 +00:00
// source is on the positive side, so we want all
// pass and target on the negative side
fliptest = true;
break;
}
}
if (k == source->numpoints)
continue; // planar with source portal
// flip the normal if the source portal is backwards
if (fliptest) {
VectorNegate (plane.normal, plane.normal);
2002-08-25 23:06:23 +00:00
plane.dist = -plane.dist;
}
// if all of the pass portal points are now on the positive side,
// this is the seperating plane
counts[0] = counts[1] = counts[2] = 0;
for (k = 0; k < pass->numpoints; k++) {
if (k == j)
continue;
d = DotProduct (pass->points[k], plane.normal) - plane.dist;
if (d < -ON_EPSILON)
break;
else if (d > ON_EPSILON)
counts[0]++;
else
counts[2]++;
}
if (k != pass->numpoints)
continue; // points on negative side, not a seperating plane
if (!counts[0]) {
continue; // planar with seperating plane
}
// flip the normal if we want the back side
if (test & 1) {
VectorNegate (plane.normal, plane.normal);
2002-08-25 23:06:23 +00:00
plane.dist = -plane.dist;
}
// clip target by the seperating plane. If target is null, then
// we're pre-caching the rest of the separators
if (target)
target = ClipWinding (target, &plane, false);
if (test < 2) {
sep_t *sep = new_separator (thread);
sep->plane = plane;
sep->next = stack->separators[test];
stack->separators[test] = sep;
} else if (!target) {
2002-08-25 23:06:23 +00:00
return NULL; // target is not visible
}
break;
2002-08-25 23:06:23 +00:00
}
2013-03-07 05:31:00 +00:00
}
return target;
2002-08-25 23:06:23 +00:00
}
static inline set_t *
select_test_set (portal_t *portal, threaddata_t *thread)
{
set_t *test;
if (portal->status == stat_done) {
thread->stats.vistest++;
test = portal->visbits;
} else {
thread->stats.mighttest++;
test = portal->mightsee;
}
return test;
}
static inline int
mightsee_more (set_t *might, const set_t *prev_might, const set_t *test,
const set_t *vis)
{
set_assign (might, prev_might);
set_intersection (might, test);
return !set_is_subset (vis, might);
}
2002-08-25 23:06:23 +00:00
/*
RecursiveClusterFlow
2002-08-25 23:06:23 +00:00
Flood fill through the clusters
If src_portal is NULL, this is the originating cluster
2002-08-25 23:06:23 +00:00
*/
static void
RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
2002-08-25 23:06:23 +00:00
{
2013-03-07 05:31:00 +00:00
int i;
set_t *might;
2013-03-07 05:31:00 +00:00
const set_t *test, *vis;
cluster_t *cluster;
pstack_t stack;
portal_t *portal;
plane_t backplane;
winding_t *source, *target;
thread->stats.chains++;
2013-03-07 05:31:00 +00:00
cluster = &clusters[clusternum];
if (CheckStack(cluster, thread))
return;
2002-08-25 23:06:23 +00:00
// mark the cluster as visible
if (!set_is_member (thread->clustervis, clusternum)) {
set_add (thread->clustervis, clusternum);
2002-08-25 23:06:23 +00:00
thread->base->numcansee++;
2013-03-07 05:31:00 +00:00
}
2002-08-25 23:06:23 +00:00
2013-03-07 05:31:00 +00:00
prevstack->next = &stack;
stack.next = NULL;
stack.cluster = cluster;
stack.portal = NULL;
LOCK;
2013-03-07 05:31:00 +00:00
stack.mightsee = set_new_size (portalclusters);
UNLOCK;
stack.separators[0] = 0;
stack.separators[1] = 0;
2013-03-07 05:31:00 +00:00
might = stack.mightsee;
vis = thread->clustervis;
2002-08-25 23:06:23 +00:00
// check all portals for flowing into other clusters
2013-03-07 05:31:00 +00:00
for (i = 0; i < cluster->numportals; i++) {
portal = cluster->portals[i];
2002-08-25 23:06:23 +00:00
if (!set_is_member (prevstack->mightsee, portal->cluster))
2002-08-25 23:06:23 +00:00
continue; // can't possibly see it
// if the portal can't see anything we haven't already seen, skip it
test = select_test_set (portal, thread);
if (!mightsee_more (might, prevstack->mightsee, test, vis)) {
// can't see anything new
2002-08-25 23:06:23 +00:00
continue;
}
2002-09-23 22:54:28 +00:00
// get plane of portal, point normal into the neighbor cluster
stack.portalplane = portal->plane;
VectorNegate (portal->plane.normal, backplane.normal);
backplane.dist = -portal->plane.dist;
2002-08-25 23:06:23 +00:00
if (_VectorCompare (prevstack->portalplane.normal, backplane.normal))
2002-08-25 23:06:23 +00:00
continue; // can't go out a coplanar face
thread->stats.portalcheck++;
2002-08-25 23:06:23 +00:00
stack.portal = portal;
2002-08-25 23:06:23 +00:00
stack.next = NULL;
target = ClipWinding (portal->winding,
&thread->pstack_head.portalplane, false);
2002-08-25 23:06:23 +00:00
if (!target)
continue;
2002-08-26 15:05:23 +00:00
if (!prevstack->pass) {
// the second cluster can be blocked only if coplanar
2002-08-25 23:06:23 +00:00
stack.source = prevstack->source;
stack.pass = target;
RecursiveClusterFlow (portal->cluster, thread, &stack);
2002-08-25 23:06:23 +00:00
FreeWinding (target);
continue;
}
target = ClipWinding (target, &prevstack->portalplane, false);
if (!target)
continue;
source = CopyWinding (prevstack->source);
source = ClipWinding (source, &backplane, false);
if (!source) {
FreeWinding (target);
continue;
}
thread->stats.portaltest++;
thread->stats.targettested++;
2002-08-25 23:06:23 +00:00
if (options.level > 0) {
// clip target to the image that would be formed by a laser
// pointing from the edges of source passing though the corners of
// pass
winding_t *old = target;
target = ClipToSeparators (thread, &stack, source, prevstack->pass,
target, 0);
2002-08-25 23:06:23 +00:00
if (!target) {
thread->stats.targetclipped++;
2002-08-25 23:06:23 +00:00
FreeWinding (source);
continue;
}
if (target != old)
thread->stats.targettrimmed++;
2002-08-25 23:06:23 +00:00
}
if (options.level > 1) {
// now pass the laser along the edges of pass from the corners of
// source. the resulting image will have a smaller aree. The
// resulting shape will be the light image produced by a backlit
// source shining past pass. eg, if source and pass are equilateral
// triangles rotated 60 (or 180) degrees relative to each other,
// parallel and in line, target will wind up being a hexagon.
winding_t *old = target;
target = ClipToSeparators (thread, &stack, prevstack->pass, source,
target, 1);
2002-08-25 23:06:23 +00:00
if (!target) {
thread->stats.targetclipped++;
2002-08-25 23:06:23 +00:00
FreeWinding (source);
continue;
}
if (target != old)
thread->stats.targettrimmed++;
2002-08-25 23:06:23 +00:00
}
thread->stats.sourcetested++;
// now do the same as for levels 1 and 2, but trimming source using
// the trimmed target
2002-08-25 23:06:23 +00:00
if (options.level > 2) {
winding_t *old = source;
source = ClipToSeparators (thread, &stack, target, prevstack->pass,
source, 2);
2002-08-25 23:06:23 +00:00
if (!source) {
thread->stats.sourceclipped++;
2002-08-25 23:06:23 +00:00
FreeWinding (target);
continue;
}
if (source != old)
thread->stats.sourcetrimmed++;
2002-08-25 23:06:23 +00:00
}
if (options.level > 3) {
winding_t *old = source;
source = ClipToSeparators (thread, &stack, prevstack->pass, target,
source, 3);
2002-08-25 23:06:23 +00:00
if (!source) {
thread->stats.sourceclipped++;
2002-08-25 23:06:23 +00:00
FreeWinding (target);
continue;
}
if (source != old)
thread->stats.sourcetrimmed++;
2002-08-25 23:06:23 +00:00
}
stack.source = source;
stack.pass = target;
thread->stats.portalpass++;
2002-08-25 23:06:23 +00:00
// flow through it for real
RecursiveClusterFlow (portal->cluster, thread, &stack);
2002-08-25 23:06:23 +00:00
FreeWinding (source);
FreeWinding (target);
2013-03-07 05:31:00 +00:00
}
free_separators (thread, &stack);
2002-08-25 23:06:23 +00:00
LOCK;
2013-03-07 05:31:00 +00:00
set_delete (stack.mightsee);
UNLOCK;
2002-08-25 23:06:23 +00:00
}
void
PortalFlow (threaddata_t *data, portal_t *portal)
2002-08-25 23:06:23 +00:00
{
LOCK;
2013-03-07 05:31:00 +00:00
if (portal->status != stat_selected)
2002-09-22 21:32:36 +00:00
Sys_Error ("PortalFlow: reflowed");
2013-03-07 05:31:00 +00:00
portal->status = stat_working;
portal->visbits = set_new_size (portalclusters);
UNLOCK;
2002-08-25 23:06:23 +00:00
data->clustervis = portal->visbits;
data->base = portal;
2002-08-25 23:06:23 +00:00
memset (&data->pstack_head, 0, sizeof (data->pstack_head));
data->pstack_head.portal = portal;
data->pstack_head.source = portal->winding;
data->pstack_head.portalplane = portal->plane;
data->pstack_head.mightsee = portal->mightsee;
2002-08-25 23:06:23 +00:00
RecursiveClusterFlow (portal->cluster, data, &data->pstack_head);
2002-08-25 23:06:23 +00:00
}