[qfvis] Use simd vector code

While whether it's any faster is debatable (it's slightly slower, but
many more portals are being tested due to different rounding in the base
vis stage), it's certainly easier to read.
This commit is contained in:
Bill Currie 2021-03-28 19:55:47 +09:00
parent b6ab832ed4
commit ff4cd84891
4 changed files with 236 additions and 145 deletions

View file

@ -74,6 +74,7 @@ extern pthread_rwlock_t *portal_locks;
#include "QF/cmem.h"
#include "QF/set.h"
#include "QF/simd/vec4f.h"
#define MAX_PORTALS 32768
#define PORTALFILE "PRT1"
@ -87,7 +88,9 @@ typedef struct winding_s {
struct winding_s *next;
qboolean original; // don't free, it's part of the portal
unsigned numpoints;
vec3_t points[MAX_PORTALS_ON_CLUSTER]; // variable sized
int id;
int thread;
vec4f_t points[MAX_PORTALS_ON_CLUSTER]; // variable sized
} winding_t;
typedef enum {
@ -98,9 +101,9 @@ typedef enum {
} vstatus_t;
typedef struct {
plane_t plane; // normal pointing into neighbor
vec4f_t plane; // normal pointing into neighbor
vspheref_t sphere; // bounding sphere
int cluster; // neighbor
sphere_t sphere; // bounding sphere
winding_t *winding;
vstatus_t status;
set_t *visbits;
@ -110,8 +113,8 @@ typedef struct {
} portal_t;
typedef struct seperating_plane_s {
vec4f_t plane; // from portal is on positive side
struct seperating_plane_s *next;
plane_t plane; // from portal is on positive side
} sep_t;
typedef struct passage_s {
@ -122,9 +125,9 @@ typedef struct passage_s {
typedef struct cluster_s {
int numportals;
int visofs;
passage_t *passages;
portal_t *portals[MAX_PORTALS_ON_CLUSTER];
int visofs;
} cluster_t;
typedef struct pstack_s {
@ -132,8 +135,8 @@ typedef struct pstack_s {
cluster_t *cluster; ///< the cluster being sub-vised
winding_t *source_winding; ///< clipped source portal winding
portal_t *pass_portal; ///< the portal exiting from the cluster
vec4f_t pass_plane; ///< plane of the pass portal
winding_t *pass_winding; ///< clipped pass portal winding
plane_t pass_plane; ///< plane of the pass portal
set_t *mightsee;
sep_t *separators[2];
} pstack_t;
@ -173,6 +176,7 @@ typedef struct threaddata_s {
memsuper_t *memsuper; ///< per-thread memory pool
set_pool_t set_pool;
int id;
int winding_id;
} threaddata_t;
typedef struct {
@ -198,7 +202,8 @@ extern byte *uncompressed;
void FreeWinding (threaddata_t *thread, winding_t *w);
winding_t *NewWinding (threaddata_t *thread, int points);
winding_t *ClipWinding (threaddata_t *thread, winding_t *in, const plane_t *split, qboolean keepon);
winding_t *ClipWinding (threaddata_t *thread, winding_t *in, vec4f_t split,
qboolean keepon);
winding_t *CopyWinding (threaddata_t *thread, const winding_t *w);
void ClusterFlow (int clusternum);

View file

@ -84,24 +84,25 @@ SimpleFlood (basethread_t *thread, portal_t *srcportal, int clusternum)
}
static inline int
test_sphere (sphere_t *sphere, plane_t *plane)
test_sphere (const vspheref_t *sphere, vec4f_t plane)
{
float d;
int front, back;
const vec4f_t zero = {};
float r = sphere->radius;
vec4f_t eps = { r, r, r, r };
vec4f_t d = _mm_addsub_ps (zero, dotf (sphere->center, plane));
vec4i_t c = (d - eps) >= 0;
d = DotProduct (sphere->center, plane->normal) - plane->dist;
front = (d >= sphere->radius);
back = (d <= -sphere->radius);
return front - back;
c = (vec4i_t) _mm_hsub_epi32 ((__m128i) c, (__m128i) c);
return c[0];
}
void
PortalBase (basethread_t *thread, portal_t *portal)
{
unsigned i, j, k;
float d;
portal_t *tp;
winding_t *winding;
unsigned i, j, k;
vec4f_t d;
portal_t *tp;
winding_t *winding;
int tp_side, portal_side;
i = portal - portals;
@ -118,14 +119,14 @@ PortalBase (basethread_t *thread, portal_t *portal)
// visibility.
// First check using the bounding spheres of the two portals.
tp_side = test_sphere (&tp->sphere, &portal->plane);
tp_side = test_sphere (&tp->sphere, portal->plane);
if (tp_side < 0) {
// The test portal definitely is entirely behind the portal's
// plane.
thread->spherecull++;
continue; // entirely behind
}
portal_side = test_sphere (&portal->sphere, &tp->plane);
portal_side = test_sphere (&portal->sphere, tp->plane);
if (portal_side > 0) {
// The portal definitely is entirely in front of the test
// portal's plane.
@ -138,9 +139,8 @@ PortalBase (basethread_t *thread, portal_t *portal)
// do a more refined check.
winding = tp->winding;
for (k = 0; k < winding->numpoints; k++) {
d = DotProduct (winding->points[k],
portal->plane.normal) - portal->plane.dist;
if (d > ON_EPSILON)
d = dotf (winding->points[k], portal->plane);
if (d[0] > ON_EPSILON)
break;
}
if (k == winding->numpoints) {
@ -154,9 +154,8 @@ PortalBase (basethread_t *thread, portal_t *portal)
// do a more refined check.
winding = portal->winding;
for (k = 0; k < winding->numpoints; k++) {
d = DotProduct (winding->points[k],
tp->plane.normal) - tp->plane.dist;
if (d < -ON_EPSILON)
d = dotf (winding->points[k], tp->plane);
if (d[0] < -ON_EPSILON)
break;
}
if (k == winding->numpoints) {

View file

@ -133,48 +133,47 @@ test_zero (float d)
}
static int
calc_plane (const vec3_t v1, const vec3_t v2, int flip, const vec3_t p,
plane_t *plane)
calc_plane (vec4f_t v1, vec4f_t v2, int flip, vec4f_t p, vec4f_t *plane)
{
vec_t length;
vec4f_t length;
if (flip < 0) {
//CrossProduct (v2, v1, plane.normal);
plane->normal[0] = v2[1] * v1[2] - v2[2] * v1[1];
plane->normal[1] = v2[2] * v1[0] - v2[0] * v1[2];
plane->normal[2] = v2[0] * v1[1] - v2[1] * v1[0];
(*plane)[0] = v2[1] * v1[2] - v2[2] * v1[1];
(*plane)[1] = v2[2] * v1[0] - v2[0] * v1[2];
(*plane)[2] = v2[0] * v1[1] - v2[1] * v1[0];
} else {
//CrossProduct (v1, v2, plane.normal);
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];
(*plane)[0] = v1[1] * v2[2] - v1[2] * v2[1];
(*plane)[1] = v1[2] * v2[0] - v1[0] * v2[2];
(*plane)[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
(*plane)[3] = 0;
length = DotProduct (plane->normal, plane->normal);
length = dotf (*plane, *plane);
// if points don't make a valid plane, skip it
if (length < ON_EPSILON)
if (length[0] < ON_EPSILON)
return 0;
length = 1 / sqrt (length);
VectorScale (plane->normal, length, plane->normal);
plane->dist = DotProduct (p, plane->normal);
*plane /= vsqrtf (length);
(*plane)[3] = -dotf (p, *plane)[0];
return 1;
}
static inline int
test_plane (const plane_t *plane, const winding_t *pass, int index)
test_plane (vec4f_t plane, const winding_t *pass, int index)
{
int s1, s2;
int k;
vec_t d;
vec4f_t d;
k = (index + 1) % pass->numpoints;
d = DotProduct (pass->points[k], plane->normal) - plane->dist;
s1 = test_zero (d);
d = dotf (pass->points[k], plane);
s1 = test_zero (d[0]);
k = (index + pass->numpoints - 1) % pass->numpoints;
d = DotProduct (pass->points[k], plane->normal) - plane->dist;
s2 = test_zero (d);
d = dotf (pass->points[k], plane);
s2 = test_zero (d[0]);
if (s1 == 0 && s2 == 0)
return 0;
if (s1 < 0 || s2 < 0)
@ -183,32 +182,30 @@ test_plane (const plane_t *plane, const winding_t *pass, int index)
}
static inline sep_t *
create_separator (threaddata_t *thread, const plane_t *src_pl,
const vec3_t p1, const vec3_t v1,
create_separator (threaddata_t *thread, vec4f_t src_pl, vec4f_t p1, vec4f_t v1,
const winding_t *pass, int index, int flip)
{
int fliptest;
vec_t d;
vec3_t v2;
plane_t plane;
vec4f_t d;
vec4f_t v2;
vec4f_t plane;
sep_t *sep;
d = DotProduct (pass->points[index], src_pl->normal) - src_pl->dist;
if ((fliptest = test_zero (d)) == 0)
d = dotf (pass->points[index], src_pl);
if ((fliptest = test_zero (d[0])) == 0)
return 0; // The point lies in the source plane
VectorSubtract (pass->points[index], p1, v2);
v2 = pass->points[index] - p1;
if (!calc_plane (v1, v2, fliptest, pass->points[index], &plane))
return 0; // point does not form a valid plane
if (!test_plane (&plane, pass, index))
if (!test_plane (plane, pass, index))
return 0; // not the right point
sep = new_separator (thread);
// flip the normal if we want the back side
if (flip) {
VectorNegate (plane.normal, sep->plane.normal);
sep->plane.dist = -plane.dist;
sep->plane = -plane;
} else {
sep->plane = plane;
}
@ -233,20 +230,20 @@ create_separator (threaddata_t *thread, const plane_t *src_pl,
*/
static sep_t *
FindSeparators (threaddata_t *thread,
const winding_t *source, const plane_t src_pl,
const winding_t *source, vec4f_t src_pl,
const winding_t *pass, int flip)
{
unsigned i, j, l;
vec3_t v1;
vec4f_t v1;
sep_t *separators = 0, *sep;
for (i = 0; i < source->numpoints; i++) {
l = (i + 1) % source->numpoints;
VectorSubtract (source->points[l], source->points[i], v1);
v1 = source->points[l] - source->points[i];
for (j = 0; j < pass->numpoints; j++) {
sep = create_separator (thread, &src_pl, source->points[i], v1,
sep = create_separator (thread, src_pl, source->points[i], v1,
pass, j, flip);
if (sep) {
sep->next = separators;
@ -259,12 +256,13 @@ FindSeparators (threaddata_t *thread,
}
static winding_t *
ClipToSeparators (threaddata_t *thread, const sep_t *separators, winding_t *target)
ClipToSeparators (threaddata_t *thread, const sep_t *separators,
winding_t *target)
{
const sep_t *sep;
for (sep = separators; target && sep; sep = sep->next) {
target = ClipWinding (thread, target, &sep->plane, false);
target = ClipWinding (thread, target, sep->plane, false);
}
return target;
}
@ -304,7 +302,6 @@ mightsee_more (set_t *might, const set_t *prev_might, const set_t *test,
RecursiveClusterFlow
Flood fill through the clusters
If src_portal is NULL, this is the originating cluster
*/
static void
RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
@ -315,8 +312,8 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
cluster_t *cluster;
pstack_t *stack;
portal_t *target_portal;
plane_t backplane;
const plane_t *source_plane, *pass_plane;
vec4f_t backplane;
vec4f_t source_plane, pass_plane;
const winding_t *pass_winding;
winding_t *source_winding, *target_winding;
@ -344,9 +341,9 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
might = stack->mightsee;
vis = thread->clustervis;
source_plane = &thread->pstack_head.pass_plane;
source_plane = thread->pstack_head.pass_plane;
pass_winding = prevstack->pass_winding;
pass_plane = &prevstack->pass_plane;
pass_plane = prevstack->pass_plane;
// check all portals for flowing into other clusters
for (i = 0; i < cluster->numportals; i++) {
@ -363,11 +360,13 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
}
// get plane of target_portal, point normal into the neighbor cluster
VectorNegate (target_portal->plane.normal, backplane.normal);
backplane.dist = -target_portal->plane.dist;
backplane = -target_portal->plane;
if (_VectorCompare (pass_plane->normal, backplane.normal))
vec4f_t diff = vabsf (pass_plane - backplane);
vec4i_t cmp = diff > (vec4f_t) {0.001, 0.001, 0.001, 0.001};
if (!(cmp[0] || cmp[1] || cmp[2])) { // dist isn't interesting
continue; // can't go out a coplanar face
}
thread->stats.portalcheck++;
@ -398,7 +397,7 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
// if it gets clipped away, earlier stack levels will get corrupted
source_winding = CopyWinding (thread, prevstack->source_winding);
source_winding = ClipWinding (thread, source_winding, &backplane, false);
source_winding = ClipWinding (thread, source_winding, backplane, false);
if (!source_winding) {
FreeWinding (thread, target_winding);
continue;
@ -412,7 +411,7 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
if (!stack->separators[0])
stack->separators[0] = FindSeparators (thread,
source_winding,
*source_plane,
source_plane,
pass_winding, 0);
target_winding = ClipToSeparators (thread,
stack->separators[0],
@ -431,7 +430,7 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
if (!stack->separators[1])
stack->separators[1] = FindSeparators (thread,
pass_winding,
*pass_plane,
pass_plane,
source_winding, 1);
target_winding = ClipToSeparators (thread,
stack->separators[1],
@ -466,7 +465,7 @@ RecursiveClusterFlow (int clusternum, threaddata_t *thread, pstack_t *prevstack)
if (options.level > 3) {
winding_t *old = source_winding;
sep_t *sep;
sep = FindSeparators (thread, pass_winding, *pass_plane,
sep = FindSeparators (thread, pass_winding, pass_plane,
target_winding, 1);
source_winding = ClipToSeparators (thread, sep, source_winding);
free_separators (thread, sep);

View file

@ -134,17 +134,19 @@ EndThreads (void)
#endif
}
static void
PlaneFromWinding (winding_t *winding, plane_t *plane)
static vec4f_t
PlaneFromWinding (winding_t *winding)
{
vec3_t v1, v2;
vec4f_t plane;
vec4f_t v1, v2;
// calc plane using CW winding
VectorSubtract (winding->points[2], winding->points[1], v1);
VectorSubtract (winding->points[0], winding->points[1], v2);
CrossProduct (v2, v1, plane->normal);
_VectorNormalize (plane->normal);
plane->dist = DotProduct (winding->points[0], plane->normal);
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 *
@ -160,6 +162,8 @@ NewWinding (threaddata_t *thread, int points)
winding = CMEMALLOC (13, winding_t, thread->winding, thread->memsuper);
memset (winding, 0, size);
thread->stats.winding_alloc++;
winding->id = thread->winding_id++;
winding->thread = thread->id;
return winding;
}
@ -188,6 +192,8 @@ CopyWinding (threaddata_t *thread, const winding_t *w)
memcpy (copy, w, size);
copy->original = false;
thread->stats.winding_alloc++;
copy->id = thread->winding_id++;
copy->thread = thread->id;
return copy;
}
@ -198,12 +204,111 @@ NewFlippedWinding (threaddata_t *thread, const winding_t *w)
unsigned i;
flipped = NewWinding (thread, w->numpoints);
for (i = 0; i < w->numpoints; i++)
VectorCopy (w->points[i], flipped->points[w->numpoints - 1 - i]);
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)
{
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;
}
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 vec4f_t onenan = { 1, 1, 1, ~0u >> 1 };
static const vec4i_t nan = { ~0u >> 1, ~0u >> 1, ~0u >> 1, ~0u >> 1};
vec4i_t x = _mm_and_ps (split, (__m128) nan) == onenan;
// plane vector has -dist in w
vec4f_t y = _mm_and_ps (split, (__m128) x) * -split[3];
mid = _mm_blendv_ps (mid, y, (__m128) x);
if (isnan (mid[0])) *(int *) 0 = 0;
return mid;
}
static inline int __attribute__((const))
is_not_on (int x)
{
return x & 1;
}
static inline int __attribute__((const))
is_back (int x)
{
return x & 2;
}
static inline int __attribute__((const))
is_not_back (unsigned x)
{
return ~x & 2;
}
static inline int __attribute__((const))
is_front (unsigned x)
{
return is_not_on (x) & (is_not_back (x) >> 1);
}
static inline int __attribute__((const))
is_back_front (unsigned x, unsigned y)
{
return (is_back (x) >> 1) & is_front (y);
}
static inline int __attribute__((const))
is_front_back (unsigned x, unsigned y)
{
return is_front (x) & (is_back (y) >> 1);
}
static inline int __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
@ -216,86 +321,68 @@ NewFlippedWinding (threaddata_t *thread, const winding_t *w)
it will be clipped away.
*/
winding_t *
ClipWinding (threaddata_t *thread, winding_t *in, const plane_t *split, qboolean keepon)
ClipWinding (threaddata_t *thread, winding_t *in, vec4f_t split,
qboolean keepon)
{
unsigned maxpts, i, j;
int counts[3], sides[MAX_POINTS_ON_WINDING];
vec_t dot;
vec_t dists[MAX_POINTS_ON_WINDING];
vec_t *p1, *p2;
vec3_t mid;
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[0] = counts[1] = counts[2] = 0;
counts[SIDE_FRONT] = counts[SIDE_ON] = counts[SIDE_BACK] = 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]]++;
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[0] && !counts[1])
if (keepon && counts[SIDE_ON] == in->numpoints) {
return in;
if (!counts[0]) {
}
if (!counts[SIDE_FRONT]) {
FreeWinding (thread, in);
return NULL;
}
if (!counts[1])
if (!counts[SIDE_BACK]) {
return in;
}
maxpts = in->numpoints + 4; // can't use counts[0] + 2 because
// of fp grouping errors
maxpts += in->numpoints - counts[SIDE_BACK];
neww = NewWinding (thread, maxpts);
for (i = 0; i < in->numpoints; i++) {
p1 = in->points[i];
if (sides[i] == SIDE_ON) {
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
neww->points[neww->numpoints++] = in->points[i];
continue;
}
if (sides[i] == SIDE_FRONT) {
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
neww->points[neww->numpoints++] = in->points[i];
}
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) {
continue;
// generate a split point
p2 = in->points[(i + 1) % in->numpoints];
dot = dists[i] / (dists[i] - dists[i + 1]);
for (j = 0; j < 3; j++) {
// avoid round off error when possible
if (split->normal[j] == 1)
mid[j] = split->dist;
else if (split->normal[j] == -1)
mid[j] = -split->dist;
else
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
VectorCopy (mid, neww->points[neww->numpoints]);
neww->numpoints++;
vec4f_t mid = split_edge (in->points, dists, i,
(i + 1) % in->numpoints, split);
neww->points[neww->numpoints++] = mid;
}
if (neww->numpoints > maxpts)
Sys_Error ("ClipWinding: points exceeded estimate");
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);
}
// free the original winding
FreeWinding (thread, in);
@ -1033,10 +1120,10 @@ LoadPortals (char *name)
int read_leafs = 0;
int clusternums[2];
cluster_t *cluster;
plane_t plane;
vec4f_t plane;
portal_t *portal;
winding_t *winding;
sphere_t sphere;
vspheref_t sphere;
QFile *f;
if (!strcmp (name, "-"))
@ -1158,6 +1245,7 @@ LoadPortals (char *name)
Sys_Error ("LoadPortals: reading portal %u", i);
line = err;
}
winding->points[j][3] = 1;
while (isspace ((byte) *line))
line++;
@ -1165,10 +1253,11 @@ LoadPortals (char *name)
Sys_Error ("LoadPortals: reading portal %u", i);
}
sphere = SmallestEnclosingBall_vf(winding->points, winding->numpoints);
//printf (VEC4F_FMT" %.9g\n", VEC4_EXP (sphere.center), sphere.radius);
// calc plane
PlaneFromWinding (winding, &plane);
sphere = SmallestEnclosingBall((const vec_t(*)[3])winding->points,
winding->numpoints);
plane = PlaneFromWinding (winding);
// create forward portal
cluster = &clusters[clusternums[0]];
@ -1178,8 +1267,7 @@ LoadPortals (char *name)
cluster->numportals++;
portal->winding = winding;
VectorNegate (plane.normal, portal->plane.normal);
portal->plane.dist = -plane.dist; // plane is for CW, portal is CCW
portal->plane = -plane; // plane is for CW, portal is CCW
portal->cluster = clusternums[1];
portal->sphere = sphere;
portal++;