#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; // can't use counts[0]+2 because of fp grouping errors maxpts = in->numpoints + 4; 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. =========== */ - (id) 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]) // duplicate plane continue; 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::: =========== */ - (SetBrush *) 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; } - (id) 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; } - (id) parent { return parent; } - (id) setParent: (id)p { parent = p; return self; } - (id) setEntityColor: (vec3_t)color { VectorCopy (color, entitycolor); return self; } - (void) freeWindings { int i; for (i = 0; i < MAX_FACES; i++) { if (faces[i].w) { free (faces[i].w); faces[i].w = NULL; } } } - (id) copyWithZone: (NSZone *) zone { SetBrush *new; [self freeWindings]; new = [[SetBrush allocWithZone: zone] init]; new->regioned = regioned; new->selected = selected; new->invalid = invalid; new->parent = parent; VectorCopy (bmins, new->bmins); VectorCopy (bmaxs, new->bmaxs); VectorCopy (entitycolor, new->entitycolor); new->numfaces = numfaces; memcpy (new->faces, faces, sizeof (new->faces)); [self calcWindings]; [new calcWindings]; return new; } - (void) dealloc { [self freeWindings]; return [super dealloc]; } /* =========== initOwner: fromTokens =========== */ int numsb; static vec3_t * ParseVerts (script_t *script, int *n_verts) { vec3_t *verts; int i; if (strcmp (Script_Token (script), ":")) Sys_Error ("parsing map file"); Script_GetToken (script, false); *n_verts = atoi (Script_Token (script)); verts = malloc (sizeof (vec3_t) * *n_verts); for (i = 0; i < *n_verts; i++) { Script_GetToken (script, true); verts[i][0] = atof (Script_Token (script)); Script_GetToken (script, true); verts[i][1] = atof (Script_Token (script)); Script_GetToken (script, true); verts[i][2] = atof (Script_Token (script)); } return verts; } - (id) initFromScript: (script_t *)script owner: own { face_t *f; vec3_t *verts = 0; int n_verts = 0; int i, j; [self init]; parent = own; f = faces; numfaces = 0; if (!Script_GetToken (script, true)) return self; if (strcmp (Script_Token (script), "(")) { verts = ParseVerts (script, &n_verts); } else { Script_UngetToken (script); } do { if (f - faces >= MAX_FACES) { printf ("%s:%d: too many faces\n", script->file, script->line); abort (); } if (!Script_GetToken (script, true)) break; if (!strcmp (Script_Token (script), "}")) break; if (verts) { int n_v, v; n_v = atoi (Script_Token (script)); Script_GetToken (script, false); for (i = 0; i < n_v; i++) { Script_GetToken (script, false); v = atoi (Script_Token (script)); if (i < 3) VectorCopy (verts[v], f->planepts[i]); } printf ("\n"); Script_GetToken (script, false); } else { 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)); while (Script_TokenAvailable (script, false)) { Script_GetToken (script, false); if (!strcmp (Script_Token (script), "detail")) ; // XXX implement else Sys_Error ("parsing map file"); } #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 /* if 0 */ f++; numfaces++; } while (1); numsb++; [self calcWindings]; return self; } - (void) writeToFile: (QFile *)file region: (BOOL)reg { int i, j; face_t *fa; texturedef_t *td; if (reg && regioned) return; Qprintf (file, "{\n"); for (i = 0; i < numfaces; i++) { fa = &faces[i]; for (j = 0; j < 3; j++) { Qprintf (file, "( %d %d %d ) ", (int) fa->planepts[j][0], (int) fa->planepts[j][1], (int) fa->planepts[j][2]); } td = &fa->texture; Qprintf (file, "%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]); } Qprintf (file, "}\n"); return; } /* ============================================================================== INTERACTION ============================================================================== */ - (void) getMins: (vec3_t)mins maxs: (vec3_t)maxs { VectorCopy (bmins, mins); VectorCopy (bmaxs, maxs); } - (BOOL) selected { return selected; } - (void) setSelected: (BOOL)s { selected = s; } - (BOOL) regioned { return regioned; } - (void) setRegioned: (BOOL)s { regioned = s; } /* =========== setTexturedef =========== */ - (void) 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 } - (void) setTexturedef: (texturedef_t *)tex forFace: (int)f { if (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 } /* =========== 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 =========== */ - (id) 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:::::: =========== */ - (id) 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 whole 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:::: =========== */ - (void) 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 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; } if (frontface < 0) { // ray started inside the polytope, don't select it *time = -1; *face = -1; return; } VectorSubtract (p2, p1, dir); VectorNormalize (dir); VectorSubtract (frontpoint, p1, frontpoint); *time = DotProduct (frontpoint, dir); if (*time < 0) Sys_Error ("hitByRay: negative t"); *face = frontface; } /* ============================================================================== DRAWING ROUTINES ============================================================================== */ BOOL fakebrush; - (id) drawConnections { id obj; int c, i; vec3_t dest, origin; vec3_t mid; vec3_t forward, right; const char *targname; vec3_t min, max, temp; const char *targ; NSArray *target_list; targ = [parent valueForQKey: "target"]; if (!targ || !targ[0]) return self; origin[0] = (bmins[0] + bmaxs[0]) / 2; origin[1] = (bmins[1] + bmaxs[1]) / 2; target_list = [map_i targetsForTargetName: targ]; c = [target_list count]; for (i = 0; i < c; i++) { obj = [target_list 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 =========== */ - (void) XYDrawSelf { int i, j; winding_t *w; vec3_t mid, end, s1, s2; const char *val; float ang; id worldent, currentent; BOOL keybrush; if ([self fakeBrush: @selector (XYDrawSelf)]) return; [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) // selected linecolor (1, 0, 0); else if (parent == currentent) // unselected, but in same entity linecolor (0, 0, 0); else // other entity green linecolor (0, 0.5, 0); 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; // 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) { val = [parent valueForQKey: "angle"]; // angle arrow 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; } /* =========== ZDrawSelf =========== */ - (void) ZDrawSelf { int i; NSPoint p; vec3_t p1, p2; vec3_t frontpoint, backpoint; int frontface, backface; qtexture_t *q; if ([self fakeBrush: @selector (ZDrawSelf)]) return; [zview_i addToHeightRange: bmins[2]]; [zview_i addToHeightRange: bmaxs[2]]; if (selected) { [[NSColor redColor] set]; NSFrameRect (NSMakeRect (1, bmins[2], 24, bmaxs[2] - bmins[2])); } [zview_i getPoint: &p]; p1[0] = p.x; p1[1] = p.y; for (i = 0; i < 2; i++) { if (bmins[i] >= p1[i] || bmaxs[i] <= p1[i]) return; } 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; q = TEX_ForName (faces[frontface].texture.texture); [[NSColor colorWithCalibratedRed: q->flatcolor.chan[0] / 255.0 green: q->flatcolor.chan[1] / 255.0 blue: q->flatcolor.chan[2] / 255.0 alpha: 1.0] set]; NSRectFill (NSMakeRect (-8, backpoint[2], 17, frontpoint[2] - backpoint[2] + 1)); [[NSColor blackColor] set]; NSFrameRect (NSMakeRect (-12, backpoint[2], 25, frontpoint[2] - backpoint[2] + 1)); return; } /* =========== CameraDrawSelf =========== */ - (void) CameraDrawSelf { int i, j; winding_t *w; id worldent, currentent; if ([self fakeBrush: @selector (CameraDrawSelf)]) return; 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; } /* =========== XYRenderSelf =========== */ - (void) XYRenderSelf { int i; if ([self fakeBrush: @selector (XYRenderSelf)]) return; for (i = 0; i < numfaces; i++) REN_DrawXYFace (&faces[i]); return; } /* =========== CameraRenderSelf =========== */ - (void) CameraRenderSelf { int i; BOOL olddraw; extern qtexture_t badtex; pixel32_t p; if ([self fakeBrush: @selector (CameraRenderSelf)]) return; // 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; } /* ============================================================================== 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; } - (void) getZdragface: (vec3_t)dragpoint { int i, j; float d; if (![self checkModifiable]) return; 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++; } } } - (void) getXYdragface: (vec3_t)dragpoint { int i, j; float d; numcontrolpoints = 0; if (![self checkModifiable]) return; 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++; } } } - (void) 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; 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; } for (j = 0; j < 3; j++) { for (k = 0; k < 3; k++) f->planepts[j][k] = rint (f->planepts[j][k]); } } } /* ============================================================================== 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 =========== */ - (id) newRegion { int i; const 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; - (void) selectPartial { int i; for (i = 0; i < 3; i++) { if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i]) return; } selected = YES; } - (void) selectComplete { int i; for (i = 0; i < 3; i++) { if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i]) return; } selected = YES; } - (void) regionPartial { int i; for (i = 0; i < 3; i++) { if (select_min[i] >= bmaxs[i] || select_max[i] <= bmins[i]) return; } selected = YES; } - (void) regionComplete { int i; for (i = 0; i < 3; i++) { if (select_min[i] > bmins[i] || select_max[i] < bmaxs[i]) return; } selected = YES; } id sb_newowner; - (void) 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]; } vec3_t sb_translate; - (void) 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]; } vec3_t sb_mins, sb_maxs; - (void) addToBBox { int k; if (numfaces < 4) return; 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]; } } - (void) flushTextures { // call when texture palette changes int i; for (i = 0; i < MAX_FACES; i++) faces[i].qtexture = NULL; [self calcWindings]; } - (void) select { [map_i setCurrentEntity: parent]; selected = YES; } - (void) 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]]; } - (void) remove { // the last selected brush determines if (!invalid) { [map_i setCurrentMinZ: bmins[2]]; [map_i setCurrentMaxZ: bmaxs[2]]; } [parent removeObject: self]; [self release]; } vec3_t sel_x, sel_y, sel_z; vec3_t sel_org; - (void) 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]; } - (void) 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]; } - (void) carveByClipper { face_t face; if (![clipper_i getFace: &face]) return; [self addFace: &face]; } - (void) takeCurrentTexture { texturedef_t td; [texturepalette_i getTextureDef: &td]; [self setTexturedef: &td]; } float sb_floor_dir, sb_floor_dist; - (void) 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; 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; } } /* =============================================================================== 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 - (id) 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]; } - (void) 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]; } - (id) 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 ================== */ - (void) setCarveVars { VectorCopy (bmins, carvemin); VectorCopy (bmaxs, carvemax); numcarvefaces = numfaces; carvefaces = faces; } - (int) getNumBrushFaces { return numfaces; } - (face_t *) getBrushFace: (int)which { return &faces[which]; } @end