quakeforge/tools/Forge/Bundles/MapEdit/SetBrush.m
Bill Currie d23300d58b Pass .m files through indent.
The result isn't perfect, but it cleans up the whitespace and makes the
code more consistent with the rest of the project.
2010-11-28 15:31:31 +09:00

1965 lines
37 KiB
Objective-C

#include "QF/script.h"
#include "QF/sys.h"
#include "SetBrush.h"
#include "Entity.h"
#include "EntityClass.h"
#include "Map.h"
#include "Preferences.h"
#include "XYView.h"
#include "ZView.h"
#include "CameraView.h"
#include "Clipper.h"
#include "QuakeEd.h"
@implementation SetBrush
/*
==================
textureAxisFromPlane
==================
*/
#if 1
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
};
#else
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
};
#endif
float
TextureAxisFromPlane (plane_t *pln, float *xv, float *yv)
{
int bestaxis;
float dot, best;
int 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);
return lightaxis[bestaxis >> 1];
}
#define BOGUS_RANGE 18000
/*
=================
CheckFace
Note: this will not catch 0 area polygons
=================
*/
void
CheckFace (face_t * f)
{
int i, j;
float *p1, *p2;
float d, edgedist;
vec3_t dir, edgenormal;
winding_t *w;
w = f->w;
if (!w)
Sys_Error ("CheckFace: no winding");
if (w->numpoints < 3)
Sys_Error ("CheckFace: %i points", w->numpoints);
for (i = 0; i < w->numpoints; i++) {
p1 = w->points[i];
for (j = 0; j < 3; j++)
if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE)
Sys_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, f->plane.normal) - f->plane.dist;
if (d < -ON_EPSILON || d > ON_EPSILON)
Sys_Error ("CheckFace: point off plane");
// check the edge isn't degenerate
p2 = w->points[j];
VectorSubtract (p2, p1, dir);
if (VectorLength (dir) < ON_EPSILON)
Sys_Error ("CheckFace: degenerate edge");
CrossProduct (f->plane.normal, dir, edgenormal);
VectorNormalize (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->points[j], edgenormal);
if (d > edgedist)
Sys_Error ("CheckFace: non-convex");
}
}
}
/*
=============================================================================
TURN PLANES INTO GROUPS OF FACES
=============================================================================
*/
/*
==================
NewWinding
==================
*/
winding_t *
NewWinding (int points)
{
winding_t *w;
size_t size;
if (points > MAX_POINTS_ON_WINDING)
Sys_Error ("NewWinding: %i points", points);
size = (size_t) ((winding_t *) 0)->points[points];
w = malloc (size);
memset (w, 0, size);
return w;
}
/*
==================
CopyWinding
==================
*/
winding_t *
CopyWinding (winding_t * w)
{
size_t size;
winding_t *c;
size = (size_t) ((winding_t *) 0)->points[w->numpoints];
c = malloc (size);
memcpy (c, w, size);
return c;
}
/*
==================
ClipWinding
Clips the winding to the plane, returning the new winding on the positive side
Frees the input winding.
==================
*/
winding_t *
ClipWinding (winding_t * in, plane_t *split)
{
float dists[MAX_POINTS_ON_WINDING];
int sides[MAX_POINTS_ON_WINDING];
int counts[3];
float dot;
int i, j;
float *p1, *p2, *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 (!counts[0] && !counts[1])
return in;
if (!counts[0]) {
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 = NewWinding (maxpts);
for (i = 0; i < in->numpoints; i++) {
p1 = in->points[i];
mid = neww->points[neww->numpoints];
if (sides[i] == SIDE_FRONT || sides[i] == SIDE_ON) {
VectorCopy (p1, mid);
mid[3] = p1[3];
mid[4] = p1[4];
neww->numpoints++;
if (sides[i] == SIDE_ON)
continue;
mid = neww->points[neww->numpoints];
}
if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
continue;
// generate a split point
if (i == in->numpoints - 1)
p2 = in->points[0];
else
p2 = p1 + 5;
neww->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;
mid[j] = p1[j] + dot * (p2[j] - p1[j]);
}
mid[3] = p1[3] + dot * (p2[3] - p1[3]);
mid[4] = p1[4] + dot * (p2[4] - p1[4]);
}
if (neww->numpoints > maxpts)
Sys_Error ("ClipWinding: points exceeded estimate");
// free the original winding
free (in);
return neww;
}
/*
=================
BasePolyForPlane
There has GOT to be a better way of doing this...
=================
*/
winding_t *
BasePolyForPlane (face_t * f)
{
int i, x;
float max, v;
vec3_t org, vright, vup;
vec3_t xaxis, yaxis;
winding_t *w;
texturedef_t *td;
plane_t *p;
float ang, sinv, cosv;
float s, t, ns, nt;
p = &f->plane;
// find the major axis
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)
Sys_Error ("BasePolyForPlane: 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);
VectorMultAdd (vup, -v, p->normal, vup);
VectorNormalize (vup);
VectorScale (p->normal, p->dist, org);
CrossProduct (vup, p->normal, vright);
VectorScale (vup, 8192, vup);
VectorScale (vright, 8192, vright);
// project a really big axis aligned box onto the plane
w = NewWinding (4);
w->numpoints = 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]);
// set texture values
f->light = TextureAxisFromPlane (&f->plane, xaxis, yaxis);
td = &f->texture;
// rotate axis
ang = td->rotate / 180 * M_PI;
sinv = sin (ang);
cosv = cos (ang);
if (!td->scale[0])
td->scale[0] = 1;
if (!td->scale[1])
td->scale[1] = 1;
for (i = 0; i < 4; i++) {
s = DotProduct (w->points[i], xaxis);
t = DotProduct (w->points[i], yaxis);
ns = cosv * s - sinv * t;
nt = sinv * s + cosv * t;
w->points[i][3] = ns / td->scale[0] + td->shift[0];
w->points[i][4] = nt / td->scale[1] + td->shift[1];
}
return w;
}
/*
===========
calcWindings
recalc the faces and mins / maxs from the planes
If a face has a NULL winding, it is an overconstraining plane and
can be removed.
===========
*/
-calcWindings
{
int i, j, k;
float v;
face_t *f;
winding_t *w;
plane_t plane;
vec3_t t1, t2, t3;
BOOL useplane[MAX_FACES];
bmins[0] = bmins[1] = bmins[2] = 99999;
bmaxs[0] = bmaxs[1] = bmaxs[2] = -99999;
invalid = NO;
[self freeWindings];
for (i = 0; i < MAX_FACES; i++) {
f = &faces[i];
// calc a plane from the points
for (j = 0; j < 3; j++) {
t1[j] = f->planepts[0][j] - f->planepts[1][j];
t2[j] = f->planepts[2][j] - f->planepts[1][j];
t3[j] = f->planepts[1][j];
}
CrossProduct (t1, t2, f->plane.normal);
if (VectorCompare (f->plane.normal, vec3_origin)) {
useplane[i] = NO;
break;
}
VectorNormalize (f->plane.normal);
f->plane.dist = DotProduct (t3, f->plane.normal);
// if the plane duplicates another plane, ignore it
// (assume it is a brush being edited that will be fixed)
useplane[i] = YES;
for (j = 0; j < i; j++) {
if (f->plane.normal[0] == faces[j].plane.normal[0]
&& f->plane.normal[1] == faces[j].plane.normal[1]
&& f->plane.normal[2] == faces[j].plane.normal[2]
&& f->plane.dist == faces[j].plane.dist) {
useplane[i] = NO;
break;
}
}
}
for (i = 0; i < numfaces; i++) {
if (!useplane[i])
continue; // duplicate plane
f = &faces[i];
w = BasePolyForPlane (f);
for (j = 0; j < numfaces && w; j++) {
if (j == i)
continue;
// flip the plane, because we want to keep the back side
VectorSubtract (vec3_origin, faces[j].plane.normal, plane.normal);
plane.dist = -faces[j].plane.dist;
w = ClipWinding (w, &plane);
}
f->w = w;
if (w) {
CheckFace (f);
for (j = 0; j < w->numpoints; j++) {
for (k = 0; k < 3; k++) {
v = w->points[j][k];
if (fabs (v - rint (v)) < FP_EPSILON)
v = w->points[j][k] = rint (v);
if (v < bmins[k])
bmins[k] = v;
if (v > bmaxs[k])
bmaxs[k] = v;
}
}
}
}
if (bmins[0] == 99999) {
invalid = YES;
VectorCopy (vec3_origin, bmins);
VectorCopy (vec3_origin, bmaxs);
return nil;
}
return self;
}
//============================================================================
/*
===========
initOwner:::
===========
*/
-initOwner: own mins:(float *)
mins maxs:(float *)
maxs texture:(texturedef_t *) tex
{
[super init];
parent = own;
[self setTexturedef:tex];
[self setMins: mins maxs:maxs];
return self;
}
-setMins:(float *)
mins maxs:(float *) maxs
{
int i, j;
vec3_t pts[4][2];
for (i = 0; i < 3; i++) {
if (maxs[i] - mins[i] <= 0) {
VectorCopy (mins, bmins);
VectorCopy (maxs, bmaxs);
invalid = YES;
numfaces = 0;
return self;
}
}
pts[0][0][0] = mins[0];
pts[0][0][1] = mins[1];
pts[1][0][0] = mins[0];
pts[1][0][1] = maxs[1];
pts[2][0][0] = maxs[0];
pts[2][0][1] = maxs[1];
pts[3][0][0] = maxs[0];
pts[3][0][1] = mins[1];
for (i = 0; i < 4; i++) {
pts[i][0][2] = mins[2];
pts[i][1][0] = pts[i][0][0];
pts[i][1][1] = pts[i][0][1];
pts[i][1][2] = maxs[2];
}
numfaces = 6;
for (i = 0; i < 4; i++) {
j = (i + 1) % 4;
faces[i].planepts[0][0] = pts[j][1][0];
faces[i].planepts[0][1] = pts[j][1][1];
faces[i].planepts[0][2] = pts[j][1][2];
faces[i].planepts[1][0] = pts[i][1][0];
faces[i].planepts[1][1] = pts[i][1][1];
faces[i].planepts[1][2] = pts[i][1][2];
faces[i].planepts[2][0] = pts[i][0][0];
faces[i].planepts[2][1] = pts[i][0][1];
faces[i].planepts[2][2] = pts[i][0][2];
}
faces[4].planepts[0][0] = pts[0][1][0];
faces[4].planepts[0][1] = pts[0][1][1];
faces[4].planepts[0][2] = pts[0][1][2];
faces[4].planepts[1][0] = pts[1][1][0];
faces[4].planepts[1][1] = pts[1][1][1];
faces[4].planepts[1][2] = pts[1][1][2];
faces[4].planepts[2][0] = pts[2][1][0];
faces[4].planepts[2][1] = pts[2][1][1];
faces[4].planepts[2][2] = pts[2][1][2];
faces[5].planepts[0][0] = pts[2][0][0];
faces[5].planepts[0][1] = pts[2][0][1];
faces[5].planepts[0][2] = pts[2][0][2];
faces[5].planepts[1][0] = pts[1][0][0];
faces[5].planepts[1][1] = pts[1][0][1];
faces[5].planepts[1][2] = pts[1][0][2];
faces[5].planepts[2][0] = pts[0][0][0];
faces[5].planepts[2][1] = pts[0][0][1];
faces[5].planepts[2][2] = pts[0][0][2];
[self calcWindings];
return self;
}
-parent
{
return parent;
}
-setParent:(id) p
{
parent = p;
return self;
}
-setEntityColor:(vec3_t) color
{
VectorCopy (color, entitycolor);
return self;
}
-freeWindings
{
int i;
for (i = 0; i < MAX_FACES; i++)
if (faces[i].w) {
free (faces[i].w);
faces[i].w = NULL;
}
return self;
}
-(void) dealloc
{
[self freeWindings];
return[super dealloc];
}
/*
===========
initOwner: fromTokens
===========
*/
int numsb;
-initFromScript:(script_t *)
script owner:own {
face_t *f;
int i, j;
[self init];
parent = own;
f = faces;
numfaces = 0;
do
{
if (!Script_GetToken (script, true))
break;
if (!strcmp (Script_Token (script), "}"))
break;
for (i = 0; i < 3; i++) {
if (i != 0)
Script_GetToken (script, true);
if (strcmp (Script_Token (script), "("))
Sys_Error ("parsing map file");
for (j = 0; j < 3; j++) {
Script_GetToken (script, false);
f->planepts[i][j] = atoi (Script_Token (script));
} Script_GetToken (script, false);
if (strcmp (Script_Token (script), ")"))
Sys_Error ("parsing map file");
}
Script_GetToken (script, false);
strcpy (f->texture.texture, Script_Token (script));
Script_GetToken (script, false);
f->texture.shift[0] = atof (Script_Token (script));
Script_GetToken (script, false);
f->texture.shift[1] = atof (Script_Token (script));
Script_GetToken (script, false);
f->texture.rotate = atof (Script_Token (script));
Script_GetToken (script, false);
f->texture.scale[0] = atof (Script_Token (script));
Script_GetToken (script, false);
f->texture.scale[1] = atof (Script_Token (script));
#if 0
flags = atoi (Script_Token (script));
flags &= 7;
f->texture.rotate = 0;
f->texture.scale[0] = 1;
f->texture.scale[1] = 1;
#define TEX_FLIPAXIS 1
#define TEX_FLIPS 2
#define TEX_FLIPT 4
if (flags & TEX_FLIPAXIS) {
f->texture.rotate = 90;
if (!(flags & TEX_FLIPT))
f->texture.scale[0] = -1;
if (flags & TEX_FLIPS)
f->texture.scale[1] = -1;
} else {
if (flags & TEX_FLIPS)
f->texture.scale[0] = -1;
if (flags & TEX_FLIPT)
f->texture.scale[1] = -1;
}
#endif
f++;
numfaces++;
} while (1);
numsb++;
[self calcWindings];
return self;
}
/*
===========
writeToFILE
===========
*/
-writeToFILE:(FILE *)
f region:(BOOL) reg
{
int i, j;
face_t *fa;
texturedef_t *td;
if (reg && regioned)
return self;
fprintf (f, "{\n");
for (i = 0; i < numfaces; i++) {
fa = &faces[i];
for (j = 0; j < 3; j++)
fprintf (f, "( %d %d %d ) ", (int) fa->planepts[j][0],
(int) fa->planepts[j][1], (int) fa->planepts[j][2]);
td = &fa->texture;
fprintf (f, "%s %d %d %d %f %f\n", td->texture, (int) td->shift[0],
(int) td->shift[1], (int) td->rotate, td->scale[0],
td->scale[1]);
}
fprintf (f, "}\n");
return self;
}
/*
==============================================================================
INTERACTION
==============================================================================
*/
-getMins:(vec3_t)
mins maxs:(vec3_t) maxs
{
VectorCopy (bmins, mins);
VectorCopy (bmaxs, maxs);
return self;
}
-(BOOL) selected
{
return selected;
}
-setSelected:(BOOL) s
{
selected = s;
return self;
}
-(BOOL) regioned
{
return regioned;
}
-setRegioned:(BOOL) s
{
regioned = s;
return self;
}
/*
===========
setTexturedef
===========
*/
-setTexturedef:(texturedef_t *) tex
{
int i;
for (i = 0; i < MAX_FACES; i++) {
faces[i].texture = *tex;
faces[i].qtexture = NULL; // recache next render
}
[self calcWindings]; // in case texture coords changed
return self;
}
-setTexturedef:(texturedef_t *)
tex forFace:(int) f
{
if ((unsigned) f > numfaces)
Sys_Error ("setTexturedef:forFace: bad face number %i", f);
faces[f].texture = *tex;
faces[f].qtexture = NULL; // recache next render
[self calcWindings]; // in case texture coords changed
return self;
}
/*
===========
texturedef
===========
*/
-(texturedef_t *) texturedef
{
return &faces[0].texture;
}
-(texturedef_t *) texturedefForFace:(int) f
{
return &faces[f].texture;
}
/*
===========
removeIfInvalid
So created veneers don't stay around
===========
*/
-removeIfInvalid
{
int i, j;
for (i = 0; i < numfaces; i++) {
if (faces[i].w)
continue;
for (j = i + 1; j < numfaces; j++)
faces[j - 1] = faces[j];
i--;
numfaces--;
}
for (; i < MAX_FACES; i++)
faces[i].w = NULL;
if (numfaces < 4) {
invalid = YES;
[self remove];
return nil;
}
return self;
}
/*
===========
containsPoint
===========
*/
-(BOOL) containsPoint:(vec3_t) pt
{
int i;
for (i = 0; i < numfaces; i++)
if (DotProduct (faces[i].plane.normal, pt) >= faces[i].plane.dist)
return NO;
return YES;
}
/*
===========
clipRay
===========
*/
-clipRay: (vec3_t) p1: (vec3_t) p2: (vec3_t) frontpoint: (int *) f_face: (vec3_t) backpoint:(int *) b_face
{
int frontface, backface;
int i, j;
face_t *f;
float d1, d2, m;
float *start;
start = p1;
frontface = -2;
backface = -2;
f = faces;
for (i = 0; i < numfaces; i++, f++) {
if (!f->w)
continue; // clipped off plane
d1 = DotProduct (p1, f->plane.normal) - f->plane.dist;
d2 = DotProduct (p2, f->plane.normal) - f->plane.dist;
if (d1 >= 0 && d2 >= 0) { // the entire ray is in front of the
// polytope
*f_face = -1;
*b_face = -1;
return self;
}
if (d1 > 0 && d2 < 0) { // new front plane
frontface = i;
m = d1 / (d1 - d2);
for (j = 0; j < 3; j++)
frontpoint[j] = p1[j] + m * (p2[j] - p1[j]);
p1 = frontpoint;
}
if (d1 < 0 && d2 > 0) { // new back plane
backface = i;
m = d1 / (d1 - d2);
for (j = 0; j < 3; j++)
backpoint[j] = p1[j] + m * (p2[j] - p1[j]);
p2 = backpoint;
}
}
*f_face = frontface;
*b_face = backface;
return self;
}
/*
===========
hitByRay
===========
*/
-hitByRay: (vec3_t) p1: (vec3_t) p2: (float *) time:(int *) face
{
vec3_t frontpoint, backpoint, dir;
int frontface, backface;
if (regioned) {
*time = -1;
*face = -1;
return self;
}
[self clipRay: p1: p2: frontpoint: &frontface: backpoint:&backface];
if (frontface == -2 && backface == -2) { // entire ray is inside the
// brush, select first face
*time = 0;
*face = 0;
return self;
}
if (frontface < 0) { // ray started inside the polytope,
// don't select it
*time = -1;
*face = -1;
return self;
}
VectorSubtract (p2, p1, dir);
VectorNormalize (dir);
VectorSubtract (frontpoint, p1, frontpoint);
*time = DotProduct (frontpoint, dir);
if (*time < 0)
Sys_Error ("hitByRay: negative t");
*face = frontface;
return self;
}
/*
==============================================================================
DRAWING ROUTINES
==============================================================================
*/
BOOL fakebrush;
-drawConnections
{
id obj;
int c, i;
vec3_t dest, origin;
vec3_t mid;
vec3_t forward, right;
char *targname;
vec3_t min, max, temp;
char *targ;
targ =[parent valueForQKey:"target"];
if (!targ || !targ[0])
return self;
origin[0] = (bmins[0] + bmaxs[0]) / 2;
origin[1] = (bmins[1] + bmaxs[1]) / 2;
c =[map_i count];
for (i = 0; i < c; i++) {
obj =[map_i objectAtIndex:i];
targname =[obj valueForQKey:"targetname"];
if (strcmp (targ, targname))
continue;
[[obj objectAtIndex: 0] getMins: min maxs:max];
dest[0] = (min[0] + max[0]) / 2;
dest[1] = (min[1] + max[1]) / 2;
XYmoveto (origin);
XYlineto (dest);
forward[0] = dest[0] - origin[0];
forward[1] = dest[1] - origin[1];
forward[2] = 0;
if (!forward[0] && !forward[1])
continue;
VectorNormalize (forward);
forward[0] = 8 * forward[0];
forward[1] = 8 * forward[1];
right[0] = forward[1];
right[1] = -forward[0];
mid[0] = (dest[0] + origin[0]) / 2;
mid[1] = (dest[1] + origin[1]) / 2;
temp[0] = mid[0] + right[0] - forward[0];
temp[1] = mid[1] + right[1] - forward[1];
XYmoveto (temp);
XYlineto (mid);
temp[0] = mid[0] - right[0] - forward[0];
temp[1] = mid[1] - right[1] - forward[1];
XYlineto (temp);
}
return self;
}
-(BOOL) fakeBrush:(SEL) call
{
id copy;
face_t face;
if (!selected || fakebrush)
return NO;
if (![clipper_i getFace:&face])
return NO;
fakebrush = YES;
copy =[self copy];
copy =[copy addFace:&face];
if (copy) {
[copy performSelector:call];
[copy dealloc];
}
fakebrush = NO;
return YES;
}
/*
===========
XYDrawSelf
===========
*/
-XYDrawSelf
{
int i, j;
winding_t *w;
vec3_t mid, end, s1, s2;
char *val;
float ang;
id worldent, currentent;
BOOL keybrush;
if ([self fakeBrush:@selector (XYDrawSelf)])
return self;
[xyview_i addToScrollRange: bmins[0]:bmins[1]];
[xyview_i addToScrollRange: bmaxs[0]:bmaxs[1]];
worldent =[map_i objectAtIndex:0];
currentent =[map_i currentEntity];
if (parent != worldent && self ==[parent objectAtIndex:0])
keybrush = YES;
else
keybrush = NO;
if (parent != worldent && worldent == currentent)
linecolor (entitycolor[0], entitycolor[1], entitycolor[2]);
else if (selected)
linecolor (1, 0, 0); // selected
else if (parent == currentent)
linecolor (0, 0, 0); // unselected, but in same entity
else
linecolor (0, 0.5, 0); // other entity green
if (keybrush)
[self drawConnections]; // target line
if (!selected &&
(bmaxs[0] < xy_draw_rect.origin.x
|| bmaxs[1] < xy_draw_rect.origin.y
|| bmins[0] > xy_draw_rect.origin.x + xy_draw_rect.size.width
|| bmins[1] > xy_draw_rect.origin.y + xy_draw_rect.size.height))
return self; // off view, don't bother
for (i = 0; i < numfaces; i++) {
w = faces[i].w;
if (!w)
continue;
if (DotProduct (faces[i].plane.normal, xy_viewnormal) > -VECTOR_EPSILON)
continue;
XYmoveto (w->points[w->numpoints - 1]);
for (j = 0; j < w->numpoints; j++)
XYlineto (w->points[j]);
}
if (keybrush) {
// angle arrow
val =[parent valueForQKey:"angle"];
if (val && val[0]) {
ang = atof (val) * M_PI / 180;
if (ang > 0) // negative values are up/down flags
{
mid[0] = (bmins[0] + bmaxs[0]) / 2;
mid[1] = (bmins[1] + bmaxs[1]) / 2;
end[0] = mid[0] + 16 * cos (ang);
end[1] = mid[1] + 16 * sin (ang);
s1[0] = mid[0] + 12 * cos (ang + 0.4);
s1[1] = mid[1] + 12 * sin (ang + 0.4);
s2[0] = mid[0] + 12 * cos (ang - 0.4);
s2[1] = mid[1] + 12 * sin (ang - 0.4);
XYmoveto (mid);
XYlineto (end);
XYmoveto (s1);
XYlineto (end);
XYlineto (s2);
}
}
}
return self;
}
/*
===========
ZDrawSelf
===========
*/
-ZDrawSelf
{
int i;
vec3_t p1, p2;
vec3_t frontpoint, backpoint;
int frontface, backface;
qtexture_t *q;
if ([self fakeBrush:@selector (ZDrawSelf)])
return self;
[zview_i addToHeightRange:bmins[2]];
[zview_i addToHeightRange:bmaxs[2]];
if (selected) {
PSmoveto (1, bmaxs[2]);
PSlineto (23, bmaxs[2]);
PSlineto (23, bmins[2]);
PSlineto (1, bmins[2]);
PSlineto (1, bmaxs[2]);
PSsetrgbcolor (1, 0, 0);
PSstroke ();
}
[zview_i getPoint:(NSPoint *) p1];
for (i = 0; i < 2; i++)
if (bmins[i] >= p1[i] || bmaxs[i] <= p1[i])
return self;
p1[2] = 4096;
p2[0] = p1[0];
p2[1] = p1[1];
p2[2] = -4096;
[self clipRay: p1: p2: frontpoint: &frontface: backpoint:&backface];
if (frontface == -1 || backface == -1)
return self;
q = TEX_ForName (faces[frontface].texture.texture);
PSmoveto (-8, frontpoint[2]);
PSlineto (8, frontpoint[2]);
PSlineto (8, backpoint[2]);
PSlineto (-8, backpoint[2]);
PSlineto (-8, frontpoint[2]);
PSsetrgbcolor (q->flatcolor.chan[0] / 255.0, q->flatcolor.chan[1] / 255.0,
q->flatcolor.chan[2] / 255.0);
PSfill ();
PSmoveto (-12, frontpoint[2]);
PSlineto (12, frontpoint[2]);
PSlineto (12, backpoint[2]);
PSlineto (-12, backpoint[2]);
PSlineto (-12, frontpoint[2]);
PSsetrgbcolor (0, 0, 0);
PSstroke ();
return self;
}
/*
===========
CameraDrawSelf
===========
*/
-CameraDrawSelf
{
int i, j;
winding_t *w;
id worldent, currentent;
if ([self fakeBrush:@selector (CameraDrawSelf)])
return self;
worldent =[map_i objectAtIndex:0];
currentent =[map_i currentEntity];
if (parent != worldent && worldent == currentent)
linecolor (entitycolor[0], entitycolor[1], entitycolor[2]);
else if (selected)
linecolor (1, 0, 0);
else if (parent ==[map_i currentEntity])
linecolor (0, 0, 0);
else
linecolor (0, 0.5, 0);
for (i = 0; i < numfaces; i++) {
w = faces[i].w;
if (!w)
continue;
CameraMoveto (w->points[w->numpoints - 1]);
for (j = 0; j < w->numpoints; j++)
CameraLineto (w->points[j]);
}
return self;
}
/*
===========
XYRenderSelf
===========
*/
-XYRenderSelf
{
int i;
if ([self fakeBrush:@selector (XYRenderSelf)])
return self;
for (i = 0; i < numfaces; i++)
REN_DrawXYFace (&faces[i]);
return self;
}
/*
===========
CameraRenderSelf
===========
*/
-CameraRenderSelf
{
int i;
BOOL olddraw;
extern qtexture_t badtex;
pixel32_t p;
if ([self fakeBrush:@selector (CameraRenderSelf)])
return self;
// hack to draw entity boxes as single flat color
if (![parent modifiable]) {
olddraw = r_drawflat;
r_drawflat = YES;
p = badtex.flatcolor;
badtex.flatcolor.chan[0] = entitycolor[0] * 255;
badtex.flatcolor.chan[1] = entitycolor[1] * 255;
badtex.flatcolor.chan[2] = entitycolor[2] * 255;
for (i = 0; i < numfaces; i++)
REN_DrawCameraFace (&faces[i]);
badtex.flatcolor = p;
r_drawflat = olddraw;
} else {
for (i = 0; i < numfaces; i++)
REN_DrawCameraFace (&faces[i]);
}
return self;
}
/*
==============================================================================
SINGLE BRUSH ACTIONS
==============================================================================
*/
face_t *dragface, *dragface2;
int numcontrolpoints;
float *controlpoints[MAX_FACES * 3];
-(BOOL) checkModifiable
{
// int i;
if ([parent modifiable])
return YES;
// don't stretch spawned entities, move all points
#if 0
numcontrolpoints = numfaces * 3;
for (i = 0; i < numcontrolpoints; i++)
controlpoints[i] = faces[i / 3].planepts[i % 3];
#endif
return NO;
}
-getZdragface:(vec3_t) dragpoint
{
int i, j;
float d;
if (![self checkModifiable])
return self;
numcontrolpoints = 0;
for (i = 0; i < numfaces; i++) {
if (!faces[i].w)
continue;
if (faces[i].plane.normal[2] == 1)
d = dragpoint[2] - faces[i].plane.dist;
else if (faces[i].plane.normal[2] == -1)
d = -faces[i].plane.dist - dragpoint[2];
else
continue;
if (d <= 0)
continue;
for (j = 0; j < 3; j++) {
controlpoints[numcontrolpoints] = faces[i].planepts[j];
numcontrolpoints++;
}
}
return self;
}
-getXYdragface:(vec3_t) dragpoint
{
int i, j;
float d;
numcontrolpoints = 0;
if (![self checkModifiable])
return self;
for (i = 0; i < numfaces; i++) {
if (!faces[i].w)
continue;
if (faces[i].plane.normal[2])
continue;
d = DotProduct (faces[i].plane.normal, dragpoint) - faces[i].plane.dist;
if (d <= 0)
continue;
for (j = 0; j < 3; j++) {
controlpoints[numcontrolpoints] = faces[i].planepts[j];
numcontrolpoints++;
}
}
return self;
}
-getXYShearPoints:(vec3_t) dragpoint
{
int i, j, k;
int facectl;
float d;
int numdragplanes;
BOOL dragplane[MAX_FACES];
winding_t *w;
face_t *f;
BOOL onplane[MAX_POINTS_ON_WINDING];
if (![self checkModifiable])
return self;
numcontrolpoints = 0;
numdragplanes = 0;
for (i = 0; i < numfaces; i++) {
dragplane[i] = NO;
if (!faces[i].w)
continue;
// if (faces[i].plane.normal[2])
// continue;
d = DotProduct (faces[i].plane.normal, dragpoint) - faces[i].plane.dist;
if (d <= -ON_EPSILON)
continue;
dragplane[i] = YES;
numdragplanes++;
}
// find faces that just share an edge with a drag plane
for (i = 0; i < numfaces; i++) {
f = &faces[i];
w = f->w;
if (!w)
continue;
if (dragplane[i] && numdragplanes == 1) {
for (j = 0; j < 3; j++) {
controlpoints[numcontrolpoints] = faces[i].planepts[j];
numcontrolpoints++;
}
continue;
}
if (!dragplane[i] && numdragplanes > 1)
continue;
facectl = 0;
for (j = 0; j < w->numpoints; j++) {
onplane[j] = NO;
for (k = 0; k < numfaces; k++) {
if (!dragplane[k])
continue;
if (k == i)
continue;
d = DotProduct (w->points[j], faces[k].plane.normal)
- faces[k].plane.dist;
if (fabs (d) > ON_EPSILON)
continue;
onplane[j] = YES;
facectl++;
break;
}
}
if (facectl == 0)
continue;
// find one or two static points to go with the controlpoints
// and change the plane points
k = 0;
for (j = 0; j < w->numpoints; j++) {
if (!onplane[j])
continue;
if (facectl >= 2 && !onplane[(j + 1) % w->numpoints])
continue;
if (facectl == 3 && !onplane[(j + 2) % w->numpoints])
continue;
VectorCopy (w->points[j], f->planepts[k]);
controlpoints[numcontrolpoints] = f->planepts[k];
numcontrolpoints++;
k++;
if (facectl >= 2) {
VectorCopy (w->points[(j + 1) % w->numpoints], f->planepts[k]);
controlpoints[numcontrolpoints] = f->planepts[k];
numcontrolpoints++;
k++;
}
if (facectl == 3) {
VectorCopy (w->points[(j + 2) % w->numpoints], f->planepts[k]);
controlpoints[numcontrolpoints] = f->planepts[k];
numcontrolpoints++;
k++;
}
break;
}
for (; j < w->numpoints && k != 3; j++)
if (!onplane[j]) {
VectorCopy (w->points[j], f->planepts[k]);
k++;
}
for (j = 0; j < w->numpoints && k != 3; j++)
if (!onplane[j]) {
VectorCopy (w->points[j], f->planepts[k]);
k++;
}
if (k != 3) {
// Sys_Error ("getXYShearPoints: didn't get three points on plane");
numcontrolpoints = 0;
return self;
}
for (j = 0; j < 3; j++)
for (k = 0; k < 3; k++)
f->planepts[j][k] = rint (f->planepts[j][k]);
}
return self;
}
/*
==============================================================================
MULTIPLE BRUSH ACTIONS
==============================================================================
*/
vec3_t region_min, region_max;
/*
===========
newRegion
Set the regioned flag based on if the object is containted in region_min/max
===========
*/
-newRegion
{
int i;
char *name;
// filter away entities
if (parent !=[map_i objectAtIndex:0]) {
if (filter_entities) {
regioned = YES;
return self;
}
name =[parent valueForQKey:"classname"];
if ((filter_light && !strncmp (name, "light", 5))
|| (filter_path && !strncmp (name, "path", 4))) {
regioned = YES;
return self;
}
} else if (filter_world) {
regioned = YES;
return self;
}
if (filter_clip_brushes && !strcasecmp (faces[0].texture.texture, "clip")) {
regioned = YES;
return self;
}
if (filter_water_brushes && faces[0].texture.texture[0] == '*') {
regioned = YES;
return self;
}
for (i = 0; i < 3; i++) {
if (region_min[i] >= bmaxs[i] || region_max[i] <= bmins[i]) {
if (selected)
[self deselect];
regioned = YES;
return self;
}
}
regioned = NO;
return self;
}
vec3_t select_min, select_max;
-selectPartial
{
int i;
for (i = 0; i < 3; i++)
if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i])
return self;
selected = YES;
return self;
}
-selectComplete
{
int i;
for (i = 0; i < 3; i++)
if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i])
return self;
selected = YES;
return self;
}
-regionPartial
{
int i;
for (i = 0; i < 3; i++)
if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i])
return self;
selected = YES;
return self;
}
-regionComplete
{
int i;
for (i = 0; i < 3; i++)
if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i])
return self;
selected = YES;
return self;
}
id sb_newowner;
-moveToEntity
{
id eclass;
float *c;
[parent removeObject:self];
parent = sb_newowner;
// hack to allow them to be copied to another map
if ([parent respondsToSelector: @selector (valueForQKey:)]) {
eclass =[entity_classes_i classForName: [parent valueForQKey:"classname"]];
c =[eclass drawColor];
[self setEntityColor:c];
}
[parent addObject:self];
return self;
}
vec3_t sb_translate;
-translate
{
int i, j;
// move the planes
for (i = 0; i < numfaces; i++)
for (j = 0; j < 3; j++) {
VectorAdd (faces[i].planepts[j], sb_translate,
faces[i].planepts[j]);
}
[self calcWindings];
return self;
}
vec3_t sb_mins, sb_maxs;
-addToBBox
{
int k;
if (numfaces < 4)
return self;
for (k = 0; k < 3; k++) {
if (bmins[k] < sb_mins[k])
sb_mins[k] = bmins[k];
if (bmaxs[k] > sb_maxs[k])
sb_maxs[k] = bmaxs[k];
}
return self;
}
-flushTextures
{ // call when texture palette changes
int i;
for (i = 0; i < MAX_FACES; i++)
faces[i].qtexture = NULL;
[self calcWindings];
return self;
}
-select
{
[map_i setCurrentEntity:parent];
selected = YES;
return self;
}
-deselect
{
selected = NO;
// the last selected brush determines
if (invalid)
printf ("WARNING: deselected invalid brush\n");
[map_i setCurrentMinZ:bmins[2]];
[map_i setCurrentMaxZ:bmaxs[2]];
return self;
}
-remove
{
// the last selected brush determines
if (!invalid) {
[map_i setCurrentMinZ:bmins[2]];
[map_i setCurrentMaxZ:bmaxs[2]];
}
[parent removeObject:self];
[self dealloc];
return nil;
}
vec3_t sel_x, sel_y, sel_z;
vec3_t sel_org;
-transform
{
int i, j;
vec3_t old;
float *p;
for (i = 0; i < numfaces; i++)
for (j = 0; j < 3; j++) {
p = faces[i].planepts[j];
VectorCopy (p, old);
VectorSubtract (old, sel_org, old);
p[0] = DotProduct (old, sel_x);
p[1] = DotProduct (old, sel_y);
p[2] = DotProduct (old, sel_z);
VectorAdd (p, sel_org, p);
}
[self calcWindings];
return self;
}
-flipNormals // used after an inside-out transform
// (flip x/y/z)
{
int i;
vec3_t temp;
for (i = 0; i < numfaces; i++) {
VectorCopy (faces[i].planepts[0], temp);
VectorCopy (faces[i].planepts[2], faces[i].planepts[0]);
VectorCopy (temp, faces[i].planepts[2]);
}
[self calcWindings];
return self;
}
-carveByClipper
{
face_t face;
if (![clipper_i getFace:&face])
return self;
[self addFace:&face];
return self;
}
-takeCurrentTexture
{
texturedef_t td;
[texturepalette_i getTextureDef:&td];
[self setTexturedef:&td];
return self;
}
float sb_floor_dir, sb_floor_dist;
-feetToFloor
{
float oldz;
vec3_t p1, p2;
int frontface, backface;
vec3_t frontpoint, backpoint;
float dist;
[cameraview_i getOrigin:p1];
VectorCopy (p1, p2);
oldz = p1[2] - 48;
p1[2] = 4096;
p2[2] = -4096;
[self clipRay: p1: p2: frontpoint: &frontface: backpoint:&backface];
if (frontface == -1)
return self;
dist = frontpoint[2] - oldz;
if (sb_floor_dir == 1) {
if (dist > 0 && dist < sb_floor_dist)
sb_floor_dist = dist;
} else {
if (dist < 0 && dist > sb_floor_dist)
sb_floor_dist = dist;
}
return self;
}
/*
===============================================================================
BRUSH SUBTRACTION
===============================================================================
*/
vec3_t carvemin, carvemax;
int numcarvefaces;
face_t *carvefaces;
id carve_in, carve_out;
// returns the new brush formed after the addition of the given plane
// nil is returned if it faced all of the original setbrush
-addFace:(face_t *) f
{
if (numfaces == MAX_FACES)
Sys_Error ("addFace: numfaces == MAX_FACES");
faces[numfaces] = *f;
faces[numfaces].texture = faces[0].texture;
faces[numfaces].qtexture = NULL;
faces[numfaces].w = NULL;
numfaces++;
[self calcWindings];
// remove any degenerate faces
return[self removeIfInvalid];
}
-clipByFace:(face_t *)
fa front:(id *)
f back:(id *) b
{
id front, back;
face_t fb;
vec3_t temp;
fb = *fa;
VectorCopy (fb.planepts[0], temp);
VectorCopy (fb.planepts[2], fb.planepts[0]);
VectorCopy (temp, fb.planepts[2]);
front =[self copy];
back =[self copy];
*b =[back addFace:fa];
*f =[front addFace:&fb];
return self;
}
-carve
{
int i;
id front, back;
#if 0
if ((i = NSMallocCheck ()))
Sys_Error ("MallocCheck failure");
#endif
// check bboxes
for (i = 0; i < 3; i++)
if (bmins[i] >= carvemax[i] || bmaxs[i] <= carvemin[i]) {
[carve_out addObject:self];
return self;
}
// carve by the planes
back = self;
for (i = 0; i < numcarvefaces; i++) {
[back clipByFace: &carvefaces[i] front: &front back:&back];
if (front)
[carve_out addObject:front];
if (!back)
return nil; // nothing completely inside
}
[carve_in addObject:back];
return self;
}
/*
==================
setCarveVars
==================
*/
-setCarveVars
{
VectorCopy (bmins, carvemin);
VectorCopy (bmaxs, carvemax);
numcarvefaces = numfaces;
carvefaces = faces;
return self;
}
-(int) getNumBrushFaces
{
return numfaces;
}
-(face_t *) getBrushFace:(int) which
{
return &faces[which];
}
@end