/* vis.c PVS/PHS generation tool Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2002 Colin Thompson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_IO_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include #ifdef HAVE_PTHREAD_H # include #endif #include "QF/bspfile.h" #include "QF/cmd.h" #include "QF/cmem.h" #include "QF/dstring.h" #include "QF/mathlib.h" #include "QF/msg.h" #include "QF/quakefs.h" #include "QF/sizebuf.h" #include "QF/sys.h" #include "tools/qfvis/include/vis.h" #include "tools/qfvis/include/options.h" #ifdef USE_PTHREADS pthread_attr_t threads_attrib; pthread_rwlock_t *global_lock; pthread_rwlock_t *portal_locks; pthread_rwlock_t *stats_lock; #endif bsp_t *bsp; options_t options; static threaddata_t main_thread; static visstat_t global_stats; unsigned long base_mightsee; unsigned long base_selfcull; unsigned long base_clustercull; unsigned long base_clustertest; unsigned long base_spherecull; unsigned long base_spheretest; unsigned long base_spherepass; unsigned long base_windingcull; unsigned long base_windingtest; unsigned long base_windingpass; static unsigned portal_count; unsigned numportals; unsigned portalclusters; unsigned numrealleafs; unsigned originalvismapsize; int totalvis; int count_sep; int bitbytes; // (portalleafs + 63)>>3 int bitlongs; int bitbytes_l; // (numrealleafs + 63)>>3 portal_t **portal_queue; portal_t *portals; cluster_t *clusters; dstring_t *visdata; byte *uncompressed; // [bitbytes * portalleafs] uint32_t *leafcluster; // leaf to cluster mappings as read from .prt file int *working; // per thread current portal # int progress_tick; static void InitThreads (void) { #ifdef USE_PTHREADS if (pthread_attr_init (&threads_attrib) == -1) Sys_Error ("pthread_attr_create failed"); if (pthread_attr_setstacksize (&threads_attrib, 0x100000) == -1) Sys_Error ("pthread_attr_setstacksize failed"); global_lock = malloc (sizeof (pthread_rwlock_t)); if (pthread_rwlock_init (global_lock, 0)) Sys_Error ("pthread_rwlock_init failed"); stats_lock = malloc (sizeof (pthread_rwlock_t)); if (pthread_rwlock_init (stats_lock, 0)) Sys_Error ("pthread_rwlock_init failed"); #else // Unable to run multi-threaded, so force threadcount to 1 options.threads = 1; #endif } static void EndThreads (void) { #ifdef USE_PTHREADS if (pthread_rwlock_destroy (global_lock) == -1) Sys_Error ("pthread_rwlock_destroy failed"); free (global_lock); if (pthread_rwlock_destroy (stats_lock) == -1) Sys_Error ("pthread_rwlock_destroy failed"); free (stats_lock); #endif } static vec4f_t PlaneFromWinding (winding_t *winding) { vec4f_t plane; vec4f_t v1, v2; // calc plane using CW winding v1 = winding->points[2] - winding->points[1]; v2 = winding->points[0] - winding->points[1]; plane = normalf (crossf (v2, v1)); // negative so dot(point, plane) includes -dist (point[3] = 1) plane[3] = -dotf (winding->points[0], plane)[0]; return plane; } winding_t * NewWinding (threaddata_t *thread, int points) { winding_t *winding; unsigned size; size = field_offset (winding_t, points[points]); winding = Hunk_RawAlloc (thread->hunk, size); memset (winding, 0, size); winding->id = thread->winding_id++; winding->thread = thread->id; return winding; } winding_t * CopyWinding (threaddata_t *thread, const winding_t *w) { unsigned size; winding_t *copy; size = field_offset (winding_t, points[w->numpoints]); copy = Hunk_RawAlloc (thread->hunk, size); memcpy (copy, w, size); copy->original = false; copy->id = thread->winding_id++; copy->thread = thread->id; return copy; } static winding_t * NewFlippedWinding (threaddata_t *thread, const winding_t *w) { winding_t *flipped; unsigned i; flipped = NewWinding (thread, w->numpoints); for (i = 0; i < w->numpoints; i++) { flipped->points[w->numpoints - 1 - i] = w->points[i]; } flipped->numpoints = w->numpoints; return flipped; } static vec4i_t signeps (vec4f_t dist) { #ifdef __SSE3__ const vec4f_t zero = {}; const vec4f_t eps = { ON_EPSILON, ON_EPSILON, ON_EPSILON, ON_EPSILON }; vec4f_t d = _mm_addsub_ps (zero, dist); vec4i_t c = (d - eps) > 0; c = (vec4i_t) _mm_hsub_epi32 ((__m128i) c, (__m128i) c); return c; #else float d = dist[0]; int front = (d >= ON_EPSILON); int back = (d <= -ON_EPSILON); int i = front - back; return (vec4i_t) { i, i, i, i }; #endif } static vec4f_t split_edge (const vec4f_t *points, const vec4f_t *dists, int ind1, int ind2, vec4f_t split) { vec4f_t p1 = points[ind1]; vec4f_t p2 = points[ind2]; vec4f_t d1 = dists[ind1]; vec4f_t d2 = dists[ind2]; // avoid nan/inf in w: d1's w is never 0 (would not be here if it was) // so the multiply ensures d1.w - d2.w cannot be 0 and thus d1.w/diff // will not result in division by 0 static const vec4f_t one = { 1, 1, 1, 0 }; vec4f_t d = d1 / (d1 - d2 * one); vec4f_t mid = p1 + d * (p2 - p1); // avoid roundoff error when possible by forcing the appropriate // component to the split-plane's distance when the split-plane's // normal is signed-canonical. // "nan" because 0x7fffffff is nan when viewed as a float static const vec4i_t onenan = {0x3f800000,0x3f800000,0x3f800000,~0u >> 1}; static const vec4i_t nan = { ~0u >> 1, ~0u >> 1, ~0u >> 1, ~0u >> 1}; vec4i_t x = ((vec4i_t) split & nan) == onenan; // plane vector has -dist in w vec4f_t y = (vec4f_t) ((vec4i_t) split & x) * -split[3]; #ifdef __SSE3__ mid = _mm_blendv_ps (mid, y, (__m128) x); #else mid = (vec4f_t) (((vec4i_t) y & x) | ((vec4i_t) mid & ~x)); #endif // if (isnan (mid[0])) *(int *) 0 = 0; return mid; } static inline unsigned __attribute__((const)) is_not_on (unsigned x) { return x & 1; } static inline unsigned __attribute__((const)) is_back (unsigned x) { return x & 2; } static inline unsigned __attribute__((const)) is_not_back (unsigned x) { return ~x & 2; } static inline unsigned __attribute__((const)) is_front (unsigned x) { return is_not_on (x) & (is_not_back (x) >> 1); } static inline unsigned __attribute__((const)) is_back_front (unsigned x, unsigned y) { return (is_back (x) >> 1) & is_front (y); } static inline unsigned __attribute__((const)) is_front_back (unsigned x, unsigned y) { return is_front (x) & (is_back (y) >> 1); } static inline unsigned __attribute__((const)) is_transition (unsigned x, unsigned y) { return is_back_front (x, y) | is_front_back (x, y); } static inline void test_point (vec4f_t split, const vec4f_t *points, int index, vec4f_t *dists, int *sides, unsigned *counts) { dists[index] = dotf (points[index], split); sides[index] = signeps (dists[index])[0]; counts[sides[index]]++; } #undef SIDE_FRONT #undef SIDE_BACK #undef SIDE_ON #define SIDE_FRONT 1 #define SIDE_BACK -1 #define SIDE_ON 0 /* ClipWinding Clips the winding to the plane, returning the new winding on the positive side Frees the input winding. If keepon is true, an exactly on-plane winding will be saved, otherwise it will be clipped away. */ winding_t * ClipWinding (threaddata_t *thread, winding_t *in, vec4f_t split, bool keepon) { unsigned maxpts = 0; unsigned i; unsigned _counts[3]; unsigned *const counts = _counts + 1; int *const sides = alloca ((in->numpoints + 1) * sizeof (int)); vec4f_t *const dists = alloca ((in->numpoints + 1) * sizeof (vec4f_t)); winding_t *neww; counts[SIDE_FRONT] = counts[SIDE_ON] = counts[SIDE_BACK] = 0; // determine sides for each point test_point (split, in->points, 0, dists, sides, counts); for (i = 1; i < in->numpoints; i++) { test_point (split, in->points, i, dists, sides, counts); maxpts += is_transition (sides[i - 1], sides[i]); } sides[i] = sides[0]; dists[i] = dists[0]; maxpts += is_transition (sides[i - 1], sides[i]); if (keepon && counts[SIDE_ON] == in->numpoints) { return in; } if (!counts[SIDE_FRONT]) { return NULL; } if (!counts[SIDE_BACK]) { return in; } maxpts += in->numpoints - counts[SIDE_BACK]; neww = NewWinding (thread, maxpts); for (i = 0; i < in->numpoints; i++) { if (sides[i] == SIDE_ON) { neww->points[neww->numpoints++] = in->points[i]; continue; } if (sides[i] == SIDE_FRONT) { neww->points[neww->numpoints++] = in->points[i]; } if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) { continue; } unsigned j = (i + 1) == in->numpoints ? 0 : i + 1; vec4f_t mid = split_edge (in->points, dists, i, j, split); neww->points[neww->numpoints++] = mid; } if (neww->numpoints < maxpts) { Sys_Error ("ClipWinding: not all points copied: n:%u m:%u i:%u %u %u %u", neww->numpoints, maxpts, in->numpoints, counts[SIDE_BACK], counts[SIDE_ON], counts[SIDE_FRONT]); } if (neww->numpoints > maxpts) { Sys_Error ("ClipWinding: points exceeded estimate: n:%u m:%u", neww->numpoints, maxpts); } return neww; } static portal_t * GetNextPortal (int limit) { portal_t *p = 0; if (!(limit && options.portal_limit > 0 && portal_count >= options.portal_limit)) { WRLOCK (global_lock); progress_tick++; if (portal_count < 2 * numportals) { p = portal_queue[portal_count++]; p->status = stat_selected; } UNLOCK (global_lock); } return p; } static void UpdateMightsee (threaddata_t *thread, cluster_t *source, cluster_t *dest) { int i, clusternum; portal_t *portal; clusternum = dest - clusters; for (i = 0; i < source->numportals; i++) { portal = &source->portals[i]; WRLOCK_PORTAL (portal); if (portal->status == stat_none) { if (set_is_member (portal->mightsee, clusternum)) { set_remove (portal->mightsee, clusternum); portal->nummightsee--; thread->stats.mightseeupdate++; } } UNLOCK_PORTAL (portal); } } static void UpdateStats (threaddata_t *thread) { WRLOCK (stats_lock); global_stats.portaltest += thread->stats.portaltest; global_stats.portalpass += thread->stats.portalpass; global_stats.portalcheck += thread->stats.portalcheck; global_stats.targettested += thread->stats.targettested; global_stats.targettrimmed += thread->stats.targettrimmed; global_stats.targetclipped += thread->stats.targetclipped; global_stats.sourcetested += thread->stats.sourcetested; global_stats.sourcetrimmed += thread->stats.sourcetrimmed; global_stats.sourceclipped += thread->stats.sourceclipped; global_stats.chains += thread->stats.chains; global_stats.mighttest += thread->stats.mighttest; global_stats.vistest += thread->stats.vistest; global_stats.mightseeupdate += thread->stats.mightseeupdate; global_stats.sep_alloc += thread->stats.sep_alloc; global_stats.sep_free += thread->stats.sep_free; global_stats.winding_mark = max (global_stats.winding_mark, thread->stats.winding_mark); global_stats.stack_alloc += thread->stats.stack_alloc; global_stats.stack_free += thread->stats.stack_free; UNLOCK (stats_lock); memset (&thread->stats, 0, sizeof (thread->stats)); } static void PortalCompleted (threaddata_t *thread, portal_t *completed) { portal_t *portal; cluster_t *cluster; set_t *changed; set_iter_t *ci; int i, j; completed->status = stat_done; changed = set_new_size_r (&thread->set_pool, portalclusters); cluster = &clusters[completed->cluster]; for (i = 0; i < cluster->numportals; i++) { portal = &cluster->portals[i]; if (portal->status != stat_done) continue; set_assign (changed, portal->mightsee); set_difference (changed, portal->visbits); #if 0 dstring_copystr (thread->str, "mightsee: "); set_as_string_r (thread->str, portal->mightsee); dstring_appendstr (thread->str, "\nvisbits: "); set_as_string_r (thread->str, portal->visbits); dstring_appendstr (thread->str, "\nchanged: "); set_as_string_r (thread->str, changed); dstring_appendstr (thread->str, "\n"); write (0, thread->str->str, thread->str->size - 1); #endif for (j = 0; j < cluster->numportals; j++) { if (j == i) continue; if (cluster->portals[j].status == stat_done) set_difference (changed, cluster->portals[j].visbits); else set_difference (changed, cluster->portals[j].mightsee); } for (ci = set_first_r (&thread->set_pool, changed); ci; ci = set_next_r (&thread->set_pool, ci)) { UpdateMightsee (thread, &clusters[ci->element], cluster); } } set_delete_r (&thread->set_pool, changed); UpdateStats (thread); } static void dump_super_stats (int id, memsuper_t *super) { size_t total_size = 0; size_t total_allocated = 0; size_t total_post_size = 0; size_t total_post_allocated = 0; size_t num_blocks = 0; size_t free_counts[MAX_CACHE_LINES]; for (memblock_t *block = super->memblocks; block; block = block->next) { num_blocks++; total_size += block->size; total_allocated += block->allocated; total_post_size += block->post_size; // post_free is a flag total_post_allocated += !block->post_free * block->post_size; } for (int i = 0; i < MAX_CACHE_LINES; i++) { free_counts[i] = 0; for (memline_t *line = super->free_lines[i]; line; line = line->free_next) { free_counts[i]++; } } WRLOCK (global_lock); printf ("cmem stats for thread %d\n", id); printf (" blocks: %zd\n", num_blocks); printf (" : s:%-8zd a:%-8zd f:%-8zd\n", total_size, total_allocated, total_size - total_allocated); printf (" post: s:%-8zd a:%-8zd f:%-8zd\n", total_post_size, total_post_allocated, total_post_size - total_post_allocated); printf (" "); for (int i = 0; i < MAX_CACHE_LINES; i++) { printf (" %5d", 64 << i); } printf ("\n"); printf (" "); for (int i = 0; i < MAX_CACHE_LINES; i++) { printf (" %5zd", free_counts[i]); } printf ("\n"); UNLOCK (global_lock); } static void * LeafThread (void *_thread) { portal_t *portal; int thread = (int) (intptr_t) _thread; threaddata_t data; int count = 0; size_t thread_memsize = 1024 * 1024; memset (&data, 0, sizeof (data)); data.hunk = Hunk_Init (Sys_Alloc (thread_memsize), thread_memsize); set_pool_init (&data.set_pool); data.id = thread; data.memsuper = new_memsuper (); data.str = dstring_new (); do { portal = GetNextPortal (1); if (!portal) break; if (options.verbosity >= 3 && (!count++ % 16)) { dump_super_stats (thread, data.memsuper); } if (working) working[thread] = (int) (portal - portals); PortalFlow (&data, portal); int whm = data.stats.winding_mark; int shw = data.stats.sep_highwater; int smb = data.stats.sep_maxbulk; PortalCompleted (&data, portal); data.stats.sep_highwater = shw; data.stats.sep_maxbulk = smb; data.stats.winding_mark = whm; if (options.verbosity >= 4) printf ("portal:%5i mightsee:%5i cansee:%5i %5u/%u\n", (int) (portal - portals), portal->nummightsee, portal->numcansee, portal_count, numportals * 2); } while (1); if (options.verbosity >= 2) { printf ("thread %d winding mark: %zd\n", thread, data.stats.winding_mark); printf ("thread %d separator highwater: %d\n", thread, data.stats.sep_highwater); printf ("thread %d separator maxbulk: %d\n", thread, data.stats.sep_maxbulk); } if (options.verbosity >= 4) printf ("thread %d done\n", thread); if (working) working[thread] = -1; delete_memsuper (data.memsuper); dstring_delete (data.str); Sys_Free (data.hunk, thread_memsize); return NULL; } static void * BaseVisThread (void *_thread) { portal_t *portal; int thread = (int) (intptr_t) _thread; basethread_t data; set_pool_t set_pool; int num_mightsee = 0; memset (&data, 0, sizeof (data)); data.id = thread; set_pool_init (&set_pool); data.portalsee = set_new_size_r (&set_pool, numportals * 2); do { portal = GetNextPortal (0); if (!portal) break; if (working) working[thread] = (int) (portal - portals); portal->mightsee = set_new_size_r (&set_pool, portalclusters); set_empty (data.portalsee); PortalBase (&data, portal); num_mightsee += data.clustersee; data.clustersee = 0; } while (1); WRLOCK (stats_lock); base_selfcull += data.selfcull; base_clustercull += data.clustercull; base_clustertest += data.clustertest; base_spherecull += data.spherecull; base_spheretest += data.spheretest; base_spherepass += data.spherepass; base_windingcull += data.windingcull; base_windingtest += data.windingtest; base_windingpass += data.windingpass; base_mightsee += num_mightsee; UNLOCK (stats_lock); if (options.verbosity >= 4) printf ("thread %d done\n", thread); if (working) working[thread] = -1; return NULL; } #ifdef USE_PTHREADS const char spinner[] = "|/-\\"; const char progress[] = "0....1....2....3....4....5....6....7....8....9...."; static void print_thread_stats (const int *local_work, int thread, int spinner_ind) { int i; for (i = 0; i < thread; i++) printf ("%6d", local_work[i]); printf (" %5u / %5u", portal_count, numportals * 2); fflush (stdout); printf (" %c\r", spinner[spinner_ind % 4]); fflush (stdout); } static int portal_progress (void) { return portal_count * 100 / (numportals * 2) + 1; } static int print_progress (int prev_prog, int prog, int spinner_ind) { prog /= 2; if (prog > prev_prog) printf ("%.*s", prog - prev_prog, progress + prev_prog); printf (" %c\b\b", spinner[spinner_ind % 4]); fflush (stdout); return prog; } typedef struct { int thread; int (*calc_progress)(void); } watch_data_t; static void * WatchThread (void *_wd) { watch_data_t *wd = _wd; int thread = wd->thread; int *local_work = malloc (thread * sizeof (int)); int i; int spinner_ind = 0; int count = 0; int prev_prog = 0; int prev_tick = 0; int stalled = 0; while (1) { usleep (1000); for (i = 0; i < thread; i ++) if (working[i] >= 0) break; if (i == thread) break; if (count++ == 50) { count = 0; for (i = 0; i < thread; i ++) local_work[i] = working[i]; if (options.verbosity >= 4) print_thread_stats (local_work, thread, spinner_ind); else if (options.verbosity >= 0) { prev_prog = print_progress (prev_prog, wd->calc_progress (), spinner_ind); } if (prev_tick != progress_tick || stalled++ == 20) { prev_tick = progress_tick; stalled = 0; spinner_ind++; } } } if (options.verbosity >= 4) { printf ("watch thread done\n"); } else if (options.verbosity >= 0) { prev_prog = print_progress (prev_prog, wd->calc_progress (), spinner_ind); } free (local_work); return NULL; } #endif static double thread_start (const char *msg) { if (options.verbosity >= 0) { printf ("%-10.10s: ", msg); } if (options.verbosity >= 4) { printf ("\n"); } return Sys_DoubleTime (); } static void thread_end (double start) { double end = Sys_DoubleTime (); double duration = end - start; if (options.verbosity >= 0) { if (duration >= 1.2) { printf (" %5.1fs", duration); } else { printf (" %5.1fms", duration * 1000); } } printf ("\n"); } void RunThreads (const char *heading, void *(*thread_func) (void *), int (*calc_progress)(void)) { #ifdef USE_PTHREADS pthread_t *work_threads; void *status; int i; double start; if (options.threads > 1) { work_threads = alloca ((options.threads + 1) * sizeof (pthread_t *)); working = calloc (options.threads, sizeof (int)); start = thread_start (heading); for (i = 0; i < options.threads; i++) { if (pthread_create (&work_threads[i], &threads_attrib, thread_func, (void *) (intptr_t) i) == -1) Sys_Error ("pthread_create failed"); } watch_data_t wd = { i, calc_progress }; if (pthread_create (&work_threads[i], &threads_attrib, WatchThread, &wd) == -1) Sys_Error ("pthread_create failed"); for (i = 0; i < options.threads; i++) { if (pthread_join (work_threads[i], &status) == -1) Sys_Error ("pthread_join failed"); } if (pthread_join (work_threads[i], &status) == -1) Sys_Error ("pthread_join failed"); thread_end (start); free (working); } else { start = thread_start (heading); thread_func (0); thread_end (start); } #else thread_func (0); #endif } int CompressRow (sizebuf_t *dest, const byte *vis, unsigned num_leafs, int utf8) { int rep, visrow, j; byte *dest_p; // if we ever need 2GB (32Gb) of vis data for a single leaf... int maxrep = utf8 ? 0x7fffffff : 255; dest_p = dest->data; visrow = (num_leafs + 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 == maxrep) break; else rep++; if (utf8) { dest->cursize = dest_p - dest->data; MSG_WriteUTF8 (dest, rep); dest_p = dest->data + dest->cursize; } else { *dest_p++ = rep; } j--; } dest->cursize = dest_p - dest->data; return dest->cursize; } static void ClusterFlowExpand (const set_t *src, byte *dest) { unsigned i, j; for (j = 1, i = 0; i < numrealleafs; i++) { if (set_is_member (src, leafcluster[i])) *dest |= j; j <<= 1; if (j == 256) { j = 1; dest++; } } } static sizebuf_t *compressed_vis; /* ClusterFlow Builds the entire visibility list for a cluster */ void ClusterFlow (int clusternum) { set_t visclusters = SET_STATIC_INIT (portalclusters, alloca); sizebuf_t *compressed = &compressed_vis[clusternum]; byte *outbuffer; int numvis, i; cluster_t *cluster; portal_t *portal; outbuffer = uncompressed + clusternum * bitbytes_l; cluster = &clusters[clusternum]; set_empty (&visclusters); // flow through all portals, collecting visible bits for (i = 0; i < cluster->numportals; i++) { portal = &cluster->portals[i]; if (portal->status != stat_done) Sys_Error ("portal not done"); if (set_is_member (portal->visbits, clusternum)) { //printf ("cluster %d saw into self via portal %d\n", // clusternum, (int) (portal - portals)); } set_union (&visclusters, portal->visbits); } set_add (&visclusters, clusternum); numvis = set_count (&visclusters); // expand to cluster->leaf PVS ClusterFlowExpand (&visclusters, outbuffer); // compress the bit string if (options.verbosity >= 4) printf ("cluster %4i : %4i visible\n", clusternum, numvis); totalvis += numvis; CompressRow (compressed, outbuffer, numrealleafs, 0); } static unsigned work_cluster; static int flow_progress (void) { return work_cluster * 100 / portalclusters; } static unsigned next_cluster (void) { unsigned cluster = ~0; WRLOCK (global_lock); progress_tick++; if (work_cluster < portalclusters) { cluster = work_cluster++; } UNLOCK (global_lock); return cluster; } static void * FlowClusters (void *d) { int thread = (intptr_t) d; while (1) { unsigned clusternum = next_cluster (); if (working) { working[thread] = clusternum; } if (clusternum == ~0u) { break; } ClusterFlow (clusternum); } return 0; } static void CompactPortalVis (void) { compressed_vis = malloc (portalclusters * sizeof (sizebuf_t)); for (unsigned i = 0; i < portalclusters; i++) { compressed_vis[i] = (sizebuf_t) { .maxsize = (bitbytes_l * 3) / 2, .data = malloc ((bitbytes_l * 3) / 2) }; } RunThreads ("Comp vis", FlowClusters, flow_progress); for (unsigned i = 0; i < portalclusters; i++) { unsigned size = compressed_vis[i].cursize; clusters[i].visofs = visdata->size; dstring_append (visdata, (char *) compressed_vis[i].data, size); } } static int portalcmp (const void *_a, const void *_b) { portal_t *a = *(portal_t **) _a; portal_t *b = *(portal_t **) _b; return a->nummightsee - b->nummightsee; } static void BasePortalVis (void) { double start, end; start = Sys_DoubleTime (); RunThreads ("Base vis", BaseVisThread, portal_progress); end = Sys_DoubleTime (); if (options.verbosity >= 1) { unsigned long n = numportals; printf ("base_mightsee: %lu %gs\n", base_mightsee, end - start); printf ("\n"); printf (" total tests: %lu\n", n * n * 4); printf ("cluster test: %lu\n", base_clustertest); printf ("cluster cull: %lu\n", base_clustercull); printf (" self cull: %lu\n", base_selfcull); printf (" sphere test: %lu\n", base_spheretest); printf (" sphere cull: %lu\n", base_spherecull); printf (" sphere pass: %lu\n", base_spherepass); printf ("winding test: %lu\n", base_windingtest); printf ("winding cull: %lu\n", base_windingcull); printf ("winding pass: %lu\n", base_windingpass); } } static void CalcPortalVis (void) { unsigned i; double start, end; // fastvis just uses mightsee for a very loose bound if (options.minimal) { for (i = 0; i < numportals * 2; i++) { portals[i].visbits = portals[i].mightsee; portals[i].status = stat_done; } return; } start = Sys_DoubleTime (); qsort (portal_queue, numportals * 2, sizeof (portal_t *), portalcmp); end = Sys_DoubleTime (); if (options.verbosity >= 1) printf ("qsort: %gs\n", end - start); portal_count = 0; RunThreads ("Full vis", LeafThread, portal_progress); if (options.verbosity >= 1) { printf ("portalcheck: %ld portaltest: %ld portalpass: %ld\n", global_stats.portalcheck, global_stats.portaltest, global_stats.portalpass); printf ("target trimmed: %ld clipped: %ld tested: %ld\n", global_stats.targettrimmed, global_stats.targetclipped, global_stats.targettested); printf ("source trimmed: %ld clipped: %ld tested: %ld\n", global_stats.sourcetrimmed, global_stats.sourceclipped, global_stats.sourcetested); printf ("vistest: %ld mighttest: %ld mightseeupdate: %ld\n", global_stats.vistest, global_stats.mighttest, global_stats.mightseeupdate); if (options.verbosity >= 2) { printf ("separators allocated: %u freed: %u %u\n", global_stats.sep_alloc, global_stats.sep_free, global_stats.sep_alloc - global_stats.sep_free); printf ("max windings mark: %zd\n", global_stats.winding_mark); printf ("stack blocks allocated: %u freed: %u %u\n", global_stats.stack_alloc, global_stats.stack_free, global_stats.stack_alloc - global_stats.stack_free); } } } static void CalcVis (void) { unsigned i; printf ("Thread count: %d\n", options.threads); BasePortalVis (); for (i = 0; i < 2 * numportals; i++) { portals[i].status = stat_none; } CalcPortalVis (); // assemble the leaf vis lists by oring and compressing the portal lists CompactPortalVis (); for (i = 0; i < numrealleafs; i++) { bsp->leafs[i + 1].visofs = clusters[leafcluster[i]].visofs; } if (options.verbosity >= 0) printf ("average clusters visible: %u\n", totalvis / portalclusters); } static void CalcClusterSpheres (void) { memhunk_t *hunk = main_thread.hunk; for (unsigned i = 0; i < portalclusters; i++) { cluster_t *cluster = &clusters[i]; int vertcount = 0; for (int j = 0; j < cluster->numportals; j++) { vertcount += cluster->portals[j].winding->numpoints; } size_t mark = Hunk_LowMark (hunk); vec4f_t *verts = Hunk_RawAlloc (hunk, vertcount * sizeof (vec4f_t)); vertcount = 0; for (int j = 0; j < cluster->numportals; j++) { memcpy (verts + vertcount, cluster->portals[j].winding->points, cluster->portals[j].winding->numpoints * sizeof (vec4f_t)); vertcount += cluster->portals[j].winding->numpoints; } cluster->sphere = SmallestEnclosingBall_vf (verts, vertcount); Hunk_RawFreeToLowMark (hunk, mark); } } #if 0 static bool PlaneCompare (plane_t *p1, plane_t *p2) { int i; if (fabs (p1->dist - p2->dist) > 0.01) return false; for (i = 0; i < 3; i++) if (fabs (p1->normal[i] - p2->normal[i]) > 0.001) return false; return true; } static sep_t * FindPassages (winding_t *source, winding_t *pass) { double length; float d; int i, j, k, l; int counts[3]; plane_t plane; bool fliptest; sep_t *sep, *list; vec3_t v1, v2; list = NULL; // check all combinations for (i = 0; i < source->numpoints; i++) { l = (i + 1) % source->numpoints; VectorSubtract (source->points[l], source->points[i], v1); // find a vertex of pass that makes a plane that puts all of the // vertexes of pass on the front side and all of the vertexes of // source on the back side for (j = 0; j < pass->numpoints; j++) { VectorSubtract (pass->points[j], source->points[i], v2); plane.normal[0] = v1[1] * v2[2] - v1[2] * v2[1]; plane.normal[1] = v1[2] * v2[0] - v1[0] * v2[2]; plane.normal[2] = v1[0] * v2[1] - v1[1] * v2[0]; // if points don't make a valid plane, skip it length = plane.normal[0] * plane.normal[0] + plane.normal[1] * plane.normal[1] + plane.normal[2] * plane.normal[2]; if (length < ON_EPSILON) continue; length = 1 / sqrt(length); plane.normal[0] *= length; plane.normal[1] *= length; plane.normal[2] *= length; plane.dist = DotProduct (pass->points[j], plane.normal); // find out which side of the generated seperating plane has the // source portal fliptest = false; for (k = 0; k < source->numpoints; k++) { if (k == i || k == l) continue; d = DotProduct (source->points[k], plane.normal) - plane.dist; if (d < -ON_EPSILON) { // source is on the negative side, so we want all // pass and target on the positive side fliptest = false; break; } else if (d > ON_EPSILON) { // source is on the positive side, so we want all // pass and target on the negative side fliptest = true; break; } } if (k == source->numpoints) continue; // planar with source portal // flip the normal if the source portal is backwards if (fliptest) { VectorNegate (plane.normal, plane.normal); plane.dist = -plane.dist; } // if all of the pass portal points are now on the positive side, // this is the seperating plane counts[0] = counts[1] = counts[2] = 0; for (k = 0; k < pass->numpoints; k++) { if (k == j) continue; d = DotProduct (pass->points[k], plane.normal) - plane.dist; if (d < -ON_EPSILON) break; else if (d > ON_EPSILON) counts[0]++; else counts[2]++; } if (k != pass->numpoints) continue; // points on negative side, not a seperating plane if (!counts[0]) continue; // planar with pass portal // save this out count_sep++; sep = malloc (sizeof (*sep)); sep->next = list; list = sep; sep->plane = plane; } } return list; } static void CalcPassages (void) { int count, count2, i, j, k; leaf_t *leaf; portal_t *p1, *p2; sep_t *sep; passage_t *passages; if (options.verbosity >= 0) printf ("building passages...\n"); count = count2 = 0; for (i = 0; i < portalleafs; i++) { leaf = &leafs[i]; for (j = 0; j < leaf->numportals; j++) { p1 = leaf->portals[j]; for (k = 0; k < leaf->numportals; k++) { if (k == j) continue; count++; p2 = leaf->portals[k]; // definately can't see into a coplanar portal if (PlaneCompare (&p1->plane, &p2->plane)) continue; count2++; sep = FindPassages (p1->winding, p2->winding); if (!sep) { count_sep++; sep = malloc (sizeof (*sep)); sep->next = NULL; sep->plane = p1->plane; } passages = malloc (sizeof (*passages)); passages->planes = sep; passages->from = p1->leaf; passages->to = p2->leaf; passages->next = leaf->passages; leaf->passages = passages; } } } if (options.verbosity >= 0) { printf ("numpassages: %i (%i)\n", count2, count); printf ("total passages: %i\n", count_sep); } } #endif static winding_t * parse_winding (const char *line, int numpoints) { winding_t *winding = NewWinding (&main_thread, numpoints); winding->original = true; winding->numpoints = numpoints; for (int i = 0; i < numpoints; i++) { // (%ld %ld %ld) while (isspace ((byte) *line)) line++; if (*line++ != '(') { return 0; } for (int j = 0; j < 3; j++) { char *err; winding->points[i][j] = strtod (line, &err); if (err == line) { return 0; } line = err; } winding->points[i][3] = 1; while (isspace ((byte) *line)) line++; if (*line++ != ')') { return 0; } } return winding; } static void LoadPortals (char *name) { typedef struct { unsigned clusters[2]; } clusterpair_t; const char *line; char *err; unsigned numpoints; int read_leafs = 0; vec4f_t plane; vspheref_t sphere; QFile *f; clusterpair_t *clusternums; if (!strcmp (name, "-")) f = Qdopen (0, "rt"); // create a QFile of stdin else { f = Qopen (name, "r"); if (!f) { printf ("LoadPortals: couldn't read %s\n", name); printf ("No vising performed.\n"); exit (1); } } line = Qgetline (f); if (line && (!strcmp (line, PORTALFILE "\n") || !strcmp (line, PORTALFILE "\r\n"))) { line = Qgetline (f); if (!line || sscanf (line, "%u\n", &portalclusters) != 1) Sys_Error ("LoadPortals: failed to read header"); line = Qgetline (f); if (!line || sscanf (line, "%u\n", &numportals) != 1) Sys_Error ("LoadPortals: failed to read header"); numrealleafs = portalclusters; } else if (line && (!strcmp (line, PORTALFILE_AM "\n") || !strcmp (line, PORTALFILE_AM "\r\n"))) { line = Qgetline (f); if (!line || sscanf (line, "%u\n", &portalclusters) != 1) Sys_Error ("LoadPortals: failed to read header"); line = Qgetline (f); if (!line || sscanf (line, "%u\n", &numportals) != 1) Sys_Error ("LoadPortals: failed to read header"); line = Qgetline (f); if (!line || sscanf (line, "%u\n", &numrealleafs) != 1) Sys_Error ("LoadPortals: failed to read header"); read_leafs = 1; } else if (line && (!strcmp (line, PORTALFILE2 "\n") || !strcmp (line, PORTALFILE2 "\r\n"))) { line = Qgetline (f); if (!line || sscanf (line, "%u\n", &numrealleafs) != 1) Sys_Error ("LoadPortals: failed to read header"); line = Qgetline (f); if (!line || sscanf (line, "%u\n", &portalclusters) != 1) Sys_Error ("LoadPortals: failed to read header"); line = Qgetline (f); if (!line || sscanf (line, "%u\n", &numportals) != 1) Sys_Error ("LoadPortals: failed to read header"); read_leafs = 1; } else { Sys_Error ("LoadPortals: not a portal file"); } if (options.verbosity >= 0) { printf ("%4u portalclusters\n", portalclusters); printf ("%4u numportals\n", numportals); printf ("%4u numrealleafs\n", numrealleafs); } bitbytes = ((portalclusters + 63) & ~63) >> 3; bitlongs = bitbytes / sizeof (long); bitbytes_l = ((numrealleafs + 63) & ~63) >> 3; // each file portal is split into two memory portals, one for each // direction portals = calloc (2 * numportals, sizeof (portal_t)); portal_queue = malloc (2 * numportals * sizeof (portal_t *)); for (unsigned i = 0; i < 2 * numportals; i++) { portal_queue[i] = &portals[i]; } #ifdef USE_PTHREADS portal_locks = calloc (2 * numportals, sizeof (pthread_rwlock_t)); for (unsigned i = 0; i < 2 * numportals; i++) { if (pthread_rwlock_init (&portal_locks[i], 0)) Sys_Error ("pthread_rwlock_init failed"); } #endif clusters = calloc (portalclusters, sizeof (cluster_t)); originalvismapsize = numrealleafs * ((numrealleafs + 7) / 8); clusternums = calloc (numportals, sizeof (clusterpair_t)); winding_t **windings = malloc (numportals * sizeof (winding_t *)); for (unsigned i = 0; i < numportals; i++) { line = Qgetline (f); if (!line) Sys_Error ("LoadPortals: reading portal %u", i); numpoints = strtol (line, &err, 10); if (err == line) Sys_Error ("LoadPortals: reading portal %u", i); line = err; for (int j = 0; j < 2; j++) { clusternums[i].clusters[j] = strtoul (line, &err, 10); if (err == line) Sys_Error ("LoadPortals: reading portal %u", i); line = err; if (clusternums[i].clusters[j] > portalclusters) Sys_Error ("LoadPortals: reading portal %u", i); } clusters[clusternums[i].clusters[0]].numportals++; clusters[clusternums[i].clusters[1]].numportals++; if (!(windings[i] = parse_winding (line, numpoints))) { Sys_Error ("LoadPortals: reading portal %u", i); } } clusters[0].portals = portals; for (unsigned i = 1; i < portalclusters; i++) { portal_t *portal = clusters[i - 1].portals; clusters[i].portals = portal + clusters[i - 1].numportals; clusters[i - 1].numportals = 0; } clusters[portalclusters - 1].numportals = 0; for (unsigned i = 0; i < numportals; i++) { winding_t *winding = windings[i]; cluster_t *cluster; portal_t *portal; sphere = SmallestEnclosingBall_vf(winding->points, winding->numpoints); //printf (VEC4F_FMT" %.9g\n", VEC4_EXP (sphere.center), sphere.radius); // calc plane plane = PlaneFromWinding (winding); // create forward portal cluster = &clusters[clusternums[i].clusters[0]]; portal = &cluster->portals[cluster->numportals++]; portal->plane = -plane; // plane is for CW, portal is CCW portal->sphere = sphere; portal->cluster = clusternums[i].clusters[1]; portal->winding = winding; // Use a flipped winding for the reverse portal so the winding // direction and plane normal match. winding = NewFlippedWinding (&main_thread, winding); // ** winding->original = true; // create backwards portal cluster = &clusters[clusternums[i].clusters[1]]; portal = &cluster->portals[cluster->numportals++]; portal->plane = plane; portal->sphere = sphere; portal->cluster = clusternums[i].clusters[0]; portal->winding = winding; } free (clusternums); free (windings); leafcluster = calloc (numrealleafs, sizeof (uint32_t)); if (read_leafs) { for (unsigned i = 0; i < numrealleafs; i++) { line = Qgetline (f); if (sscanf (line, "%i\n", &leafcluster[i]) != 1) Sys_Error ("LoadPortals: parse error in leaf->cluster " "mappings"); } } else { for (unsigned i = 0; i < numrealleafs; i++) leafcluster[i] = i; } Qclose (f); } static void generate_pvs (void) { QFile *f; dstring_t *portalfile = dstring_new (); dstring_t *backupfile = dstring_new (); visdata = dstring_new (); dstring_copystr (portalfile, options.bspfile->str); QFS_SetExtension (portalfile, ".prt"); LoadPortals (portalfile->str); uncompressed = calloc (bitbytes_l, portalclusters); CalcClusterSpheres (); CalcVis (); if (options.verbosity >= 1) printf ("chains: %ld%s\n", global_stats.chains, options.threads > 1 ? " (not reliable)" :""); BSP_AddVisibility (bsp, (byte *) visdata->str, visdata->size); if (options.verbosity >= 0) printf ("visdatasize:%ld compressed from %ld\n", (long) bsp->visdatasize, (long) originalvismapsize); CalcAmbientSounds (); dstring_copystr (backupfile, options.bspfile->str); QFS_SetExtension (backupfile, ".bsp~"); Qrename (options.bspfile->str, backupfile->str); f = Qopen (options.bspfile->str, "wb"); if (!f) Sys_Error ("couldn't open %s for writing.", options.bspfile->str); WriteBSPFile (bsp, f); Qclose (f); dstring_delete (backupfile); dstring_delete (portalfile); dstring_delete (visdata); dstring_delete (options.bspfile); free (leafcluster); free (uncompressed); free (portals); free (clusters); } int main (int argc, char **argv) { double start, stop; QFile *f; size_t main_memsize = 128 * 1024 * 1024; main_thread.memsuper = new_memsuper (); main_thread.hunk = Hunk_Init (Sys_Alloc (main_memsize), main_memsize); start = Sys_DoubleTime (); this_program = argv[0]; DecodeArgs (argc, argv); InitThreads (); if (!options.bspfile) { usage (1); Sys_Error ("%s: no bsp file specified.", this_program); } QFS_SetExtension (options.bspfile, ".bsp"); f = Qopen (options.bspfile->str, "rb"); if (!f) Sys_Error ("couldn't open %s for reading.", options.bspfile->str); bsp = LoadBSPFile (f, Qfilesize (f)); Qclose (f); if (!options.no_auto_pvs) { generate_pvs (); } if (options.fat_pvs) { CalcFatPVS (); } BSP_Free (bsp); EndThreads (); stop = Sys_DoubleTime (); if (options.verbosity >= -1) printf ("%5.1f seconds elapsed\n", stop - start); return 0; }