gtkradiant/radiant/winding.cpp

823 lines
17 KiB
C++
Raw Normal View History

/*
Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "stdafx.h"
#include <assert.h>
#include "winding.h"
#define BOGUS_RANGE (g_MaxWorldCoord+1)
/*
=============
Plane_Equal
=============
*/
#define NORMAL_EPSILON 0.0001
#define DIST_EPSILON 0.02
int Plane_Equal(plane_t *a, plane_t *b, int flip)
{
vec3_t normal;
float dist;
if (flip) {
normal[0] = - b->normal[0];
normal[1] = - b->normal[1];
normal[2] = - b->normal[2];
dist = - b->dist;
}
else {
normal[0] = b->normal[0];
normal[1] = b->normal[1];
normal[2] = b->normal[2];
dist = b->dist;
}
if (
fabs(a->normal[0] - normal[0]) < NORMAL_EPSILON
&& fabs(a->normal[1] - normal[1]) < NORMAL_EPSILON
&& fabs(a->normal[2] - normal[2]) < NORMAL_EPSILON
&& fabs(a->dist - dist) < DIST_EPSILON )
return true;
return false;
}
/*
============
Plane_FromPoints
============
*/
int Plane_FromPoints(vec3_t p1, vec3_t p2, vec3_t p3, plane_t *plane)
{
vec3_t v1, v2;
VectorSubtract(p2, p1, v1);
VectorSubtract(p3, p1, v2);
//CrossProduct(v2, v1, plane->normal);
CrossProduct(v1, v2, plane->normal);
if (VectorNormalize(plane->normal, plane->normal) < 0.1) return false;
plane->dist = DotProduct(p1, plane->normal);
return true;
}
/*
=================
Point_Equal
=================
*/
int Point_Equal(vec3_t p1, vec3_t p2, float epsilon)
{
int i;
for (i = 0; i < 3; i++)
{
if (fabs(p1[i] - p2[i]) > epsilon) return false;
}
return true;
}
/*
=================
Winding_BaseForPlane
=================
*/
//#define DBG_WNDG
winding_t *Winding_BaseForPlane (plane_t *p)
{
int i, x;
vec_t max, v;
vec3_t org, vright, vup;
winding_t *w;
// find the major axis
#ifdef DBG_WNDG
Sys_Printf("Winding_BaseForPlane %p\n",p);
#endif
max = -BOGUS_RANGE;
x = -1;
for (i=0 ; i<3; i++)
{
v = fabs(p->normal[i]);
if (v > max)
{
x = i;
max = v;
}
}
if (x==-1)
Error ("Winding_BaseForPlane: 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, p->normal);
VectorMA (vup, -v, p->normal, vup);
VectorNormalize (vup, vup);
VectorScale (p->normal, p->dist, org);
CrossProduct (vup, p->normal, vright);
VectorScale (vup, BOGUS_RANGE, vup);
VectorScale (vright, BOGUS_RANGE, vright);
// project a really big axis aligned box onto the plane
w = Winding_Alloc (4);
VectorSubtract (org, vright, w->points[0]);
VectorAdd (w->points[0], vup, w->points[0]);
VectorAdd (org, vright, w->points[1]);
VectorAdd (w->points[1], vup, w->points[1]);
VectorAdd (org, vright, w->points[2]);
VectorSubtract (w->points[2], vup, w->points[2]);
VectorSubtract (org, vright, w->points[3]);
VectorSubtract (w->points[3], vup, w->points[3]);
w->numpoints = 4;
return w;
}
// macro to compute winding size
#define WINDING_SIZE(pt) (sizeof(int)*2+sizeof(float)*5*(pt))
/*
==================
Winding_Alloc
==================
*/
winding_t *Winding_Alloc (int points)
{
winding_t *w;
int size;
if (points > MAX_POINTS_ON_WINDING)
Error ("Winding_Alloc: %i points", points);
// size = (int)((winding_t *)0)->points[points];
size = WINDING_SIZE(points);
w = (winding_t*) malloc (size);
memset (w, 0, size);
w->maxpoints = points;
return w;
}
void Winding_Free (winding_t *w)
{
free(w);
}
/*
==================
Winding_Clone
==================
*/
winding_t *Winding_Clone(winding_t *w)
{
int size;
winding_t *c;
// size = (int)((winding_t *)0)->points[w->numpoints];
size = WINDING_SIZE(w->numpoints);
c = (winding_t*)qmalloc (size);
memcpy (c, w, size);
return c;
}
/*
==================
ReverseWinding
==================
*/
winding_t *Winding_Reverse(winding_t *w)
{
int i;
winding_t *c;
c = Winding_Alloc(w->numpoints);
for (i = 0; i < w->numpoints; i++)
{
VectorCopy (w->points[w->numpoints-1-i], c->points[i]);
}
c->numpoints = w->numpoints;
return c;
}
/*
==============
Winding_RemovePoint
==============
*/
void Winding_RemovePoint(winding_t *w, int point)
{
if (point < 0 || point >= w->numpoints)
Error("Winding_RemovePoint: point out of range");
if (point < w->numpoints-1)
{
memmove(&w->points[point], &w->points[point+1], (size_t)((winding_t *)0)->points[w->numpoints - point - 1]);
}
w->numpoints--;
}
/*
=============
Winding_InsertPoint
=============
*/
winding_t *Winding_InsertPoint(winding_t *w, vec3_t point, int spot)
{
int i, j;
winding_t *neww;
if (spot > w->numpoints)
{
Error("Winding_InsertPoint: spot > w->numpoints");
} //end if
if (spot < 0)
{
Error("Winding_InsertPoint: spot < 0");
} //end if
neww = Winding_Alloc(w->numpoints + 1);
neww->numpoints = w->numpoints + 1;
for (i = 0, j = 0; i < neww->numpoints; i++)
{
if (i == spot)
{
VectorCopy(point, neww->points[i]);
}
else
{
VectorCopy(w->points[j], neww->points[i]);
j++;
}
}
return neww;
}
/*
==============
Winding_IsTiny
==============
*/
#define EDGE_LENGTH 0.2
int Winding_IsTiny (winding_t *w)
{
int i, j;
vec_t len;
vec3_t delta;
int edges;
edges = 0;
for (i=0 ; i<w->numpoints ; i++)
{
j = i == w->numpoints - 1 ? 0 : i+1;
VectorSubtract (w->points[j], w->points[i], delta);
len = VectorLength (delta);
if (len > EDGE_LENGTH)
{
if (++edges == 3)
return false;
}
}
return true;
}
/*
==============
Winding_IsHuge
==============
*/
int Winding_IsHuge(winding_t *w)
{
int i, j;
for (i=0 ; i<w->numpoints ; i++)
{
for (j=0 ; j<3 ; j++)
if (w->points[i][j] < -BOGUS_RANGE+1 || w->points[i][j] > BOGUS_RANGE-1)
return true;
}
return false;
}
/*
=============
Winding_PlanesConcave
=============
*/
#define WCONVEX_EPSILON 0.2
int Winding_PlanesConcave(winding_t *w1, winding_t *w2,
vec3_t normal1, vec3_t normal2,
float dist1, float dist2)
{
int i;
if (!w1 || !w2) return false;
// check if one of the points of winding 1 is at the back of the plane of winding 2
for (i = 0; i < w1->numpoints; i++)
{
if (DotProduct(normal2, w1->points[i]) - dist2 > WCONVEX_EPSILON) return true;
}
// check if one of the points of winding 2 is at the back of the plane of winding 1
for (i = 0; i < w2->numpoints; i++)
{
if (DotProduct(normal1, w2->points[i]) - dist1 > WCONVEX_EPSILON) return true;
}
return false;
}
/*
==================
Winding_Clip
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 *Winding_Clip (winding_t *in, plane_t *split, qboolean keepon)
{
vec_t dists[MAX_POINTS_ON_WINDING];
int sides[MAX_POINTS_ON_WINDING];
int counts[3];
vec_t dot;
int i, j;
vec_t *p1, *p2;
vec3_t mid;
winding_t *neww;
int maxpts;
counts[0] = counts[1] = counts[2] = 0;
// determine sides for each point
for (i=0 ; i<in->numpoints ; i++)
{
dot = DotProduct (in->points[i], split->normal);
dot -= split->dist;
dists[i] = dot;
if (dot > ON_EPSILON)
sides[i] = SIDE_FRONT;
else if (dot < -ON_EPSILON)
sides[i] = SIDE_BACK;
else
{
sides[i] = SIDE_ON;
}
counts[sides[i]]++;
}
sides[i] = sides[0];
dists[i] = dists[0];
if (keepon && !counts[0] && !counts[1])
return in;
if (!counts[0])
{
Winding_Free (in);
return NULL;
}
if (!counts[1])
return in;
maxpts = in->numpoints+4; // can't use counts[0]+2 because
// of fp grouping errors
neww = Winding_Alloc (maxpts);
for (i=0 ; i<in->numpoints ; i++)
{
p1 = in->points[i];
if (sides[i] == SIDE_ON)
{
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
continue;
}
if (sides[i] == SIDE_FRONT)
{
VectorCopy (p1, neww->points[neww->numpoints]);
neww->numpoints++;
}
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
continue;
// generate a split point
p2 = in->points[(i+1)%in->numpoints];
dot = dists[i] / (dists[i]-dists[i+1]);
for (j=0 ; j<3 ; j++)
{ // avoid round off error when possible
if (split->normal[j] == 1)
mid[j] = split->dist;
else if (split->normal[j] == -1)
mid[j] = -split->dist;
else
mid[j] = p1[j] + dot*(p2[j]-p1[j]);
}
VectorCopy (mid, neww->points[neww->numpoints]);
neww->numpoints++;
}
if (neww->numpoints > maxpts)
Error ("Winding_Clip: points exceeded estimate");
// free the original winding
Winding_Free (in);
return neww;
}
/*
=============
Winding_SplitEpsilon
split the input winding with the plane
the input winding stays untouched
=============
*/
void Winding_SplitEpsilon (winding_t *in, vec3_t normal, double dist,
vec_t epsilon, winding_t **front, winding_t **back)
{
vec_t dists[MAX_POINTS_ON_WINDING+4];
int sides[MAX_POINTS_ON_WINDING+4];
int counts[3];
vec_t dot;
int i, j;
vec_t *p1, *p2;
vec3_t mid;
winding_t *f, *b;
int maxpts;
counts[0] = counts[1] = counts[2] = 0;
// determine sides for each point
for (i = 0; i < in->numpoints; i++)
{
dot = DotProduct (in->points[i], 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 = Winding_Clone(in);
return;
}
if (!counts[1])
{
*front = Winding_Clone(in);
return;
}
maxpts = in->numpoints+4; // cant use counts[0]+2 because
// of fp grouping errors
*front = f = Winding_Alloc (maxpts);
*back = b = Winding_Alloc (maxpts);
for (i = 0; i < in->numpoints; i++)
{
p1 = in->points[i];
if (sides[i] == SIDE_ON)
{
VectorCopy (p1, f->points[f->numpoints]);
f->numpoints++;
VectorCopy (p1, b->points[b->numpoints]);
b->numpoints++;
continue;
}
if (sides[i] == SIDE_FRONT)
{
VectorCopy (p1, f->points[f->numpoints]);
f->numpoints++;
}
if (sides[i] == SIDE_BACK)
{
VectorCopy (p1, b->points[b->numpoints]);
b->numpoints++;
}
if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
continue;
// generate a split point
p2 = in->points[(i+1)%in->numpoints];
dot = dists[i] / (dists[i]-dists[i+1]);
for (j = 0; j < 3; j++)
{
// avoid round off error when possible
if (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->points[f->numpoints]);
f->numpoints++;
VectorCopy (mid, b->points[b->numpoints]);
b->numpoints++;
}
if (f->numpoints > maxpts || b->numpoints > maxpts)
Error ("Winding_Clip: points exceeded estimate");
if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING)
Error ("Winding_Clip: MAX_POINTS_ON_WINDING");
}
/*
=============
Winding_TryMerge
If two windings 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 windings couldn't be merged, or the new winding.
The originals will NOT be freed.
if keep is true no points are ever removed
=============
*/
#define CONTINUOUS_EPSILON 0.005
winding_t *Winding_TryMerge(winding_t *f1, winding_t *f2, vec3_t planenormal, int keep)
{
vec_t *p1, *p2, *p3, *p4, *back;
winding_t *newf;
int 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->points[i];
p2 = f1->points[(i+1) % f1->numpoints];
for (j = 0; j < f2->numpoints; j++)
{
p3 = f2->points[j];
p4 = f2->points[(j+1) % f2->numpoints];
for (k = 0; k < 3; k++)
{
if (fabs(p1[k] - p4[k]) > 0.1)//EQUAL_EPSILON) //ME
break;
if (fabs(p2[k] - p3[k]) > 0.1)//EQUAL_EPSILON) //ME
break;
} //end for
if (k==3)
break;
} //end for
if (j < f2->numpoints)
break;
} //end for
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->points[(i+f1->numpoints-1)%f1->numpoints];
VectorSubtract (p1, back, delta);
CrossProduct (planenormal, delta, normal);
VectorNormalize (normal, normal);
back = f2->points[(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->points[(i+2)%f1->numpoints];
VectorSubtract (back, p2, delta);
CrossProduct (planenormal, delta, normal);
VectorNormalize (normal, normal);
back = f2->points[(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 = Winding_Alloc (f1->numpoints + f2->numpoints);
// copy first polygon
for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints)
{
if (!keep && k==(i+1)%f1->numpoints && !keep2)
continue;
VectorCopy (f1->points[k], newf->points[newf->numpoints]);
newf->numpoints++;
}
// copy second polygon
for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints)
{
if (!keep && l==(j+1)%f2->numpoints && !keep1)
continue;
VectorCopy (f2->points[l], newf->points[newf->numpoints]);
newf->numpoints++;
}
return newf;
}
/*
============
Winding_Plane
============
*/
void Winding_Plane (winding_t *w, vec3_t normal, double *dist)
{
vec3_t v1, v2;
int i;
//find two vectors each longer than 0.5 units
for (i = 0; i < w->numpoints; i++)
{
VectorSubtract(w->points[(i+1) % w->numpoints], w->points[i], v1);
VectorSubtract(w->points[(i+2) % w->numpoints], w->points[i], v2);
if (VectorLength(v1) > 0.5 && VectorLength(v2) > 0.5) break;
}
CrossProduct(v2, v1, normal);
VectorNormalize(normal, normal);
*dist = DotProduct(w->points[0], normal);
}
/*
=============
Winding_Area
=============
*/
float Winding_Area (winding_t *w)
{
int i;
vec3_t d1, d2, cross;
float total;
total = 0;
for (i=2 ; i<w->numpoints ; i++)
{
VectorSubtract (w->points[i-1], w->points[0], d1);
VectorSubtract (w->points[i], w->points[0], d2);
CrossProduct (d1, d2, cross);
total += 0.5 * VectorLength ( cross );
}
return total;
}
/*
=============
Winding_Bounds
=============
*/
void Winding_Bounds (winding_t *w, vec3_t mins, vec3_t maxs)
{
vec_t v;
int i,j;
mins[0] = mins[1] = mins[2] = 99999;
maxs[0] = maxs[1] = maxs[2] = -99999;
for (i=0 ; i<w->numpoints ; i++)
{
for (j=0 ; j<3 ; j++)
{
v = w->points[i][j];
if (v < mins[j])
mins[j] = v;
if (v > maxs[j])
maxs[j] = v;
}
}
}
/*
=================
Winding_PointInside
=================
*/
int Winding_PointInside(winding_t *w, plane_t *plane, vec3_t point, float epsilon)
{
int i;
vec3_t dir, normal, pointvec;
for (i = 0; i < w->numpoints; i++)
{
VectorSubtract(w->points[(i+1) % w->numpoints], w->points[i], dir);
VectorSubtract(point, w->points[i], pointvec);
//
CrossProduct(dir, plane->normal, normal);
//
if (DotProduct(pointvec, normal) < -epsilon) return false;
}
return true;
}
/*
=================
Winding_VectorIntersect
=================
*/
int Winding_VectorIntersect(winding_t *w, plane_t *plane, vec3_t p1, vec3_t p2, float epsilon)
{
float front, back, frac;
vec3_t mid;
front = DotProduct(p1, plane->normal) - plane->dist;
back = DotProduct(p2, plane->normal) - plane->dist;
//if both points at the same side of the plane
if (front < -epsilon && back < -epsilon) return false;
if (front > epsilon && back > epsilon) return false;
//get point of intersection with winding plane
if (fabs(front-back) < 0.001)
{
VectorCopy(p2, mid);
}
else
{
frac = front/(front-back);
mid[0] = p1[0] + (p2[0] - p1[0]) * frac;
mid[1] = p1[1] + (p2[1] - p1[1]) * frac;
mid[2] = p1[2] + (p2[2] - p1[2]) * frac;
}
return Winding_PointInside(w, plane, mid, epsilon);
}