#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