Add Hideo's WIP qflight & qfvis.

This commit is contained in:
Ragnvald Maartmann-Moe IV 2002-08-25 23:06:23 +00:00
parent 5567a694d3
commit a3af2d9fa9
23 changed files with 3727 additions and 1 deletions

View file

@ -1,2 +1,2 @@
SUBDIRS=pak qfcc qfdefs qfprogs qwaq wav
SUBDIRS=pak qfcc qfdefs qflight qfprogs qfvis qwaq wav
bin_SCRIPTS=zpak

View file

@ -0,0 +1,7 @@
AUTOMAKE_OPTIONS= foreign
SUBDIRS= include source
man_MANS= qflight.1
EXTRA_DIST= $(man_MANS)

View file

@ -0,0 +1,3 @@
AUTOMAKE_OPTIONS= foreign
EXTRA_DIST= entities.h light.h options.h threads.h

View file

@ -0,0 +1,63 @@
/*
entities.h
(description)
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __entities_h
#define __entities_h
#define DEFAULTLIGHTLEVEL 300
typedef struct epair_s {
struct epair_s *next;
char key[MAX_KEY];
char value[MAX_VALUE];
} epair_t;
typedef struct entity_s {
char classname[64];
vec3_t origin;
float angle;
int light;
int style;
char target[32];
char targetname[32];
struct epair_s *epairs;
struct entity_s *targetent;
} entity_t;
extern entity_t entities[MAX_MAP_ENTITIES];
extern int num_entities;
char *ValueForKey (entity_t *ent, char *key);
void SetKeyValue (entity_t *ent, char *key, char *value);
float FloatForKey (entity_t *ent, char *key);
void GetVectorForKey (entity_t *ent, char *key, vec3_t vec);
void LoadEntities (void);
void WriteEntitiesToString (void);
#endif// __entities_h

View file

@ -0,0 +1,63 @@
/*
light.h
Light tool
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __light_h
#define __light_h
#define ON_EPSILON 0.1
#define MAXLIGHTS 1024
extern float scaledist;
extern float scalecos;
extern float rangescale;
extern int c_culldistplane, c_proper;
extern byte *filebase;
extern vec3_t bsp_origin;
extern vec3_t bsp_xvector;
extern vec3_t bsp_yvector;
extern qboolean extrasamples;
extern float minlights[MAX_MAP_FACES];
void LoadNodes (char *file);
qboolean TestLine (vec3_t start, vec3_t stop);
void LightFace (int surfnum);
void LightLeaf (dleaf_t *leaf);
void MakeTnodes (dmodel_t *bm);
byte *GetFileSpace (int size);
void TransformSample (vec3_t in, vec3_t out);
void RotateSample (vec3_t in, vec3_t out);
#endif// __light_h

View file

@ -0,0 +1,49 @@
/*
options.h
command line options handling
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __options_h
#define __options_h
#include "QF/qtypes.h"
typedef struct {
int verbosity; // 0=silent
int threads;
qboolean extra;
float distance;
float range;
} options_t;
extern options_t options;
extern char *bspfile;
extern const char *this_program;
int DecodeArgs (int argc, char **argv);
void usage (int status);
#endif//__options_h

View file

@ -0,0 +1,49 @@
/*
threads.h
Thread management
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __threads_h
#define __threads_h
#ifdef __alpha
#include <pthread.h>
extern pthread_mutex_t *my_mutex;
#define LOCK pthread_mutex_lock (my_mutex)
#define UNLOCK pthread_mutex_unlock (my_mutex)
#else
#define LOCK
#define UNLOCK
#endif
extern int numthreads;
typedef void (threadfunc_t) (void *);
void InitThreads (void);
void RunThreadsOn (threadfunc_t func);
#endif// __threads_h

98
tools/qflight/qflight.1 Normal file
View file

@ -0,0 +1,98 @@
.\" hey, Emacs: -*- nroff -*-
.\" qfbsp 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; see the file COPYING. If not, write to:
.\"
.\" Free Software Foundation, Inc.
.\" 59 Temple Place, Suite 330
.\" Boston, MA 02111-1307, USA
.\"
.\" Some roff macros, for reference:
.\" .nh disable hyphenation
.\" .hy enable hyphenation
.\" .ad l left justify
.\" .ad b justify to both left and right margins (default)
.\" .nf disable filling
.\" .fi enable filling
.\" .br insert line break
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.\"
.TH QFBSP 1 "24 August, 2002" QuakeForge "QuakeForge User's Manual"
.\" Please update the above date whenever this man page is modified.
.SH NAME
pak \- The QuakeForge BSP Tool
.SH SYNOPSIS
.B qfbsp
<\fBcommand\fP> [\fBoptions\fP] \fIARCHIVE\fP \fIFILE\fP...
.SH DESCRIPTION
\fBqfbsp\fP is a utility for managing pack (*.pak) archives. The format used is
compatible with Quake and Quake II. Pak archives are a convenient way to store
groups of related files. Some engines allow pak archives to be compressed
internally.
.SH COMMANDS
\fBqfbsp\fP accepts the following arguments as a command. Only one command may be
used at a time.
.TP
.B \-c, \-\-create
Create an archive. Overwrites any contents the archive may have had.
.TP
.B \-t, \-\-test
Test an archive for valid formatting.
.TP
.B \-x, \-\-extract
Extract an archive. Overwrites any files in the current directory with the same
names as those inside the archive.
.TP
.B \-h, \-\-help
Show summary of options.
.TP
.B \-V, \-\-version
Show the version of pak.
.SH OPTIONS
\fBpak\fP takes the following arguments as options.
.TP
.B \-f, \-\-file ARCHIVE
Use ARCHIVE as the archive filename, instead of the first non-option argument.
This option is provided for compatibility with older versions of \fBpak\fP.
.TP
.B \-p, \-\-pad
Can be useful when creating an archive. File space is padded to a 4\-byte
boundary, assisting in the speed of loading files from the archive. The file
data itself is not changed.
.TP
.B \-q, \-\-quiet
Inhibit some of pak's normal output.
.TP
.B \-v, \-\-verbose
Display more output than usual.
.SH "EXIT STATUS"
\fBpak\fP returns a zero exit status when it has completed a command
successfully (without error). Otherwise, it returns a non-zero exit status.
.SH BUGS
.PP
The program does not currently handle compression or decompression. This
is planned for a future version.
.PP
The program does not currently handle extraction of individual files inside an
archive. This is planned for a future version.
.PP
The program cannot modify an archive once it has been created.
.SH AUTHORS
Bill Currie (taniwha@quakeforge.net) wrote most of the program.
.PP
Jeff Teunissen (deek@quakeforge.net) wrote the command-line interface and the
documentation.
.SH "SEE ALSO"
.BR quakeforge (1),
.BR qfcc (1)

View file

@ -0,0 +1,14 @@
AUTOMAKE_OPTIONS= foreign
QFLIGHT_LIBS=@QFLIGHT_LIBS@
QFLIGHT_DEPS=@QFLIGHT_DEPS@
QFLIGHT_INCS=@QFLIGHT_INCS@
INCLUDES= -I$(top_srcdir)/include $(QFLIGHT_INCS)
bin_PROGRAMS= qflight
qflight_SOURCES= entities.c ltface.c options.c qflight.c threads.c trace.c
qflight_LDADD= $(QFLIGHT_LIBS)
qflight_DEPENDENCIES= $(QFLIGHT_DEPS)

View file

@ -0,0 +1,311 @@
/*
trace.c
(description)
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.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
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <stdlib.h>
#include "QF/bspfile.h"
#include "QF/dstring.h"
#include "QF/idparse.h"
#include "QF/mathlib.h"
#include "QF/qtypes.h"
#include "QF/sys.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "light.h"
#include "threads.h"
#include "entities.h"
#include "options.h"
entity_t entities[MAX_MAP_ENTITIES];
int num_entities;
/*
ENTITY FILE PARSING
If a light has a targetname, generate a unique style in the 32-63 range
*/
int numlighttargets;
char lighttargets[32][64];
int
LightStyleForTargetname (char *targetname, qboolean alloc)
{
int i;
for (i = 0; i < numlighttargets; i++)
if (!strcmp (lighttargets[i], targetname))
return 32 + i;
if (!alloc)
return -1;
strcpy (lighttargets[i], targetname);
numlighttargets++;
return numlighttargets - 1 + 32;
}
void
MatchTargets (void)
{
int i, j;
for (i = 0; i < num_entities; i++) {
if (!entities[i].target[0])
continue;
for (j = 0; j < num_entities; j++)
if (!strcmp (entities[j].targetname, entities[i].target)) {
entities[i].targetent = &entities[j];
break;
}
if (j == num_entities) {
printf ("WARNING: entity at (%i,%i,%i) (%s) has unmatched "
"target\n",
(int) entities[i].origin[0], (int) entities[i].origin[1],
(int) entities[i].origin[2], entities[i].classname);
continue;
}
// set the style on the source ent for switchable lights
if (entities[j].style) {
char s[16];
entities[i].style = entities[j].style;
sprintf (s, "%i", entities[i].style);
SetKeyValue (&entities[i], "style", s);
}
}
}
void
LoadEntities (void)
{
const char *data;
entity_t *entity;
char key[64];
epair_t *epair;
double vec[3];
int i;
data = dentdata;
// start parsing
num_entities = 0;
// go through all the entities
while (1) {
// parse the opening brace
data = COM_Parse (data);
if (!data)
break;
if (com_token[0] != '{')
fprintf (stderr, "LoadEntities: found %s when expecting {",
com_token);
if (num_entities == MAX_MAP_ENTITIES)
fprintf (stderr, "LoadEntities: MAX_MAP_ENTITIES");
entity = &entities[num_entities];
num_entities++;
// go through all the keys in this entity
while (1) {
int c;
// parse key
data = COM_Parse (data);
if (!data)
fprintf (stderr, "LoadEntities: EOF without closing brace");
if (!strcmp (com_token, "}"))
break;
strcpy (key, com_token);
// parse value
data = COM_Parse (data);
if (!data)
fprintf (stderr, "LoadEntities: EOF without closing brace");
c = com_token[0];
if (c == '}')
fprintf (stderr, "LoadEntities: closing brace without data");
epair = malloc (sizeof (epair_t));
memset (epair, 0, sizeof (epair));
strcpy (epair->key, key);
strcpy (epair->value, com_token);
epair->next = entity->epairs;
entity->epairs = epair;
if (!strcmp (key, "classname"))
strcpy (entity->classname, com_token);
else if (!strcmp (key, "target"))
strcpy (entity->target, com_token);
else if (!strcmp (key, "targetname"))
strcpy (entity->targetname, com_token);
else if (!strcmp (key, "origin")) {
// scan into doubles, then assign
// which makes it vec_t size independent
if (sscanf (com_token, "%lf %lf %lf",
&vec[0], &vec[1], &vec[2]) != 3)
fprintf (stderr, "LoadEntities: not 3 values for origin");
for (i = 0; i < 3; i++)
entity->origin[i] = vec[i];
} else if (!strncmp (key, "light", 5) || !strcmp (key, "_light")) {
entity->light = atof(com_token);
} else if (!strcmp (key, "style")) {
entity->style = atof (com_token);
if ((unsigned) entity->style > 254)
fprintf (stderr, "Bad light style %i (must be 0-254)",
entity->style);
} else if (!strcmp (key, "angle")) {
entity->angle = atof(com_token);
}
}
// all fields have been parsed
if (!strncmp (entity->classname, "light", 5) && !entity->light)
entity->light = DEFAULTLIGHTLEVEL;
if (!strcmp (entity->classname, "light")) {
if (entity->targetname[0] && !entity->style) {
char s[16];
entity->style = LightStyleForTargetname (entity->targetname,
true);
sprintf (s, "%i", entity->style);
SetKeyValue (entity, "style", s);
}
}
}
if (options.verbosity >= 0)
printf ("%d entities read\n", num_entities);
MatchTargets ();
}
char *
ValueForKey (entity_t *ent, char *key)
{
epair_t *ep;
for (ep = ent->epairs; ep; ep = ep->next)
if (!strcmp (ep->key, key))
return ep->value;
return "";
}
void
SetKeyValue (entity_t *ent, char *key, char *value)
{
epair_t *ep;
for (ep = ent->epairs; ep; ep = ep->next)
if (!strcmp (ep->key, key)) {
strcpy (ep->value, value);
return;
}
ep = malloc (sizeof (*ep));
ep->next = ent->epairs;
ent->epairs = ep;
strcpy (ep->key, key);
strcpy (ep->value, value);
}
float
FloatForKey (entity_t *ent, char *key)
{
char *k;
k = ValueForKey (ent, key);
return atof (k);
}
void
GetVectorForKey (entity_t *ent, char *key, vec3_t vec)
{
char *k;
k = ValueForKey (ent, key);
sscanf (k, "%f %f %f", &vec[0], &vec[1], &vec[2]);
}
void
WriteEntitiesToString (void)
{
char *buf, *end;
char line[128];
epair_t *ep;
int i;
buf = dentdata;
end = buf;
*end = 0;
if (options.verbosity >= 0)
printf ("%i switchable light styles\n", numlighttargets);
for (i = 0; i < num_entities; i++) {
ep = entities[i].epairs;
if (!ep)
continue; // ent got removed
strcat (end, "{\n");
end += 2;
for (ep = entities[i].epairs; ep; ep = ep->next) {
sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value);
strcat (end, line);
end += strlen (line);
}
strcat (end, "}\n");
end += 2;
if (end > buf + MAX_MAP_ENTSTRING)
fprintf (stderr, "Entity text too long");
}
entdatasize = end - buf + 1;
}

View file

@ -0,0 +1,571 @@
/*
trace.c
(description)
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] = "$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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 <stdlib.h>
#include "QF/qtypes.h"
#include "QF/sys.h"
#include "QF/dstring.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "light.h"
#include "entities.h"
#include "options.h"
#define SINGLEMAP (18*18*4)
typedef struct {
vec_t lightmaps[MAXLIGHTMAPS][SINGLEMAP];
int numlightstyles;
vec_t *light;
vec_t facedist;
vec3_t facenormal;
int numsurfpt;
vec3_t surfpt[SINGLEMAP];
vec3_t texorg;
vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0]
vec3_t textoworld[2]; // world = texorg + s * textoworld[0]
vec_t exactmins[2], exactmaxs[2];
int texmins[2], texsize[2];
int lightstyles[256];
int surfnum;
dface_t *face;
} lightinfo_t;
int c_bad;
int c_culldistplane, c_proper;
/*
CastRay
Returns the distance between the points, or -1 if blocked
*/
vec_t
CastRay (vec3_t p1, vec3_t p2)
{
int i;
vec_t t;
qboolean trace;
trace = TestLine (p1, p2);
if (!trace)
return -1; // ray was blocked
t = 0;
for (i = 0; i < 3; i++)
t += (p2[i] - p1[i]) * (p2[i] - p1[i]);
if (t == 0)
t = 1; // don't blow up...
return sqrt (t);
}
/*
SAMPLE POINT DETERMINATION
void SetupBlock (dface_t *f) Returns with surfpt[] set
This is a little tricky because the lightmap covers more area than the face.
If done in the straightforward fashion, some of the
sample points will be inside walls or on the other side of walls, causing
false shadows and light bleeds.
To solve this, I only consider a sample point valid if a line can be drawn
between it and the exact midpoint of the face. If invalid, it is adjusted
towards the center until it is valid.
(this doesn't completely work)
*/
/*
CalcFaceVectors
Fills in texorg, worldtotex. and textoworld
*/
void
CalcFaceVectors (lightinfo_t *l)
{
texinfo_t *tex;
int i, j;
vec3_t texnormal;
float distscale;
vec_t dist, len;
tex = &texinfo[l->face->texinfo];
// convert from float to vec_t
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
l->worldtotex[i][j] = tex->vecs[i][j];
// calculate a normal to the texture axis. points can
// be moved along this without changing their S/T
texnormal[0] = tex->vecs[1][1] * tex->vecs[0][2] -
tex->vecs[1][2] * tex->vecs[0][1];
texnormal[1] = tex->vecs[1][2] * tex->vecs[0][0] -
tex->vecs[1][0] * tex->vecs[0][2];
texnormal[2] = tex->vecs[1][0] * tex->vecs[0][1] -
tex->vecs[1][1] * tex->vecs[0][0];
VectorNormalize (texnormal);
// flip it towards plane normal
distscale = DotProduct (texnormal, l->facenormal);
if (!distscale)
fprintf (stderr, "Texture axis perpendicular to face");
if (distscale < 0) {
distscale = -distscale;
VectorSubtract (vec3_origin, texnormal, texnormal);
}
// distscale is the ratio of the distance along the
// texture normal to the distance along the plane normal
distscale = 1 / distscale;
for (i = 0; i < 2; i++) {
len = VectorLength (l->worldtotex[i]);
dist = DotProduct (l->worldtotex[i], l->facenormal);
dist *= distscale;
VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]);
VectorScale (l->textoworld[i], (1 / len) * (1 / len), l->textoworld[i]);
}
// calculate texorg on the texture plane
for (i = 0; i < 3; i++)
l->texorg[i] = -tex->vecs[0][3] * l->textoworld[0][i] -
tex->vecs[1][3] * l->textoworld[1][i];
// project back to the face plane
dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1;
dist *= distscale;
VectorMA (l->texorg, -dist, texnormal, l->texorg);
}
/*
CalcFaceExtents
Fills in s->texmins[] and s->texsize[]
also sets exactmins[] and exactmaxs[]
*/
void
CalcFaceExtents (lightinfo_t *l)
{
dface_t *s;
vec_t mins[2], maxs[2], val;
int i, j, e;
dvertex_t *v;
texinfo_t *tex;
s = l->face;
mins[0] = mins[1] = 999999;
maxs[0] = maxs[1] = -99999;
tex = &texinfo[s->texinfo];
for (i = 0; i < s->numedges; i++) {
e = dsurfedges[s->firstedge + i];
if (e >= 0)
v = dvertexes + dedges[e].v[0];
else
v = dvertexes + dedges[-e].v[1];
for (j = 0; j < 2; j++) {
val = v->point[0] * tex->vecs[j][0] +
v->point[1] * tex->vecs[j][1] +
v->point[2] * tex->vecs[j][2] + tex->vecs[j][3];
if (val < mins[j])
mins[j] = val;
if (val > maxs[j])
maxs[j] = val;
}
}
for (i = 0; i < 2; i++) {
l->exactmins[i] = mins[i];
l->exactmaxs[i] = maxs[i];
mins[i] = floor (mins[i] / 16);
maxs[i] = ceil (maxs[i] / 16);
l->texmins[i] = mins[i];
l->texsize[i] = maxs[i] - mins[i];
if (l->texsize[i] > 17)
fprintf (stderr, "Bad surface extents");
}
}
/*
CalcPoints
For each texture aligned grid point, back project onto the plane
to get the world xyz value of the sample point
*/
void
CalcPoints (lightinfo_t *l)
{
int i;
int s, t, j;
int w, h, step;
vec_t starts, startt, us, ut;
vec_t *surf;
vec_t mids, midt;
vec3_t facemid, move;
// fill in surforg
// the points are biased towards the center of the surface
// to help avoid edge cases just inside walls
surf = l->surfpt[0];
mids = (l->exactmaxs[0] + l->exactmins[0]) / 2;
midt = (l->exactmaxs[1] + l->exactmins[1]) / 2;
for (j = 0; j < 3; j++)
facemid[j] = l->texorg[j] +
l->textoworld[0][j] * mids +
l->textoworld[1][j] * midt;
if (extrasamples) {
// extra filtering
h = (l->texsize[1] + 1) * 2;
w = (l->texsize[0] + 1) * 2;
starts = (l->texmins[0] - 0.5) * 16;
startt = (l->texmins[1] - 0.5) * 16;
step = 8;
} else {
h = l->texsize[1] + 1;
w = l->texsize[0] + 1;
starts = l->texmins[0] * 16;
startt = l->texmins[1] * 16;
step = 16;
}
l->numsurfpt = w * h;
for (t = 0; t < h; t++) {
for (s = 0; s < w; s++, surf += 3) {
us = starts + s * step;
ut = startt + t * step;
// if a line can be traced from surf to facemid,
// the point is good
for (i = 0; i < 6; i++) {
// calculate texture point
for (j = 0; j < 3; j++)
surf[j] = l->texorg[j] +
l->textoworld[0][j] * us +
l->textoworld[1][j] * ut;
if (CastRay (facemid, surf) != -1)
break; // got it
if (i & 1) {
if (us > mids) {
us -= 8;
if (us < mids)
us = mids;
} else {
us += 8;
if (us > mids)
us = mids;
}
} else {
if (ut > midt) {
ut -= 8;
if (ut < midt)
ut = midt;
} else {
ut += 8;
if (ut > midt)
ut = midt;
}
}
// move surf 8 pixels towards the center
VectorSubtract (facemid, surf, move);
VectorNormalize (move);
VectorMA (surf, 8, move, surf);
}
if (i == 2)
c_bad++;
}
}
}
void
SingleLightFace (entity_t *light, lightinfo_t *l)
{
vec_t dist;
vec3_t incoming;
vec_t angle;
vec_t add;
vec_t *surf;
qboolean hit;
int mapnum;
int size;
int c, i;
vec3_t rel;
vec3_t spotvec;
vec_t falloff;
vec_t *lightsamp;
VectorSubtract (light->origin, bsp_origin, rel);
dist = options.distance * (DotProduct (rel, l->facenormal) - l->facedist);
// don't bother with lights behind the surface
if (dist <= 0)
return;
// don't bother with light too far away
if (dist > light->light) {
c_culldistplane++;
return;
}
if (light->targetent) {
VectorSubtract (light->targetent->origin, light->origin, spotvec);
VectorNormalize (spotvec);
if (!light->angle)
falloff = -cos (20 * M_PI / 180);
else
falloff = -cos (light->angle / 2 * M_PI / 180);
} else
falloff = 0;
mapnum = 0;
for (mapnum = 0; mapnum < l->numlightstyles; mapnum++)
if (l->lightstyles[mapnum] == light->style)
break;
lightsamp = l->lightmaps[mapnum];
if (mapnum == l->numlightstyles) {
// init a new light map
if (mapnum == MAXLIGHTMAPS) {
printf ("WARNING: Too many light styles on a face\n");
return;
}
size = (l->texsize[1] + 1) * (l->texsize[0] + 1);
for (i = 0; i < size; i++)
lightsamp[i] = 0;
}
// check it for real
hit = false;
c_proper++;
surf = l->surfpt[0];
for (c = 0; c < l->numsurfpt; c++, surf += 3) {
dist = CastRay (light->origin, surf) * options.distance;
if (dist < 0)
continue; // light doesn't reach
VectorSubtract (light->origin, surf, incoming);
VectorNormalize (incoming);
angle = DotProduct (incoming, l->facenormal);
if (light->targetent) {
// spotlight cutoff
if (DotProduct (spotvec, incoming) > falloff)
continue;
}
angle = (1.0 - scalecos) + scalecos * angle;
add = light->light - dist;
add *= angle;
if (add < 0)
continue;
lightsamp[c] += add;
if (lightsamp[c] > 1)
// ignore real tiny lights
hit = true;
}
if (mapnum == l->numlightstyles && hit) {
l->lightstyles[mapnum] = light->style;
l->numlightstyles++; // the style has some real data now
}
}
void
FixMinlight (lightinfo_t *l)
{
int i, j;
float minlight;
minlight = minlights[l->surfnum];
// if minlight is set, there must be a style 0 light map
if (!minlight)
return;
for (i = 0; i < l->numlightstyles; i++) {
if (l->lightstyles[i] == 0)
break;
}
if (i == l->numlightstyles) {
if (l->numlightstyles == MAXLIGHTMAPS)
return; // oh well..
for (j = 0; j < l->numsurfpt; j++)
l->lightmaps[i][j] = minlight;
l->lightstyles[i] = 0;
l->numlightstyles++;
} else {
for (j = 0; j < l->numsurfpt; j++)
if (l->lightmaps[i][j] < minlight)
l->lightmaps[i][j] = minlight;
}
}
void
LightFace (int surfnum)
{
dface_t *f;
lightinfo_t l;
int s, t;
int i, j, c;
vec_t total;
int size;
int lightmapwidth, lightmapsize;
byte *out;
vec_t *light;
int w, h;
f = dfaces + surfnum;
// some surfaces don't need lightmaps
f->lightofs = -1;
for (j = 0; j < MAXLIGHTMAPS; j++)
f->styles[j] = 255;
if (texinfo[f->texinfo].flags & TEX_SPECIAL)
// non-lit texture
return;
memset(&l, 0, sizeof (l));
l.surfnum = surfnum;
l.face = f;
// rotate plane
VectorCopy (dplanes[f->planenum].normal, l.facenormal);
l.facedist = dplanes[f->planenum].dist;
if (f->side) {
VectorSubtract (vec3_origin, l.facenormal, l.facenormal);
l.facedist = -l.facedist;
}
CalcFaceVectors (&l);
CalcFaceExtents (&l);
CalcPoints (&l);
lightmapwidth = l.texsize[0] + 1;
size = lightmapwidth * (l.texsize[1] + 1);
if (size > SINGLEMAP)
fprintf (stderr, "Bad lightmap size");
for (i = 0; i < MAXLIGHTMAPS; i++)
l.lightstyles[i] = 255;
// cast all lights
l.numlightstyles = 0;
for (i = 0; i < num_entities; i++) {
if (entities[i].light)
SingleLightFace (&entities[i], &l);
}
FixMinlight (&l);
if (!l.numlightstyles)
// no light hitting it
return;
// save out the values
for (i = 0; i < MAXLIGHTMAPS; i++)
f->styles[i] = l.lightstyles[i];
lightmapsize = size * l.numlightstyles;
out = GetFileSpace (lightmapsize);
f->lightofs = out - filebase;
// extra filtering
h = (l.texsize[1] + 1) * 2;
w = (l.texsize[0] + 1) * 2;
for (i = 0; i < l.numlightstyles; i++) {
if (l.lightstyles[i] == 0xff)
fprintf (stderr, "Wrote empty lightmap");
light = l.lightmaps[i];
c = 0;
for (t = 0; t <= l.texsize[1]; t++)
for (s = 0; s <= l.texsize[0]; s++, c++) {
if (extrasamples) {
// filtered sample
total = light[t * 2 * w + s * 2] +
light[t * 2 * w + s * 2 + 1] +
light[(t * 2 + 1) * w + s * 2] +
light[(t * 2 + 1) * w + s * 2 + 1];
total *= 0.25;
} else
total = light[c];
total *= options.range; // scale before clamping
if (total > 255)
total = 255;
if (total < 0)
fprintf (stderr, "light < 0");
*out++ = total;
}
}
}

View file

@ -0,0 +1,145 @@
/*
options.c
command line options handling
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.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 <stdlib.h>
#include "QF/qtypes.h"
#include "options.h"
const char *this_program;
static struct option const long_options[] = {
{"quiet", no_argument, 0, 'q'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{"threads", no_argument, 0, 't'},
{"extra", no_argument, 0, 'e'},
{"distance", no_argument, 0, 'd'},
{"range", no_argument, 0, 'r'},
{"file", no_argument, 0, 'f'},
{NULL, 0, NULL, 0}
};
static const char *short_options =
"-" // magic option parsing mode doohicky (must come first)
"q" // quiet
"v" // verbose
"h" // help
"V" // version
"t" // threads
"e" // extra sampling
"d" // scale distance
"r" // scale range
"f"
;
void
usage (int status)
{
printf ("%s - QuakeForge light tool\n", this_program);
printf ("Usage: %s [options] bspfile\n", this_program);
printf ("Options:\n"
" -q, --quiet Inhibit usual output\n"
" -v, --verbose Display more output than usual\n"
" -h, --help Display this help and exit\n"
" -V, --version Output version information and exit\n"
" -t, --threads [num] Number of threads to use\n"
" -e, --extra Apply extra sampling\n"
" -d, --distance Scale distance\n"
" -r, --range Scale range\n"
" -f, --file BSP file\n\n");
exit (status);
}
int
DecodeArgs (int argc, char **argv)
{
int c;
options.verbosity = 0;
options.threads = 1;
options.extra = false;
options.distance = 1.0;
options.range = 0.5;
bspfile = NULL;
while ((c = getopt_long (argc, argv, short_options, long_options, 0))
!= EOF) {
switch (c) {
case 'q': // quiet
options.verbosity -= 1;
break;
case 'v': // verbose
options.verbosity += 1;
break;
case 'h': // help
usage (0);
break;
case 'V': // version
printf ("%s version %s\n", PACKAGE, VERSION);
exit (0);
break;
case 't': // threads
options.threads = atoi (optarg);
break;
case 'e': // extra
options.extra = true;
break;
case 'd': // scale distance
options.distance = atof (optarg);
break;
case 'r': // scale range
options.range = atof (optarg);
break;
case 'f':
bspfile = strdup (optarg);
break;
default:
usage (1);
}
}
if ((!bspfile) && argv[optind] && *(argv[optind]))
bspfile = strdup (argv[optind++]);
return optind;
}

View file

@ -0,0 +1,164 @@
/*
trace.c
(description)
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] = "$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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 <stdlib.h>
#include "QF/qtypes.h"
#include "QF/sys.h"
#include "QF/dstring.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "light.h"
#include "threads.h"
#include "entities.h"
#include "options.h"
options_t options;
char *bspfile;
float scalecos = 0.5;
byte *filebase, *file_p, *file_end;
dmodel_t *bspmodel;
int bspfileface; // next surface to dispatch
vec3_t bsp_origin;
qboolean extrasamples;
float minlights[MAX_MAP_FACES];
byte *
GetFileSpace (int size)
{
byte *buf;
LOCK;
file_p = (byte *) (((long) file_p + 3) & ~3);
buf = file_p;
file_p += size;
UNLOCK;
if (file_p > file_end)
fprintf (stderr, "GetFileSpace: overrun");
return buf;
}
void
LightThread (void *junk)
{
int i;
while (1) {
LOCK;
i = bspfileface++;
UNLOCK;
if (i >= numfaces)
return;
LightFace (i);
}
}
void
LightWorld (void)
{
filebase = file_p = dlightdata;
file_end = filebase + MAX_MAP_LIGHTING;
RunThreadsOn (LightThread);
lightdatasize = file_p - filebase;
if (options.verbosity >= 0)
printf ("lightdatasize: %i\n", lightdatasize);
}
int
main (int argc, char **argv)
{
double start, stop;
start = Sys_DoubleTime ();
this_program = argv[0];
DecodeArgs (argc, argv);
if (!bspfile) {
fprintf (stderr, "%s: no bsp file specified.\n", this_program);
usage (1);
}
InitThreads ();
COM_StripExtension (bspfile, bspfile);
COM_DefaultExtension (bspfile, ".bsp");
LoadBSPFile (bspfile);
LoadEntities ();
MakeTnodes (&dmodels[0]);
LightWorld ();
WriteEntitiesToString ();
WriteBSPFile (bspfile);
stop = Sys_DoubleTime ();
if (options.verbosity >= 0)
printf ("%5.1f seconds elapsed\n", stop - start);
return 0;
}

View file

@ -0,0 +1,106 @@
/*
trace.c
(description)
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] = "$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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 "QF/qtypes.h"
#include "QF/qendian.h"
#include "threads.h"
#ifdef __alpha
int numthreads = 4;
pthread_mutex_t *my_mutex;
#else
int numthreads = 1;
#endif
void
InitThreads (void)
{
#ifdef __alpha
pthread_mutexattr_t mattrib;
my_mutex = malloc (sizeof (*my_mutex));
if (pthread_mutexattr_create (&mattrib) == -1)
fprintf (stderr, "pthread_mutex_attr_create failed");
if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1)
fprintf (stderr, "pthread_mutexattr_setkind_np failed");
if (pthread_mutex_init (my_mutex, mattrib) == -1)
fprintf (stderr, "pthread_mutex_init failed");
#endif
}
void
RunThreadsOn (threadfunc_t func)
{
#ifdef __alpha
pthread_t work_threads[256];
pthread_addr_t status;
pthread_attr_t attrib;
int i;
if (numthreads == 1) {
func (NULL);
return;
}
if (pthread_attr_create (&attrib) == -1)
fprintf (stderr, "pthread_attr_create failed");
if (pthread_attr_setstacksize (&attrib, 0x100000) == -1)
fprintf (stderr, "pthread_attr_setstacksize failed");
for (i = 0; i < numthreads; i++) {
if (pthread_create (&work_threads[i], attrib, (pthread_startroutine_t) func, (pthread_addr_t) i) == -1)
fprintf (stderr, "pthread_create failed");
}
for (i = 0; i < numthreads; i++) {
if (pthread_join (work_threads[i], &status) == -1)
fprintf (stderr, "pthread_join failed");
}
#else
func (NULL);
#endif
}

View file

@ -0,0 +1,231 @@
/*
trace.c
(description)
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] = "$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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 <stdlib.h>
#include "QF/qtypes.h"
#include "QF/sys.h"
#include "QF/dstring.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "light.h"
typedef struct {
int type;
vec3_t normal;
float dist;
int children[2];
int pad;
} tnode_t;
typedef struct {
vec3_t backpt;
int side;
int node;
} tracestack_t;
tnode_t *tnodes, *tnode_p;
/*
LINE TRACING
The major lighting operation is a point to point visibility test, performed
by recursive subdivision of the line by the BSP tree.
*/
/*
MakeTnode
Converts the disk node structure into the efficient tracing structure
*/
void
MakeTnode (int nodenum)
{
tnode_t *t;
dplane_t *plane;
int i;
dnode_t *node;
t = tnode_p++;
node = dnodes + nodenum;
plane = dplanes + node->planenum;
t->type = plane->type;
VectorCopy (plane->normal, t->normal);
t->dist = plane->dist;
for (i = 0; i < 2; i++) {
if (node->children[i] < 0)
t->children[i] = dleafs[-node->children[i] - 1].contents;
else {
t->children[i] = tnode_p - tnodes;
MakeTnode (node->children[i]);
}
}
}
/*
MakeTnodes
Loads the node structure out of a .bsp file to be used for light occlusion
*/
void
MakeTnodes (dmodel_t *bm)
{
tnode_p = tnodes = malloc (numnodes * sizeof (tnode_t));
MakeTnode (0);
}
qboolean
TestLine (vec3_t start, vec3_t stop)
{
int node;
float front, back;
tracestack_t *tstack_p;
int side;
float frontx, fronty, frontz, backx, backy, backz;
tracestack_t tracestack[64];
tnode_t *tnode;
frontx = start[0];
fronty = start[1];
frontz = start[2];
backx = stop[0];
backy = stop[1];
backz = stop[2];
tstack_p = tracestack;
node = 0;
while (1) {
while (node < 0 && node != CONTENTS_SOLID) {
// pop up the stack for a back side
tstack_p--;
if (tstack_p < tracestack)
return true;
node = tstack_p->node;
// set the hit point for this plane
frontx = backx;
fronty = backy;
frontz = backz;
// go down the back side
backx = tstack_p->backpt[0];
backy = tstack_p->backpt[1];
backz = tstack_p->backpt[2];
node = tnodes[tstack_p->node].children[!tstack_p->side];
}
if (node == CONTENTS_SOLID)
return false; // DONE!
tnode = &tnodes[node];
switch (tnode->type) {
case PLANE_X:
front = frontx - tnode->dist;
back = backx - tnode->dist;
break;
case PLANE_Y:
front = fronty - tnode->dist;
back = backy - tnode->dist;
break;
case PLANE_Z:
front = frontz - tnode->dist;
back = backz - tnode->dist;
break;
default:
front = (frontx * tnode->normal[0] +
fronty * tnode->normal[1] +
frontz * tnode->normal[2]) - tnode->dist;
back = (backx * tnode->normal[0] +
backy * tnode->normal[1] +
backz * tnode->normal[2]) - tnode->dist;
break;
}
if (front > -ON_EPSILON && back > -ON_EPSILON)
// if (front > 0 && back > 0)
{
node = tnode->children[0];
continue;
}
if (front < ON_EPSILON && back < ON_EPSILON)
// if (front <= 0 && back <= 0)
{
node = tnode->children[1];
continue;
}
side = front < 0;
front = front / (front - back);
tstack_p->node = node;
tstack_p->side = side;
tstack_p->backpt[0] = backx;
tstack_p->backpt[1] = backy;
tstack_p->backpt[2] = backz;
tstack_p++;
backx = frontx + front * (backx - frontx);
backy = fronty + front * (backy - fronty);
backz = frontz + front * (backz - frontz);
node = tnode->children[side];
}
}

View file

@ -0,0 +1,3 @@
AUTOMAKE_OPTIONS= foreign
EXTRA_DIST= options.h vis.h

View file

@ -0,0 +1,46 @@
/*
options.h
command line options handling
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __options_h
#define __options_h
#include "QF/qtypes.h"
typedef struct {
int verbosity; // 0=silent
int threads;
qboolean minimal;
int level;
char *bspfile;
} options_t;
extern options_t options;
int DecodeArgs (int argc, char **argv);
void usage (int status);
extern const char *this_program;
#endif//__options_h

137
tools/qfvis/include/vis.h Normal file
View file

@ -0,0 +1,137 @@
/*
vis.h
PVS/PHS generation tool
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
Boston, MA 02111-1307, USA
$Id$
*/
#ifndef __vis_h
#define __vis_h
#ifdef __alpha
#include <pthread.h>
extern pthread_mutex_t *my_mutex;
#define LOCK pthread_mutex_lock (my_mutex)
#define UNLOCK pthread_mutex_unlock (my_mutex)
#else
#define LOCK
#define UNLOCK
#endif
#define MAX_PORTALS 32768
#define PORTALFILE "PRT1"
#define ON_EPSILON 0.1
#define MAX_POINTS_ON_WINDING 64
#define MAX_PORTALS_ON_LEAF 128
typedef struct {
vec3_t normal;
float dist;
} plane_t;
typedef struct {
qboolean original; // don't free, it's part of the portal
int numpoints;
vec3_t points[8]; // variable sized
} winding_t;
typedef enum {
stat_none,
stat_working,
stat_done
} vstatus_t;
typedef struct {
plane_t plane; // normal pointing into neighbor
int leaf; // neighbor
winding_t *winding;
vstatus_t status;
byte *visbits;
byte *mightsee;
int nummightsee;
int numcansee;
} portal_t;
typedef struct seperating_plane_s {
struct seperating_plane_s *next;
plane_t plane; // from portal is on positive side
} sep_t;
typedef struct passage_s {
struct passage_s *next;
int from, to; // leaf numbers
sep_t *planes;
} passage_t;
typedef struct leaf_s {
int numportals;
passage_t *passages;
portal_t *portals[MAX_PORTALS_ON_LEAF];
} leaf_t;
typedef struct pstack_s {
struct pstack_s *next;
leaf_t *leaf;
portal_t *portal; // portal exiting
winding_t *source, *pass;
plane_t portalplane;
byte *mightsee; // bit string
} pstack_t;
typedef struct {
byte *leafvis; // bit string
portal_t *base;
pstack_t pstack_head;
} threaddata_t;
extern int numportals;
extern int portalleafs;
extern int c_portaltest;
extern int c_portalpass;
extern int c_portalcheck;
extern int bitbytes;
extern int bitlongs;
extern portal_t *portals;
extern leaf_t *leafs;
extern qboolean showgetleaf;
extern byte *vismap, *vismap_p, *vismap_end; // past visfile
extern byte *uncompressed;
extern int c_portalskip, c_leafskip;
extern int c_vistest, c_mighttest;
extern int c_chains;
void FreeWinding (winding_t *winding);
winding_t *NewWinding (int points);
winding_t *ClipWinding (winding_t *in, plane_t *split, qboolean keepon);
winding_t *CopyWinding (winding_t *winding);
void LeafFlow (int leafnum);
void BasePortalVis (void);
void PortalFlow (portal_t *portal);
void CalcAmbientSounds (void);
#endif// __vis_h

View file

@ -0,0 +1,14 @@
AUTOMAKE_OPTIONS= foreign
QFVIS_LIBS=@QFVIS_LIBS@
QFVIS_DEPS=@QFVIS_DEPS@
QFVIS_INCS=@QFVIS_INCS@
INCLUDES= -I$(top_srcdir)/include $(QFVIS_INCS)
bin_PROGRAMS= qfvis
qfvis_SOURCES= flow.c options.c qfvis.c soundphs.c
qfvis_LDADD= $(QFVIS_LIBS)
qfvis_DEPENDENCIES= $(QFVIS_DEPS)

457
tools/qfvis/source/flow.c Normal file
View file

@ -0,0 +1,457 @@
/*
flow.c
PVS PHS generator tool
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#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/cmd.h"
#include "QF/sys.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "vis.h"
#include "options.h"
int c_chains;
int c_portalskip, c_leafskip;
int c_vistest, c_mighttest;
int c_leafsee, c_portalsee;
int active;
byte portalsee[MAX_PORTALS];
void
CheckStack (leaf_t *leaf, threaddata_t *thread)
{
pstack_t *p;
for (p = thread->pstack_head.next; p; p = p->next)
if (p->leaf == leaf)
fprintf (stderr, "CheckStack: leaf recursion");
}
/*
ClipToSeperators
Source, pass, and target are an ordering of portals.
Generates seperating planes candidates 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
flipclip should be set.
*/
winding_t *
ClipToSeperators (winding_t *source, winding_t *pass, winding_t *target,
qboolean flipclip)
{
int i, j, k, l;
plane_t plane;
vec3_t v1, v2;
float d;
vec_t length;
int counts[3];
qboolean fliptest;
// check all combinations
for (i = 0; i < source->numpoints; i++) {
l = (i + 1) % source->numpoints;
VectorSubtract (source->points[l], source->points[i], v1);
// fing 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++) {
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];
// if points don't make a valid plane, skip it
length = plane.normal[0] * plane.normal[0] +
plane.normal[1] * plane.normal[1] +
plane.normal[2] * plane.normal[2];
if (length < ON_EPSILON)
continue;
length = 1 / sqrt (length);
plane.normal[0] *= length;
plane.normal[1] *= length;
plane.normal[2] *= length;
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) {
// 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) {
// 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) {
VectorSubtract (vec3_origin, plane.normal, plane.normal);
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 (flipclip) {
VectorSubtract (vec3_origin, plane.normal, plane.normal);
plane.dist = -plane.dist;
}
// clip target by the seperating plane
target = ClipWinding (target, &plane, false);
if (!target)
return NULL; // target is not visible
}
}
return target;
}
/*
RecursiveLeafFlow
Flood fill through the leafs
If src_portal is NULL, this is the originating leaf
*/
void
RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack)
{
int i, j;
pstack_t stack;
portal_t *p;
plane_t backplane;
winding_t *source, *target;
leaf_t *leaf;
long *test, *might, *vis;
qboolean more;
c_chains++;
leaf = &leafs[leafnum];
CheckStack(leaf, thread);
// mark the leaf as visible
if (!(thread->leafvis[leafnum >> 3] & (1 << (leafnum & 7)))) {
thread->leafvis[leafnum >> 3] |= 1 << (leafnum & 7);
thread->base->numcansee++;
}
prevstack->next = &stack;
stack.next = NULL;
stack.leaf = leaf;
stack.portal = NULL;
stack.mightsee = malloc(bitbytes);
might = (long *) stack.mightsee;
vis = (long *) thread->leafvis;
// check all portals for flowing into other leafs
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
if (!(prevstack->mightsee[p->leaf >> 3] & (1 << (p->leaf & 7)))) {
c_leafskip++;
continue; // can't possibly see it
}
// if the portal can't see anything we haven't already seen, skip it
if (p->status == stat_done) {
c_vistest++;
test = (long *) p->visbits;
} else {
c_mighttest++;
test = (long *) p->mightsee;
}
more = false;
for (j = 0; j < bitlongs; j++) {
might[j] = ((long *) prevstack->mightsee)[j] & test[j];
if (might[j] & ~vis[j])
more = true;
}
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))
continue; // can't go out a coplanar face
c_portalcheck++;
stack.portal = p;
stack.next = NULL;
target = ClipWinding(p->winding, &thread->pstack_head.portalplane, false);
if (!target)
continue;
if (!prevstack->pass) { // the second leaf can only be blocked if coplanar
stack.source = prevstack->source;
stack.pass = target;
RecursiveLeafFlow (p->leaf, thread, &stack);
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;
}
c_portaltest++;
if (options.level > 0) {
target = ClipToSeperators (source, prevstack->pass, target, false);
if (!target) {
FreeWinding (source);
continue;
}
}
if (options.level > 1) {
target = ClipToSeperators (prevstack->pass, source, target, true);
if (!target) {
FreeWinding (source);
continue;
}
}
if (options.level > 2) {
source = ClipToSeperators (target, prevstack->pass, source, false);
if (!source) {
FreeWinding (target);
continue;
}
}
if (options.level > 3) {
source = ClipToSeperators (prevstack->pass, target, source, true);
if (!source) {
FreeWinding (target);
continue;
}
}
stack.source = source;
stack.pass = target;
c_portalpass++;
// flow through it for real
RecursiveLeafFlow (p->leaf, thread, &stack);
FreeWinding (source);
FreeWinding (target);
}
free (stack.mightsee);
}
void
PortalFlow (portal_t *p)
{
threaddata_t data;
if (p->status != stat_working)
fprintf (stderr, "PortalFlow: reflowed");
p->status = stat_working;
p->visbits = malloc (bitbytes);
memset (p->visbits, 0, bitbytes);
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);
p->status = stat_done;
}
/*
This is a rough first-order aproximation that is used to trivially reject some
of the final calculations.
*/
void
SimpleFlood (portal_t *srcportal, int leafnum)
{
int i;
leaf_t *leaf;
portal_t *p;
if (srcportal->mightsee[leafnum >> 3] & (1 << (leafnum & 7)))
return;
srcportal->mightsee[leafnum >> 3] |= (1 << (leafnum & 7));
c_leafsee++;
leaf = &leafs[leafnum];
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
if (!portalsee[p - portals])
continue;
SimpleFlood (srcportal, p->leaf);
}
}
void
BasePortalVis (void)
{
int i, j, k;
float d;
portal_t *tp, *p;
winding_t *winding;
for (i = 0, p = portals; i < numportals * 2; i++, p++) {
p->mightsee = malloc (bitbytes);
memset (p->mightsee, 0, bitbytes);
c_portalsee = 0;
memset (portalsee, 0, numportals * 2);
for (j = 0, tp = portals; j < numportals * 2; j++, tp++) {
if (j == i)
continue;
winding = tp->winding;
for (k = 0; k < winding->numpoints; k++) {
d = DotProduct (winding->points[k], p->plane.normal) - p->plane.dist;
if (d > ON_EPSILON)
break;
}
if (k == winding->numpoints)
continue; // no points on front
winding = p->winding;
for (k = 0; k < winding->numpoints; k++) {
d = DotProduct (winding->points[k], tp->plane.normal) - tp->plane.dist;
if (d < -ON_EPSILON)
break;
}
if (k == winding->numpoints)
continue; // no points on front
portalsee[j] = 1;
c_portalsee++;
}
c_leafsee = 0;
SimpleFlood (p, p->leaf);
p->nummightsee = c_leafsee;
}
}

View file

@ -0,0 +1,135 @@
/*
options.c
command line options handling
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include "options.h"
const char *this_program;
static struct option const long_options[] = {
{"quiet", no_argument, 0, 'q'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{"threads", no_argument, 0, 't'},
{"minimal", no_argument, 0, 'm'},
{"level", no_argument, 0, 'l'},
{"file", required_argument, 0, 'f'},
{NULL, 0, NULL, 0}
};
static const char *short_options =
"-" // magic option parsing mode doohicky (must come first)
"q" // quiet
"v" // verbose
"h" // help
"V" // version
"t" // threads
"m" // minimal vis
"l" // level
"f" // file
;
void
usage (int status)
{
printf ("%s - QuakeForge PVS/PHS generation tool\n", this_program);
printf ("Usage: %s [options]\n", this_program);
printf (
"Options:\n"
" -q, --quiet Inhibit usual output\n"
" -v, --verbose Display more output than usual\n"
" -h, --help Display this help and exit\n"
" -V, --version Output version information and exit\n"
" -t, --threads [num] Number of threads to use\n"
" -m, --minimal Perform minimal vis'ing\n"
" -l, --level Inhibit usual output\n"
" -f, --file BSP file to vis\n\n"
);
exit (status);
}
int
DecodeArgs (int argc, char **argv)
{
int c;
options.verbosity = 0;
options.bspfile = NULL;
while ((c = getopt_long (argc, argv, short_options, long_options, 0))
!= EOF) {
switch (c) {
case 'q': // quiet
options.verbosity -= 1;
break;
case 'v': // verbose
options.verbosity += 1;
break;
case 'h': // help
usage (0);
break;
case 'V': // version
printf ("%s version %s\n", PACKAGE, VERSION);
exit (0);
break;
case 't': // threads
options.threads = atoi (optarg);
break;
case 'm': // minimal vis
options.minimal = true;
break;
case 'l': // level
options.level = atoi (optarg);
break;
case 'f': // set filename
options.bspfile = strdup (optarg);
break;
default:
usage (1);
}
}
if ((!options.bspfile) && argv[optind] && *(argv[optind]))
options.bspfile = strdup (argv[optind++]);
return optind;
}

884
tools/qfvis/source/qfvis.c Normal file
View file

@ -0,0 +1,884 @@
/*
vis.c
PVS/PHS generation tool
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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 <stdlib.h>
#include "QF/qtypes.h"
#include "QF/cmd.h"
#include "QF/sys.h"
#include "QF/dstring.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "vis.h"
#include "options.h"
#define MAX_THREADS 4
#ifdef __alpha
pthread_mutex_t *my_mutex;
int numthreads = 4;
#else
int numthreads = 1;
#endif
options_t options;
int numportals;
int portalleafs;
int c_portaltest;
int c_portalpass;
int c_portalcheck;
int originalvismapsize;
int totalvis;
int count_sep;
int bitbytes; // (portalleafs + 63)>>3
int bitlongs;
int leafon; // the next leaf to be given to a thread to process
portal_t *portals;
leaf_t *leafs;
qboolean showgetleaf = true;
byte *vismap, *vismap_p, *vismap_end; // past visfile
byte *uncompressed; // [bitbytes * portalleafs]
#if 0
void
NormalizePlane (plane_t *dp)
{
vec_t ax, ay, az;
if (dp->normal[0] == -1.0) {
dp->normal[0] = 1.0;
dp->dist = -dp->dist;
return;
}
if (dp->normal[1] == -1.0) {
dp->normal[1] = 1.0;
dp->dist = -dp->dist;
return;
}
if (dp->normal[2] == -1.0) {
dp->normal[2] = 1.0;
dp->dist = -dp->dist;
return;
}
ax = fabs (dp->normal[0]);
ay = fabs (dp->normal[1]);
az = fabs (dp->normal[2]);
if (ax >= ay && ax >= az) {
if (dp->normal[0] < 0) {
VectorSubtract (vec3_origin, dp->normal, dp->normal);
dp->dist = -dp->dist;
}
return;
}
if (ay >= ax && ay >= az) {
if (dp->normal[1] < 0) {
VectorSubtract (vec3_origin, dp->normal, dp->normal);
dp->dist = -dp->dist;
}
return;
}
if (dp->normal[2] < 0) {
VectorSubtract (vec3_origin, dp->normal, dp->normal);
dp->dist = -dp->dist;
}
}
#endif
void
PlaneFromWinding (winding_t *winding, plane_t *plane)
{
vec3_t v1, v2;
// calc plane
VectorSubtract (winding->points[2], winding->points[1], v1);
VectorSubtract (winding->points[0], winding->points[1], v2);
CrossProduct (v2, v1, plane->normal);
VectorNormalize (plane->normal);
plane->dist = DotProduct (winding->points[0], plane->normal);
}
winding_t *
NewWinding (int points)
{
winding_t *winding;
int size;
if (points > MAX_POINTS_ON_WINDING)
fprintf (stderr, "NewWinding: %i points", points);
size = (int) ((winding_t *) 0)->points[points];
winding = malloc (size);
memset (winding, 0, size);
return winding;
}
void
FreeWinding (winding_t *winding)
{
if (!winding->original)
free (winding);
}
// FIXME: currently unused
void
pw (winding_t *winding)
{
int i;
for (i = 0; i < winding->numpoints; i++)
printf ("(%5.1f, %5.1f, %5.1f)\n", winding->points[i][0],
winding->points[i][1],
winding->points[i][2]);
}
// FIXME: currently unused
void
prl (leaf_t *leaf)
{
int i;
portal_t *portal;
plane_t plane;
for (i = 0; i < leaf->numportals; i++) {
portal = leaf->portals[i];
plane = portal->plane;
printf ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",
(int) (portal - portals),
portal->leaf, plane.dist,
plane.normal[0], plane.normal[1], plane.normal[2]);
}
}
winding_t *
CopyWinding (winding_t *winding)
{
int size;
winding_t *copy;
size = (int) ((winding_t *) 0)->points[winding->numpoints];
copy = malloc (size);
memcpy (copy, winding, size);
copy->original = false;
return copy;
}
/*
ClipWinding
Clips the winding to the plane, returning the new winding on the positive side
Frees the input winding.
If keepon is true, an exactly on-plane winding will be saved, otherwise
it will be clipped away.
*/
winding_t *
ClipWinding (winding_t *in, plane_t *split, qboolean keepon)
{
int i, j;
vec_t dists[MAX_POINTS_ON_WINDING];
int sides[MAX_POINTS_ON_WINDING];
int counts[3];
vec_t dot;
vec_t *p1, *p2;
vec3_t mid;
winding_t *neww;
int maxpts;
counts[0] = counts[1] = counts[2] = 0;
// determine sides for each point
for (i = 0; i < in->numpoints; i++) {
dot = DotProduct (in->points[i], split->normal);
dot -= split->dist;
dists[i] = dot;
if (dot > ON_EPSILON)
sides[i] = SIDE_FRONT;
else if (dot < -ON_EPSILON)
sides[i] = SIDE_BACK;
else {
sides[i] = SIDE_ON;
}
counts[sides[i]]++;
}
sides[i] = sides[0];
dists[i] = dists[0];
if (keepon && !counts[0] && !counts[1])
return in;
if (!counts[0]) {
FreeWinding (in);
return NULL;
}
if (!counts[1])
return in;
maxpts = in->numpoints + 4; // can't use counts[0] + 2 because
// of fp grouping errors
neww = NewWinding (maxpts);
for (i = 0; i < in->numpoints; i++) {
p1 = in->points[i];
if (sides[i] == SIDE_ON) {
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
continue;
}
if (sides[i] == SIDE_FRONT) {
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
}
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
continue;
// generate a split point
p2 = in->points[(i + 1) % in->numpoints];
dot = dists[i] / (dists[i] - dists[i + 1]);
for (j = 0; j < 3; j++) {
// avoid round off error when possible
if (split->normal[j] == 1)
mid[j] = split->dist;
else if (split->normal[j] == -1)
mid[j] = -split->dist;
else
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
VectorCopy (mid, neww->points[neww->numpoints]);
neww->numpoints++;
}
if (neww->numpoints > maxpts)
fprintf (stderr, "ClipWinding: points exceeded estimate");
// free the original winding
FreeWinding (in);
return neww;
}
/*
GetNextPortal
Returns the next portal for a thread to work on
Returns the portals from the least complex, so the later ones can reuse
the earlier information.
*/
portal_t *
GetNextPortal (void)
{
int j;
portal_t *p, *tp;
int min;
LOCK;
min = 99999;
p = NULL;
for (j = 0, tp = portals; j < numportals * 2; j++, tp++) {
if (tp->nummightsee < min && tp->status == stat_none) {
min = tp->nummightsee;
p = tp;
}
}
if (p)
p->status = stat_working;
UNLOCK;
return p;
}
#ifdef __alpha
pthread_addr_t LeafThread (pthread_addr_t thread)
#else
void *LeafThread (int thread)
#endif
{
portal_t *portal;
do {
portal = GetNextPortal ();
if (!portal)
break;
PortalFlow (portal);
if (options.verbosity >= 0)
printf ("portal:%4i mightsee:%4i cansee:%4i\n",
(int) (portal - portals),
portal->nummightsee,
portal->numcansee);
} while (1);
return NULL;
}
int
CompressRow (byte *vis, byte *dest)
{
int j;
int rep;
int visrow;
byte *dest_p;
dest_p = dest;
visrow = (portalleafs + 7) >> 3;
for (j = 0; j < visrow; j++) {
*dest_p++ = vis[j];
if (vis[j])
continue;
rep = 1;
for (j++; j < visrow; j++)
if (vis[j] || rep == 255)
break;
else
rep++;
*dest_p++ = rep;
j--;
}
return dest_p - dest;
}
/*
LeafFlow
Builds the entire visibility list for a leaf
*/
void
LeafFlow (int leafnum)
{
int i, j;
leaf_t *leaf;
byte *outbuffer;
byte compressed[MAX_MAP_LEAFS / 8];
int numvis;
byte *dest;
portal_t *portal;
// flow through all portals, collecting visible bits
outbuffer = uncompressed + leafnum * bitbytes;
leaf = &leafs[leafnum];
for (i = 0; i < leaf->numportals; i++) {
portal = leaf->portals[i];
if (portal->status != stat_done)
fprintf (stderr, "portal not done");
for (j = 0; j < bitbytes; j++)
outbuffer[j] |= portal->visbits[j];
}
if (outbuffer[leafnum >> 3] & (1 << (leafnum & 7)))
fprintf (stderr, "Leaf portals saw into leaf");
outbuffer[leafnum >> 3] |= (1 << (leafnum & 7));
numvis = 0;
for (i = 0; i < portalleafs; i++)
if (outbuffer[i >> 3] & (1 << (i & 3)))
numvis++;
// compress the bit string
if (options.verbosity >= 0)
printf ("leaf %4i : %4i visible\n", leafnum, numvis);
totalvis += numvis;
#if 0
i = (portalleafs + 7) >> 3;
memcpy (compressed, outbuffer, i);
#else
i = CompressRow (outbuffer, compressed);
#endif
dest = vismap_p;
vismap_p += i;
if (vismap_p > vismap_end)
fprintf (stderr, "Vismap expansion overflow");
dleafs[leafnum + 1].visofs = dest - vismap; // leaf 0 is a common solid
memcpy (dest, compressed, i);
}
void
CalcPortalVis (void)
{
int i;
// fastvis just uses mightsee for a very loose bound
if (options.minimal) {
for (i = 0; i < numportals * 2; i++) {
portals[i].visbits = portals[i].mightsee;
portals[i].status = stat_done;
}
return;
}
leafon = 0;
#ifdef __alpha
{
pthread_t work_threads[MAX_THREADS];
pthread_addr_t status;
pthread_attr_t attrib;
pthread_mutexattr_t mattrib;
my_mutex = malloc (sizeof (*my_mutex));
if (pthread_mutexattr_create (&mattrib) == -1)
fprintf (stderr, "pthread_mutex_attr_create failed");
if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1)
fprintf (stderr, "pthread_mutexattr_setkind_np failed");
if (pthread_mutex_init (my_mutex, mattrib) == -1)
fprintf (stderr, "pthread_mutex_init failed");
if (pthread_attr_create (&attrib) == -1)
fprintf (stderr, "pthread_attr_create failed");
if (pthread_attr_setstacksize (&attrib, 0x100000) == -1)
fprintf (stderr, "pthread_attr_setstacksize failed");
for (i = 0; i < numthreads; i++) {
if (pthread_create (&work_threads[i], attrib, LeafThread, (pthread_addr_t) i) == -1)
fprintf (stderr, "pthread_create failed");
}
for (i = 0; i < numthreads; i++) {
if (pthread_join (work_threads[i], &status) == -1)
fprintf (stderr, "pthread_join failed");
}
if (pthread_mutex_destroy (my_mutex) == -1)
fprintf (stderr, "pthread_mutex_destroy failed");
}
#else
LeafThread (0);
#endif
if (options.verbosity >= 0) {
printf ("portalcheck: %i portaltest: %i portalpass: %i\n", c_portalcheck, c_portaltest, c_portalpass);
printf ("c_vistest: %i c_mighttest: %i\n", c_vistest, c_mighttest);
}
}
void
CalcVis (void)
{
int i;
BasePortalVis ();
CalcPortalVis ();
// assemble the leaf vis lists by oring and compressing the portal lists
for (i = 0; i < portalleafs; i++)
LeafFlow (i);
if (options.verbosity >= 0)
printf ("average leafs visible: %i\n", totalvis / portalleafs);
}
qboolean
PlaneCompare (plane_t *p1, plane_t *p2)
{
int i;
if (fabs (p1->dist - p2->dist) > 0.01)
return false;
for (i = 0; i < 3; i++)
if (fabs (p1->normal[i] - p2->normal[i]) > 0.001)
return false;
return true;
}
sep_t *
Findpassages (winding_t *source, winding_t *pass)
{
int i, j, k, l;
plane_t plane;
vec3_t v1, v2;
float d;
double length;
int counts[3];
qboolean fliptest;
sep_t *sep, *list;
list = NULL;
// check all combinations
for (i = 0; i < source->numpoints; i++) {
l = (i + 1) % source->numpoints;
VectorSubtract (source->points[l], source->points[i], v1);
// fing 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++) {
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];
// if points don't make a valid plane, skip it
length = plane.normal[0] * plane.normal[0] +
plane.normal[1] * plane.normal[1] +
plane.normal[2] * plane.normal[2];
if (length < ON_EPSILON)
continue;
length = 1 / sqrt(length);
plane.normal[0] *= length;
plane.normal[1] *= length;
plane.normal[2] *= length;
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) {
// 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) {
// 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) {
VectorSubtract (vec3_origin, plane.normal, plane.normal);
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 pass portal
// save this out
count_sep++;
sep = malloc (sizeof (*sep));
sep->next = list;
list = sep;
sep->plane = plane;
}
}
return list;
}
void
CalcPassages (void)
{
int i, j, k;
int count, count2;
leaf_t *leaf;
portal_t *p1, *p2;
sep_t *sep;
passage_t *passages;
if (options.verbosity >= 0)
printf ("building passages...\n");
count = count2 = 0;
for (i = 0; i < portalleafs; i++) {
leaf = &leafs[i];
for (j = 0; j < leaf->numportals; j++) {
p1 = leaf->portals[j];
for (k = 0; k < leaf->numportals; k++) {
if (k == j)
continue;
count++;
p2 = leaf->portals[k];
// definately can't see into a coplanar portal
if (PlaneCompare (&p1->plane, &p2->plane))
continue;
count2++;
sep = Findpassages (p1->winding, p2->winding);
if (!sep) {
count_sep++;
sep = malloc (sizeof (*sep));
sep->next = NULL;
sep->plane = p1->plane;
}
passages = malloc (sizeof (*passages));
passages->planes = sep;
passages->from = p1->leaf;
passages->to = p2->leaf;
passages->next = leaf->passages;
leaf->passages = passages;
}
}
}
if (options.verbosity >= 0) {
printf ("numpassages: %i (%i)\n", count2, count);
printf ("total passages: %i\n", count_sep);
}
}
void
LoadPortals (char *name)
{
int i, j;
portal_t *portal;
leaf_t *leaf;
winding_t *winding;
plane_t plane;
char magic[80];
FILE *f;
int numpoints;
int leafnums[2];
if (!strcmp (name, "-"))
f = stdin;
else {
f = fopen (name, "r");
if (!f) {
printf ("LoadPortals: couldn't read %s\n", name);
printf ("No vising performed.\n");
exit (1);
}
}
if (fscanf (f, "%79s\n%i\n%i\n", magic, &portalleafs, &numportals) != 3)
fprintf (stderr, "LoadPortals: failed to read header");
if (strcmp (magic, PORTALFILE))
fprintf (stderr, "LoadPortals: not a portal file");
if (options.verbosity >= 0) {
printf ("%4i portalleafs\n", portalleafs);
printf ("%4i numportals\n", numportals);
}
bitbytes = ((portalleafs + 63) & ~63) >> 3;
bitlongs = bitbytes / sizeof (long);
// each file portal is split into two memory portals
portals = malloc (2 * numportals * sizeof (portal_t));
memset (portals, 0, 2 * numportals * sizeof (portal_t));
leafs = malloc (portalleafs * sizeof (leaf_t));
memset (leafs, 0, portalleafs * sizeof (leaf_t));
originalvismapsize = portalleafs * ((portalleafs + 7) / 8);
vismap = vismap_p = dvisdata;
vismap_end = vismap + MAX_MAP_VISIBILITY;
for (i = 0, portal = portals; i < numportals; i++) {
if (fscanf (f, "%i %i %i ", &numpoints, &leafnums[0], &leafnums[1]) != 3)
fprintf (stderr, "LoadPortals: reading portal %i", i);
if (numpoints > MAX_POINTS_ON_WINDING)
fprintf (stderr, "LoadPortals: portal %i has too many points", i);
if ((unsigned) leafnums[0] > portalleafs || (unsigned) leafnums[1] > portalleafs)
fprintf (stderr, "LoadPortals: reading portal %i", i);
winding = portal->winding = NewWinding (numpoints);
winding->original = true;
winding->numpoints = numpoints;
for (j = 0; j < numpoints; j++) {
double v[3];
int k;
// scanf into double, then assign to vec_t
if (fscanf (f, "(%lf %lf %lf ) ", &v[0], &v[1], &v[2]) != 3)
fprintf (stderr, "LoadPortals: reading portal %i", i);
for (k = 0; k < 3; k++)
winding->points[j][k] = v[k];
}
fscanf (f, "\n");
// calc plane
PlaneFromWinding (winding, &plane);
// create forward portal
leaf = &leafs[leafnums[0]];
if (leaf->numportals == MAX_PORTALS_ON_LEAF)
fprintf (stderr, "Leaf with too many portals");
leaf->portals[leaf->numportals] = portal;
leaf->numportals++;
portal->winding = winding;
VectorSubtract (vec3_origin, plane.normal, portal->plane.normal);
portal->plane.dist = -plane.dist;
portal->leaf = leafnums[1];
portal++;
// create backwards portal
leaf = &leafs[leafnums[1]];
if (leaf->numportals == MAX_PORTALS_ON_LEAF)
fprintf (stderr, "Leaf with too many portals");
leaf->portals[leaf->numportals] = portal;
leaf->numportals++;
portal->winding = winding;
portal->plane = plane;
portal->leaf = leafnums[0];
portal++;
}
fclose (f);
}
int
main (int argc, char **argv)
{
double start, stop;
start = Sys_DoubleTime ();
this_program = argv[0];
DecodeArgs (argc, argv);
if (!options.bspfile) {
fprintf (stderr, "%s: no bsp file specified.\n", this_program);
usage (1);
}
COM_StripExtension (options.bspfile, options.bspfile);
COM_DefaultExtension (options.bspfile, ".bsp");
LoadBSPFile (options.bspfile);
// LoadPortals (portalfile);
// uncompressed = malloc (bitbytes * portalleafs);
// memset (uncompressed, 0, bitbytes * portalleafs);
CalcVis ();
if (options.verbosity >= 0)
printf ("c_chains: %i\n", c_chains);
visdatasize = vismap_p - dvisdata;
if (options.verbosity >= 0)
printf ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize);
CalcAmbientSounds ();
WriteBSPFile (options.bspfile);
stop = Sys_DoubleTime ();
if (options.verbosity >= 0)
printf ("%5.1f seconds elapsed\n", stop - start);
return 0;
}

View file

@ -0,0 +1,176 @@
/*
soundphs.c
PHS (possible hearable set) for ambient brushes
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
Boston, MA 02111-1307, USA
*/
static const char rcsid[] =
"$Id$";
#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/cmd.h"
#include "QF/sys.h"
#include "QF/vfile.h"
#include "QF/vfs.h"
#include "QF/bspfile.h"
#include "QF/mathlib.h"
#include "vis.h"
/*
Some textures (sky, water, slime, lava) are considered ambient sound emiters.
Find an aproximate distance to the nearest emiter of each class for each leaf.
*/
void
SurfaceBBox (dface_t *s, vec3_t mins, vec3_t maxs)
{
int i, j;
int e;
int vi;
float *v;
mins[0] = mins[1] = 999999;
maxs[0] = maxs[1] = -99999;
for (i = 0; i < s->numedges; i++) {
e = dsurfedges[s->firstedge + i];
if (e >= 0)
vi = dedges[e].v[0];
else
vi = dedges[-e].v[1];
v = dvertexes[vi].point;
for (j = 0; j < 3; j++) {
if (v[j] < mins[j])
mins[j] = v[j];
if (v[j] > maxs[j])
maxs[j] = v[j];
}
}
}
void
CalcAmbientSounds (void)
{
int i, j, k, l;
dleaf_t *leaf, *hit;
byte *vis;
dface_t *surf;
vec3_t mins, maxs;
float d, maxd;
int ambient_type;
texinfo_t *info;
miptex_t *miptex;
int ofs;
float dists[NUM_AMBIENTS];
float vol;
for (i = 0; i < portalleafs; i++) {
leaf = &dleafs[i + 1];
// clear ambients
for (j = 0; j < NUM_AMBIENTS; j++)
dists[j] = 1020;
vis = &uncompressed[i * bitbytes];
for (j = 0; j < portalleafs; j++) {
if (!(vis[j >> 3] & (1 << (j & 7))))
continue;
// check this leaf for sound textures
hit = &dleafs[j + 1];
for (k = 0; k < hit->nummarksurfaces; k++) {
surf = &dfaces[dmarksurfaces[hit->firstmarksurface + k]];
info = &texinfo[surf->texinfo];
ofs = ((dmiptexlump_t *) dtexdata)->dataofs[info->miptex];
miptex = (miptex_t *) (&dtexdata[ofs]);
if (!strncasecmp (miptex->name, "*water", 6))
ambient_type = AMBIENT_WATER;
else if (!strncasecmp (miptex->name, "sky", 3))
ambient_type = AMBIENT_SKY;
else if (!strncasecmp (miptex->name, "*slime", 6))
ambient_type = AMBIENT_WATER;
else if (!strncasecmp (miptex->name, "*lava", 6))
ambient_type = AMBIENT_LAVA;
else if (!strncasecmp (miptex->name, "*04water", 8))
ambient_type = AMBIENT_WATER;
else
continue;
// find distance from source leaf to polygon
SurfaceBBox (surf, mins, maxs);
maxd = 0;
for (l = 0; l < 3; l++) {
if (mins[l] > leaf->maxs[l])
d = mins[l] - leaf->maxs[l];
else if (maxs[l] < leaf->mins[l])
d = leaf->mins[l] - mins[l];
else
d = 0;
if (d > maxd)
maxd = d;
}
maxd = 0.25;
if (maxd < dists[ambient_type])
dists[ambient_type] = maxd;
}
}
for (j = 0; j < NUM_AMBIENTS; j++) {
if (dists[j] < 100)
vol = 1.0;
else {
vol = 1.0 - dists[2] * 0.002;
if (vol < 0)
vol = 0;
}
leaf->ambient_level[j] = vol * 255;
}
}
}