BEU5APLPKXWD7RKKWDR6ACTC7KIVWPV4F62OLQPLCLKPRIWBUZ2QC
#include "common/cmdlib.h"
#include "common/mathlib.h"
#include "common/qfiles.h"
#include "common/threads.h"
static char * help_string =
"4bld supporting v38 and v220 map formats plus QBSP extended limits.\n"
"Usage: 4bld [options] [mapfile | bspfile]\n\n"
" -moddir [path]: Set a mod directory. Default is parent dir of map file.\n"
" -basedir [path]: Set the directory for assets not in moddir. Default is moddir.\n"
" -gamedir [path]: Set game directory, the folder with game executable.\n"
" -threads #: number of CPU threads to use\n"
"BSP pass:\n"
" -bsp: enable bsp pass, requires a .map file as input\n"
" -chop #: Subdivide size.\n"
" Default: 240 Range: 32-1024\n"
" -choplight #: Subdivide size for surface lights.\n"
" Default: 240 Range: 32-1024\n"
" -largebounds or -lb: Increase max map size for supporting engines.\n"
" -micro #: Minimum microbrush size. Default: 0.02\n"
" Suggested range: 0.02 - 1.0\n"
" -nosubdiv: Disable subdivision.\n"
" -qbsp: Greatly expanded map and entity limits for supporting engines.\n"
"VIS pass:\n"
" -vis: enable vis pass, requires a .bsp file as input or bsp pass enabled\n"
" -fast: fast single vis pass"
"RAD pass:\n"
" -rad: enable rad pass, requires a .bsp file as input or bsp and vis passes enabled\n"
" -ambient #\n"
" -bounce #\n"
" -dice\n"
" -direct #\n"
" -entity #\n"
" -extra\n"
" -maxdata #\n"
" -maxlight #\n"
" -noedgefix\n"
" -nudge #\n"
" -saturate #\n"
" -scale #\n"
" -smooth #\n"
" -subdiv\n"
" -sunradscale #\n"
"Debugging tools:\n"
" -block # #: Division tree block size, square\n"
" -blocks # # # #: Div tree block size, rectangular\n"
" -blocksize: map cube size for processing. Default: 1024\n"
" -fulldetail: Change most brushes to detail.\n"
" -leaktest: Perform leak test only.\n"
" -nocsg: No constructive solid geometry.\n"
" -nodetail: No detail brushes.\n"
" -nomerge: Don't merge visible faces per node.\n"
" -noorigfix: Disable texture fix for origin offsets.\n"
" -noprune: Disable node pruning.\n"
" -noshare: Don't look for shared edges on save.\n"
" -noskipfix: Do not automatically set skip contents to zero.\n"
" -notjunc: Disable edge cleanup.\n"
" -nowater: Ignore warp surfaces.\n"
" -noweld: Disable vertex welding.\n"
" -onlyents: Grab the entites and resave.\n"
" -v: Display more verbose output.\n"
" -dump\n"
" -noblock\n"
" -nopvs\n"
" -savetrace\n"
" -tmpin\n"
" -tmpout\n"
;
extern qboolean origfix;
extern qboolean noweld;
extern qboolean nocsg;
extern qboolean noshare;
extern qboolean notjunc;
extern qboolean nowater;
extern qboolean noprune;
extern qboolean nomerge;
extern qboolean nosubdiv;
extern qboolean nodetail;
extern qboolean fulldetail;
extern qboolean onlyents;
extern float microvolume;
extern qboolean leaktest;
extern qboolean use_qbsp;
extern int32_t max_entities;
extern int32_t max_bounds;
extern int32_t block_size;
extern qboolean noskipfix;
extern float subdivide_size;
extern float sublight_size;
extern int32_t block_xl;
extern int32_t block_yl;
extern int32_t block_xh;
extern int32_t block_yh;
extern char inbase[32];
extern char outbase[32];
extern qboolean fastvis;
extern qboolean nosort;
extern qboolean dumppatches;
extern int32_t numbounce;
extern qboolean extrasamples;
extern qboolean noedgefix;
extern int32_t maxdata;
extern float lightscale;
extern float sunradscale;
extern float patch_cutoff;
extern float direct_scale;
extern float entity_scale;
extern qboolean noblock;
extern float smoothing_value;
extern float sample_nudge;
extern float ambient;
extern qboolean save_trace;
extern float maxlight;
extern qboolean dicepatches;
extern float saturation;
extern qboolean nopvs;
void BSP_ProcessArgument(const char * arg);
void VIS_ProcessArgument(const char * arg);
void RAD_ProcessArgument(const char * arg);
int main(int argc, char *argv []) {
char tgamedir[1024] = "";
char tbasedir[1024] = "";
char tmoddir[1024] = "";
qboolean do_bsp = false;
qboolean do_vis = false;
qboolean do_rad = false;
ThreadSetDefault();
int32_t i;
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-bsp")) {
do_bsp = true;
} else if (!strcmp(argv[i], "-vis")) {
do_vis = true;
} else if (!strcmp(argv[i], "-rad")) {
do_rad = true;
} else if (!strcmp(argv[i], "-noorigfix")) {
printf("origfix = false\n");
origfix = false;
} else if (!strcmp(argv[i], "-v")) {
printf("verbose = true\n");
verbose = true;
} else if (!strcmp(argv[i], "-help")) {
printf("%s\n", help_string);
exit(1);
} else if (!strcmp(argv[i],"-threads")) {
numthreads = atoi(argv[i+1]);
printf("threads = %i\n", numthreads);
i++;
} else if (!strcmp(argv[i], "-noweld")) {
printf("noweld = true\n");
noweld = true;
} else if (!strcmp(argv[i], "-nocsg")) {
printf("nocsg = true\n");
nocsg = true;
} else if (!strcmp(argv[i], "-noshare")) {
printf("noshare = true\n");
noshare = true;
} else if (!strcmp(argv[i], "-notjunc")) {
printf("notjunc = true\n");
notjunc = true;
} else if (!strcmp(argv[i], "-nowater")) {
printf("nowater = true\n");
nowater = true;
} else if (!strcmp(argv[i], "-noprune")) {
printf("noprune = true\n");
noprune = true;
} else if (!strcmp(argv[i], "-nomerge")) {
printf("nomerge = true\n");
nomerge = true;
} else if (!strcmp(argv[i], "-nosubdiv")) {
printf("nosubdiv = true\n");
nosubdiv = true;
} else if (!strcmp(argv[i], "-nodetail")) {
printf("nodetail = true\n");
nodetail = true;
} else if (!strcmp(argv[i], "-fulldetail")) {
printf("fulldetail = true\n");
fulldetail = true;
} else if (!strcmp(argv[i], "-onlyents")) {
printf("onlyents = true\n");
onlyents = true;
} else if (!strcmp(argv[i], "-micro")) {
microvolume = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-leaktest")) {
printf("leaktest = true\n");
leaktest = true;
} else if (!strcmp(argv[i], "-qbsp")) {
// qb: qbsp
printf("use_qbsp = true\n");
use_qbsp = true;
max_entities = MAX_MAP_ENTITIES_QBSP;
max_bounds = MAX_MAP_SIZE;
block_size = MAX_BLOCK_SIZE; // qb: otherwise limits map range
} else if (!strcmp(argv[i], "-noskipfix")) {
printf("noskipfix = true\n");
noskipfix = true;
} else if (!strcmp(argv[i], "-largebounds") || !strcmp(argv[i], "-lb")) {
// qb: from kmqbsp3- Knightmare added
if (use_qbsp) {
printf("[-largebounds is not required with -qbsp]\n");
} else {
max_bounds = MAX_MAP_SIZE;
block_size = MAX_BLOCK_SIZE; // qb: otherwise limits map range
printf("largebounds: using max bound size of %i\n", MAX_MAP_SIZE);
}
}
// qb: set gamedir, moddir, and basedir
else if (!strcmp(argv[i], "-gamedir")) {
strcpy(tgamedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-moddir")) {
strcpy(tmoddir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-basedir")) {
strcpy(tbasedir, argv[i + 1]);
i++;
} else if((!strcmp(argv[i], "-chop")) || (!strcmp(argv[i], "-subdiv"))) {
subdivide_size = atof(argv[i + 1]);
if (subdivide_size < 32) {
subdivide_size = 32;
printf("subdivide_size set to minimum size: 32\n");
}
if (subdivide_size > 1024) {
subdivide_size = 1024;
printf("subdivide_size set to maximum size: 1024\n");
}
printf("subdivide_size = %f\n", subdivide_size);
i++;
} else if((!strcmp(argv[i], "-choplight")) || (!strcmp(argv[i], "-choplights")) || (!strcmp(argv[i], "-subdivlight"))) {
// qb: chop surf lights independently
sublight_size = atof(argv[i + 1]);
if (sublight_size < 32) {
sublight_size = 32;
printf("sublight_size set to minimum size: 32\n");
}
if (sublight_size > 1024) {
sublight_size = 1024;
printf("sublight_size set to maximum size: 1024\n");
}
printf("sublight_size = %f\n", sublight_size);
i++;
} else if(!strcmp(argv[i], "-blocksize")) {
block_size = atof(argv[i + 1]);
if (block_size < 128) {
block_size = 128;
printf("block_size set to minimum size: 128\n");
}
if (block_size > MAX_BLOCK_SIZE) {
block_size = MAX_BLOCK_SIZE;
printf("block_size set to minimum size: MAX_BLOCK_SIZE\n");
}
printf("blocksize: %i\n", block_size);
i++;
} else if (!strcmp(argv[i], "-block")) {
block_xl = block_yl = atoi(argv[i + 1]); // qb: fixed... has it always been wrong? was xl = xh and yl = yh
block_xh = block_yh = atoi(argv[i + 2]);
printf("block: %i,%i\n", block_xl, block_xh);
i += 2;
} else if (!strcmp(argv[i], "-blocks")) {
block_xl = atoi(argv[i + 1]);
block_yl = atoi(argv[i + 2]);
block_xh = atoi(argv[i + 3]);
block_yh = atoi(argv[i + 4]);
printf("blocks: %i,%i to %i,%i\n",
block_xl, block_yl, block_xh, block_yh);
i += 4;
} else if (!strcmp(argv[i], "-tmpin"))
strcpy(inbase, "/tmp");
else if (!strcmp(argv[i], "-tmpout"))
strcpy(outbase, "/tmp");
else if (!strcmp(argv[i], "-fast")) {
printf("fastvis = true\n");
fastvis = true;
} else if (!strcmp(argv[i], "-nosort")) {
printf("nosort = true\n");
nosort = true;
} else if (!strcmp(argv[i], "-dump")) {
printf("dicepatches = true\n");
dumppatches = true;
} else if (!strcmp(argv[i], "-bounce")) {
numbounce = atoi(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-extra")) {
extrasamples = true;
printf("extrasamples = true\n");
} else if (!strcmp(argv[i], "-noedgefix")) {
// qb: light warp surfaces
noedgefix = true;
printf("no edge fix = true\n");
} else if (!strcmp(argv[i], "-dice")) {
dicepatches = true;
printf("dicepatches = true\n");
} else if (!strcmp(argv[i], "-threads")) {
numthreads = atoi(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-maxdata")) { // qb: allows increase for some engines
maxdata = atoi(argv[i + 1]);
i++;
if (maxdata > DEFAULT_MAP_LIGHTING) {
printf("lighting maxdata (%i) exceeds typical limit (%i).\n", maxdata, DEFAULT_MAP_LIGHTING);
}
} else if (!strcmp(argv[i], "-scale")) {
lightscale = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-sunradscale")) {
sunradscale = atof(argv[i + 1]);
if (sunradscale < 0) {
sunradscale = 0;
printf("sunradscale set to minimum: 0\n");
}
printf("sunradscale = %f\n", sunradscale);
i++;
} else if (!strcmp(argv[i], "-saturation")) {
saturation = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-radmin")) {
patch_cutoff = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-direct")) {
direct_scale *= atof(argv[i + 1]);
// printf ("direct light scaling at %f\n", direct_scale);
i++;
} else if (!strcmp(argv[i], "-entity")) {
entity_scale *= atof(argv[i + 1]);
// printf ("entity light scaling at %f\n", entity_scale);
i++;
} else if (!strcmp(argv[i], "-nopvs")) {
nopvs = true;
printf("nopvs = true\n");
} else if (!strcmp(argv[i], "-noblock")) {
noblock = true;
printf("noblock = true\n");
} else if (!strcmp(argv[i], "-smooth")) {
// qb: limit range
smoothing_value = BOUND(0, atof(argv[i + 1]), 90);
i++;
} else if (!strcmp(argv[i], "-nudge")) {
sample_nudge = atof(argv[i + 1]);
// qb: nah, go crazy. sample_nudge = BOUND(0, sample_nudge, 1.0);
i++;
} else if (!strcmp(argv[i], "-ambient")) {
ambient = BOUND(0, atof(argv[i + 1]), 255);
i++;
} else if (!strcmp(argv[i], "-savetrace")) {
save_trace = true;
printf("savetrace = true\n");
} else if (!strcmp(argv[i], "-maxlight")) {
maxlight = BOUND(0, atof(argv[i + 1]), 255);
} else
break;
}
for(; i < argc; i++) {
size_t input_length = strlen(argv[i]);
qboolean is_map = strcmp(argv[i] + input_length - 4, ".map") == 0;
if(do_bsp) {
if(!is_map)
Error("bsp operation requires a map file input.");
} else if(do_vis || do_rad) {
if(is_map)
Error("bsp operation requires a bsp file input.");
} else {
Error("no operations chosen to be performed on input.");
}
SetQdirFromPath(argv[i]);
if (strcmp(tmoddir, "")) {
strcpy(moddir, tmoddir);
Q_pathslash(moddir);
strcpy(basedir, moddir);
}
if (strcmp(tbasedir, "")) {
strcpy(basedir, tbasedir);
Q_pathslash(basedir);
if (!strcmp(tmoddir, ""))
strcpy(moddir, basedir);
}
if (strcmp(tgamedir, "")) {
strcpy(gamedir, tgamedir);
Q_pathslash(gamedir);
}
// qb: display dirs
printf("moddir = %s\n", moddir);
printf("basedir = %s\n", basedir);
printf("gamedir = %s\n", gamedir);
if(do_bsp) {
int32_t old_numthreads = numthreads;
// qb: below is from original source release. On Windows, multi threads cause false leak errors.
numthreads = 1; // multiple threads aren't helping...
BSP_ProcessArgument(argv[i]);
numthreads = old_numthreads;
}
if(do_vis || (do_bsp && do_rad && is_map)) {
VIS_ProcessArgument(argv[i]);
}
if(do_rad) {
RAD_ProcessArgument(argv[i]);
}
}
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
//
// trilib.c: library for loading triangles from an Alias triangle file
//
#include <stdio.h>
#include "cmdlib.h"
#include "mathlib.h"
#include "4data.h"
// on disk representation of a face
#define FLOAT_START BOGUS_RANGE
#define FLOAT_END -FLOAT_START
#define MAGIC 123322
//#define NOISY 1
typedef struct {
float v[3];
} vector;
typedef struct
{
vector n; /* normal */
vector p; /* point */
vector c; /* color */
float u; /* u */
float v; /* v */
} aliaspoint_t;
typedef struct {
aliaspoint_t pt[3];
} tf_triangle;
void ByteSwapTri(tf_triangle *tri) {
int32_t i;
for (i = 0; i < sizeof(tf_triangle) / 4; i++) {
((int32_t *)tri)[i] = BigLong(((int32_t *)tri)[i]);
}
}
void LoadTriangleList(char *filename, triangle_t **pptri, int32_t *numtriangles) {
FILE *input;
float start;
char name[256], tex[256];
int32_t i, count, magic;
tf_triangle tri;
triangle_t *ptri;
int32_t iLevel;
int32_t exitpattern;
float t;
t = -FLOAT_START;
*((uint8_t *)&exitpattern + 0) = *((uint8_t *)&t + 3);
*((uint8_t *)&exitpattern + 1) = *((uint8_t *)&t + 2);
*((uint8_t *)&exitpattern + 2) = *((uint8_t *)&t + 1);
*((uint8_t *)&exitpattern + 3) = *((uint8_t *)&t + 0);
if ((input = fopen(filename, "rb")) == 0)
Error("reader: could not open file '%s'", filename);
iLevel = 0;
fread(&magic, sizeof(int32_t), 1, input);
if (BigLong(magic) != MAGIC)
Error("%s is not a Alias object separated triangle file, magic number is wrong.", filename);
ptri = malloc(MAXTRIANGLES * sizeof(triangle_t));
*pptri = ptri;
while (feof(input) == 0) {
if (fread(&start, sizeof(float), 1, input) < 1)
break;
*(int32_t *)&start = BigLong(*(int32_t *)&start);
if (*(int32_t *)&start != exitpattern) {
if (start == FLOAT_START) {
/* Start of an object or group of objects. */
i = -1;
do {
/* There are probably better ways to read a string from */
/* a file, but this does allow you to do error checking */
/* (which I'm not doing) on a per character basis. */
++i;
fread(&(name[i]), sizeof(char), 1, input);
} while (name[i] != '\0');
// indent();
// fprintf(stdout,"OBJECT START: %s\n",name);
fread(&count, sizeof(int32_t), 1, input);
count = BigLong(count);
++iLevel;
if (count != 0) {
// indent();
// fprintf(stdout,"NUMBER OF TRIANGLES: %d\n",count);
i = -1;
do {
++i;
fread(&(tex[i]), sizeof(char), 1, input);
} while (tex[i] != '\0');
// indent();
// fprintf(stdout," Object texture name: '%s'\n",tex);
}
/* Else (count == 0) this is the start of a group, and */
/* no texture name is present. */
} else if (start == FLOAT_END) {
/* End of an object or group. Yes, the name should be */
/* obvious from context, but it is in here just to be */
/* safe and to provide a little extra information for */
/* those who do not wish to write a recursive reader. */
/* Mia culpa. */
--iLevel;
i = -1;
do {
++i;
fread(&(name[i]), sizeof(char), 1, input);
} while (name[i] != '\0');
// indent();
// fprintf(stdout,"OBJECT END: %s\n",name);
continue;
}
}
//
// read the triangles
//
for (i = 0; i < count; ++i) {
int32_t j;
fread(&tri, sizeof(tf_triangle), 1, input);
ByteSwapTri(&tri);
for (j = 0; j < 3; j++) {
int32_t k;
for (k = 0; k < 3; k++) {
ptri->verts[j][k] = tri.pt[j].p.v[k];
}
}
ptri++;
if ((ptri - *pptri) >= MAXTRIANGLES)
Error("Error: too many triangles; increase MAXTRIANGLES\n");
}
}
*numtriangles = ptri - *pptri;
fclose(input);
free(ptri); // qb: stop mem leak
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "cmdlib.h"
#include "threads.h"
#define MAX_THREADS 64
int32_t dispatch;
int32_t workcount;
int32_t oldf;
qboolean pacifier;
qboolean threaded;
/*
=============
GetThreadWork
=============
*/
int32_t GetThreadWork(void) {
int32_t r;
int32_t f;
ThreadLock();
if (dispatch == workcount) {
ThreadUnlock();
return -1;
}
f = 10 * dispatch / workcount;
if (f != oldf) {
oldf = f;
if (pacifier) {
printf("%i...", f);
fflush(stdout);
}
}
r = dispatch;
dispatch++;
ThreadUnlock();
return r;
}
void (*workfunction)(int32_t);
void ThreadWorkerFunction(int32_t threadnum) {
int32_t work;
while (1) {
work = GetThreadWork();
if (work == -1)
break;
// printf ("thread %i, work %i\n", threadnum, work);
workfunction(work);
}
}
void RunThreadsOnIndividual(int32_t workcnt, qboolean showpacifier, void (*func)(int32_t)) {
if (numthreads == -1)
ThreadSetDefault();
workfunction = func;
RunThreadsOn(workcnt, showpacifier, ThreadWorkerFunction);
}
#ifdef USE_PTHREADS
#ifdef WIN32
#define USED
#include <windows.h>
int32_t numthreads = -1;
CRITICAL_SECTION crit;
static int32_t enter;
void ThreadSetDefault(void) {
SYSTEM_INFO info;
if (numthreads == -1) // not set manually
{
GetSystemInfo(&info);
numthreads = info.dwNumberOfProcessors;
if (numthreads < 1 || numthreads > 32)
numthreads = 1;
}
qprintf("%i threads\n", numthreads);
}
void ThreadLock(void) {
if (!threaded)
return;
EnterCriticalSection(&crit);
if (enter)
Error("Recursive ThreadLock\n");
enter = 1;
}
void ThreadUnlock(void) {
if (!threaded)
return;
if (!enter)
Error("ThreadUnlock without lock\n");
enter = 0;
LeaveCriticalSection(&crit);
}
/*
=============
RunThreadsOn
=============
*/
void RunThreadsOn(int32_t workcnt, qboolean showpacifier, void (*func)(int32_t)) {
int32_t threadid[MAX_THREADS];
HANDLE threadhandle[MAX_THREADS];
int32_t i;
int32_t start, end;
start = I_FloatTime();
dispatch = 0;
workcount = workcnt;
oldf = -1;
pacifier = showpacifier;
threaded = true;
//
// run threads in parallel
//
InitializeCriticalSection(&crit);
if (numthreads == 1) { // use same thread
func(0);
} else {
for (i = 0; i < numthreads; i++) {
threadhandle[i] = CreateThread(
NULL, // LPSECURITY_ATTRIBUTES lpsa,
0, // DWORD cbStack,
(LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr,
(LPVOID)i, // LPVOID lpvThreadParm,
0, // DWORD fdwCreate,
&threadid[i]);
}
for (i = 0; i < numthreads; i++)
WaitForSingleObject(threadhandle[i], INFINITE);
}
DeleteCriticalSection(&crit);
threaded = false;
end = I_FloatTime();
if (pacifier)
printf(" (%i)\n", end - start);
}
#else
#define USED
int32_t numthreads = 4;
void ThreadSetDefault(void) {
if (numthreads == -1) // not set manually
{
numthreads = 4;
}
}
#include <pthread.h>
pthread_mutex_t *my_mutex;
void ThreadLock(void) {
if (my_mutex)
pthread_mutex_lock(my_mutex);
}
void ThreadUnlock(void) {
if (my_mutex)
pthread_mutex_unlock(my_mutex);
}
/*
=============
RunThreadsOn
=============
*/
void RunThreadsOn(int32_t workcnt, qboolean showpacifier, void (*func)(int32_t)) {
int32_t i;
pthread_t work_threads[MAX_THREADS];
void *status;
pthread_attr_t attrib;
pthread_mutexattr_t mattrib;
int32_t start, end;
start = I_FloatTime();
dispatch = 0;
workcount = workcnt;
oldf = -1;
pacifier = showpacifier;
threaded = true;
if (pacifier)
setbuf(stdout, NULL);
if (!my_mutex) {
my_mutex = malloc(sizeof(*my_mutex));
if (pthread_mutexattr_init(&mattrib) == -1)
Error("pthread_mutex_attr_create failed");
if (pthread_mutex_init(my_mutex, &mattrib) == -1)
Error("pthread_mutex_init failed");
}
if (pthread_attr_init(&attrib) == -1)
Error("pthread_attr_create failed");
if (pthread_attr_setstacksize(&attrib, 0x1000000) == -1)
Error("pthread_attr_setstacksize failed");
for (i = 0; i < numthreads; i++) {
if (pthread_create(&work_threads[i], &attrib, (void *)func, &i) == -1)
Error("pthread_create failed");
}
for (i = 0; i < numthreads; i++) {
if (pthread_join(work_threads[i], &status) == -1)
Error("pthread_join failed");
}
threaded = false;
end = I_FloatTime();
if (pacifier)
printf(" (%i)\n", end - start);
}
#endif
#endif
/*
=======================================================================
SINGLE THREAD
=======================================================================
*/
#ifndef USED
int32_t numthreads = 1;
void ThreadSetDefault(void) {
numthreads = 1;
}
void ThreadLock(void) {
}
void ThreadUnlock(void) {
}
/*
=============
RunThreadsOn
=============
*/
void RunThreadsOn(int32_t workcnt, qboolean showpacifier, void (*func)(int32_t)) {
int32_t start, end;
dispatch = 0;
workcount = workcnt;
oldf = -1;
pacifier = showpacifier;
start = I_FloatTime();
#ifdef NeXT
if (pacifier)
setbuf(stdout, NULL);
#endif
func(0);
end = I_FloatTime();
if (pacifier)
printf(" (%i)\n", end - start);
}
#endif
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
// scriplib.c
#include "cmdlib.h"
#include "scriplib.h"
/*
=============================================================================
PARSING STUFF
=============================================================================
*/
typedef struct
{
char filename[1024];
char *buffer, *script_p, *end_p;
int32_t line;
} script_t;
#define MAX_INCLUDES 8
script_t scriptstack[MAX_INCLUDES];
script_t *script;
int32_t scriptline;
char token[MAXTOKEN];
qboolean endofscript;
qboolean tokenready; // only true if UnGetToken was just called
// qb: brush info from AA tools
char brush_info[2000] = "No brushes processed yet. Look near beginning of map";
static int32_t brush_begin = 1;
void MarkBrushBegin() {
brush_begin = 1;
}
void TestBrushBegin() {
if (!brush_begin)
return;
brush_begin = 0;
sprintf(brush_info, "Line %d, file %s", script->line, script->filename);
}
/*
==============
AddScriptToStack
==============
*/
void AddScriptToStack(char *filename) {
int32_t size;
script++;
if (script == &scriptstack[MAX_INCLUDES])
Error("script file exceeded MAX_INCLUDES");
strcpy(script->filename, ExpandPath(filename));
size = LoadFile(script->filename, (void **)&script->buffer);
printf("entering %s\n", script->filename);
script->line = 1;
script->script_p = script->buffer;
script->end_p = script->buffer + size;
}
/*
==============
LoadScriptFile
==============
*/
void LoadScriptFile(char *filename) {
script = scriptstack;
AddScriptToStack(filename);
endofscript = false;
tokenready = false;
}
/*
==============
ParseFromMemory
==============
*/
void ParseFromMemory(char *buffer, int32_t size) {
script = scriptstack;
script++;
if (script == &scriptstack[MAX_INCLUDES])
Error("script file exceeded MAX_INCLUDES");
strcpy(script->filename, "memory buffer");
script->buffer = buffer;
script->line = 1;
script->script_p = script->buffer;
script->end_p = script->buffer + size;
endofscript = false;
tokenready = false;
}
/*
==============
UnGetToken
Signals that the current token was not used, and should be reported
for the next GetToken. Note that
GetToken (true);
UnGetToken ();
GetToken (false);
could cross a line boundary.
==============
*/
void UnGetToken(void) {
tokenready = true;
}
qboolean EndOfScript(qboolean crossline) {
if (!crossline)
Error("Line %i is incomplete\n", scriptline);
if (!strcmp(script->filename, "memory buffer")) {
endofscript = true;
return false;
}
free(script->buffer);
if (script == scriptstack + 1) {
endofscript = true;
return false;
}
script--;
scriptline = script->line;
printf("returning to %s\n", script->filename);
return GetToken(crossline);
}
/*
==============
GetToken
==============
*/
qboolean GetToken(qboolean crossline) {
char *token_p;
if (tokenready) // is a token allready waiting?
{
tokenready = false;
return true;
}
if (script->script_p >= script->end_p)
return EndOfScript(crossline);
TestBrushBegin();
//
// skip space
//
skipspace:
while (*script->script_p <= 32) {
if (script->script_p >= script->end_p)
return EndOfScript(crossline);
if (*script->script_p++ == '\n') {
if (!crossline)
Error("Line %i is incomplete\n", scriptline);
scriptline = script->line++;
}
}
if (script->script_p >= script->end_p)
return EndOfScript(crossline);
// ; # // comments
if (*script->script_p == ';' || *script->script_p == '#' || (script->script_p[0] == '/' && script->script_p[1] == '/')) {
if (!crossline)
Error("Line %i is incomplete\n", scriptline);
while (*script->script_p++ != '\n')
if (script->script_p >= script->end_p)
return EndOfScript(crossline);
scriptline = script->line++;
goto skipspace;
}
// /* */ comments
if (script->script_p[0] == '/' && script->script_p[1] == '*') {
if (!crossline)
Error("Line %i is incomplete\n", scriptline);
script->script_p += 2;
while (script->script_p[0] != '*' && script->script_p[1] != '/') {
if (script->script_p[0] == '\n')
scriptline = script->line++;
script->script_p++;
if (script->script_p >= script->end_p)
return EndOfScript(crossline);
}
script->script_p += 2;
goto skipspace;
}
//
// copy token
//
token_p = token;
if (*script->script_p == '"') {
// quoted token
script->script_p++;
while (*script->script_p != '"') {
*token_p++ = *script->script_p++;
if (script->script_p == script->end_p)
break;
if (token_p == &token[MAXTOKEN])
Error("Token too large on line %i\n", scriptline);
}
script->script_p++;
} else // regular token
while (*script->script_p > 32 && *script->script_p != ';') {
*token_p++ = *script->script_p++;
if (script->script_p == script->end_p)
break;
if (token_p == &token[MAXTOKEN])
Error("Token too large on line %i\n", scriptline);
}
*token_p = 0;
if (!strcmp(token, "$include")) {
GetToken(false);
AddScriptToStack(token);
return GetToken(crossline);
}
return true;
}
/*
==============
TokenAvailable
Returns true if there is another token on the line
==============
*/
qboolean TokenAvailable(void) {
char *search_p;
search_p = script->script_p;
if (search_p >= script->end_p)
return false;
while (*search_p <= 32) {
if (*search_p == '\n')
return false;
search_p++;
if (search_p == script->end_p)
return false;
}
if (*search_p == ';')
return false;
return true;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "cmdlib.h"
#include "mathlib.h"
#include "polylib.h"
extern int32_t numthreads;
// counters are only bumped when running single threaded,
// because they are an awefull coherence problem
int32_t c_active_windings;
int32_t c_peak_windings;
int32_t c_winding_allocs;
int32_t c_winding_points;
void pw(winding_t *w) {
int32_t i;
for (i = 0; i < w->numpoints; i++)
printf("(%5.1f, %5.1f, %5.1f)\n", w->p[i][0], w->p[i][1], w->p[i][2]);
}
/*
=============
AllocWinding
=============
*/
winding_t *AllocWinding(int32_t points) {
winding_t *w;
int32_t s;
if (numthreads == 1) {
c_winding_allocs++;
c_winding_points += points;
c_active_windings++;
if (c_active_windings > c_peak_windings)
c_peak_windings = c_active_windings;
}
s = sizeof(vec_t) * 3 * points + sizeof(int32_t);
w = malloc(s);
memset(w, 0, s);
return w;
}
void FreeWinding(winding_t *w) {
if (*(unsigned *)w == 0xdeaddead)
Error("FreeWinding: freed a freed winding");
*(unsigned *)w = 0xdeaddead;
if (numthreads == 1)
c_active_windings--;
free(w);
}
/*
============
RemoveColinearPoints
============
*/
int32_t c_removed;
void RemoveColinearPoints(winding_t *w) {
int32_t i, j, k;
vec3_t v1, v2;
int32_t nump;
vec3_t p[MAX_POINTS_ON_WINDING];
nump = 0;
for (i = 0; i < w->numpoints; i++) {
j = (i + 1) % w->numpoints;
k = (i + w->numpoints - 1) % w->numpoints;
VectorSubtract(w->p[j], w->p[i], v1);
VectorSubtract(w->p[i], w->p[k], v2);
VectorNormalize(v1, v1);
VectorNormalize(v2, v2);
if (DotProduct(v1, v2) < 0.999) {
VectorCopy(w->p[i], p[nump]);
nump++;
}
}
if (nump == w->numpoints)
return;
if (numthreads == 1)
c_removed += w->numpoints - nump;
w->numpoints = nump;
memcpy(w->p, p, nump * sizeof(p[0]));
}
/*
============
WindingPlane
============
*/
void WindingPlane(winding_t *w, vec3_t normal, vec_t *dist) {
vec3_t v1, v2;
VectorSubtract(w->p[1], w->p[0], v1);
VectorSubtract(w->p[2], w->p[0], v2);
CrossProduct(v2, v1, normal);
VectorNormalize(normal, normal);
*dist = DotProduct(w->p[0], normal);
}
/*
=============
WindingArea
=============
*/
vec_t WindingArea(winding_t *w) {
int32_t i;
vec3_t d1, d2, cross;
vec_t total;
total = 0;
for (i = 2; i < w->numpoints; i++) {
VectorSubtract(w->p[i - 1], w->p[0], d1);
VectorSubtract(w->p[i], w->p[0], d2);
CrossProduct(d1, d2, cross);
total += 0.5 * VectorLength(cross);
}
return total;
}
void WindingBounds(winding_t *w, vec3_t mins, vec3_t maxs) {
vec_t v;
int32_t i, j;
mins[0] = mins[1] = mins[2] = BOGUS_RANGE;
maxs[0] = maxs[1] = maxs[2] = -BOGUS_RANGE;
for (i = 0; i < w->numpoints; i++) {
for (j = 0; j < 3; j++) {
v = w->p[i][j];
if (v < mins[j])
mins[j] = v;
if (v > maxs[j])
maxs[j] = v;
}
}
}
/*
=============
WindingCenter
=============
*/
void WindingCenter(winding_t *w, vec3_t center) {
int32_t i;
float scale;
VectorCopy(vec3_origin, center);
for (i = 0; i < w->numpoints; i++)
VectorAdd(w->p[i], center, center);
scale = 1.0 / w->numpoints;
VectorScale(center, scale, center);
}
/*
=================
BaseWindingForPlane
=================
*/
winding_t *BaseWindingForPlane(vec3_t normal, vec_t dist) {
int32_t i, x;
vec_t max, v;
vec3_t org, vright, vup;
winding_t *w;
// find the major axis
max = -BOGUS_RANGE;
x = -1;
for (i = 0; i < 3; i++) {
v = fabs(normal[i]);
if (v > max) {
x = i;
max = v;
}
}
if (x == -1)
Error("BaseWindingForPlane: no axis found");
VectorCopy(vec3_origin, vup);
switch (x) {
case 0:
case 1:
vup[2] = 1;
break;
case 2:
vup[0] = 1;
break;
}
v = DotProduct(vup, normal);
VectorMA(vup, -v, normal, vup);
VectorNormalize(vup, vup);
VectorScale(normal, dist, org);
CrossProduct(vup, normal, vright);
VectorScale(vup, BOGUS_RANGE, vup);
VectorScale(vright, BOGUS_RANGE, vright);
// project a really big axis aligned box onto the plane
w = AllocWinding(4);
VectorSubtract(org, vright, w->p[0]);
VectorAdd(w->p[0], vup, w->p[0]);
VectorAdd(org, vright, w->p[1]);
VectorAdd(w->p[1], vup, w->p[1]);
VectorAdd(org, vright, w->p[2]);
VectorSubtract(w->p[2], vup, w->p[2]);
VectorSubtract(org, vright, w->p[3]);
VectorSubtract(w->p[3], vup, w->p[3]);
w->numpoints = 4;
return w;
}
/*
==================
CopyWinding
==================
*/
winding_t *CopyWinding(const winding_t *w) {
int32_t size;
winding_t *c;
c = AllocWinding(w->numpoints);
size = (intptr_t)((winding_t *)0)->p[w->numpoints];
memcpy(c, w, size);
return c;
}
/*
==================
ReverseWinding
==================
*/
winding_t *ReverseWinding(winding_t *w) {
int32_t i;
winding_t *c;
c = AllocWinding(w->numpoints);
for (i = 0; i < w->numpoints; i++) {
VectorCopy(w->p[w->numpoints - 1 - i], c->p[i]);
}
c->numpoints = w->numpoints;
return c;
}
/*
=============
ClipWindingEpsilon
=============
*/
void ClipWindingEpsilon(
const winding_t *in,
const vec3_t normal,
const vec_t dist,
const vec_t epsilon,
winding_t **front, winding_t **back) {
vec_t dists[MAX_POINTS_ON_WINDING + 4];
int32_t sides[MAX_POINTS_ON_WINDING + 4];
int32_t counts[3];
static vec_t dot; // VC 4.2 optimizer bug if not static
int32_t i, j;
vec_t *p1, *p2;
vec3_t mid;
winding_t *f, *b;
int32_t maxpts;
counts[0] = counts[1] = counts[2] = 0;
sides[0] = dists[0] = 0;
// determine sides for each point
for (i = 0; i < in->numpoints; i++) {
dot = DotProduct(in->p[i], normal);
dot -= dist;
dists[i] = dot;
if (dot > epsilon)
sides[i] = SIDE_FRONT;
else if (dot < -epsilon)
sides[i] = SIDE_BACK;
else
sides[i] = SIDE_ON;
counts[sides[i]]++;
}
sides[i] = sides[0];
dists[i] = dists[0];
*front = *back = NULL;
if (!counts[0]) {
*back = CopyWinding(in);
return;
}
if (!counts[1]) {
*front = CopyWinding(in);
return;
}
maxpts = in->numpoints + 4; // cant use counts[0]+2 because of fp grouping errors
*front = f = AllocWinding(maxpts);
*back = b = AllocWinding(maxpts);
for (i = 0; i < in->numpoints; i++) {
p1 = ((winding_t *)in)->p[i];
if (sides[i] == SIDE_ON) {
VectorCopy(p1, f->p[f->numpoints]);
f->numpoints++;
VectorCopy(p1, b->p[b->numpoints]);
b->numpoints++;
continue;
}
if (sides[i] == SIDE_FRONT) {
VectorCopy(p1, f->p[f->numpoints]);
f->numpoints++;
}
if (sides[i] == SIDE_BACK) {
VectorCopy(p1, b->p[b->numpoints]);
b->numpoints++;
}
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
continue;
// generate a split point
p2 = ((winding_t *)in)->p[(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 (normal[j] == 1)
mid[j] = dist;
else if (normal[j] == -1)
mid[j] = -dist;
else
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
VectorCopy(mid, f->p[f->numpoints]);
f->numpoints++;
VectorCopy(mid, b->p[b->numpoints]);
b->numpoints++;
}
if (f->numpoints > maxpts || b->numpoints > maxpts)
Error("ClipWinding: points exceeded estimate");
if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING)
Error("ClipWinding: MAX_POINTS_ON_WINDING");
}
/*
=============
ChopWindingInPlace
=============
*/
void ChopWindingInPlace(winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) {
winding_t *in;
vec_t dists[MAX_POINTS_ON_WINDING + 4];
int32_t sides[MAX_POINTS_ON_WINDING + 4];
int32_t counts[3];
static vec_t dot; // VC 4.2 optimizer bug if not static
int32_t i, j;
vec_t *p1, *p2;
vec3_t mid;
winding_t *f;
int32_t maxpts;
in = *inout;
counts[0] = counts[1] = counts[2] = 0;
// determine sides for each point
for (i = 0; i < in->numpoints; i++) {
dot = DotProduct(in->p[i], normal);
dot -= dist;
dists[i] = dot;
if (dot > epsilon)
sides[i] = SIDE_FRONT;
else if (dot < -epsilon)
sides[i] = SIDE_BACK;
else {
sides[i] = SIDE_ON;
}
counts[sides[i]]++;
}
sides[i] = sides[0];
dists[i] = dists[0];
if (!counts[0]) {
FreeWinding(in);
*inout = NULL;
return;
}
if (!counts[1])
return; // inout stays the same
maxpts = in->numpoints + 4; // can't use counts[0]+2 because of fp grouping errors
f = AllocWinding(maxpts);
for (i = 0; i < in->numpoints; i++) {
p1 = in->p[i];
if (sides[i] == SIDE_ON) {
VectorCopy(p1, f->p[f->numpoints]);
f->numpoints++;
continue;
}
if (sides[i] == SIDE_FRONT) {
VectorCopy(p1, f->p[f->numpoints]);
f->numpoints++;
}
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
continue;
// generate a split point
p2 = in->p[(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 (normal[j] >= 1)
mid[j] = dist;
else if (normal[j] <= -1)
mid[j] = -dist;
else
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
VectorCopy(mid, f->p[f->numpoints]);
f->numpoints++;
}
if (f->numpoints > maxpts)
Error("ClipWinding: points exceeded estimate");
if (f->numpoints > MAX_POINTS_ON_WINDING)
Error("ClipWinding: MAX_POINTS_ON_WINDING");
FreeWinding(in);
*inout = f;
}
/*
=================
ChopWinding
Returns the fragment of in that is on the front side
of the cliping plane. The original is freed.
=================
*/
winding_t *ChopWinding(winding_t *in, vec3_t normal, vec_t dist) {
winding_t *f, *b;
ClipWindingEpsilon(in, normal, dist, ON_EPSILON, &f, &b);
FreeWinding(in);
if (b)
FreeWinding(b);
return f;
}
/*
=================
CheckWinding
=================
*/
void CheckWinding(winding_t *w) {
int32_t i, j;
vec_t *p1, *p2;
vec_t d, edgedist;
vec3_t dir, edgenormal, facenormal;
vec_t area;
vec_t facedist;
if (w->numpoints < 3)
Error("CheckWinding: %i points", w->numpoints);
area = WindingArea(w);
if (area < 1)
Error("CheckWinding: %f area", area);
WindingPlane(w, facenormal, &facedist);
for (i = 0; i < w->numpoints; i++) {
p1 = w->p[i];
for (j = 0; j < 3; j++)
if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE)
Error("CheckFace: BUGUS_RANGE: %f", p1[j]);
j = i + 1 == w->numpoints ? 0 : i + 1;
// check the point is on the face plane
d = DotProduct(p1, facenormal) - facedist;
if (d < -ON_EPSILON || d > ON_EPSILON)
Error("CheckWinding: point off plane");
// check the edge isnt degenerate
p2 = w->p[j];
VectorSubtract(p2, p1, dir);
if (VectorLength(dir) < ON_EPSILON)
Error("CheckWinding: degenerate edge");
CrossProduct(facenormal, dir, edgenormal);
VectorNormalize(edgenormal, edgenormal);
edgedist = DotProduct(p1, edgenormal);
edgedist += ON_EPSILON;
// all other points must be on front side
for (j = 0; j < w->numpoints; j++) {
if (j == i)
continue;
d = DotProduct(w->p[j], edgenormal);
if (d > edgedist)
Error("CheckWinding: non-convex");
}
}
}
/*
============
WindingOnPlaneSide
============
*/
int32_t WindingOnPlaneSide(winding_t *w, vec3_t normal, vec_t dist) {
qboolean front, back;
int32_t i;
vec_t d;
front = false;
back = false;
for (i = 0; i < w->numpoints; i++) {
d = DotProduct(w->p[i], normal) - dist;
if (d < -ON_EPSILON) {
if (front)
return SIDE_CROSS;
back = true;
continue;
}
if (d > ON_EPSILON) {
if (back)
return SIDE_CROSS;
front = true;
continue;
}
}
if (back)
return SIDE_BACK;
if (front)
return SIDE_FRONT;
return SIDE_ON;
}
/*
mdfour.c
An implementation of MD4 designed for use in the samba SMB
authentication protocol
Copyright (C) 1997-1998 Andrew Tridgell
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
*/
#include <stdint.h>
#include <string.h> /* XoXus: needed for memset call */
#include "mdfour.h"
/* NOTE: This code makes no attempt to be fast!
It assumes that a int32_t is at least 32 bits long
*/
static struct mdfour *m;
#define F(X, Y, Z) (((X) & (Y)) | ((~(X)) & (Z)))
#define G(X, Y, Z) (((X) & (Y)) | ((X) & (Z)) | ((Y) & (Z)))
#define H(X, Y, Z) ((X) ^ (Y) ^ (Z))
#ifdef LARGE_INT32
#define lshift(x, s) ((((x) << (s)) & 0xFFFFFFFF) | (((x) >> (32 - (s))) & 0xFFFFFFFF))
#else
#define lshift(x, s) (((x) << (s)) | ((x) >> (32 - (s))))
#endif
#define ROUND1(a, b, c, d, k, s) a = lshift(a + F(b, c, d) + X[k], s)
#define ROUND2(a, b, c, d, k, s) a = lshift(a + G(b, c, d) + X[k] + 0x5A827999, s)
#define ROUND3(a, b, c, d, k, s) a = lshift(a + H(b, c, d) + X[k] + 0x6ED9EBA1, s)
/* this applies md4 to 64 byte chunks */
static void mdfour64(uint32_t *M) {
int32_t j;
uint32_t AA, BB, CC, DD;
uint32_t X[16];
uint32_t A, B, C, D;
for (j = 0; j < 16; j++)
X[j] = M[j];
A = m->A;
B = m->B;
C = m->C;
D = m->D;
AA = A;
BB = B;
CC = C;
DD = D;
ROUND1(A, B, C, D, 0, 3);
ROUND1(D, A, B, C, 1, 7);
ROUND1(C, D, A, B, 2, 11);
ROUND1(B, C, D, A, 3, 19);
ROUND1(A, B, C, D, 4, 3);
ROUND1(D, A, B, C, 5, 7);
ROUND1(C, D, A, B, 6, 11);
ROUND1(B, C, D, A, 7, 19);
ROUND1(A, B, C, D, 8, 3);
ROUND1(D, A, B, C, 9, 7);
ROUND1(C, D, A, B, 10, 11);
ROUND1(B, C, D, A, 11, 19);
ROUND1(A, B, C, D, 12, 3);
ROUND1(D, A, B, C, 13, 7);
ROUND1(C, D, A, B, 14, 11);
ROUND1(B, C, D, A, 15, 19);
ROUND2(A, B, C, D, 0, 3);
ROUND2(D, A, B, C, 4, 5);
ROUND2(C, D, A, B, 8, 9);
ROUND2(B, C, D, A, 12, 13);
ROUND2(A, B, C, D, 1, 3);
ROUND2(D, A, B, C, 5, 5);
ROUND2(C, D, A, B, 9, 9);
ROUND2(B, C, D, A, 13, 13);
ROUND2(A, B, C, D, 2, 3);
ROUND2(D, A, B, C, 6, 5);
ROUND2(C, D, A, B, 10, 9);
ROUND2(B, C, D, A, 14, 13);
ROUND2(A, B, C, D, 3, 3);
ROUND2(D, A, B, C, 7, 5);
ROUND2(C, D, A, B, 11, 9);
ROUND2(B, C, D, A, 15, 13);
ROUND3(A, B, C, D, 0, 3);
ROUND3(D, A, B, C, 8, 9);
ROUND3(C, D, A, B, 4, 11);
ROUND3(B, C, D, A, 12, 15);
ROUND3(A, B, C, D, 2, 3);
ROUND3(D, A, B, C, 10, 9);
ROUND3(C, D, A, B, 6, 11);
ROUND3(B, C, D, A, 14, 15);
ROUND3(A, B, C, D, 1, 3);
ROUND3(D, A, B, C, 9, 9);
ROUND3(C, D, A, B, 5, 11);
ROUND3(B, C, D, A, 13, 15);
ROUND3(A, B, C, D, 3, 3);
ROUND3(D, A, B, C, 11, 9);
ROUND3(C, D, A, B, 7, 11);
ROUND3(B, C, D, A, 15, 15);
A += AA;
B += BB;
C += CC;
D += DD;
#ifdef LARGE_INT32
A &= 0xFFFFFFFF;
B &= 0xFFFFFFFF;
C &= 0xFFFFFFFF;
D &= 0xFFFFFFFF;
#endif
for (j = 0; j < 16; j++)
X[j] = 0;
m->A = A;
m->B = B;
m->C = C;
m->D = D;
}
static void copy64(uint32_t *M, uint8_t *in) {
int32_t i;
for (i = 0; i < 16; i++)
M[i] = (in[i * 4 + 3] << 24) | (in[i * 4 + 2] << 16) |
(in[i * 4 + 1] << 8) | (in[i * 4 + 0] << 0);
}
static void copy4(uint8_t *out, uint32_t x) {
out[0] = x & 0xFF;
out[1] = (x >> 8) & 0xFF;
out[2] = (x >> 16) & 0xFF;
out[3] = (x >> 24) & 0xFF;
}
void mdfour_begin(struct mdfour *md) {
md->A = 0x67452301;
md->B = 0xefcdab89;
md->C = 0x98badcfe;
md->D = 0x10325476;
md->totalN = 0;
}
static void mdfour_tail(uint8_t *in, int32_t n) {
uint8_t buf[128];
uint32_t M[16];
uint32_t b;
m->totalN += n;
b = m->totalN * 8;
memset(buf, 0, 128);
if (n)
memcpy(buf, in, n);
buf[n] = 0x80;
if (n <= 55) {
copy4(buf + 56, b);
copy64(M, buf);
mdfour64(M);
} else {
copy4(buf + 120, b);
copy64(M, buf);
mdfour64(M);
copy64(M, buf + 64);
mdfour64(M);
}
}
void mdfour_update(struct mdfour *md, uint8_t *in, int32_t n) {
uint32_t M[16];
if (n == 0)
mdfour_tail(in, n);
m = md;
while (n >= 64) {
copy64(M, in);
mdfour64(M);
in += 64;
n -= 64;
m->totalN += 64;
}
mdfour_tail(in, n);
}
void mdfour_result(struct mdfour *md, uint8_t *out) {
m = md;
copy4(out, m->A);
copy4(out + 4, m->B);
copy4(out + 8, m->C);
copy4(out + 12, m->D);
}
void mdfour(uint8_t *out, uint8_t *in, int32_t n) {
struct mdfour md;
mdfour_begin(&md);
mdfour_update(&md, in, n);
mdfour_result(&md, out);
}
///////////////////////////////////////////////////////////////
// MD4-based checksum utility functions
//
// Copyright (C) 2000 Jeff Teunissen <d2deek@pmail.net>
//
// Author: Jeff Teunissen <d2deek@pmail.net>
// Date: 01 Jan 2000
unsigned Com_BlockChecksum(void *buffer, int32_t length) {
int32_t digest[4];
unsigned val;
mdfour((uint8_t *)digest, (uint8_t *)buffer, length);
val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];
return val;
}
void Com_BlockFullChecksum(void *buffer, int32_t len, uint8_t *outbuf) {
mdfour(outbuf, (uint8_t *)buffer, len);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
// mathlib.c -- math primitives
#include "cmdlib.h"
#include "mathlib.h"
vec3_t vec3_origin = {0, 0, 0};
// qb: inline math from AA tools
#ifndef MATH_INLINE
double VectorLength(vec3_t v) {
int32_t i;
double length;
length = 0;
for (i = 0; i < 3; i++)
length += v[i] * v[i];
length = sqrt(length); // FIXME
return length;
}
qboolean VectorCompare(vec3_t v1, vec3_t v2) {
int32_t i;
for (i = 0; i < 3; i++)
if (fabs(v1[i] - v2[i]) > EQUAL_EPSILON)
return false;
return true;
}
vec_t Q_rint(vec_t in) {
return floor(in + 0.5);
}
void VectorMA(vec3_t va, double scale, vec3_t vb, vec3_t vc) {
vc[0] = va[0] + scale * vb[0];
vc[1] = va[1] + scale * vb[1];
vc[2] = va[2] + scale * vb[2];
}
void CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross) {
cross[0] = v1[1] * v2[2] - v1[2] * v2[1];
cross[1] = v1[2] * v2[0] - v1[0] * v2[2];
cross[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
vec_t _DotProduct(vec3_t v1, vec3_t v2) {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
void _VectorSubtract(vec3_t va, vec3_t vb, vec3_t out) {
out[0] = va[0] - vb[0];
out[1] = va[1] - vb[1];
out[2] = va[2] - vb[2];
}
void _VectorAdd(vec3_t va, vec3_t vb, vec3_t out) {
out[0] = va[0] + vb[0];
out[1] = va[1] + vb[1];
out[2] = va[2] + vb[2];
}
void _VectorCopy(vec3_t in, vec3_t out) {
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
}
void _VectorScale(vec3_t v, vec_t scale, vec3_t out) {
out[0] = v[0] * scale;
out[1] = v[1] * scale;
out[2] = v[2] * scale;
}
vec_t VectorNormalize(vec3_t in, vec3_t out) {
vec_t length, ilength;
length = sqrt(in[0] * in[0] + in[1] * in[1] + in[2] * in[2]);
if (length == 0) {
VectorClear(out);
return 0;
}
ilength = 1.0 / length;
out[0] = in[0] * ilength;
out[1] = in[1] * ilength;
out[2] = in[2] * ilength;
return length;
}
vec_t ColorNormalize(vec3_t in, vec3_t out) {
float max, scale;
max = in[0];
if (in[1] > max)
max = in[1];
if (in[2] > max)
max = in[2];
if (max == 0)
return 0;
scale = 1.0 / max;
VectorScale(in, scale, out);
return max;
}
void VectorInverse(vec3_t v) {
v[0] = -v[0];
v[1] = -v[1];
v[2] = -v[2];
}
void ClearBounds(vec3_t mins, vec3_t maxs) {
mins[0] = mins[1] = mins[2] = BOGUS_RANGE;
maxs[0] = maxs[1] = maxs[2] = -BOGUS_RANGE;
}
void AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs) {
int32_t i;
vec_t val;
for (i = 0; i < 3; i++) {
val = v[i];
if (val < mins[i])
mins[i] = val;
if (val > maxs[i])
maxs[i] = val;
}
}
// qb: from AA tools
qboolean RayPlaneIntersect(vec3_t p_n, vec_t p_d, vec3_t l_o, vec3_t l_n,
vec3_t res) {
float dot, t;
dot = DotProduct(p_n, l_n);
if (dot > -0.001)
return false;
t = (p_d - (l_o[0] * p_n[0]) - (l_o[1] * p_n[1]) - (l_o[2] * p_n[2])) / dot;
res[0] = l_o[0] + (t * l_n[0]);
res[1] = l_o[1] + (t * l_n[1]);
res[2] = l_o[2] + (t * l_n[2]);
return true;
}
#endif
//
// llwolib.c: library for loading triangles from a Lightwave triangle file
//
#include <stdio.h>
#include "cmdlib.h"
#include "mathlib.h"
#include "4data.h"
#define MAXPOINTS 2000
#define MAXPOLYS 2000
struct {
float p[3];
} pnt[MAXPOINTS];
struct {
short pl[3];
} ply[MAXPOLYS];
char trashcan[10000];
char buffer[1000];
long counter;
union {
char c[4];
float f;
long l;
} buffer4;
union {
char c[2];
short i;
} buffer2;
FILE *lwo;
short numpoints, numpolys;
long lflip(long x) {
union {
char b[4];
long l;
} in, out;
in.l = x;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.l;
}
float fflip(float x) {
union {
char b[4];
float l;
} in, out;
in.l = x;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.l;
}
int sflip(short x) {
union {
char b[2];
short i;
} in, out;
in.i = x;
out.b[0] = in.b[1];
out.b[1] = in.b[0];
return out.i;
}
void skipchunk() {
long chunksize;
fread(buffer4.c, 4, 1, lwo);
counter -= 8;
chunksize = lflip(buffer4.l);
fread(trashcan, chunksize, 1, lwo);
counter -= chunksize;
};
void getpoints() {
long chunksize;
short i, j;
fread(buffer4.c, 4, 1, lwo);
counter -= 8;
chunksize = lflip(buffer4.l);
counter -= chunksize;
numpoints = chunksize / 12;
if (numpoints > MAXPOINTS) {
fprintf(stderr, "reader: Too many points!!!");
exit(0);
}
for (i = 0; i < numpoints; i++) {
for (j = 0; j < 3; j++) {
fread(buffer4.c, 4, 1, lwo);
pnt[i].p[j] = fflip(buffer4.f);
}
}
}
void getpolys() {
short temp, i, j;
short polypoints;
long chunksize;
fread(buffer4.c, 4, 1, lwo);
counter -= 8;
chunksize = lflip(buffer4.l);
counter -= chunksize;
numpolys = 0;
for (i = 0; i < chunksize;) {
fread(buffer2.c, 2, 1, lwo);
polypoints = sflip(buffer2.i);
if (polypoints != 3) {
fprintf(stderr, "reader: Not a triangle!!!");
exit(0);
}
i += 10;
for (j = 0; j < 3; j++) {
fread(buffer2.c, 2, 1, lwo);
temp = sflip(buffer2.i);
ply[numpolys].pl[j] = temp;
}
fread(buffer2.c, 2, 1, lwo);
numpolys++;
if (numpolys > MAXPOLYS) {
fprintf(stderr, "reader: Too many polygons!!!\n");
exit(0);
}
}
}
void LoadLWOTriangleList(char *filename, triangle_t **pptri, int32_t *numtriangles) {
int32_t i, j;
triangle_t *ptri;
if ((lwo = fopen(filename, "rb")) == 0) {
fprintf(stderr, "reader: could not open file '%s'\n", filename);
exit(0);
}
fread(buffer4.c, 4, 1, lwo);
fread(buffer4.c, 4, 1, lwo);
counter = lflip(buffer4.l) - 4;
fread(buffer4.c, 4, 1, lwo);
while (counter > 0) {
fread(buffer4.c, 4, 1, lwo);
if (!strncmp(buffer4.c, "PNTS", 4)) {
getpoints();
} else {
if (!strncmp(buffer4.c, "POLS", 4)) {
getpolys();
} else {
skipchunk();
}
}
}
fclose(lwo);
ptri = malloc(MAXTRIANGLES * sizeof(triangle_t));
*pptri = ptri;
for (i = 0; i < numpolys; i++) {
for (j = 0; j < 3; j++) {
ptri[i].verts[j][0] = pnt[ply[i].pl[j]].p[0];
ptri[i].verts[j][1] = pnt[ply[i].pl[j]].p[2];
ptri[i].verts[j][2] = pnt[ply[i].pl[j]].p[1];
}
}
*numtriangles = numpolys;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
// lbmlib.c
#include "cmdlib.h"
#include "lbmlib.h"
/*
============================================================================
LBM STUFF
============================================================================
*/
typedef uint8_t UBYTE;
// conflicts with windows typedef short WORD;
typedef uint16_t UWORD;
typedef long LONG;
typedef enum { ms_none, ms_mask, ms_transcolor, ms_lasso } mask_t;
typedef enum { cm_none, cm_rle1 } compress_t;
typedef struct {
UWORD w, h;
short x, y;
UBYTE nPlanes;
UBYTE masking;
UBYTE compression;
UBYTE pad1;
UWORD transparentColor;
UBYTE xAspect, yAspect;
short pageWidth, pageHeight;
} bmhd_t;
extern bmhd_t bmhd; // will be in native byte order
#define FORMID ('F' + ('O' << 8) + ((int32_t)'R' << 16) + ((int32_t)'M' << 24))
#define ILBMID ('I' + ('L' << 8) + ((int32_t)'B' << 16) + ((int32_t)'M' << 24))
#define PBMID ('P' + ('B' << 8) + ((int32_t)'M' << 16) + ((int32_t)' ' << 24))
#define BMHDID ('B' + ('M' << 8) + ((int32_t)'H' << 16) + ((int32_t)'D' << 24))
#define BODYID ('B' + ('O' << 8) + ((int32_t)'D' << 16) + ((int32_t)'Y' << 24))
#define CMAPID ('C' + ('M' << 8) + ((int32_t)'A' << 16) + ((int32_t)'P' << 24))
bmhd_t bmhd;
int32_t Align(int32_t l) {
if(l & 1)
return l + 1;
return l;
}
/*
================
LBMRLEdecompress
Source must be evenly aligned!
================
*/
byte *LBMRLEDecompress(byte *source, byte *unpacked, int32_t bpwidth) {
int32_t count;
byte b, rept;
count = 0;
do {
rept = *source++;
if(rept > 0x80) {
rept = (rept ^ 0xff) + 2;
b = *source++;
memset(unpacked, b, rept);
unpacked += rept;
} else if(rept < 0x80) {
rept++;
memcpy(unpacked, source, rept);
unpacked += rept;
source += rept;
} else
rept = 0; // rept of 0x80 is NOP
count += rept;
} while(count < bpwidth);
if(count > bpwidth)
Error("Decompression exceeded width!\n");
return source;
}
/*
=================
LoadLBM
=================
*/
void LoadLBM(char *filename, byte **picture, byte **palette) {
byte *LBMbuffer, *picbuffer, *cmapbuffer;
int32_t y;
byte *LBM_P, *LBMEND_P;
byte *pic_p;
byte *body_p;
int32_t formtype, formlength;
int32_t chunktype, chunklength;
// qiet compiler warnings
picbuffer = NULL;
cmapbuffer = NULL;
//
// load the LBM
//
LoadFile(filename, (void **)&LBMbuffer);
//
// parse the LBM header
//
LBM_P = LBMbuffer;
if(*(int32_t *)LBMbuffer != LittleLong(FORMID))
Error("No FORM ID at start of file!\n");
LBM_P += 4;
formlength = BigLong(*(int32_t *)LBM_P);
LBM_P += 4;
LBMEND_P = LBM_P + Align(formlength);
formtype = LittleLong(*(int32_t *)LBM_P);
if(formtype != ILBMID && formtype != PBMID)
Error("Unrecognized form type: %c%c%c%c\n", formtype & 0xff, (formtype >> 8) & 0xff, (formtype >> 16) & 0xff,
(formtype >> 24) & 0xff);
LBM_P += 4;
//
// parse chunks
//
while(LBM_P < LBMEND_P) {
chunktype = LBM_P[0] + (LBM_P[1] << 8) + (LBM_P[2] << 16) + (LBM_P[3] << 24);
LBM_P += 4;
chunklength = LBM_P[3] + (LBM_P[2] << 8) + (LBM_P[1] << 16) + (LBM_P[0] << 24);
LBM_P += 4;
switch(chunktype) {
case BMHDID:
memcpy(&bmhd, LBM_P, sizeof(bmhd));
bmhd.w = BigShort(bmhd.w);
bmhd.h = BigShort(bmhd.h);
bmhd.x = BigShort(bmhd.x);
bmhd.y = BigShort(bmhd.y);
bmhd.pageWidth = BigShort(bmhd.pageWidth);
bmhd.pageHeight = BigShort(bmhd.pageHeight);
break;
case CMAPID:
cmapbuffer = malloc(768);
memset(cmapbuffer, 0, 768);
memcpy(cmapbuffer, LBM_P, chunklength);
break;
case BODYID:
body_p = LBM_P;
pic_p = picbuffer = malloc(bmhd.w * bmhd.h);
if(formtype == PBMID) {
//
// unpack PBM
//
for(y = 0; y < bmhd.h; y++, pic_p += bmhd.w) {
if(bmhd.compression == cm_rle1)
body_p = LBMRLEDecompress((byte *)body_p, pic_p, bmhd.w);
else if(bmhd.compression == cm_none) {
memcpy(pic_p, body_p, bmhd.w);
body_p += Align(bmhd.w);
}
}
} else {
//
// unpack ILBM
//
Error("%s is an interlaced LBM, not packed", filename);
}
break;
}
LBM_P += Align(chunklength);
}
free(LBMbuffer);
*picture = picbuffer;
if(palette)
*palette = cmapbuffer;
}
/*
============================================================================
WRITE LBM
============================================================================
*/
/*
==============
WriteLBMfile
==============
*/
void WriteLBMfile(char *filename, byte *data, int32_t width, int32_t height, byte *palette) {
byte *lbm, *lbmptr;
int32_t *formlength, *bmhdlength, *cmaplength, *bodylength;
int32_t length;
bmhd_t basebmhd;
lbm = lbmptr = malloc(width * height + 1000);
if(!lbm)
return; // qb: NULL if width or height is zero.
//
// start FORM
//
*lbmptr++ = 'F';
*lbmptr++ = 'O';
*lbmptr++ = 'R';
*lbmptr++ = 'M';
formlength = (int32_t *)lbmptr;
lbmptr += 4; // leave space for length
*lbmptr++ = 'P';
*lbmptr++ = 'B';
*lbmptr++ = 'M';
*lbmptr++ = ' ';
//
// write BMHD
//
*lbmptr++ = 'B';
*lbmptr++ = 'M';
*lbmptr++ = 'H';
*lbmptr++ = 'D';
bmhdlength = (int32_t *)lbmptr;
lbmptr += 4; // leave space for length
memset(&basebmhd, 0, sizeof(basebmhd));
basebmhd.w = BigShort((short)width);
basebmhd.h = BigShort((short)height);
basebmhd.nPlanes = BigShort(8);
basebmhd.xAspect = BigShort(5);
basebmhd.yAspect = BigShort(6);
basebmhd.pageWidth = BigShort((short)width);
basebmhd.pageHeight = BigShort((short)height);
memcpy(lbmptr, &basebmhd, sizeof(basebmhd));
lbmptr += sizeof(basebmhd);
length = lbmptr - (byte *)bmhdlength - 4;
*bmhdlength = BigLong(length);
if(length & 1)
*lbmptr++ = 0; // pad chunk to even offset
//
// write CMAP
//
*lbmptr++ = 'C';
*lbmptr++ = 'M';
*lbmptr++ = 'A';
*lbmptr++ = 'P';
cmaplength = (int32_t *)lbmptr;
lbmptr += 4; // leave space for length
memcpy(lbmptr, palette, 768);
lbmptr += 768;
length = lbmptr - (byte *)cmaplength - 4;
*cmaplength = BigLong(length);
if(length & 1)
*lbmptr++ = 0; // pad chunk to even offset
//
// write BODY
//
*lbmptr++ = 'B';
*lbmptr++ = 'O';
*lbmptr++ = 'D';
*lbmptr++ = 'Y';
bodylength = (int32_t *)lbmptr;
lbmptr += 4; // leave space for length
memcpy(lbmptr, data, width * height);
lbmptr += width * height;
length = lbmptr - (byte *)bodylength - 4;
*bodylength = BigLong(length);
if(length & 1)
*lbmptr++ = 0; // pad chunk to even offset
//
// done
//
length = lbmptr - (byte *)formlength - 4;
*formlength = BigLong(length);
if(length & 1)
*lbmptr++ = 0; // pad chunk to even offset
//
// write output file
//
SaveFile(filename, lbm, lbmptr - lbm);
free(lbm);
}
/*
============================================================================
LOAD PCX
============================================================================
*/
typedef struct {
char manufacturer;
char version;
char encoding;
char bits_per_pixel;
uint16_t xmin, ymin, xmax, ymax;
uint16_t hres, vres;
uint8_t palette[48];
char reserved;
char color_planes;
uint16_t bytes_per_line;
uint16_t palette_type;
char filler[58];
uint8_t data; // unbounded
} pcx_t;
/*
==============
LoadPCX
==============
*/
void LoadPCX(char *filename, byte **pic, byte **palette, int32_t *width, int32_t *height) {
byte *raw;
pcx_t *pcx;
int32_t x, y;
int32_t len;
int32_t dataByte, runLength;
byte *out, *pix;
//
// load the file
//
len = LoadFile(filename, (void **)&raw);
//
// parse the PCX file
//
pcx = (pcx_t *)raw;
raw = &pcx->data;
pcx->xmin = LittleShort(pcx->xmin);
pcx->ymin = LittleShort(pcx->ymin);
pcx->xmax = LittleShort(pcx->xmax);
pcx->ymax = LittleShort(pcx->ymax);
pcx->hres = LittleShort(pcx->hres);
pcx->vres = LittleShort(pcx->vres);
pcx->bytes_per_line = LittleShort(pcx->bytes_per_line);
pcx->palette_type = LittleShort(pcx->palette_type);
if(pcx->manufacturer != 0x0a || pcx->version != 5 || pcx->encoding != 1 || pcx->bits_per_pixel != 8 ||
pcx->xmax >= 640 || pcx->ymax >= 480)
Error("Bad pcx file %s", filename);
if(palette) {
*palette = malloc(768);
memcpy(*palette, (byte *)pcx + len - 768, 768);
}
if(width)
*width = pcx->xmax + 1;
if(height)
*height = pcx->ymax + 1;
if(!pic)
return;
out = malloc((pcx->ymax + 1) * (pcx->xmax + 1));
if(!out)
Error("Skin_Cache: couldn't allocate");
*pic = out;
pix = out;
for(y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1) {
for(x = 0; x <= pcx->xmax;) {
dataByte = *raw++;
if((dataByte & 0xC0) == 0xC0) {
runLength = dataByte & 0x3F;
dataByte = *raw++;
} else
runLength = 1;
while(runLength-- > 0)
pix[x++] = dataByte;
}
}
if(raw - (byte *)pcx > len)
Error("PCX file %s was malformed", filename);
free(pcx);
}
/*
==============
WritePCXfile
==============
*/
void WritePCXfile(char *filename, byte *data, int32_t width, int32_t height, byte *palette) {
int32_t i, j, length;
pcx_t *pcx;
byte *pack;
pcx = malloc(width * height * 2 + 1000);
memset(pcx, 0, sizeof(*pcx));
pcx->manufacturer = 0x0a; // PCX id
pcx->version = 5; // 256 color
pcx->encoding = 1; // uncompressed
pcx->bits_per_pixel = 8; // 256 color
pcx->xmin = 0;
pcx->ymin = 0;
pcx->xmax = LittleShort((short)(width - 1));
pcx->ymax = LittleShort((short)(height - 1));
pcx->hres = LittleShort((short)width);
pcx->vres = LittleShort((short)height);
pcx->color_planes = 1; // chunky image
pcx->bytes_per_line = LittleShort((short)width);
pcx->palette_type = LittleShort(2); // not a grey scale
// pack the image
pack = &pcx->data;
for(i = 0; i < height; i++) {
for(j = 0; j < width; j++) {
if((*data & 0xc0) != 0xc0)
*pack++ = *data++;
else {
*pack++ = 0xc1;
*pack++ = *data++;
}
}
}
// write the palette
*pack++ = 0x0c; // palette ID byte
for(i = 0; i < 768; i++)
*pack++ = *palette++;
// write output file
length = pack - (byte *)pcx;
SaveFile(filename, pcx, length);
free(pcx);
}
/*
============================================================================
LOAD IMAGE
============================================================================
*/
/*
==============
Load256Image
Will load either an lbm or pcx, depending on extension.
Any of the return pointers can be NULL if you don't want them.
==============
*/
void Load256Image(char *name, byte **pixels, byte **palette, int32_t *width, int32_t *height) {
char ext[128];
ExtractFileExtension(name, ext);
if(!Q_strcasecmp(ext, "lbm")) {
LoadLBM(name, pixels, palette);
if(width)
*width = bmhd.w;
if(height)
*height = bmhd.h;
} else if(!Q_strcasecmp(ext, "pcx")) {
LoadPCX(name, pixels, palette, width, height);
} else
Error("%s doesn't have a known image extension", name);
}
/*
==============
Save256Image
Will save either an lbm or pcx, depending on extension.
==============
*/
void Save256Image(char *name, byte *pixels, byte *palette, int32_t width, int32_t height) {
char ext[128];
ExtractFileExtension(name, ext);
if(!Q_strcasecmp(ext, "lbm")) {
WriteLBMfile(name, pixels, width, height, palette);
} else if(!Q_strcasecmp(ext, "pcx")) {
WritePCXfile(name, pixels, width, height, palette);
} else
Error("%s doesn't have a known image extension", name);
}
/*
============================================================================
TARGA IMAGE
============================================================================
*/
typedef struct _TargaHeader {
uint8_t id_length, colormap_type, image_type;
uint16_t colormap_index, colormap_length;
uint8_t colormap_size;
uint16_t x_origin, y_origin, width, height;
uint8_t pixel_size, attributes;
} TargaHeader;
int32_t fgetLittleShort(FILE *f) {
byte b1, b2;
b1 = fgetc(f);
b2 = fgetc(f);
return (short)(b1 + b2 * 256);
}
int32_t fgetLittleLong(FILE *f) {
byte b1, b2, b3, b4;
b1 = fgetc(f);
b2 = fgetc(f);
b3 = fgetc(f);
b4 = fgetc(f);
return b1 + (b2 << 8) + (b3 << 16) + (b4 << 24);
}
/*
=============
LoadTGA
=============
*/
void LoadTGA(char *name, byte **pixels, int32_t *width, int32_t *height) {
int32_t columns, rows, numPixels;
byte *pixbuf;
int32_t row, column;
FILE *fin;
byte *targa_rgba;
TargaHeader targa_header;
fin = fopen(name, "rb");
if(!fin)
Error("Couldn't read %s", name);
targa_header.id_length = fgetc(fin);
targa_header.colormap_type = fgetc(fin);
targa_header.image_type = fgetc(fin);
targa_header.colormap_index = fgetLittleShort(fin);
targa_header.colormap_length = fgetLittleShort(fin);
targa_header.colormap_size = fgetc(fin);
targa_header.x_origin = fgetLittleShort(fin);
targa_header.y_origin = fgetLittleShort(fin);
targa_header.width = fgetLittleShort(fin);
targa_header.height = fgetLittleShort(fin);
targa_header.pixel_size = fgetc(fin);
targa_header.attributes = fgetc(fin);
if(targa_header.image_type != 2 && targa_header.image_type != 10)
Error("LoadTGA %s: Only type 2 and 10 targa RGB images supported\n", name);
if(targa_header.colormap_type != 0 || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24))
Error("LoadTGA %s: Only 32 or 24 bit images supported (no colormaps)\n", name);
columns = targa_header.width;
rows = targa_header.height;
numPixels = columns * rows;
if(width)
*width = columns;
if(height)
*height = rows;
targa_rgba = malloc(numPixels * 4);
*pixels = targa_rgba;
if(targa_header.id_length != 0)
fseek(fin, targa_header.id_length, SEEK_CUR); // skip TARGA image comment
if(targa_header.image_type == 2) { // Uncompressed, RGB images
for(row = rows - 1; row >= 0; row--) {
pixbuf = targa_rgba + row * columns * 4;
for(column = 0; column < columns; column++) {
uint8_t red, green, blue, alphabyte;
switch(targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
}
}
}
} else if(targa_header.image_type == 10) { // Runlength encoded RGB images
// qb: set defaults. from AAtools
uint8_t red = 255, green = 255, blue = 255, alphabyte = 255, packetHeader, packetSize, j;
for(row = rows - 1; row >= 0; row--) {
pixbuf = targa_rgba + row * columns * 4;
for(column = 0; column < columns;) {
packetHeader = getc(fin);
packetSize = 1 + (packetHeader & 0x7f);
if(packetHeader & 0x80) { // run-length packet
switch(targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
break;
}
for(j = 0; j < packetSize; j++) {
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
column++;
if(column == columns) { // run spans across rows
column = 0;
if(row > 0)
row--;
else
goto breakOut;
pixbuf = targa_rgba + row * columns * 4;
}
}
} else { // non run-length packet
for(j = 0; j < packetSize; j++) {
switch(targa_header.pixel_size) {
case 24:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = 255;
break;
case 32:
blue = getc(fin);
green = getc(fin);
red = getc(fin);
alphabyte = getc(fin);
*pixbuf++ = red;
*pixbuf++ = green;
*pixbuf++ = blue;
*pixbuf++ = alphabyte;
break;
}
column++;
if(column == columns) { // pixel packet run spans across rows
column = 0;
if(row > 0)
row--;
else
goto breakOut;
pixbuf = targa_rgba + row * columns * 4;
}
}
}
}
breakOut:;
}
}
fclose(fin);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
//
// l3dslib.c: library for loading triangles from an Alias triangle file
//
#include <stdio.h>
#include "cmdlib.h"
#include "mathlib.h"
#include "4data.h"
#define MAIN3DS 0x4D4D
#define EDIT3DS 0x3D3D // this is the start of the editor config
#define EDIT_OBJECT 0x4000
#define OBJ_TRIMESH 0x4100
#define TRI_VERTEXL 0x4110
#define TRI_FACEL1 0x4120
#define MAXVERTS 2000
typedef struct {
int32_t v[4];
} tri;
float fverts[MAXVERTS][3];
tri tris[MAXTRIANGLES];
int32_t bytesread, level, numtris, totaltris;
int32_t vertsfound, trisfound;
triangle_t *ptri;
// Alias stores triangles as 3 explicit vertices in .tri files, so even though we
// start out with a vertex pool and vertex indices for triangles, we have to convert
// to raw, explicit triangles
void StoreAliasTriangles(void) {
int32_t i, j, k;
if ((totaltris + numtris) > MAXTRIANGLES)
Error("Error: Too many triangles");
for (i = 0; i < numtris; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 3; k++) {
ptri[i + totaltris].verts[j][k] = fverts[tris[i].v[j]][k];
}
}
}
totaltris += numtris;
numtris = 0;
vertsfound = 0;
trisfound = 0;
}
int32_t ParseVertexL(FILE *input) {
int32_t i, j, startbytesread, numverts;
uint16_t tshort;
if (vertsfound)
Error("Error: Multiple vertex chunks");
vertsfound = 1;
startbytesread = bytesread;
if (feof(input))
Error("Error: unexpected end of file");
fread(&tshort, sizeof(tshort), 1, input);
bytesread += sizeof(tshort);
numverts = (int32_t)tshort;
if (numverts > MAXVERTS)
Error("Error: Too many vertices");
for (i = 0; i < numverts; i++) {
for (j = 0; j < 3; j++) {
if (feof(input))
Error("Error: unexpected end of file");
fread(&fverts[i][j], sizeof(float), 1, input);
bytesread += sizeof(float);
}
}
if (vertsfound && trisfound)
StoreAliasTriangles();
return bytesread - startbytesread;
}
int32_t ParseFaceL1(FILE *input) {
int32_t i, j, startbytesread;
uint16_t tshort;
if (trisfound)
Error("Error: Multiple face chunks");
trisfound = 1;
startbytesread = bytesread;
if (feof(input))
Error("Error: unexpected end of file");
fread(&tshort, sizeof(tshort), 1, input);
bytesread += sizeof(tshort);
numtris = (int32_t)tshort;
if (numtris > MAXTRIANGLES)
Error("Error: Too many triangles");
for (i = 0; i < numtris; i++) {
for (j = 0; j < 4; j++) {
if (feof(input))
Error("Error: unexpected end of file");
fread(&tshort, sizeof(tshort), 1, input);
bytesread += sizeof(tshort);
tris[i].v[j] = (int32_t)tshort;
}
}
if (vertsfound && trisfound)
StoreAliasTriangles();
return bytesread - startbytesread;
}
int32_t ParseChunk(FILE *input) {
#define CHUNK_SIZE 4096
char temp[CHUNK_SIZE];
uint16_t type;
int32_t i, length, w, t, retval;
level++;
retval = 0;
// chunk type
if (feof(input))
Error("Error: unexpected end of file");
fread(&type, sizeof(type), 1, input);
bytesread += sizeof(type);
// chunk length
if (feof(input))
Error("Error: unexpected end of file");
fread(&length, sizeof(length), 1, input);
bytesread += sizeof(length);
w = length - 6;
// process chunk if we care about it, otherwise skip it
switch (type) {
case TRI_VERTEXL:
w -= ParseVertexL(input);
goto ParseSubchunk;
case TRI_FACEL1:
w -= ParseFaceL1(input);
goto ParseSubchunk;
case EDIT_OBJECT:
// read the name
i = 0;
do {
if (feof(input))
Error("Error: unexpected end of file");
fread(&temp[i], 1, 1, input);
i++;
w--;
bytesread++;
} while (temp[i - 1]);
case MAIN3DS:
case OBJ_TRIMESH:
case EDIT3DS:
// parse through subchunks
ParseSubchunk:
while (w > 0) {
w -= ParseChunk(input);
}
retval = length;
goto Done;
default:
// skip other chunks
while (w > 0) {
t = w;
if (t > CHUNK_SIZE)
t = CHUNK_SIZE;
if (feof(input))
Error("Error: unexpected end of file");
fread(&temp, t, 1, input);
bytesread += t;
w -= t;
}
retval = length;
goto Done;
}
Done:
level--;
return retval;
}
void Load3DSTriangleList(char *filename, triangle_t **pptri, int32_t *numtriangles) {
FILE *input;
int16_t tshort;
bytesread = 0;
level = 0;
numtris = 0;
totaltris = 0;
vertsfound = 0;
trisfound = 0;
if ((input = fopen(filename, "rb")) == 0) {
fprintf(stderr, "reader: could not open file '%s'\n", filename);
exit(0);
}
fread(&tshort, sizeof(tshort), 1, input);
// should only be MAIN3DS, but some files seem to start with EDIT3DS, with
// no MAIN3DS
if ((tshort != MAIN3DS) && (tshort != EDIT3DS)) {
fprintf(stderr, "File is not a 3DS file.\n");
exit(0);
}
// back to top of file so we can parse the first chunk descriptor
fseek(input, 0, SEEK_SET);
ptri = malloc(MAXTRIANGLES * sizeof(triangle_t));
*pptri = ptri;
// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT |
// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks
ParseChunk(input);
if (vertsfound || trisfound)
Error("Incomplete triangle set");
*numtriangles = totaltris;
fclose(input);
free(ptri); // qb: stop mem leak
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
// cmdlib.c
#include "cmdlib.h"
#include <sys/types.h>
#include <sys/stat.h>
#ifdef WIN32
#include <direct.h>
#endif
#ifdef NeXT
#include <libc.h>
#endif
#ifdef __unix__
#include <unistd.h>
#endif
#define PATHSEPERATOR '/'
// set these before calling CheckParm
int32_t myargc;
char **myargv;
char com_token[1024];
qboolean com_eof;
qboolean archive;
char archivedir[1024];
char inbase[32];
char outbase[32];
char source[1024];
/*
===================
ExpandWildcards
Mimic unix command line expansion
===================
*/
#define MAX_EX_ARGC 1024
int32_t ex_argc;
char *ex_argv[MAX_EX_ARGC];
#ifdef _WIN32
#include "io.h"
void ExpandWildcards(int32_t *argc, char ***argv) {
struct _finddata_t fileinfo;
int32_t handle;
int32_t i;
char filename[1024];
char filebase[1024];
char *path;
ex_argc = 0;
for (i = 0; i < *argc; i++) {
path = (*argv)[i];
if (path[0] == '-' || (!strstr(path, "*") && !strstr(path, "?"))) {
ex_argv[ex_argc++] = path;
continue;
}
handle = _findfirst(path, &fileinfo);
if (handle == -1)
return;
ExtractFilePath(path, filebase);
do {
sprintf(filename, "%s%s", filebase, fileinfo.name);
ex_argv[ex_argc++] = copystring(filename);
} while (_findnext(handle, &fileinfo) != -1);
_findclose(handle);
}
*argc = ex_argc;
*argv = ex_argv;
}
#else
void ExpandWildcards(int32_t *argc, char ***argv) {
}
#endif
#ifdef WIN_ERROR
#include <windows.h>
/*
=================
Error
For abnormal program terminations in windowed apps
=================
*/
void Error(char *error, ...) {
va_list argptr;
char text[1024];
char text2[1024];
int32_t err;
err = GetLastError();
va_start(argptr, error);
vsprintf(text, error, argptr);
va_end(argptr);
sprintf(text2, "%s\nGetLastError() = %i", text, err);
MessageBox(NULL, text2, "Error", 0 /* MB_OK */);
exit(1);
}
#else
/*
=================
Error
For abnormal program terminations in console apps
=================
*/
void Error(char *error, ...) {
va_list argptr;
printf("\n************ ERROR ************\n");
va_start(argptr, error);
vprintf(error, argptr);
va_end(argptr);
printf("\n");
exit(1);
}
#endif
// only qprintf if in verbose mode
qboolean verbose = false;
void qprintf(char *format, ...) {
va_list argptr;
if (!verbose)
return;
va_start(argptr, format);
vprintf(format, argptr);
va_end(argptr);
fflush(stdout);
}
// qb: similar to AAtools, except basedir defaults to moddir
/*
* Path determination
*
* assumes
* moddir is parent of whatever directory contains the .map/.bsp
* basedir is moddir
* gamedir is parent of moddir
* qdir is parent of gamedir
*/
char qdir[1024] = "";
char gamedir[1024] = "";
char moddir[1024] = "";
char basedir[1024] = "";
void SetQdirFromPath(char *path) {
char cwd_map[512];
char *lastslash;
memset(cwd_map, 0, sizeof(cwd_map));
strncpy(cwd_map, path, sizeof(cwd_map) - 1);
#ifdef WIN32
lastslash = strrchr(cwd_map, '\\');
// I believe Win32 can use either slash type. -Max
if (lastslash == NULL)
#endif
lastslash = strrchr(cwd_map, '/');
if (lastslash == NULL) {
// no slashes: CWD
Q_getwd(cwd_map); // appends path separator
} else {
// get rid of everything after last path separator
lastslash++;
*lastslash = '\0';
}
strcpy(moddir, cwd_map);
#ifdef WIN32
strcat(moddir, "..\\");
#else
strcat(moddir, "../");
#endif
strcpy(basedir, moddir);
strcpy(gamedir, moddir);
#ifdef WIN32
strcat(gamedir, "..\\");
#else
strcat(gamedir, "../");
#endif
strcpy(qdir, gamedir);
#ifdef WIN32
strcat(qdir, "..\\");
#else
strcat(qdir, "../");
#endif
}
char *ExpandArg(const char *path) {
static char full[1024];
if (path[0] != '/' && path[0] != '\\' && path[1] != ':') {
Q_getwd(full);
strcat(full, path);
} else
strcpy(full, path);
return full;
}
char *ExpandPath(char *path) {
static char full[1024];
if (!qdir[0])
Error("ExpandPath called without qdir set");
if (path[0] == '/' || path[0] == '\\' || path[1] == ':')
return path;
sprintf(full, "%s%s", qdir, path);
return full;
}
char *ExpandPathAndArchive(char *path) {
char *expanded;
char archivename[2060];
expanded = ExpandPath(path);
if (archive) {
sprintf(archivename, "%s/%s", archivedir, path);
QCopyFile(expanded, archivename);
}
return expanded;
}
char *copystring(char *s) {
char *b;
b = malloc(strlen(s) + 1);
strcpy(b, s);
return b;
}
/*
================
I_FloatTime
================
*/
double I_FloatTime(void) {
time_t t;
time(&t);
return t;
#if 0
// more precise, less portable
struct timeval tp;
struct timezone tzp;
static int32_t secbase;
gettimeofday(&tp, &tzp);
if (!secbase)
{
secbase = tp.tv_sec;
return tp.tv_usec / 1000000.0;
}
return (tp.tv_sec - secbase) + tp.tv_usec / 1000000.0;
#endif
}
void Q_pathslash(char *out) { // qb: added
qboolean lastslash;
#ifdef WIN32
lastslash = (*out && out[strlen(out + 1)] == '\\');
// I believe Win32 can use either slash type. -Max
if (!lastslash)
#endif
lastslash = (*out && out[strlen(out + 1)] == '/');
if (!lastslash)
#ifdef WIN32
strcat(out, "\\");
#else
strcat(out, "/");
#endif
}
void Q_getwd(char *out) {
#ifdef WIN32
if (!_getcwd(out, 256))
Error("_getcwd failed");
strcat(out, "\\");
#else
if (!getcwd(out, 256)) // qb: was getwd(out)
Error("getcwd failed");
strcat(out, "/");
#endif
}
void Q_mkdir(char *path) {
#ifdef WIN32
if (_mkdir(path) != -1)
return;
#else
if (mkdir(path, 0777) != -1)
return;
#endif
if (errno != EEXIST)
Error("mkdir %s: %s", path, strerror(errno));
}
/*
============
FileTime
returns -1 if not present
============
*/
int32_t FileTime(char *path) {
struct stat buf;
if (stat(path, &buf) == -1)
return -1;
return buf.st_mtime;
}
/*
==============
COM_Parse
Parse a token out of a string
==============
*/
char *COM_Parse(char *data) {
int32_t c;
int32_t len;
len = 0;
com_token[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ((c = *data) <= ' ') {
if (c == 0) {
com_eof = true;
return NULL; // end of file;
}
data++;
}
// skip // comments
if (c == '/' && data[1] == '/') {
while (*data && *data != '\n')
data++;
goto skipwhite;
}
// handle quoted strings specially
if (c == '\"') {
data++;
do {
c = *data++;
if (c == '\"') {
com_token[len] = 0;
return data;
}
com_token[len] = c;
len++;
} while (1);
}
// parse single characters
if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':') {
com_token[len] = c;
len++;
com_token[len] = 0;
return data + 1;
}
// parse a regular word
do {
com_token[len] = c;
data++;
len++;
c = *data;
if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':')
break;
} while (c > 32);
com_token[len] = 0;
return data;
}
int32_t Q_strncasecmp(char *s1, char *s2, int32_t n) {
int32_t c1, c2;
do {
c1 = *s1++;
c2 = *s2++;
if (!n--)
return 0; // strings are equal until end point
if (c1 != c2) {
if (c1 >= 'a' && c1 <= 'z')
c1 -= ('a' - 'A');
if (c2 >= 'a' && c2 <= 'z')
c2 -= ('a' - 'A');
if (c1 != c2)
return -1; // strings not equal
}
} while (c1);
return 0; // strings are equal
}
int32_t Q_strcasecmp(char *s1, char *s2) {
return Q_strncasecmp(s1, s2, 99999);
}
char *strtoupper(char *start) {
char *in;
in = start;
while (*in) {
*in = toupper(*in);
in++;
}
return start;
}
char *strlower(char *start) {
char *in;
in = start;
while (*in) {
*in = tolower(*in);
in++;
}
return start;
}
/*
=============================================================================
MISC FUNCTIONS
=============================================================================
*/
/*
=================
CheckParm
Checks for the given parameter in the program's command line arguments
Returns the argument number (1 to argc-1) or 0 if not present
=================
*/
int32_t CheckParm(char *check) {
int32_t i;
for (i = 1; i < myargc; i++) {
if (!Q_strcasecmp(check, myargv[i]))
return i;
}
return 0;
}
/*
================
Q_filelength
================
*/
int32_t Q_filelength(FILE *f) {
int32_t pos;
int32_t end;
pos = ftell(f);
fseek(f, 0, SEEK_END);
end = ftell(f);
fseek(f, pos, SEEK_SET);
return end;
}
FILE *SafeOpenWrite(char *filename) {
FILE *f;
f = fopen(filename, "wb");
if (!f)
Error("Error opening %s: %s", filename, strerror(errno));
return f;
}
FILE *SafeOpenRead(char *filename) {
FILE *f;
f = fopen(filename, "rb");
if (!f)
Error("Error opening %s: %s", filename, strerror(errno));
return f;
}
void SafeRead(FILE *f, void *buffer, int32_t count) {
if (fread(buffer, 1, count, f) != (size_t)count)
Error("File read failure");
}
void SafeWrite(FILE *f, void *buffer, int32_t count) {
if (fwrite(buffer, 1, count, f) != (size_t)count)
Error("File write failure");
}
/*
==============
FileExists
==============
*/
qboolean FileExists(char *filename) {
FILE *f;
f = fopen(filename, "r");
if (!f)
return false;
fclose(f);
return true;
}
/*
==============
LoadFile
==============
*/
int32_t LoadFile(char *filename, void **bufferptr) {
FILE *f;
int32_t length;
void *buffer;
f = SafeOpenRead(filename);
length = Q_filelength(f);
buffer = malloc(length + 1);
((char *)buffer)[length] = 0;
SafeRead(f, buffer, length);
fclose(f);
*bufferptr = buffer;
return length;
}
/*
==============
TryLoadFile
Allows failure
==============
*/
int32_t TryLoadFile(char *filename, void **bufferptr, int32_t print_error) {
FILE *f;
int32_t length;
void *buffer;
*bufferptr = NULL;
f = fopen(filename, "rb");
if (!f) // qb: print error - GDD tools
{
if (print_error)
printf(" File %s failed to open\n", filename);
return -1;
}
length = Q_filelength(f);
buffer = malloc(length + 1);
((char *)buffer)[length] = 0;
SafeRead(f, buffer, length);
fclose(f);
*bufferptr = buffer;
return length;
}
/*
==============
TryLoadFileFromPak //qb: GDD tools
Allows failure
==============
*/
typedef struct
{
uint8_t magic[4]; // Name of the new WAD format
long diroffset; // Position of WAD directory from start of file
long dirsize; // Number of entries * 0x40 (64 char)
uint8_t bogus[50];
} pakheader_t;
typedef struct
{
uint8_t filename[0x38]; // Name of the file, Unix style, with extension,
// 56 chars, padded with '\0'.
long offset; // Position of the entry in PACK file
long size; // Size of the entry in PACK file
} pakentry_t;
int32_t TryLoadFileFromPak(char *filename, void **bufferptr, char *gd) {
FILE *f;
int32_t n, i, ret_len;
long dir_ents;
void *buffer;
pakheader_t pak_header;
pakentry_t *pak_entry;
char file[1024];
for (n = 0; filename[n] != 0; n++) {
if (filename[n] == '\\')
filename[n] = '/';
}
*bufferptr = NULL;
ret_len = -1;
for (n = 0;; n++) {
if (ret_len != -1)
break;
sprintf(file, "%spak%d.pak", gd, n);
f = fopen(file, "rb");
if (!f) {
// if(errno == ENOENT) jit
return -1;
continue;
}
SafeRead(f, &pak_header, sizeof(pakheader_t));
dir_ents = pak_header.dirsize / sizeof(pakentry_t);
pak_entry = malloc(pak_header.dirsize);
if (pak_entry != NULL) {
if (!fseek(f, pak_header.diroffset, SEEK_SET)) {
memset(pak_entry, 0, pak_header.dirsize);
SafeRead(f, pak_entry, pak_header.dirsize);
for (i = 0; i < dir_ents; i++) {
for (n = 0; pak_entry[i].filename[n] != 0; n++) {
if (pak_entry[i].filename[n] == '\\')
pak_entry[i].filename[n] = '/';
}
if (!Q_strncasecmp((char *)pak_entry[i].filename, filename, 56)) {
if (!fseek(f, pak_entry[i].offset, SEEK_SET)) {
buffer = malloc(pak_entry[i].size + 1);
((char *)buffer)[pak_entry[i].size] = 0;
SafeRead(f, buffer, pak_entry[i].size);
*bufferptr = buffer;
ret_len = pak_entry[i].size;
}
break;
}
}
}
}
if (pak_entry != NULL)
free(pak_entry);
fclose(f);
}
return ret_len;
}
/*
*/
/*
==============
SaveFile
==============
*/
void SaveFile(char *filename, void *buffer, int32_t count) {
FILE *f;
f = SafeOpenWrite(filename);
SafeWrite(f, buffer, count);
fclose(f);
}
void DefaultExtension(char *path, char *extension) {
char *src;
//
// if path doesnt have a .EXT, append extension
// (extension should include the .)
//
src = path + strlen(path) - 1;
while (*src != PATHSEPERATOR && src != path) {
if (*src == '.')
return; // it has an extension
src--;
}
strcat(path, extension);
}
void DefaultPath(char *path, char *basepath) {
char temp[128];
if (path[0] == PATHSEPERATOR)
return; // absolute path location
strcpy(temp, path);
strcpy(path, basepath);
strcat(path, temp);
}
void StripFilename(char *path) {
int32_t length;
length = strlen(path) - 1;
while (length > 0 && path[length] != PATHSEPERATOR)
length--;
path[length] = 0;
}
void StripExtension(char *path) {
int32_t length;
length = strlen(path) - 1;
while (length > 0 && path[length] != '.') {
length--;
if (path[length] == '/' || path[length] == '\\')
return; // no extension
}
if (length)
path[length] = 0;
}
/*
====================
Extract file parts
====================
*/
// FIXME: should include the slash, otherwise
// backing to an empty path will be wrong when appending a slash
void ExtractFilePath(char *path, char *dest) {
char *src;
src = path + strlen(path) - 1;
//
// back up until a \ or the start
//
while (src != path && *(src - 1) != '\\' && *(src - 1) != '/')
src--;
memcpy(dest, path, src - path);
dest[src - path] = 0;
}
void ExtractFileBase(char *path, char *dest) {
char *src;
src = path + strlen(path) - 1;
//
// back up until a \ or the start
//
while (src != path && *(src - 1) != PATHSEPERATOR)
src--;
while (*src && *src != '.') {
*dest++ = *src++;
}
*dest = 0;
}
void ExtractFileExtension(char *path, char *dest) {
char *src;
src = path + strlen(path) - 1;
//
// back up until a . or the start
//
while (src != path && *(src - 1) != '.')
src--;
if (src == path) {
*dest = 0; // no extension
return;
}
strcpy(dest, src);
}
/*
==============
ParseNum / ParseHex
==============
*/
int32_t ParseHex(char *hex) {
char *str;
int32_t num;
num = 0;
str = hex;
while (*str) {
num <<= 4;
if (*str >= '0' && *str <= '9')
num += *str - '0';
else if (*str >= 'a' && *str <= 'f')
num += 10 + *str - 'a';
else if (*str >= 'A' && *str <= 'F')
num += 10 + *str - 'A';
else
Error("Bad hex number: %s", hex);
str++;
}
return num;
}
int32_t ParseNum(char *str) {
if (str[0] == '$')
return ParseHex(str + 1);
if (str[0] == '0' && str[1] == 'x')
return ParseHex(str + 2);
return atol(str);
}
/*
============================================================================
BYTE ORDER FUNCTIONS
============================================================================
*/
#ifdef _SGI_SOURCE
#define __BIG_ENDIAN__
#endif
#ifdef __BIG_ENDIAN__
short LittleShort(short l) {
byte b1, b2;
b1 = l & 255;
b2 = (l >> 8) & 255;
return (b1 << 8) + b2;
}
short BigShort(short l) {
return l;
}
int32_t LittleLong(int32_t l) {
byte b1, b2, b3, b4;
b1 = l & 255;
b2 = (l >> 8) & 255;
b3 = (l >> 16) & 255;
b4 = (l >> 24) & 255;
return ((int32_t)b1 << 24) + ((int32_t)b2 << 16) + ((int32_t)b3 << 8) + b4;
}
int32_t BigLong(int32_t l) {
return l;
}
float LittleFloat(float l) {
union {
byte b[4];
float f;
} in, out;
in.f = l;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.f;
}
float BigFloat(float l) {
return l;
}
#else
short BigShort(short l) {
byte b1, b2;
b1 = l & 255;
b2 = (l >> 8) & 255;
return (b1 << 8) + b2;
}
short LittleShort(short l) {
return l;
}
int32_t BigLong(int32_t l) {
byte b1, b2, b3, b4;
b1 = l & 255;
b2 = (l >> 8) & 255;
b3 = (l >> 16) & 255;
b4 = (l >> 24) & 255;
return ((int32_t)b1 << 24) + ((int32_t)b2 << 16) + ((int32_t)b3 << 8) + b4;
}
int32_t LittleLong(int32_t l) {
return l;
}
float BigFloat(float l) {
union {
byte b[4];
float f;
} in, out;
in.f = l;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.f;
}
float LittleFloat(float l) {
return l;
}
#endif
//=======================================================
// FIXME: byte swap?
// this is a 16 bit, non-reflected CRC using the polynomial 0x1021
// and the initial and final xor values shown below... in other words, the
// CCITT standard CRC used by XMODEM
#define CRC_INIT_VALUE 0xffff
#define CRC_XOR_VALUE 0x0000
static uint16_t crctable[256] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0};
void CRC_Init(uint16_t *crcvalue) {
*crcvalue = CRC_INIT_VALUE;
}
void CRC_ProcessByte(uint16_t *crcvalue, byte data) {
*crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data];
}
uint16_t CRC_Value(uint16_t crcvalue) {
return crcvalue ^ CRC_XOR_VALUE;
}
//=============================================================================
/*
============
CreatePath
============
*/
void CreatePath(char *path) {
char *ofs, c;
if (path[1] == ':')
path += 2;
for (ofs = path + 1; *ofs; ofs++) {
c = *ofs;
if (c == '/' || c == '\\') { // create the directory
*ofs = 0;
Q_mkdir(path);
*ofs = c;
}
}
}
/*
============
QCopyFile
Used to archive source files
============
*/
void QCopyFile(char *from, char *to) {
void *buffer;
int32_t length;
length = LoadFile(from, &buffer);
CreatePath(to);
SaveFile(to, buffer, length);
free(buffer);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "cmdlib.h"
#include "mathlib.h"
#include "bspfile.h"
#include "scriplib.h"
qboolean use_qbsp = false; // qb: huge map support
qboolean noskipfix = false; // qb: warn about SURF_SKIP contents rather than silently changing to zero
void GetLeafNums(void);
//=============================================================================
// qb: add qbsp types
int32_t nummodels;
dmodel_t * dmodels;//[MAX_MAP_MODELS_QBSP];
int32_t visdatasize;
byte * dvisdata;//[MAX_MAP_VISIBILITY_QBSP];
dvis_t *dvis;// = (dvis_t *)dvisdata;
int32_t lightdatasize;
byte * dlightdata;//[MAX_MAP_LIGHTING_QBSP];
int32_t entdatasize;
char * dentdata;//[MAX_MAP_ENTSTRING_QBSP];
int32_t numleafs;
dleaf_t * dleafs;//[MAX_MAP_LEAFS];
dleaf_tx * dleafsX;//[MAX_MAP_LEAFS_QBSP];
int32_t numplanes;
dplane_t * dplanes;//[MAX_MAP_PLANES_QBSP];
int32_t numvertexes;
dvertex_t * dvertexes;//[MAX_MAP_VERTS_QBSP];
int32_t numnodes;
dnode_t * dnodes;//[MAX_MAP_NODES];
dnode_tx * dnodesX;//[MAX_MAP_NODES_QBSP];
int32_t numtexinfo;
texinfo_t * texinfo;//[MAX_MAP_TEXINFO_QBSP];
int32_t numfaces;
dface_t * dfaces;//[MAX_MAP_FACES];
dface_tx * dfacesX;//[MAX_MAP_FACES_QBSP];
int32_t numedges;
dedge_t * dedges;//[MAX_MAP_EDGES];
dedge_tx * dedgesX;//[MAX_MAP_EDGES_QBSP];
int32_t numleaffaces;
uint16_t * dleaffaces;//[MAX_MAP_LEAFFACES];
uint32_t * dleaffacesX;//[MAX_MAP_LEAFFACES_QBSP];
int32_t numleafbrushes;
uint16_t * dleafbrushes;//[MAX_MAP_LEAFBRUSHES];
uint32_t * dleafbrushesX;//[MAX_MAP_LEAFBRUSHES_QBSP];
int32_t numsurfedges;
int32_t * dsurfedges;//[MAX_MAP_SURFEDGES_QBSP];
int32_t numbrushes;
dbrush_t * dbrushes;//[MAX_MAP_BRUSHES_QBSP];
int32_t numbrushsides;
dbrushside_t * dbrushsides;//[MAX_MAP_BRUSHSIDES];
dbrushside_tx * dbrushsidesX;//[MAX_MAP_BRUSHSIDES_QBSP];
int32_t numareas;
darea_t * dareas;//[MAX_MAP_AREAS];
int32_t numareaportals;
dareaportal_t * dareaportals;//[MAX_MAP_AREAPORTALS];
byte dpop[256];
void InitBSPFile(void) {
static qboolean init = false;
if(!init) {
init = true;
dmodels = (dmodel_t*)malloc(sizeof(*dmodels) * MAX_MAP_MODELS_QBSP);
dvisdata = (byte*)malloc(sizeof(*dvisdata) * MAX_MAP_VISIBILITY_QBSP);
dvis = (dvis_t *)dvisdata;
dlightdata = (byte*)malloc(sizeof(*dlightdata) * MAX_MAP_LIGHTING_QBSP);
dentdata = (char*)malloc(sizeof(*dentdata) * MAX_MAP_ENTSTRING_QBSP);
dleafs = (dleaf_t*)malloc(sizeof(*dleafs) * MAX_MAP_LEAFS);
dleafsX = (dleaf_tx*)malloc(sizeof(*dleafsX) * MAX_MAP_LEAFS_QBSP);
dplanes = (dplane_t*)malloc(sizeof(*dplanes) * MAX_MAP_PLANES_QBSP);
dvertexes = (dvertex_t*)malloc(sizeof(*dvertexes) * MAX_MAP_VERTS_QBSP);
dnodes = (dnode_t*)malloc(sizeof(*dnodes) * MAX_MAP_NODES);
dnodesX = (dnode_tx*)malloc(sizeof(*dnodesX) * MAX_MAP_NODES_QBSP);
texinfo = (texinfo_t*)malloc(sizeof(*texinfo) * MAX_MAP_TEXINFO_QBSP);
dfaces = (dface_t*)malloc(sizeof(*dfaces) * MAX_MAP_FACES);
dfacesX = (dface_tx*)malloc(sizeof(*dfacesX) * MAX_MAP_FACES_QBSP);
dedges = (dedge_t*)malloc(sizeof(*dedges) * MAX_MAP_EDGES);
dedgesX = (dedge_tx*)malloc(sizeof(*dedgesX) * MAX_MAP_EDGES_QBSP);
dleaffaces = (uint16_t*)malloc(sizeof(*dleaffaces) * MAX_MAP_LEAFFACES);
dleaffacesX = (uint32_t*)malloc(sizeof(*dleaffacesX) * MAX_MAP_LEAFFACES_QBSP);
dleafbrushes = (uint16_t*)malloc(sizeof(*dleafbrushes) * MAX_MAP_LEAFBRUSHES);
dleafbrushesX = (uint32_t*)malloc(sizeof(*dleafbrushesX) * MAX_MAP_LEAFBRUSHES_QBSP);
dsurfedges = (int32_t*)malloc(sizeof(*dsurfedges) * MAX_MAP_SURFEDGES_QBSP);
dbrushes = (dbrush_t*)malloc(sizeof(*dbrushes) * MAX_MAP_BRUSHES_QBSP);
dbrushsides = (dbrushside_t*)malloc(sizeof(*dbrushsides) * MAX_MAP_BRUSHSIDES);
dbrushsidesX = (dbrushside_tx*)malloc(sizeof(*dbrushsidesX) * MAX_MAP_BRUSHSIDES_QBSP);
dareas = (darea_t*)malloc(sizeof(*dareas) * MAX_MAP_AREAS);
dareaportals = (dareaportal_t*)malloc(sizeof(*dareaportals) * MAX_MAP_AREAPORTALS);
}
}
/*
===============
CompressVis
===============
*/
int32_t CompressVis(byte *vis, byte *dest) {
int32_t j;
int32_t rep;
int32_t visrow;
byte *dest_p;
dest_p = dest;
// visrow = (r_numvisleafs + 7)>>3;
visrow = (dvis->numclusters + 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;
}
/*
===================
DecompressVis
===================
*/
void DecompressVis(byte *in, byte *decompressed) {
int32_t c;
byte *out;
int32_t row;
// row = (r_numvisleafs+7)>>3;
row = (dvis->numclusters + 7) >> 3;
out = decompressed;
do {
if (*in) {
*out++ = *in++;
continue;
}
c = in[1];
if (!c)
Error("DecompressVis: 0 repeat");
in += 2;
while (c) {
*out++ = 0;
c--;
}
} while (out - decompressed < row);
}
//=============================================================================
/*
=============
SwapBSPFile
Byte swaps all data in a bsp file.
=============
*/
void SwapBSPFile(qboolean todisk) {
int32_t i, j;
dmodel_t *d;
// models
for (i = 0; i < nummodels; i++) {
d = &dmodels[i];
d->firstface = LittleLong(d->firstface);
d->numfaces = LittleLong(d->numfaces);
d->headnode = LittleLong(d->headnode);
for (j = 0; j < 3; j++) {
d->mins[j] = LittleFloat(d->mins[j]);
d->maxs[j] = LittleFloat(d->maxs[j]);
d->origin[j] = LittleFloat(d->origin[j]);
}
}
//
// vertexes
//
for (i = 0; i < numvertexes; i++) {
for (j = 0; j < 3; j++)
dvertexes[i].point[j] = LittleFloat(dvertexes[i].point[j]);
}
//
// planes
//
for (i = 0; i < numplanes; i++) {
for (j = 0; j < 3; j++)
dplanes[i].normal[j] = LittleFloat(dplanes[i].normal[j]);
dplanes[i].dist = LittleFloat(dplanes[i].dist);
dplanes[i].type = LittleLong(dplanes[i].type);
}
//
// texinfos
//
for (i = 0; i < numtexinfo; i++) {
texinfo[i].flags = LittleLong(texinfo[i].flags);
texinfo[i].value = LittleLong(texinfo[i].value);
texinfo[i].nexttexinfo = LittleLong(texinfo[i].nexttexinfo);
}
//
// faces nodes leafs brushsides leafbrushes leaffaces
//
if (use_qbsp) {
for (i = 0; i < numfaces; i++) {
dfacesX[i].texinfo = LittleLong(dfacesX[i].texinfo);
dfacesX[i].planenum = LittleLong(dfacesX[i].planenum);
dfacesX[i].side = LittleLong(dfacesX[i].side);
dfacesX[i].lightofs = LittleLong(dfacesX[i].lightofs);
dfacesX[i].firstedge = LittleLong(dfacesX[i].firstedge);
dfacesX[i].numedges = LittleLong(dfacesX[i].numedges);
}
for (i = 0; i < numnodes; i++) {
dnodesX[i].planenum = LittleLong(dnodesX[i].planenum);
for (j = 0; j < 3; j++) {
dnodesX[i].mins[j] = LittleFloat(dnodesX[i].mins[j]);
dnodesX[i].maxs[j] = LittleFloat(dnodesX[i].maxs[j]);
}
dnodesX[i].children[0] = LittleLong(dnodesX[i].children[0]);
dnodesX[i].children[1] = LittleLong(dnodesX[i].children[1]);
dnodesX[i].firstface = LittleLong(dnodesX[i].firstface);
dnodesX[i].numfaces = LittleLong(dnodesX[i].numfaces);
}
for (i = 0; i < numleafs; i++) {
dleafsX[i].contents = LittleLong(dleafsX[i].contents);
dleafsX[i].cluster = LittleLong(dleafsX[i].cluster);
dleafsX[i].area = LittleLong(dleafsX[i].area);
for (j = 0; j < 3; j++) {
dleafsX[i].mins[j] = LittleFloat(dleafsX[i].mins[j]);
dleafsX[i].maxs[j] = LittleFloat(dleafsX[i].maxs[j]);
}
dleafsX[i].firstleafface = LittleLong(dleafsX[i].firstleafface);
dleafsX[i].numleaffaces = LittleLong(dleafsX[i].numleaffaces);
dleafsX[i].firstleafbrush = LittleLong(dleafsX[i].firstleafbrush);
dleafsX[i].numleafbrushes = LittleLong(dleafsX[i].numleafbrushes);
}
for (i = 0; i < numbrushsides; i++) {
dbrushsidesX[i].planenum = LittleLong(dbrushsidesX[i].planenum);
dbrushsidesX[i].texinfo = LittleLong(dbrushsidesX[i].texinfo);
}
for (i = 0; i < numleafbrushes; i++)
dleafbrushesX[i] = LittleLong(dleafbrushesX[i]);
for (i = 0; i < numleaffaces; i++)
dleaffacesX[i] = LittleLong(dleaffacesX[i]);
} else {
for (i = 0; i < numfaces; i++) {
dfaces[i].texinfo = LittleShort(dfaces[i].texinfo);
dfaces[i].planenum = LittleShort(dfaces[i].planenum);
dfaces[i].side = LittleShort(dfaces[i].side);
dfaces[i].lightofs = LittleLong(dfaces[i].lightofs);
dfaces[i].firstedge = LittleLong(dfaces[i].firstedge);
dfaces[i].numedges = LittleShort(dfaces[i].numedges);
}
for (i = 0; i < numnodes; i++) {
dnodes[i].planenum = LittleLong(dnodes[i].planenum);
for (j = 0; j < 3; j++) {
dnodes[i].mins[j] = LittleShort(dnodes[i].mins[j]);
dnodes[i].maxs[j] = LittleShort(dnodes[i].maxs[j]);
}
dnodes[i].children[0] = LittleLong(dnodes[i].children[0]);
dnodes[i].children[1] = LittleLong(dnodes[i].children[1]);
dnodes[i].firstface = LittleShort(dnodes[i].firstface);
dnodes[i].numfaces = LittleShort(dnodes[i].numfaces);
}
for (i = 0; i < numleafs; i++) {
dleafs[i].contents = LittleLong(dleafs[i].contents);
dleafs[i].cluster = LittleShort(dleafs[i].cluster);
dleafs[i].area = LittleShort(dleafs[i].area);
for (j = 0; j < 3; j++) {
dleafs[i].mins[j] = LittleShort(dleafs[i].mins[j]);
dleafs[i].maxs[j] = LittleShort(dleafs[i].maxs[j]);
}
dleafs[i].firstleafface = LittleShort(dleafs[i].firstleafface);
dleafs[i].numleaffaces = LittleShort(dleafs[i].numleaffaces);
dleafs[i].firstleafbrush = LittleShort(dleafs[i].firstleafbrush);
dleafs[i].numleafbrushes = LittleShort(dleafs[i].numleafbrushes);
}
for (i = 0; i < numbrushsides; i++) {
dbrushsides[i].planenum = LittleShort(dbrushsides[i].planenum);
dbrushsides[i].texinfo = LittleShort(dbrushsides[i].texinfo);
}
for (i = 0; i < numleafbrushes; i++)
dleafbrushes[i] = LittleShort(dleafbrushes[i]);
for (i = 0; i < numleaffaces; i++)
dleaffaces[i] = LittleShort(dleaffaces[i]);
}
//
// surfedges
//
for (i = 0; i < numsurfedges; i++)
dsurfedges[i] = LittleLong(dsurfedges[i]);
//
// edges
//
if (use_qbsp)
for (i = 0; i < numedges; i++) {
dedgesX[i].v[0] = LittleLong(dedgesX[i].v[0]);
dedgesX[i].v[1] = LittleLong(dedgesX[i].v[1]);
}
else
for (i = 0; i < numedges; i++) {
dedges[i].v[0] = LittleShort(dedges[i].v[0]);
dedges[i].v[1] = LittleShort(dedges[i].v[1]);
}
//
// brushes
//
for (i = 0; i < numbrushes; i++) {
dbrushes[i].firstside = LittleLong(dbrushes[i].firstside);
dbrushes[i].numsides = LittleLong(dbrushes[i].numsides);
dbrushes[i].contents = LittleLong(dbrushes[i].contents);
}
//
// areas
//
for (i = 0; i < numareas; i++) {
dareas[i].numareaportals = LittleLong(dareas[i].numareaportals);
dareas[i].firstareaportal = LittleLong(dareas[i].firstareaportal);
}
//
// areasportals
//
for (i = 0; i < numareaportals; i++) {
dareaportals[i].portalnum = LittleLong(dareaportals[i].portalnum);
dareaportals[i].otherarea = LittleLong(dareaportals[i].otherarea);
}
//
// visibility
//
if (todisk)
j = dvis->numclusters;
else
j = LittleLong(dvis->numclusters);
dvis->numclusters = LittleLong(dvis->numclusters);
for (i = 0; i < j; i++) {
dvis->bitofs[i][0] = LittleLong(dvis->bitofs[i][0]);
dvis->bitofs[i][1] = LittleLong(dvis->bitofs[i][1]);
}
}
dheader_t *header;
int32_t CopyLump(int32_t lump, void *dest, int32_t size) {
int32_t length, ofs;
length = header->lumps[lump].filelen;
ofs = header->lumps[lump].fileofs;
if (length % size)
Error("LoadBSPFile: odd lump size (length: %i size: %i remainder: %i)", length, size, length % size);
memcpy(dest, (byte *)header + ofs, length);
return length / size;
}
/*
=============
LoadBSPFile
=============
*/
void LoadBSPFile(char *filename) {
int32_t i;
InitBSPFile();
//
// load the file header
//
LoadFile(filename, (void **)&header);
// swap the header
for (i = 0; i < sizeof(dheader_t) / 4; i++)
((int32_t *)header)[i] = LittleLong(((int32_t *)header)[i]);
// qb: qbsp
use_qbsp = false;
switch (header->ident) {
case IDBSPHEADER:
break;
case QBSPHEADER:
use_qbsp = true;
printf("using QBSP extended limits \n");
break;
default:
Error("%s is not a recognized BSP file (IBSP or QBSP).",
filename);
}
if (header->version != BSPVERSION)
Error("%s is version %i, not %i", filename, header->version, BSPVERSION);
nummodels = CopyLump(LUMP_MODELS, dmodels, sizeof(dmodel_t));
numvertexes = CopyLump(LUMP_VERTEXES, dvertexes, sizeof(dvertex_t));
numplanes = CopyLump(LUMP_PLANES, dplanes, sizeof(dplane_t));
if (use_qbsp) {
numleafs = CopyLump(LUMP_LEAFS, dleafsX, sizeof(dleaf_tx));
numnodes = CopyLump(LUMP_NODES, dnodesX, sizeof(dnode_tx));
numtexinfo = CopyLump(LUMP_TEXINFO, texinfo, sizeof(texinfo_t));
numfaces = CopyLump(LUMP_FACES, dfacesX, sizeof(dface_tx));
numleaffaces = CopyLump(LUMP_LEAFFACES, dleaffacesX, sizeof(dleaffacesX[0]));
numleafbrushes = CopyLump(LUMP_LEAFBRUSHES, dleafbrushesX, sizeof(dleafbrushesX[0]));
} else {
numleafs = CopyLump(LUMP_LEAFS, dleafs, sizeof(dleaf_t));
numnodes = CopyLump(LUMP_NODES, dnodes, sizeof(dnode_t));
numtexinfo = CopyLump(LUMP_TEXINFO, texinfo, sizeof(texinfo_t));
numfaces = CopyLump(LUMP_FACES, dfaces, sizeof(dface_t));
numleaffaces = CopyLump(LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0]));
numleafbrushes = CopyLump(LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0]));
}
numsurfedges = CopyLump(LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0]));
if (use_qbsp)
numedges = CopyLump(LUMP_EDGES, dedgesX, sizeof(dedge_tx));
else
numedges = CopyLump(LUMP_EDGES, dedges, sizeof(dedge_t));
numbrushes = CopyLump(LUMP_BRUSHES, dbrushes, sizeof(dbrush_t));
if (use_qbsp)
numbrushsides = CopyLump(LUMP_BRUSHSIDES, dbrushsidesX, sizeof(dbrushside_tx));
else
numbrushsides = CopyLump(LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t));
numareas = CopyLump(LUMP_AREAS, dareas, sizeof(darea_t));
numareaportals = CopyLump(LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t));
visdatasize = CopyLump(LUMP_VISIBILITY, dvisdata, 1);
lightdatasize = CopyLump(LUMP_LIGHTING, dlightdata, 1);
entdatasize = CopyLump(LUMP_ENTITIES, dentdata, 1);
CopyLump(LUMP_POP, dpop, 1);
free(header); // everything has been copied out
//
// swap everything
//
SwapBSPFile(false);
}
/*
=============
LoadBSPFileTexinfo
Only loads the texinfo lump, so 4data can scan for textures
=============
*/
void LoadBSPFileTexinfo(char *filename) {
int32_t i;
FILE *f;
int32_t length, ofs;
header = malloc(sizeof(dheader_t));
f = fopen(filename, "rb");
if (!fread(header, sizeof(dheader_t), 1, f))
Error("Texinfo header read error");
// swap the header
for (i = 0; i < sizeof(dheader_t) / 4; i++)
((int32_t *)header)[i] = LittleLong(((int32_t *)header)[i]);
if (header->ident != IDBSPHEADER && header->ident != QBSPHEADER)
Error("%s is not an IBSP or QBSP file", filename);
if (header->version != BSPVERSION)
Error("%s is version %i, not %i", filename, header->version, BSPVERSION);
length = header->lumps[LUMP_TEXINFO].filelen;
ofs = header->lumps[LUMP_TEXINFO].fileofs;
fseek(f, ofs, SEEK_SET);
if (!fread(texinfo, length, 1, f))
Error("Texinfo lump read error");
fclose(f);
numtexinfo = length / sizeof(texinfo_t);
free(header); // everything has been copied out
SwapBSPFile(false);
}
//============================================================================
FILE *wadfile;
dheader_t outheader;
void AddLump(int32_t lumpnum, void *data, int32_t len) {
lump_t *lump;
lump = &header->lumps[lumpnum];
lump->fileofs = LittleLong(ftell(wadfile));
lump->filelen = LittleLong(len);
SafeWrite(wadfile, data, (len + 3) & ~3);
}
/*
=============
WriteBSPFile
Swaps the bsp file in place, so it should not be referenced again
=============
*/
void WriteBSPFile(char *filename) {
header = &outheader;
memset(header, 0, sizeof(dheader_t));
SwapBSPFile(true);
if (use_qbsp)
header->ident = LittleLong(QBSPHEADER);
else
header->ident = LittleLong(IDBSPHEADER);
header->version = LittleLong(BSPVERSION);
wadfile = SafeOpenWrite(filename);
SafeWrite(wadfile, header, sizeof(dheader_t)); // overwritten later
AddLump(LUMP_PLANES, dplanes, numplanes * sizeof(dplane_t));
if (use_qbsp)
AddLump(LUMP_LEAFS, dleafsX, numleafs * sizeof(dleaf_tx));
else
AddLump(LUMP_LEAFS, dleafs, numleafs * sizeof(dleaf_t));
AddLump(LUMP_VERTEXES, dvertexes, numvertexes * sizeof(dvertex_t));
if (use_qbsp)
AddLump(LUMP_NODES, dnodesX, numnodes * sizeof(dnode_tx));
else
AddLump(LUMP_NODES, dnodes, numnodes * sizeof(dnode_t));
AddLump(LUMP_TEXINFO, texinfo, numtexinfo * sizeof(texinfo_t));
if (use_qbsp)
AddLump(LUMP_FACES, dfacesX, numfaces * sizeof(dface_tx));
else
AddLump(LUMP_FACES, dfaces, numfaces * sizeof(dface_t));
AddLump(LUMP_BRUSHES, dbrushes, numbrushes * sizeof(dbrush_t));
if (use_qbsp) {
AddLump(LUMP_BRUSHSIDES, dbrushsidesX, numbrushsides * sizeof(dbrushside_tx));
AddLump(LUMP_LEAFFACES, dleaffacesX, numleaffaces * sizeof(dleaffacesX[0]));
AddLump(LUMP_LEAFBRUSHES, dleafbrushesX, numleafbrushes * sizeof(dleafbrushesX[0]));
} else {
AddLump(LUMP_BRUSHSIDES, dbrushsides, numbrushsides * sizeof(dbrushside_t));
AddLump(LUMP_LEAFFACES, dleaffaces, numleaffaces * sizeof(dleaffaces[0]));
AddLump(LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes * sizeof(dleafbrushes[0]));
}
AddLump(LUMP_SURFEDGES, dsurfedges, numsurfedges * sizeof(dsurfedges[0]));
if (use_qbsp)
AddLump(LUMP_EDGES, dedgesX, numedges * sizeof(dedge_tx));
else
AddLump(LUMP_EDGES, dedges, numedges * sizeof(dedge_t));
AddLump(LUMP_MODELS, dmodels, nummodels * sizeof(dmodel_t));
AddLump(LUMP_AREAS, dareas, numareas * sizeof(darea_t));
AddLump(LUMP_AREAPORTALS, dareaportals, numareaportals * sizeof(dareaportal_t));
AddLump(LUMP_LIGHTING, dlightdata, lightdatasize);
AddLump(LUMP_VISIBILITY, dvisdata, visdatasize);
AddLump(LUMP_ENTITIES, dentdata, entdatasize);
AddLump(LUMP_POP, dpop, sizeof(dpop));
fseek(wadfile, 0, SEEK_SET);
SafeWrite(wadfile, header, sizeof(dheader_t));
fclose(wadfile);
}
//============================================================================
/*
=============
PrintBSPFileSizes
Dumps info about current file
=============
*/
void PrintBSPFileSizes(void) {
if (!num_entities)
ParseEntities();
printf("\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< FILE STATS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("models: %7i size: %7i\n", nummodels, (int32_t)(nummodels * sizeof(dmodel_t)));
printf("brushes: %7i size: %7i\n", numbrushes, (int32_t)(numbrushes * sizeof(dbrush_t)));
if (use_qbsp)
printf("brushsides: %7i size: %7i\n", numbrushsides, (int32_t)(numbrushsides * sizeof(dbrushside_tx)));
else
printf("brushsides: %7i size: %7i\n", numbrushsides, (int32_t)(numbrushsides * sizeof(dbrushside_t)));
printf("planes: %7i size: %7i\n", numplanes, (int32_t)(numplanes * sizeof(dplane_t)));
printf("texinfo: %7i size: %7i\n", numtexinfo, (int32_t)(numtexinfo * sizeof(texinfo_t)));
printf("entdata: %7i size: %7i\n", num_entities, entdatasize);
printf("vertices: %7i size: %7i\n", numvertexes, (int32_t)(numvertexes * sizeof(dvertex_t)));
if (use_qbsp) {
printf("nodes: %7i size: %7i\n", numnodes, (int32_t)(numnodes * sizeof(dnode_tx)));
printf("faces: %7i size: %7i\n", numfaces, (int32_t)(numfaces * sizeof(dface_tx)));
printf("leafs: %7i size: %7i\n", numleafs, (int32_t)(numleafs * sizeof(dleaf_tx)));
printf("leaffaces: %7i size: %7i\n", numleaffaces, (int32_t)(numleaffaces * sizeof(dleaffacesX[0])));
printf("leafbrushes: %7i size: %7i\n", numleafbrushes, (int32_t)(numleafbrushes * sizeof(dleafbrushesX[0])));
printf("edges: %7i size: %7i\n", numedges, (int32_t)(numedges * sizeof(dedge_tx)));
} else {
printf("nodes: %7i size: %7i\n", numnodes, (int32_t)(numnodes * sizeof(dnode_t)));
printf("faces: %7i size: %7i\n", numfaces, (int32_t)(numfaces * sizeof(dface_t)));
printf("leafs: %7i size: %7i\n", numleafs, (int32_t)(numleafs * sizeof(dleaf_t)));
printf("leaffaces: %7i size: %7i\n", numleaffaces, (int32_t)(numleaffaces * sizeof(dleaffaces[0])));
printf("leafbrushes: %7i size: %7i\n", numleafbrushes, (int32_t)(numleafbrushes * sizeof(dleafbrushes[0])));
printf("edges: %7i size: %7i\n", numedges, (int32_t)(numedges * sizeof(dedge_t)));
}
printf("surfedges: %7i size: %7i\n", numsurfedges, (int32_t)(numsurfedges * sizeof(dsurfedges[0])));
printf(" lightdata size: %7i\n", lightdatasize);
printf(" visdata size: %7i\n", visdatasize);
}
//============================================
int32_t num_entities;
entity_t entities[MAX_MAP_ENTITIES_QBSP];
void StripTrailing(char *e) {
char *s;
s = e + strlen(e) - 1;
while (s >= e && *s <= 32) {
*s = 0;
s--;
}
}
/*
=================
ParseEpair
=================
*/
epair_t *ParseEpair(void) {
epair_t *e;
e = malloc(sizeof(epair_t));
memset(e, 0, sizeof(epair_t));
if (strlen(token) >= MAX_KEY - 1)
Error("ParseEpar: token too long");
e->key = copystring(token);
GetToken(false);
if (strlen(token) >= MAX_VALUE - 1)
Error("ParseEpar: token too long");
e->value = copystring(token);
// strip trailing spaces
StripTrailing(e->key);
StripTrailing(e->value);
return e;
}
/*
================
ParseEntity
================
*/
qboolean ParseEntity(void) {
epair_t *e;
entity_t *mapent;
if (!GetToken(true))
return false;
if (strcmp(token, "{"))
Error("ParseEntity: { not found");
if (use_qbsp) {
if (num_entities == MAX_MAP_ENTITIES_QBSP)
Error("num_entities == MAX_MAP_ENTITIES_QBSP (%i)", MAX_MAP_ENTITIES_QBSP);
} else if (num_entities == MAX_MAP_ENTITIES)
Error("num_entities == MAX_MAP_ENTITIES (%i)", MAX_MAP_ENTITIES);
mapent = &entities[num_entities];
num_entities++;
do {
if (!GetToken(true))
Error("ParseEntity: EOF without closing brace");
if (!strcmp(token, "}"))
break;
e = ParseEpair();
e->next = mapent->epairs;
mapent->epairs = e;
} while (1);
return true;
}
/*
================
ParseEntities
Parses the dentdata string into entities
================
*/
void ParseEntities(void) {
num_entities = 0;
ParseFromMemory(dentdata, entdatasize);
while (ParseEntity()) {
}
}
/*
================
UnparseEntities
Generates the dentdata string from all the entities
================
*/
void UnparseEntities(void) {
char *buf, *end;
epair_t *ep;
char line[2060];
int32_t i;
char key[1024], value[1024];
buf = dentdata;
end = buf;
*end = 0;
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) {
strcpy(key, ep->key);
StripTrailing(key);
strcpy(value, ep->value);
StripTrailing(value);
sprintf(line, "\"%s\" \"%s\"\n", key, value);
strcat(end, line);
end += strlen(line);
}
strcat(end, "}\n");
end += 2;
if (use_qbsp) {
if (end > buf + MAX_MAP_ENTSTRING_QBSP)
Error("QBSP Entity text too long");
} else if (end > buf + MAX_MAP_ENTSTRING)
Error("Entity text too long");
}
entdatasize = end - buf + 1;
}
void PrintEntity(entity_t *ent) {
epair_t *ep;
printf("------- entity %p -------\n", ent);
for (ep = ent->epairs; ep; ep = ep->next) {
printf("%s = %s\n", ep->key, ep->value);
}
}
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)) {
free(ep->value);
ep->value = copystring(value);
return;
}
ep = malloc(sizeof(*ep));
if (ep) // qb: gcc -fanalyzer: could be NULL
{
ep->next = ent->epairs;
ent->epairs = ep;
ep->key = copystring(key);
ep->value = copystring(value);
}
}
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 "";
}
vec_t 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;
double v1, v2, v3;
k = ValueForKey(ent, key);
// scanf into doubles, then assign, so it is vec_t size independent
v1 = v2 = v3 = 0;
sscanf(k, "%lf %lf %lf", &v1, &v2, &v3);
vec[0] = v1;
vec[1] = v2;
vec[2] = v3;
}
void RemoveLastEpair(entity_t *ent) {
epair_t *e = ent->epairs->next;
if (ent->epairs->key)
free(ent->epairs->key);
if (ent->epairs->value)
free(ent->epairs->value);
free(ent->epairs);
ent->epairs = e;
}
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "vis.h"
/*
each portal will have a list of all possible to see from first portal
if (!thread->portalmightsee[portalnum])
portal mightsee
for p2 = all other portals in leaf
get sperating planes
for all portals that might be seen by p2
mark as unseen if not present in seperating plane
flood fill a new mightsee
save as passagemightsee
void CalcMightSee (leaf_t *leaf,
*/
int32_t CountBits(byte *bits, int32_t numbits) {
int32_t i;
int32_t c;
c = 0;
for (i = 0; i < numbits; i++)
if (bits[i >> 3] & (1 << (i & 7)))
c++;
return c;
}
int32_t c_fullskip;
int32_t c_portalskip, c_leafskip;
int32_t c_vistest, c_mighttest;
int32_t c_chop, c_nochop;
int32_t active;
void CheckStack(leaf_t *leaf, threaddata_t *thread) {
pstack_t *p, *p2;
for (p = thread->pstack_head.next; p; p = p->next) {
// printf ("=");
if (p->leaf == leaf)
Error("CheckStack: leaf recursion");
for (p2 = thread->pstack_head.next; p2 != p; p2 = p2->next)
if (p2->leaf == p->leaf)
Error("CheckStack: late leaf recursion");
}
// printf ("\n");
}
winding_t *AllocStackWinding(pstack_t *stack) {
int32_t i;
for (i = 0; i < 3; i++) {
if (stack->freewindings[i]) {
stack->freewindings[i] = 0;
return &stack->windings[i];
}
}
Error("AllocStackWinding: failed");
return NULL;
}
void FreeStackWinding(winding_t *w, pstack_t *stack) {
int32_t i;
i = w - stack->windings;
if (i < 0 || i > 2)
return; // not from local
if (stack->freewindings[i])
Error("FreeStackWinding: allready free");
stack->freewindings[i] = 1;
}
/*
==============
ChopWinding_flow
==============
*/
winding_t *ChopWinding_flow(winding_t *in, pstack_t *stack, plane_t *split) // qb: renamed, dupe in polylib.c
{
vec_t dists[128];
int32_t sides[128];
int32_t counts[3];
vec_t dot;
int32_t i, j;
vec_t *p1, *p2;
vec3_t mid;
winding_t *neww;
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]]++;
}
if (!counts[1])
return in; // completely on front side
if (!counts[0]) {
FreeStackWinding(in, stack);
return NULL;
}
sides[i] = sides[0];
dists[i] = dists[0];
neww = AllocStackWinding(stack);
neww->numpoints = 0;
for (i = 0; i < in->numpoints; i++) {
p1 = in->points[i];
if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) {
FreeStackWinding(neww, stack);
return in; // can't chop -- fall back to original
}
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;
if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) {
FreeStackWinding(neww, stack);
return in; // can't chop -- fall back to original
}
// 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++;
}
// free the original winding
FreeStackWinding(in, stack);
return neww;
}
/*
==============
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
flipclip should be set.
==============
*/
winding_t *ClipToSeperators(winding_t *source, winding_t *pass, winding_t *target, qboolean flipclip, pstack_t *stack) {
int32_t i, j, k, l;
plane_t plane;
vec3_t v1, v2;
float d;
vec_t length;
int32_t 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
//
#if 1
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
#else
fliptest = flipclip;
#endif
//
// flip the normal if the source portal is backwards
//
if (fliptest) {
VectorSubtract(vec3_origin, plane.normal, plane.normal);
plane.dist = -plane.dist;
}
#if 1
//
// 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
#else
k = (j + 1) % pass->numpoints;
d = DotProduct(pass->points[k], plane.normal) - plane.dist;
if (d < -ON_EPSILON)
continue;
k = (j + pass->numpoints - 1) % pass->numpoints;
d = DotProduct(pass->points[k], plane.normal) - plane.dist;
if (d < -ON_EPSILON)
continue;
#endif
//
// 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 = ChopWinding_flow(target, stack, &plane);
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(int32_t leafnum, threaddata_t *thread, pstack_t *prevstack) {
pstack_t stack;
portal_t *p;
plane_t backplane;
leaf_t *leaf;
int32_t i, j;
long *test, *might, *vis, more;
int32_t pnum;
thread->c_chains++;
leaf = &leafs[leafnum];
// CheckStack (leaf, thread);
prevstack->next = &stack;
stack.next = NULL;
stack.leaf = leaf;
stack.portal = NULL;
might = (long *)stack.mightsee;
vis = (long *)thread->base->portalvis;
// check all portals for flowing into other leafs
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
pnum = p - portals;
if (!(prevstack->mightsee[pnum >> 3] & (1 << (pnum & 7)))) {
continue; // can't possibly see it
}
// if the portal can't see anything we haven't allready seen, skip it
if (p->status == stat_done) {
test = (long *)p->portalvis;
} else {
test = (long *)p->portalflood;
}
more = 0;
for (j = 0; j < portallongs; j++) {
might[j] = ((long *)prevstack->mightsee)[j] & test[j];
more |= (might[j] & ~vis[j]);
}
if (!more &&
(thread->base->portalvis[pnum >> 3] & (1 << (pnum & 7)))) { // can't see anything new
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;
// c_portalcheck++;
stack.portal = p;
stack.next = NULL;
stack.freewindings[0] = 1;
stack.freewindings[1] = 1;
stack.freewindings[2] = 1;
#if 1
{
float d;
d = DotProduct(p->origin, thread->pstack_head.portalplane.normal);
d -= thread->pstack_head.portalplane.dist;
if (d < -p->radius) {
continue;
} else if (d > p->radius) {
stack.pass = p->winding;
} else {
stack.pass = ChopWinding_flow(p->winding, &stack, &thread->pstack_head.portalplane);
if (!stack.pass)
continue;
}
}
#else
stack.pass = ChopWinding_flow(p->winding, &stack, &thread->pstack_head.portalplane);
if (!stack.pass)
continue;
#endif
#if 1
{
float d;
d = DotProduct(thread->base->origin, p->plane.normal);
d -= p->plane.dist;
// if (d > p->radius) qb: GDD tools fix
if (d > thread->base->radius) {
continue;
}
// else if (d < -p->radius)
else if (d < -thread->base->radius) {
stack.source = prevstack->source;
} else {
stack.source = ChopWinding_flow(prevstack->source, &stack, &backplane);
if (!stack.source)
continue;
}
}
#else
stack.source = ChopWinding_flow(prevstack->source, &stack, &backplane);
if (!stack.source)
continue;
#endif
if (!prevstack->pass) { // the second leaf can only be blocked if coplanar
// mark the portal as visible
thread->base->portalvis[pnum >> 3] |= (1 << (pnum & 7));
RecursiveLeafFlow(p->leaf, thread, &stack);
continue;
}
stack.pass = ClipToSeperators(stack.source, prevstack->pass, stack.pass, false, &stack);
if (!stack.pass)
continue;
stack.pass = ClipToSeperators(prevstack->pass, stack.source, stack.pass, true, &stack);
if (!stack.pass)
continue;
// mark the portal as visible
thread->base->portalvis[pnum >> 3] |= (1 << (pnum & 7));
// flow through it for real
RecursiveLeafFlow(p->leaf, thread, &stack);
}
}
/*
===============
PortalFlow
generates the portalvis bit vector
===============
*/
void PortalFlow(int32_t portalnum) {
threaddata_t data;
int32_t i;
portal_t *p;
int32_t c_might, c_can;
p = sorted_portals[portalnum];
p->status = stat_working;
c_might = CountBits(p->portalflood, numportals * 2);
memset(&data, 0, sizeof(data));
data.base = p;
data.pstack_head.portal = p;
data.pstack_head.source = p->winding;
data.pstack_head.portalplane = p->plane;
for (i = 0; i < portallongs; i++)
((long *)data.pstack_head.mightsee)[i] = ((long *)p->portalflood)[i];
RecursiveLeafFlow(p->leaf, &data, &data.pstack_head);
p->status = stat_done;
c_can = CountBits(p->portalvis, numportals * 2);
qprintf("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n",
(int32_t)(p - portals), c_might, c_can, data.c_chains);
}
/*
===============================================================================
This is a rough first-order aproximation that is used to trivially reject some
of the final calculations.
Calculates portalfront and portalflood bit vectors
thinking about:
typedef struct passage_s
{
struct passage_s *next;
struct portal_s *to;
stryct sep_s *seperators;
byte *mightsee;
} passage_t;
typedef struct portal_s
{
struct passage_s *passages;
int32_t leaf; // leaf portal faces into
} portal_s;
leaf = portal->leaf
clear
for all portals
calc portal visibility
clear bit vector
for all passages
passage visibility
for a portal to be visible to a passage, it must be on the front of
all seperating planes, and both portals must be behind the mew portal
===============================================================================
*/
int32_t c_flood, c_vis;
char test_leaf[MAX_MAP_LEAFS_QBSP];
/*
==================
SimpleFlood
==================
*/
void SimpleFlood(portal_t *srcportal, int32_t leafnum) {
int32_t i;
leaf_t *leaf;
portal_t *p;
int32_t pnum;
leaf = &leafs[leafnum];
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
pnum = p - portals;
if (!(srcportal->portalfront[pnum >> 3] & (1 << (pnum & 7))))
continue;
if (srcportal->portalflood[pnum >> 3] & (1 << (pnum & 7)))
continue;
srcportal->portalflood[pnum >> 3] |= (1 << (pnum & 7));
SimpleFlood(srcportal, p->leaf);
}
}
/*
==============
BasePortalVis
==============
*/
void BasePortalVis(int32_t portalnum) {
int32_t j, k;
portal_t *tp, *p;
float d;
winding_t *w;
p = portals + portalnum;
p->portalfront = malloc(portalbytes);
memset(p->portalfront, 0, portalbytes);
p->portalflood = malloc(portalbytes);
memset(p->portalflood, 0, portalbytes);
p->portalvis = malloc(portalbytes);
memset(p->portalvis, 0, portalbytes);
for (j = 0, tp = portals; j < numportals * 2; j++, tp++) {
if (j == portalnum)
continue;
w = tp->winding;
for (k = 0; k < w->numpoints; k++) {
d = DotProduct(w->points[k], p->plane.normal) - p->plane.dist;
if (d > ON_EPSILON)
break;
}
if (k == w->numpoints)
continue; // no points on front
w = p->winding;
for (k = 0; k < w->numpoints; k++) {
d = DotProduct(w->points[k], tp->plane.normal) - tp->plane.dist;
if (d < -ON_EPSILON)
break;
}
if (k == w->numpoints)
continue; // no points on front
p->portalfront[j >> 3] |= (1 << (j & 7));
}
SimpleFlood(p, p->leaf);
p->nummightsee = CountBits(p->portalflood, numportals * 2);
// printf ("portal %i: %i mightsee\n", portalnum, p->nummightsee);
c_flood += p->nummightsee;
}
/*
===============================================================================
This is a second order aproximation
Calculates portalvis bit vector
WAAAAAAY too slow.
===============================================================================
*/
/*
==================
RecursiveLeafBitFlow
==================
*/
void RecursiveLeafBitFlow(int32_t leafnum, byte *mightsee, byte *cansee) {
portal_t *p;
leaf_t *leaf;
int32_t i, j;
long more;
int32_t pnum;
byte newmight[MAX_MAP_PORTALS_QBSP / 8];
leaf = &leafs[leafnum];
// check all portals for flowing into other leafs
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
pnum = p - portals;
// if some previous portal can't see it, skip
if (!(mightsee[pnum >> 3] & (1 << (pnum & 7))))
continue;
// if this portal can see some portals we mightsee, recurse
more = 0;
for (j = 0; j < portallongs; j++) {
((long *)newmight)[j] = ((long *)mightsee)[j] & ((long *)p->portalflood)[j];
more |= ((long *)newmight)[j] & ~((long *)cansee)[j];
}
if (!more)
continue; // can't see anything new
cansee[pnum >> 3] |= (1 << (pnum & 7));
RecursiveLeafBitFlow(p->leaf, newmight, cansee);
}
}
/*
==============
BetterPortalVis
==============
*/
void BetterPortalVis(int32_t portalnum) {
portal_t *p;
p = portals + portalnum;
RecursiveLeafBitFlow(p->leaf, p->portalflood, p->portalvis);
// build leaf vis information
p->nummightsee = CountBits(p->portalvis, numportals * 2);
c_vis += p->nummightsee;
}
#include "stdlib.h"
#include "vis.h"
#include "threads.h"
extern qboolean fastvis;
extern qboolean nosort;
extern char inbase[32];
extern char outbase[32];
void VIS_ProcessArgument(const char * arg);
int32_t main(int32_t argc, char **argv) {
int32_t i;
char tgamedir[1024] = "", tbasedir[1024] = "", tmoddir[1024] = "";
printf("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4vis >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("visibility compiler build " __DATE__ "\n");
verbose = false;
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-threads")) {
numthreads = atoi(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-help")) {
printf("usage: 4vis [options] [mapname]\n\n"
" -fast: uses 'might see' for a quick loose bound\n"
" -threads #: number of CPU threads to use\n"
" -tmpin: read map from 'tmp' folder\n"
" -tmpout: write map to 'tmp' folder\n"
" -moddir [path]: Set a mod directory. Default is parent dir of map file.\n"
" -basedir [path]: Set the directory for assets not in moddir. Default is moddir.\n"
" -gamedir [path]: Set game directory, the folder with game executable.\n"
" -v: extra verbose console output\n\n");
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4vis HELP >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
exit(1);
} else if (!strcmp(argv[i], "-fast")) {
printf("fastvis = true\n");
fastvis = true;
} else if (!strcmp(argv[i], "-v")) {
printf("verbose = true\n");
verbose = true;
} else if (!strcmp(argv[i], "-nosort")) {
printf("nosort = true\n");
nosort = true;
}
// qb: set gamedir and basedir
else if (!strcmp(argv[i], "-gamedir")) {
strcpy(tgamedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-basedir")) {
strcpy(tbasedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-moddir")) {
strcpy(tmoddir, argv[i + 1]);
i++;
}
else if (!strcmp(argv[i], "-tmpin"))
strcpy(inbase, "/tmp");
else if (!strcmp(argv[i], "-tmpout"))
strcpy(outbase, "/tmp");
else if (argv[i][0] == '-')
Error("Unknown option \"%s\"", argv[i]);
else
break;
}
if (i != argc - 1) {
printf("usage: 4vis [options] mapfile\n"
" -fast -help -threads #\n"
" -basedir [path] -gamedir [path] \n"
" -tmpin -tmpout -v (verbose)\n\n");
exit(1);
}
ThreadSetDefault();
SetQdirFromPath(argv[i]);
if (strcmp(tmoddir, "")) {
strcpy(moddir, tmoddir);
Q_pathslash(moddir);
strcpy(basedir, moddir);
}
if (strcmp(tbasedir, "")) {
strcpy(basedir, tbasedir);
Q_pathslash(basedir);
if (!strcmp(tmoddir, ""))
strcpy(moddir, basedir);
}
if (strcmp(tgamedir, "")) {
strcpy(gamedir, tgamedir);
Q_pathslash(gamedir);
}
// qb: display dirs
printf("moddir = %s\n", moddir);
printf("basedir = %s\n", basedir);
printf("gamedir = %s\n", gamedir);
VIS_ProcessArgument(argv[i]);
return 0;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "vis.h"
#include "threads.h"
#include "stdlib.h"
extern qboolean use_qbsp;
int32_t numportals;
int32_t portalclusters;
portal_t *portals;
leaf_t *leafs;
int32_t c_portaltest, c_portalpass, c_portalcheck;
byte *uncompressedvis;
byte *vismap, *vismap_p, *vismap_end; // past visfile
int32_t originalvismapsize;
int32_t leafbytes; // (portalclusters+63)>>3
int32_t leaflongs;
int32_t portalbytes, portallongs;
qboolean fastvis;
qboolean nosort;
int32_t totalvis;
portal_t *sorted_portals[MAX_MAP_PORTALS_QBSP * 2];
extern char inbase[32];
extern char outbase[32];
//=============================================================================
void PlaneFromWinding(winding_t *w, plane_t *plane) {
vec3_t v1, v2;
// calc plane
VectorSubtract(w->points[2], w->points[1], v1);
VectorSubtract(w->points[0], w->points[1], v2);
CrossProduct(v2, v1, plane->normal);
VectorNormalize(plane->normal, plane->normal);
plane->dist = DotProduct(w->points[0], plane->normal);
}
/*
==================
NewWinding
==================
*/
winding_t *NewWinding(int32_t points) {
winding_t *w;
int32_t size;
if (points > MAX_POINTS_ON_WINDING)
Error("NewWinding: %i points", points);
size = (intptr_t)((winding_t *)0)->points[points];
w = malloc(size);
memset(w, 0, size);
return w;
}
void prl(leaf_t *l) {
int32_t i;
portal_t *p;
plane_t pl;
for (i = 0; i < l->numportals; i++) {
p = l->portals[i];
pl = p->plane;
printf("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n", (int32_t)(p - portals), p->leaf, pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]);
}
}
//=============================================================================
/*
=============
SortPortals
Sorts the portals from the least complex, so the later ones can reuse
the earlier information.
=============
*/
int32_t PComp(const void *a, const void *b) {
if ((*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee)
return 0;
if ((*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee)
return -1;
return 1;
}
void SortPortals(void) {
int32_t i;
for (i = 0; i < numportals * 2; i++)
sorted_portals[i] = &portals[i];
if (nosort)
return;
qsort(sorted_portals, numportals * 2, sizeof(sorted_portals[0]), PComp);
}
/*
==============
LeafVectorFromPortalVector
==============
*/
int32_t LeafVectorFromPortalVector(byte *portalbits, byte *leafbits) {
int32_t i;
portal_t *p;
int32_t c_leafs;
memset(leafbits, 0, leafbytes);
for (i = 0; i < numportals * 2; i++) {
if (portalbits[i >> 3] & (1 << (i & 7))) {
p = portals + i;
leafbits[p->leaf >> 3] |= (1 << (p->leaf & 7));
}
}
c_leafs = CountBits(leafbits, portalclusters);
return c_leafs;
}
/*
===============
ClusterMerge
Merges the portal visibility for a leaf
===============
*/
void ClusterMerge(int32_t leafnum) {
leaf_t *leaf;
byte portalvector[MAX_PORTALS_QBSP / 8];
byte uncompressed[MAX_MAP_LEAFS_QBSP / 8];
byte compressed[MAX_MAP_LEAFS_QBSP / 8];
int32_t i, j;
int32_t numvis;
byte *dest;
portal_t *p;
int32_t pnum;
// OR together all the portalvis bits
memset(portalvector, 0, portalbytes);
leaf = &leafs[leafnum];
for (i = 0; i < leaf->numportals; i++) {
p = leaf->portals[i];
if (p->status != stat_done)
Error("portal not done");
for (j = 0; j < portallongs; j++)
((long *)portalvector)[j] |= ((long *)p->portalvis)[j];
pnum = p - portals;
portalvector[pnum >> 3] |= 1 << (pnum & 7);
}
// convert portal bits to leaf bits
numvis = LeafVectorFromPortalVector(portalvector, uncompressed);
if (uncompressed[leafnum >> 3] & (1 << (leafnum & 7)))
printf("WARNING: Leaf portals saw into leaf\n");
uncompressed[leafnum >> 3] |= (1 << (leafnum & 7));
numvis++; // count the leaf itself
// save uncompressed for PHS calculation
memcpy(uncompressedvis + leafnum * leafbytes, uncompressed, leafbytes);
//
// compress the bit string
//
qprintf("cluster %4i : %4i visible\n", leafnum, numvis);
totalvis += numvis;
i = CompressVis(uncompressed, compressed);
dest = vismap_p;
vismap_p += i;
if (vismap_p > vismap_end)
Error("Vismap expansion overflow. Exceeds extended limit");
dvis->bitofs[leafnum][DVIS_PVS] = dest - vismap;
memcpy(dest, compressed, i);
}
/*
==================
CalcPortalVis
==================
*/
void CalcPortalVis(void) {
int32_t i;
// fastvis just uses mightsee for a very loose bound
if (fastvis) {
for (i = 0; i < numportals * 2; i++) {
portals[i].portalvis = portals[i].portalflood;
portals[i].status = stat_done;
}
return;
}
RunThreadsOnIndividual(numportals * 2, true, PortalFlow);
}
/*
==================
CalcVis
==================
*/
void CalcVis(void) {
int32_t i;
RunThreadsOnIndividual(numportals * 2, true, BasePortalVis);
// RunThreadsOnIndividual (numportals*2, true, BetterPortalVis);
SortPortals();
CalcPortalVis();
//
// assemble the leaf vis lists by oring and compressing the portal lists
//
for (i = 0; i < portalclusters; i++)
ClusterMerge(i);
printf("Average clusters visible: %i\n", totalvis / portalclusters);
}
void SetPortalSphere(portal_t *p) {
int32_t i;
vec3_t total, dist;
winding_t *w;
float r, bestr;
w = p->winding;
VectorCopy(vec3_origin, total);
for (i = 0; i < w->numpoints; i++) {
VectorAdd(total, w->points[i], total);
}
for (i = 0; i < 3; i++)
total[i] /= w->numpoints;
bestr = 0;
for (i = 0; i < w->numpoints; i++) {
VectorSubtract(w->points[i], total, dist);
r = VectorLength(dist);
if (r > bestr)
bestr = r;
}
VectorCopy(total, p->origin);
p->radius = bestr;
}
/*
============
LoadPortals
============
*/
void LoadPortals(char *name) {
int32_t i, j;
portal_t *p;
leaf_t *l;
char magic[80];
FILE *f;
int32_t numpoints;
winding_t *w;
int32_t leafnums[2];
plane_t plane;
if (!strcmp(name, "-"))
f = stdin;
else {
f = fopen(name, "r");
if (!f)
Error("LoadPortals: couldn't read %s\n", name);
}
if (fscanf(f, "%79s\n%i\n%i\n", magic, &portalclusters, &numportals) != 3)
Error("LoadPortals: failed to read header");
if (strcmp(magic, PORTALFILE))
Error("LoadPortals: not a portal file");
printf("%4i portalclusters\n", portalclusters);
printf("%4i numportals\n", numportals);
// these counts should take advantage of 64 bit systems automatically
leafbytes = ((portalclusters + 63) & ~63) >> 3;
leaflongs = leafbytes / sizeof(long);
portalbytes = ((numportals * 2 + 63) & ~63) >> 3;
portallongs = portalbytes / 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(portalclusters * sizeof(leaf_t));
memset(leafs, 0, portalclusters * sizeof(leaf_t));
originalvismapsize = portalclusters * leafbytes;
uncompressedvis = malloc(originalvismapsize);
vismap = vismap_p = dvisdata;
dvis->numclusters = portalclusters;
vismap_p = (byte *)&dvis->bitofs[portalclusters];
vismap_end = vismap + MAX_MAP_VISIBILITY_QBSP;
for (i = 0, p = portals; i < numportals; i++) {
if (fscanf(f, "%i %i %i ", &numpoints, &leafnums[0], &leafnums[1]) != 3)
Error("LoadPortals: reading portal %i", i);
if (numpoints > MAX_POINTS_ON_WINDING)
Error("LoadPortals: portal %i has too many points", i);
if ((unsigned)leafnums[0] > portalclusters || (unsigned)leafnums[1] > portalclusters)
Error("LoadPortals: reading portal %i", i);
w = p->winding = NewWinding(numpoints);
w->original = true;
w->numpoints = numpoints;
for (j = 0; j < numpoints; j++) {
double v[3];
int32_t k;
// scanf into double, then assign to vec_t
// so we don't care what size vec_t is
if (fscanf(f, "(%lf %lf %lf ) ", &v[0], &v[1], &v[2]) != 3)
Error("LoadPortals: reading portal %i", i);
for (k = 0; k < 3; k++)
w->points[j][k] = v[k];
}
fscanf(f, "\n");
// calc plane
PlaneFromWinding(w, &plane);
// create forward portal
l = &leafs[leafnums[0]];
if (l->numportals == MAX_PORTALS_ON_LEAF)
Error("Leaf with too many portals");
l->portals[l->numportals] = p;
l->numportals++;
p->winding = w;
VectorSubtract(vec3_origin, plane.normal, p->plane.normal);
p->plane.dist = -plane.dist;
p->leaf = leafnums[1];
SetPortalSphere(p);
p++;
// create backwards portal
l = &leafs[leafnums[1]];
if (l->numportals == MAX_PORTALS_ON_LEAF)
Error("Leaf with too many portals");
l->portals[l->numportals] = p;
l->numportals++;
p->winding = NewWinding(w->numpoints);
p->winding->numpoints = w->numpoints;
for (j = 0; j < w->numpoints; j++) {
VectorCopy(w->points[w->numpoints - 1 - j], p->winding->points[j]);
}
p->plane = plane;
p->leaf = leafnums[0];
SetPortalSphere(p);
p++;
}
fclose(f);
}
/*
================
CalcPHS
Calculate the PHS (Potentially Hearable Set)
by ORing together all the PVS visible from a leaf
================
*/
void CalcPHS(void) {
int32_t i, j, k, l, index;
int32_t bitbyte;
long *dest, *src;
byte *scan;
int32_t count;
byte uncompressed[MAX_MAP_LEAFS_QBSP / 8];
byte compressed[MAX_MAP_LEAFS_QBSP / 8];
printf("Building PHS...\n");
count = 0;
for (i = 0; i < portalclusters; i++) {
scan = uncompressedvis + i * leafbytes;
memcpy(uncompressed, scan, leafbytes);
for (j = 0; j < leafbytes; j++) {
bitbyte = scan[j];
if (!bitbyte)
continue;
for (k = 0; k < 8; k++) {
if (!(bitbyte & (1 << k)))
continue;
// OR this pvs row into the phs
index = ((j << 3) + k);
if (index >= portalclusters)
Error("Bad bit in PVS"); // pad bits should be 0
src = (long *)(uncompressedvis + index * leafbytes);
dest = (long *)uncompressed;
for (l = 0; l < leaflongs; l++)
((long *)uncompressed)[l] |= src[l];
}
}
for (j = 0; j < portalclusters; j++)
if (uncompressed[j >> 3] & (1 << (j & 7)))
count++;
//
// compress the bit string
//
j = CompressVis(uncompressed, compressed);
dest = (long *)vismap_p;
vismap_p += j;
if (vismap_p > vismap_end)
Error("Vismap expansion overflow. Exceeds extended limit");
dvis->bitofs[i][DVIS_PHS] = (byte *)dest - vismap;
memcpy(dest, compressed, j);
}
printf("Average clusters hearable: %i\n", count / portalclusters);
}
/*
===========
main
===========
*/
void VIS_ProcessArgument(const char * arg) {
char portalfile[1024];
char source[1024];
char name[1060];
strcpy(source, ExpandArg(arg));
StripExtension(source);
DefaultExtension(source, ".bsp");
sprintf(name, "%s%s", inbase, source);
printf("reading %s\n", name);
LoadBSPFile(name);
if (numnodes == 0 || numfaces == 0)
Error("Empty map");
sprintf(portalfile, "%s%s", inbase, ExpandArg(arg));
StripExtension(portalfile);
strcat(portalfile, ".prt");
printf("reading %s\n", portalfile);
LoadPortals(portalfile);
CalcVis();
CalcPHS();
visdatasize = vismap_p - dvisdata;
printf("visdatasize: %i compressed from %i\n", visdatasize, originalvismapsize * 2);
if (!use_qbsp && vismap_p > (vismap + DEFAULT_MAP_VISIBILITY))
printf("\nWARNING: visdatasize exceeds default limit of %i\n\n", DEFAULT_MAP_VISIBILITY);
sprintf(name, "%s%s", outbase, source);
WriteBSPFile(name);
PrintBSPFileSizes();
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END 4vis >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qrad.h"
#include <assert.h>
typedef struct tnode_s {
int32_t type;
vec3_t normal;
float dist;
int32_t children[2];
int32_t children_leaf[2]; // valid if the corresponding child is a leaf
int32_t pad;
} tnode_t;
tnode_t *tnodes, *tnode_p;
/*
==============
MakeTnode
Converts the disk node structure into the efficient tracing structure
==============
*/
static int32_t tnode_mask;
void MakeTnode(int32_t nodenum) {
tnode_t *t;
dplane_t *plane;
int32_t i;
t = tnode_p++;
if (use_qbsp) {
dnode_tx *node;
node = dnodesX + 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] = (dleafsX[-node->children[i] - 1].contents & tnode_mask) | (1 << 31);
t->children_leaf[i] = -node->children[i] - 1;
} else {
t->children[i] = tnode_p - tnodes;
MakeTnode(node->children[i]);
}
}
} else {
dnode_t *node;
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 & tnode_mask) | (1 << 31);
t->children_leaf[i] = -node->children[i] - 1;
} 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) {
// 32 byte align the structs
tnodes = malloc((numnodes + 1) * sizeof(tnode_t));
tnodes = (tnode_t *)(((intptr_t)tnodes + 31) & ~31);
tnode_p = tnodes;
tnode_mask = CONTENTS_SOLID | CONTENTS_WINDOW;
// TODO: or-in CONTENTS_WINDOW in response to a command-line argument
MakeTnode(0);
}
//==========================================================
/*
=============
BuildPolygons
Applies the same basic algorithm as r_surf.c:BSP_BuildPolygonFromSurface to
every surface in the BSP, precomputing the xyz and st coordinates of each
vertex of each polygon. Skip non-translucent surfaces for now.
=============
*/
int32_t PointInNodenum(vec3_t point) {
int32_t nodenum, oldnodenum;
vec_t dist;
dplane_t *plane;
nodenum = 0;
if (use_qbsp) {
dnode_tx *node;
while (nodenum >= 0) {
node = &dnodesX[nodenum];
plane = &dplanes[node->planenum];
dist = DotProduct(point, plane->normal) - plane->dist;
oldnodenum = nodenum;
if (dist > 0)
nodenum = node->children[0];
else
nodenum = node->children[1];
}
} else {
dnode_t *node;
while (nodenum >= 0) {
node = &dnodes[nodenum];
plane = &dplanes[node->planenum];
dist = DotProduct(point, plane->normal) - plane->dist;
oldnodenum = nodenum;
if (dist > 0)
nodenum = node->children[0];
else
nodenum = node->children[1];
}
}
return oldnodenum;
}
int32_t TestLine_r(int32_t node, vec3_t set_start, vec3_t stop) {
tnode_t *tnode;
float front, back;
vec3_t mid, _start;
vec_t *start;
float frac;
int32_t side;
int32_t r;
start = set_start;
re_test:
r = 0;
if (node & (1 << 31)) {
if ((r = node & ~(1 << 31)) != CONTENTS_WINDOW) {
return r;
}
return 0;
}
tnode = &tnodes[node];
switch (tnode->type) {
case PLANE_X:
front = start[0] - tnode->dist;
back = stop[0] - tnode->dist;
break;
case PLANE_Y:
front = start[1] - tnode->dist;
back = stop[1] - tnode->dist;
break;
case PLANE_Z:
front = start[2] - tnode->dist;
back = stop[2] - tnode->dist;
break;
default:
front = (start[0] * tnode->normal[0] + start[1] * tnode->normal[1] + start[2] * tnode->normal[2]) - tnode->dist;
back = (stop[0] * tnode->normal[0] + stop[1] * tnode->normal[1] + stop[2] * tnode->normal[2]) - tnode->dist;
break;
}
if (front >= -ON_EPSILON && back >= -ON_EPSILON) {
node = tnode->children[0];
goto re_test;
}
if (front < ON_EPSILON && back < ON_EPSILON) {
node = tnode->children[1];
goto re_test;
}
side = front < 0;
frac = front / (front - back);
mid[0] = start[0] + (stop[0] - start[0]) * frac;
mid[1] = start[1] + (stop[1] - start[1]) * frac;
mid[2] = start[2] + (stop[2] - start[2]) * frac;
if ((r = TestLine_r(tnode->children[side], start, mid)))
return r;
node = tnode->children[!side];
start = _start;
start[0] = mid[0];
start[1] = mid[1];
start[2] = mid[2];
goto re_test;
}
int32_t TestLine(vec3_t start, vec3_t stop) {
vec3_t occluded;
occluded[0] = occluded[1] = occluded[2] = 1.0;
return TestLine_r(0, start, stop);
}
int32_t TestLine_color(int32_t node, vec3_t start, vec3_t stop, vec3_t occluded) {
occluded[0] = occluded[1] = occluded[2] = 1.0;
return TestLine_r(node, start, stop);
}
/*
==============================================================================
LINE TRACING
The major lighting operation is a point to point visibility test, performed
by recursive subdivision of the line by the BSP tree.
==============================================================================
*/
typedef struct
{
vec3_t backpt;
int32_t side;
int32_t node;
} tracestack_t;
/*
==============
TestLine
==============
*/
qboolean _TestLine(vec3_t start, vec3_t stop) {
int32_t node;
float front, back;
tracestack_t *tstack_p;
int32_t 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) {
if (node == CONTENTS_SOLID) {
#if 0
float d1, d2, d3;
d1 = backx - frontx;
d2 = backy - fronty;
d3 = backz - frontz;
if (d1*d1 + d2*d2 + d3*d3 > 1)
#endif
return false; // DONE!
}
while (node < 0) {
// 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];
}
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];
}
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qrad.h"
vec3_t texture_reflectivity[MAX_MAP_TEXINFO_QBSP];
int32_t cluster_neg_one = 0;
int32_t texture_sizes[MAX_MAP_TEXINFO_QBSP][2];
/*
===================================================================
TEXTURE LIGHT VALUES
===================================================================
*/
/*
======================
CalcTextureReflectivity
======================
*/
void CalcTextureReflectivity(void) {
int32_t i, j, k, count;
int32_t texels, texel;
qboolean wal_tex;
float color[3], cur_color[3], tex_a, a;
char path[1200];
float *r, *g, *b;
float c;
byte *pbuffer = NULL; // mxd. "potentially uninitialized local pointer variable" in VS2017 if uninitialized
byte *palette_frompak = NULL;
byte *ptexel;
byte *palette;
miptex_t *mt = NULL; // mxd. "potentially uninitialized local pointer variable" in VS2017 if uninitialized
float *fbuffer, *ftexel;
int32_t width, height;
// for TGA RGBA texture images
wal_tex = false;
// get the game palette
// qb: looks in moddir then basedir
sprintf(path, "%spics/colormap.pcx", moddir);
if(FileExists(path)) {
Load256Image(path, NULL, &palette, NULL, NULL);
} else {
sprintf(path, "%spics/colormap.pcx", basedir);
if(FileExists(path)) {
Load256Image(path, NULL, &palette, NULL, NULL);
} else if((i = TryLoadFileFromPak("pics/colormap.pcx", (void **)&palette_frompak, moddir)) != -1) {
// unicat: load from pack files, palette is loaded from the last 768 bytes
palette = palette_frompak - (i - 768);
} else {
Error("unable to load pics/colormap.pcx");
}
}
// always set index 0 even if no textures
texture_reflectivity[0][0] = 0.5;
texture_reflectivity[0][1] = 0.5;
texture_reflectivity[0][2] = 0.5;
for(i = 0; i < numtexinfo; i++) {
// default
texture_reflectivity[i][0] = 0.5f;
texture_reflectivity[i][1] = 0.5f;
texture_reflectivity[i][2] = 0.5f;
// see if an earlier texinfo already got the value
for(j = 0; j < i; j++) {
if(!strcmp(texinfo[i].texture, texinfo[j].texture)) {
VectorCopy(texture_reflectivity[j], texture_reflectivity[i]);
texture_sizes[i][0] = texture_sizes[j][0];
texture_sizes[i][1] = texture_sizes[j][1];
break;
}
}
if(j != i)
continue;
// buffer is RGBA (A set to 255 for 24 bit format)
// qb: looks in moddir then basedir
sprintf(path, "%stextures/%s.tga", moddir, texinfo[i].texture);
if(FileExists(path)) // LoadTGA expects file to exist
{
LoadTGA(path, &pbuffer, &width, &height); // load rgba data
qprintf("load %s\n", path);
} else {
// look for wal file in moddir
sprintf(path, "%stextures/%s.wal", moddir, texinfo[i].texture);
qprintf("attempting %s\n", path);
// load the miptex to get the flags and values
if(FileExists(path)) // qb: linux segfault if not exist
{
if(TryLoadFile(path, (void **)&mt, false) != -1)
wal_tex = true;
} else {
// look for TGA in basedir sprintf(path, "%stextures/%s.tga", basedir, texinfo[i].texture);
if(FileExists(path)) {
LoadTGA(path, &pbuffer, &width, &height); // load rgba data
qprintf("load %s\n", path);
} else {
// look for wal file in base dir
sprintf(path, "%stextures/%s.wal", basedir, texinfo[i].texture);
qprintf("load %s\n", path);
// load the miptex to get the flags and values
if(FileExists(path)) // qb: linux segfault if not exist
{
if(TryLoadFile(path, (void **)&mt, false) != -1)
wal_tex = true;
} else {
qprintf("NOT FOUND %s\n", path);
continue;
}
}
}
}
//
// Calculate the "average color" for the texture
//
if(wal_tex) {
texels = LittleLong(mt->width) * LittleLong(mt->height);
color[0] = color[1] = color[2] = 0;
for(j = 0; j < texels; j++) {
texel = ((byte *)mt)[LittleLong(mt->offsets[0]) + j];
for(k = 0; k < 3; k++)
color[k] += palette[texel * 3 + k];
}
} else {
texels = width * height;
if(texels <= 0) {
qprintf("tex %i (%s) no rgba data (file broken?)\n", i, path);
continue; // empty texture, possible bad file
}
color[0] = color[1] = color[2] = 0.0f;
ptexel = pbuffer;
// fbuffer = malloc(texels * 4 * sizeof(float));
// ftexel = fbuffer;
for(count = texels; count--;) {
cur_color[0] = (float)(*ptexel++); // r
cur_color[1] = (float)(*ptexel++); // g
cur_color[2] = (float)(*ptexel++); // b
tex_a = (float)(*ptexel++);
if(texinfo[i].flags & (SURF_WARP | SURF_NODRAW)) {
a = 0.0;
} else if((texinfo[i].flags & SURF_TRANS33) && (texinfo[i].flags & SURF_TRANS66)) {
a = tex_a / 511.0;
} else if(texinfo[i].flags & SURF_TRANS33) {
a = tex_a / 765.0;
} else if(texinfo[i].flags & SURF_TRANS66) {
a = tex_a / 382.5;
} else {
a = 1.0;
}
for(j = 0; j < 3; j++) {
color[j] += cur_color[j] * a;
}
}
free(pbuffer);
}
for(j = 0; j < 3; j++) {
// average RGB for the texture to 0.0..1.0 range
c = color[j] / (float)texels / 255.0f;
texture_reflectivity[i][j] = c;
}
// reflectivity saturation
#define Pr .299
#define Pg .587
#define Pb .114
// public-domain function by Darel Rex Finley
//
// The passed-in RGB values can be on any desired scale, such as 0 to
// to 1, or 0 to 255. (But use the same scale for all three!)
//
// The "saturation" parameter works like this:
// 0.0 creates a black-and-white image.
// 0.5 reduces the color saturation by half.
// 1.0 causes no change.
// 2.0 doubles the color saturation.
// Note: A "change" value greater than 1.0 may project your RGB values
// beyond their normal range, in which case you probably should truncate
// them to the desired range before trying to use them in an image.
r = &texture_reflectivity[i][0];
g = &texture_reflectivity[i][1];
b = &texture_reflectivity[i][2];
float P = sqrt((*r) * (*r) * Pr + (*g) * (*g) * Pg + (*b) * (*b) * Pb);
*r = BOUND(0, P + (*r - P) * saturation, 255);
*g = BOUND(0, P + (*g - P) * saturation, 255);
*b = BOUND(0, P + (*b - P) * saturation, 255);
qprintf("tex %i (%s) avg rgb [ %f, %f, %f ]\n", i, path, texture_reflectivity[i][0], texture_reflectivity[i][1],
texture_reflectivity[i][2]);
}
if(palette_frompak) {
free(palette_frompak);
} else {
free(palette);
}
}
/*
=======================================================================
MAKE FACES
=======================================================================
*/
/*
=============
WindingFromFace
=============
*/
winding_t *WindingFromFaceX(dface_tx *f) {
int32_t i;
int32_t se;
dvertex_t *dv;
int32_t v;
winding_t *w;
w = AllocWinding(f->numedges);
w->numpoints = f->numedges;
for(i = 0; i < f->numedges; i++) {
se = dsurfedges[f->firstedge + i];
if(se < 0)
v = dedgesX[-se].v[1];
else
v = dedgesX[se].v[0];
dv = &dvertexes[v];
VectorCopy(dv->point, w->p[i]);
}
RemoveColinearPoints(w);
return w;
}
winding_t *WindingFromFace(dface_t *f) {
int32_t i;
int32_t se;
dvertex_t *dv;
int32_t v;
winding_t *w;
w = AllocWinding(f->numedges);
w->numpoints = f->numedges;
for(i = 0; i < f->numedges; i++) {
se = dsurfedges[f->firstedge + i];
if(se < 0)
v = dedges[-se].v[1];
else
v = dedges[se].v[0];
dv = &dvertexes[v];
VectorCopy(dv->point, w->p[i]);
}
RemoveColinearPoints(w);
return w;
}
/*
=============
BaseLightForFace
=============
*/
struct SH1 BaseLightForFaceX(dface_tx *f) {
texinfo_t *tx;
//
// check for light emited by texture
//
tx = &texinfo[f->texinfo];
if(!(tx->flags & SURF_LIGHT) || tx->value == 0) {
if(tx->flags & SURF_LIGHT) {
printf("Surface light has 0 intensity.\n");
}
return SH1_Clear();
}
// non-standard use, get a more diffuse baselight from short direction vector
vec3_t normal, color;
VectorScale((f->side ? backplanes[f->planenum].normal : dplanes[f->planenum].normal), -0.25, normal);
VectorScale(texture_reflectivity[f->texinfo], tx->value, color);
return SH1_FromDirectionalLight(normal, color);
}
struct SH1 BaseLightForFaceI(dface_t *f) {
texinfo_t *tx;
//
// check for light emited by texture
//
tx = &texinfo[f->texinfo];
if(!(tx->flags & SURF_LIGHT) || tx->value == 0) {
if(tx->flags & SURF_LIGHT) {
printf("Surface light has 0 intensity.\n");
}
return SH1_Clear();
}
// non-standard use, get a more diffuse baselight from short direction vector
vec3_t normal, color;
VectorScale(f->side ? backplanes[f->planenum].normal : dplanes[f->planenum].normal, 0.25, normal);
VectorScale(texture_reflectivity[f->texinfo], tx->value, color);
return SH1_FromDirectionalLight(normal, color);
}
qboolean IsSkyX(dface_tx *f) {
texinfo_t *tx;
tx = &texinfo[f->texinfo];
if(tx->flags & SURF_SKY)
return true;
return false;
}
qboolean IsSkyI(dface_t *f) {
texinfo_t *tx;
tx = &texinfo[f->texinfo];
if(tx->flags & SURF_SKY)
return true;
return false;
}
/*
=============
MakePatchForFace
=============
*/
float totalarea;
void MakePatchForFace(int32_t fn, winding_t *w) {
float area;
patch_t *patch;
dplane_t *pl;
int32_t i;
vec3_t color = {1.0f, 1.0f, 1.0f};
area = WindingArea(w);
totalarea += area;
patch = &patches[num_patches];
if(use_qbsp) {
if(num_patches == MAX_PATCHES_QBSP)
Error("Exceeded MAX_PATCHES_QBSP %i", MAX_PATCHES_QBSP);
} else if(num_patches == MAX_PATCHES)
Error("Exceeded MAX_PATCHES %i", MAX_PATCHES);
patch->next = face_patches[fn];
face_patches[fn] = patch;
patch->winding = w;
if(use_qbsp) {
dface_tx *f;
dleaf_tx *leaf;
f = &dfacesX[fn];
if(f->side)
patch->plane = &backplanes[f->planenum];
else
patch->plane = &dplanes[f->planenum];
if(face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2]) {
// origin offset faces must create new planes
if(use_qbsp) {
if(numplanes + fakeplanes >= MAX_MAP_PLANES_QBSP)
Error("numplanes + fakeplanes >= MAX_MAP_PLANES_QBSP");
} else if(numplanes + fakeplanes >= MAX_MAP_PLANES)
Error("numplanes + fakeplanes >= MAX_MAP_PLANES");
pl = &dplanes[numplanes + fakeplanes];
fakeplanes++;
*pl = *(patch->plane);
pl->dist += DotProduct(face_offset[fn], pl->normal);
patch->plane = pl;
}
WindingCenter(w, patch->origin);
VectorAdd(patch->origin, patch->plane->normal, patch->origin);
leaf = RadPointInLeafX(patch->origin);
patch->cluster = leaf->cluster;
if(patch->cluster == -1) {
// qprintf ("patch->cluster == -1\n");
++cluster_neg_one;
}
patch->faceNumber = fn; // qb: for patch sorting
patch->area = area;
if(patch->area <= 1)
patch->area = 1;
patch->sky = IsSkyX(f);
VectorCopy(texture_reflectivity[f->texinfo], patch->reflectivity);
// non-bmodel patches can emit light
if(fn < dmodels[0].numfaces) {
ColorNormalize(patch->reflectivity, color);
patch->baselight = SH1_ColorScale(BaseLightForFaceX(f), color);
// VectorCopy(patch->baselight, patch->totallight);
patch->totallight = patch->baselight;
}
} else {
dface_t *f;
dleaf_t *leaf;
f = &dfaces[fn];
if(f->side)
patch->plane = &backplanes[f->planenum];
else
patch->plane = &dplanes[f->planenum];
if(face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2]) {
// origin offset faces must create new planes
if(use_qbsp) {
if(numplanes + fakeplanes >= MAX_MAP_PLANES_QBSP)
Error("numplanes + fakeplanes >= MAX_MAP_PLANES_QBSP");
} else if(numplanes + fakeplanes >= MAX_MAP_PLANES)
Error("numplanes + fakeplanes >= MAX_MAP_PLANES");
pl = &dplanes[numplanes + fakeplanes];
fakeplanes++;
*pl = *(patch->plane);
pl->dist += DotProduct(face_offset[fn], pl->normal);
patch->plane = pl;
}
WindingCenter(w, patch->origin);
VectorAdd(patch->origin, patch->plane->normal, patch->origin);
leaf = RadPointInLeaf(patch->origin);
patch->cluster = leaf->cluster;
if(patch->cluster == -1) {
// qprintf ("patch->cluster == -1\n");
++cluster_neg_one;
}
patch->faceNumber = fn; // qb: for patch sorting
patch->area = area;
if(patch->area <= 1)
patch->area = 1;
patch->sky = IsSkyI(f);
VectorCopy(texture_reflectivity[f->texinfo], patch->reflectivity);
// non-bmodel patches can emit light
if(fn < dmodels[0].numfaces) {
ColorNormalize(patch->reflectivity, color);
patch->baselight = SH1_ColorScale(BaseLightForFaceI(f), color);
// VectorCopy(patch->baselight, patch->totallight);
patch->totallight = patch->baselight;
}
}
num_patches++;
}
entity_t *EntityForModel(int32_t modnum) {
int32_t i;
char *s;
char name[16];
sprintf(name, "*%i", modnum);
// search the entities for one using modnum
for(i = 0; i < num_entities; i++) {
s = ValueForKey(&entities[i], "model");
if(!strcmp(s, name))
return &entities[i];
}
return &entities[0];
}
/*
=============
MakePatches
=============
*/
void MakePatches(void) {
int32_t i, j, k;
int32_t fn;
winding_t *w;
dmodel_t *mod;
vec3_t origin;
entity_t *ent;
qprintf("%i faces\n", numfaces);
for(i = 0; i < nummodels; i++) {
mod = &dmodels[i];
ent = EntityForModel(i);
// bmodels with origin brushes need to be offset into their
// in-use position
GetVectorForKey(ent, "origin", origin);
// VectorCopy (vec3_origin, origin);
for(j = 0; j < mod->numfaces; j++) {
fn = mod->firstface + j;
face_entity[fn] = ent;
VectorCopy(origin, face_offset[fn]);
if(use_qbsp) {
dface_tx *f;
f = &dfacesX[fn];
w = WindingFromFaceX(f);
} else {
dface_t *f;
f = &dfaces[fn];
w = WindingFromFace(f);
}
for(k = 0; k < w->numpoints; k++) {
VectorAdd(w->p[k], origin, w->p[k]);
}
MakePatchForFace(fn, w);
}
}
qprintf("%i sqaure feet\n", (int32_t)(totalarea / 64));
}
/*
=======================================================================
SUBDIVIDE
=======================================================================
*/
void FinishSplit(patch_t *patch, patch_t *newp) {
newp->baselight = patch->baselight;
newp->totallight = patch->totallight;
VectorCopy(patch->reflectivity, newp->reflectivity);
newp->plane = patch->plane;
newp->sky = patch->sky;
patch->area = WindingArea(patch->winding);
newp->area = WindingArea(newp->winding);
if(patch->area <= 1)
patch->area = 1;
if(newp->area <= 1)
newp->area = 1;
if(use_qbsp) {
dleaf_tx *leaf;
WindingCenter(patch->winding, patch->origin);
VectorAdd(patch->origin, patch->plane->normal, patch->origin);
leaf = RadPointInLeafX(patch->origin);
patch->cluster = leaf->cluster;
if(patch->cluster == -1)
qprintf("patch->cluster == -1\n");
WindingCenter(newp->winding, newp->origin);
VectorAdd(newp->origin, newp->plane->normal, newp->origin);
leaf = RadPointInLeafX(newp->origin);
newp->cluster = leaf->cluster;
if(newp->cluster == -1)
qprintf("patch->cluster == -1\n");
} else {
dleaf_t *leaf;
WindingCenter(patch->winding, patch->origin);
VectorAdd(patch->origin, patch->plane->normal, patch->origin);
leaf = RadPointInLeaf(patch->origin);
patch->cluster = leaf->cluster;
if(patch->cluster == -1)
qprintf("patch->cluster == -1\n");
WindingCenter(newp->winding, newp->origin);
VectorAdd(newp->origin, newp->plane->normal, newp->origin);
leaf = RadPointInLeaf(newp->origin);
newp->cluster = leaf->cluster;
if(newp->cluster == -1)
qprintf("patch->cluster == -1\n");
}
}
/*
=============
SubdividePatch
Chops the patch only if its local bounds exceed the max size
=============
*/
void SubdividePatch(patch_t *patch) {
winding_t *w, *o1, *o2;
vec3_t mins, maxs, total;
vec3_t split;
vec_t dist;
int32_t i, j;
vec_t v;
patch_t *newp;
w = patch->winding;
mins[0] = mins[1] = mins[2] = BOGUS_RANGE;
maxs[0] = maxs[1] = maxs[2] = -BOGUS_RANGE;
for(i = 0; i < w->numpoints; i++) {
for(j = 0; j < 3; j++) {
v = w->p[i][j];
if(v < mins[j])
mins[j] = v;
if(v > maxs[j])
maxs[j] = v;
}
}
VectorSubtract(maxs, mins, total);
for(i = 0; i < 3; i++)
if(total[i] > (subdiv + 1))
break;
if(i == 3) {
// no splitting needed
return;
}
//
// split the winding
//
VectorCopy(vec3_origin, split);
split[i] = 1;
dist = (mins[i] + maxs[i]) * 0.5;
ClipWindingEpsilon(w, split, dist, ON_EPSILON, &o1, &o2);
//
// create a new patch
//
if(use_qbsp) {
if(num_patches == MAX_PATCHES_QBSP)
Error("Exceeded MAX_PATCHES_QBSP %i", MAX_PATCHES_QBSP);
} else if(num_patches == MAX_PATCHES)
Error("Exceeded MAX_PATCHES %i", MAX_PATCHES);
newp = &patches[num_patches];
num_patches++;
newp->next = patch->next;
patch->next = newp;
patch->winding = o1;
newp->winding = o2;
FinishSplit(patch, newp);
SubdividePatch(patch);
SubdividePatch(newp);
}
/*
=============
DicePatch
Chops the patch by a global grid
=============
*/
void DicePatch(patch_t *patch) {
winding_t *w, *o1, *o2;
vec3_t mins, maxs;
vec3_t split;
vec_t dist;
int32_t i;
patch_t *newp;
w = patch->winding;
WindingBounds(w, mins, maxs); // 3D AABB for polygon
for(i = 0; i < 3; i++)
if(floor((mins[i] + 1) / subdiv) < floor((maxs[i] - 1) / subdiv))
break;
if(i == 3) {
// no splitting needed
return;
}
//
// split the winding
//
VectorCopy(vec3_origin, split);
split[i] = 1;
dist = subdiv * (1 + floor((mins[i] + 1) / subdiv));
ClipWindingEpsilon(w, split, dist, ON_EPSILON, &o1, &o2);
//
// create a new patch
//
if(use_qbsp) {
if(num_patches == MAX_PATCHES_QBSP)
Error("Exceeded MAX_PATCHES_QBSP %i", MAX_PATCHES_QBSP);
} else if(num_patches == MAX_PATCHES)
Error("Exceeded MAX_PATCHES %i", MAX_PATCHES);
newp = &patches[num_patches];
num_patches++;
newp->next = patch->next;
patch->next = newp;
patch->winding = o1;
newp->winding = o2;
FinishSplit(patch, newp);
DicePatch(patch);
DicePatch(newp);
}
/*
=============
SubdividePatches
=============
*/
void SubdividePatches(void) {
int32_t i, num;
if(subdiv < 1)
return;
num = num_patches; // because the list will grow
for(i = 0; i < num; i++) {
if(dicepatches)
DicePatch(&patches[i]);
else
SubdividePatch(&patches[i]);
}
for(i = 0; i < num_patches; i++)
patches[i].nodenum = PointInNodenum(patches[i].origin);
printf("%i subdiv patches\n", num_patches);
printf("-------------------------\n");
qprintf("[? patch->cluster=-1 count is %i ?in solid leaf?]\n", cluster_neg_one);
}
//=====================================================================
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "stddef.h"
#include "assert.h"
#include "qrad.h"
#define MAX_LSTYLES 256
#define SINGLEMAP (64 * 64 * 4)
#define QBSP_SINGLEMAP (256 * 256 * 4) // qb: higher res lightmaps
typedef struct
{
dface_t *faces[2];
dface_tx *facesX[2];
qboolean coplanar;
qboolean smooth;
vec_t cos_normals_angle;
vec3_t interface_normal;
vec3_t vertex_normal[2];
} edgeshare_t;
edgeshare_t edgeshare[MAX_MAP_EDGES_QBSP];
int32_t facelinks[MAX_MAP_FACES_QBSP];
int32_t planelinks[2][MAX_MAP_PLANES_QBSP];
int32_t maxdata = DEFAULT_MAP_LIGHTING, step = LMSTEP;
vec3_t face_texnormals[MAX_MAP_FACES_QBSP];
float sunradscale = 0.5;
byte *dlightdata_ptr;
// qb: quemap- face extents
typedef struct face_extents_s {
vec3_t mins, maxs;
vec3_t center;
vec_t st_mins[2], st_maxs[2];
} face_extents_t;
static face_extents_t face_extents[MAX_MAP_FACES_QBSP];
const dplane_t *getPlaneFromFaceNumber(const uint32_t faceNumber) {
if (use_qbsp) {
dface_tx *face = &dfacesX[faceNumber];
if (face->side) {
return &backplanes[face->planenum];
} else {
return &dplanes[face->planenum];
}
} else {
dface_t *face = &dfaces[faceNumber];
if (face->side) {
return &backplanes[face->planenum];
} else {
return &dplanes[face->planenum];
}
}
}
qboolean GetIntertexnormal(int32_t facenum1, int32_t facenum2) {
vec3_t normal;
const dplane_t *p1 = getPlaneFromFaceNumber(facenum1);
const dplane_t *p2 = getPlaneFromFaceNumber(facenum2);
VectorAdd(face_texnormals[facenum1], face_texnormals[facenum2], normal);
if (!VectorNormalize(normal, normal) || DotProduct(normal, p1->normal) <= NORMAL_EPSILON || DotProduct(normal, p2->normal) <= NORMAL_EPSILON) {
return false;
}
return true;
}
/**
* @brief Populates face_extents for all d_bsp_face_t, prior to light creation.
* This is done so that sample positions may be nudged outward along
* the face normal and towards the face center to help with traces.
*/
void BuildFaceExtents(void) {
const dvertex_t *v;
int32_t i, j, k;
if (use_qbsp)
for (k = 0; k < numfaces; k++) {
const dface_tx *s = &dfacesX[k];
const texinfo_t *tex = &texinfo[s->texinfo];
const size_t face_index = (ptrdiff_t)(s - dfacesX);
vec_t *mins = face_extents[face_index].mins;
vec_t *maxs = face_extents[face_index].maxs;
vec_t *center = face_extents[face_index].center;
vec_t *st_mins = face_extents[face_index].st_mins;
vec_t *st_maxs = face_extents[face_index].st_maxs;
mins[0] = mins[1] = BOGUS_RANGE;
maxs[0] = maxs[1] = -BOGUS_RANGE;
for (i = 0; i < s->numedges; i++) {
const int32_t e = dsurfedges[s->firstedge + i];
if (e >= 0) {
v = dvertexes + dedgesX[e].v[0];
} else {
v = dvertexes + dedgesX[-e].v[1];
}
for (j = 0; j < 3; j++) // calculate mins, maxs
{
if (v->point[j] > maxs[j]) {
maxs[j] = v->point[j];
}
if (v->point[j] < mins[j]) {
mins[j] = v->point[j];
}
}
/* qb: from ericw-tools light/ltface.cc:
* The (long double) casts below are important: The original code
* was written for x87 floating-point which uses 80-bit floats for
* intermediate calculations. But if you compile it without the
* casts for modern x86_64, the compiler will round each
* intermediate result to a 32-bit float, which introduces extra
* rounding error.
*
* This becomes a problem if the rounding error causes the light
* utilities and the engine to disagree about the lightmap size
* for some surfaces.
*
* Casting to (long double) keeps the intermediate values at at
* least 64 bits of precision, probably 128.
*/
for (j = 0; j < 2; j++) // calculate st_mins, st_maxs
{
// const vec_t val = DotProduct(v->point, tex->vecs[j]) + tex->vecs[j][3];
const vec_t val = (long double)v->point[0] * tex->vecs[j][0] +
(long double)v->point[1] * tex->vecs[j][1] +
(long double)v->point[2] * tex->vecs[j][2] +
tex->vecs[j][3];
if (val < st_mins[j]) {
st_mins[j] = val;
}
if (val > st_maxs[j]) {
st_maxs[j] = val;
}
}
}
for (i = 0; i < 3; i++) // calculate center
{
center[i] = (mins[i] + maxs[i]) / 2.0;
}
}
else // ibsp
for (k = 0; k < numfaces; k++) {
const dface_t *s = &dfaces[k];
const texinfo_t *tex = &texinfo[s->texinfo];
const size_t face_index = (ptrdiff_t)(s - dfaces);
vec_t *mins = face_extents[face_index].mins;
vec_t *maxs = face_extents[face_index].maxs;
vec_t *center = face_extents[face_index].center;
vec_t *st_mins = face_extents[face_index].st_mins;
vec_t *st_maxs = face_extents[face_index].st_maxs;
mins[0] = mins[1] = BOGUS_RANGE;
maxs[0] = maxs[1] = -BOGUS_RANGE;
for (i = 0; i < s->numedges; i++) {
const int32_t 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 < 3; j++) // calculate mins, maxs
{
if (v->point[j] > maxs[j]) {
maxs[j] = v->point[j];
}
if (v->point[j] < mins[j]) {
mins[j] = v->point[j];
}
}
for (j = 0; j < 2; j++) // calculate st_mins, st_maxs
{
// const vec_t val = DotProduct(v->point, tex->vecs[j]) + tex->vecs[j][3];
const vec_t val = (long double)v->point[0] * tex->vecs[j][0] +
(long double)v->point[1] * tex->vecs[j][1] +
(long double)v->point[2] * tex->vecs[j][2] +
tex->vecs[j][3];
if (val < st_mins[j]) {
st_mins[j] = val;
}
if (val > st_maxs[j]) {
st_maxs[j] = val;
}
}
}
for (i = 0; i < 3; i++) // calculate center
{
center[i] = (mins[i] + maxs[i]) / 2.0;
}
}
}
/*
============
LinkPlaneFaces
============
*/
void LinkPlaneFaces(void) {
int32_t i;
if (use_qbsp) {
dface_tx *f;
f = dfacesX;
for (i = 0; i < numfaces; i++, f++) {
facelinks[i] = planelinks[f->side][f->planenum];
planelinks[f->side][f->planenum] = i;
}
} else {
dface_t *f;
f = dfaces;
for (i = 0; i < numfaces; i++, f++) {
facelinks[i] = planelinks[f->side][f->planenum];
planelinks[f->side][f->planenum] = i;
}
}
}
const dplane_t *getPlaneFromFace(const dface_t *face) {
if (!face) {
Error("getPlaneFromFace face was NULL\n");
}
if (face->side) {
return &backplanes[face->planenum];
} else {
return &dplanes[face->planenum];
}
}
const dplane_t *getPlaneFromFaceX(const dface_tx *face) {
if (!face) {
Error("getPlaneFromFaceX face was NULL\n");
}
if (face->side) {
return &backplanes[face->planenum];
} else {
return &dplanes[face->planenum];
}
}
/*
============
PairEdges
============
*/
// qb: VHLT
int32_t AddFaceForVertexNormalX(const int32_t edgeabs, int32_t edgeabsnext, const int32_t edgeend, int32_t edgeendnext, dface_tx *const f, dface_tx *fnext, vec_t angle, vec3_t normal)
// Must guarantee these faces will form a loop or a chain, otherwise will result in endless loop.
//
// e[end]/enext[endnext]
// *
// |\.
// |a\ fnext
// | \,
// | f \.
// | \.
// e enext
//
{
VectorCopy(getPlaneFromFaceX(f)->normal, normal);
int32_t vnum = dedgesX[edgeabs].v[edgeend];
int32_t edge = 0, edgenext = 0;
int32_t i, e, count1, count2;
vec_t dot;
for (count1 = count2 = 0, i = 0; i < f->numedges; i++) {
e = dsurfedges[f->firstedge + i];
if (dedgesX[abs(e)].v[0] == dedgesX[abs(e)].v[1])
continue;
if (abs(e) == edgeabs) {
edge = e;
count1++;
} else if (dedgesX[abs(e)].v[0] == vnum || dedgesX[abs(e)].v[1] == vnum) {
edgenext = e;
count2++;
}
}
if (count1 != 1 || count2 != 1) {
qprintf("AddFaceForVertexNormalX bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
int32_t vnum11, vnum12, vnum21, vnum22;
vec3_t vec1, vec2;
vnum11 = dedgesX[abs(edge)].v[edge > 0 ? 0 : 1];
vnum12 = dedgesX[abs(edge)].v[edge > 0 ? 1 : 0];
vnum21 = dedgesX[abs(edgenext)].v[edgenext > 0 ? 0 : 1];
vnum22 = dedgesX[abs(edgenext)].v[edgenext > 0 ? 1 : 0];
if (vnum == vnum12 && vnum == vnum21 && vnum != vnum11 && vnum != vnum22) {
VectorSubtract(dvertexes[vnum11].point, dvertexes[vnum].point, vec1);
VectorSubtract(dvertexes[vnum22].point, dvertexes[vnum].point, vec2);
edgeabsnext = abs(edgenext);
edgeendnext = edgenext > 0 ? 0 : 1;
} else if (vnum == vnum11 && vnum == vnum22 && vnum != vnum12 && vnum != vnum21) {
VectorSubtract(dvertexes[vnum12].point, dvertexes[vnum].point, vec1);
VectorSubtract(dvertexes[vnum21].point, dvertexes[vnum].point, vec2);
edgeabsnext = abs(edgenext);
edgeendnext = edgenext > 0 ? 1 : 0;
} else {
qprintf("AddFaceForVertexNormalX bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
VectorNormalize(vec1, vec1);
VectorNormalize(vec2, vec2);
dot = DotProduct(vec1, vec2);
dot = dot > 1 ? 1 : dot < -1 ? -1
: dot;
angle = acos(dot);
edgeshare_t *es = &edgeshare[edgeabsnext];
if (!(es->facesX[0] && es->facesX[1]))
return 1;
if (es->facesX[0] == f && es->facesX[1] != f)
fnext = es->facesX[1];
else if (es->facesX[1] == f && es->facesX[0] != f)
fnext = es->facesX[0];
else {
qprintf("AddFaceForVertexNormalX bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
return 0;
}
int32_t AddFaceForVertexNormal(const int32_t edgeabs, int32_t edgeabsnext, const int32_t edgeend, int32_t edgeendnext, dface_t *const f, dface_t *fnext, vec_t angle, vec3_t normal) {
VectorCopy(getPlaneFromFace(f)->normal, normal);
int32_t vnum = dedgesX[edgeabs].v[edgeend];
int32_t edge = 0, edgenext = 0;
int32_t i, e, count1, count2;
vec_t dot;
for (count1 = count2 = 0, i = 0; i < f->numedges; i++) {
e = dsurfedges[f->firstedge + i];
if (dedges[abs(e)].v[0] == dedges[abs(e)].v[1])
continue;
if (abs(e) == edgeabs) {
edge = e;
count1++;
} else if (dedges[abs(e)].v[0] == vnum || dedges[abs(e)].v[1] == vnum) {
edgenext = e;
count2++;
}
}
if (count1 != 1 || count2 != 1) {
qprintf("AddFaceForVertexNormal bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
int32_t vnum11, vnum12, vnum21, vnum22;
vec3_t vec1, vec2;
vnum11 = dedges[abs(edge)].v[edge > 0 ? 0 : 1];
vnum12 = dedges[abs(edge)].v[edge > 0 ? 1 : 0];
vnum21 = dedges[abs(edgenext)].v[edgenext > 0 ? 0 : 1];
vnum22 = dedges[abs(edgenext)].v[edgenext > 0 ? 1 : 0];
if (vnum == vnum12 && vnum == vnum21 && vnum != vnum11 && vnum != vnum22) {
VectorSubtract(dvertexes[vnum11].point, dvertexes[vnum].point, vec1);
VectorSubtract(dvertexes[vnum22].point, dvertexes[vnum].point, vec2);
edgeabsnext = abs(edgenext);
edgeendnext = edgenext > 0 ? 0 : 1;
} else if (vnum == vnum11 && vnum == vnum22 && vnum != vnum12 && vnum != vnum21) {
VectorSubtract(dvertexes[vnum12].point, dvertexes[vnum].point, vec1);
VectorSubtract(dvertexes[vnum21].point, dvertexes[vnum].point, vec2);
edgeabsnext = abs(edgenext);
edgeendnext = edgenext > 0 ? 1 : 0;
} else {
qprintf("AddFaceForVertexNormal bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
VectorNormalize(vec1, vec1);
VectorNormalize(vec2, vec2);
dot = DotProduct(vec1, vec2);
dot = dot > 1 ? 1 : dot < -1 ? -1
: dot;
angle = acos(dot);
edgeshare_t *es = &edgeshare[edgeabsnext];
if (!(es->faces[0] && es->faces[1]))
return 1;
if (es->faces[0] == f && es->faces[1] != f)
fnext = es->faces[1];
else if (es->faces[1] == f && es->faces[0] != f)
fnext = es->faces[0];
else {
qprintf("AddFaceForVertexNormal bad face: edgeabs=%d edgeend=%d\n", edgeabs, edgeend);
return -1;
}
return 0;
}
// =====================================================================================
// PairEdges
// =====================================================================================
void PairEdges() {
int32_t i, j, k;
edgeshare_t *e;
memset(&edgeshare, 0, sizeof(edgeshare));
if (use_qbsp) {
dface_tx *f;
f = dfacesX;
for (i = 0; i < numfaces; i++, f++) {
{
const dplane_t *fp = getPlaneFromFaceX(f);
vec3_t texnormal;
const texinfo_t *tex = &texinfo[f->texinfo];
CrossProduct(tex->vecs[1], tex->vecs[0], texnormal);
VectorNormalize(texnormal, texnormal);
if (DotProduct(texnormal, fp->normal) < 0) {
VectorSubtract(vec3_origin, texnormal, texnormal);
}
VectorCopy(texnormal, face_texnormals[i]);
}
for (j = 0; j < f->numedges; j++) {
k = dsurfedges[f->firstedge + j];
if (k < 0) {
e = &edgeshare[-k];
assert(e->facesX[1] == NULL);
e->facesX[1] = f;
} else {
e = &edgeshare[k];
assert(e->facesX[0] == NULL);
e->facesX[0] = f;
}
if (e->facesX[0] && e->facesX[1]) {
// determine if coplanar
if ((e->facesX[0]->planenum == e->facesX[1]->planenum) && (e->facesX[0]->side == e->facesX[1]->side)) {
e->coplanar = true;
VectorCopy(getPlaneFromFaceX(e->facesX[0])->normal, e->interface_normal);
e->cos_normals_angle = 1.0;
} else {
// see if they fall into a "smoothing group" based on angle of the normals
vec3_t normals[2];
VectorCopy(getPlaneFromFaceX(e->facesX[0])->normal, normals[0]);
VectorCopy(getPlaneFromFaceX(e->facesX[1])->normal, normals[1]);
e->cos_normals_angle = DotProduct(normals[0], normals[1]);
if (e->cos_normals_angle > (1.0 - 0.01)) // qb: get sloppier than 1 - NORMAL_EPSILON
{
e->coplanar = true;
VectorCopy(getPlaneFromFaceX(e->facesX[0])->normal, e->interface_normal);
e->cos_normals_angle = 1.0;
} else if (smoothing_threshold > 0.0) {
if (e->cos_normals_angle >= smoothing_threshold) {
num_smoothing += 1;
VectorAdd(normals[0], normals[1], e->interface_normal);
VectorNormalize(e->interface_normal, e->interface_normal);
}
}
}
if (!VectorCompare(e->interface_normal, vec3_origin)) {
e->smooth = true;
}
if (!GetIntertexnormal(e->facesX[0] - dfacesX, e->facesX[1] - dfacesX)) {
// printf ("!GetIntertexnormal hit.\n");
e->coplanar = false;
VectorClear(e->interface_normal);
e->smooth = false;
}
}
}
}
} else // ibsp
{
dface_t *f;
f = dfaces;
for (i = 0; i < numfaces; i++, f++) {
{
const dplane_t *fp = getPlaneFromFace(f);
vec3_t texnormal;
const texinfo_t *tex = &texinfo[f->texinfo];
CrossProduct(tex->vecs[1], tex->vecs[0], texnormal);
VectorNormalize(texnormal, texnormal);
if (DotProduct(texnormal, fp->normal) < 0) {
VectorSubtract(vec3_origin, texnormal, texnormal);
}
VectorCopy(texnormal, face_texnormals[i]);
}
for (j = 0; j < f->numedges; j++) {
k = dsurfedges[f->firstedge + j];
if (k < 0) {
e = &edgeshare[-k];
assert(e->faces[1] == NULL);
e->faces[1] = f;
} else {
e = &edgeshare[k];
assert(e->faces[0] == NULL);
e->faces[0] = f;
}
if (e->faces[0] && e->faces[1]) {
// determine if coplanar
if ((e->faces[0]->planenum == e->faces[1]->planenum) && (e->faces[0]->side == e->faces[1]->side)) {
e->coplanar = true;
VectorCopy(getPlaneFromFace(e->faces[0])->normal, e->interface_normal);
e->cos_normals_angle = 1.0;
} else {
// see if they fall into a "smoothing group" based on angle of the normals
vec3_t normals[2];
VectorCopy(getPlaneFromFace(e->faces[0])->normal, normals[0]);
VectorCopy(getPlaneFromFace(e->faces[1])->normal, normals[1]);
e->cos_normals_angle = DotProduct(normals[0], normals[1]);
if (e->cos_normals_angle > (1.0 - 0.01)) // qb: get sloppier than 1 - NORMAL_EPSILON
{
e->coplanar = true;
VectorCopy(getPlaneFromFace(e->faces[0])->normal, e->interface_normal);
e->cos_normals_angle = 1.0;
} else if (smoothing_threshold > 0.0) {
if (e->cos_normals_angle >= smoothing_threshold) {
num_smoothing += 1;
VectorAdd(normals[0], normals[1], e->interface_normal);
VectorNormalize(e->interface_normal, e->interface_normal);
}
}
}
if (!VectorCompare(e->interface_normal, vec3_origin)) {
e->smooth = true;
}
if (!GetIntertexnormal(e->faces[0] - dfaces, e->faces[1] - dfaces)) {
// printf ("!GetIntertexnormal hit.\n");
e->coplanar = false;
VectorClear(e->interface_normal);
e->smooth = false;
}
}
}
}
}
// qb: VHLT
{
int32_t edgeabs, edgeabsnext;
int32_t edgeend, edgeendnext;
int32_t d;
vec_t angle = 0, angles = 0;
vec3_t normal, normals;
vec3_t edgenormal;
int32_t r, count, mme;
if (use_qbsp)
mme = MAX_MAP_EDGES_QBSP;
else
mme = MAX_MAP_EDGES;
for (edgeabs = 0; edgeabs < mme; edgeabs++) {
e = &edgeshare[edgeabs];
if (!e->smooth)
continue;
VectorCopy(e->interface_normal, edgenormal);
if (use_qbsp) {
dface_tx *f, *fcurrent, *fnext;
if (dedgesX[edgeabs].v[0] == dedgesX[edgeabs].v[1]) {
vec3_t errorpos;
VectorCopy(dvertexes[dedgesX[edgeabs].v[0]].point, errorpos);
VectorAdd(errorpos, face_offset[e->facesX[0] - dfacesX], errorpos);
Error("PairEdges: invalid edge at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
VectorCopy(edgenormal, e->vertex_normal[0]);
VectorCopy(edgenormal, e->vertex_normal[1]);
} else {
const dplane_t *p0 = getPlaneFromFaceX(e->facesX[0]);
const dplane_t *p1 = getPlaneFromFaceX(e->facesX[1]);
for (edgeend = 0; edgeend < 2; edgeend++) {
vec3_t errorpos;
VectorCopy(dvertexes[dedgesX[edgeabs].v[edgeend]].point, errorpos);
VectorAdd(errorpos, face_offset[e->facesX[0] - dfacesX], errorpos);
angles = 0;
VectorClear(normals);
for (d = 0; d < 2; d++) {
f = e->facesX[d];
count = 0, fnext = f, edgeabsnext = edgeabs, edgeendnext = edgeend;
while (1) {
fcurrent = fnext;
r = AddFaceForVertexNormalX(edgeabsnext, edgeabsnext, edgeendnext, edgeendnext, fcurrent, fnext, angle, normal);
count++;
if (r == -1) {
// qprintf("PairEdges: face edges mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
break;
}
if (count >= 100) {
// qprintf("PairEdges: faces mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
break;
}
if (DotProduct(normal, p0->normal) <= NORMAL_EPSILON || DotProduct(normal, p1->normal) <= NORMAL_EPSILON)
break;
if (DotProduct(edgenormal, normal) + NORMAL_EPSILON < smoothing_threshold)
break;
if (!GetIntertexnormal(fcurrent - dfacesX, e->facesX[0] - dfacesX) || !GetIntertexnormal(fcurrent - dfacesX, e->facesX[1] - dfacesX))
break;
angles += angle;
VectorMA(normals, angle, normal, normals);
if (r != 0 || fnext == f)
break;
}
}
if (angles < NORMAL_EPSILON) {
VectorCopy(edgenormal, e->vertex_normal[edgeend]);
// qprintf("PairEdges: no valid faces at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
} else {
VectorNormalize(normals, e->vertex_normal[edgeend]);
}
}
}
} else // ibsp
{
dface_t *f, *fcurrent, *fnext;
if (dedges[edgeabs].v[0] == dedges[edgeabs].v[1]) {
vec3_t errorpos;
VectorCopy(dvertexes[dedges[edgeabs].v[0]].point, errorpos);
VectorAdd(errorpos, face_offset[e->faces[0] - dfaces], errorpos);
Error("PairEdges: invalid edge at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
VectorCopy(edgenormal, e->vertex_normal[0]);
VectorCopy(edgenormal, e->vertex_normal[1]);
} else {
const dplane_t *p0 = getPlaneFromFace(e->faces[0]);
const dplane_t *p1 = getPlaneFromFace(e->faces[1]);
for (edgeend = 0; edgeend < 2; edgeend++) {
vec3_t errorpos;
VectorCopy(dvertexes[dedges[edgeabs].v[edgeend]].point, errorpos);
VectorAdd(errorpos, face_offset[e->faces[0] - dfaces], errorpos);
angles = 0;
VectorClear(normals);
for (d = 0; d < 2; d++) {
f = e->faces[d];
count = 0, fnext = f, edgeabsnext = edgeabs, edgeendnext = edgeend;
while (1) {
fcurrent = fnext;
r = AddFaceForVertexNormal(edgeabsnext, edgeabsnext, edgeendnext, edgeendnext, fcurrent, fnext, angle, normal);
count++;
if (r == -1) {
// qprintf("PairEdges: face edges mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
break;
}
if (count >= 100) {
// qprintf("PairEdges: faces mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
break;
}
if (DotProduct(normal, p0->normal) <= NORMAL_EPSILON || DotProduct(normal, p1->normal) <= NORMAL_EPSILON)
break;
if (DotProduct(edgenormal, normal) + NORMAL_EPSILON < smoothing_threshold)
break;
if (!GetIntertexnormal(fcurrent - dfaces, e->faces[0] - dfaces) || !GetIntertexnormal(fcurrent - dfaces, e->faces[1] - dfaces))
break;
angles += angle;
VectorMA(normals, angle, normal, normals);
if (r != 0 || fnext == f)
break;
}
}
if (angles < NORMAL_EPSILON) {
VectorCopy(edgenormal, e->vertex_normal[edgeend]);
// qprintf("PairEdges: no valid faces at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]);
} else {
VectorNormalize(normals, e->vertex_normal[edgeend]);
}
}
}
}
if (e->coplanar) {
if (!VectorCompare(e->vertex_normal[0], e->interface_normal) || !VectorCompare(e->vertex_normal[1], e->interface_normal)) {
e->coplanar = false;
}
}
}
}
}
/*
=================================================================
POINT TRIANGULATION
=================================================================
*/
typedef struct triedge_s {
int32_t p0, p1;
vec3_t normal;
vec_t dist;
struct triangle_s *tri;
} triedge_t;
typedef struct triangle_s {
triedge_t *edges[3];
} triangle_t;
#define MAX_TRI_POINTS 1024
#define MAX_TRI_EDGES (MAX_TRI_POINTS * 6)
#define MAX_TRI_TRIS (MAX_TRI_POINTS * 2)
typedef struct
{
int32_t numpoints;
int32_t numedges;
int32_t numtris;
dplane_t *plane;
triedge_t *edgematrix[MAX_TRI_POINTS][MAX_TRI_POINTS];
patch_t *points[MAX_TRI_POINTS];
triedge_t edges[MAX_TRI_EDGES];
triangle_t tris[MAX_TRI_TRIS];
} triangulation_t;
/*
===============
AllocTriangulation
===============
*/
triangulation_t *AllocTriangulation(dplane_t *plane) {
triangulation_t *t;
t = malloc(sizeof(triangulation_t));
t->numpoints = 0;
t->numedges = 0;
t->numtris = 0;
t->plane = plane;
// memset (t->edgematrix, 0, sizeof(t->edgematrix));
return t;
}
/*
===============
FreeTriangulation
===============
*/
void FreeTriangulation(triangulation_t *tr) {
free(tr);
}
triedge_t *FindEdge(triangulation_t *trian, int32_t p0, int32_t p1) {
triedge_t *e, *be;
vec3_t v1;
vec3_t normal;
vec_t dist;
if (trian->edgematrix[p0][p1])
return trian->edgematrix[p0][p1];
if (trian->numedges > MAX_TRI_EDGES - 2)
Error("trian->numedges > MAX_TRI_EDGES-2");
VectorSubtract(trian->points[p1]->origin, trian->points[p0]->origin, v1);
VectorNormalize(v1, v1);
CrossProduct(v1, trian->plane->normal, normal);
dist = DotProduct(trian->points[p0]->origin, normal);
e = &trian->edges[trian->numedges];
e->p0 = p0;
e->p1 = p1;
e->tri = NULL;
VectorCopy(normal, e->normal);
e->dist = dist;
trian->numedges++;
trian->edgematrix[p0][p1] = e;
be = &trian->edges[trian->numedges];
be->p0 = p1;
be->p1 = p0;
be->tri = NULL;
VectorSubtract(vec3_origin, normal, be->normal);
be->dist = -dist;
trian->numedges++;
trian->edgematrix[p1][p0] = be;
return e;
}
triangle_t *AllocTriangle(triangulation_t *trian) {
triangle_t *t;
if (trian->numtris >= MAX_TRI_TRIS)
Error("trian->numtris >= MAX_TRI_TRIS");
t = &trian->tris[trian->numtris];
trian->numtris++;
return t;
}
/*
============
TriEdge_r
============
*/
void TriEdge_r(triangulation_t *trian, triedge_t *e) {
int32_t i, bestp = 0;
vec3_t v1, v2;
vec_t *p0, *p1, *p;
vec_t best, ang;
triangle_t *nt;
if (e->tri)
return; // allready connected by someone
// find the point with the best angle
p0 = trian->points[e->p0]->origin;
p1 = trian->points[e->p1]->origin;
best = 1.1;
for (i = 0; i < trian->numpoints; i++) {
p = trian->points[i]->origin;
// a 0 dist will form a degenerate triangle
if (DotProduct(p, e->normal) - e->dist < 0)
continue; // behind edge
VectorSubtract(p0, p, v1);
VectorSubtract(p1, p, v2);
if (!VectorNormalize(v1, v1))
continue;
if (!VectorNormalize(v2, v2))
continue;
ang = DotProduct(v1, v2);
if (ang < best) {
best = ang;
bestp = i;
}
}
if (best >= 1)
return; // edge doesn't match anything
// make a new triangle
nt = AllocTriangle(trian);
nt->edges[0] = e;
nt->edges[1] = FindEdge(trian, e->p1, bestp);
nt->edges[2] = FindEdge(trian, bestp, e->p0);
for (i = 0; i < 3; i++)
nt->edges[i]->tri = nt;
TriEdge_r(trian, FindEdge(trian, bestp, e->p1));
TriEdge_r(trian, FindEdge(trian, e->p0, bestp));
}
/*
============
TriangulatePoints
============
*/
void TriangulatePoints(triangulation_t *trian) {
vec_t d, bestd;
vec3_t v1;
int32_t bp1 = 0, bp2 = 0, i, j;
vec_t *p1, *p2;
triedge_t *e, *e2;
if (trian->numpoints < 2)
return;
// find the two closest points
bestd = BOGUS_RANGE;
for (i = 0; i < trian->numpoints; i++) {
p1 = trian->points[i]->origin;
for (j = i + 1; j < trian->numpoints; j++) {
p2 = trian->points[j]->origin;
VectorSubtract(p2, p1, v1);
d = VectorLength(v1);
if (d < bestd) {
bestd = d;
bp1 = i;
bp2 = j;
}
}
}
e = FindEdge(trian, bp1, bp2);
e2 = FindEdge(trian, bp2, bp1);
TriEdge_r(trian, e);
TriEdge_r(trian, e2);
}
/*
===============
AddPointToTriangulation
===============
*/
void AddPointToTriangulation(patch_t *patch, triangulation_t *trian) {
int32_t pnum;
pnum = trian->numpoints;
if (pnum == MAX_TRI_POINTS)
Error("trian->numpoints == MAX_TRI_POINTS");
trian->points[pnum] = patch;
trian->numpoints++;
}
/*
===============
LerpTriangle
===============
*/
struct SH1 LerpTriangle(triangulation_t *trian, triangle_t *t, vec3_t point) {
patch_t *p1 = trian->points[t->edges[0]->p0];
patch_t *p2 = trian->points[t->edges[1]->p0];
patch_t *p3 = trian->points[t->edges[2]->p0];
float x1 = DotProduct(p3->origin, t->edges[0]->normal) - t->edges[0]->dist;
float y1 = DotProduct(p2->origin, t->edges[2]->normal) - t->edges[2]->dist;
float c2 = (fabs(x1) >= ON_EPSILON) ? (DotProduct(point, t->edges[0]->normal) - t->edges[0]->dist) / x1 : 0;
float c3 = (fabs(y1) >= ON_EPSILON) ? (DotProduct(point, t->edges[2]->normal) - t->edges[2]->dist) / y1 : 0;
float c1 = 1.0f - (c2 + c3);
return SH1_Add(
SH1_Add(
SH1_Scale(p1->totallight, c1),
SH1_Scale(p2->totallight, c2)
), SH1_Scale(p3->totallight, c3)
);
}
qboolean PointInTriangle(vec3_t point, triangle_t *t) {
int32_t i;
triedge_t *e;
vec_t d;
for (i = 0; i < 3; i++) {
e = t->edges[i];
d = DotProduct(e->normal, point) - e->dist;
if (d < 0)
return false; // not inside
}
return true;
}
/*
===============
SampleTriangulation
===============
*/
struct SH1 SampleTriangulation(vec3_t point, triangulation_t *trian, triangle_t **last_valid) {
triangle_t *t;
triedge_t *e;
vec_t d, best;
patch_t *p0, *p1;
vec3_t v1, v2;
int32_t i, j;
if (trian->numpoints == 0) {
return SH1_Clear();
}
if (trian->numpoints == 1) {
return trian->points[0]->totallight;
}
// try the last one
if (*last_valid) {
if (PointInTriangle(point, *last_valid)) {
return LerpTriangle(trian, *last_valid, point);
}
}
// search for triangles
for (t = trian->tris, j = 0; j < trian->numtris; t++, j++) {
if (t == *last_valid)
continue;
if (!PointInTriangle(point, t))
continue;
*last_valid = t;
return LerpTriangle(trian, t, point);
}
// search for exterior edge
for (e = trian->edges, j = 0; j < trian->numedges; e++, j++) {
if (e->tri)
continue; // not an exterior edge
d = DotProduct(point, e->normal) - e->dist;
if (d < 0)
continue; // not in front of edge
p0 = trian->points[e->p0];
p1 = trian->points[e->p1];
VectorSubtract(p1->origin, p0->origin, v1);
VectorNormalize(v1, v1);
VectorSubtract(point, p0->origin, v2);
d = DotProduct(v2, v1);
if (d < 0)
continue;
if (d > 1)
continue;
return SH1_Add(SH1_Scale(p0->totallight, 1.0f - d), SH1_Scale(p1->totallight, d));
}
// search for nearest point
best = BOGUS_RANGE;
p1 = NULL;
for (j = 0; j < trian->numpoints; j++) {
p0 = trian->points[j];
VectorSubtract(point, p0->origin, v1);
d = VectorLength(v1);
if (d < best) {
best = d;
p1 = p0;
}
}
if (!p1)
Error("SampleTriangulation: no points");
return p1->totallight;
}
/*
=================================================================
LIGHTMAP SAMPLE GENERATION
=================================================================
*/
typedef struct
{
vec_t facedist;
vec3_t facenormal;
int32_t numsurfpt;
vec3_t surfpt[QBSP_SINGLEMAP];
vec3_t modelorg; // for origined bmodels
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];
int32_t texmins[2], texsize[2];
int32_t surfnum;
dface_t *face;
dface_tx *faceX;
} lightinfo_t;
/*
================
CalcFaceExtents
Fills in s->texmins[] and s->texsize[]
also sets exactmins[] and exactmaxs[]
================
*/
void CalcFaceExtents(lightinfo_t *l) {
vec_t mins[2], maxs[2], val;
int32_t i, j, e, map = SINGLEMAP;
dvertex_t *v;
texinfo_t *tex;
vec3_t vt;
if (use_qbsp) {
map = QBSP_SINGLEMAP;
dface_tx *s;
s = l->faceX;
mins[0] = mins[1] = BOGUS_RANGE;
maxs[0] = maxs[1] = -BOGUS_RANGE;
tex = &texinfo[s->texinfo];
for (i = 0; i < s->numedges; i++) {
e = dsurfedges[s->firstedge + i];
if (e >= 0)
v = dvertexes + dedgesX[e].v[0];
else
v = dvertexes + dedgesX[-e].v[1];
// VectorAdd (v->point, l->modelorg, vt);
VectorCopy(v->point, vt);
for (j = 0; j < 2; j++) {
val = DotProduct(vt, tex->vecs[j]) + tex->vecs[j][3];
if (val < mins[j])
mins[j] = val;
if (val > maxs[j])
maxs[j] = val;
}
}
} else {
dface_t *s;
s = l->face;
mins[0] = mins[1] = BOGUS_RANGE;
maxs[0] = maxs[1] = -BOGUS_RANGE;
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];
// VectorAdd (v->point, l->modelorg, vt);
VectorCopy(v->point, vt);
for (j = 0; j < 2; j++) {
val = DotProduct(vt, tex->vecs[j]) + 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] / step);
maxs[i] = ceil(maxs[i] / step);
l->texmins[i] = mins[i];
l->texsize[i] = maxs[i] - mins[i];
}
if (l->texsize[0] * l->texsize[1] > map / 4) // div 4 for extrasamples
{
char s[3] = {'X', 'Y', 'Z'};
for (i = 0; i < 2; i++) {
printf("Axis: %c\n", s[i]);
l->exactmins[i] = mins[i];
l->exactmaxs[i] = maxs[i];
mins[i] = floor(mins[i] / step);
maxs[i] = ceil(maxs[i] / step);
l->texmins[i] = mins[i];
l->texsize[i] = maxs[i] - mins[i];
printf(" Mins = %10.3f, Maxs = %10.3f, Size = %10.3f\n", (double)mins[i], (double)maxs[i], (double)(maxs[i] - mins[i]));
}
Error("Surface too large to map");
}
}
/*
================
CalcFaceVectors
Fills in texorg, worldtotex. and textoworld
================
*/
void CalcFaceVectors(lightinfo_t *l) {
texinfo_t *tex;
int32_t i, j;
vec3_t texnormal;
vec_t distscale;
vec_t dist, len;
int32_t w, h;
if (use_qbsp)
tex = &texinfo[l->faceX->texinfo];
else
tex = &texinfo[l->face->texinfo];
// convert from float to double
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, texnormal);
// flip it towards plane normal
distscale = DotProduct(texnormal, l->facenormal);
if (!distscale) {
qprintf("WARNING: Texture axis perpendicular to face\n");
distscale = 1;
}
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);
// compensate for org'd bmodels
VectorAdd(l->texorg, l->modelorg, l->texorg);
// total sample count
h = l->texsize[1] + 1;
w = l->texsize[0] + 1;
l->numsurfpt = w * h;
}
/*
=================
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, float sofs, float tofs) {
int32_t i;
int32_t s, t, j;
int32_t w, h;
vec_t starts, startt, us, ut;
vec_t *surf;
vec_t mids, midt;
vec3_t facemid;
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;
h = l->texsize[1] + 1;
w = l->texsize[0] + 1;
l->numsurfpt = w * h;
starts = l->texmins[0] * step;
startt = l->texmins[1] * step;
for (t = 0; t < h; t++) {
for (s = 0; s < w; s++, surf += 3) {
us = starts + (s + sofs) * step;
ut = startt + (t + tofs) * 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 (use_qbsp) {
dleaf_tx *leaf;
leaf = RadPointInLeafX(surf);
if (leaf->contents != CONTENTS_SOLID) {
if (!TestLine_r(0, facemid, surf))
break; // got it
}
} else {
dleaf_t *leaf;
leaf = RadPointInLeaf(surf);
if (leaf->contents != CONTENTS_SOLID) {
if (!TestLine_r(0, facemid, surf))
break; // got it
}
}
// nudge 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;
}
}
}
}
}
}
//==============================================================
#define MAX_STYLES 32
typedef struct
{
int32_t numsamples;
float *origins;
int32_t numstyles;
int32_t stylenums[MAX_STYLES];
struct SH1 *samples[MAX_STYLES];
} facelight_t;
directlight_t *directlights[MAX_MAP_LEAFS_QBSP];
facelight_t facelight[MAX_MAP_FACES_QBSP];
int32_t numdlights;
/*
==================
FindTargetEntity
==================
*/
entity_t *FindTargetEntity(char *target) {
int32_t i;
char *n;
for (i = 0; i < num_entities; i++) {
n = ValueForKey(&entities[i], "targetname");
if (!strcmp(n, target))
return &entities[i];
}
return NULL;
}
//#define DIRECT_LIGHT 3000
#define DIRECT_LIGHT 3
/*
=============
CreateDirectLights
=============
*/
void CreateDirectLights(void) {
int32_t i;
patch_t *p;
directlight_t *dl;
dleaf_t *leaf;
dleaf_tx *leafX;
int32_t cluster;
entity_t *e, *e2;
char *name;
char *target;
float angle;
vec3_t dest;
char *_color;
float intensity;
char *sun_target = NULL;
char *proc_num;
//
// entities
//
for (i = 0; i < num_entities; i++) {
e = &entities[i];
name = ValueForKey(e, "classname");
if (strncmp(name, "light", 5)) {
if (!strncmp(name, "worldspawn", 10)) {
sun_target = ValueForKey(e, "_sun");
if (strlen(sun_target) > 0) {
printf("Sun activated.\n");
printf("Sky radiosity (sunradscale): %f \n", sunradscale);
sun = true;
}
proc_num = ValueForKey(e, "_sun_ambient");
if (strlen(proc_num) > 0) {
sun_ambient = atof(proc_num);
}
proc_num = ValueForKey(e, "_sun_light");
if (strlen(proc_num) > 0) {
sun_main = atof(proc_num);
}
proc_num = ValueForKey(e, "_sun_color");
if (strlen(proc_num) > 0) {
GetVectorForKey(e, "_sun_color", sun_color);
sun_alt_color = true;
ColorNormalize(sun_color, sun_color);
}
}
continue;
}
target = ValueForKey(e, "target");
if (strlen(target) >= 1 && sun_target && !strcmp(target, sun_target)) // qb: add sun_target check
{
vec3_t sun_s, sun_t;
printf("Sun target found.\n");
GetVectorForKey(e, "origin", sun_s);
e2 = FindTargetEntity(target);
if (!e2) {
printf("WARNING: sun missing target, 0,0,0 used\n");
sun_t[0] = 0;
sun_t[1] = 0;
sun_t[2] = 0;
} else {
GetVectorForKey(e2, "origin", sun_t);
}
VectorSubtract(sun_s, sun_t, sun_pos);
VectorNormalize(sun_pos, sun_pos);
printf("SUN VECTOR: %f, %f, %f\n", sun_pos[0], sun_pos[1], sun_pos[2]);
continue;
}
numdlights++;
dl = malloc(sizeof(directlight_t));
memset(dl, 0, sizeof(*dl));
GetVectorForKey(e, "origin", dl->origin);
dl->style = FloatForKey(e, "_style");
if (!dl->style)
dl->style = FloatForKey(e, "style");
if (dl->style < 0 || dl->style >= MAX_LSTYLES)
dl->style = 0;
dl->nodenum = PointInNodenum(dl->origin);
if (use_qbsp) {
leafX = RadPointInLeafX(dl->origin);
cluster = leafX->cluster;
} else {
leaf = RadPointInLeaf(dl->origin);
cluster = leaf->cluster;
}
dl->next = directlights[cluster];
directlights[cluster] = dl;
proc_num = ValueForKey(e, "_wait");
if (strlen(proc_num) > 0)
dl->wait = atof(proc_num);
else {
proc_num = ValueForKey(e, "wait");
if (strlen(proc_num) > 0)
dl->wait = atof(proc_num);
else
dl->wait = 1.0f;
}
if (dl->wait <= EQUAL_EPSILON)
dl->wait = 1.0f;
proc_num = ValueForKey(e, "_angwait");
if (strlen(proc_num) > 0)
dl->adjangle = atof(proc_num);
else
dl->adjangle = 1.0f;
// [slipyx] add _falloff
dl->falloff = atoi(ValueForKey(e, "_falloff"));
if (dl->falloff < 0)
dl->falloff = 0;
intensity = FloatForKey(e, "light");
if (!intensity)
intensity = FloatForKey(e, "_light");
if (!intensity)
intensity = 300;
_color = ValueForKey(e, "_color");
if (_color && _color[0]) {
sscanf(_color, "%f %f %f", &dl->color.f[0], &dl->color.f[4], &dl->color.f[8]);
dl->color = SH1_Normalize(dl->color, NULL);
} else
dl->color.f[0] = dl->color.f[4] = dl->color.f[8] = 1.0;
dl->intensity = intensity * entity_scale;
dl->type = emit_point;
target = ValueForKey(e, "target");
if (!strcmp(name, "light_spot") || target[0]) {
dl->type = emit_spotlight;
dl->spotlight.dot = FloatForKey(e, "_cone");
if (!dl->spotlight.dot)
dl->spotlight.dot = 20; // qb: doubled for new calc
dl->spotlight.dot = cos(dl->spotlight.dot / 90 * 3.14159); // qb: doubled for new calc
if (target[0]) {
// point towards target
e2 = FindTargetEntity(target);
if (!e2)
printf("WARNING: light at (%i %i %i) has missing target\n",
(int32_t)dl->origin[0], (int32_t)dl->origin[1], (int32_t)dl->origin[2]);
else {
GetVectorForKey(e2, "origin", dest);
VectorSubtract(dest, dl->origin, dl->spotlight.normal);
VectorNormalize(dl->spotlight.normal, dl->spotlight.normal);
}
} else {
// point down angle
angle = FloatForKey(e, "angle");
if (angle == ANGLE_UP) {
dl->spotlight.normal[0] = dl->spotlight.normal[1] = 0;
dl->spotlight.normal[2] = 1;
} else if (angle == ANGLE_DOWN) {
dl->spotlight.normal[0] = dl->spotlight.normal[1] = 0;
dl->spotlight.normal[2] = -1;
} else {
dl->spotlight.normal[2] = 0;
dl->spotlight.normal[0] = cos(angle / 180 * 3.14159);
dl->spotlight.normal[1] = sin(angle / 180 * 3.14159);
}
}
}
}
//
// surfaces
//
for (i = 0, p = patches; i < num_patches; i++, p++) {
if ((!sun || !p->sky) && p->totallight.f[0] < DIRECT_LIGHT && p->totallight.f[4] < DIRECT_LIGHT && p->totallight.f[8] < DIRECT_LIGHT)
continue;
numdlights++;
dl = malloc(sizeof(directlight_t));
memset(dl, 0, sizeof(*dl));
VectorCopy(p->origin, dl->origin);
if (use_qbsp) {
leafX = RadPointInLeafX(dl->origin);
cluster = leafX->cluster;
dl->leafX = leafX;
} else {
leaf = RadPointInLeaf(dl->origin);
cluster = leaf->cluster;
dl->leaf = leaf;
}
dl->next = directlights[cluster];
directlights[cluster] = dl;
if (sun && p->sky) {
dl->plane = p->plane;
dl->type = emit_sky;
// qb: for sky radiosity, was dl->intensity = 1.0f;
dl->color = SH1_Normalize(p->totallight, &dl->intensity);
dl->intensity *= p->area * direct_scale;
VectorCopy(p->plane->normal, dl->sky.normal);
} else {
dl->type = emit_surface;
dl->color = SH1_Normalize(p->totallight, &dl->intensity);
dl->intensity *= p->area * direct_scale;
VectorCopy(p->plane->normal, dl->surface.normal);
dl->surface.winding = p->winding;
}
p->totallight = SH1_Clear();
}
printf("%i direct lights\n", numdlights);
}
#ifdef WIN32
static inline int32_t lowestCommonNode(int32_t nodeNum1, int32_t nodeNum2)
#else
static inline int32_t lowestCommonNode(int32_t nodeNum1, int32_t nodeNum2)
#endif
{
int32_t child1, tmp, headNode = 0;
if (nodeNum1 > nodeNum2) {
tmp = nodeNum1;
nodeNum1 = nodeNum2;
nodeNum2 = tmp;
}
re_test:
// headNode is guaranteed to be <= nodeNum1 and nodeNum1 is < nodeNum2
if (headNode == nodeNum1)
return headNode;
if (use_qbsp) {
dnode_tx *node;
child1 = (node = dnodesX + headNode)->children[1];
if (nodeNum2 < child1)
// Both nodeNum1 and nodeNum2 are less than child1.
// In this case, child0 is always a node, not a leaf, so we don't need
// to check to make sure.
headNode = node->children[0];
else if (nodeNum1 < child1)
// Child1 sits between nodeNum1 and nodeNum2.
// This means that headNode is the lowest node which contains both
// nodeNum1 and nodeNum2.
return headNode;
else if (child1 > 0)
// Both nodeNum1 and nodeNum2 are greater than child1.
// If child1 is a node, that means it contains both nodeNum1 and
// nodeNum2.
headNode = child1;
else
// Child1 is a leaf, therefore by process of elimination child0 must be
// a node and must contain boste nodeNum1 and nodeNum2.
headNode = node->children[0];
// goto instead of while(1) because it makes the CPU branch predict easier
} else {
dnode_t *node;
child1 = (node = dnodes + headNode)->children[1];
if (nodeNum2 < child1)
headNode = node->children[0];
else if (nodeNum1 < child1)
return headNode;
else if (child1 > 0)
headNode = child1;
else
headNode = node->children[0];
}
goto re_test;
}
/*
=============
LightContributionToPoint
=============
*/
static struct SH1 LightContributionToPoint(directlight_t *l, vec3_t pos, int32_t nodenum,
vec3_t normal, // vec3_t color,
float lightscale2, qboolean *sun_main_once, qboolean *sun_ambient_once) {
struct SH1 result = SH1_Clear();
// check for simple occlusion
int32_t common_node = lowestCommonNode(nodenum, l->nodenum);
if(!noblock && TestLine_r(common_node, pos, l->origin))
return result;
float total_scale = lightscale2 * 0.25;
vec3_t direction, color;
float distance, source_dot, scale;
VectorSubtract(l->origin, pos, direction);
if(DotProduct(direction, normal) <= EQUAL_EPSILON) {
if(l->type == emit_sky)
goto do_sky;
return result;
}
switch(l->type) {
case emit_surface:
case emit_sky: {
int hits = 0;
for(int j = 0; j < l->surface.winding->numpoints; j++) {
VectorSubtract(l->surface.winding->p[j], pos, direction);
distance = VectorNormalize(direction, direction);
source_dot = -DotProduct(direction, l->surface.normal);
if(source_dot <= 0)
continue;
scale = l->intensity / (distance * distance);
SH1_Sample(SH1_Scale(l->color, scale), direction, color);
result = SH1_Add(result, SH1_FromDirectionalLight(direction, color));
hits++;
}
if(hits == 0)
return result;
result = SH1_Scale(result, 1.0f / (float)hits);
if(l->type == emit_sky) {
result = SH1_Scale(result, total_scale);
goto do_sky;
}
break;
}
case emit_point: {
distance = VectorNormalize(direction, direction);
scale = l->falloff == 0 ? (l->intensity - l->wait * distance)
: l->falloff == 1 ? (l->intensity / distance)
: (l->intensity / (distance * distance))
;
if(scale < 0)
return result;
SH1_Sample(SH1_Scale(l->color, scale), direction, color);
result = SH1_FromDirectionalLight(direction, color);
break;
}
case emit_spotlight: {
distance = VectorNormalize(direction, direction);
source_dot = -DotProduct(direction, l->spotlight.normal);
if(source_dot <= l->spotlight.dot)
return result;
scale = (l->intensity - l->wait * distance) * powf(source_dot, 25) * 15;
SH1_Sample(SH1_Scale(l->color, scale), direction, color);
result = SH1_FromDirectionalLight(direction, color);
break;
}
}
return SH1_Scale(result, total_scale);
do_sky:
if(!*sun_ambient_once) {
// TODO
}
if(!*sun_main_once) {
// TODO
}
return result;
}
/*
=============
GatherSampleLight
Lightscale2 is the normalizer for multisampling, -extra cmd line arg
=============
*/
void GatherSampleLight(vec3_t pos, vec3_t normal, struct SH1 **styletable, int32_t offset, int32_t mapsize,
float lightscale2, qboolean *sun_main_once, qboolean *sun_ambient_once, byte *pvs) {
int32_t i;
directlight_t *l;
int32_t nodenum;
struct SH1 colors[MAX_LSTYLES];
memset(colors, 0, sizeof(colors));
// get the PVS for the pos to limit the number of checks
if(!PvsForOrigin(pos, pvs)) {
return;
}
nodenum = PointInNodenum(pos);
for(i = 0; i < dvis->numclusters; i++) {
if(!(pvs[i >> 3] & (1 << (i & 7))))
continue;
for(l = directlights[i]; l; l = l->next) {
struct SH1 sh1 =
LightContributionToPoint(l, pos, nodenum, normal, /* color, */ lightscale2, sun_main_once, sun_ambient_once);
colors[l->style] = SH1_Add(colors[l->style], sh1);
}
}
for(i = 0; i < MAX_LSTYLES; i++) {
// no contribution
if(fabs(colors[i].f[0]) <= EQUAL_EPSILON && fabs(colors[i].f[4]) <= EQUAL_EPSILON && fabs(colors[i].f[8]) <= EQUAL_EPSILON)
continue;
// if this style doesn't have a table yet, allocate one
if(!styletable[i]) {
styletable[i] = malloc(mapsize);
memset(styletable[i], 0, mapsize);
}
styletable[i][offset] = SH1_Add(styletable[i][offset], colors[i]);
}
}
/*
=============
AddSampleToPatch
Take the sample's collected light and
add it back into the apropriate patch
for the radiosity pass.
The sample is added to all patches that might include
any part of it. They are counted and averaged, so it
doesn't generate extra light.
=============
*/
void AddSampleToPatch(vec3_t pos, struct SH1 sh1, int32_t facenum) {
patch_t *patch;
vec3_t mins, maxs;
int32_t i;
if (numbounce == 0)
return;
for (patch = face_patches[facenum]; patch; patch = patch->next) {
// see if the point is in this patch (roughly)
WindingBounds(patch->winding, mins, maxs);
for (i = 0; i < 3; i++) {
if (mins[i] > pos[i] + step)
goto nextpatch;
if (maxs[i] < pos[i] - step)
goto nextpatch;
}
// add the sample to the patch
patch->samples++;
patch->samplelight = SH1_Add(patch->samplelight, sh1);
nextpatch:;
}
}
// qb: phong from vluzacn VHLT
// =====================================================================================
// GetPhongNormal
// =====================================================================================
void GetPhongNormal(int32_t facenum, vec3_t spot, vec3_t phongnormal) {
int32_t j, ne;
int32_t s; // split every edge into two parts
vec3_t facenormal;
// Calculate modified point normal for surface
// Use the edge normals if they are defined. Bend the surface towards the edge normal(s)
// Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal.
// Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric)
// Better third attempt: generate the point normals for all vertices and do baricentric triangulation.
const dface_tx *fx = dfacesX + facenum;
const dface_t *fi = dfaces + facenum;
if (use_qbsp) {
const dplane_t *p = getPlaneFromFaceX(fx);
ne = fx->numedges;
VectorCopy(p->normal, facenormal);
} else {
const dplane_t *p = getPlaneFromFace(fi);
ne = fi->numedges;
VectorCopy(p->normal, facenormal);
}
VectorCopy(facenormal, phongnormal);
for (j = 0; j < ne; j++) {
vec3_t p1;
vec3_t p2;
vec3_t v1;
vec3_t v2;
vec3_t vspot;
unsigned prev_edge;
unsigned next_edge;
int32_t e;
int32_t e1;
int32_t e2;
edgeshare_t *es;
edgeshare_t *es1;
edgeshare_t *es2;
float a1;
float a2;
float aa;
float bb;
float ab;
if (use_qbsp) {
if (j) {
prev_edge = fx->firstedge + ((j + fx->numedges - 1) % fx->numedges);
} else {
prev_edge = fx->firstedge + fx->numedges - 1;
}
if ((j + 1) != fx->numedges) {
next_edge = fx->firstedge + ((j + 1) % fx->numedges);
} else {
next_edge = fx->firstedge;
}
e = dsurfedges[fx->firstedge + j];
e1 = dsurfedges[prev_edge];
e2 = dsurfedges[next_edge];
es = &edgeshare[abs(e)];
es1 = &edgeshare[abs(e1)];
es2 = &edgeshare[abs(e2)];
if ((!es->smooth || es->coplanar) && (!es1->smooth || es1->coplanar) && (!es2->smooth || es2->coplanar)) {
continue;
}
if (e > 0) {
VectorCopy(dvertexes[dedgesX[e].v[0]].point, p1);
VectorCopy(dvertexes[dedgesX[e].v[1]].point, p2);
} else {
VectorCopy(dvertexes[dedgesX[-e].v[1]].point, p1);
VectorCopy(dvertexes[dedgesX[-e].v[0]].point, p2);
}
}
else {
if (j) {
prev_edge = fi->firstedge + ((j + fi->numedges - 1) % fi->numedges);
} else {
prev_edge = fi->firstedge + fi->numedges - 1;
}
if ((j + 1) != fi->numedges) {
next_edge = fi->firstedge + ((j + 1) % fi->numedges);
} else {
next_edge = fi->firstedge;
}
e = dsurfedges[fi->firstedge + j];
e1 = dsurfedges[prev_edge];
e2 = dsurfedges[next_edge];
es = &edgeshare[abs(e)];
es1 = &edgeshare[abs(e1)];
es2 = &edgeshare[abs(e2)];
if ((!es->smooth || es->coplanar) && (!es1->smooth || es1->coplanar) && (!es2->smooth || es2->coplanar)) {
continue;
}
if (e > 0) {
VectorCopy(dvertexes[dedges[e].v[0]].point, p1);
VectorCopy(dvertexes[dedges[e].v[1]].point, p2);
} else {
VectorCopy(dvertexes[dedges[-e].v[1]].point, p1);
VectorCopy(dvertexes[dedges[-e].v[0]].point, p2);
}
}
// Adjust for origin-based models
VectorAdd(p1, face_offset[facenum], p1);
VectorAdd(p2, face_offset[facenum], p2);
for (s = 0; s < 2; s++) {
vec3_t s1, s2;
if (s == 0) {
VectorCopy(p1, s1);
} else {
VectorCopy(p2, s1);
}
VectorAdd(p1, p2, s2); // edge center
VectorScale(s2, 0.5, s2);
VectorSubtract(s1, face_extents[facenum].center, v1);
VectorSubtract(s2, face_extents[facenum].center, v2);
VectorSubtract(spot, face_extents[facenum].center, vspot);
aa = DotProduct(v1, v1);
bb = DotProduct(v2, v2);
ab = DotProduct(v1, v2);
a1 = (bb * DotProduct(v1, vspot) - ab * DotProduct(vspot, v2)) / (aa * bb - ab * ab);
a2 = (DotProduct(vspot, v2) - a1 * ab) / bb;
// Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors)
if (a1 >= -0.01 && a2 >= -0.01) {
// calculate distance from edge to pos
vec3_t n1, n2;
vec3_t temp;
if (es->smooth)
if (s == 0) {
VectorCopy(es->vertex_normal[e > 0 ? 0 : 1], n1);
} else {
VectorCopy(es->vertex_normal[e > 0 ? 1 : 0], n1);
}
else if (s == 0 && es1->smooth) {
VectorCopy(es1->vertex_normal[e1 > 0 ? 1 : 0], n1);
} else if (s == 1 && es2->smooth) {
VectorCopy(es2->vertex_normal[e2 > 0 ? 0 : 1], n1);
} else {
VectorCopy(facenormal, n1);
}
if (es->smooth) {
VectorCopy(es->interface_normal, n2);
} else {
VectorCopy(facenormal, n2);
}
// Interpolate between the center and edge normals based on sample position
// VectorScale(facenormal, 1.0 - a1 - a2, phongnormal);
VectorScale(facenormal, fabs((1.0 - a1) - a2), phongnormal); // qb: eureka... need that fabs()!
VectorScale(n1, a1, temp);
VectorAdd(phongnormal, temp, phongnormal);
VectorScale(n2, a2, temp);
VectorAdd(phongnormal, temp, phongnormal);
VectorNormalize(phongnormal, phongnormal);
break;
}
}
}
}
/**
* @brief Move the incoming sample position towards the surface center and along the
* surface normal to reduce false-positive traces. Test the PVS at the new
* position, returning true if the new point is valid, false otherwise.
*/
static qboolean NudgeSamplePosition(const vec3_t in, const vec3_t normal, const vec3_t center,
vec3_t out, byte *pvs) {
vec3_t dir;
VectorCopy(in, out);
// move into the level using the normal and surface center
VectorSubtract(out, center, dir);
VectorNormalize(dir, dir);
VectorMA(out, sample_nudge, dir, out);
VectorMA(out, sample_nudge, normal, out);
return PvsForOrigin(out, pvs);
}
/*
=============
BuildFacelights
=============
*/
float sampleofs[5][2] =
{{0, 0}, {-0.25, -0.25}, {0.25, -0.25}, {0.25, 0.25}, {-0.25, 0.25}};
void BuildFacelights(int32_t facenum) {
lightinfo_t * liteinfo;//[5];
struct SH1 **styletable;//[MAX_LSTYLES];
int32_t i, j;
float *spot;
patch_t *patch;
int32_t numsamples;
int32_t tablesize;
facelight_t *fl;
qboolean sun_main_once, sun_ambient_once;
vec_t *center;
vec3_t pos;
vec3_t pointnormal;
liteinfo = malloc(sizeof(*liteinfo) * 5);
styletable = malloc(sizeof(*styletable) * MAX_LSTYLES);
if (use_qbsp) {
dface_tx *this_face;
this_face = &dfacesX[facenum];
if (texinfo[this_face->texinfo].flags & (SURF_WARP | SURF_SKY))
goto cleanup; // non-lit texture
memset(styletable, 0, sizeof(*styletable) * MAX_LSTYLES);
if (extrasamples) // set with -extra option
numsamples = 5;
else
numsamples = 1;
for (i = 0; i < numsamples; i++) {
memset(&liteinfo[i], 0, sizeof(liteinfo[i]));
liteinfo[i].surfnum = facenum;
liteinfo[i].faceX = this_face;
VectorCopy(dplanes[this_face->planenum].normal, liteinfo[i].facenormal);
liteinfo[i].facedist = dplanes[this_face->planenum].dist;
if (this_face->side) {
VectorSubtract(vec3_origin, liteinfo[i].facenormal, liteinfo[i].facenormal);
liteinfo[i].facedist = -liteinfo[i].facedist;
}
// get the origin offset for rotating bmodels
VectorCopy(face_offset[facenum], liteinfo[i].modelorg);
CalcFaceVectors(&liteinfo[i]);
CalcFaceExtents(&liteinfo[i]);
CalcPoints(&liteinfo[i], sampleofs[i][0], sampleofs[i][1]);
}
} else {
dface_t *this_face;
this_face = &dfaces[facenum];
if (texinfo[this_face->texinfo].flags & (SURF_WARP | SURF_SKY))
goto cleanup; // non-lit texture
memset(styletable, 0, sizeof(*styletable) * MAX_LSTYLES);
if (extrasamples) // set with -extra option
numsamples = 5;
else
numsamples = 1;
for (i = 0; i < numsamples; i++) {
memset(&liteinfo[i], 0, sizeof(liteinfo[i]));
liteinfo[i].surfnum = facenum;
liteinfo[i].face = this_face;
VectorCopy(dplanes[this_face->planenum].normal, liteinfo[i].facenormal);
liteinfo[i].facedist = dplanes[this_face->planenum].dist;
if (this_face->side) {
VectorSubtract(vec3_origin, liteinfo[i].facenormal, liteinfo[i].facenormal);
liteinfo[i].facedist = -liteinfo[i].facedist;
}
// get the origin offset for rotating bmodels
VectorCopy(face_offset[facenum], liteinfo[i].modelorg);
CalcFaceVectors(&liteinfo[i]);
CalcFaceExtents(&liteinfo[i]);
CalcPoints(&liteinfo[i], sampleofs[i][0], sampleofs[i][1]);
}
}
tablesize = liteinfo[0].numsurfpt * sizeof(struct SH1);
styletable[0] = malloc(tablesize);
memset(styletable[0], 0, tablesize);
fl = &facelight[facenum];
fl->numsamples = liteinfo[0].numsurfpt;
fl->origins = malloc(tablesize);
memcpy(fl->origins, liteinfo[0].surfpt, tablesize);
center = face_extents[facenum].center; // center of the face
for (i = 0; i < liteinfo[0].numsurfpt; i++) {
sun_ambient_once = false;
sun_main_once = false;
for (j = 0; j < numsamples; j++) {
byte pvs[(MAX_MAP_LEAFS_QBSP + 7) / 8];
if (numsamples > 1) {
if (!NudgeSamplePosition(liteinfo[j].surfpt[i], liteinfo[0].facenormal, center, pos, pvs)) {
continue; // not a valid point
}
} else
VectorCopy(liteinfo[j].surfpt[i], pos);
if (smoothing_threshold > 0.0)
GetPhongNormal(facenum, pos, pointnormal); // qb: VHLT
else
VectorCopy(liteinfo[0].facenormal, pointnormal);
GatherSampleLight(pos, pointnormal, styletable, i, tablesize, 1.0 / numsamples,
&sun_main_once, &sun_ambient_once, pvs);
}
// contribute the sample to one or more patches
AddSampleToPatch(liteinfo[0].surfpt[i], styletable[0][i], facenum);
}
// average up the direct light on each patch for radiosity
for (patch = face_patches[facenum]; patch; patch = patch->next) {
if (patch->samples) {
vec3_t normal;
VectorCopy(patch->plane->normal, normal);
patch->samplelight = SH1_Reflect(SH1_Scale(patch->samplelight, 1.0f / patch->samples), normal);
}
}
for (i = 0; i < MAX_LSTYLES; i++) {
if (!styletable[i])
continue;
if (fl->numstyles == MAX_STYLES)
break;
fl->samples[fl->numstyles] = styletable[i];
fl->stylenums[fl->numstyles] = i;
fl->numstyles++;
}
// the light from DIRECT_LIGHTS is sent out, but the
// texture itself should still be full bright
if (face_patches[facenum]->baselight.f[0] >= DIRECT_LIGHT ||
face_patches[facenum]->baselight.f[4] >= DIRECT_LIGHT ||
face_patches[facenum]->baselight.f[8] >= DIRECT_LIGHT) {
for (i = 0; i < liteinfo[0].numsurfpt; i++) {
fl->samples[0][i] = SH1_Add(fl->samples[0][i], face_patches[facenum]->baselight);
}
}
cleanup:
free(liteinfo);
free(styletable);
}
/*
=============
FinalLightFace
Add the indirect lighting on top of the direct
lighting and save into final map format
=============
*/
void FinalLightFace(int32_t facenum) {
int32_t i, j, st;
vec3_t lb;
patch_t *patch;
triangulation_t *trian = NULL;
facelight_t *fl;
float max;
float newmax;
byte *dest_rgb0, *dest_r1, *dest_g1, *dest_b1;
triangle_t *last_valid;
int32_t pfacenum;
vec3_t facemins, facemaxs;
fl = &facelight[facenum];
ThreadLock();
i = lightdatasize;
lightdatasize += fl->numstyles * (fl->numsamples * 3 * 4);
if (lightdatasize > maxdata) {
printf("face %d of %d\n", facenum, numfaces);
Error("lightdatasize %i > maxdata %i", lightdatasize, maxdata);
}
ThreadUnlock();
if (use_qbsp) {
// dface_tx *f;
// f = &dfacesX[facenum];
// if (texinfo[f->texinfo].flags & (SURF_WARP | SURF_SKY))
// return; // non-lit texture
// f->lightofs = i;
// f->styles[0] = 0;
// f->styles[1] = f->styles[2] = f->styles[3] = 0xff;
// //
// // set up the triangulation
// //
// if (numbounce > 0) {
// ClearBounds(facemins, facemaxs);
// for (i = 0; i < f->numedges; i++) {
// int32_t ednum;
// ednum = dsurfedges[f->firstedge + i];
// if (ednum >= 0)
// AddPointToBounds(dvertexes[dedgesX[ednum].v[0]].point,
// facemins, facemaxs);
// else
// AddPointToBounds(dvertexes[dedgesX[-ednum].v[1]].point,
// facemins, facemaxs);
// }
// trian = AllocTriangulation(&dplanes[f->planenum]);
// // for all faces on the plane, add the nearby patches
// // to the triangulation
// for (pfacenum = planelinks[f->side][f->planenum]; pfacenum; pfacenum = facelinks[pfacenum]) {
// for (patch = face_patches[pfacenum]; patch; patch = patch->next) {
// for (i = 0; i < 3; i++) {
// if (facemins[i] - patch->origin[i] > subdiv * 2)
// break;
// if (patch->origin[i] - facemaxs[i] > subdiv * 2)
// break;
// }
// if (i != 3)
// continue; // not needed for this face
// AddPointToTriangulation(patch, trian);
// }
// }
// for (i = 0; i < trian->numpoints; i++)
// memset(trian->edgematrix[i], 0, trian->numpoints * sizeof(trian->edgematrix[0][0]));
// TriangulatePoints(trian);
// }
// //
// // sample the triangulation
// //
// dest = &dlightdata_ptr[f->lightofs];
// if (fl->numstyles > MAXLIGHTMAPS) {
// fl->numstyles = MAXLIGHTMAPS;
// // printf ("face with too many lightstyles: (%f %f %f)\n",
// // face_patches[facenum]->origin[0],
// // face_patches[facenum]->origin[1],
// // face_patches[facenum]->origin[2]
// // );
// }
// for (st = 0; st < fl->numstyles; st++) {
// last_valid = NULL;
// f->styles[st] = fl->stylenums[st];
// for (j = 0; j < fl->numsamples; j++) {
// struct SH1 color = fl->samples[st][j];
// if (numbounce > 0 && st == 0) {
// color = SH1_Add(color, SampleTriangulation(fl->origins + j * 3, trian, &last_valid));
// }
// SH1_Sample(color, f->side ? backplanes[f->planenum].normal : dplanes[f->planenum].normal, lb);
// /*
// * to allow experimenting, ambient and lightscale are not limited
// * to reasonable ranges.
// */
// if (ambient >= -255.0f && ambient <= 255.0f) {
// // add fixed white ambient.
// lb[0] += ambient;
// lb[1] += ambient;
// lb[2] += ambient;
// }
// if (lightscale > 0.0f) {
// // apply lightscale, scale down or up
// lb[0] *= lightscale;
// lb[1] *= lightscale;
// lb[2] *= lightscale;
// }
// // negative values not allowed
// lb[0] = (lb[0] < 0.0f) ? 0.0f : lb[0];
// lb[1] = (lb[1] < 0.0f) ? 0.0f : lb[1];
// lb[2] = (lb[2] < 0.0f) ? 0.0f : lb[2];
// /* qprintf("{%f %f %f}:",lb[0],lb[1],lb[2]);*/
// // determine max of R,G,B
// max = lb[0] > lb[1] ? lb[0] : lb[1];
// max = max > lb[2] ? max : lb[2];
// if (max < 1.0f)
// max = 1.0f;
// // note that maxlight based scaling is per-sample based on
// // highest value of R, G, and B
// // adjust for -maxlight option
// newmax = max;
// if (max > maxlight) {
// newmax = maxlight;
// newmax /= max; // scaling factor 0.0..1.0
// // scale into 0.0..maxlight range
// lb[0] *= newmax;
// lb[1] *= newmax;
// lb[2] *= newmax;
// }
// // and output to 8:8:8 RGB
// *dest++ = (byte)(lb[0] + 0.5);
// *dest++ = (byte)(lb[1] + 0.5);
// *dest++ = (byte)(lb[2] + 0.5);
// }
// }
} else {
// ibsp
dface_t *f;
f = &dfaces[facenum];
if (texinfo[f->texinfo].flags & (SURF_WARP | SURF_SKY))
return; // non-lit texture
f->lightofs = i;
f->styles[0] = 0;
f->styles[1] = f->styles[2] = f->styles[3] = 0xff;
//
// set up the triangulation
//
if (numbounce > 0) {
ClearBounds(facemins, facemaxs);
for (i = 0; i < f->numedges; i++) {
int32_t ednum;
ednum = dsurfedges[f->firstedge + i];
if (ednum >= 0)
AddPointToBounds(dvertexes[dedges[ednum].v[0]].point,
facemins, facemaxs);
else
AddPointToBounds(dvertexes[dedges[-ednum].v[1]].point,
facemins, facemaxs);
}
trian = AllocTriangulation(&dplanes[f->planenum]);
// for all faces on the plane, add the nearby patches
// to the triangulation
for (pfacenum = planelinks[f->side][f->planenum]; pfacenum; pfacenum = facelinks[pfacenum]) {
for (patch = face_patches[pfacenum]; patch; patch = patch->next) {
for (i = 0; i < 3; i++) {
if (facemins[i] - patch->origin[i] > subdiv * 2)
break;
if (patch->origin[i] - facemaxs[i] > subdiv * 2)
break;
}
if (i != 3)
continue; // not needed for this face
AddPointToTriangulation(patch, trian);
}
}
for (i = 0; i < trian->numpoints; i++)
memset(trian->edgematrix[i], 0, trian->numpoints * sizeof(trian->edgematrix[0][0]));
TriangulatePoints(trian);
}
//
// sample the triangulation
//
dest_rgb0 = &dlightdata_ptr[f->lightofs + (fl->numsamples * 3 * 0)];
dest_r1 = &dlightdata_ptr[f->lightofs + (fl->numsamples * 3 * 1)];
dest_g1 = &dlightdata_ptr[f->lightofs + (fl->numsamples * 3 * 2)];
dest_b1 = &dlightdata_ptr[f->lightofs + (fl->numsamples * 3 * 3)];
if (fl->numstyles > MAXLIGHTMAPS) {
fl->numstyles = MAXLIGHTMAPS;
// printf ("face with too many lightstyles: (%f %f %f)\n",
// face_patches[facenum]->origin[0],
// face_patches[facenum]->origin[1],
// face_patches[facenum]->origin[2]
// );
}
for (st = 0; st < fl->numstyles; st++) {
last_valid = NULL;
f->styles[st] = fl->stylenums[st];
for (j = 0; j < fl->numsamples; j++) {
struct SH1 color = fl->samples[st][j];
if (numbounce > 0 && st == 0) {
color = SH1_Add(color, SampleTriangulation(fl->origins + j * 3, trian, &last_valid));
}
/*
* to allow experimenting, ambient and lightscale are not limited
* to reasonable ranges.
*/
if (ambient >= -255.0f && ambient <= 255.0f) {
// add fixed white ambient.
color.f[0] += ambient;
color.f[4] += ambient;
color.f[8] += ambient;
}
if (lightscale > 0.0f) {
// apply lightscale, scale down or up
color = SH1_Scale(color, lightscale);
}
float color_max;
color = SH1_NormalizeMaximum(color, maxlight - 0.5f, &color_max);
for(int component = 0; component < 3; component++) {
if(color.f[component * 4 + 0] > maxlight)
assert(0);
for(int basis = 0; basis < 3; basis++) {
color.f[component * 4 + basis + 1] *= 0.5f;
color.f[component * 4 + basis + 1] += 127.0f;
if(color.f[component * 4 + basis + 1] > 256)
assert(0);
}
}
// and output to 8:8:8 RGB
*dest_rgb0++ = (byte)(color.f[0] + 0.5f);
*dest_r1++ = (byte)(color.f[1] + 0.5f);
*dest_r1++ = (byte)(color.f[2] + 0.5f);
*dest_r1++ = (byte)(color.f[3] + 0.5f);
*dest_rgb0++ = (byte)(color.f[4] + 0.5f);
*dest_g1++ = (byte)(color.f[5] + 0.5f);
*dest_g1++ = (byte)(color.f[6] + 0.5f);
*dest_g1++ = (byte)(color.f[7] + 0.5f);
*dest_rgb0++ = (byte)(color.f[8] + 0.5f);
*dest_b1++ = (byte)(color.f[9] + 0.5f);
*dest_b1++ = (byte)(color.f[10] + 0.5f);
*dest_b1++ = (byte)(color.f[11] + 0.5f);
}
}
}
if (numbounce > 0)
FreeTriangulation(trian);
}
#include "qrad.h"
extern qboolean dumppatches;
extern qboolean nopvs;
extern qboolean save_trace;
extern float patch_cutoff;
extern char inbase[32];
extern char outbase[32];
void RAD_ProcessArgument(const char * arg);
int32_t main(int32_t argc, char **argv) {
int32_t i;
char tgamedir[1024] = "", tbasedir[1024] = "", tmoddir[1024] = "";
printf("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4rad >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("radiosity compiler build " __DATE__ "\n");
verbose = false;
numthreads = -1;
maxdata = DEFAULT_MAP_LIGHTING;
step = LMSTEP;
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-dump"))
dumppatches = true;
else if (!strcmp(argv[i], "-bounce")) {
numbounce = atoi(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-v")) {
verbose = true;
} else if (!strcmp(argv[i], "-help")) {
printf("4rad with automatic phong and QBSP extended limit support.\n"
"Usage: 4rad [options] [mapname]\n\n"
" -ambient #: Minimum light level.\n"
" range: 0 to 255.\n"
" -moddir [path]: Set a mod directory. Default is parent dir of map file.\n"
" -basedir [path]: Set the directory for assets not in moddir. Default is moddir.\n"
" -gamedir [path]: Set game directory, the folder with game executable.\n"
" -bounce #: Max number of light bounces for radiosity.\n"
" -dice: Subdivide patches with a global grid rather than per patch.\n"
" -direct #: Direct light scale factor.\n"
" -entity #: Entity light scale factor.\n"
" -extra: Use extra samples to smooth lighting.\n"
" -maxdata #: 2097152 is default max. Not needed for QBSP format.\n"
" Increase requires a supporting engine.\n"
" -maxlight #: Maximium light level.\n"
" range: 0 to 255.\n"
" -noedgefix: disable dark edges at sky fix. More of a hack, really.\n"
" -nudge #: Nudge factor for samples. Distance fraction from center.\n"
" -saturate #: Saturation factor of light bounced off surfaces.\n"
" -scale #: Light intensity multiplier.\n"
" -smooth #: Threshold angle (# and 180deg - #) for phong smoothing.\n"
" -subdiv (or -chop) #: Maximum patch size. Default: 64\n"
" -sunradscale #: Sky light intensity scale when sun is active.\n"
" -threads #: Number of CPU cores to use.\n"
"Debugging tools:\n"
" -dump: Dump patches to a text file.\n"
" -noblock: Brushes don't block lighting path.\n"
" -nopvs: Don't do potential visibility set check.\n"
" -savetrace: Test traces and report errors.\n"
" -tmpin: Read from 'tmp' directory.\n"
" -tmpout: Write to 'tmp' directory.\n"
" -v: Verbose output for debugging.\n\n");
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4rad HELP >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
exit(1);
}
else if (!strcmp(argv[i], "-extra")) {
extrasamples = true;
printf("extrasamples = true\n");
} else if (!strcmp(argv[i], "-noedgefix")) // qb: light warp surfaces
{
noedgefix = true;
printf("no edge fix = true\n");
} else if (!strcmp(argv[i], "-dice")) {
dicepatches = true;
printf("dicepatches = true\n");
} else if (!strcmp(argv[i], "-threads")) {
numthreads = atoi(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-maxdata")) { // qb: allows increase for some engines
maxdata = atoi(argv[i + 1]);
i++;
if (maxdata > DEFAULT_MAP_LIGHTING) {
printf("lighting maxdata (%i) exceeds typical limit (%i).\n", maxdata, DEFAULT_MAP_LIGHTING);
}
}
// qb: set gamedir, moddir, and basedir
else if (!strcmp(argv[i], "-gamedir")) {
strcpy(tgamedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-basedir")) {
strcpy(tbasedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-moddir")) {
strcpy(tmoddir, argv[i + 1]);
i++;
}
else if ((!strcmp(argv[i], "-chop")) || (!strcmp(argv[i], "-subdiv"))) {
subdiv = atoi(argv[i + 1]);
if (subdiv < 16) {
subdiv = 16;
printf("subdiv set to minimum: 16\n");
} else if (subdiv > 1024) {
subdiv = 1024;
printf("subdiv set to maximum: 1024\n");
}
i++;
}
else if (!strcmp(argv[i], "-scale")) {
lightscale = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-sunradscale")) {
sunradscale = atof(argv[i + 1]);
if (sunradscale < 0) {
sunradscale = 0;
printf("sunradscale set to minimum: 0\n");
}
printf("sunradscale = %f\n", sunradscale);
i++;
} else if (!strcmp(argv[i], "-saturation")) {
saturation = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-radmin")) {
patch_cutoff = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-direct")) {
direct_scale *= atof(argv[i + 1]);
// printf ("direct light scaling at %f\n", direct_scale);
i++;
} else if (!strcmp(argv[i], "-entity")) {
entity_scale *= atof(argv[i + 1]);
// printf ("entity light scaling at %f\n", entity_scale);
i++;
} else if (!strcmp(argv[i], "-nopvs")) {
nopvs = true;
printf("nopvs = true\n");
} else if (!strcmp(argv[i], "-nopvs")) {
nopvs = true;
printf("nopvs = true\n");
} else if (!strcmp(argv[i], "-noblock")) {
noblock = true;
printf("noblock = true\n");
} else if (!strcmp(argv[i], "-smooth")) {
// qb: limit range
smoothing_value = BOUND(0, atof(argv[i + 1]), 90);
i++;
} else if (!strcmp(argv[i], "-nudge")) {
sample_nudge = atof(argv[i + 1]);
// qb: nah, go crazy. sample_nudge = BOUND(0, sample_nudge, 1.0);
i++;
} else if (!strcmp(argv[i], "-ambient")) {
ambient = BOUND(0, atof(argv[i + 1]), 255);
i++;
} else if (!strcmp(argv[i], "-savetrace")) {
save_trace = true;
printf("savetrace = true\n");
} else if (!strcmp(argv[i], "-maxlight")) {
maxlight = BOUND(0, atof(argv[i + 1]), 255);
i++;
} else if (!strcmp(argv[i], "-tmpin"))
strcpy(inbase, "/tmp");
else if (!strcmp(argv[i], "-tmpout"))
strcpy(outbase, "/tmp");
else
break;
}
if (i != argc - 1) {
printf("Usage: 4rad [options] [mapname]\n"
" -ambient # -bounce #\n"
" -dice -direct # -entity #\n"
" -extra -help -maxdata #\n"
" -maxlight # -noedgefix -nudge #\n"
" -saturate # -scale # -smooth #\n"
" -subdiv -sunradscale # -threads #\n"
" -gamedir [path] -basedir [path] -moddir [path]\n"
"Debugging tools:\n"
" -dump -noblock -nopvs\n"
" -savetrace -tmpin -tmpout\n"
" -v (verbose)\n\n");
exit(1);
}
printf("sample nudge: %f\n", sample_nudge);
printf("ambient : %f\n", ambient);
printf("scale : %f\n", lightscale);
printf("maxlight : %f\n", maxlight);
printf("entity : %f\n", entity_scale);
printf("direct : %f\n", direct_scale);
printf("saturation : %f\n", saturation);
printf("bounce : %d\n", numbounce);
printf("radmin : %f\n", patch_cutoff);
printf("subdiv : %f\n", subdiv);
printf("smooth angle: %f\n", smoothing_value);
printf("nudge : %f\n", sample_nudge);
printf("threads : %d\n", numthreads);
ThreadSetDefault();
smoothing_threshold = (float)cos(smoothing_value * (Q_PI / 180.0));
SetQdirFromPath(argv[i]);
if (strcmp(tmoddir, "")) {
strcpy(moddir, tmoddir);
Q_pathslash(moddir);
strcpy(basedir, moddir);
}
if (strcmp(tbasedir, "")) {
strcpy(basedir, tbasedir);
Q_pathslash(basedir);
if (!strcmp(tmoddir, ""))
strcpy(moddir, basedir);
}
if (strcmp(tgamedir, "")) {
strcpy(gamedir, tgamedir);
Q_pathslash(gamedir);
}
// qb: display dirs
printf("moddir = %s\n", moddir);
printf("basedir = %s\n", basedir);
printf("gamedir = %s\n", gamedir);
RAD_ProcessArgument(argv[i]);
return 0;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qrad.h"
/*
NOTES
-----
every surface must be divided into at least two patches each axis
*/
patch_t *face_patches[MAX_MAP_FACES_QBSP];
entity_t *face_entity[MAX_MAP_FACES_QBSP];
patch_t *patches; //[MAX_PATCHES_QBSP];
unsigned num_patches;
int32_t num_smoothing; // qb: number of phong hits
struct SH1 radiosity[MAX_PATCHES_QBSP]; // light leaving a patch
struct SH1 illumination[MAX_PATCHES_QBSP]; // light arriving at a patch
vec3_t face_offset[MAX_MAP_FACES_QBSP]; // for rotating bmodels
dplane_t backplanes[MAX_MAP_PLANES_QBSP];
extern char inbase[32], outbase[32];
int32_t fakeplanes; // created planes for origin offset
int32_t numbounce = 4; // default was 8
qboolean noblock = false; // when true, disables occlusion testing on light rays
qboolean extrasamples = false;
qboolean dicepatches = false;
qboolean noedgefix = false;
int32_t memory = false;
float patch_cutoff = 0.0f; // set with -radmin 0.0..1.0, see MakeTransfers()
float subdiv = 64;
qboolean dumppatches;
void BuildFaceExtents(void); // qb: from quemap
int32_t TestLine(vec3_t start, vec3_t stop);
float smoothing_threshold; // qb: phong from VHLT
float smoothing_value = DEFAULT_SMOOTHING_VALUE;
float sample_nudge = DEFAULT_NUDGE_VALUE; // qb: adjustable nudge for multisample
/*
* 2010-09 Notes
* These variables are somewhat confusing. The floating point color values are
* in the range 0..255 (floating point color should be 0.0 .. 1.0 IMO.)
* The following may or may not be precisely correct.
* (There are other variables, surface flags, etc., affecting lighting. What
* they do, or whether they work at all is "to be determined")
*
* see lightmap.c:FinalLightFace()
* sequence: ambient is added, lightscale is applied, RGB is "normalized" to
* 0..255 range, grayscale is applied, RGB is clamped to maxlight.
*
* ambient:
* set with -ambient option, 0..255 (but only small numbers are useful)
* adds the same value to R, G & B
* default is 0
*
* lightscale:
* set with -scale option, 0.0..1.0
* scales lightmap globally.
*
* saturation:
* set with -saturation, 0.0..1.0 //qb: does higher than 1 work?
* proportionally saturation texture reflectivity
*
* direct_scale:
* set with -direct option, 0.0..1.0
* controls reflection from emissive surfaces, i.e. brushes with a light value
* research indicates it is not the usual practice to include this in
* radiosity, so the default should be 0.0. (Would be nice to have this
* a per-surface option where surfaces are used like point lights.)
*
* entity_scale:
* set with -entity option, 0.0..1.0
* controls point light radiosity, i.e. light entities.
* default is 1.0, no attenuation of point lights
*
*
*/
#include <assert.h>
float ambient = 0.0f;
float lightscale = 1.0f;
float maxlight = 255.0f;
// qboolean nocolor = false;
float grayscale = 0.0f;
float saturation = 1.0f; // qb: change desaturate to saturation
float direct_scale = 1.0f;
float entity_scale = 1.0f;
/*
* 2010-09 Notes:
* These are controlled by setting keys in the worldspawn entity. A light
* entity targeting an info_null entity is used to determine the vector for
* the sun directional lighting; variable name "sun_pos" is a misnomer, i think.
* Example:
* "_sun" "sun_target" # activates sun
* "_sun_color" "1.0 1.0 0.0" # for yellow, sets sun_alt_color true
* "_sun_light" "50" # light value, variable is sun_main
* "_sun_ambient" "2" # an ambient light value in the sun color. variable is sun_ambient
*
* It might or might not be the case:
* if there is no info_null for the light entity with the "sun_target" to
* target, then {0,0,0} is used for the target. If _sun_color is not specified
* in the .map, the color of the light entity is used.
*/
qboolean sun = false;
qboolean sun_alt_color = false;
vec3_t sun_pos = {0.0f, 0.0f, 1.0f};
float sun_main = 250.0f;
float sun_ambient = 0.0f;
vec3_t sun_color = {1, 1, 1};
qboolean nopvs;
qboolean save_trace = false;
extern char source[1024];
/*
===================================================================
MISC
===================================================================
*/
/*
=============
MakeBackplanes
=============
*/
void MakeBackplanes(void) {
int32_t i;
for(i = 0; i < numplanes; i++) {
backplanes[i].dist = -dplanes[i].dist;
VectorSubtract(vec3_origin, dplanes[i].normal, backplanes[i].normal);
}
}
int32_t leafparents[MAX_MAP_LEAFS_QBSP];
int32_t nodeparents[MAX_MAP_NODES_QBSP];
/*
=============
MakeParents
=============
*/
void MakeParents(int32_t nodenum, int32_t parent) {
int32_t i, j;
nodeparents[nodenum] = parent;
if(use_qbsp) {
dnode_tx *node;
node = &dnodesX[nodenum];
for(i = 0; i < 2; i++) {
j = node->children[i];
if(j < 0)
leafparents[-j - 1] = nodenum;
else
MakeParents(j, nodenum);
}
} else {
dnode_t *node;
node = &dnodes[nodenum];
for(i = 0; i < 2; i++) {
j = node->children[i];
if(j < 0)
leafparents[-j - 1] = nodenum;
else
MakeParents(j, nodenum);
}
}
}
/*
===================================================================
TRANSFER SCALES
===================================================================
*/
int32_t PointInLeafnum(vec3_t point) {
int32_t nodenum;
vec_t dist;
dplane_t *plane;
nodenum = 0;
if(use_qbsp) {
dnode_tx *node;
while(nodenum >= 0) {
node = &dnodesX[nodenum];
plane = &dplanes[node->planenum];
dist = DotProduct(point, plane->normal) - plane->dist;
if(dist > 0)
nodenum = node->children[0];
else
nodenum = node->children[1];
}
} else {
dnode_t *node;
while(nodenum >= 0) {
node = &dnodes[nodenum];
plane = &dplanes[node->planenum];
dist = DotProduct(point, plane->normal) - plane->dist;
if(dist > 0)
nodenum = node->children[0];
else
nodenum = node->children[1];
}
}
return -nodenum - 1;
}
dleaf_tx *RadPointInLeafX(vec3_t point) {
int32_t num;
num = PointInLeafnum(point);
return &dleafsX[num];
}
dleaf_t *RadPointInLeaf(vec3_t point) {
int32_t num;
num = PointInLeafnum(point);
return &dleafs[num];
}
qboolean PvsForOrigin(vec3_t org, byte *pvs) {
if(!visdatasize) {
memset(pvs, 255, (numleafs + 7) / 8);
return true;
}
if(use_qbsp) {
dleaf_tx *leaf;
leaf = RadPointInLeafX(org);
if(leaf->cluster == -1)
return false; // in solid leaf
DecompressVis(dvisdata + dvis->bitofs[leaf->cluster][DVIS_PVS], pvs);
} else {
dleaf_t *leaf;
leaf = RadPointInLeaf(org);
if(leaf->cluster == -1)
return false; // in solid leaf
DecompressVis(dvisdata + dvis->bitofs[leaf->cluster][DVIS_PVS], pvs);
}
return true;
}
typedef struct tnode_s {
int32_t type;
vec3_t normal;
float dist;
int32_t children[2];
int32_t pad;
} tnode_t;
extern tnode_t *tnodes;
int32_t total_transfer;
static long total_mem;
static int32_t first_transfer = 1;
#define MAX_TRACE_BUF ((MAX_PATCHES_QBSP + 7) / 8)
#define TRACE_BYTE(x) (((x) + 7) >> 3)
#define TRACE_BIT(x) ((x)&0x1F)
static byte trace_buf[MAX_TRACE_BUF + 1];
static byte trace_tmp[MAX_TRACE_BUF + 1];
static int32_t trace_buf_size;
int32_t CompressBytes(int32_t size, byte *source, byte *dest) {
int32_t j;
int32_t rep;
byte *dest_p;
dest_p = dest + 1;
for(j = 0; j < size; j++) {
*dest_p++ = source[j];
if((dest_p - dest - 1) >= size) {
memcpy(dest + 1, source, size);
dest[0] = 0;
return size + 1;
}
if(source[j])
continue;
rep = 1;
for(j++; j < size; j++)
if(source[j] || rep == 255)
break;
else
rep++;
*dest_p++ = rep;
if((dest_p - dest - 1) >= size) {
memcpy(dest + 1, source, size);
dest[0] = 0;
return size + 1;
}
j--;
}
dest[0] = 1;
return dest_p - dest;
}
void DecompressBytes(int32_t size, byte *in, byte *decompressed) {
int32_t c;
byte *out;
if(in[0] == 0) // not compressed
{
memcpy(decompressed, in + 1, size);
return;
}
out = decompressed;
in++;
do {
if(*in) {
*out++ = *in++;
continue;
}
c = in[1];
if(!c)
Error("DecompressBytes: 0 repeat");
in += 2;
while(c) {
*out++ = 0;
c--;
}
} while(out - decompressed < size);
}
static int32_t trace_bytes = 0;
#ifdef WIN32
static inline int32_t lowestCommonNode(int32_t nodeNum1, int32_t nodeNum2)
#else
static inline int32_t lowestCommonNode(int32_t nodeNum1, int32_t nodeNum2)
#endif
{
int32_t child1, tmp, headNode = 0;
if(nodeNum1 > nodeNum2) {
tmp = nodeNum1;
nodeNum1 = nodeNum2;
nodeNum2 = tmp;
}
re_test:
// headNode is guaranteed to be <= nodeNum1 and nodeNum1 is < nodeNum2
if(headNode == nodeNum1)
return headNode;
if(use_qbsp) {
dnode_tx *node;
child1 = (node = dnodesX + headNode)->children[1];
if(nodeNum2 < child1)
// Both nodeNum1 and nodeNum2 are less than child1.
// In this case, child0 is always a node, not a leaf, so we don't need
// to check to make sure.
headNode = node->children[0];
else if(nodeNum1 < child1)
// Child1 sits between nodeNum1 and nodeNum2.
// This means that headNode is the lowest node which contains both
// nodeNum1 and nodeNum2.
return headNode;
else if(child1 > 0)
// Both nodeNum1 and nodeNum2 are greater than child1.
// If child1 is a node, that means it contains both nodeNum1 and
// nodeNum2.
headNode = child1;
else
// Child1 is a leaf, therefore by process of elimination child0 must be
// a node and must contain boste nodeNum1 and nodeNum2.
headNode = node->children[0];
// goto instead of while(1) because it makes the CPU branch predict easier
} else {
dnode_t *node;
child1 = (node = dnodes + headNode)->children[1];
if(nodeNum2 < child1)
headNode = node->children[0];
else if(nodeNum1 < child1)
return headNode;
else if(child1 > 0)
headNode = child1;
else
headNode = node->children[0];
}
goto re_test;
}
void MakeTransfers(int32_t i) {
int32_t j;
vec3_t delta;
vec_t dist, inv_dist = 0, scale;
float trans;
int32_t itrans;
patch_t *patch, *patch2;
float total, inv_total;
dplane_t plane;
vec3_t origin;
float *transfers; //[MAX_PATCHES_QBSP];
int32_t s;
int32_t itotal;
byte pvs[(MAX_MAP_LEAFS_QBSP + 7) / 8];
int32_t cluster;
int32_t calc_trace, test_trace;
patch = patches + i;
total = 0;
VectorCopy(patch->origin, origin);
plane = *patch->plane;
if(!PvsForOrigin(patch->origin, pvs))
return;
if(patch->area == 0)
return;
// find out which patches will collect light from patch
patch->numtransfers = 0;
calc_trace = (save_trace && memory && first_transfer);
test_trace = (save_trace && memory && !first_transfer);
if(calc_trace) {
memset(trace_buf, 0, trace_buf_size);
} else if(test_trace) {
DecompressBytes(trace_buf_size, patch->trace_hit, trace_buf);
}
transfers = malloc(sizeof(*transfers) * num_patches);
for(j = 0, patch2 = patches; j < num_patches; j++, patch2++) {
transfers[j] = 0;
if(j == i)
continue;
if(patch2->area == 0)
continue;
// check pvs bit
if(!nopvs) {
cluster = patch2->cluster;
if(cluster == -1)
continue;
if(!(pvs[cluster >> 3] & (1 << (cluster & 7))))
continue; // not in pvs
}
if(test_trace && !(trace_buf[TRACE_BYTE(j)] & TRACE_BIT(j)))
continue;
// calculate vector
VectorSubtract(patch2->origin, origin, delta);
dist = VectorNormalize(delta, delta);
if(dist == 0) {
continue;
} else {
dist = sqrt(dist);
inv_dist = 1.0f / dist;
delta[0] *= inv_dist;
delta[1] *= inv_dist;
delta[2] *= inv_dist;
}
// relative angles
float leaving_dot = DotProduct(delta, plane.normal);
float incoming_dot = -DotProduct(delta, patch2->plane->normal);
if(leaving_dot * incoming_dot <= 0)
continue;
// check exact transfer
scale = leaving_dot * incoming_dot;
trans = scale * patch2->area * inv_dist * inv_dist;
if(trans > patch_cutoff) {
if(!test_trace && !noblock && patch2->nodenum != patch->nodenum &&
TestLine_r(lowestCommonNode(patch->nodenum, patch2->nodenum), patch->origin, patch2->origin)) {
transfers[j] = 0;
continue;
}
transfers[j] = trans;
total += trans;
patch->numtransfers++;
}
}
// copy the transfers out and normalize
// total should be somewhere near PI if everything went right
// because partial occlusion isn't accounted for, and nearby
// patches have underestimated form factors, it will usually
// be higher than PI
if(patch->numtransfers) {
transfer_t *t;
if(patch->numtransfers < 0 || patch->numtransfers > MAX_PATCHES_QBSP)
Error("Weird numtransfers");
s = patch->numtransfers * sizeof(transfer_t);
patch->transfers = malloc(s);
total_mem += s;
if(!patch->transfers)
Error("Memory allocation failure");
//
// normalize all transfers so all of the light
// is transfered to the surroundings
//
t = patch->transfers;
itotal = 0;
inv_total = 65536.0f / total;
for(j = 0; j < num_patches; j++) {
if(transfers[j] <= 0)
continue;
itrans = transfers[j] * inv_total;
itotal += itrans;
t->transfer = itrans;
t->patch = j;
t++;
if(calc_trace) {
trace_buf[TRACE_BYTE(j)] |= TRACE_BIT(j);
}
}
}
if(calc_trace) {
j = CompressBytes(trace_buf_size, trace_buf, trace_tmp);
patch->trace_hit = malloc(j);
memcpy(patch->trace_hit, trace_tmp, j);
trace_bytes += j;
}
// don't bother locking around this. not that important.
total_transfer += patch->numtransfers;
// cleanup:
free(transfers);
}
/*
=============
FreeTransfers
=============
*/
void FreeTransfers(void) {
int32_t i;
for(i = 0; i < num_patches; i++) {
if(!memory) {
free(patches[i].transfers);
patches[i].transfers = NULL;
} else if(patches[i].trace_hit != NULL) {
free(patches[i].trace_hit);
patches[i].trace_hit = NULL;
}
}
}
//===================================================================
/*
=============
WriteWorld
=============
*/
void WriteWorld(char *name) {
int32_t i, j;
FILE *out;
patch_t *patch;
winding_t *w;
out = fopen(name, "w");
if(!out)
Error("Couldn't open %s", name);
for(j = 0, patch = patches; j < num_patches; j++, patch++) {
w = patch->winding;
fprintf(out, "%i\n", w->numpoints);
for(i = 0; i < w->numpoints; i++) {
fprintf(out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", w->p[i][0], w->p[i][1], w->p[i][2], patch->totallight.f[0],
patch->totallight.f[4], patch->totallight.f[8]);
}
fprintf(out, "\n");
}
fclose(out);
}
//==============================================================
/*
=============
CollectLight
=============
*/
float CollectLight(void) {
int32_t i, j;
patch_t *patch;
vec_t total;
total = 0;
for(i = 0, patch = patches; i < num_patches; i++, patch++) {
// skys never collect light, it is just dropped
if(patch->sky) {
radiosity[i] = SH1_Clear();
continue;
}
vec3_t normal;
VectorCopy(patch->plane->normal, normal);
struct SH1 illum = SH1_Reflect(illumination[i], normal);
patch->totallight = SH1_Add(patch->totallight, SH1_Scale(illum, 1.0f / patch->area));
radiosity[i] = SH1_ColorScale(illum, patch->reflectivity);
total += radiosity[i].f[0] + radiosity[i].f[4] + radiosity[i].f[8];
}
memset(illumination, 0, sizeof(illumination[0]) * num_patches);
return total;
}
/*
=============
ShootLight
Send light out to other patches
Run multi-threaded
=============
*/
int32_t c_progress;
int32_t p_progress;
void ShootLight(int32_t patchnum) {
int32_t k, l;
transfer_t *trans;
int32_t num;
patch_t *patch;
// vec3_t send;
// this is the amount of light we are distributing
// prescale it so that multiplying by the 16 bit
// transfer values gives a proper output value
struct SH1 send = SH1_Scale(radiosity[patchnum], 1.0f / 0x10000);
patch = &patches[patchnum];
if(memory) {
c_progress = 10 * patchnum / num_patches;
if(c_progress != p_progress) {
printf("%i...", c_progress);
p_progress = c_progress;
}
MakeTransfers(patchnum);
}
trans = patch->transfers;
num = patch->numtransfers;
for(k = 0; k < num; k++, trans++) {
vec3_t direction, color;
VectorSubtract(patches[trans->patch].origin, patch->origin, direction);
VectorNormalize(direction, direction);
SH1_Sample(send, direction, color);
illumination[trans->patch] =
SH1_Add(illumination[trans->patch], SH1_Scale(SH1_FromDirectionalLight(direction, color), trans->transfer));
// for (l = 0; l < 3; l++)
// illumination[trans->patch][l] += send[l] * trans->transfer;
}
if(memory) {
free(patches[patchnum].transfers);
patches[patchnum].transfers = NULL;
}
}
/*
=============
BounceLight
=============
*/
void BounceLight(void) {
int32_t i, j, start = 0, stop;
float added;
char name[64];
patch_t *p;
for(i = 0; i < num_patches; i++) {
p = &patches[i];
// for (j = 0; j < 3; j++) {
// p->totallight[j] = p->samplelight[j];
// radiosity[i][j] = p->samplelight[j] * p->reflectivity[j] * p->area;
// }
radiosity[i] = SH1_Scale(SH1_ColorScale(p->samplelight, p->reflectivity), p->area);
}
if(memory)
trace_buf_size = (num_patches + 7) / 8;
for(i = 0; i < numbounce; i++) {
if(memory) {
p_progress = -1;
start = I_FloatTime();
printf("[%d remaining] ", numbounce - i);
total_mem = 0;
}
RunThreadsOnIndividual(num_patches, false, ShootLight);
first_transfer = 0;
if(memory) {
stop = I_FloatTime();
printf(" (%i)\n", stop - start);
}
added = CollectLight();
qprintf("bounce:%i added:%f\n", i, added);
if(dumppatches && (i == 0 || i == numbounce - 1)) {
sprintf(name, "bounce%i.txt", i);
WriteWorld(name);
}
}
}
//==============================================================
void CheckPatches(void) {
int32_t i;
patch_t *patch;
for(i = 0; i < num_patches; i++) {
patch = &patches[i];
if(patch->totallight.f[0] < 0 || patch->totallight.f[4] < 0 || patch->totallight.f[8] < 0)
Error("negative patch totallight\n");
}
}
/*
=============
RadWorld
=============
*/
void RadWorld(void) {
if(numnodes == 0 || numfaces == 0)
Error("Empty map");
MakeBackplanes();
MakeParents(0, -1);
MakeTnodes(&dmodels[0]);
// turn each face into a single patch
MakePatches();
// subdivide patches to a maximum dimension
SubdividePatches();
BuildFaceExtents(); // qb: from quetoo
// create directlights out of patches and lights
CreateDirectLights();
PairEdges(); // qb: moved here for phong
// build initial facelights
RunThreadsOnIndividual(numfaces, true, BuildFacelights);
if(numbounce > 0) {
// build transfer lists
if(!memory) {
RunThreadsOnIndividual(num_patches, true, MakeTransfers);
qprintf("transfer lists: %5.1f megs\n", (float)total_transfer * sizeof(transfer_t) / (1024 * 1024));
}
numthreads = 1;
// spread light around
BounceLight();
FreeTransfers();
CheckPatches();
} else
numthreads = 1;
if(memory) {
printf("Non-memory conservation would require %4.1f\n", (float)(total_mem - trace_bytes) / 1048576.0f);
printf(" megabytes more memory then currently used\n");
}
// blend bounced light into direct light and save
LinkPlaneFaces();
lightdatasize = 0;
RunThreadsOnIndividual(numfaces, true, FinalLightFace);
}
/*
========
main
light modelfile
========
*/
void RAD_ProcessArgument(const char *arg) {
double start, end;
char *name;
char *tgamedir = "";
char *tbasedir = "";
char *tmoddir = "";
patches = malloc(sizeof(*patches) * MAX_PATCHES_QBSP);
start = I_FloatTime();
strcpy(source, ExpandArg(arg));
StripExtension(source);
DefaultExtension(source, ".bsp");
// ReadLightFile ();
name = (char *)malloc(strlen(inbase) + strlen(source) + 1);
sprintf(name, "%s%s", inbase, source);
printf("reading %s\n", name);
LoadBSPFile(name);
dlightdata_ptr = dlightdata;
if(use_qbsp) {
maxdata = MAX_MAP_LIGHTING_QBSP;
step = QBSP_LMSTEP;
}
ParseEntities();
CalcTextureReflectivity();
if(!visdatasize) {
printf("No vis information, direct lighting only.\n");
numbounce = 0;
ambient = 0.1;
}
RadWorld();
if(smoothing_threshold > 0.0) {
printf("Smoothing edges found: %i\n", num_smoothing);
}
sprintf(name, "%s%s", outbase, source);
printf("writing %s\n", name);
WriteBSPFile(name);
end = I_FloatTime();
printf("%5.0f seconds elapsed\n", end - start);
printf("%i bytes light data used of %i max.\n", lightdatasize, maxdata);
PrintBSPFileSizes();
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END 4rad >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
free(patches);
patches = NULL;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
int32_t c_nofaces;
int32_t c_facenodes;
/*
=========================================================
ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES
=========================================================
*/
int32_t planeused[MAX_MAP_PLANES_QBSP];
/*
============
EmitPlanes
There is no oportunity to discard planes, because all of the original
brushes will be saved in the map.
============
*/
void EmitPlanes(void) {
int32_t i;
dplane_t *dp;
plane_t *mp;
mp = mapplanes;
for (i = 0; i < nummapplanes; i++, mp++) {
dp = &dplanes[numplanes];
VectorCopy(mp->normal, dp->normal);
dp->dist = mp->dist;
dp->type = mp->type;
numplanes++;
}
}
//========================================================
void EmitMarkFace(dleaf_t *leaf_p, face_t *f) {
int32_t i;
int32_t facenum;
while (f->merged)
f = f->merged;
if (f->split[0]) {
EmitMarkFace(leaf_p, f->split[0]);
EmitMarkFace(leaf_p, f->split[1]);
return;
}
facenum = f->outputnumber;
if (facenum == -1)
return; // degenerate face
if (facenum < 0 || facenum >= numfaces)
Error("Bad leafface");
for (i = leaf_p->firstleafface; i < numleaffaces; i++)
if (dleaffaces[i] == facenum)
break; // merged out face
if (i == numleaffaces) {
if (numleaffaces >= MAX_MAP_LEAFFACES)
Error("MAX_MAP_LEAFFACES");
dleaffaces[numleaffaces] = facenum;
numleaffaces++;
}
}
void EmitMarkFaceX(dleaf_tx *leaf_p, face_t *f) {
int32_t i;
int32_t facenum;
while (f->merged)
f = f->merged;
if (f->split[0]) {
EmitMarkFaceX(leaf_p, f->split[0]);
EmitMarkFaceX(leaf_p, f->split[1]);
return;
}
facenum = f->outputnumber;
if (facenum == -1)
return; // degenerate face
if (facenum < 0 || facenum >= numfaces)
Error("Bad leafface");
for (i = leaf_p->firstleafface; i < numleaffaces; i++)
if (dleaffacesX[i] == facenum)
break; // merged out face
if (i == numleaffaces) {
if (numleaffaces >= MAX_MAP_LEAFFACES_QBSP)
Error("MAX_MAP_LEAFFACES_QBSP");
dleaffacesX[numleaffaces] = facenum;
numleaffaces++;
}
}
/*
==================
EmitLeaf
==================
*/
void EmitLeaf(node_t *node) {
portal_t *p;
int32_t s;
face_t *f;
bspbrush_t *b;
int32_t i;
int32_t brushnum;
// emit a leaf
if (use_qbsp) // qb: qbsp
{
dleaf_tx *leaf_p;
if (numleafs >= MAX_MAP_LEAFS_QBSP)
Error("MAX_MAP_LEAFS_QBSP");
leaf_p = &dleafsX[numleafs];
numleafs++;
leaf_p->contents = node->contents;
leaf_p->cluster = node->cluster;
leaf_p->area = node->area;
//
// write bounding box info
//
VectorCopy(node->mins, leaf_p->mins);
VectorCopy(node->maxs, leaf_p->maxs);
//
// write the leafbrushes
//
leaf_p->firstleafbrush = numleafbrushes;
for (b = node->brushlist; b; b = b->next) {
if (numleafbrushes >= MAX_MAP_LEAFBRUSHES_QBSP)
Error("MAX_MAP_LEAFBRUSHES_QBSP");
brushnum = b->original - mapbrushes;
for (i = leaf_p->firstleafbrush; i < numleafbrushes; i++)
if (dleafbrushesX[i] == brushnum)
break;
if (i == numleafbrushes) {
dleafbrushesX[numleafbrushes] = brushnum;
numleafbrushes++;
}
}
leaf_p->numleafbrushes = numleafbrushes - leaf_p->firstleafbrush;
//
// write the leaffaces
//
if (leaf_p->contents & CONTENTS_SOLID)
return; // no leaffaces in solids
leaf_p->firstleafface = numleaffaces;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
f = p->face[s];
if (!f)
continue; // not a visible portal
EmitMarkFaceX(leaf_p, f);
}
leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface;
}
else {
dleaf_t *leaf_p;
if (numleafs >= MAX_MAP_LEAFS)
Error("MAX_MAP_LEAFS");
leaf_p = &dleafs[numleafs];
numleafs++;
leaf_p->contents = node->contents;
leaf_p->cluster = node->cluster;
leaf_p->area = node->area;
//
// write bounding box info
//
VectorCopy(node->mins, leaf_p->mins);
VectorCopy(node->maxs, leaf_p->maxs);
//
// write the leafbrushes
//
leaf_p->firstleafbrush = numleafbrushes;
for (b = node->brushlist; b; b = b->next) {
if (numleafbrushes >= MAX_MAP_LEAFBRUSHES)
Error("MAX_MAP_LEAFBRUSHES");
brushnum = b->original - mapbrushes;
for (i = leaf_p->firstleafbrush; i < numleafbrushes; i++)
if (dleafbrushes[i] == brushnum)
break;
if (i == numleafbrushes) {
dleafbrushes[numleafbrushes] = brushnum;
numleafbrushes++;
}
}
leaf_p->numleafbrushes = numleafbrushes - leaf_p->firstleafbrush;
//
// write the leaffaces
//
if (leaf_p->contents & CONTENTS_SOLID)
return; // no leaffaces in solids
leaf_p->firstleafface = numleaffaces;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
f = p->face[s];
if (!f)
continue; // not a visible portal
EmitMarkFace(leaf_p, f);
}
leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface;
}
}
/*
==================
EmitFace
==================
*/
void EmitFace(face_t *f) {
int32_t i;
int32_t e;
f->outputnumber = -1;
if (f->numpoints < 3) {
return; // degenerated
}
if (f->merged || f->split[0] || f->split[1]) {
return; // not a final face
}
// save output number so leaffaces can use
f->outputnumber = numfaces;
if (use_qbsp) {
dface_tx *df;
if (numfaces >= MAX_MAP_FACES_QBSP)
Error("numfaces == MAX_MAP_FACES_QBSP");
df = &dfacesX[numfaces];
numfaces++;
// planenum is used by qlight, but not the engine
df->planenum = f->planenum & (~1);
df->side = f->planenum & 1;
df->firstedge = numsurfedges;
df->numedges = f->numpoints;
df->texinfo = f->texinfo;
for (i = 0; i < f->numpoints; i++) {
e = GetEdge(f->vertexnums[i], f->vertexnums[(i + 1) % f->numpoints], f);
if (numsurfedges >= MAX_MAP_SURFEDGES_QBSP)
Error("numsurfedges == MAX_MAP_SURFEDGES_QBSP");
dsurfedges[numsurfedges] = e;
numsurfedges++;
}
} else {
dface_t *df;
if (numfaces >= MAX_MAP_FACES)
Error("numfaces == MAX_MAP_FACES");
df = &dfaces[numfaces];
numfaces++;
// planenum is used by qlight, but not the engine
df->planenum = f->planenum & (~1);
df->side = f->planenum & 1;
df->firstedge = numsurfedges;
df->numedges = f->numpoints;
df->texinfo = f->texinfo;
for (i = 0; i < f->numpoints; i++) {
e = GetEdge(f->vertexnums[i], f->vertexnums[(i + 1) % f->numpoints], f);
if (numsurfedges >= MAX_MAP_SURFEDGES)
Error("numsurfedges == MAX_MAP_SURFEDGES");
dsurfedges[numsurfedges] = e;
numsurfedges++;
}
}
}
/*
============
EmitDrawingNode_r
============
*/
int32_t EmitDrawNode_r(node_t *node) {
face_t *f;
int32_t i;
if (node->planenum == PLANENUM_LEAF) {
EmitLeaf(node);
return -numleafs;
}
// emit a node
if (use_qbsp) {
dnode_tx *n;
if (numnodes == MAX_MAP_NODES_QBSP)
Error("MAX_MAP_NODES_QBSP");
n = &dnodesX[numnodes];
numnodes++;
VectorCopy(node->mins, n->mins);
VectorCopy(node->maxs, n->maxs);
planeused[node->planenum]++;
planeused[node->planenum ^ 1]++;
if (node->planenum & 1)
Error("WriteDrawNodes_r: odd planenum");
n->planenum = node->planenum;
n->firstface = numfaces;
if (!node->faces)
c_nofaces++;
else
c_facenodes++;
for (f = node->faces; f; f = f->next)
EmitFace(f);
n->numfaces = numfaces - n->firstface;
// recursively output the other nodes
for (i = 0; i < 2; i++) {
if (node->children[i]->planenum == PLANENUM_LEAF) {
n->children[i] = -(numleafs + 1);
EmitLeaf(node->children[i]);
} else {
n->children[i] = numnodes;
EmitDrawNode_r(node->children[i]);
}
}
return n - dnodesX;
}
else // ibsp
{
dnode_t *n;
if (numnodes == MAX_MAP_NODES)
Error("MAX_MAP_NODES");
n = &dnodes[numnodes];
numnodes++;
VectorCopy(node->mins, n->mins);
VectorCopy(node->maxs, n->maxs);
planeused[node->planenum]++;
planeused[node->planenum ^ 1]++;
if (node->planenum & 1)
Error("WriteDrawNodes_r: odd planenum");
n->planenum = node->planenum;
n->firstface = numfaces;
if (!node->faces)
c_nofaces++;
else
c_facenodes++;
for (f = node->faces; f; f = f->next)
EmitFace(f);
n->numfaces = numfaces - n->firstface;
// recursively output the other nodes
for (i = 0; i < 2; i++) {
if (node->children[i]->planenum == PLANENUM_LEAF) {
n->children[i] = -(numleafs + 1);
EmitLeaf(node->children[i]);
} else {
n->children[i] = numnodes;
EmitDrawNode_r(node->children[i]);
}
}
return n - dnodes;
}
}
//=========================================================
/*
============
WriteBSP
============
*/
void WriteBSP(node_t *headnode) {
int32_t oldfaces;
c_nofaces = 0;
c_facenodes = 0;
qprintf("--- WriteBSP ---\n");
oldfaces = numfaces;
dmodels[nummodels].headnode = EmitDrawNode_r(headnode);
EmitAreaPortals(headnode);
qprintf("%5i nodes with faces\n", c_facenodes);
qprintf("%5i nodes without faces\n", c_nofaces);
qprintf("%5i faces\n", numfaces - oldfaces);
}
//===========================================================
/*
============
SetModelNumbers
============
*/
void SetModelNumbers(void) {
int32_t i;
int32_t models;
char value[12];
models = 1;
for (i = 1; i < num_entities; i++) {
if (entities[i].numbrushes) {
sprintf(value, "*%i", models);
models++;
SetKeyValue(&entities[i], "model", value);
}
}
}
/*
============
SetLightStyles
============
*/
#define MAX_SWITCHED_LIGHTS 32
void SetLightStyles(void) {
int32_t stylenum;
char *t;
entity_t *e;
int32_t i, j;
char value[11];
char lighttargets[MAX_SWITCHED_LIGHTS][64];
// any light that is controlled (has a targetname)
// must have a unique style number generated for it
stylenum = 0;
for (i = 1; i < num_entities; i++) {
e = &entities[i];
t = ValueForKey(e, "classname");
if (Q_strncasecmp(t, "light", 5))
continue;
t = ValueForKey(e, "targetname");
if (!t[0])
continue;
// find this targetname
for (j = 0; j < stylenum; j++)
if (!strcmp(lighttargets[j], t))
break;
if (j == stylenum) {
if (stylenum == MAX_SWITCHED_LIGHTS)
Error("stylenum == MAX_SWITCHED_LIGHTS");
strcpy(lighttargets[j], t);
stylenum++;
}
sprintf(value, "%i", 32 + j);
SetKeyValue(e, "style", value);
}
}
//===========================================================
/*
============
EmitBrushes
============
*/
void EmitBrushes(void) {
int32_t i, j, bnum, s, x;
dbrush_t *db;
mapbrush_t *b;
vec3_t normal;
vec_t dist;
int32_t planenum;
numbrushsides = 0;
numbrushes = nummapbrushes;
for (bnum = 0; bnum < nummapbrushes; bnum++) {
b = &mapbrushes[bnum];
db = &dbrushes[bnum];
db->contents = b->contents;
db->firstside = numbrushsides;
db->numsides = b->numsides;
if (use_qbsp) {
dbrushside_tx *cp;
for (j = 0; j < b->numsides; j++) {
if (numbrushsides == MAX_MAP_BRUSHSIDES_QBSP)
Error("MAX_MAP_BRUSHSIDES_QBSP");
cp = &dbrushsidesX[numbrushsides];
numbrushsides++;
cp->planenum = b->original_sides[j].planenum;
cp->texinfo = b->original_sides[j].texinfo;
}
} else {
for (j = 0; j < b->numsides; j++) {
dbrushside_t *cp;
if (numbrushsides == MAX_MAP_BRUSHSIDES)
Error("MAX_MAP_BRUSHSIDES");
cp = &dbrushsides[numbrushsides];
numbrushsides++;
cp->planenum = b->original_sides[j].planenum;
cp->texinfo = b->original_sides[j].texinfo;
}
}
// add any axis planes not contained in the brush to bevel off corners
for (x = 0; x < 3; x++)
for (s = -1; s <= 1; s += 2) {
// add the plane
VectorCopy(vec3_origin, normal);
normal[x] = s;
if (s == -1)
dist = -b->mins[x];
else
dist = b->maxs[x];
planenum = FindFloatPlane(normal, dist, b->brushnum);
for (i = 0; i < b->numsides; i++)
if (b->original_sides[i].planenum == planenum)
break;
if (i == b->numsides) {
if (use_qbsp) {
if (numbrushsides >= MAX_MAP_BRUSHSIDES_QBSP)
Error("MAX_MAP_BRUSHSIDES_QBSP");
dbrushsidesX[numbrushsides].planenum = planenum;
dbrushsidesX[numbrushsides].texinfo = dbrushsidesX[numbrushsides - 1].texinfo;
} else {
if (numbrushsides >= MAX_MAP_BRUSHSIDES)
Error("MAX_MAP_BRUSHSIDES");
dbrushsides[numbrushsides].planenum = planenum;
dbrushsides[numbrushsides].texinfo = dbrushsides[numbrushsides - 1].texinfo;
}
numbrushsides++;
db->numsides++;
}
}
}
}
//===========================================================
/*
==================
BeginBSPFile
==================
*/
void BeginBSPFile(void) {
// these values may actually be initialized
// if the file existed when loaded, so clear them explicitly
nummodels = 0;
numfaces = 0;
numnodes = 0;
numbrushsides = 0;
numvertexes = 0;
numleaffaces = 0;
numleafbrushes = 0;
numsurfedges = 0;
// edge 0 is not used, because 0 can't be negated
numedges = 1;
// leave vertex 0 as an error
numvertexes = 1;
// leave leaf 0 as an error
numleafs = 1;
dleafs[0].contents = CONTENTS_SOLID;
dleafsX[0].contents = CONTENTS_SOLID;
}
/*
============
EndBSPFile
============
*/
void EndBSPFile(void) {
char path[1030];
EmitBrushes();
EmitPlanes();
UnparseEntities();
// load the pop
#if 0
sprintf (path, "%s/pics/pop.lmp", gamedir);
len = LoadFile (path, &buf);
memcpy (dpop, buf, sizeof(dpop));
free (buf);
#endif
// write the map
sprintf(path, "%s.bsp", source);
WriteBSPFile(path);
}
/*
==================
BeginModel
==================
*/
int32_t firstmodleaf;
extern int32_t firstmodeledge;
extern int32_t firstmodelface;
void BeginModel(void) {
dmodel_t *mod;
int32_t start, end;
mapbrush_t *b;
int32_t j;
entity_t *e;
vec3_t mins, maxs;
if (use_qbsp) {
if (nummodels == WARN_MAP_MODELS_QBSP)
printf("WARNING: nummodels may exceed protocol limit (%i)\n", WARN_MAP_MODELS_QBSP);
if (nummodels == MAX_MAP_MODELS_QBSP)
Error("nummodels exceeds MAX_MAP_MODELS_QBSP");
} else if (nummodels == MAX_MAP_MODELS)
Error("nummodels exceeds MAX_MAP_MODELS");
mod = &dmodels[nummodels];
mod->firstface = numfaces;
firstmodleaf = numleafs;
firstmodeledge = numedges;
firstmodelface = numfaces;
//
// bound the brushes
//
e = &entities[entity_num];
start = e->firstbrush;
end = start + e->numbrushes;
ClearBounds(mins, maxs);
for (j = start; j < end; j++) {
b = &mapbrushes[j];
if (!b->numsides)
continue; // not a real brush (origin brush)
AddPointToBounds(b->mins, mins, maxs);
AddPointToBounds(b->maxs, mins, maxs);
}
VectorCopy(mins, mod->mins);
VectorCopy(maxs, mod->maxs);
}
/*
==================
EndModel
==================
*/
void EndModel(void) {
dmodel_t *mod;
mod = &dmodels[nummodels];
mod->numfaces = numfaces - mod->firstface;
nummodels++;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
extern int32_t c_nodes;
void RemovePortalFromNode(portal_t *portal, node_t *l);
node_t *NodeForPoint(node_t *node, vec3_t origin) {
plane_t *plane;
vec_t d;
while (node->planenum != PLANENUM_LEAF) {
plane = &mapplanes[node->planenum];
d = DotProduct(origin, plane->normal) - plane->dist;
if (d >= 0)
node = node->children[0];
else
node = node->children[1];
}
return node;
}
/*
=============
FreeTreePortals_r
=============
*/
void FreeTreePortals_r(node_t *node) {
portal_t *p, *nextp;
int32_t s;
// free children
if (node->planenum != PLANENUM_LEAF) {
FreeTreePortals_r(node->children[0]);
FreeTreePortals_r(node->children[1]);
}
// free portals
for (p = node->portals; p; p = nextp) {
s = (p->nodes[1] == node);
nextp = p->next[s];
RemovePortalFromNode(p, p->nodes[!s]);
FreePortal(p);
}
node->portals = NULL;
}
/*
=============
FreeTree_r
=============
*/
void FreeTree_r(node_t *node) {
face_t *f, *nextf;
// free children
if (node->planenum != PLANENUM_LEAF) {
FreeTree_r(node->children[0]);
FreeTree_r(node->children[1]);
}
// free bspbrushes
FreeBrushList(node->brushlist);
// free faces
for (f = node->faces; f; f = nextf) {
nextf = f->next;
FreeFace(f);
}
// free the node
if (node->volume)
FreeBrush(node->volume);
if (numthreads == 1)
c_nodes--;
free(node);
}
/*
=============
FreeTree
=============
*/
void FreeTree(tree_t *tree) {
FreeTreePortals_r(tree->headnode);
FreeTree_r(tree->headnode);
free(tree);
}
//===============================================================
void PrintTree_r(node_t *node, int32_t depth) {
int32_t i;
plane_t *plane;
bspbrush_t *bb;
for (i = 0; i < depth; i++)
printf(" ");
if (node->planenum == PLANENUM_LEAF) {
if (!node->brushlist)
printf("NULL\n");
else {
for (bb = node->brushlist; bb; bb = bb->next)
printf("%i ", bb->original->brushnum);
printf("\n");
}
return;
}
plane = &mapplanes[node->planenum];
printf("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum,
plane->normal[0], plane->normal[1], plane->normal[2],
plane->dist);
PrintTree_r(node->children[0], depth + 1);
PrintTree_r(node->children[1], depth + 1);
}
/*
=========================================================
NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED
=========================================================
*/
int32_t c_pruned;
/*
============
PruneNodes_r
============
*/
void PruneNodes_r(node_t *node) {
bspbrush_t *b, *next;
if (node->planenum == PLANENUM_LEAF)
return;
PruneNodes_r(node->children[0]);
PruneNodes_r(node->children[1]);
if ((node->children[0]->contents & CONTENTS_SOLID) && (node->children[1]->contents & CONTENTS_SOLID)) {
if (node->faces)
Error("node->faces seperating CONTENTS_SOLID");
if (node->children[0]->faces || node->children[1]->faces)
Error("!node->faces with children");
// FIXME: free stuff
node->planenum = PLANENUM_LEAF;
node->contents = CONTENTS_SOLID;
node->detail_seperator = false;
if (node->brushlist)
Error("PruneNodes: node->brushlist");
// combine brush lists
node->brushlist = node->children[1]->brushlist;
for (b = node->children[0]->brushlist; b; b = next) {
next = b->next;
b->next = node->brushlist;
node->brushlist = b;
}
c_pruned++;
}
}
void PruneNodes(node_t *node) {
qprintf("--- PruneNodes ---\n");
c_pruned = 0;
PruneNodes_r(node);
qprintf("%5i pruned nodes\n", c_pruned);
}
//===========================================================
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
int32_t nummiptex;
textureref_t textureref[MAX_MAP_TEXTURES];
//==========================================================================
int32_t FindMiptex(char *name) {
int32_t i, mod_fail;
char path[1080];
char pakpath[56];
miptex_t *mt;
for (i = 0; i < nummiptex; i++)
if (!strcmp(name, textureref[i].name)) {
return i;
}
if (nummiptex == MAX_MAP_TEXTURES)
Error("MAX_MAP_TEXTURES");
strcpy(textureref[i].name, name);
mod_fail = true;
sprintf(pakpath, "textures/%s.wal", name);
if (moddir[0] != 0) {
sprintf(path, "%s%s", moddir, pakpath);
// load the miptex to get the flags and values
if (TryLoadFile(path, (void **)&mt, false) != -1 ||
TryLoadFileFromPak(pakpath, (void **)&mt, moddir) != -1) {
textureref[i].value = LittleLong(mt->value);
textureref[i].flags = LittleLong(mt->flags);
textureref[i].contents = LittleLong(mt->contents);
strcpy(textureref[i].animname, mt->animname);
free(mt);
mod_fail = false;
}
}
if (mod_fail) {
// load the miptex to get the flags and values
sprintf(path, "%s%s", basedir, pakpath);
if (TryLoadFile(path, (void **)&mt, false) != -1 ||
TryLoadFileFromPak(pakpath, (void **)&mt, basedir) != -1) {
textureref[i].value = LittleLong(mt->value);
textureref[i].flags = LittleLong(mt->flags);
textureref[i].contents = LittleLong(mt->contents);
strcpy(textureref[i].animname, mt->animname);
free(mt);
mod_fail = false;
}
}
if (mod_fail)
printf("WARNING: couldn't locate texture %s\n", name);
nummiptex++;
if (textureref[i].animname[0])
FindMiptex(textureref[i].animname);
return i;
}
/*
==================
textureAxisFromPlane
==================
*/
vec3_t baseaxis[18] =
{
{0, 0, 1}, {1, 0, 0}, {0, -1, 0}, // floor
{0, 0, -1},
{1, 0, 0},
{0, -1, 0}, // ceiling
{1, 0, 0},
{0, 1, 0},
{0, 0, -1}, // west wall
{-1, 0, 0},
{0, 1, 0},
{0, 0, -1}, // east wall
{0, 1, 0},
{1, 0, 0},
{0, 0, -1}, // south wall
{0, -1, 0},
{1, 0, 0},
{0, 0, -1} // north wall
};
void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv) {
int32_t bestaxis;
vec_t dot, best;
int32_t i;
best = 0;
bestaxis = 0;
for (i = 0; i < 6; i++) {
dot = DotProduct(pln->normal, baseaxis[i * 3]);
if (dot > best) {
best = dot;
bestaxis = i;
}
}
VectorCopy(baseaxis[bestaxis * 3 + 1], xv);
VectorCopy(baseaxis[bestaxis * 3 + 2], yv);
}
static inline void CheckTexinfoCount() // mxd
{
if (use_qbsp) {
if (numtexinfo == MAX_MAP_TEXINFO_QBSP)
printf("WARNING: texinfo count exceeds qbsp limit (%i).\n", MAX_MAP_TEXINFO_QBSP);
} else if (numtexinfo == DEFAULT_MAP_TEXINFO)
printf("WARNING: texinfo count exceeds vanilla limit (%i).\n", DEFAULT_MAP_TEXINFO);
else if (numtexinfo >= MAX_MAP_TEXINFO)
Error("ERROR: texinfo count exceeds extended limit (%i).\n", MAX_MAP_TEXINFO);
}
qboolean TexinfosMatch(texinfo_t t1, texinfo_t t2) // mxd
{
if (t1.flags != t2.flags || t1.value != t2.value || strcmp(t1.texture, t2.texture))
return false;
for (int32_t j = 0; j < 2; j++)
for (int32_t k = 0; k < 4; k++)
if ((int32_t)(t1.vecs[j][k] * 100) != (int32_t)(t2.vecs[j][k] * 100)) // qb: round to two decimal places
return false;
return true;
}
// mxd. Applies origin brush offset to existing v220 texinfo...
int32_t ApplyTexinfoOffset_UV(int32_t texinfoindex, const brush_texture_t *bt, const vec3_t origin) {
if ((!origin[0] && !origin[1] && !origin[2]) || texinfoindex < 0)
return texinfoindex;
const texinfo_t otx = texinfo[texinfoindex];
// Copy texinfo
texinfo_t tx;
memcpy(&tx, &otx, sizeof(tx));
// Transform origin to UV space and add it to ST offsets
tx.vecs[0][3] += origin[0] * tx.vecs[0][0] + origin[1] * tx.vecs[0][1] + origin[2] * tx.vecs[0][2];
tx.vecs[1][3] += origin[0] * tx.vecs[1][0] + origin[1] * tx.vecs[1][1] + origin[2] * tx.vecs[1][2];
// Find or replace texinfo
texinfo_t *tc = texinfo;
for (texinfoindex = 0; texinfoindex < numtexinfo; texinfoindex++, tc++)
if (TexinfosMatch(*tc, tx))
return texinfoindex;
*tc = tx;
numtexinfo++;
CheckTexinfoCount();
// Repeat for the next animation frame
const int32_t mt = FindMiptex(tx.texture);
if (textureref[mt].animname[0]) {
brush_texture_t anim = *bt;
strcpy(anim.name, textureref[mt].animname);
tc->nexttexinfo = ApplyTexinfoOffset_UV(tc->nexttexinfo, &anim, origin);
} else {
tc->nexttexinfo = -1;
}
// Return new texinfo index.
return texinfoindex;
}
// DarkEssence: function for new #mapversion with UVaxis
int32_t TexinfoForBrushTexture_UV(brush_texture_t *bt, vec_t *UVaxis) {
if (!bt->name[0])
return 0;
texinfo_t tx;
memset(&tx, 0, sizeof(tx));
strcpy(tx.texture, bt->name);
if (!bt->scale[0])
bt->scale[0] = 1;
if (!bt->scale[1])
bt->scale[1] = 1;
for (int32_t i = 0; i < 2; i++)
for (int32_t j = 0; j < 3; j++)
tx.vecs[i][j] = UVaxis[i * 3 + j] / bt->scale[i];
tx.vecs[0][3] = bt->shift[0];
tx.vecs[1][3] = bt->shift[1];
tx.flags = bt->flags;
tx.value = bt->value;
// Find or replace texinfo
int32_t texinfoindex;
texinfo_t *tc = texinfo;
for (texinfoindex = 0; texinfoindex < numtexinfo; texinfoindex++, tc++)
if (TexinfosMatch(*tc, tx))
return texinfoindex;
*tc = tx;
numtexinfo++;
CheckTexinfoCount(); // mxd
// Load the next animation
const int32_t mt = FindMiptex(bt->name);
if (textureref[mt].animname[0]) {
brush_texture_t anim = *bt;
strcpy(anim.name, textureref[mt].animname);
tc->nexttexinfo = TexinfoForBrushTexture_UV(&anim, UVaxis);
} else
tc->nexttexinfo = -1;
return texinfoindex;
}
extern qboolean origfix;
int32_t TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin) {
if (!bt->name[0])
return 0;
texinfo_t tx;
memset(&tx, 0, sizeof(tx));
strcpy(tx.texture, bt->name);
vec3_t vecs[2];
TextureAxisFromPlane(plane, vecs[0], vecs[1]);
/* Originally:
shift[0] = DotProduct (origin, vecs[0]);
shift[1] = DotProduct (origin, vecs[1]);
*/
if (!bt->scale[0])
bt->scale[0] = 1;
if (!bt->scale[1])
bt->scale[1] = 1;
// DWH: Fix for scaled textures using an origin brush
float shift[2];
vec3_t scaled_origin;
if (origfix) {
VectorScale(origin, 1.0 / bt->scale[0], scaled_origin);
shift[0] = DotProduct(scaled_origin, vecs[0]);
VectorScale(origin, 1.0 / bt->scale[1], scaled_origin);
shift[1] = DotProduct(scaled_origin, vecs[1]);
} else {
shift[0] = DotProduct(origin, vecs[0]);
shift[1] = DotProduct(origin, vecs[1]);
}
// Rotate axis
vec_t sinv, cosv;
if (bt->rotate == 0) {
sinv = 0;
cosv = 1;
} else if (bt->rotate == 90) {
sinv = 1;
cosv = 0;
} else if (bt->rotate == 180) {
sinv = 0;
cosv = -1;
} else if (bt->rotate == 270) {
sinv = -1;
cosv = 0;
} else {
const vec_t ang = bt->rotate / 180 * Q_PI;
sinv = sin(ang);
cosv = cos(ang);
}
// DWH: and again...
if (origfix) {
const vec_t ns = cosv * shift[0] - sinv * shift[1];
const vec_t nt = sinv * shift[0] + cosv * shift[1];
shift[0] = ns;
shift[1] = nt;
}
int32_t sv, tv;
if (vecs[0][0])
sv = 0;
else if (vecs[0][1])
sv = 1;
else
sv = 2;
if (vecs[1][0])
tv = 0;
else if (vecs[1][1])
tv = 1;
else
tv = 2;
for (int32_t i = 0; i < 2; i++) {
const vec_t ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
const vec_t nt = sinv * vecs[i][sv] + cosv * vecs[i][tv];
vecs[i][sv] = ns;
vecs[i][tv] = nt;
}
for (int32_t i = 0; i < 2; i++)
for (int32_t j = 0; j < 3; j++)
tx.vecs[i][j] = vecs[i][j] / bt->scale[i];
tx.vecs[0][3] = bt->shift[0] + shift[0];
tx.vecs[1][3] = bt->shift[1] + shift[1];
tx.flags = bt->flags;
tx.value = bt->value;
// Find or replace texinfo
int32_t texinfoindex;
texinfo_t *tc = texinfo;
for (texinfoindex = 0; texinfoindex < numtexinfo; texinfoindex++, tc++)
if (TexinfosMatch(*tc, tx))
return texinfoindex;
*tc = tx;
numtexinfo++;
CheckTexinfoCount(); // mxd
// load the next animation
const int32_t mt = FindMiptex(bt->name);
if (textureref[mt].animname[0]) {
brush_texture_t anim = *bt;
strcpy(anim.name, textureref[mt].animname);
tc->nexttexinfo = TexinfoForBrushTexture(plane, &anim, origin);
} else
tc->nexttexinfo = -1;
return texinfoindex;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
/*
==============================================================================
PORTAL FILE GENERATION
Save out name.prt for qvis to read
==============================================================================
*/
#define PORTALFILE "PRT1"
extern qboolean use_qbsp;
FILE *pf;
int32_t num_visclusters; // clusters the player can be in
int32_t num_visportals;
void WriteFloat(FILE *f, vec_t v) {
if (fabs(v - Q_rint(v)) < 0.001)
fprintf(f, "%i ", (int32_t)Q_rint(v));
else
fprintf(f, "%f ", v);
}
/*
=================
WritePortalFile_r
=================
*/
void WritePortalFile_r(node_t *node) {
int32_t i, s;
portal_t *p;
winding_t *w;
vec3_t normal;
vec_t dist;
// decision node
if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) {
WritePortalFile_r(node->children[0]);
WritePortalFile_r(node->children[1]);
return;
}
if (node->contents & CONTENTS_SOLID)
return;
for (p = node->portals; p; p = p->next[s]) {
w = p->winding;
s = (p->nodes[1] == node);
if (w && p->nodes[0] == node) {
if (!Portal_VisFlood(p))
continue;
// write out to the file
// sometimes planes get turned around when they are very near
// the changeover point between different axis. interpret the
// plane the same way vis will, and flip the side orders if needed
// FIXME: is this still relevent?
WindingPlane(w, normal, &dist);
if (DotProduct(p->plane.normal, normal) < 0.99) {
// backwards...
fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster);
} else
fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster);
for (i = 0; i < w->numpoints; i++) {
fprintf(pf, "(");
WriteFloat(pf, w->p[i][0]);
WriteFloat(pf, w->p[i][1]);
WriteFloat(pf, w->p[i][2]);
fprintf(pf, ") ");
}
fprintf(pf, "\n");
}
}
}
/*
================
FillLeafNumbers_r
All of the leafs under node will have the same cluster
================
*/
void FillLeafNumbers_r(node_t *node, int32_t num) {
if (node->planenum == PLANENUM_LEAF) {
if (node->contents & CONTENTS_SOLID)
node->cluster = -1;
else
node->cluster = num;
return;
}
node->cluster = num;
FillLeafNumbers_r(node->children[0], num);
FillLeafNumbers_r(node->children[1], num);
}
/*
================
NumberLeafs_r
================
*/
void NumberLeafs_r(node_t *node) {
portal_t *p;
if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) {
// decision node
node->cluster = -99;
NumberLeafs_r(node->children[0]);
NumberLeafs_r(node->children[1]);
return;
}
// either a leaf or a detail cluster
if (node->contents & CONTENTS_SOLID) {
// solid block, viewpoint never inside
node->cluster = -1;
return;
}
FillLeafNumbers_r(node, num_visclusters);
num_visclusters++;
// count the portals
for (p = node->portals; p;) {
if (p->nodes[0] == node) // only write out from first leaf
{
if (Portal_VisFlood(p))
num_visportals++;
p = p->next[0];
} else
p = p->next[1];
}
}
/*
================
CreateVisPortals_r
================
*/
void CreateVisPortals_r(node_t *node) {
// stop as soon as we get to a detail_seperator, which
// means that everything below is in a single cluster
if (node->planenum == PLANENUM_LEAF || node->detail_seperator)
return;
MakeNodePortal(node);
SplitNodePortals(node);
CreateVisPortals_r(node->children[0]);
CreateVisPortals_r(node->children[1]);
}
/*
================
FinishVisPortals_r
================
*/
void FinishVisPortals2_r(node_t *node) {
if (node->planenum == PLANENUM_LEAF)
return;
MakeNodePortal(node);
SplitNodePortals(node);
FinishVisPortals2_r(node->children[0]);
FinishVisPortals2_r(node->children[1]);
}
void FinishVisPortals_r(node_t *node) {
if (node->planenum == PLANENUM_LEAF)
return;
if (node->detail_seperator) {
FinishVisPortals2_r(node);
return;
}
FinishVisPortals_r(node->children[0]);
FinishVisPortals_r(node->children[1]);
}
int32_t clusterleaf;
void SaveClusters_r(node_t *node) {
if (node->planenum == PLANENUM_LEAF) {
if (use_qbsp)
dleafsX[clusterleaf++].cluster = node->cluster;
else
dleafs[clusterleaf++].cluster = node->cluster;
return;
}
SaveClusters_r(node->children[0]);
SaveClusters_r(node->children[1]);
}
/*
================
WritePortalFile
================
*/
void WritePortalFile(tree_t *tree) {
char filename[1030];
node_t *headnode;
qprintf("--- WritePortalFile ---\n");
headnode = tree->headnode;
num_visclusters = 0;
num_visportals = 0;
FreeTreePortals_r(headnode);
MakeHeadnodePortals(tree);
CreateVisPortals_r(headnode);
// set the cluster field in every leaf and count the total number of portals
NumberLeafs_r(headnode);
// write the file
sprintf(filename, "%s.prt", source);
if (use_qbsp)
printf("\ntexinfo count: %i of %i maximum\n", numtexinfo, MAX_MAP_TEXINFO_QBSP);
else
printf("\ntexinfo count: %i of %i maximum\n", numtexinfo, MAX_MAP_TEXINFO);
if (use_qbsp)
printf("brushsides count: %i of %i maximum\n", nummapbrushsides, MAX_MAP_BRUSHSIDES_QBSP);
else
printf("brushsides count: %i of %i maximum\n", nummapbrushsides, MAX_MAP_BRUSHSIDES);
printf("writing %s\n", filename);
pf = fopen(filename, "w");
if (!pf)
Error("Error opening %s", filename);
fprintf(pf, "%s\n", PORTALFILE);
fprintf(pf, "%i\n", num_visclusters);
fprintf(pf, "%i\n", num_visportals);
qprintf("%5i visclusters\n", num_visclusters);
qprintf("%5i visportals\n", num_visportals);
WritePortalFile_r(headnode);
fclose(pf);
// we need to store the clusters out now because ordering
// issues made us do this after writebsp...
clusterleaf = 1;
SaveClusters_r(headnode);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
int32_t c_active_portals;
int32_t c_peak_portals;
int32_t c_boundary;
int32_t c_boundary_sides;
/*
===========
AllocPortal
===========
*/
portal_t *AllocPortal(void) {
portal_t *p;
if (numthreads == 1)
c_active_portals++;
if (c_active_portals > c_peak_portals)
c_peak_portals = c_active_portals;
p = malloc(sizeof(portal_t));
memset(p, 0, sizeof(portal_t));
return p;
}
void FreePortal(portal_t *p) {
if (p->winding)
FreeWinding(p->winding);
if (numthreads == 1)
c_active_portals--;
free(p);
}
//==============================================================
/*
==============
VisibleContents
Returns the single content bit of the
strongest visible content present
==============
*/
int32_t VisibleContents(int32_t contents) {
int32_t i;
for (i = 1; i <= LAST_VISIBLE_CONTENTS; i <<= 1)
if (contents & i)
return i;
return 0;
}
/*
===============
ClusterContents
===============
*/
int32_t ClusterContents(node_t *node) {
int32_t c1, c2, c;
if (node->planenum == PLANENUM_LEAF)
return node->contents;
c1 = ClusterContents(node->children[0]);
c2 = ClusterContents(node->children[1]);
c = c1 | c2;
// a cluster may include some solid detail areas, but
// still be seen into
if (!(c1 & CONTENTS_SOLID) || !(c2 & CONTENTS_SOLID))
c &= ~CONTENTS_SOLID;
return c;
}
/*
=============
Portal_VisFlood
Returns true if the portal is empty or translucent, allowing
the PVS calculation to see through it.
The nodes on either side of the portal may actually be clusters,
not leafs, so all contents should be ored together
=============
*/
qboolean Portal_VisFlood(portal_t *p) {
int32_t c1, c2;
if (!p->onnode)
return false; // to global outsideleaf
c1 = ClusterContents(p->nodes[0]);
c2 = ClusterContents(p->nodes[1]);
if (!VisibleContents(c1 ^ c2))
return true;
if (c1 & (CONTENTS_TRANSLUCENT | CONTENTS_DETAIL))
c1 = 0;
if (c2 & (CONTENTS_TRANSLUCENT | CONTENTS_DETAIL))
c2 = 0;
if ((c1 | c2) & CONTENTS_SOLID)
return false; // can't see through solid
if (!(c1 ^ c2))
return true; // identical on both sides
if (!VisibleContents(c1 ^ c2))
return true;
return false;
}
/*
===============
Portal_EntityFlood
The entity flood determines which areas are
"outside" on the map, which are then filled in.
Flowing from side s to side !s
===============
*/
qboolean Portal_EntityFlood(portal_t *p, int32_t s) {
if (p->nodes[0]->planenum != PLANENUM_LEAF || p->nodes[1]->planenum != PLANENUM_LEAF)
Error("Portal_EntityFlood: not a leaf");
// can never cross to a solid
if ((p->nodes[0]->contents & CONTENTS_SOLID) || (p->nodes[1]->contents & CONTENTS_SOLID))
return false;
// can flood through everything else
return true;
}
//=============================================================================
int32_t c_tinyportals;
/*
=============
AddPortalToNodes
=============
*/
void AddPortalToNodes(portal_t *p, node_t *front, node_t *back) {
if (p->nodes[0] || p->nodes[1])
Error("AddPortalToNode: already included");
p->nodes[0] = front;
p->next[0] = front->portals;
front->portals = p;
p->nodes[1] = back;
p->next[1] = back->portals;
back->portals = p;
}
/*
=============
RemovePortalFromNode
=============
*/
void RemovePortalFromNode(portal_t *portal, node_t *l) {
portal_t **pp, *t;
// remove reference to the current portal
pp = &l->portals;
while (1) {
t = *pp;
if (!t)
Error("RemovePortalFromNode: portal not in leaf");
if (t == portal)
break;
if (t->nodes[0] == l)
pp = &t->next[0];
else if (t->nodes[1] == l)
pp = &t->next[1];
else
Error("RemovePortalFromNode: portal not bounding leaf");
}
if (portal->nodes[0] == l) {
*pp = portal->next[0];
portal->nodes[0] = NULL;
} else if (portal->nodes[1] == l) {
*pp = portal->next[1];
portal->nodes[1] = NULL;
}
}
//============================================================================
void PrintPortal(portal_t *p) {
int32_t i;
winding_t *w;
w = p->winding;
for (i = 0; i < w->numpoints; i++)
printf("(%5.0f,%5.0f,%5.0f)\n", w->p[i][0], w->p[i][1], w->p[i][2]);
}
/*
================
MakeHeadnodePortals
The created portals will face the global outside_node
================
*/
#define SIDESPACE 8
void MakeHeadnodePortals(tree_t *tree) {
vec3_t bounds[2];
int32_t i, j, n;
portal_t *p, *portals[6];
plane_t bplanes[6], *pl;
node_t *node;
node = tree->headnode;
// pad with some space so there will never be null volume leafs
for (i = 0; i < 3; i++) {
bounds[0][i] = tree->mins[i] - SIDESPACE;
bounds[1][i] = tree->maxs[i] + SIDESPACE;
}
tree->outside_node.planenum = PLANENUM_LEAF;
tree->outside_node.brushlist = NULL;
tree->outside_node.portals = NULL;
tree->outside_node.contents = 0;
for (i = 0; i < 3; i++)
for (j = 0; j < 2; j++) {
n = j * 3 + i;
p = AllocPortal();
portals[n] = p;
pl = &bplanes[n];
memset(pl, 0, sizeof(*pl));
if (j) {
pl->normal[i] = -1;
pl->dist = -bounds[j][i];
} else {
pl->normal[i] = 1;
pl->dist = bounds[j][i];
}
p->plane = *pl;
p->winding = BaseWindingForPlane(pl->normal, pl->dist);
AddPortalToNodes(p, node, &tree->outside_node);
}
// clip the basewindings by all the other planes
for (i = 0; i < 6; i++) {
for (j = 0; j < 6; j++) {
if (j == i)
continue;
ChopWindingInPlace(&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON);
}
}
}
//===================================================
/*
================
BaseWindingForNode
================
*/
#define BASE_WINDING_EPSILON 0.001
#define SPLIT_WINDING_EPSILON 0.001
winding_t *BaseWindingForNode(node_t *node) {
winding_t *w;
node_t *n;
plane_t *plane;
vec3_t normal;
vec_t dist;
w = BaseWindingForPlane(mapplanes[node->planenum].normal,
mapplanes[node->planenum].dist);
// clip by all the parents
for (n = node->parent; n && w;) {
plane = &mapplanes[n->planenum];
if (n->children[0] == node) { // take front
ChopWindingInPlace(&w, plane->normal, plane->dist, BASE_WINDING_EPSILON);
} else { // take back
VectorSubtract(vec3_origin, plane->normal, normal);
dist = -plane->dist;
ChopWindingInPlace(&w, normal, dist, BASE_WINDING_EPSILON);
}
node = n;
n = n->parent;
}
return w;
}
//============================================================
qboolean WindingIsTiny(winding_t *w);
/*
==================
MakeNodePortal
create the new portal by taking the full plane winding for the cutting plane
and clipping it by all of parents of this node
==================
*/
void MakeNodePortal(node_t *node) {
portal_t *new_portal, *p;
winding_t *w;
vec3_t normal;
vec_t dist = 0;
int32_t side = 0;
w = BaseWindingForNode(node);
// clip the portal by all the other portals in the node
for (p = node->portals; p && w; p = p->next[side]) {
if (p->nodes[0] == node) {
side = 0;
VectorCopy(p->plane.normal, normal);
dist = p->plane.dist;
} else if (p->nodes[1] == node) {
side = 1;
VectorSubtract(vec3_origin, p->plane.normal, normal);
dist = -p->plane.dist;
} else
Error("CutNodePortals_r: mislinked portal");
ChopWindingInPlace(&w, normal, dist, 0.1);
}
if (!w) {
return;
}
if (WindingIsTiny(w)) {
c_tinyportals++;
FreeWinding(w);
return;
}
new_portal = AllocPortal();
new_portal->plane = mapplanes[node->planenum];
new_portal->onnode = node;
new_portal->winding = w;
AddPortalToNodes(new_portal, node->children[0], node->children[1]);
}
/*
==============
SplitNodePortals
Move or split the portals that bound node so that the node's
children have portals instead of node.
==============
*/
void SplitNodePortals(node_t *node) {
portal_t *p, *next_portal, *new_portal;
node_t *f, *b, *other_node;
int32_t side = 0;
plane_t *plane;
winding_t *frontwinding, *backwinding;
plane = &mapplanes[node->planenum];
f = node->children[0];
b = node->children[1];
for (p = node->portals; p; p = next_portal) {
if (p->nodes[0] == node)
side = 0;
else if (p->nodes[1] == node)
side = 1;
else
Error("CutNodePortals_r: mislinked portal");
next_portal = p->next[side];
other_node = p->nodes[!side];
RemovePortalFromNode(p, p->nodes[0]);
RemovePortalFromNode(p, p->nodes[1]);
//
// cut the portal into two portals, one on each side of the cut plane
//
ClipWindingEpsilon(p->winding, plane->normal, plane->dist,
SPLIT_WINDING_EPSILON, &frontwinding, &backwinding);
if (frontwinding && WindingIsTiny(frontwinding)) {
FreeWinding(frontwinding);
frontwinding = NULL;
c_tinyportals++;
}
if (backwinding && WindingIsTiny(backwinding)) {
FreeWinding(backwinding);
backwinding = NULL;
c_tinyportals++;
}
if (!frontwinding && !backwinding) { // tiny windings on both sides
continue;
}
if (!frontwinding) {
FreeWinding(backwinding);
if (side == 0)
AddPortalToNodes(p, b, other_node);
else
AddPortalToNodes(p, other_node, b);
continue;
}
if (!backwinding) {
FreeWinding(frontwinding);
if (side == 0)
AddPortalToNodes(p, f, other_node);
else
AddPortalToNodes(p, other_node, f);
continue;
}
// the winding is split
new_portal = AllocPortal();
*new_portal = *p;
new_portal->winding = backwinding;
FreeWinding(p->winding);
p->winding = frontwinding;
if (side == 0) {
AddPortalToNodes(p, f, other_node);
AddPortalToNodes(new_portal, b, other_node);
} else {
AddPortalToNodes(p, other_node, f);
AddPortalToNodes(new_portal, other_node, b);
}
}
node->portals = NULL;
}
/*
================
CalcNodeBounds
================
*/
void CalcNodeBounds(node_t *node) {
portal_t *p;
int32_t s;
int32_t i;
// calc mins/maxs for both leafs and nodes
ClearBounds(node->mins, node->maxs);
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
for (i = 0; i < p->winding->numpoints; i++)
AddPointToBounds(p->winding->p[i], node->mins, node->maxs);
}
}
/*
==================
MakeTreePortals_r
==================
*/
void MakeTreePortals_r(node_t *node) {
int32_t i;
CalcNodeBounds(node);
for (i = 0; i < 3; i++) {
if (node->mins[i] < -2 * max_bounds || node->maxs[i] > 2 * max_bounds) {
printf("WARNING: node with unbounded volume\n");
printf(" Bounds: %g %g %g -> %g %g %g\n",
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
break;
}
}
if (node->planenum == PLANENUM_LEAF)
return;
MakeNodePortal(node);
SplitNodePortals(node);
MakeTreePortals_r(node->children[0]);
MakeTreePortals_r(node->children[1]);
}
/*
==================
MakeTreePortals
==================
*/
void MakeTreePortals(tree_t *tree) {
MakeHeadnodePortals(tree);
MakeTreePortals_r(tree->headnode);
}
/*
=========================================================
FLOOD ENTITIES
=========================================================
*/
/*
=============
FloodPortals_r
=============
*/
void FloodPortals_r(node_t *node, int32_t dist) {
portal_t *p;
int32_t s;
node->occupied = dist;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
if (p->nodes[!s]->occupied)
continue;
if (!Portal_EntityFlood(p, s))
continue;
FloodPortals_r(p->nodes[!s], dist + 1);
}
}
/*
=============
PlaceOccupant
=============
*/
qboolean PlaceOccupant(node_t *headnode, vec3_t origin, entity_t *occupant) {
node_t *node;
vec_t d;
plane_t *plane;
// find the leaf to start in
node = headnode;
while (node->planenum != PLANENUM_LEAF) {
plane = &mapplanes[node->planenum];
d = DotProduct(origin, plane->normal) - plane->dist;
if (d >= 0)
node = node->children[0];
else
node = node->children[1];
}
if (node->contents == CONTENTS_SOLID)
return false;
node->occupant = occupant;
FloodPortals_r(node, 1);
return true;
}
/*
=============
FloodEntities
Marks all nodes that can be reached by entites
=============
*/
qboolean FloodEntities(tree_t *tree) {
int32_t i;
vec3_t origin;
char *cl;
qboolean inside;
node_t *headnode;
headnode = tree->headnode;
qprintf("--- FloodEntities ---\n");
inside = false;
tree->outside_node.occupied = 0;
for (i = 1; i < num_entities; i++) {
GetVectorForKey(&entities[i], "origin", origin);
if (VectorCompare(origin, vec3_origin))
continue;
cl = ValueForKey(&entities[i], "classname");
origin[2] += 1; // so objects on floor are ok
// nudge playerstart around if needed so clipping hulls allways
// have a vlaid point
if (!strcmp(cl, "info_player_start")) {
int32_t x, y;
for (x = -16; x <= 16; x += 16) {
for (y = -16; y <= 16; y += 16) {
origin[0] += x;
origin[1] += y;
if (PlaceOccupant(headnode, origin, &entities[i])) {
inside = true;
goto gotit;
}
origin[0] -= x;
origin[1] -= y;
}
}
gotit:;
} else {
if (PlaceOccupant(headnode, origin, &entities[i]))
inside = true;
}
}
if (!inside) {
qprintf("no entities in open -- no filling\n");
} else if (tree->outside_node.occupied) {
qprintf("entity reached from outside -- no filling\n");
}
return (qboolean)(inside && !tree->outside_node.occupied);
}
/*
=========================================================
FLOOD AREAS
=========================================================
*/
int32_t c_areas;
/*
=============
FloodAreas_r
=============
*/
void FloodAreas_r(node_t *node) {
portal_t *p;
int32_t s;
bspbrush_t *b;
entity_t *e;
if (node->contents == CONTENTS_AREAPORTAL) {
// this node is part of an area portal
b = node->brushlist;
e = &entities[b->original->entitynum];
// if the current area has allready touched this
// portal, we are done
if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas)
return;
// note the current area as bounding the portal
if (e->portalareas[1]) {
printf("WARNING: areaportal entity %i touches > 2 areas\n Node Bounds: %g %g %g -> %g %g %g\n", b->original->entitynum,
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
return;
}
if (e->portalareas[0])
e->portalareas[1] = c_areas;
else
e->portalareas[0] = c_areas;
return;
}
if (node->area)
return; // allready got it
node->area = c_areas;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
#if 0
if (p->nodes[!s]->occupied)
continue;
#endif
if (!Portal_EntityFlood(p, s))
continue;
FloodAreas_r(p->nodes[!s]);
}
}
/*
=============
FindAreas_r
Just decend the tree, and for each node that hasn't had an
area set, flood fill out from there
=============
*/
void FindAreas_r(node_t *node) {
if (node->planenum != PLANENUM_LEAF) {
FindAreas_r(node->children[0]);
FindAreas_r(node->children[1]);
return;
}
if (node->area)
return; // allready got it
if (node->contents & CONTENTS_SOLID)
return;
if (!node->occupied)
return; // not reachable by entities
// area portals are allways only flooded into, never
// out of
if (node->contents == CONTENTS_AREAPORTAL)
return;
c_areas++;
FloodAreas_r(node);
}
/*
=============
SetAreaPortalAreas_r
Just decend the tree, and for each node that hasn't had an
area set, flood fill out from there
=============
*/
void SetAreaPortalAreas_r(node_t *node) {
bspbrush_t *b;
entity_t *e;
if (node->planenum != PLANENUM_LEAF) {
SetAreaPortalAreas_r(node->children[0]);
SetAreaPortalAreas_r(node->children[1]);
return;
}
if (node->contents == CONTENTS_AREAPORTAL) {
if (node->area)
return; // allready set
b = node->brushlist;
e = &entities[b->original->entitynum];
node->area = e->portalareas[0];
if (!e->portalareas[1]) {
printf("WARNING: areaportal entity %i doesn't touch two areas\n Node Bounds: %g %g %g -> %g %g %g\n", b->original->entitynum,
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
return;
}
}
}
/*
=============
EmitAreaPortals
=============
*/
void EmitAreaPortals(node_t *headnode) {
int32_t i, j;
entity_t *e;
dareaportal_t *dp;
if (c_areas > MAX_MAP_AREAS)
Error("MAX_MAP_AREAS");
numareas = c_areas + 1;
numareaportals = 1; // leave 0 as an error
for (i = 1; i <= c_areas; i++) {
dareas[i].firstareaportal = numareaportals;
for (j = 0; j < num_entities; j++) {
e = &entities[j];
if (!e->areaportalnum)
continue;
dp = &dareaportals[numareaportals];
if (e->portalareas[0] == i) {
dp->portalnum = e->areaportalnum;
dp->otherarea = e->portalareas[1];
numareaportals++;
} else if (e->portalareas[1] == i) {
dp->portalnum = e->areaportalnum;
dp->otherarea = e->portalareas[0];
numareaportals++;
}
}
dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal;
}
qprintf("%5i numareas\n", numareas);
qprintf("%5i numareaportals\n", numareaportals);
}
/*
=============
FloodAreas
Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL
=============
*/
void FloodAreas(tree_t *tree) {
qprintf("--- FloodAreas ---\n");
FindAreas_r(tree->headnode);
SetAreaPortalAreas_r(tree->headnode);
qprintf("%5i areas\n", c_areas);
}
//======================================================
int32_t c_outside;
int32_t c_inside;
int32_t c_solid;
void FillOutside_r(node_t *node) {
if (node->planenum != PLANENUM_LEAF) {
FillOutside_r(node->children[0]);
FillOutside_r(node->children[1]);
return;
}
// anything not reachable by an entity
// can be filled away
if (!node->occupied) {
if (node->contents != CONTENTS_SOLID) {
c_outside++;
node->contents = CONTENTS_SOLID;
} else
c_solid++;
} else
c_inside++;
}
/*
=============
FillOutside
Fill all nodes that can't be reached by entities
=============
*/
void FillOutside(node_t *headnode) {
c_outside = 0;
c_inside = 0;
c_solid = 0;
qprintf("--- FillOutside ---\n");
FillOutside_r(headnode);
qprintf("%5i solid leafs\n", c_solid);
qprintf("%5i leafs filled\n", c_outside);
qprintf("%5i inside leafs\n", c_inside);
}
//==============================================================
/*
============
FindPortalSide
Finds a brush side to use for texturing the given portal
============
*/
void FindPortalSide(portal_t *p) {
int32_t viscontents;
bspbrush_t *bb;
mapbrush_t *brush;
node_t *n;
int32_t i, j;
int32_t planenum;
side_t *side, *bestside;
vec_t dot, bestdot;
plane_t *p1, *p2;
// decide which content change is strongest
// solid > lava > water, etc
viscontents = VisibleContents(p->nodes[0]->contents ^ p->nodes[1]->contents);
if (!viscontents)
return;
planenum = p->onnode->planenum;
bestside = NULL;
bestdot = 0;
for (j = 0; j < 2; j++) {
n = p->nodes[j];
p1 = &mapplanes[p->onnode->planenum];
for (bb = n->brushlist; bb; bb = bb->next) {
brush = bb->original;
if (!(brush->contents & viscontents))
continue;
for (i = 0; i < brush->numsides; i++) {
side = &brush->original_sides[i];
if (side->bevel)
continue;
if (side->texinfo == TEXINFO_NODE)
continue; // non-visible
if ((side->planenum & ~1) == planenum) { // exact match
bestside = &brush->original_sides[i];
goto gotit;
}
// see how close the match is
p2 = &mapplanes[side->planenum & ~1];
dot = DotProduct(p1->normal, p2->normal);
if (dot > bestdot) {
bestdot = dot;
bestside = side;
}
}
}
}
gotit:
if (!bestside)
qprintf("WARNING: side not found for portal\n");
p->sidefound = true;
p->side = bestside;
}
/*
===============
MarkVisibleSides_r
===============
*/
void MarkVisibleSides_r(node_t *node) {
portal_t *p;
int32_t s;
if (node->planenum != PLANENUM_LEAF) {
MarkVisibleSides_r(node->children[0]);
MarkVisibleSides_r(node->children[1]);
return;
}
// empty leafs are never boundary leafs
if (!node->contents)
return;
// see if there is a visible face
for (p = node->portals; p; p = p->next[!s]) {
s = (p->nodes[0] == node);
if (!p->onnode)
continue; // edge of world
if (!p->sidefound)
FindPortalSide(p);
if (p->side)
p->side->visible = true;
}
}
/*
=============
MarkVisibleSides
=============
*/
void MarkVisibleSides(tree_t *tree, int32_t startbrush, int32_t endbrush) {
int32_t i, j;
mapbrush_t *mb;
int32_t numsides;
qprintf("--- MarkVisibleSides ---\n");
// clear all the visible flags
for (i = startbrush; i < endbrush; i++) {
mb = &mapbrushes[i];
numsides = mb->numsides;
for (j = 0; j < numsides; j++)
mb->original_sides[j].visible = false;
}
// set visible flags on the sides that are used by portals
MarkVisibleSides_r(tree->headnode);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
extern qboolean onlyents;
int32_t nummapbrushes;
mapbrush_t mapbrushes[MAX_MAP_BRUSHES_QBSP];
int32_t nummapbrushsides;
side_t brushsides[MAX_MAP_SIDES];
brush_texture_t side_brushtextures[MAX_MAP_SIDES];
int32_t nummapplanes;
plane_t mapplanes[MAX_MAP_PLANES_QBSP];
#define PLANE_HASHES 1024
plane_t *planehash[PLANE_HASHES];
vec3_t map_mins, map_maxs;
void TestExpandBrushes(void);
int32_t c_boxbevels;
int32_t c_edgebevels;
int32_t c_areaportals;
int32_t c_clipbrushes;
int32_t g_nMapFileVersion = 0; // DarkEssence: variable for check #mapversion
// #mapversion in search to find in code
/*
=============================================================================
PLANE FINDING
=============================================================================
*/
/*
=================
PlaneTypeForNormal
=================
*/
int32_t PlaneTypeForNormal(vec3_t normal) {
vec_t ax, ay, az;
// NOTE: should these have an epsilon around 1.0?
if (normal[0] >= 1.0 || normal[0] <= -1.0)
return PLANE_X;
if (normal[1] >= 1.0 || normal[1] <= -1.0)
return PLANE_Y;
if (normal[2] >= 1.0 || normal[2] <= -1.0)
return PLANE_Z;
ax = fabs(normal[0]);
ay = fabs(normal[1]);
az = fabs(normal[2]);
if (ax >= ay && ax >= az)
return PLANE_ANYX;
if (ay >= ax && ay >= az)
return PLANE_ANYY;
return PLANE_ANYZ;
}
/*
================
PlaneEqual
================
*/
#define DIST_EPSILON 0.01
qboolean PlaneEqual(plane_t *p, vec3_t normal, vec_t dist) {
#if 1
if (
fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON && fabs(p->dist - dist) < DIST_EPSILON)
return true;
#else
if (p->normal[0] == normal[0] && p->normal[1] == normal[1] && p->normal[2] == normal[2] && p->dist == dist)
return true;
#endif
return false;
}
/*
================
AddPlaneToHash
================
*/
void AddPlaneToHash(plane_t *p) {
int32_t hash;
hash = (int32_t)fabs(p->dist) / 8;
hash &= (PLANE_HASHES - 1);
p->hash_chain = planehash[hash];
planehash[hash] = p;
}
/*
================
CreateNewFloatPlane
================
*/
int32_t CreateNewFloatPlane(vec3_t normal, vec_t dist, int32_t bnum) {
plane_t *p, temp;
if (VectorLength(normal) < 0.5)
Error("FloatPlane: bad normal. Brush %i", bnum); // qb: add brushnum
// create a new plane
if (use_qbsp) {
if (nummapplanes + 2 > MAX_MAP_PLANES_QBSP)
Error("MAX_MAP_PLANES_QBSP");
} else if (nummapplanes + 2 > MAX_MAP_PLANES)
Error("MAX_MAP_PLANES");
p = &mapplanes[nummapplanes];
VectorCopy(normal, p->normal);
p->dist = dist;
p->type = (p + 1)->type = PlaneTypeForNormal(p->normal);
VectorSubtract(vec3_origin, normal, (p + 1)->normal);
(p + 1)->dist = -dist;
nummapplanes += 2;
// allways put axial planes facing positive first
if (p->type < 3) {
if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) {
// flip order
temp = *p;
*p = *(p + 1);
*(p + 1) = temp;
AddPlaneToHash(p);
AddPlaneToHash(p + 1);
return nummapplanes - 1;
}
}
AddPlaneToHash(p);
AddPlaneToHash(p + 1);
return nummapplanes - 2;
}
/*
==============
SnapVector
==============
*/
void SnapVector(vec3_t normal) {
int32_t i;
for (i = 0; i < 3; i++) {
if (fabs(normal[i] - 1) < NORMAL_EPSILON) {
VectorClear(normal);
normal[i] = 1;
break;
}
if (fabs(normal[i] - -1) < NORMAL_EPSILON) {
VectorClear(normal);
normal[i] = -1;
break;
}
}
}
/*
==============
SnapPlane
==============
*/
void SnapPlane(vec3_t normal, vec_t *dist) {
SnapVector(normal);
if (fabs(*dist - Q_rint(*dist)) < DIST_EPSILON)
*dist = Q_rint(*dist);
}
/*
=============
FindFloatPlane
=============
*/
int32_t FindFloatPlane(vec3_t normal, vec_t dist, int32_t bnum) {
int32_t i;
plane_t *p;
int32_t hash, h;
SnapPlane(normal, &dist);
hash = (int32_t)fabs(dist) / 8;
hash &= (PLANE_HASHES - 1);
// search the border bins as well
for (i = -1; i <= 1; i++) {
h = (hash + i) & (PLANE_HASHES - 1);
for (p = planehash[h]; p; p = p->hash_chain) {
if (PlaneEqual(p, normal, dist))
return p - mapplanes;
}
}
return CreateNewFloatPlane(normal, dist, bnum);
}
/*
================
PlaneFromPoints
================
*/
int32_t PlaneFromPoints(vec3_t p0, vec3_t p1, vec3_t p2, mapbrush_t *b) {
vec3_t t1, t2, normal;
vec_t dist;
VectorSubtract(p0, p1, t1);
VectorSubtract(p2, p1, t2);
CrossProduct(t1, t2, normal);
VectorNormalize(normal, normal);
dist = DotProduct(p0, normal);
return FindFloatPlane(normal, dist, b->brushnum);
}
//====================================================================
/*
===========
BrushContents
===========
*/
int32_t BrushContents(mapbrush_t *b) {
int32_t contents;
side_t *s;
int32_t i;
int32_t trans;
s = &b->original_sides[0];
contents = s->contents;
trans = texinfo[s->texinfo].flags;
for (i = 1; i < b->numsides; i++, s++) {
s = &b->original_sides[i];
trans |= texinfo[s->texinfo].flags;
if (s->contents != contents) {
printf("Entity %i, Brush %i, Line %i: mixed face contents\n", b->entitynum, b->brushnum, scriptline + 1); // qb: add scriptline
break;
}
}
// if any side is translucent, mark the contents
// and change solid to window
if (trans & (SURF_TRANS33 | SURF_TRANS66)) {
contents |= CONTENTS_TRANSLUCENT;
if (contents & CONTENTS_SOLID) {
contents &= ~CONTENTS_SOLID;
contents |= CONTENTS_WINDOW;
}
}
return contents;
}
//============================================================================
/*
=================
AddBrushBevels
Adds any additional planes necessary to allow the brush to be expanded
against axial bounding boxes
=================
*/
void AddBrushBevels(mapbrush_t *b) {
int32_t axis, dir;
int32_t i, j, k, l, order;
side_t sidetemp;
brush_texture_t tdtemp;
side_t *s, *s2;
vec3_t normal;
vec_t dist;
winding_t *w, *w2;
vec3_t vec, vec2;
vec_t d;
//
// add the axial planes
//
order = 0;
for (axis = 0; axis < 3; axis++) {
for (dir = -1; dir <= 1; dir += 2, order++) {
// see if the plane is allready present
for (i = 0, s = b->original_sides; i < b->numsides; i++, s++) {
if (mapplanes[s->planenum].normal[axis] == dir)
break;
}
if (i == b->numsides) {
// add a new side
if (use_qbsp) {
if (nummapbrushsides == MAX_MAP_BRUSHSIDES_QBSP)
Error("MAX_MAP_BRUSHSIDES_QBSP");
} else if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
Error("MAX_MAP_BRUSHSIDES");
nummapbrushsides++;
b->numsides++;
VectorClear(normal);
normal[axis] = dir;
if (dir == 1)
dist = b->maxs[axis];
else
dist = -b->mins[axis];
s->planenum = FindFloatPlane(normal, dist, b->brushnum);
s->texinfo = b->original_sides[0].texinfo;
s->contents = b->original_sides[0].contents;
s->bevel = true;
c_boxbevels++;
}
// if the plane is not in it canonical order, swap it
if (i != order) {
sidetemp = b->original_sides[order];
b->original_sides[order] = b->original_sides[i];
b->original_sides[i] = sidetemp;
j = b->original_sides - brushsides;
tdtemp = side_brushtextures[j + order];
side_brushtextures[j + order] = side_brushtextures[j + i];
side_brushtextures[j + i] = tdtemp;
}
}
}
//
// add the edge bevels
//
if (b->numsides == 6)
return; // pure axial
// test the non-axial plane edges
for (i = 6; i < b->numsides; i++) {
s = b->original_sides + i;
w = s->winding;
if (!w)
continue;
for (j = 0; j < w->numpoints; j++) {
k = (j + 1) % w->numpoints;
VectorSubtract(w->p[j], w->p[k], vec);
if (VectorNormalize(vec, vec) < 0.5)
continue;
SnapVector(vec);
for (k = 0; k < 3; k++)
if (vec[k] == -1 || vec[k] == 1)
break; // axial
if (k != 3)
continue; // only test non-axial edges
// try the six possible slanted axials from this edge
for (axis = 0; axis < 3; axis++) {
for (dir = -1; dir <= 1; dir += 2) {
// construct a plane
VectorClear(vec2);
vec2[axis] = dir;
CrossProduct(vec, vec2, normal);
if (VectorNormalize(normal, normal) < 0.5)
continue;
dist = DotProduct(w->p[j], normal);
// if all the points on all the sides are
// behind this plane, it is a proper edge bevel
for (k = 0; k < b->numsides; k++) {
// if this plane has allready been used, skip it
if (PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist))
break;
w2 = b->original_sides[k].winding;
if (!w2)
continue;
for (l = 0; l < w2->numpoints; l++) {
d = DotProduct(w2->p[l], normal) - dist;
if (d > 0.1)
break; // point in front
}
if (l != w2->numpoints)
break;
}
if (k != b->numsides)
continue; // wasn't part of the outer hull
// add this plane
if (use_qbsp) {
if (nummapbrushsides == MAX_MAP_BRUSHSIDES_QBSP)
Error("MAX_MAP_BRUSHSIDES_QBSP");
} else if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
Error("MAX_MAP_BRUSHSIDES");
nummapbrushsides++;
s2 = &b->original_sides[b->numsides];
s2->planenum = FindFloatPlane(normal, dist, b->brushnum);
s2->texinfo = b->original_sides[0].texinfo;
s2->contents = b->original_sides[0].contents;
s2->bevel = true;
c_edgebevels++;
b->numsides++;
}
}
}
}
}
/*
================
MakeBrushWindings
makes basewindigs for sides and mins / maxs for the brush
================
*/
void MakeBrushWindings(mapbrush_t *ob) {
int32_t i, j;
winding_t *w;
side_t *side;
plane_t *plane;
ClearBounds(ob->mins, ob->maxs);
for (i = 0; i < ob->numsides; i++) {
plane = &mapplanes[ob->original_sides[i].planenum];
w = BaseWindingForPlane(plane->normal, plane->dist);
for (j = 0; j < ob->numsides && w; j++) {
if (i == j)
continue;
if (ob->original_sides[j].bevel)
continue;
plane = &mapplanes[ob->original_sides[j].planenum ^ 1];
ChopWindingInPlace(&w, plane->normal, plane->dist, 0);
}
side = &ob->original_sides[i];
side->winding = w;
if (w) {
side->visible = true;
for (j = 0; j < w->numpoints; j++)
AddPointToBounds(w->p[j], ob->mins, ob->maxs);
}
}
for (i = 0; i < 3; i++) {
if (ob->mins[i] < -max_bounds || ob->maxs[i] > max_bounds) {
printf("Entity %i, Brush %i, Line %i: bounds out of range\n", ob->entitynum, ob->brushnum, scriptline + 1); // qb: add scriptline
printf("bounds: %g %g %g -> %g %g %g\n",
ob->mins[0], ob->mins[1], ob->mins[2], ob->maxs[0], ob->maxs[1], ob->maxs[2]);
return;
}
if (ob->mins[i] > max_bounds || ob->maxs[i] < -max_bounds) {
printf("Entity %i, Brush %i, Line %i: no visible sides on brush\n", ob->entitynum, ob->brushnum, scriptline + 1); // qb: add scriptline
printf("bounds: %g %g %g -> %g %g %g\n",
ob->mins[0], ob->mins[1], ob->mins[2], ob->maxs[0], ob->maxs[1], ob->maxs[2]);
return;
}
}
return;
}
/*
=================
ParseBrush
=================
*/
void ParseBrush(entity_t *mapent) {
mapbrush_t *b;
int32_t i, j, k;
int32_t mt;
side_t *side, *s2;
int32_t planenum;
brush_texture_t td;
vec3_t planepts[3];
vec_t UVaxis[6]; // DarkEssence: UV axis in 220 #mapversion
if (use_qbsp) {
if (nummapbrushes == MAX_MAP_BRUSHES_QBSP)
Error("nummapbrushes == MAX_MAP_BRUSHES_QBSP (%i)", MAX_MAP_BRUSHES_QBSP);
} else if (nummapbrushes == MAX_MAP_BRUSHES)
Error("nummapbrushes == MAX_MAP_BRUSHES (%i)", MAX_MAP_BRUSHES);
b = &mapbrushes[nummapbrushes];
b->original_sides = &brushsides[nummapbrushsides];
b->entitynum = num_entities - 1;
b->brushnum = nummapbrushes - mapent->firstbrush;
do {
if (!GetToken(true))
break;
if (!strcmp(token, "}"))
break;
if (use_qbsp) {
if (nummapbrushsides == MAX_MAP_BRUSHSIDES_QBSP)
Error("MAX_MAP_BRUSHSIDES_QBSP");
} else if (nummapbrushsides == MAX_MAP_BRUSHSIDES)
Error("MAX_MAP_BRUSHSIDES");
side = &brushsides[nummapbrushsides];
// read the three point plane definition
for (i = 0; i < 3; i++) {
if (i != 0)
GetToken(true);
if (strcmp(token, "("))
Error("parsing brush %i", i + 1);
for (j = 0; j < 3; j++) {
GetToken(false);
planepts[i][j] = atof(token);
}
GetToken(false);
if (strcmp(token, ")"))
Error("parsing brush %i", i + 1);
}
//
// read the texturedef
//
GetToken(false);
if (!strcmp(token, "__TB_empty")) {
printf("Face without texture ( %s ) at line %i\n", token, scriptline + 1);
}
strcpy(td.name, token);
// DarkEssence: take parms according to mapversion
if (g_nMapFileVersion < 220) // old #mapversion
{
GetToken(false);
td.shift[0] = atoi(token);
GetToken(false);
td.shift[1] = atoi(token);
} else // new #mapversion
{
GetToken(false);
if (strcmp(token, "[")) {
Error("missing '[ in texturedef");
}
GetToken(false);
UVaxis[0] = atof(token);
GetToken(false);
UVaxis[1] = atof(token);
GetToken(false);
UVaxis[2] = atof(token);
GetToken(false);
td.shift[0] = atof(token);
GetToken(false);
if (strcmp(token, "]")) {
Error("missing ']' in texturedef");
}
// texture V axis
GetToken(false);
if (strcmp(token, "[")) {
Error("missing '[ in texturedef");
}
GetToken(false);
UVaxis[3] = atof(token);
GetToken(false);
UVaxis[4] = atof(token);
GetToken(false);
UVaxis[5] = atof(token);
GetToken(false);
td.shift[1] = atof(token);
GetToken(false);
if (strcmp(token, "]")) {
Error("missing ']' in texturedef");
}
}
GetToken(false);
td.rotate = atoi(token);
GetToken(false);
td.scale[0] = atof(token);
GetToken(false);
td.scale[1] = atof(token);
// find default flags and values
mt = FindMiptex(td.name);
td.flags = textureref[mt].flags;
td.value = textureref[mt].value;
side->contents = textureref[mt].contents;
side->surf = td.flags = textureref[mt].flags;
if (TokenAvailable()) {
GetToken(false);
side->contents = atoi(token);
GetToken(false);
side->surf = td.flags = atoi(token);
GetToken(false);
td.value = atoi(token);
}
// translucent objects are automatically classified as detail
if (side->surf & (SURF_TRANS33 | SURF_TRANS66))
side->contents |= CONTENTS_DETAIL;
if (side->contents & (CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP))
side->contents |= CONTENTS_DETAIL;
if (fulldetail)
side->contents &= ~CONTENTS_DETAIL;
if (!(side->contents & ((LAST_VISIBLE_CONTENTS - 1) | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST)))
side->contents |= CONTENTS_SOLID;
// qb: don't change SURF_SKIP contents
if (noskipfix) {
if (side->surf & SURF_HINT) {
side->contents = 0;
side->surf &= ~CONTENTS_DETAIL;
}
} else {
// hints and skips have no contents
if (side->surf & (SURF_HINT | SURF_SKIP)) {
side->contents = 0;
side->surf &= ~CONTENTS_DETAIL;
}
}
//
// find the plane number
//
planenum = PlaneFromPoints(planepts[0], planepts[1], planepts[2], b);
if (planenum == -1) {
printf("Entity %i, Brush %i, Line %i: plane with no normal\n", b->entitynum, b->brushnum, scriptline + 1); // qb: add scriptline
continue;
}
//
// see if the plane has been used already
//
for (k = 0; k < b->numsides; k++) {
s2 = b->original_sides + k;
if (s2->planenum == planenum) {
printf("Entity %i, Brush %i, Line %i: duplicate plane\n", b->entitynum, b->brushnum, scriptline + 1); // qb: add scriptline
break;
}
if (s2->planenum == (planenum ^ 1)) {
printf("Entity %i, Brush %i, Line %i: mirrored plane\n", b->entitynum, b->brushnum, scriptline + 1); // qb: add scriptline
break;
}
}
if (k != b->numsides)
continue; // duplicated
//
// keep this side
//
side = b->original_sides + b->numsides;
side->planenum = planenum;
if (g_nMapFileVersion < 220) // DarkEssence: texinfo #mapversion
side->texinfo = TexinfoForBrushTexture(&mapplanes[planenum], &td, vec3_origin);
else // texinfo for #mapversion 220
side->texinfo = TexinfoForBrushTexture_UV(&td, UVaxis);
// save the td off in case there is an origin brush and we have to recalculate the texinfo
side_brushtextures[nummapbrushsides] = td;
nummapbrushsides++;
b->numsides++;
} while (true);
// get the content for the entire brush
b->contents = BrushContents(b);
// allow detail brushes to be removed
if (nodetail && (b->contents & CONTENTS_DETAIL)) {
b->numsides = 0;
return;
}
// allow water brushes to be removed
if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))) {
b->numsides = 0;
return;
}
// create windings for sides and bounds for brush
MakeBrushWindings(b);
// brushes that will not be visible at all will never be
// used as bsp splitters
if (b->contents & (CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP)) {
c_clipbrushes++;
for (i = 0; i < b->numsides; i++)
b->original_sides[i].texinfo = TEXINFO_NODE;
}
//
// origin brushes are removed, but they set the rotation origin for the rest of the brushes in the entity.
// After the entire entity is parsed, the planenums and texinfos will be adjusted for the origin brush
//
if (b->contents & CONTENTS_ORIGIN) {
char string[32];
vec3_t origin;
if (num_entities == 1) {
Error("Entity %i, Brush %i, Line %i: origin brushes not allowed in world", b->entitynum, b->brushnum, scriptline + 1); // qb: add scriptline
return;
}
VectorAdd(b->mins, b->maxs, origin);
VectorScale(origin, 0.5, origin);
sprintf(string, "%i %i %i", (int32_t)origin[0], (int32_t)origin[1], (int32_t)origin[2]);
SetKeyValue(&entities[b->entitynum], "origin", string);
VectorCopy(origin, entities[b->entitynum].origin);
// don't keep this brush
b->numsides = 0;
return;
}
AddBrushBevels(b);
nummapbrushes++;
mapent->numbrushes++;
}
/*
================
MoveBrushesToWorld
Takes all of the brushes from the current entity and
adds them to the world's brush list.
Used by func_group and func_areaportal
================
*/
void MoveBrushesToWorld(entity_t *mapent) {
int32_t newbrushes;
int32_t worldbrushes;
mapbrush_t *temp;
int32_t i;
// this is pretty gross, because the brushes are expected to be
// in linear order for each entity
newbrushes = mapent->numbrushes;
worldbrushes = entities[0].numbrushes;
temp = malloc(newbrushes * sizeof(mapbrush_t));
memcpy(temp, mapbrushes + mapent->firstbrush, newbrushes * sizeof(mapbrush_t));
#if 0 // let them keep their original brush numbers
for (i=0 ; i<newbrushes ; i++)
temp[i].entitynum = 0;
#endif
// make space to move the brushes (overlapped copy)
memmove(mapbrushes + worldbrushes + newbrushes,
mapbrushes + worldbrushes,
sizeof(mapbrush_t) * (nummapbrushes - worldbrushes - newbrushes));
// copy the new brushes down
memcpy(mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes);
// fix up indexes
entities[0].numbrushes += newbrushes;
for (i = 1; i < num_entities; i++)
entities[i].firstbrush += newbrushes;
free(temp);
mapent->numbrushes = 0;
}
/*
================
ParseMapEntity
================
*/
qboolean ParseMapEntity(void) {
mapbrush_t *b;
if (!GetToken(true))
return false;
if (strcmp(token, "{"))
Error("ParseEntity: { not found");
if (use_qbsp) {
if (num_entities == WARN_MAP_ENTITIES_QBSP)
printf("WARNING: num_entities may exceed protocol limit (%i)", WARN_MAP_ENTITIES_QBSP);
if (num_entities == max_entities)
Error("num_entities exceeds MAX_MAP_ENTITIES_QBSP (%i)", MAX_MAP_ENTITIES_QBSP);
} else {
if (num_entities == DEFAULT_MAP_ENTITIES)
printf("WARNING: num_entities exceeds vanilla limit (%i)", DEFAULT_MAP_ENTITIES);
if (num_entities == max_entities) // qb: from kmqbsp3 Knightmare changed- was MAX_MAP_ENTITIES
Error("num_entities exceeds MAX_MAP_ENTITIES (%i)", MAX_MAP_ENTITIES);
}
entity_t *mapent = &entities[num_entities];
num_entities++;
memset(mapent, 0, sizeof(*mapent));
mapent->firstbrush = nummapbrushes;
mapent->numbrushes = 0;
// mapent->portalareas[0] = -1;
// mapent->portalareas[1] = -1;
do {
if (!GetToken(true))
Error("ParseEntity: EOF without closing brace");
if (!strcmp(token, "}"))
break;
if (!strcmp(token, "{")) {
ParseBrush(mapent);
} else {
epair_t *e = ParseEpair();
e->next = mapent->epairs;
mapent->epairs = e;
if (!strcmp(e->key, "mapversion")) // DarkEssence: set #mapversion
{
g_nMapFileVersion = atoi(e->value); // or keep default value - 0
RemoveLastEpair(mapent);
}
}
} while (true);
GetVectorForKey(mapent, "origin", mapent->origin);
//
// if there was an origin brush, offset all of the planes and texinfo
//
if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) {
for (int32_t i = 0; i < mapent->numbrushes; i++) {
b = &mapbrushes[mapent->firstbrush + i];
for (int32_t j = 0; j < b->numsides; j++) {
side_t *s = &b->original_sides[j];
const vec_t newdist = mapplanes[s->planenum].dist - DotProduct(mapplanes[s->planenum].normal, mapent->origin);
s->planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist, b->brushnum);
if (g_nMapFileVersion < 220) // mxd
s->texinfo = TexinfoForBrushTexture(&mapplanes[s->planenum], &side_brushtextures[s - brushsides], mapent->origin);
else
s->texinfo = ApplyTexinfoOffset_UV(s->texinfo, &side_brushtextures[s - brushsides], mapent->origin);
}
MakeBrushWindings(b);
}
}
// group entities are just for editor convenience
// toss all brushes into the world entity
if (!strcmp("func_group", ValueForKey(mapent, "classname"))) {
MoveBrushesToWorld(mapent);
mapent->numbrushes = 0;
return true;
}
// areaportal entities move their brushes, but don't eliminate
// the entity
if (!strcmp("func_areaportal", ValueForKey(mapent, "classname"))) {
char str[128];
if (mapent->numbrushes != 1)
Error("Entity %i: func_areaportal can only be a single brush", num_entities - 1);
b = &mapbrushes[nummapbrushes - 1];
b->contents = CONTENTS_AREAPORTAL;
c_areaportals++;
mapent->areaportalnum = c_areaportals;
// set the portal number as "style"
sprintf(str, "%i", c_areaportals);
SetKeyValue(mapent, "style", str);
MoveBrushesToWorld(mapent);
return true;
}
return true;
}
//===================================================================
/*
================
LoadMapFile
================
*/
void LoadMapFile(char *filename) {
int32_t i;
qprintf("--- LoadMapFile ---\n");
LoadScriptFile(filename);
nummapbrushsides = 0;
num_entities = 0;
while (ParseMapEntity()) {
}
ClearBounds(map_mins, map_maxs);
for (i = 0; i < entities[0].numbrushes; i++) {
if (mapbrushes[i].mins[0] > max_bounds)
continue; // no valid points
AddPointToBounds(mapbrushes[i].mins, map_mins, map_maxs);
AddPointToBounds(mapbrushes[i].maxs, map_mins, map_maxs);
}
qprintf("%5i brushes\n", nummapbrushes);
qprintf("%5i clipbrushes\n", c_clipbrushes);
qprintf("%5i total sides\n", nummapbrushsides);
qprintf("%5i boxbevels\n", c_boxbevels);
qprintf("%5i edgebevels\n", c_edgebevels);
qprintf("%5i entities\n", num_entities);
qprintf("%5i planes\n", nummapplanes);
qprintf("%5i areaportals\n", c_areaportals);
qprintf("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0], map_mins[1], map_mins[2],
map_maxs[0], map_maxs[1], map_maxs[2]);
// TestExpandBrushes ();
}
//====================================================================
/*
================
TestExpandBrushes
Expands all the brush planes and saves a new map out
================
*/
void TestExpandBrushes(void) {
FILE *f;
side_t *s;
int32_t i, j, bn;
winding_t *w;
char *name = "expanded.map";
mapbrush_t *brush;
vec_t dist;
f = fopen(name, "wb");
if (!f)
Error("Can't write %s\b", name);
fprintf(f, "{\n\"classname\" \"worldspawn\"\n");
for (bn = 0; bn < nummapbrushes; bn++) {
brush = &mapbrushes[bn];
fprintf(f, "{\n");
for (i = 0; i < brush->numsides; i++) {
s = brush->original_sides + i;
dist = mapplanes[s->planenum].dist;
for (j = 0; j < 3; j++)
dist += fabs(16 * mapplanes[s->planenum].normal[j]);
w = BaseWindingForPlane(mapplanes[s->planenum].normal, dist);
fprintf(f, "( %g %g %g ) ", w->p[0][0], w->p[0][1], w->p[0][2]);
fprintf(f, "( %g %g %g ) ", w->p[1][0], w->p[1][1], w->p[1][2]);
fprintf(f, "( %g %g %g ) ", w->p[2][0], w->p[2][1], w->p[2][2]);
fprintf(f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture);
FreeWinding(w);
}
fprintf(f, "}\n");
}
fprintf(f, "}\n");
fclose(f);
Error("can't proceed after expanding brushes");
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
/*
==============================================================================
LEAF FILE GENERATION
Save out name.line for qe3 to read
==============================================================================
*/
/*
=============
LeakFile
Finds the shortest possible chain of portals
that leads from the outside leaf to a specifically
occupied leaf
=============
*/
void LeakFile(tree_t *tree) {
vec3_t mid;
FILE *linefile;
char filename[1030];
node_t *node;
int32_t count;
if (!tree->outside_node.occupied)
return;
qprintf("--- LeakFile ---\n");
//
// write the points to the file
//
sprintf(filename, "%s.pts", source);
linefile = fopen(filename, "w");
if (!linefile)
Error("Couldn't open %s\n", filename);
count = 0;
node = &tree->outside_node;
while (node->occupied > 1) {
int32_t next;
portal_t *p, *nextportal = NULL;
node_t *nextnode = NULL;
int32_t s;
// find the best portal exit
next = node->occupied;
for (p = node->portals; p; p = p->next[!s]) {
s = (p->nodes[0] == node);
if (p->nodes[s]->occupied && p->nodes[s]->occupied < next) {
nextportal = p;
nextnode = p->nodes[s];
next = nextnode->occupied;
}
}
node = nextnode;
if (nextportal) // qb: could be NULL
{
WindingCenter(nextportal->winding, mid);
fprintf(linefile, "%f %f %f\n", mid[0], mid[1], mid[2]);
}
count++;
}
// add the occupant center
GetVectorForKey(node->occupant, "origin", mid);
fprintf(linefile, "%f %f %f\n", mid[0], mid[1], mid[2]);
qprintf("%5i point linefile\n", count + 1);
fclose(linefile);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
/*
some faces will be removed before saving, but still form nodes:
the insides of sky volumes
meeting planes of different water current volumes
*/
#define INTEGRAL_EPSILON 0.01
// qb: was 0.5. fix by tapir to 0.1 via jdolan on insideqc.
//qb: update- 0.1 causes gaps. Possibly just needs to be slightly less than OFF_EPSILON
#define POINT_EPSILON 0.49
#define OFF_EPSILON 0.5
extern brush_texture_t side_brushtextures[MAX_MAP_SIDES]; // qb: kmbsp3: caulk
int32_t c_merge;
int32_t c_subdivide;
int32_t c_totalverts;
int32_t c_uniqueverts;
int32_t c_degenerate;
int32_t c_tjunctions;
int32_t c_faceoverflows;
int32_t c_facecollapse;
int32_t c_badstartverts;
#define MAX_SUPERVERTS 512
int32_t superverts[MAX_SUPERVERTS];
int32_t numsuperverts;
face_t *edgefaces[MAX_MAP_EDGES_QBSP][2];
int32_t firstmodeledge = 1;
int32_t firstmodelface;
int32_t c_tryedges;
vec3_t edge_dir;
vec3_t edge_start;
vec_t edge_len;
int32_t num_edge_verts;
int32_t edge_verts[MAX_MAP_VERTS_QBSP];
float subdivide_size = 240;
float sublight_size = 240;
face_t *NewFaceFromFace(face_t *f);
//===========================================================================
typedef struct hashvert_s {
struct hashvert_s *next;
int32_t num;
} hashvert_t;
#define HASH_SIZE MAX_POINTS_HASH // qb: per kmbsp3. Was 64
int32_t vertexchain[MAX_MAP_VERTS_QBSP]; // the next vertex in a hash chain
int32_t hashverts[HASH_SIZE * HASH_SIZE]; // a vertex number, or 0 for no verts
//============================================================================
unsigned HashVec(vec3_t vec) {
int32_t x, y;
x = (max_bounds + (int32_t)(vec[0] + 0.5)) >> 7;
y = (max_bounds + (int32_t)(vec[1] + 0.5)) >> 7;
if (x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE)
Error("HashVec: point outside valid range");
return y * HASH_SIZE + x;
}
vec_t g_min_vertex_diff_sq = BOGUS_RANGE; // jitdebug
vec3_t g_min_vertex_pos; // jitdebug
/*
=============
GetVertex
Uses hashing
=============
*/
int32_t GetVertexnum(vec3_t in) {
int32_t h;
int32_t i;
float *p;
vec3_t vert;
int32_t vnum;
c_totalverts++;
for (i = 0; i < 3; i++) {
if (fabs(in[i] - Q_rint(in[i])) < INTEGRAL_EPSILON)
vert[i] = Q_rint(in[i]);
else
vert[i] = in[i];
}
h = HashVec(vert);
for (vnum = hashverts[h]; vnum; vnum = vertexchain[vnum]) {
vec3_t diff;
vec_t length_sq; // jit
p = dvertexes[vnum].point;
VectorSubtract(p, vert, diff); // jit
length_sq = VectorLengthSq(diff); // jit
if (length_sq < POINT_EPSILON * POINT_EPSILON)
return vnum;
if (length_sq < g_min_vertex_diff_sq) // jitdebug
{
g_min_vertex_diff_sq = length_sq; // jitdebug
VectorCopy(p, g_min_vertex_pos);
}
}
// emit a vertex
if (use_qbsp) {
if (numvertexes == MAX_MAP_VERTS_QBSP)
Error("MAX_MAP_VERTS_QBSP");
} else if (numvertexes == MAX_MAP_VERTS)
Error("MAX_MAP_VERTS");
dvertexes[numvertexes].point[0] = vert[0];
dvertexes[numvertexes].point[1] = vert[1];
dvertexes[numvertexes].point[2] = vert[2];
vertexchain[numvertexes] = hashverts[h];
hashverts[h] = numvertexes;
c_uniqueverts++;
numvertexes++;
return numvertexes - 1;
}
/*
==================
FaceFromSuperverts
The faces vertexes have beeb added to the superverts[] array,
and there may be more there than can be held in a face (MAXEDGES).
If less, the faces vertexnums[] will be filled in, otherwise
face will reference a tree of split[] faces until all of the
vertexnums can be added.
superverts[base] will become face->vertexnums[0], and the others
will be circularly filled in.
==================
*/
void FaceFromSuperverts(node_t *node, face_t *f, int32_t base) {
face_t *newf;
int32_t remaining;
int32_t i;
remaining = numsuperverts;
while (remaining > MAXEDGES) {
// must split into two faces, because of vertex overload
c_faceoverflows++;
newf = f->split[0] = NewFaceFromFace(f);
newf = f->split[0];
newf->next = node->faces;
node->faces = newf;
newf->numpoints = MAXEDGES;
for (i = 0; i < MAXEDGES; i++)
newf->vertexnums[i] = superverts[(i + base) % numsuperverts];
f->split[1] = NewFaceFromFace(f);
f = f->split[1];
f->next = node->faces;
node->faces = f;
remaining -= (MAXEDGES - 2);
base = (base + MAXEDGES - 1) % numsuperverts;
}
// copy the vertexes back to the face
f->numpoints = remaining;
for (i = 0; i < remaining; i++)
f->vertexnums[i] = superverts[(i + base) % numsuperverts];
}
/*
==================
EmitFaceVertexes
==================
*/
void EmitFaceVertexes(node_t *node, face_t *f) {
winding_t *w;
int32_t i;
if (f->merged || f->split[0] || f->split[1])
return;
w = f->w;
for (i = 0; i < w->numpoints; i++) {
if (noweld) {
// make every point unique
if (use_qbsp) {
if (numvertexes == MAX_MAP_VERTS_QBSP)
Error("MAX_MAP_VERTS_QBSP");
} else if (numvertexes == MAX_MAP_VERTS)
Error("MAX_MAP_VERTS");
superverts[i] = numvertexes;
VectorCopy(w->p[i], dvertexes[numvertexes].point);
numvertexes++;
c_uniqueverts++;
c_totalverts++;
} else
superverts[i] = GetVertexnum(w->p[i]);
}
numsuperverts = w->numpoints;
// this may fragment the face if > MAXEDGES
FaceFromSuperverts(node, f, 0);
}
/*
==================
EmitVertexes_r
==================
*/
void EmitVertexes_r(node_t *node) {
int32_t i;
face_t *f;
if (node->planenum == PLANENUM_LEAF)
return;
for (f = node->faces; f; f = f->next) {
EmitFaceVertexes(node, f);
}
for (i = 0; i < 2; i++)
EmitVertexes_r(node->children[i]);
}
/*
==========
FindEdgeVerts
Uses the hash tables to cut down to a small number
==========
*/
void FindEdgeVerts(vec3_t v1, vec3_t v2) {
int32_t x1, x2, y1, y2, t;
int32_t x, y;
int32_t vnum;
x1 = (max_bounds + (int32_t)(v1[0] + 0.5)) >> 7;
y1 = (max_bounds + (int32_t)(v1[1] + 0.5)) >> 7;
x2 = (max_bounds + (int32_t)(v2[0] + 0.5)) >> 7;
y2 = (max_bounds + (int32_t)(v2[1] + 0.5)) >> 7;
if (x1 > x2) {
t = x1;
x1 = x2;
x2 = t;
}
if (y1 > y2) {
t = y1;
y1 = y2;
y2 = t;
}
num_edge_verts = 0;
for (x = x1; x <= x2; x++) {
for (y = y1; y <= y2; y++) {
for (vnum = hashverts[y * HASH_SIZE + x]; vnum; vnum = vertexchain[vnum]) {
edge_verts[num_edge_verts++] = vnum;
}
}
}
}
/*
==========
TestEdge
Can be recursively reentered
==========
*/
void TestEdge(vec_t start, vec_t end, int32_t p1, int32_t p2, int32_t startvert) {
int32_t j, k;
vec_t dist;
vec3_t delta;
vec3_t exact;
vec3_t off;
vec_t error;
vec3_t p;
if (p1 == p2) {
c_degenerate++;
return; // degenerate edge
}
for (k = startvert; k < num_edge_verts; k++) {
j = edge_verts[k];
if (j == p1 || j == p2)
continue;
VectorCopy(dvertexes[j].point, p);
VectorSubtract(p, edge_start, delta);
dist = DotProduct(delta, edge_dir);
if (dist <= start || dist >= end)
continue; // off an end
VectorMA(edge_start, dist, edge_dir, exact);
VectorSubtract(p, exact, off);
error = VectorLength(off);
if (fabs(error) > OFF_EPSILON)
continue; // not on the edge
// break the edge
c_tjunctions++;
TestEdge(start, dist, p1, j, k + 1);
TestEdge(dist, end, j, p2, k + 1);
return;
}
// the edge p1 to p2 is now free of tjunctions
if (numsuperverts >= MAX_SUPERVERTS)
Error("MAX_SUPERVERTS");
superverts[numsuperverts] = p1;
numsuperverts++;
}
/*
==================
FixFaceEdges
==================
*/
void FixFaceEdges(node_t *node, face_t *f) {
int32_t p1, p2;
int32_t i;
vec3_t e2;
vec_t len;
int32_t count[MAX_SUPERVERTS], start[MAX_SUPERVERTS];
int32_t base;
if (f->merged || f->split[0] || f->split[1])
return;
numsuperverts = 0;
for (i = 0; i < f->numpoints; i++) {
p1 = f->vertexnums[i];
p2 = f->vertexnums[(i + 1) % f->numpoints];
VectorCopy(dvertexes[p1].point, edge_start);
VectorCopy(dvertexes[p2].point, e2);
FindEdgeVerts(edge_start, e2);
VectorSubtract(e2, edge_start, edge_dir);
len = VectorNormalize(edge_dir, edge_dir);
start[i] = numsuperverts;
TestEdge(0, len, p1, p2, 0);
count[i] = numsuperverts - start[i];
}
if (numsuperverts < 3) {
// entire face collapsed
f->numpoints = 0;
c_facecollapse++;
return;
}
// we want to pick a vertex that doesn't have tjunctions
// on either side, which can cause artifacts on trifans,
// especially underwater
for (i = 0; i < f->numpoints; i++) {
if (count[i] == 1 && count[(i + f->numpoints - 1) % f->numpoints] == 1)
break;
}
if (i == f->numpoints) {
f->badstartvert = true;
c_badstartverts++;
base = 0;
} else {
// rotate the vertex order
base = start[i];
}
// this may fragment the face if > MAXEDGES
FaceFromSuperverts(node, f, base);
}
/*
==================
FixEdges_r
==================
*/
void FixEdges_r(node_t *node) {
int32_t i;
face_t *f;
if (node->planenum == PLANENUM_LEAF)
return;
for (f = node->faces; f; f = f->next)
FixFaceEdges(node, f);
for (i = 0; i < 2; i++)
FixEdges_r(node->children[i]);
}
/*
===========
FixTjuncs
===========
*/
void FixTjuncs(node_t *headnode) {
// snap and merge all vertexes
qprintf("---- snap verts ----\n");
memset(hashverts, 0, sizeof(hashverts));
c_totalverts = 0;
c_uniqueverts = 0;
c_faceoverflows = 0;
EmitVertexes_r(headnode);
qprintf("%i unique from %i\n", c_uniqueverts, c_totalverts);
// break edges on tjunctions
qprintf("---- tjunc ----\n");
c_tryedges = 0;
c_degenerate = 0;
c_facecollapse = 0;
c_tjunctions = 0;
if (!notjunc)
FixEdges_r(headnode);
qprintf("%5i edges degenerated\n", c_degenerate);
qprintf("%5i faces degenerated\n", c_facecollapse);
qprintf("%5i edges added by tjunctions\n", c_tjunctions);
qprintf("%5i faces added by tjunctions\n", c_faceoverflows);
qprintf("%5i bad start verts\n", c_badstartverts);
}
//========================================================
int32_t c_faces;
face_t *AllocFace(void) {
face_t *f;
f = malloc(sizeof(face_t));
memset(f, 0, sizeof(face_t));
c_faces++;
return f;
}
face_t *NewFaceFromFace(face_t *f) {
face_t *newf;
newf = AllocFace();
*newf = *f;
newf->merged = NULL;
newf->split[0] = newf->split[1] = NULL;
newf->w = NULL;
return newf;
}
void FreeFace(face_t *f) {
if (f->w)
FreeWinding(f->w);
free(f);
c_faces--;
}
//========================================================
/*
==================
GetEdge
Called by writebsp.
Don't allow four way edges
==================
*/
int32_t GetEdge(int32_t v1, int32_t v2, face_t *f) {
int32_t i;
c_tryedges++;
if (!noshare) {
if (use_qbsp) {
dedge_tx *edge;
for (i = firstmodeledge; i < numedges; i++) {
edge = &dedgesX[i];
if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[i][0]->contents == f->contents) {
if (edgefaces[i][1])
// printf ("WARNING: multiple backward edge\n");
continue;
edgefaces[i][1] = f;
return -i;
}
}
// emit an edge
if (numedges >= MAX_MAP_EDGES_QBSP)
Error("numedges == MAX_MAP_EDGES_QBSP");
edge = &dedgesX[numedges];
numedges++;
edge->v[0] = v1;
edge->v[1] = v2;
edgefaces[numedges - 1][0] = f;
} else {
dedge_t *edge;
for (i = firstmodeledge; i < numedges; i++) {
edge = &dedges[i];
if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[i][0]->contents == f->contents) {
if (edgefaces[i][1])
// printf ("WARNING: multiple backward edge\n");
continue;
edgefaces[i][1] = f;
return -i;
}
}
// emit an edge
if (numedges >= MAX_MAP_EDGES)
Error("numedges == MAX_MAP_EDGES");
edge = &dedges[numedges];
numedges++;
edge->v[0] = v1;
edge->v[1] = v2;
edgefaces[numedges - 1][0] = f;
}
}
return numedges - 1;
}
/*
===========================================================================
FACE MERGING
===========================================================================
*/
#define CONTINUOUS_EPSILON 0.001
/*
=============
TryMergeWinding
If two polygons share a common edge and the edges that meet at the
common points are both inside the other polygons, merge them
Returns NULL if the faces couldn't be merged, or the new face.
The originals will NOT be freed.
=============
*/
winding_t *TryMergeWinding(winding_t *f1, winding_t *f2, vec3_t planenormal) {
vec_t *p1, *p2, *p3, *p4, *back;
winding_t *newf;
int32_t i, j, k, l;
vec3_t normal, delta;
vec_t dot;
qboolean keep1, keep2;
//
// find a common edge
//
p1 = p2 = NULL; // stop compiler warning
j = 0; //
for (i = 0; i < f1->numpoints; i++) {
p1 = f1->p[i];
p2 = f1->p[(i + 1) % f1->numpoints];
for (j = 0; j < f2->numpoints; j++) {
p3 = f2->p[j];
p4 = f2->p[(j + 1) % f2->numpoints];
for (k = 0; k < 3; k++) {
if (fabs(p1[k] - p4[k]) > EQUAL_EPSILON)
break;
if (fabs(p2[k] - p3[k]) > EQUAL_EPSILON)
break;
}
if (k == 3)
break;
}
if (j < f2->numpoints)
break;
}
if (i == f1->numpoints)
return NULL; // no matching edges
//
// check slope of connected lines
// if the slopes are colinear, the point can be removed
//
back = f1->p[(i + f1->numpoints - 1) % f1->numpoints];
VectorSubtract(p1, back, delta);
CrossProduct(planenormal, delta, normal);
VectorNormalize(normal, normal);
back = f2->p[(j + 2) % f2->numpoints];
VectorSubtract(back, p1, delta);
dot = DotProduct(delta, normal);
if (dot > CONTINUOUS_EPSILON)
return NULL; // not a convex polygon
keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON);
back = f1->p[(i + 2) % f1->numpoints];
VectorSubtract(back, p2, delta);
CrossProduct(planenormal, delta, normal);
VectorNormalize(normal, normal);
back = f2->p[(j + f2->numpoints - 1) % f2->numpoints];
VectorSubtract(back, p2, delta);
dot = DotProduct(delta, normal);
if (dot > CONTINUOUS_EPSILON)
return NULL; // not a convex polygon
keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON);
//
// build the new polygon
//
newf = AllocWinding(f1->numpoints + f2->numpoints);
// copy first polygon
for (k = (i + 1) % f1->numpoints; k != i; k = (k + 1) % f1->numpoints) {
if (k == (i + 1) % f1->numpoints && !keep2)
continue;
VectorCopy(f1->p[k], newf->p[newf->numpoints]);
newf->numpoints++;
}
// copy second polygon
for (l = (j + 1) % f2->numpoints; l != j; l = (l + 1) % f2->numpoints) {
if (l == (j + 1) % f2->numpoints && !keep1)
continue;
VectorCopy(f2->p[l], newf->p[newf->numpoints]);
newf->numpoints++;
}
return newf;
}
/*
=============
TryMerge
If two polygons share a common edge and the edges that meet at the
common points are both inside the other polygons, merge them
Returns NULL if the faces couldn't be merged, or the new face.
The originals will NOT be freed.
=============
*/
face_t *TryMerge(face_t *f1, face_t *f2, vec3_t planenormal) {
face_t *newf;
winding_t *nw;
if (!f1->w || !f2->w)
return NULL;
if (f1->texinfo != f2->texinfo)
return NULL;
if (f1->planenum != f2->planenum) // on front and back sides
return NULL;
if (f1->contents != f2->contents)
return NULL;
nw = TryMergeWinding(f1->w, f2->w, planenormal);
if (!nw)
return NULL;
c_merge++;
newf = NewFaceFromFace(f1);
newf->w = nw;
f1->merged = newf;
f2->merged = newf;
return newf;
}
/*
===============
MergeNodeFaces
===============
*/
void MergeNodeFaces(node_t *node) {
face_t *f1, *f2, *end;
face_t *merged;
plane_t *plane;
merged = NULL;
for (f1 = node->faces; f1; f1 = f1->next) {
if (f1->merged || f1->split[0] || f1->split[1])
continue;
for (f2 = node->faces; f2 != f1; f2 = f2->next) {
if (f2->merged || f2->split[0] || f2->split[1])
continue;
plane = &mapplanes[f1->planenum];
merged = TryMerge(f1, f2, plane->normal);
if (!merged)
continue;
// add merged to the end of the node face list
// so it will be checked against all the faces again
for (end = node->faces; end->next; end = end->next)
;
merged->next = NULL;
end->next = merged;
break;
}
}
}
//=====================================================================
/*
===============
SubdivideFace
Chop up faces that are larger than we want in the surface cache
===============
*/
void SubdivideFace(node_t *node, face_t *f) {
vec_t mins, maxs;
vec_t v;
int32_t axis, i;
texinfo_t *tex;
vec3_t temp;
vec_t dist;
winding_t *w, *frontw, *backw;
if (f->merged)
return;
// special (non-surface cached) faces don't need subdivision
tex = &texinfo[f->texinfo];
if (tex->flags & (SURF_WARP | SURF_SKY)) {
return;
}
for (axis = 0; axis < 2; axis++) {
while (1) {
mins = BOGUS_RANGE;
maxs = -BOGUS_RANGE;
VectorCopy(tex->vecs[axis], temp);
w = f->w;
for (i = 0; i < w->numpoints; i++) {
v = DotProduct(w->p[i], temp);
if (v < mins)
mins = v;
if (v > maxs)
maxs = v;
}
v = VectorNormalize(temp, temp);
if (tex->flags & SURF_LIGHT) {
if (maxs - mins <= sublight_size) // qb: control surf light chop independently
break;
else
dist = (mins + sublight_size - 16) / v;
} else {
if (maxs - mins <= subdivide_size)
break;
else
dist = (mins + subdivide_size - 16) / v;
}
// split it
c_subdivide++;
ClipWindingEpsilon(w, temp, dist, ON_EPSILON, &frontw, &backw);
if (!frontw || !backw)
Error("SubdivideFace: didn't split the polygon\n Node Bounds: %g %g %g -> %g %g %g\n",
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
f->split[0] = NewFaceFromFace(f);
f->split[0]->w = frontw;
f->split[0]->next = node->faces;
node->faces = f->split[0];
f->split[1] = NewFaceFromFace(f);
f->split[1]->w = backw;
f->split[1]->next = node->faces;
node->faces = f->split[1];
SubdivideFace(node, f->split[0]);
SubdivideFace(node, f->split[1]);
return;
}
}
}
void SubdivideNodeFaces(node_t *node) {
face_t *f;
for (f = node->faces; f; f = f->next) {
SubdivideFace(node, f);
}
}
//===========================================================================
int32_t c_nodefaces;
/*
============
FaceFromPortal
============
*/
face_t *FaceFromPortal(portal_t *p, int32_t pside) {
face_t *f;
side_t *side;
side = p->side;
if (!side)
return NULL; // portal does not bridge different visible contents
// qb: Paril noted SURF_NODRAW was not processed anywhere, pulled here.
// Turns out it was not implemented in original compiler tools or engine.
// SURF_SKY textures are often also flagged nodraw but need to keep those.
if (!strcmp(side_brushtextures[side - brushsides].name, "common/caulk") ||
((side_brushtextures[side - brushsides].flags & SURF_NODRAW) && !(side_brushtextures[side - brushsides].flags & SURF_SKY)))
return NULL;
f = AllocFace();
f->texinfo = side->texinfo;
f->planenum = (side->planenum & ~1) | pside;
f->portal = p;
if ((p->nodes[pside]->contents & CONTENTS_WINDOW) && VisibleContents(p->nodes[!pside]->contents ^ p->nodes[pside]->contents) == CONTENTS_WINDOW)
return NULL; // don't show insides of windows
if ((p->nodes[pside]->contents & CONTENTS_AUX) && VisibleContents(p->nodes[!pside]->contents ^ p->nodes[pside]->contents) == CONTENTS_AUX)
return NULL; // qb: don't show insides of CONTENTS_AUX
if (pside) {
f->w = ReverseWinding(p->winding);
f->contents = p->nodes[1]->contents;
} else {
f->w = CopyWinding(p->winding);
f->contents = p->nodes[0]->contents;
}
return f;
}
/*
===============
MakeFaces_r
If a portal will make a visible face,
mark the side that originally created it
solid / empty : solid
solid / water : solid
water / empty : water
water / water : none
===============
*/
void MakeFaces_r(node_t *node) {
portal_t *p;
int32_t s;
// recurse down to leafs
if (node->planenum != PLANENUM_LEAF) {
MakeFaces_r(node->children[0]);
MakeFaces_r(node->children[1]);
// merge together all visible faces on the node
if (!nomerge)
MergeNodeFaces(node);
if (!nosubdiv)
SubdivideNodeFaces(node);
return;
}
// solid leafs never have visible faces
if (node->contents & CONTENTS_SOLID)
return;
// see which portals are valid
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
p->face[s] = FaceFromPortal(p, s);
if (p->face[s]) {
c_nodefaces++;
p->face[s]->next = p->onnode->faces;
p->onnode->faces = p->face[s];
}
}
}
/*
============
MakeFaces
============
*/
void MakeFaces(node_t *node) {
qprintf("--- MakeFaces ---\n");
c_merge = 0;
c_subdivide = 0;
c_nodefaces = 0;
MakeFaces_r(node);
qprintf("%5i makefaces\n", c_nodefaces);
qprintf("%5i merged\n", c_merge);
qprintf("%5i subdivided\n", c_subdivide);
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
/*
tag all brushes with original contents
brushes may contain multiple contents
there will be no brush overlap after csg phase
each side has a count of the other sides it splits
the best split will be the one that minimizes the total split counts
of all remaining sides
precalc side on plane table
evaluate split side
{
cost = 0
for all sides
for all sides
get
if side splits side and splitside is on same child
cost++;
}
*/
void SplitBrush2(bspbrush_t *brush, int32_t planenum,
bspbrush_t **front, bspbrush_t **back) {
SplitBrush(brush, planenum, front, back);
#if 0
if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1)
(*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1
if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1)
(*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1
#endif
}
/*
===============
SubtractBrush
Returns a list of brushes that remain after B is subtracted from A.
May by empty if A is contained inside B.
The originals are undisturbed.
===============
*/
bspbrush_t *SubtractBrush(bspbrush_t *a, bspbrush_t *b) {
// a - b = out (list)
int32_t i;
bspbrush_t *front, *back;
bspbrush_t *out, *in;
in = a;
out = NULL;
for (i = 0; i < b->numsides && in; i++) {
SplitBrush2(in, b->sides[i].planenum, &front, &back);
if (in != a)
FreeBrush(in);
if (front) {
// add to list
front->next = out;
out = front;
}
in = back;
}
if (in)
FreeBrush(in);
else {
// didn't really intersect
FreeBrushList(out);
return a;
}
return out;
}
/*
===============
IntersectBrush
Returns a single brush made up by the intersection of the
two provided brushes, or NULL if they are disjoint.
The originals are undisturbed.
===============
*/
bspbrush_t *IntersectBrush(bspbrush_t *a, bspbrush_t *b) {
int32_t i;
bspbrush_t *front, *back;
bspbrush_t *in;
in = a;
for (i = 0; i < b->numsides && in; i++) {
SplitBrush2(in, b->sides[i].planenum, &front, &back);
if (in != a)
FreeBrush(in);
if (front)
FreeBrush(front);
in = back;
}
if (in == a)
return NULL;
in->next = NULL;
return in;
}
/*
===============
BrushesDisjoint
Returns true if the two brushes definately do not intersect.
There will be false negatives for some non-axial combinations.
===============
*/
qboolean BrushesDisjoint(bspbrush_t *a, bspbrush_t *b) {
int32_t i, j;
// check bounding boxes
for (i = 0; i < 3; i++)
if (a->mins[i] >= b->maxs[i] || a->maxs[i] <= b->mins[i])
return true; // bounding boxes don't overlap
// check for opposing planes
for (i = 0; i < a->numsides; i++) {
for (j = 0; j < b->numsides; j++) {
if (a->sides[i].planenum ==
(b->sides[j].planenum ^ 1))
return true; // opposite planes, so not touching
}
}
return false; // might intersect
}
/*
===============
IntersectionContents
Returns a content word for the intersection of two brushes.
Some combinations will generate a combination (water + clip),
but most will be the stronger of the two contents.
===============
*/
int32_t IntersectionContents(int32_t c1, int32_t c2) {
int32_t out;
out = c1 | c2;
if (out & CONTENTS_SOLID)
out = CONTENTS_SOLID;
return out;
}
int32_t minplanenums[3];
int32_t maxplanenums[3];
/*
===============
ClipBrushToBox
Any planes shared with the box edge will be set to no texinfo
===============
*/
bspbrush_t *ClipBrushToBox(bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) {
int32_t i, j;
bspbrush_t *front, *back;
int32_t p;
for (j = 0; j < 2; j++) {
if (brush->maxs[j] > clipmaxs[j]) {
SplitBrush(brush, maxplanenums[j], &front, &back);
FreeBrush(brush); // AA tools fix
if (front)
FreeBrush(front);
brush = back;
if (!brush)
return NULL;
}
if (brush->mins[j] < clipmins[j]) {
SplitBrush(brush, minplanenums[j], &front, &back);
FreeBrush(brush); // qb: AA tools fix
if (back)
FreeBrush(back);
brush = front;
if (!brush)
return NULL;
}
}
// remove any colinear faces
for (i = 0; i < brush->numsides; i++) {
p = brush->sides[i].planenum & ~1;
if (p == maxplanenums[0] || p == maxplanenums[1] || p == minplanenums[0] || p == minplanenums[1]) {
brush->sides[i].texinfo = TEXINFO_NODE;
brush->sides[i].visible = false;
}
}
return brush;
}
/*
===============
MakeBspBrushList
===============
*/
bspbrush_t *MakeBspBrushList(int32_t startbrush, int32_t endbrush,
vec3_t clipmins, vec3_t clipmaxs) {
mapbrush_t *mb;
bspbrush_t *brushlist, *newbrush;
int32_t i, j;
int32_t c_faces;
int32_t c_brushes;
int32_t numsides;
int32_t vis;
vec3_t normal;
vec_t dist; // jit (use higher precision, if enabled)
for (i = 0; i < 2; i++) {
VectorClear(normal);
normal[i] = 1;
dist = clipmaxs[i];
maxplanenums[i] = FindFloatPlane(normal, dist, 0);
dist = clipmins[i];
minplanenums[i] = FindFloatPlane(normal, dist, 0);
}
brushlist = NULL;
c_faces = 0;
c_brushes = 0;
for (i = startbrush; i < endbrush; i++) {
mb = &mapbrushes[i];
numsides = mb->numsides;
if (!numsides)
continue;
// make sure the brush has at least one face showing
vis = 0;
for (j = 0; j < numsides; j++)
if (mb->original_sides[j].visible && mb->original_sides[j].winding)
vis++;
#if 0
if (!vis)
continue; // no faces at all
#endif
// if the brush is outside the clip area, skip it
for (j = 0; j < 3; j++)
if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j])
break;
if (j != 3)
continue;
//
// make a copy of the brush
//
newbrush = AllocBrush(mb->numsides);
newbrush->original = mb;
newbrush->numsides = mb->numsides;
memcpy(newbrush->sides, mb->original_sides, numsides * sizeof(side_t));
for (j = 0; j < numsides; j++) {
if (newbrush->sides[j].winding)
newbrush->sides[j].winding = CopyWinding(newbrush->sides[j].winding);
if (newbrush->sides[j].surf & SURF_HINT)
newbrush->sides[j].visible = true; // hints are always visible
}
VectorCopy(mb->mins, newbrush->mins);
VectorCopy(mb->maxs, newbrush->maxs);
//
// carve off anything outside the clip box
//
newbrush = ClipBrushToBox(newbrush, clipmins, clipmaxs);
if (!newbrush)
continue;
c_faces += vis;
c_brushes++;
newbrush->next = brushlist;
brushlist = newbrush;
}
return brushlist;
}
/*
===============
AddBspBrushListToTail
===============
*/
bspbrush_t *AddBrushListToTail(bspbrush_t *list, bspbrush_t *tail) {
bspbrush_t *walk, *next;
for (walk = list; walk; walk = next) {
// add to end of list
next = walk->next;
walk->next = NULL;
tail->next = walk;
tail = walk;
}
return tail;
}
/*
===========
CullList
Builds a new list that doesn't hold the given brush
===========
*/
bspbrush_t *CullList(bspbrush_t *list, bspbrush_t *skip1) {
bspbrush_t *newlist;
bspbrush_t *next;
newlist = NULL;
for (; list; list = next) {
next = list->next;
if (list == skip1) {
FreeBrush(list);
continue;
}
list->next = newlist;
newlist = list;
}
return newlist;
}
/*
==================
WriteBrushMap
==================
*/
void WriteBrushMap(char *name, bspbrush_t *list) {
FILE *f;
side_t *s;
int32_t i;
winding_t *w;
if (use_qbsp)
printf("\ntexinfo count: %i of %i maximum\n", numtexinfo, MAX_MAP_TEXINFO_QBSP);
else
printf("\ntexinfo count: %i of %i maximum\n", numtexinfo, MAX_MAP_TEXINFO);
if (use_qbsp)
printf("brushsides count: %i of %i maximum\n", nummapbrushsides, MAX_MAP_BRUSHSIDES_QBSP);
else
printf("brushsides count: %i of %i maximum\n", nummapbrushsides, MAX_MAP_BRUSHSIDES);
printf("writing %s\n", name);
f = fopen(name, "wb");
if (!f)
Error("Can't write %s\b", name);
fprintf(f, "{\n\"classname\" \"worldspawn\"\n");
for (; list; list = list->next) {
fprintf(f, "{\n");
for (i = 0, s = list->sides; i < list->numsides; i++, s++) {
w = BaseWindingForPlane(mapplanes[s->planenum].normal, mapplanes[s->planenum].dist);
fprintf(f, "( %i %i %i ) ", (int32_t)w->p[0][0], (int32_t)w->p[0][1], (int32_t)w->p[0][2]);
fprintf(f, "( %i %i %i ) ", (int32_t)w->p[1][0], (int32_t)w->p[1][1], (int32_t)w->p[1][2]);
fprintf(f, "( %i %i %i ) ", (int32_t)w->p[2][0], (int32_t)w->p[2][1], (int32_t)w->p[2][2]);
fprintf(f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture);
FreeWinding(w);
}
fprintf(f, "}\n");
}
fprintf(f, "}\n");
fclose(f);
}
/*
==================
BrushGE
Returns true if b1 is allowed to bite b2
==================
*/
qboolean BrushGE(bspbrush_t *b1, bspbrush_t *b2) {
// detail brushes never bite structural brushes
if ((b1->original->contents & CONTENTS_DETAIL) && !(b2->original->contents & CONTENTS_DETAIL))
return false;
if (b1->original->contents & CONTENTS_SOLID)
return true;
return false;
}
/*
=================
ChopBrushes
Carves any intersecting solid brushes into the minimum number
of non-intersecting brushes.
=================
*/
bspbrush_t *ChopBrushes(bspbrush_t *head) {
bspbrush_t *b1, *b2, *next;
bspbrush_t *tail;
bspbrush_t *keep;
bspbrush_t *sub, *sub2;
int32_t c1, c2;
qprintf("---- ChopBrushes ----\n");
qprintf("original brushes: %i\n", CountBrushList(head));
keep = NULL;
newlist:
// find tail
if (!head)
return NULL;
for (tail = head; tail->next; tail = tail->next)
;
for (b1 = head; b1; b1 = next) {
next = b1->next;
for (b2 = b1->next; b2; b2 = b2->next) {
if (BrushesDisjoint(b1, b2))
continue;
sub = NULL;
sub2 = NULL;
c1 = BOGUS_RANGE;
c2 = BOGUS_RANGE;
if (BrushGE(b2, b1)) {
sub = SubtractBrush(b1, b2);
if (sub == b1)
continue; // didn't really intersect
if (!sub) {
// b1 is swallowed by b2
head = CullList(b1, b1);
goto newlist;
}
c1 = CountBrushList(sub);
}
if (BrushGE(b1, b2)) {
sub2 = SubtractBrush(b2, b1);
if (sub2 == b2)
continue; // didn't really intersect
if (!sub2) {
// b2 is swallowed by b1
FreeBrushList(sub);
head = CullList(b1, b2);
goto newlist;
}
c2 = CountBrushList(sub2);
}
if (!sub && !sub2)
continue; // neither one can bite
// only accept if it didn't fragment
// (commening this out allows full fragmentation)
if (c1 > 1 && c2 > 1) {
if (sub2)
FreeBrushList(sub2);
if (sub)
FreeBrushList(sub);
continue;
}
if (c1 < c2) {
if (sub2)
FreeBrushList(sub2);
tail = AddBrushListToTail(sub, tail);
head = CullList(b1, b1);
goto newlist;
} else {
if (sub)
FreeBrushList(sub);
tail = AddBrushListToTail(sub2, tail);
head = CullList(b1, b2);
goto newlist;
}
}
if (!b2) {
// b1 is no longer intersecting anything, so keep it
b1->next = keep;
keep = b1;
}
}
qprintf("output brushes: %i\n", CountBrushList(keep));
return keep;
}
/*
=================
InitialBrushList
=================
*/
bspbrush_t *InitialBrushList(bspbrush_t *list) {
bspbrush_t *b;
bspbrush_t *out, *newb;
int32_t i;
// only return brushes that have visible faces
out = NULL;
for (b = list; b; b = b->next) {
#if 0
for (i=0 ; i<b->numsides ; i++)
if (b->sides[i].visible)
break;
if (i == b->numsides)
continue;
#endif
newb = CopyBrush(b);
newb->next = out;
out = newb;
// clear visible, so it must be set by MarkVisibleFaces_r
// to be used in the optimized list
for (i = 0; i < b->numsides; i++) {
newb->sides[i].original = &b->sides[i];
// newb->sides[i].visible = true;
b->sides[i].visible = false;
}
}
return out;
}
/*
=================
OptimizedBrushList
=================
*/
bspbrush_t *OptimizedBrushList(bspbrush_t *list) {
bspbrush_t *b;
bspbrush_t *out, *newb;
int32_t i;
// only return brushes that have visible faces
out = NULL;
for (b = list; b; b = b->next) {
for (i = 0; i < b->numsides; i++)
if (b->sides[i].visible)
break;
if (i == b->numsides)
continue;
newb = CopyBrush(b);
newb->next = out;
out = newb;
}
return out;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
int32_t c_nodes;
int32_t c_nonvis;
int32_t c_active_brushes;
#define PSIDE_FRONT 1
#define PSIDE_BACK 2
#define PSIDE_BOTH (PSIDE_FRONT | PSIDE_BACK)
#define PSIDE_FACING 4
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane); // qb: GDD tools
void FindBrushInTree(node_t *node, int32_t brushnum) {
bspbrush_t *b;
if (node->planenum == PLANENUM_LEAF) {
for (b = node->brushlist; b; b = b->next)
if (b->original->brushnum == brushnum)
return;
}
FindBrushInTree(node->children[0], brushnum);
FindBrushInTree(node->children[1], brushnum);
}
//==================================================
/*
==================
BoundBrush
Sets the mins/maxs based on the windings
==================
*/
void BoundBrush(bspbrush_t *brush) {
int32_t i, j;
winding_t *w;
ClearBounds(brush->mins, brush->maxs);
for (i = 0; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (!w)
continue;
for (j = 0; j < w->numpoints; j++)
AddPointToBounds(w->p[j], brush->mins, brush->maxs);
}
}
/*
==================
CreateBrushWindings
==================
*/
void CreateBrushWindings(bspbrush_t *brush) {
int32_t i, j;
winding_t *w;
side_t *side;
plane_t *plane;
for (i = 0; i < brush->numsides; i++) {
side = &brush->sides[i];
plane = &mapplanes[side->planenum];
w = BaseWindingForPlane(plane->normal, plane->dist);
for (j = 0; j < brush->numsides && w; j++) {
if (i == j)
continue;
if (brush->sides[j].bevel)
continue;
plane = &mapplanes[brush->sides[j].planenum ^ 1];
ChopWindingInPlace(&w, plane->normal, plane->dist, 0);
}
side->winding = w;
}
BoundBrush(brush);
}
/*
==================
BrushFromBounds
Creates a new axial brush
==================
*/
bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs) {
bspbrush_t *b;
int32_t i;
vec3_t normal;
vec_t dist;
b = AllocBrush(6);
b->numsides = 6;
for (i = 0; i < 3; i++) {
VectorClear(normal);
normal[i] = 1;
dist = maxs[i];
b->sides[i].planenum = FindFloatPlane(normal, dist, 0);
normal[i] = -1;
dist = -mins[i];
b->sides[3 + i].planenum = FindFloatPlane(normal, dist, 0);
}
CreateBrushWindings(b);
return b;
}
/*
==================
BrushVolume
==================
*/
vec_t BrushVolume(bspbrush_t *brush) {
int32_t i;
winding_t *w;
vec3_t corner;
vec_t d, area, volume;
plane_t *plane;
if (!brush)
return 0;
// grab the first valid point as the corner
w = NULL;
for (i = 0; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (w)
break;
}
if (!w)
return 0;
VectorCopy(w->p[0], corner);
// make tetrahedrons to all other faces
volume = 0;
for (; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (!w)
continue;
plane = &mapplanes[brush->sides[i].planenum];
d = -(DotProduct(corner, plane->normal) - plane->dist);
area = WindingArea(w);
volume += d * area;
}
volume /= 3;
return volume;
}
/*
================
CountBrushList
================
*/
int32_t CountBrushList(bspbrush_t *brushes) {
int32_t c;
c = 0;
for (; brushes; brushes = brushes->next)
c++;
return c;
}
/*
================
AllocTree
================
*/
tree_t *AllocTree(void) {
tree_t *tree;
tree = malloc(sizeof(*tree));
memset(tree, 0, sizeof(*tree));
ClearBounds(tree->mins, tree->maxs);
return tree;
}
/*
================
AllocNode
================
*/
node_t *AllocNode(void) {
node_t *node;
node = malloc(sizeof(*node));
memset(node, 0, sizeof(*node));
return node;
}
/*
================
AllocBrush
================
*/
bspbrush_t *AllocBrush(int32_t numsides) {
bspbrush_t *bb;
int32_t c;
c = (intptr_t) & (((bspbrush_t *)0)->sides[numsides]);
bb = malloc(c);
memset(bb, 0, c);
if (numthreads == 1)
c_active_brushes++;
return bb;
}
/*
================
FreeBrush
================
*/
void FreeBrush(bspbrush_t *brushes) {
int32_t i;
for (i = 0; i < brushes->numsides; i++)
if (brushes->sides[i].winding)
FreeWinding(brushes->sides[i].winding);
free(brushes);
if (numthreads == 1)
c_active_brushes--;
}
/*
================
FreeBrushList
================
*/
void FreeBrushList(bspbrush_t *brushes) {
bspbrush_t *next;
for (; brushes; brushes = next) {
next = brushes->next;
FreeBrush(brushes);
}
}
/*
==================
CopyBrush
Duplicates the brush, the sides, and the windings
==================
*/
bspbrush_t *CopyBrush(bspbrush_t *brush) {
bspbrush_t *newbrush;
int32_t size;
int32_t i;
size = (intptr_t) & (((bspbrush_t *)0)->sides[brush->numsides]);
newbrush = AllocBrush(brush->numsides);
memcpy(newbrush, brush, size);
for (i = 0; i < brush->numsides; i++) {
if (brush->sides[i].winding)
newbrush->sides[i].winding = CopyWinding(brush->sides[i].winding);
}
return newbrush;
}
/*
==================
PointInLeaf
==================
*/
node_t *PointInLeaf(node_t *node, vec3_t point) {
vec_t d;
plane_t *plane;
while (node->planenum != PLANENUM_LEAF) {
plane = &mapplanes[node->planenum];
d = DotProduct(point, plane->normal) - plane->dist;
if (d > 0)
node = node->children[0];
else
node = node->children[1];
}
return node;
}
//========================================================
/*
==============
BoxOnPlaneSide
Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH
==============
*/
int32_t BoxOnPlaneSide(vec3_t mins, vec3_t maxs, plane_t *plane) {
int32_t side;
int32_t i;
vec3_t corners[2];
vec_t dist1, dist2;
// axial planes are easy
if (plane->type < 3) {
side = 0;
if (maxs[plane->type] > plane->dist + PLANESIDE_EPSILON)
side |= PSIDE_FRONT;
if (mins[plane->type] < plane->dist - PLANESIDE_EPSILON)
side |= PSIDE_BACK;
return side;
}
// create the proper leading and trailing verts for the box
for (i = 0; i < 3; i++) {
if (plane->normal[i] < 0) {
corners[0][i] = mins[i];
corners[1][i] = maxs[i];
} else {
corners[1][i] = mins[i];
corners[0][i] = maxs[i];
}
}
dist1 = DotProduct(plane->normal, corners[0]) - plane->dist;
dist2 = DotProduct(plane->normal, corners[1]) - plane->dist;
side = 0;
if (dist1 >= PLANESIDE_EPSILON)
side = PSIDE_FRONT;
if (dist2 < PLANESIDE_EPSILON)
side |= PSIDE_BACK;
return side;
}
/*
============
QuickTestBrushToPlanenum
============
*/
int32_t QuickTestBrushToPlanenum(bspbrush_t *brush, int32_t planenum, int32_t *numsplits) {
int32_t i, num;
plane_t *plane;
int32_t s;
*numsplits = 0;
// if the brush actually uses the planenum,
// we can tell the side for sure
for (i = 0; i < brush->numsides; i++) {
num = brush->sides[i].planenum;
if (num >= MAX_MAP_PLANES_QBSP)
Error("bad planenum");
if (num == planenum)
return PSIDE_BACK | PSIDE_FACING;
if (num == (planenum ^ 1))
return PSIDE_FRONT | PSIDE_FACING;
}
// box on plane side
plane = &mapplanes[planenum];
s = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
// if both sides, count the visible faces split
if (s == PSIDE_BOTH) {
*numsplits += 3;
}
return s;
}
/*
============
TestBrushToPlanenum
============
*/
// qb: GDD tools detailsplit
int32_t TestBrushToPlanenum(bspbrush_t *brush, int32_t planenum,
int32_t *numsplits, qboolean *hintsplit, qboolean *detailsplit, int32_t *epsilonbrush) {
int32_t i, j, num;
plane_t *plane;
int32_t s;
winding_t *w;
vec_t d, d_front, d_back;
int32_t front, back;
*numsplits = 0;
*hintsplit = false;
// if the brush actually uses the planenum,
// we can tell the side for sure
for (i = 0; i < brush->numsides; i++) {
num = brush->sides[i].planenum;
if (num >= MAX_MAP_PLANES_QBSP)
Error("bad planenum");
if (num == planenum)
return PSIDE_BACK | PSIDE_FACING;
if (num == (planenum ^ 1))
return PSIDE_FRONT | PSIDE_FACING;
}
// box on plane side
plane = &mapplanes[planenum];
s = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
if (s != PSIDE_BOTH)
return s;
// if both sides, count the visible faces split
d_front = d_back = 0;
for (i = 0; i < brush->numsides; i++) {
if (brush->sides[i].texinfo == TEXINFO_NODE)
continue; // on node, don't worry about splits
if (!brush->sides[i].visible)
continue; // we don't care about non-visible
w = brush->sides[i].winding;
if (!w)
continue;
front = back = 0;
for (j = 0; j < w->numpoints; j++) {
d = DotProduct(w->p[j], plane->normal) - plane->dist;
if (d > d_front)
d_front = d;
if (d < d_back)
d_back = d;
if (d > ON_EPSILON) // qb: was 0.1
front = 1;
if (d < -ON_EPSILON) // qb: was -0.1
back = 1;
}
if (front && back) {
if (!(brush->sides[i].surf & SURF_SKIP)) {
(*numsplits)++;
if (brush->sides[i].surf & SURF_HINT)
*hintsplit = true;
if (brush->sides[i].contents & CONTENTS_DETAIL)
*detailsplit = true;
}
}
}
if ((d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0))
(*epsilonbrush)++;
#if 0
if (*numsplits == 0)
{
// didn't really need to be split
if (front)
s = PSIDE_FRONT;
else if (back)
s = PSIDE_BACK;
else
s = 0;
}
#endif
return s;
}
//========================================================
/*
================
WindingIsTiny
Returns true if the winding would be crunched out of
existance by the vertex snapping.
================
*/
#define EDGE_LENGTH 0.2
qboolean WindingIsTiny(winding_t *w) {
#if 0
if (WindingArea (w) < 1)
return true;
return false;
#else
int32_t i, j;
vec_t len;
vec3_t delta;
int32_t edges;
edges = 0;
for (i = 0; i < w->numpoints; i++) {
j = i == w->numpoints - 1 ? 0 : i + 1;
VectorSubtract(w->p[j], w->p[i], delta);
len = VectorLength(delta);
if (len > EDGE_LENGTH) {
if (++edges == 3)
return false;
}
}
return true;
#endif
}
/*
================
WindingIsHuge
Returns true if the winding still has one of the points
from basewinding for plane
================
*/
qboolean WindingIsHuge(winding_t *w) {
int32_t i, j;
for (i = 0; i < w->numpoints; i++) {
for (j = 0; j < 3; j++)
if (w->p[i][j] < -2 * max_bounds || w->p[i][j] > 2 * max_bounds)
return true;
}
return false;
}
//============================================================
/*
================
Leafnode
================
*/
void LeafNode(node_t *node, bspbrush_t *brushes) {
bspbrush_t *b;
int32_t i;
node->planenum = PLANENUM_LEAF;
node->contents = 0;
for (b = brushes; b; b = b->next) {
// if the brush is solid and all of its sides are on nodes,
// it eats everything
if (b->original->contents & CONTENTS_SOLID) {
for (i = 0; i < b->numsides; i++)
if (b->sides[i].texinfo != TEXINFO_NODE)
break;
if (i == b->numsides) {
node->contents = CONTENTS_SOLID;
break;
}
}
node->contents |= b->original->contents;
}
node->brushlist = brushes;
}
//============================================================
// qb: GDD tools: brush info on error
void CheckPlaneAgainstParents(int32_t pnum, node_t *node, bspbrush_t *brush) {
node_t *p;
for (p = node->parent; p; p = p->parent) {
if (p->planenum == pnum) {
Error("Tried parent\n Brush Bounds: %g %g %g -> %g %g %g\n",
brush->mins[0], brush->mins[1], brush->mins[2], brush->maxs[0], brush->maxs[1], brush->maxs[2]);
}
}
}
qboolean CheckPlaneAgainstVolume(int32_t pnum, node_t *node) {
bspbrush_t *front, *back;
qboolean good;
SplitBrush(node->volume, pnum, &front, &back);
good = (front && back);
if (front)
FreeBrush(front);
if (back)
FreeBrush(back);
return good;
}
/*
================
SelectSplitSide
Using a hueristic, choses one of the sides out of the brushlist
to partition the brushes with.
Returns NULL if there are no valid planes to split with..
================
*/
side_t *SelectSplitSide(bspbrush_t *brushes, node_t *node) {
int32_t value, bestvalue;
bspbrush_t *brush, *test;
side_t *side, *bestside;
int32_t i, j, pass, numpasses;
int32_t pnum;
int32_t s;
int32_t front, back, both, facing, splits;
int32_t bsplits;
int32_t epsilonbrush;
qboolean hintsplit, detailsplit;
bestside = NULL;
bestvalue = -BOGUS_RANGE;
// the search order goes: visible-structural, visible-detail,
// nonvisible-structural, nonvisible-detail.
// If any valid plane is available in a pass, no further
// passes will be tried.
numpasses = 4;
for (pass = 0; pass < numpasses; pass++) {
for (brush = brushes; brush; brush = brush->next) {
if ((pass & 1) && !(brush->original->contents & CONTENTS_DETAIL))
continue;
if (!(pass & 1) && (brush->original->contents & CONTENTS_DETAIL))
continue;
for (i = 0; i < brush->numsides; i++) {
side = brush->sides + i;
if (side->bevel)
continue; // never use a bevel as a spliter
if (!side->winding)
continue; // nothing visible, so it can't split
if (side->texinfo == TEXINFO_NODE)
continue; // allready a node splitter
if (side->tested)
continue; // we allready have metrics for this plane
if (side->surf & SURF_SKIP)
continue; // skip surfaces are never chosen
if (side->visible ^ (pass < 2))
continue; // only check visible faces on first pass
pnum = side->planenum;
pnum &= ~1; // allways use positive facing plane
CheckPlaneAgainstParents(pnum, node, brush);
if (!CheckPlaneAgainstVolume(pnum, node))
continue; // would produce a tiny volume
front = 0;
back = 0;
both = 0;
facing = 0;
splits = 0;
epsilonbrush = 0;
for (test = brushes; test; test = test->next) {
s = TestBrushToPlanenum(test, pnum, &bsplits, &hintsplit, &detailsplit, &epsilonbrush);
splits += bsplits;
if (bsplits && (s & PSIDE_FACING))
Error("PSIDE_FACING with splits");
test->testside = s;
// if the brush shares this face, don't bother
// testing that facenum as a splitter again
if (s & PSIDE_FACING) {
facing++;
for (j = 0; j < test->numsides; j++) {
if ((test->sides[j].planenum & ~1) == pnum)
test->sides[j].tested = true;
}
}
if (s & PSIDE_FRONT)
front++;
if (s & PSIDE_BACK)
back++;
if (s == PSIDE_BOTH)
both++;
}
// give a value estimate for using this plane
value = 5 * facing - 5 * splits - abs(front - back);
// value = -5*splits;
// value = 5*facing - 5*splits;
if (mapplanes[pnum].type < 3)
value += 5; // axial is better
value -= epsilonbrush * 1000; // avoid!
// never split a hint side except with another hint
if ((hintsplit && !(side->surf & SURF_HINT)) && (!detailsplit || (side->contents & CONTENTS_DETAIL)))
value = -BOGUS_RANGE;
// save off the side test so we don't need
// to recalculate it when we actually seperate
// the brushes
if (value > bestvalue) {
bestvalue = value;
bestside = side;
for (test = brushes; test; test = test->next)
test->side = test->testside;
}
}
}
// if we found a good plane, don't bother trying any
// other passes
if (bestside) {
if (pass > 1) {
if (numthreads == 1)
c_nonvis++;
}
if (pass > 0)
node->detail_seperator = true; // not needed for vis
break;
}
}
//
// clear all the tested flags we set
//
for (brush = brushes; brush; brush = brush->next) {
for (i = 0; i < brush->numsides; i++)
brush->sides[i].tested = false;
}
return bestside;
}
/*
==================
BrushMostlyOnSide
==================
*/
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane) {
int32_t i, j;
winding_t *w;
vec_t d, max;
int32_t side;
max = 0;
side = PSIDE_FRONT;
for (i = 0; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (!w)
continue;
for (j = 0; j < w->numpoints; j++) {
d = DotProduct(w->p[j], plane->normal) - plane->dist;
if (d > max) {
max = d;
side = PSIDE_FRONT;
}
if (-d > max) {
max = -d;
side = PSIDE_BACK;
}
}
}
return side;
}
/*
================
SplitBrush
Generates two new brushes, leaving the original
unchanged
================
*/
void SplitBrush(bspbrush_t *brush, int32_t planenum,
bspbrush_t **front, bspbrush_t **back) {
bspbrush_t *b[2];
int32_t i, j;
winding_t *w, *cw[2], *midwinding;
plane_t *plane, *plane2;
side_t *s, *cs;
vec_t d, d_front, d_back;
*front = *back = NULL;
plane = &mapplanes[planenum];
// check all points
d_front = d_back = 0;
for (i = 0; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (!w)
continue;
for (j = 0; j < w->numpoints; j++) {
d = DotProduct(w->p[j], plane->normal) - plane->dist;
if (d > 0 && d > d_front)
d_front = d;
if (d < 0 && d < d_back)
d_back = d;
}
}
// If the brush only overaps the plane by .1 units, don't bother splitting it.
if (d_front < ON_EPSILON) // qb: was 0.1
{
// only on back
*back = CopyBrush(brush);
return;
}
if (d_back > -ON_EPSILON) // qb: was -0.1
{
// only on front
*front = CopyBrush(brush);
return;
}
// create a new winding from the split plane
w = BaseWindingForPlane(plane->normal, plane->dist);
for (i = 0; i < brush->numsides && w; i++) {
plane2 = &mapplanes[brush->sides[i].planenum ^ 1];
ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON);
}
if (!w || WindingIsTiny(w)) {
// the brush isn't really split
int32_t side;
side = BrushMostlyOnSide(brush, plane);
if (side == PSIDE_FRONT)
*front = CopyBrush(brush);
if (side == PSIDE_BACK)
*back = CopyBrush(brush);
return;
}
if (WindingIsHuge(w)) {
qprintf("WARNING: huge winding\n");
}
midwinding = w;
// split it for real
for (i = 0; i < 2; i++) {
b[i] = AllocBrush(brush->numsides + 1);
b[i]->original = brush->original;
}
// split all the current windings
for (i = 0; i < brush->numsides; i++) {
s = &brush->sides[i];
w = s->winding;
if (!w)
continue;
ClipWindingEpsilon(w, plane->normal, plane->dist,
0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); // qb:reenabling leaves gaps between some planes
for (j = 0; j < 2; j++) {
if (!cw[j])
continue;
#if 0
if (WindingIsTiny (cw[j]))
{
FreeWinding (cw[j]);
continue;
}
#endif
cs = &b[j]->sides[b[j]->numsides];
b[j]->numsides++;
*cs = *s;
// cs->planenum = s->planenum;
// cs->texinfo = s->texinfo;
// cs->visible = s->visible;
// cs->original = s->original;
cs->winding = cw[j];
cs->tested = false;
}
}
// see if we have valid polygons on both sides
for (i = 0; i < 2; i++) {
BoundBrush(b[i]);
for (j = 0; j < 3; j++) {
if (b[i]->mins[j] < -max_bounds || b[i]->maxs[j] > max_bounds) {
qprintf("bogus brush after clip\n");
break;
}
}
if (b[i]->numsides < 3 || j < 3) {
FreeBrush(b[i]);
b[i] = NULL;
}
}
if (!(b[0] && b[1])) {
if (!b[0] && !b[1])
qprintf("split removed brush\n");
else
qprintf("split not on both sides\n");
if (b[0]) {
FreeBrush(b[0]);
*front = CopyBrush(brush);
}
if (b[1]) {
FreeBrush(b[1]);
*back = CopyBrush(brush);
}
return;
}
// add the midwinding to both sides
for (i = 0; i < 2; i++) {
cs = &b[i]->sides[b[i]->numsides];
b[i]->numsides++;
cs->planenum = planenum ^ i ^ 1;
cs->texinfo = TEXINFO_NODE;
cs->visible = false;
cs->tested = false;
if (i == 0)
cs->winding = CopyWinding(midwinding);
else
cs->winding = midwinding;
}
{
vec_t v1;
int32_t i;
for (i = 0; i < 2; i++) {
v1 = BrushVolume(b[i]);
if (v1 < microvolume) // jit - allow for smaller brush volumes
{
FreeBrush(b[i]);
b[i] = NULL;
// qprintf ("tiny volume after clip\n");
}
}
}
*front = b[0];
*back = b[1];
}
/*
================
SplitBrushList
================
*/
void SplitBrushList(bspbrush_t *brushes,
node_t *node, bspbrush_t **front, bspbrush_t **back) {
bspbrush_t *brush, *newbrush, *newbrush2;
side_t *side;
int32_t sides;
int32_t i;
*front = *back = NULL;
for (brush = brushes; brush; brush = brush->next) {
sides = brush->side;
if (sides == PSIDE_BOTH) {
// split into two brushes
SplitBrush(brush, node->planenum, &newbrush, &newbrush2);
if (newbrush) {
newbrush->next = *front;
*front = newbrush;
}
if (newbrush2) {
newbrush2->next = *back;
*back = newbrush2;
}
continue;
}
newbrush = CopyBrush(brush);
// if the planenum is actualy a part of the brush
// find the plane and flag it as used so it won't be tried
// as a splitter again
if (sides & PSIDE_FACING) {
for (i = 0; i < newbrush->numsides; i++) {
side = newbrush->sides + i;
if ((side->planenum & ~1) == node->planenum)
side->texinfo = TEXINFO_NODE;
}
}
if (sides & PSIDE_FRONT) {
newbrush->next = *front;
*front = newbrush;
continue;
}
if (sides & PSIDE_BACK) {
newbrush->next = *back;
*back = newbrush;
continue;
}
}
}
/*
================
BuildTree_r
================
*/
node_t *BuildTree_r(node_t *node, bspbrush_t *brushes) {
node_t *newnode;
side_t *bestside;
int32_t i;
bspbrush_t *children[2];
if (numthreads == 1)
c_nodes++;
// find the best plane to use as a splitter
bestside = SelectSplitSide(brushes, node);
if (!bestside) {
// leaf node
node->side = NULL;
node->planenum = -1;
LeafNode(node, brushes);
return node;
}
// this is a splitplane node
node->side = bestside;
node->planenum = bestside->planenum & ~1; // always use front facing
SplitBrushList(brushes, node, &children[0], &children[1]);
FreeBrushList(brushes);
// allocate children before recursing
for (i = 0; i < 2; i++) {
newnode = AllocNode();
newnode->parent = node;
node->children[i] = newnode;
}
SplitBrush(node->volume, node->planenum, &node->children[0]->volume,
&node->children[1]->volume);
// recursively process children
for (i = 0; i < 2; i++) {
node->children[i] = BuildTree_r(node->children[i], children[i]);
}
return node;
}
//===========================================================
/*
=================
BrushBSP
The incoming list will be freed before exiting
=================
*/
tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) {
node_t *node;
bspbrush_t *b;
int32_t c_faces, c_nonvisfaces;
int32_t c_brushes;
tree_t *tree;
int32_t i;
vec_t volume;
qprintf("--- BrushBSP ---\n");
tree = AllocTree();
c_faces = 0;
c_nonvisfaces = 0;
c_brushes = 0;
for (b = brushlist; b; b = b->next) {
c_brushes++;
volume = BrushVolume(b);
if (volume < microvolume) {
printf("WARNING: Entity %i, Brush %i, Line %i: microbrush\n Bounds: %g %g %g -> %g %g %g\n",
b->original->entitynum, b->original->brushnum, scriptline + 1, // qb: add scriptline
b->mins[0], b->mins[1], b->mins[2], b->maxs[0], b->maxs[1], b->maxs[2]);
}
for (i = 0; i < b->numsides; i++) {
if (b->sides[i].bevel)
continue;
if (!b->sides[i].winding)
continue;
if (b->sides[i].texinfo == TEXINFO_NODE)
continue;
if (b->sides[i].visible)
c_faces++;
else
c_nonvisfaces++;
}
AddPointToBounds(b->mins, tree->mins, tree->maxs);
AddPointToBounds(b->maxs, tree->mins, tree->maxs);
}
qprintf("%5i brushes\n", c_brushes);
qprintf("%5i visible faces\n", c_faces);
qprintf("%5i nonvisible faces\n", c_nonvisfaces);
c_nodes = 0;
c_nonvis = 0;
node = AllocNode();
node->volume = BrushFromBounds(mins, maxs);
tree->headnode = node;
node = BuildTree_r(node, brushlist);
qprintf("%5i visible nodes\n", c_nodes / 2 - c_nonvis);
qprintf("%5i nonvis nodes\n", c_nonvis);
qprintf("%5i leafs\n", (c_nodes + 1) / 2);
#if 0
{
// debug code
static node_t *tnode;
vec3_t p;
p[0] = -1469;
p[1] = -118;
p[2] = 119;
tnode = PointInLeaf (tree->headnode, p);
printf ("contents: %i\n", tnode->contents);
p[0] = 0;
}
#endif
return tree;
}
#include "qbsp.h"
extern char name[1024];
extern qboolean origfix;
extern qboolean nocsg;
extern qboolean onlyents;
extern qboolean leaktest;
extern int32_t block_size;
extern float subdivide_size;
extern float sublight_size;
extern int32_t block_xl;
extern int32_t block_yl;
extern int32_t block_xh;
extern int32_t block_yh;
void BSP_ProcessArgument(const char * arg) ;
int32_t main(int32_t argc, char **argv) {
int32_t i;
char tgamedir[1024] = "", tbasedir[1024] = "", tmoddir[1024] = "";
printf("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4bsp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("BSP compiler build " __DATE__ "\n");
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-noorigfix")) {
printf("origfix = false\n");
origfix = false;
} else if (!strcmp(argv[i], "-v")) {
printf("verbose = true\n");
verbose = true;
} else if (!strcmp(argv[i], "-help")) {
printf("4bsp supporting v38 and v220 map formats plus QBSP extended limits.\n"
"Usage: 4bsp [options] [mapname]\n\n"
" -chop #: Subdivide size.\n"
" Default: 240 Range: 32-1024\n"
" -choplight #: Subdivide size for surface lights.\n"
" Default: 240 Range: 32-1024\n"
" -largebounds: Increase max map size for supporting engines.\n"
" -micro #: Minimum microbrush size. Default: 0.02\n"
" Suggested range: 0.02 - 1.0\n"
" -nosubdiv: Disable subdivision.\n"
" -qbsp: Greatly expanded map and entity limits for supporting engines.\n"
" -moddir [path]: Set a mod directory. Default is parent dir of map file.\n"
" -basedir [path]: Set the directory for assets not in moddir. Default is moddir.\n"
" -gamedir [path]: Set game directory, the folder with game executable.\n"
//" -threads #: number of CPU threads to use\n"
"Debugging tools:\n"
" -block # #: Division tree block size, square\n"
" -blocks # # # #: Div tree block size, rectangular\n"
" -blocksize: map cube size for processing. Default: 1024\n"
" -fulldetail: Change most brushes to detail.\n"
" -leaktest: Perform leak test only.\n"
" -nocsg: No constructive solid geometry.\n"
" -nodetail: No detail brushes.\n"
" -nomerge: Don't merge visible faces per node.\n"
" -noorigfix: Disable texture fix for origin offsets.\n"
" -noprune: Disable node pruning.\n"
" -noshare: Don't look for shared edges on save.\n"
" -noskipfix: Do not automatically set skip contents to zero.\n"
" -notjunc: Disable edge cleanup.\n"
" -nowater: Ignore warp surfaces.\n"
" -noweld: Disable vertex welding.\n"
" -onlyents: Grab the entites and resave.\n"
" -v: Display more verbose output.\n"
"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 4bsp HELP >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
exit(1);
}
/*
if (!strcmp(argv[i],"-threads"))
{
numthreads = atoi (argv[i+1]);
i++;
}
*/
else if (!strcmp(argv[i], "-noweld")) {
printf("noweld = true\n");
noweld = true;
} else if (!strcmp(argv[i], "-nocsg")) {
printf("nocsg = true\n");
nocsg = true;
} else if (!strcmp(argv[i], "-noshare")) {
printf("noshare = true\n");
noshare = true;
} else if (!strcmp(argv[i], "-notjunc")) {
printf("notjunc = true\n");
notjunc = true;
} else if (!strcmp(argv[i], "-nowater")) {
printf("nowater = true\n");
nowater = true;
} else if (!strcmp(argv[i], "-noprune")) {
printf("noprune = true\n");
noprune = true;
} else if (!strcmp(argv[i], "-nomerge")) {
printf("nomerge = true\n");
nomerge = true;
} else if (!strcmp(argv[i], "-nosubdiv")) {
printf("nosubdiv = true\n");
nosubdiv = true;
} else if (!strcmp(argv[i], "-nodetail")) {
printf("nodetail = true\n");
nodetail = true;
} else if (!strcmp(argv[i], "-fulldetail")) {
printf("fulldetail = true\n");
fulldetail = true;
} else if (!strcmp(argv[i], "-onlyents")) {
printf("onlyents = true\n");
onlyents = true;
} else if (!strcmp(argv[i], "-micro")) {
microvolume = atof(argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-leaktest")) {
printf("leaktest = true\n");
leaktest = true;
}
// qb: qbsp
else if (!strcmp(argv[i], "-qbsp")) {
printf("use_qbsp = true\n");
use_qbsp = true;
max_entities = MAX_MAP_ENTITIES_QBSP;
max_bounds = MAX_MAP_SIZE;
block_size = MAX_BLOCK_SIZE; // qb: otherwise limits map range
} else if (!strcmp(argv[i], "-noskipfix")) {
printf("noskipfix = true\n");
noskipfix = true;
}
// qb: from kmqbsp3- Knightmare added
else if (!strcmp(argv[i], "-largebounds") || !strcmp(argv[i], "-lb")) {
if (use_qbsp) {
printf("[-largebounds is not required with -qbsp]\n");
} else {
max_bounds = MAX_MAP_SIZE;
block_size = MAX_BLOCK_SIZE; // qb: otherwise limits map range
printf("largebounds: using max bound size of %i\n", MAX_MAP_SIZE);
}
}
// qb: set gamedir, moddir, and basedir
else if (!strcmp(argv[i], "-gamedir")) {
strcpy(tgamedir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-moddir")) {
strcpy(tmoddir, argv[i + 1]);
i++;
} else if (!strcmp(argv[i], "-basedir")) {
strcpy(tbasedir, argv[i + 1]);
i++;
}
else if ((!strcmp(argv[i], "-chop")) || (!strcmp(argv[i], "-subdiv"))) {
subdivide_size = atof(argv[i + 1]);
if (subdivide_size < 32) {
subdivide_size = 32;
printf("subdivide_size set to minimum size: 32\n");
}
if (subdivide_size > 1024) {
subdivide_size = 1024;
printf("subdivide_size set to maximum size: 1024\n");
}
printf("subdivide_size = %f\n", subdivide_size);
i++;
} else if ((!strcmp(argv[i], "-choplight")) || (!strcmp(argv[i], "-choplights")) || (!strcmp(argv[i], "-subdivlight"))) // qb: chop surf lights independently
{
sublight_size = atof(argv[i + 1]);
if (sublight_size < 32) {
sublight_size = 32;
printf("sublight_size set to minimum size: 32\n");
}
if (sublight_size > 1024) {
sublight_size = 1024;
printf("sublight_size set to maximum size: 1024\n");
}
printf("sublight_size = %f\n", sublight_size);
i++;
} else if (!strcmp(argv[i], "-blocksize")) {
block_size = atof(argv[i + 1]);
if (block_size < 128) {
block_size = 128;
printf("block_size set to minimum size: 128\n");
}
if (block_size > MAX_BLOCK_SIZE) {
block_size = MAX_BLOCK_SIZE;
printf("block_size set to minimum size: MAX_BLOCK_SIZE\n");
}
printf("blocksize: %i\n", block_size);
i++;
} else if (!strcmp(argv[i], "-block")) {
block_xl = block_yl = atoi(argv[i + 1]); // qb: fixed... has it always been wrong? was xl = xh and yl = yh
block_xh = block_yh = atoi(argv[i + 2]);
printf("block: %i,%i\n", block_xl, block_xh);
i += 2;
} else if (!strcmp(argv[i], "-blocks")) {
block_xl = atoi(argv[i + 1]);
block_yl = atoi(argv[i + 2]);
block_xh = atoi(argv[i + 3]);
block_yh = atoi(argv[i + 4]);
printf("blocks: %i,%i to %i,%i\n",
block_xl, block_yl, block_xh, block_yh);
i += 4;
} else
break;
}
if (i != argc - 1) {
printf("Supporting v38 and v220 map formats plus QBSP extended limits.\n"
"Usage: 4bsp [options] [mapname]\n"
" -chop # -choplight # -help\n"
" -largebounds -micro # -nosubdiv\n"
" -qbsp -gamedir -basedir\n"
"Debugging tools: -block # # -blocks # # # #\n"
" -blocksize # -fulldetail -leaktest\n"
" -nocsg -nodetail -nomerge\n"
" -noorigfix -noprune -noshare\n"
" -noskipfix -notjunc -nowater\n"
" -noweld -onlyents -v (verbose)\n\n");
exit(1);
}
ThreadSetDefault();
// qb: below is from original source release. On Windows, multi threads cause false leak errors.
numthreads = 1; // multiple threads aren't helping...
SetQdirFromPath(argv[i]);
if (strcmp(tmoddir, "")) {
strcpy(moddir, tmoddir);
Q_pathslash(moddir);
strcpy(basedir, moddir);
}
if (strcmp(tbasedir, "")) {
strcpy(basedir, tbasedir);
Q_pathslash(basedir);
if (!strcmp(tmoddir, ""))
strcpy(moddir, basedir);
}
if (strcmp(tgamedir, "")) {
strcpy(gamedir, tgamedir);
Q_pathslash(gamedir);
}
printf("microvolume = %f\n\n", microvolume);
// qb: display dirs
printf("moddir = %s\n", moddir);
printf("basedir = %s\n", basedir);
printf("gamedir = %s\n", gamedir);
BSP_ProcessArgument(argv[i]);
return 0;
}
/*
===========================================================================
Copyright (C) 1997-2006 Id Software, Inc.
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
===========================================================================
*/
#include "qbsp.h"
extern float subdivide_size;
extern float sublight_size;
extern char source[1024];
char name[1024];
vec_t microvolume = 0.02f; // jit - was 1.0, but this messes up small brushes
qboolean noprune = false;
qboolean glview = false;
qboolean nodetail = false;
qboolean fulldetail = false;
qboolean onlyents = false;
qboolean nomerge = false;
qboolean nowater = false;
qboolean nocsg = false;
qboolean noweld = false;
qboolean noshare = false;
qboolean nosubdiv = false;
qboolean notjunc = false;
qboolean leaktest = false;
qboolean badnormal_check = false;
qboolean origfix = true; // default to true
int32_t block_xl = -8, block_xh = 7, block_yl = -8, block_yh = 7;
int32_t entity_num;
int32_t max_entities = MAX_MAP_ENTITIES; // qb: from kmqbsp3- Knightmare- adjustable entity limit
int32_t max_bounds = DEFAULT_MAP_SIZE; // Knightmare- adjustable max bounds
int32_t block_size = 1024; // Knightmare- adjustable block size
node_t *block_nodes[10][10];
/*
============
BlockTree
============
*/
node_t *BlockTree(int32_t xl, int32_t yl, int32_t xh, int32_t yh) {
node_t *node;
vec3_t normal;
vec_t dist;
int32_t mid;
if (xl == xh && yl == yh) {
node = block_nodes[xl + 5][yl + 5];
if (!node) {
// return an empty leaf
node = AllocNode();
node->planenum = PLANENUM_LEAF;
node->contents = 0; // CONTENTS_SOLID;
return node;
}
return node;
}
// create a seperator along the largest axis
node = AllocNode();
if (xh - xl > yh - yl) {
// split x axis
mid = xl + (xh - xl) / 2 + 1;
normal[0] = 1;
normal[1] = 0;
normal[2] = 0;
dist = mid * block_size;
node->planenum = FindFloatPlane(normal, dist, 0);
node->children[0] = BlockTree(mid, yl, xh, yh);
node->children[1] = BlockTree(xl, yl, mid - 1, yh);
} else {
mid = yl + (yh - yl) / 2 + 1;
normal[0] = 0;
normal[1] = 1;
normal[2] = 0;
dist = mid * block_size;
node->planenum = FindFloatPlane(normal, dist, 0);
node->children[0] = BlockTree(xl, mid, xh, yh);
node->children[1] = BlockTree(xl, yl, xh, mid - 1);
}
return node;
}
/*
============
ProcessBlock_Thread
============
*/
int32_t brush_start, brush_end;
void ProcessBlock_Thread(int32_t blocknum) {
int32_t xblock, yblock;
vec3_t mins, maxs;
bspbrush_t *brushes;
tree_t *tree;
node_t *node;
yblock = block_yl + blocknum / (block_xh - block_xl + 1);
xblock = block_xl + blocknum % (block_xh - block_xl + 1);
qprintf("############### block %2i,%2i ###############\n", xblock, yblock);
mins[0] = xblock * block_size;
mins[1] = yblock * block_size;
mins[2] = -max_bounds; // was -4096
maxs[0] = (xblock + 1) * block_size;
maxs[1] = (yblock + 1) * block_size;
maxs[2] = max_bounds; // was 4096
// the makelist and chopbrushes could be cached between the passes...
brushes = MakeBspBrushList(brush_start, brush_end, mins, maxs);
if (!brushes) {
node = AllocNode();
node->planenum = PLANENUM_LEAF;
node->contents = CONTENTS_SOLID;
block_nodes[xblock + 5][yblock + 5] = node;
return;
}
if (!nocsg)
brushes = ChopBrushes(brushes);
tree = BrushBSP(brushes, mins, maxs);
block_nodes[xblock + 5][yblock + 5] = tree->headnode;
}
/*
============
ProcessWorldModel
============
*/
void ProcessWorldModel(void) {
entity_t *e;
tree_t *tree;
qboolean leaked;
qboolean optimize;
e = &entities[entity_num];
brush_start = e->firstbrush;
brush_end = brush_start + e->numbrushes;
leaked = false;
//
// perform per-block operations
//
if (block_xh * block_size > map_maxs[0])
block_xh = floor(map_maxs[0] / block_size);
if ((block_xl + 1) * block_size < map_mins[0])
block_xl = floor(map_mins[0] / block_size);
if (block_yh * block_size > map_maxs[1])
block_yh = floor(map_maxs[1] / block_size);
if ((block_yl + 1) * block_size < map_mins[1])
block_yl = floor(map_mins[1] / block_size);
if (block_xl < -4)
block_xl = -4;
if (block_yl < -4)
block_yl = -4;
if (block_xh > 3)
block_xh = 3;
if (block_yh > 3)
block_yh = 3;
for (optimize = false; optimize <= true; optimize++) {
qprintf("--------------------------------------------\n");
RunThreadsOnIndividual((block_xh - block_xl + 1) * (block_yh - block_yl + 1),
!verbose, ProcessBlock_Thread);
//
// build the division tree
// oversizing the blocks guarantees that all the boundaries
// will also get nodes.
//
qprintf("--------------------------------------------\n");
tree = AllocTree();
tree->headnode = BlockTree(block_xl - 1, block_yl - 1, block_xh + 1, block_yh + 1);
tree->mins[0] = (block_xl)*block_size;
tree->mins[1] = (block_yl)*block_size;
tree->mins[2] = map_mins[2] - 8;
tree->maxs[0] = (block_xh + 1) * block_size;
tree->maxs[1] = (block_yh + 1) * block_size;
tree->maxs[2] = map_maxs[2] + 8;
//
// perform the global operations
//
MakeTreePortals(tree);
if (FloodEntities(tree))
FillOutside(tree->headnode);
else {
printf("**** leaked ****\n");
leaked = true;
LeakFile(tree);
if (leaktest) {
printf("--- MAP LEAKED ---\n");
exit(0);
}
}
MarkVisibleSides(tree, brush_start, brush_end);
if (leaked)
break;
if (!optimize) {
FreeTree(tree);
}
}
FloodAreas(tree);
MakeFaces(tree->headnode);
FixTjuncs(tree->headnode);
if (!noprune)
PruneNodes(tree->headnode);
WriteBSP(tree->headnode);
if (!leaked)
WritePortalFile(tree);
FreeTree(tree);
}
/*
============
ProcessSubModel
============
*/
void ProcessSubModel(void) {
entity_t *e;
int32_t start, end;
tree_t *tree;
bspbrush_t *list;
vec3_t mins, maxs;
e = &entities[entity_num];
start = e->firstbrush;
end = start + e->numbrushes;
mins[0] = mins[1] = mins[2] = -max_bounds;
maxs[0] = maxs[1] = maxs[2] = max_bounds;
list = MakeBspBrushList(start, end, mins, maxs);
if (!nocsg)
list = ChopBrushes(list);
tree = BrushBSP(list, mins, maxs);
MakeTreePortals(tree);
MarkVisibleSides(tree, start, end);
MakeFaces(tree->headnode);
FixTjuncs(tree->headnode);
WriteBSP(tree->headnode);
FreeTree(tree);
}
/*
============
ProcessModels
============
*/
void ProcessModels(void) {
BeginBSPFile();
for (entity_num = 0; entity_num < num_entities; entity_num++) {
if (!entities[entity_num].numbrushes)
continue;
qprintf("############### model %i ###############\n", nummodels);
BeginModel();
if (entity_num == 0)
ProcessWorldModel();
else
ProcessSubModel();
EndModel();
}
EndBSPFile();
}
void BSP_ProcessArgument(const char * arg) {
char path[2053];
strcpy(source, ExpandArg(arg));
StripExtension(source);
// delete portal and line files
sprintf(path, "%s.prt", source);
remove(path);
sprintf(path, "%s.pts", source);
remove(path);
strcpy(name, ExpandArg(arg));
DefaultExtension(name, ".map"); // might be .reg
InitBSPFile();
//
// if onlyents, just grab the entites and resave
//
if (onlyents) {
char out[2053];
sprintf(out, "%s.bsp", source);
LoadBSPFile(out);
if (use_qbsp)
printf("use_qbsp = true\n");
num_entities = 0;
LoadMapFile(name);
SetModelNumbers();
SetLightStyles();
UnparseEntities();
WriteBSPFile(out);
} else {
extern void ProcessModels();
//
// start from scratch
//
LoadMapFile(name);
SetModelNumbers();
SetLightStyles();
ProcessModels();
}
PrintBSPFileSizes();
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END 4bsp >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
}
per_platform executable ___-q2tools \
--include-directory quake2/tools/common \
--lflag -lm \
quake2/tools/main.c \
quake2/tools/4bsp/4bsp.c \
quake2/tools/4bsp/brushbsp.c \
quake2/tools/4bsp/csg.c \
quake2/tools/4bsp/faces.c \
quake2/tools/4bsp/leakfile.c \
quake2/tools/4bsp/map.c \
quake2/tools/4bsp/portals.c \
quake2/tools/4bsp/prtfile.c \
quake2/tools/4bsp/textures.c \
quake2/tools/4bsp/tree.c \
quake2/tools/4bsp/writebsp.c \
quake2/tools/4rad/4rad.c \
quake2/tools/4rad/lightmap.c \
quake2/tools/4rad/patches.c \
quake2/tools/4rad/trace.c \
quake2/tools/4vis/4vis.c \
quake2/tools/4vis/flow.c \
quake2/tools/common/bspfile.c \
quake2/tools/common/cmdlib.c \
quake2/tools/common/l3dslib.c \
quake2/tools/common/lbmlib.c \
quake2/tools/common/llwolib.c \
quake2/tools/common/mathlib.c \
quake2/tools/common/mdfour.c \
quake2/tools/common/polylib.c \
quake2/tools/common/scriplib.c \
quake2/tools/common/threads.c \
quake2/tools/common/trilib.c
#quake2/tools/4data/4data.c \
#quake2/tools/4data/images.c \
#quake2/tools/4data/models.c \
#quake2/tools/4data/sprites.c \
#quake2/tools/4data/tables.c \
#quake2/tools/4data/video.c \