From e4cb063480f9256b902aa00f47d18fa23f5bda12 Mon Sep 17 00:00:00 2001 From: Travis Bradshaw Date: Tue, 31 Jan 2012 15:51:12 -0600 Subject: [PATCH] Source release of QuakeEd, the map editing application on NEXTSTEP for Quake. --- QuakeEd/.nfs7072 | 33 + QuakeEd/Brush.h | 55 + QuakeEd/CameraView.h | 61 + QuakeEd/CameraView.m | 970 ++ QuakeEd/Clipper.h | 24 + QuakeEd/Clipper.m | 227 + QuakeEd/Dict.h | 47 + QuakeEd/Dict.m | 583 + QuakeEd/DictList.h | 12 + QuakeEd/DictList.m | 69 + QuakeEd/DownArrow.tiff | Bin 0 -> 256 bytes QuakeEd/English.lproj/Info.nib/data.classes | 1 + QuakeEd/English.lproj/Info.nib/data.nib | Bin 0 -> 1000 bytes .../English.lproj/QuakeEd.nib/data.classes | 220 + QuakeEd/English.lproj/QuakeEd.nib/data.nib | Bin 0 -> 29981 bytes QuakeEd/Entity.h | 40 + QuakeEd/Entity.m | 485 + QuakeEd/EntityClass.h | 42 + QuakeEd/EntityClass.m | 266 + QuakeEd/InspectorControl.h | 71 + QuakeEd/InspectorControl.m | 128 + QuakeEd/KeypairView.h | 16 + QuakeEd/KeypairView.m | 96 + QuakeEd/Makefile | 62 + QuakeEd/Makefile.postamble | 115 + QuakeEd/Makefile.preamble | 108 + QuakeEd/Map.h | 68 + QuakeEd/Map.m | 1121 ++ QuakeEd/PB.gdbinit | 7 + QuakeEd/PB.project | 26 + QuakeEd/PopScrollView.h | 11 + QuakeEd/PopScrollView.m | 87 + QuakeEd/Preferences.h | 78 + QuakeEd/Preferences.m | 330 + QuakeEd/Project.h | 108 + QuakeEd/Project.m | 526 + QuakeEd/QuakeEd.h | 98 + QuakeEd/QuakeEd.iconheader | 2 + QuakeEd/QuakeEd.m | 1026 ++ QuakeEd/QuakeEd_main.m | 15 + QuakeEd/README | 33 + QuakeEd/SetBrush.h | 158 + QuakeEd/SetBrush.m | 2034 +++ QuakeEd/TexturePalette.h | 113 + QuakeEd/TexturePalette.m | 818 ++ QuakeEd/TextureView.h | 12 + QuakeEd/TextureView.m | 152 + QuakeEd/Things.h | 42 + QuakeEd/Things.m | 317 + QuakeEd/UpArrow.tiff | Bin 0 -> 258 bytes QuakeEd/UserPath.h | 72 + QuakeEd/UserPath.m | 209 + QuakeEd/XYView.h | 66 + QuakeEd/XYView.m | 1384 ++ QuakeEd/ZScrollView.h | 11 + QuakeEd/ZScrollView.m | 71 + QuakeEd/ZView.h | 42 + QuakeEd/ZView.m | 872 ++ QuakeEd/cmdlib.c | 611 + QuakeEd/cmdlib.h | 62 + QuakeEd/help.txt | 175 + QuakeEd/i_90d.tiff | Bin 0 -> 1346 bytes QuakeEd/i_add.tiff | Bin 0 -> 1346 bytes QuakeEd/i_brushes.tiff | Bin 0 -> 1346 bytes QuakeEd/i_fliph.tiff | Bin 0 -> 1346 bytes QuakeEd/i_flipv.tiff | Bin 0 -> 1346 bytes QuakeEd/i_quakeed.tiff | Bin 0 -> 29799 bytes QuakeEd/i_sub.tiff | Bin 0 -> 1346 bytes QuakeEd/inspectors.tiff | Bin 0 -> 231654 bytes QuakeEd/jrbase1.map | 11858 ++++++++++++++++ QuakeEd/mainwindow.tiff | Bin 0 -> 45188 bytes QuakeEd/mathlib.c | 93 + QuakeEd/mathlib.h | 31 + QuakeEd/misc.m | 274 + QuakeEd/qedefs.h | 56 + QuakeEd/quake.qpr | 13 + QuakeEd/readme.txt | 33 + QuakeEd/render.h | 13 + QuakeEd/render.m | 748 + QuakeEd/short.tiff | Bin 0 -> 1346 bytes QuakeEd/tall.tiff | Bin 0 -> 1346 bytes QuakeEd/triggers.qc | 738 + 82 files changed, 28345 insertions(+) create mode 100644 QuakeEd/.nfs7072 create mode 100644 QuakeEd/Brush.h create mode 100644 QuakeEd/CameraView.h create mode 100644 QuakeEd/CameraView.m create mode 100644 QuakeEd/Clipper.h create mode 100644 QuakeEd/Clipper.m create mode 100644 QuakeEd/Dict.h create mode 100644 QuakeEd/Dict.m create mode 100644 QuakeEd/DictList.h create mode 100644 QuakeEd/DictList.m create mode 100644 QuakeEd/DownArrow.tiff create mode 100644 QuakeEd/English.lproj/Info.nib/data.classes create mode 100644 QuakeEd/English.lproj/Info.nib/data.nib create mode 100644 QuakeEd/English.lproj/QuakeEd.nib/data.classes create mode 100644 QuakeEd/English.lproj/QuakeEd.nib/data.nib create mode 100644 QuakeEd/Entity.h create mode 100644 QuakeEd/Entity.m create mode 100644 QuakeEd/EntityClass.h create mode 100644 QuakeEd/EntityClass.m create mode 100644 QuakeEd/InspectorControl.h create mode 100644 QuakeEd/InspectorControl.m create mode 100644 QuakeEd/KeypairView.h create mode 100644 QuakeEd/KeypairView.m create mode 100644 QuakeEd/Makefile create mode 100644 QuakeEd/Makefile.postamble create mode 100644 QuakeEd/Makefile.preamble create mode 100644 QuakeEd/Map.h create mode 100644 QuakeEd/Map.m create mode 100644 QuakeEd/PB.gdbinit create mode 100644 QuakeEd/PB.project create mode 100644 QuakeEd/PopScrollView.h create mode 100644 QuakeEd/PopScrollView.m create mode 100644 QuakeEd/Preferences.h create mode 100644 QuakeEd/Preferences.m create mode 100644 QuakeEd/Project.h create mode 100644 QuakeEd/Project.m create mode 100644 QuakeEd/QuakeEd.h create mode 100644 QuakeEd/QuakeEd.iconheader create mode 100644 QuakeEd/QuakeEd.m create mode 100644 QuakeEd/QuakeEd_main.m create mode 100644 QuakeEd/README create mode 100644 QuakeEd/SetBrush.h create mode 100644 QuakeEd/SetBrush.m create mode 100644 QuakeEd/TexturePalette.h create mode 100644 QuakeEd/TexturePalette.m create mode 100644 QuakeEd/TextureView.h create mode 100644 QuakeEd/TextureView.m create mode 100644 QuakeEd/Things.h create mode 100644 QuakeEd/Things.m create mode 100644 QuakeEd/UpArrow.tiff create mode 100644 QuakeEd/UserPath.h create mode 100644 QuakeEd/UserPath.m create mode 100644 QuakeEd/XYView.h create mode 100644 QuakeEd/XYView.m create mode 100644 QuakeEd/ZScrollView.h create mode 100644 QuakeEd/ZScrollView.m create mode 100644 QuakeEd/ZView.h create mode 100644 QuakeEd/ZView.m create mode 100644 QuakeEd/cmdlib.c create mode 100644 QuakeEd/cmdlib.h create mode 100644 QuakeEd/help.txt create mode 100644 QuakeEd/i_90d.tiff create mode 100644 QuakeEd/i_add.tiff create mode 100644 QuakeEd/i_brushes.tiff create mode 100644 QuakeEd/i_fliph.tiff create mode 100644 QuakeEd/i_flipv.tiff create mode 100644 QuakeEd/i_quakeed.tiff create mode 100644 QuakeEd/i_sub.tiff create mode 100644 QuakeEd/inspectors.tiff create mode 100644 QuakeEd/jrbase1.map create mode 100644 QuakeEd/mainwindow.tiff create mode 100644 QuakeEd/mathlib.c create mode 100644 QuakeEd/mathlib.h create mode 100644 QuakeEd/misc.m create mode 100644 QuakeEd/qedefs.h create mode 100644 QuakeEd/quake.qpr create mode 100644 QuakeEd/readme.txt create mode 100644 QuakeEd/render.h create mode 100644 QuakeEd/render.m create mode 100644 QuakeEd/short.tiff create mode 100644 QuakeEd/tall.tiff create mode 100644 QuakeEd/triggers.qc diff --git a/QuakeEd/.nfs7072 b/QuakeEd/.nfs7072 new file mode 100644 index 0000000..579785b --- /dev/null +++ b/QuakeEd/.nfs7072 @@ -0,0 +1,33 @@ + +5/18/96 + +This is a dump of the current source code for QuakeEd, our map editing application. + +This does not include everything necessary to build maps. There are graphics files, prog files, and other utilities needed. I plan on releasing a full development set of tools after the game ships. This is just intended to help out anyone working on their own map editor. + +This is a NEXTSTEP application, so hardly anyone is going to be able to use the code as is. This is not an OPENSTEP application. It doesn't even use the foundation kit, so porting to gnustep or openstep-solaris/mach/nt would not be trivial. + +There are lots of mixed case and >8 character filenames, so I'm using unix gzip (compressed) format. + +Because most people won't have access to a NEXTSTEP machine, I took pictures of some of the more important stuff from interface builder: + +mainwindow.tiff : a screenshot of the primary window +inspectors.tiff : a screenshot of the important inspector views +help.txt : a dump of the (minimal) help inspector's contents. + +I included some sample data to help you follow the code: + +quake.qpr : our current project file +jrbase1.map : a sample map +triggers.qc : a sample qc source file that includes some /*QUAKED comments + +There will not be any major changes to this code base. I am eagerly looking forward to writing a brand new editor for windows NT + open GL as soon as Quake ships. + +This application was really not a very good fit for NEXTSTEP. The display postscript model fundamentally doesn't fit very well with what we need here -- if you run in an 8 bit color mode, the line drawing runs at an ok speed, but the texture view goes half the speed it should as it dithers from 24 bit color down to 8 bit. If you run in 24 bit color mode, you get less screen real estate and significantly slower line drawing as a 3 megabyte XY view is flushed. Sigh. If anyone does actually run this on NEXTSTEP be advised that you want a fast machine. I never had the time to properly optimize QuakeEd. + +The texture view rendering code in here is crap. Anyone coding a new editor is strongly advised to just use an available optimized library, like open GL or direct 3D. + + +John Carmack +Id Software +johnc@idsoftware.com diff --git a/QuakeEd/Brush.h b/QuakeEd/Brush.h new file mode 100644 index 0000000..94b449f --- /dev/null +++ b/QuakeEd/Brush.h @@ -0,0 +1,55 @@ +#import +#import "SetBrush.h" +#import "EditWindow.h" + +extern id brush_i; + +extern BOOL brushdraw; // YES when drawing cutbrushes and ents + +@interface Brush : SetBrush +{ + id cutbrushes_i; + id cutentities_i; + boolean updatemask[MAXBRUSHVERTEX]; + BOOL dontdraw; // for modal instance loops + BOOL deleted; // when not visible at all +} + +- init; + +- initFromSetBrush: br; + +- deselect; +- (BOOL)isSelected; + +- (BOOL)XYmouseDown: (NXPoint *)pt; // return YES if brush handled +- (BOOL)ZmouseDown: (NXPoint *)pt; // return YES if brush handled + +- _keyDown:(NXEvent *)theEvent; + +- (NXPoint)centerPoint; // for camera flyby mode + +- InstanceSize; +- XYDrawSelf; +- ZDrawSelf; +- CameraDrawSelf; + +- flipHorizontal: sender; +- flipVertical: sender; +- rotate90: sender; + +- makeTall: sender; +- makeShort: sender; +- makeWide: sender; +- makeNarrow: sender; + +- placeEntity: sender; + +- cut: sender; +- copy: sender; + +- addBrush; + +@end + + diff --git a/QuakeEd/CameraView.h b/QuakeEd/CameraView.h new file mode 100644 index 0000000..03ddc06 --- /dev/null +++ b/QuakeEd/CameraView.h @@ -0,0 +1,61 @@ +#import +#import "mathlib.h" +#import "SetBrush.h" + +extern id cameraview_i; + +extern byte renderlist[1024*1024*4]; + +void CameraMoveto(vec3_t p); +void CameraLineto(vec3_t p); + +extern BOOL timedrawing; + +@interface CameraView : View +{ + float xa, ya, za; + float move; + + float *zbuffer; + unsigned *imagebuffer; + + BOOL angleChange; // JR 6.8.95 + + vec3_t origin; + vec3_t matrix[3]; + + NXPoint dragspot; + + drawmode_t drawmode; + +// UI links + id mode_radio_i; + +} + +- setXYOrigin: (NXPoint *)pt; +- setZOrigin: (float)pt; + +- setOrigin: (vec3_t)org angle: (float)angle; +- getOrigin: (vec3_t)org; + +- (float)yawAngle; + +- matrixFromAngles; +- _keyDown: (NXEvent *)theEvent; + +- drawMode: sender; +- setDrawMode: (drawmode_t)mode; + +- homeView: sender; + +- XYDrawSelf; // for drawing viewpoint in XY view +- ZDrawSelf; // for drawing viewpoint in XY view +- (BOOL)XYmouseDown: (NXPoint *)pt flags:(int)flags; // return YES if brush handled +- (BOOL)ZmouseDown: (NXPoint *)pt flags:(int)flags; // return YES if brush handled + +- upFloor:sender; +- downFloor: sender; + +@end + diff --git a/QuakeEd/CameraView.m b/QuakeEd/CameraView.m new file mode 100644 index 0000000..9ee6771 --- /dev/null +++ b/QuakeEd/CameraView.m @@ -0,0 +1,970 @@ +#import "qedefs.h" + +id cameraview_i; + +BOOL timedrawing = 0; + +@implementation CameraView + +/* +================== +initFrame: +================== +*/ +- initFrame:(const NXRect *)frameRect +{ + int size; + + [super initFrame: frameRect]; + + cameraview_i = self; + + xa = ya = za = 0; + + [self matrixFromAngles]; + + origin[0] = 64; + origin[1] = 64; + origin[2] = 48; + + move = 16; + + size = bounds.size.width * bounds.size.height; + zbuffer = malloc (size*4); + imagebuffer = malloc (size*4); + + return self; +} + +- setXYOrigin: (NXPoint *)pt +{ + origin[0] = pt->x; + origin[1] = pt->y; + return self; +} + +- setZOrigin: (float)pt +{ + origin[2] = pt; + return self; +} + +- setOrigin: (vec3_t)org angle: (float)angle +{ + VectorCopy (org, origin); + ya = angle; + [self matrixFromAngles]; + return self; +} + +- getOrigin: (vec3_t)org +{ + VectorCopy (origin, org); + return self; +} + +- (float)yawAngle +{ + return ya; +} + +- upFloor:sender +{ + sb_floor_dir = 1; + sb_floor_dist = 99999; + [map_i makeAllPerform: @selector(feetToFloor)]; + if (sb_floor_dist == 99999) + { + qprintf ("already on top floor"); + return self; + } + qprintf ("up floor"); + origin[2] += sb_floor_dist; + [quakeed_i updateCamera]; + return self; +} + +- downFloor: sender +{ + sb_floor_dir = -1; + sb_floor_dist = -99999; + [map_i makeAllPerform: @selector(feetToFloor)]; + if (sb_floor_dist == -99999) + { + qprintf ("already on bottom floor"); + return self; + } + qprintf ("down floor"); + origin[2] += sb_floor_dist; + [quakeed_i updateCamera]; + return self; +} + +/* +=============================================================================== + +UI TARGETS + +=============================================================================== +*/ + +/* +============ +homeView +============ +*/ +- homeView: sender +{ + xa = za = 0; + + [self matrixFromAngles]; + + [quakeed_i updateAll]; + + qprintf ("homed view angle"); + + return self; +} + +- drawMode: sender +{ + drawmode = [sender selectedCol]; + [quakeed_i updateCamera]; + return self; +} + +- setDrawMode: (drawmode_t)mode +{ + drawmode = mode; + [mode_radio_i selectCellAt:0: mode]; + [quakeed_i updateCamera]; + return self; +} + +/* +=============================================================================== + +TRANSFORMATION METHODS + +=============================================================================== +*/ + +- matrixFromAngles +{ + if (xa > M_PI*0.4) + xa = M_PI*0.4; + if (xa < -M_PI*0.4) + xa = -M_PI*0.4; + +// vpn + matrix[2][0] = cos(xa)*cos(ya); + matrix[2][1] = cos(xa)*sin(ya); + matrix[2][2] = sin(xa); + +// vup + matrix[1][0] = cos(xa+M_PI/2)*cos(ya); + matrix[1][1] = cos(xa+M_PI/2)*sin(ya); + matrix[1][2] = sin(xa+M_PI/2); + +// vright + CrossProduct (matrix[2], matrix[1], matrix[0]); + + return self; +} + + +- inverseTransform: (vec_t *)invec to:(vec_t *)outvec +{ + vec3_t inverse[3]; + vec3_t temp; + int i,j; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<3 ; j++) + inverse[i][j] = matrix[j][i]; + + temp[0] = DotProduct(invec, inverse[0]); + temp[1] = DotProduct(invec, inverse[1]); + temp[2] = DotProduct(invec, inverse[2]); + + VectorAdd (temp, origin, outvec); + + return self; +} + + + +/* +=============================================================================== + + DRAWING METHODS + +=============================================================================== +*/ + +typedef struct +{ + vec3_t trans; + int clipflags; + vec3_t screen; // only valid if clipflags == 0 +} campt_t; +#define CLIP_RIGHT 1 +#define CLIP_LEFT 2 +#define CLIP_TOP 4 +#define CLIP_BOTTOM 8 +#define CLIP_FRONT 16 + +int cam_cur; +campt_t campts[2]; + +vec3_t r_matrix[3]; +vec3_t r_origin; +float mid_x, mid_y; +float topscale = (240.0/3)/160; +float bottomscale = (240.0*2/3)/160; + +extern plane_t frustum[5]; + +void MakeCampt (vec3_t in, campt_t *pt) +{ + vec3_t temp; + float scale; + +// transform the points + VectorSubtract (in, r_origin, temp); + + pt->trans[0] = DotProduct(temp, r_matrix[0]); + pt->trans[1] = DotProduct(temp, r_matrix[1]); + pt->trans[2] = DotProduct(temp, r_matrix[2]); + +// check clip flags + if (pt->trans[2] < 1) + pt->clipflags = CLIP_FRONT; + else + pt->clipflags = 0; + + if (pt->trans[0] > pt->trans[2]) + pt->clipflags |= CLIP_RIGHT; + else if (-pt->trans[0] > pt->trans[2]) + pt->clipflags |= CLIP_LEFT; + + if (pt->trans[1] > pt->trans[2]*topscale ) + pt->clipflags |= CLIP_TOP; + else if (-pt->trans[1] > pt->trans[2]*bottomscale ) + pt->clipflags |= CLIP_BOTTOM; + + if (pt->clipflags) + return; + +// project + scale = mid_x/pt->trans[2]; + pt->screen[0] = mid_x + pt->trans[0]*scale; + pt->screen[1] = mid_y + pt->trans[1]*scale; +} + + +void CameraMoveto(vec3_t p) +{ + campt_t *pt; + + if (upath->numberOfPoints > 2048) + lineflush (); + + pt = &campts[cam_cur]; + cam_cur ^= 1; + MakeCampt (p,pt); + if (!pt->clipflags) + { // onscreen, so move there immediately + UPmoveto (upath, pt->screen[0], pt->screen[1]); + } +} + +void ClipLine (vec3_t p1, vec3_t p2, int planenum) +{ + float d, d2, frac; + vec3_t new; + plane_t *pl; + float scale; + + if (planenum == 5) + { // draw it! + scale = mid_x/p1[2]; + new[0] = mid_x + p1[0]*scale; + new[1] = mid_y + p1[1]*scale; + UPmoveto (upath, new[0], new[1]); + + scale = mid_x/p2[2]; + new[0] = mid_x + p2[0]*scale; + new[1] = mid_y + p2[1]*scale; + UPlineto (upath, new[0], new[1]); + return; + } + + pl = &frustum[planenum]; + + d = DotProduct (p1, pl->normal) - pl->dist; + d2 = DotProduct (p2, pl->normal) - pl->dist; + if (d <= ON_EPSILON && d2 <= ON_EPSILON) + { // off screen + return; + } + + if (d >= 0 && d2 >= 0) + { // on front + ClipLine (p1, p2, planenum+1); + return; + } + + frac = d/(d-d2); + new[0] = p1[0] + frac*(p2[0]-p1[0]); + new[1] = p1[1] + frac*(p2[1]-p1[1]); + new[2] = p1[2] + frac*(p2[2]-p1[2]); + + if (d > 0) + ClipLine (p1, new, planenum+1); + else + ClipLine (new, p2, planenum+1); +} + +int c_off, c_on, c_clip; + +void CameraLineto(vec3_t p) +{ + campt_t *p1, *p2; + int bits; + + p2 = &campts[cam_cur]; + cam_cur ^= 1; + p1 = &campts[cam_cur]; + MakeCampt (p, p2); + + if (p1->clipflags & p2->clipflags) + { + c_off++; + return; // entirely off screen + } + + bits = p1->clipflags | p2->clipflags; + + if (! bits ) + { + c_on++; + UPmoveto (upath, p1->screen[0], p1->screen[1]); + UPlineto (upath, p2->screen[0], p2->screen[1]); + return; // entirely on screen + } + +// needs to be clipped + c_clip++; + + ClipLine (p1->trans, p2->trans, 0); +} + + +/* +============= +drawSolid +============= +*/ +- drawSolid +{ + unsigned char *planes[5]; + +// +// draw it +// + VectorCopy (origin, r_origin); + VectorCopy (matrix[0], r_matrix[0]); + VectorCopy (matrix[1], r_matrix[1]); + VectorCopy (matrix[2], r_matrix[2]); + + r_width = bounds.size.width; + r_height = bounds.size.height; + r_picbuffer = imagebuffer; + r_zbuffer = zbuffer; + + r_drawflat = (drawmode == dr_flat); + + REN_BeginCamera (); + REN_ClearBuffers (); + +// +// render the setbrushes +// + [map_i makeAllPerform: @selector(CameraRenderSelf)]; + +// +// display the output +// + [[self window] setBackingType:NX_RETAINED]; + + planes[0] = (unsigned char *)imagebuffer; + NXDrawBitmap( + &bounds, + r_width, + r_height, + 8, + 3, + 32, + r_width*4, + NO, + NO, + NX_RGBColorSpace, + planes + ); + + NXPing (); + [[self window] setBackingType:NX_BUFFERED]; + + + + return self; +} + + +/* +=================== +drawWire +=================== +*/ +- drawWire: (const NXRect *)rect +{ +// copy current info to globals for the C callbacks + mid_x = bounds.size.width / 2; + mid_y = 2 * bounds.size.height / 3; + + VectorCopy (origin, r_origin); + VectorCopy (matrix[0], r_matrix[0]); + VectorCopy (matrix[1], r_matrix[1]); + VectorCopy (matrix[2], r_matrix[2]); + + r_width = bounds.size.width; + r_height = bounds.size.height; + r_picbuffer = imagebuffer; + r_zbuffer = zbuffer; + + REN_BeginCamera (); + +// erase window + NXEraseRect (rect); + +// draw all entities + linestart (0,0,0); + [map_i makeUnselectedPerform: @selector(CameraDrawSelf)]; + lineflush (); + + return self; +} + +/* +=================== +drawSelf +=================== +*/ +- drawSelf:(const NXRect *)rects :(int)rectCount +{ + static float drawtime; // static to shut up compiler warning + + if (timedrawing) + drawtime = I_FloatTime (); + + if (drawmode == dr_texture || drawmode == dr_flat) + [self drawSolid]; + else + [self drawWire: rects]; + + if (timedrawing) + { + NXPing (); + drawtime = I_FloatTime() - drawtime; + printf ("CameraView drawtime: %5.3f\n", drawtime); + } + + return self; +} + + +/* +============= +XYDrawSelf +============= +*/ +- XYDrawSelf +{ + + PSsetrgbcolor (0,0,1.0); + PSsetlinewidth (0.15); + PSmoveto (origin[0]-16,origin[1]); + PSrlineto (16,8); + PSrlineto (16,-8); + PSrlineto (-16,-8); + PSrlineto (-16,8); + PSrlineto (32,0); + + PSmoveto (origin[0],origin[1]); + PSrlineto (64*cos(ya+M_PI/4), 64*sin(ya+M_PI/4)); + PSmoveto (origin[0],origin[1]); + PSrlineto (64*cos(ya-M_PI/4), 64*sin(ya-M_PI/4)); + + PSstroke (); + + return self; +} + +/* +============= +ZDrawSelf +============= +*/ +- ZDrawSelf +{ + PSsetrgbcolor (0,0,1.0); + PSsetlinewidth (0.15); + + PSmoveto (-16,origin[2]); + PSrlineto (16,8); + PSrlineto (16,-8); + PSrlineto (-16,-8); + PSrlineto (-16,8); + PSrlineto (32,0); + + PSmoveto (-15,origin[2]-47); + PSrlineto (29,0); + PSrlineto (0,54); + PSrlineto (-29,0); + PSrlineto (0,-54); + + PSstroke (); + + return self; +} + + +/* +=============================================================================== + + XYZ mouse view methods + +=============================================================================== +*/ + +/* +================ +modalMoveLoop +================ +*/ +- modalMoveLoop: (NXPoint *)basept :(vec3_t)movemod : converter +{ + vec3_t originbase; + NXEvent *event; + NXPoint newpt; +// NXPoint brushpt; + vec3_t delta; +// id ent; + int i; +// vec3_t temp; + + qprintf ("moving camera position"); + + VectorCopy (origin, originbase); + +// +// modal event loop using instance drawing +// + goto drawentry; + + while (event->type != NX_LMOUSEUP && event->type != NX_RMOUSEUP) + { + // + // calculate new point + // + newpt = event->location; + [converter convertPoint:&newpt fromView:NULL]; + + delta[0] = newpt.x-basept->x; + delta[1] = newpt.y-basept->y; + delta[2] = delta[1]; // height change + + for (i=0 ; i<3 ; i++) + origin[i] = originbase[i]+movemod[i]*delta[i]; + +#if 0 // FIXME + // + // if command is down, look towards brush or entity + // + if (event->flags & NX_SHIFTMASK) + { + ent = [quakemap_i selectedEntity]; + if (ent) + { + [ent origin: temp]; + brushpt.x = temp[0]; + brushpt.y = temp[1]; + } + else + brushpt = [brush_i centerPoint]; + ya = atan2 (brushpt.y - newpt.y, brushpt.x - newpt.x); + [self matrixFromAngles]; + } +#endif + +drawentry: + // + // instance draw new frame + // + [quakeed_i newinstance]; + [self display]; + + event = [NXApp getNextEvent: NX_LMOUSEUPMASK | NX_LMOUSEDRAGGEDMASK + | NX_RMOUSEUPMASK | NX_RMOUSEDRAGGEDMASK | NX_APPDEFINEDMASK]; + + if (event->type == NX_KEYDOWN) + { + [self _keyDown: event]; + [self display]; + goto drawentry; + } + + } + + return self; +} + +//============================================================================ + +/* +=============== +XYmouseDown +=============== +*/ +- (BOOL)XYmouseDown: (NXPoint *)pt flags:(int)flags // return YES if brush handled +{ + vec3_t movemod; + + if (fabs(pt->x - origin[0]) > 16 + || fabs(pt->y - origin[1]) > 16 ) + return NO; + +#if 0 + if (flags & NX_ALTERNATEMASK) + { // up / down drag + movemod[0] = 0; + movemod[1] = 0; + movemod[2] = 1; + } + else +#endif + { + movemod[0] = 1; + movemod[1] = 1; + movemod[2] = 0; + } + + [self modalMoveLoop: pt : movemod : xyview_i]; + + return YES; +} + + +/* +=============== +ZmouseDown +=============== +*/ +- (BOOL)ZmouseDown: (NXPoint *)pt flags:(int)flags // return YES if brush handled +{ + vec3_t movemod; + + if (fabs(pt->y - origin[2]) > 16 + || pt->x < -8 || pt->x > 8 ) + return NO; + + movemod[0] = 0; + movemod[1] = 0; + movemod[2] = 1; + + [self modalMoveLoop: pt : movemod : zview_i]; + + return YES; +} + + +//============================================================================= + +/* +=================== +viewDrag: +=================== +*/ +- viewDrag:(NXPoint *)pt +{ + float dx,dy; + NXEvent *event; + NXPoint newpt; + +// +// modal event loop using instance drawing +// + goto drawentry; + + while (event->type != NX_RMOUSEUP) + { + // + // calculate new point + // + newpt = event->location; + [self convertPoint:&newpt fromView:NULL]; + + dx = newpt.x - pt->x; + dy = newpt.y - pt->y; + *pt = newpt; + + ya -= dx/bounds.size.width*M_PI/2 * 4; + xa += dy/bounds.size.width*M_PI/2 * 4; + + [self matrixFromAngles]; + +drawentry: + [quakeed_i newinstance]; + [self display]; + + event = [NXApp getNextEvent: + NX_KEYDOWNMASK | NX_RMOUSEUPMASK | NX_RMOUSEDRAGGEDMASK]; + + if (event->type == NX_KEYDOWN) + { + [self _keyDown: event]; + [self display]; + goto drawentry; + } + + } + + return self; +} + + +//============================================================================= + +/* +=================== +mouseDown +=================== +*/ +- mouseDown:(NXEvent *)theEvent +{ + NXPoint pt; + int i; + vec3_t p1, p2; + float forward, right, up; + int flags; + + pt = theEvent->location; + + [self convertPoint:&pt fromView:NULL]; + + VectorCopy (origin, p1); + forward = 160; + right = pt.x - 160; + up = pt.y - 240*2/3; + for (i=0 ; i<3 ; i++) + p2[i] = forward*matrix[2][i] + up*matrix[1][i] + right*matrix[0][i]; + for (i=0 ; i<3 ; i++) + p2[i] = p1[i] + 100*p2[i]; + + flags = theEvent->flags & (NX_SHIFTMASK | NX_CONTROLMASK | NX_ALTERNATEMASK | NX_COMMANDMASK); + +// +// bare click to select a texture +// + if (flags == 0) + { + [map_i getTextureRay: p1 : p2]; + return self; + } + +// +// shift click to select / deselect a brush from the world +// + if (flags == NX_SHIFTMASK) + { + [map_i selectRay: p1 : p2 : NO]; + return self; + } + + +// +// cmd-shift click to set a target/targetname entity connection +// + if (flags == (NX_SHIFTMASK|NX_COMMANDMASK) ) + { + [map_i entityConnect: p1 : p2]; + return self; + } + +// +// alt click = set entire brush texture +// + if (flags == NX_ALTERNATEMASK) + { + if (drawmode != dr_texture) + { + qprintf ("No texture setting except in texture mode!\n"); + NopSound (); + return self; + } + [map_i setTextureRay: p1 : p2 : YES]; + [quakeed_i updateAll]; + return self; + } + +// +// ctrl-alt click = set single face texture +// + if (flags == (NX_CONTROLMASK | NX_ALTERNATEMASK) ) + { + if (drawmode != dr_texture) + { + qprintf ("No texture setting except in texture mode!\n"); + NopSound (); + return self; + } + [map_i setTextureRay: p1 : p2 : NO]; + [quakeed_i updateAll]; + return self; + } + + + qprintf ("bad flags for click"); + NopSound (); + + return self; +} + +/* +=================== +rightMouseDown +=================== +*/ +- rightMouseDown:(NXEvent *)theEvent +{ + NXPoint pt; + int flags; + + pt = theEvent->location; + + [self convertPoint:&pt fromView:NULL]; + + flags = theEvent->flags & (NX_SHIFTMASK | NX_CONTROLMASK | NX_ALTERNATEMASK | NX_COMMANDMASK); + +// +// click = drag camera +// + if (flags == 0) + { + qprintf ("looking"); + [self viewDrag: &pt]; + qprintf (""); + return self; + } + + qprintf ("bad flags for click"); + NopSound (); + + return self; +} + +/* +=============== +keyDown +=============== +*/ + +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf + + +- _keyDown: (NXEvent *)theEvent +{ + int ch; + + ch = tolower(theEvent->data.key.charCode); + + switch (ch) + { + case 13: + return self; + + case 'a': + case 'A': + xa += M_PI/8; + [self matrixFromAngles]; + [quakeed_i updateCamera]; + return self; + + case 'z': + case 'Z': + xa -= M_PI/8; + [self matrixFromAngles]; + [quakeed_i updateCamera]; + return self; + + case KEY_RIGHTARROW: + ya -= M_PI*move/(64*2); + [self matrixFromAngles]; + [quakeed_i updateCamera]; + break; + + case KEY_LEFTARROW: + ya += M_PI*move/(64*2); + [self matrixFromAngles]; + [quakeed_i updateCamera]; + break; + + case KEY_UPARROW: + origin[0] += move*cos(ya); + origin[1] += move*sin(ya); + [quakeed_i updateCamera]; + break; + + case KEY_DOWNARROW: + origin[0] -= move*cos(ya); + origin[1] -= move*sin(ya); + [quakeed_i updateCamera]; + break; + + case '.': + origin[0] += move*cos(ya-M_PI_2); + origin[1] += move*sin(ya-M_PI_2); + [quakeed_i updateCamera]; + break; + + case ',': + origin[0] -= move*cos(ya-M_PI_2); + origin[1] -= move*sin(ya-M_PI_2); + [quakeed_i updateCamera]; + break; + + case 'd': + case 'D': + origin[2] += move; + [quakeed_i updateCamera]; + break; + + case 'c': + case 'C': + origin[2] -= move; + [quakeed_i updateCamera]; + break; + + } + + + return self; +} + + +@end + diff --git a/QuakeEd/Clipper.h b/QuakeEd/Clipper.h new file mode 100644 index 0000000..7756d2d --- /dev/null +++ b/QuakeEd/Clipper.h @@ -0,0 +1,24 @@ + +extern id clipper_i; + +@interface Clipper : Object +{ + int num; + vec3_t pos[3]; + plane_t plane; +} + +- (BOOL)hide; +- XYClick: (NXPoint)pt; +- (BOOL)XYDrag: (NXPoint *)pt; +- ZClick: (NXPoint)pt; +- carve; +- flipNormal; +- (BOOL)getFace: (face_t *)pl; + +- cameraDrawSelf; +- XYDrawSelf; +- ZDrawSelf; + +@end + diff --git a/QuakeEd/Clipper.m b/QuakeEd/Clipper.m new file mode 100644 index 0000000..f6ba911 --- /dev/null +++ b/QuakeEd/Clipper.m @@ -0,0 +1,227 @@ + +#include "qedefs.h" + +id clipper_i; + +@implementation Clipper + +- init +{ + [super init]; + clipper_i = self; + return self; +} + +- (BOOL)hide +{ + int oldnum; + + oldnum = num; + num = 0; + return (oldnum > 0); +} + +- flipNormal +{ + vec3_t temp; + + if (num == 2) + { + VectorCopy (pos[0], temp); + VectorCopy (pos[1], pos[0]); + VectorCopy (temp, pos[1]); + } + else if (num == 3) + { + VectorCopy (pos[0], temp); + VectorCopy (pos[2], pos[0]); + VectorCopy (temp, pos[2]); + } + else + { + qprintf ("no clipplane"); + NXBeep (); + } + + return self; +} + +- (BOOL)getFace: (face_t *)f +{ + vec3_t v1, v2, norm; + int i; + + VectorCopy (vec3_origin, plane.normal); + plane.dist = 0; + if (num < 2) + return NO; + if (num == 2) + { + VectorCopy (pos[0], pos[2]); + pos[2][2] += 16; + } + + for (i=0 ; i<3 ; i++) + VectorCopy (pos[i], f->planepts[i]); + + VectorSubtract (pos[2], pos[0], v1); + VectorSubtract (pos[1], pos[0], v2); + + CrossProduct (v1, v2, norm); + VectorNormalize (norm); + + if ( !norm[0] && !norm[1] && !norm[2] ) + return NO; + + [texturepalette_i getTextureDef: &f->texture]; + + return YES; +} + +/* +================ +XYClick +================ +*/ +- XYClick: (NXPoint)pt +{ + int i; + vec3_t new; + + new[0] = [xyview_i snapToGrid: pt.x]; + new[1] = [xyview_i snapToGrid: pt.y]; + new[2] = [map_i currentMinZ]; + +// see if a point is allready there + for (i=0 ; ix - pos[i][0] > 10) || fabs(pt->y - pos[i][1] > 10) ) + continue; + // drag this point + + } + + return NO; +} + +- ZClick: (NXPoint)pt +{ + return self; +} + +//============================================================================= + +- carve +{ + [map_i makeSelectedPerform: @selector(carveByClipper)]; + num = 0; + return self; +} + + +- cameraDrawSelf +{ + vec3_t mid; + int i; + + linecolor (1,0.5,0); + + for (i=0 ; i + +typedef struct +{ + char *key; + char *value; +} dict_t; + +@interface Dict:Storage +{ +} + +- initFromFile:(FILE *)fp; + +- (id) parseMultipleFrom:(char *)value; +- (int) getValueUnits:(char *)key; +- delString:(char *)string fromValue:(char *)key; +- addString:(char *)string toValue:(char *)key; +- (char *)convertListToString:(id)list; +- (char *)getStringFor:(char *)name; +- removeKeyword:(char *)key; +- (unsigned int)getValueFor:(char *)name; +- changeStringFor:(char *)key to:(char *)value; +- (dict_t *) findKeyword:(char *)key; + +- writeBlockTo:(FILE *)fp; +- writeFile:(char *)path; + +// INTERNAL +- init; +- (id) parseBraceBlock:(FILE *)fp; +- setupMultiple:(char *)value; +- (char *)getNextParameter; + +@end + +int GetNextChar(FILE *fp); +void CopyUntilWhitespc(FILE *fp,char *buffer); +void CopyUntilQuote(FILE *fp,char *buffer); +int FindBrace(FILE *fp); +int FindQuote(FILE *fp); +int FindWhitespc(FILE *fp); +int FindNonwhitespc(FILE *fp); + +char *FindWhitespcInBuffer(char *buffer); +char *FindNonwhitespcInBuffer(char *buffer); diff --git a/QuakeEd/Dict.m b/QuakeEd/Dict.m new file mode 100644 index 0000000..546d617 --- /dev/null +++ b/QuakeEd/Dict.m @@ -0,0 +1,583 @@ + +#import "qedefs.h" + +@implementation Dict + +- init +{ + [super initCount:0 + elementSize:sizeof(dict_t) + description:NULL]; + return self; +} + +- print +{ + int i; + dict_t *d; + + for (i=0 ; ikey, d->value); + } + return self; +} + +/* +=========== +copyFromZone + +JDC +=========== +*/ +- copyFromZone:(NXZone *)zone +{ + id new; + int i; + dict_t *d; + char *old; + + new = [super copyFromZone: zone]; + for (i=0 ; ikey; + d->key = malloc(strlen(old)+1); + strcpy (d->key, old); + + old = d->value; + d->value = malloc(strlen(old)+1); + strcpy (d->value, old); + } + + return new; +} + +- initFromFile:(FILE *)fp +{ + [self init]; + return [self parseBraceBlock:fp]; +} + +//=============================================== +// +// Dictionary pair functions +// +//=============================================== + +// +// Write a { } block out to a FILE* +// +- writeBlockTo:(FILE *)fp +{ + int max; + int i; + dict_t *d; + + fprintf(fp,"{\n"); + max = [super count]; + for (i = 0;i < max;i++) + { + d = [super elementAt:i]; + fprintf(fp,"\t{\"%s\"\t\"%s\"}\n",d->key,d->value); + } + fprintf(fp,"}\n"); + + return self; +} + +// +// Write a single { } block out +// +- writeFile:(char *)path +{ + FILE *fp; + + fp = fopen(path,"w+t"); + if (fp != NULL) + { + printf("Writing dictionary file %s.\n",path); + fprintf(fp,"// QE_Project file %s\n",path); + [self writeBlockTo:fp]; + fclose(fp); + } + else + { + printf("Error writing %s!\n",path); + return NULL; + } + + return self; +} + +//=============================================== +// +// Utility methods +// +//=============================================== + +// +// Find a keyword in storage +// Returns * to dict_t, otherwise NULL +// +- (dict_t *) findKeyword:(char *)key +{ + int max; + int i; + dict_t *d; + + max = [super count]; + for (i = 0;i < max;i++) + { + d = [super elementAt:i]; + if (!strcmp(d->key,key)) + return d; + } + + return NULL; +} + +// +// Change a keyword's string +// +- changeStringFor:(char *)key to:(char *)value +{ + dict_t *d; + dict_t newd; + + d = [self findKeyword:key]; + if (d != NULL) + { + free(d->value); + d->value = malloc(strlen(value)+1); + strcpy(d->value,value); + } + else + { + newd.key = malloc(strlen(key)+1); + strcpy(newd.key,key); + newd.value = malloc(strlen(value)+1); + strcpy(newd.value,value); + [self addElement:&newd]; + } + return self; +} + +// +// Search for keyword, return the string * +// +- (char *)getStringFor:(char *)name +{ + dict_t *d; + + d = [self findKeyword:name]; + if (d != NULL) + return d->value; + + return ""; +} + +// +// Search for keyword, return the value +// +- (unsigned int)getValueFor:(char *)name +{ + dict_t *d; + + d = [self findKeyword:name]; + if (d != NULL) + return atol(d->value); + + return 0; +} + +// +// Return # of units in keyword's value +// +- (int) getValueUnits:(char *)key +{ + id temp; + int count; + + temp = [self parseMultipleFrom:key]; + count = [temp count]; + [temp free]; + + return count; +} + +// +// Convert List to string +// +- (char *)convertListToString:(id)list +{ + int i; + int max; + char tempstr[4096]; + char *s; + char *newstr; + + max = [list count]; + tempstr[0] = 0; + for (i = 0;i < max;i++) + { + s = [list elementAt:i]; + strcat(tempstr,s); + strcat(tempstr," "); + } + newstr = malloc(strlen(tempstr)+1); + strcpy(newstr,tempstr); + + return newstr; +} + +// +// JDC: I wrote this to simplify removing vectors +// +- removeKeyword:(char *)key +{ + dict_t *d; + + d = [self findKeyword:key]; + if (d == NULL) + return self; + [self removeElementAt:d - (dict_t*)dataPtr]; + return self; +} + +// +// Delete string from keyword's value +// +- delString:(char *)string fromValue:(char *)key +{ + id temp; + int count; + int i; + char *s; + dict_t *d; + + d = [self findKeyword:key]; + if (d == NULL) + return NULL; + temp = [self parseMultipleFrom:key]; + count = [temp count]; + for (i = 0;i < count;i++) + { + s = [temp elementAt:i]; + if (!strcmp(s,string)) + { + [temp removeElementAt:i]; + free(d->value); + d->value = [self convertListToString:temp]; + [temp free]; + + break; + } + } + return self; +} + +// +// Add string to keyword's value +// +- addString:(char *)string toValue:(char *)key +{ + char *newstr; + char spacing[] = "\t"; + dict_t *d; + + d = [self findKeyword:key]; + if (d == NULL) + return NULL; + newstr = malloc(strlen(string) + strlen(d->value) + strlen(spacing) + 1); + strcpy(newstr,d->value); + strcat(newstr,spacing); + strcat(newstr,string); + free(d->value); + d->value = newstr; + + return self; +} + +//=============================================== +// +// Use these for multiple parameters in a keyword value +// +//=============================================== +char *searchStr; +char item[4096]; + +- setupMultiple:(char *)value +{ + searchStr = value; + return self; +} + +- (char *)getNextParameter +{ + char *s; + + if (!searchStr) + return NULL; + strcpy(item,searchStr); + s = FindWhitespcInBuffer(item); + if (!*s) + searchStr = NULL; + else + { + *s = 0; + searchStr = FindNonwhitespcInBuffer(s+1); + } + return item; +} + +// +// Parses a keyvalue string & returns a Storage full of those items +// +- (id) parseMultipleFrom:(char *)key +{ + #define ITEMSIZE 128 + id stuff; + char string[ITEMSIZE]; + char *s; + + s = [self getStringFor:key]; + if (s == NULL) + return NULL; + + stuff = [[Storage alloc] + initCount:0 + elementSize:ITEMSIZE + description:NULL]; + + [self setupMultiple:s]; + while((s = [self getNextParameter])) + { + bzero(string,ITEMSIZE); + strcpy(string,s); + [stuff addElement:string]; + } + + return stuff; +} + +//=============================================== +// +// Dictionary pair parsing +// +//=============================================== + +// +// parse all keyword/value pairs within { } 's +// +- (id) parseBraceBlock:(FILE *)fp +{ + int c; + dict_t pair; + char string[1024]; + + c = FindBrace(fp); + if (c == -1) + return NULL; + + while((c = FindBrace(fp)) != '}') + { + if (c == -1) + return NULL; +// c = FindNonwhitespc(fp); +// if (c == -1) +// return NULL; +// CopyUntilWhitespc(fp,string); + +// JDC: fixed to allow quoted keys + c = FindNonwhitespc(fp); + if (c == -1) + return NULL; + c = fgetc(fp); + if ( c == '\"') + CopyUntilQuote(fp,string); + else + { + ungetc (c,fp); + CopyUntilWhitespc(fp,string); + } + + pair.key = malloc(strlen(string)+1); + strcpy(pair.key,string); + + c = FindQuote(fp); + CopyUntilQuote(fp,string); + pair.value = malloc(strlen(string)+1); + strcpy(pair.value,string); + + [super addElement:&pair]; + c = FindBrace(fp); + } + + return self; +} + +@end + +//=============================================== +// +// C routines for string parsing +// +//=============================================== +int GetNextChar(FILE *fp) +{ + int c; + int c2; + + c = getc(fp); + if (c == EOF) + return -1; + if (c == '/') // parse comments + { + c2 = getc(fp); + if (c2 == '/') + { + while((c2 = getc(fp)) != '\n'); + c = getc(fp); + } + else + ungetc(c2,fp); + } + return c; +} + +void CopyUntilWhitespc(FILE *fp,char *buffer) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return; + if (c <= ' ') + { + *buffer = 0; + return; + } + *buffer++ = c; + } +} + +void CopyUntilQuote(FILE *fp,char *buffer) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return; + if (c == '\"') + { + *buffer = 0; + return; + } + *buffer++ = c; + } +} + +int FindBrace(FILE *fp) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return -1; + if (c == '{' || + c == '}') + return c; + } + return -1; +} + +int FindQuote(FILE *fp) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return -1; + if (c == '\"') + return c; + } + return -1; +} + +int FindWhitespc(FILE *fp) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return -1; + if (c <= ' ') + { + ungetc(c,fp); + return c; + } + } + return -1; +} + +int FindNonwhitespc(FILE *fp) +{ + int count = 800; + int c; + + while(count--) + { + c = GetNextChar(fp); + if (c == EOF) + return -1; + if (c > ' ') + { + ungetc(c,fp); + return c; + } + } + return -1; +} + +char *FindWhitespcInBuffer(char *buffer) +{ + int count = 1000; + char *b = buffer; + + while(count--) + if (*b <= ' ') + return b; + else + b++; + return NULL; +} + +char *FindNonwhitespcInBuffer(char *buffer) +{ + int count = 1000; + char *b = buffer; + + while(count--) + if (*b > ' ') + return b; + else + b++; + return NULL; +} diff --git a/QuakeEd/DictList.h b/QuakeEd/DictList.h new file mode 100644 index 0000000..947ba60 --- /dev/null +++ b/QuakeEd/DictList.h @@ -0,0 +1,12 @@ + +#import + +@interface DictList:List +{ +} + +- initListFromFile:(FILE *)fp; +- writeListFile:(char *)filename; +- (id) findDictKeyword:(char *)key; + +@end diff --git a/QuakeEd/DictList.m b/QuakeEd/DictList.m new file mode 100644 index 0000000..0d24b35 --- /dev/null +++ b/QuakeEd/DictList.m @@ -0,0 +1,69 @@ + +#import "qedefs.h" + +@implementation DictList + +// +// Read in variable # of objects from FILE * +// +- initListFromFile:(FILE *)fp +{ + id d; + + [super init]; + do + { + d = [(Dict *)[Dict alloc] initFromFile:fp]; + if (d != NULL) + [self addObject:d]; + } while(d != NULL); + [d free]; + + return self; +} + +// +// Write out list file +// +- writeListFile:(char *)filename +{ + FILE *fp; + int i; + id obj; + + fp = fopen(filename,"w+t"); + if (fp == NULL) + return NULL; + + fprintf(fp,"// Object List written by QuakeEd\n"); + + for (i = 0;i < maxElements;i++) + { + obj = [self objectAt:i]; + [obj writeBlockTo:fp]; + } + fclose(fp); + + return self; +} + +// +// Find the keyword in all the Dict objects +// +- (id) findDictKeyword:(char *)key +{ + int i; + dict_t *d; + id dict; + + for (i = 0;i < maxElements;i++) + { + dict = [self objectAt:i]; + d = [(Dict *)dict findKeyword:key]; + if (d != NULL) + return dict; + } + return NULL; +} + +@end diff --git a/QuakeEd/DownArrow.tiff b/QuakeEd/DownArrow.tiff new file mode 100644 index 0000000000000000000000000000000000000000..cd0ce1418eeb79375f152bcb13577cce0c2817a6 GIT binary patch literal 256 zcmebEWzb?^VDMMcJsBPY literal 0 HcmV?d00001 diff --git a/QuakeEd/English.lproj/Info.nib/data.classes b/QuakeEd/English.lproj/Info.nib/data.classes new file mode 100644 index 0000000..b0bfacf --- /dev/null +++ b/QuakeEd/English.lproj/Info.nib/data.classes @@ -0,0 +1 @@ +FirstResponder = {ACTIONS = {}; SUPERCLASS = Object; }; diff --git a/QuakeEd/English.lproj/Info.nib/data.nib b/QuakeEd/English.lproj/Info.nib/data.nib new file mode 100644 index 0000000000000000000000000000000000000000..4f8b796db58e536d0baa7894fea9fd472822eb76 GIT binary patch literal 1000 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 + { // don't copy target and targetname fields + if (strncmp(e->key,"target",6)) + [new setKey: e->key toValue: e->value]; + } + + for (i=0 ; inext; + free (e); + } + return [super free]; +} + +- (BOOL)modifiable +{ + return modifiable; +} + +- setModifiable: (BOOL)m +{ + modifiable = m; + return self; +} + +- removeObject: o +{ + o = [super removeObject: o]; + if (numElements) + return o; +// the entity is empty, so remove the entire thing + if ( self == [map_i objectAt: 0]) + return o; // never remove the world + + [map_i removeObject: self]; + [self free]; + + return o; +} + + +- (char *)valueForQKey: (char *)k +{ + epair_t *e; + static char ret[64]; + + for (e=epairs ; e ; e=e->next) + if (!strcmp(k,e->key)) + { + strcpy (ret, e->value); + return ret; + } + return ""; +} + +- getVector: (vec3_t)v forKey: (char *)k +{ + char *c; + + c = [self valueForQKey: k]; + + v[0] = v[1] = v[2] = 0; + + sscanf (c, "%f %f %f", &v[0], &v[1], &v[2]); + + return self; +} + +- print +{ + epair_t *e; + + for (e=epairs ; e ; e=e->next) + printf ("%20s : %20s\n",e->key, e->value); + + return self; +} + +- setKey:(char *)k toValue:(char *)v +{ + epair_t *e; + + if (strlen(k) > MAX_KEY) + Error ("setKey: %s > MAX_KEY", k); + if (strlen(v) > MAX_VALUE) + Error ("setKey: %s > MAX_VALUE", v); + + while (*k && *k <= ' ') + k++; + if (!*k) + return self; // don't set NULL values + + for (e=epairs ; e ; e=e->next) + if (!strcmp(k,e->key)) + { + memset (e->value, 0, sizeof(e->value)); + strcpy (e->value, v); + return self; + } + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + strcpy (e->key, k); + strcpy (e->value, v); + e->next = epairs; + epairs = e; + + return self; +} + +- (int)numPairs +{ + int i; + epair_t *e; + + i=0; + for (e=epairs ; e ; e=e->next) + i++; + return i; +} + +- (epair_t *)epairs +{ + return epairs; +} + +- removeKeyPair: (char *)key +{ + epair_t *e, *e2; + + if (!epairs) + return self; + e = epairs; + if (!strcmp(e->key, key)) + { + epairs = e->next; + free (e); + return self; + } + + for (; e ; e=e->next) + { + if (e->next && !strcmp(e->next->key, key)) + { + e2 = e->next; + e->next = e2->next; + free (e2); + return self; + } + } + + printf ("WARNING: removeKeyPair: %s not found\n", key); + return self; +} + + +/* +============= +targetname + +If the entity does not have a "targetname" key, a unique one is generated +============= +*/ +- (char *)targetname +{ + char *t; + int i, count; + id ent; + int tval, maxt; + char name[20]; + + t = [self valueForQKey: "targetname"]; + if (t && t[0]) + return t; + +// make a unique name of the form t + count = [map_i count]; + maxt = 0; + for (i=1 ; i maxt) + maxt = tval; + } + + sprintf (name,"t%i",maxt+1); + + [self setKey: "targetname" toValue: name]; + + return [self valueForQKey: "targetname"]; // so it's not on the stack +} + +/* +============================================================================== + +FILE METHODS + +============================================================================== +*/ + +int nument; + +- initFromTokens +{ + char key[MAXTOKEN]; + id eclass, brush; + char *spawn; + vec3_t emins, emaxs; + vec3_t org; + texturedef_t td; + esize_t esize; + int i, c; + float *color; + + [self init]; + + if (!GetToken (true)) + { + [self free]; + return nil; + } + + if (strcmp (token, "{") ) + Error ("initFromFileP: { not found"); + + do + { + if (!GetToken (true)) + break; + if (!strcmp (token, "}") ) + break; + if (!strcmp (token, "{") ) + { // read a brush + brush = [[SetBrush alloc] initFromTokens: self]; + [self addObject: brush]; + } + else + { // read a key / value pair + strcpy (key, token); + GetToken (false); + [self setKey: key toValue:token]; + } + } while (1); + + nument++; + +// get class + spawn = [self valueForQKey: "classname"]; + eclass = [entity_classes_i classForName: spawn]; + + esize = [eclass esize]; + + [self getVector: org forKey: "origin"]; + + if ([self count] && esize != esize_model) + { + printf ("WARNING:Entity with brushes and wrong model type\n"); + [self empty]; + } + + if (![self count] && esize == esize_model) + { + printf ("WARNING:Entity with no brushes and esize_model\n"); + [texturepalette_i getTextureDef: &td]; + for (i=0 ; i<3 ; i++) + { + emins[i] = org[i] - 8; + emaxs[i] = org[i] + 8; + } + brush = [[SetBrush alloc] initOwner: self mins:emins maxs:emaxs + texture: &td]; + [self addObject: brush]; + } + +// create a brush if needed + if (esize == esize_fixed) + [self createFixedBrush: org]; + else + modifiable = YES; + +// set all the brush colors + color = [eclass drawColor]; + + c = [self count]; + for (i=0 ; inext) + fprintf (f,"\"%s\"\t\"%s\"\n", e->key, e->value); + +// fixed size entities don't save out brushes + if ( modifiable ) + { + for (i=0 ; i +#import "mathlib.h" + +typedef enum {esize_model, esize_fixed} esize_t; + +#define MAX_FLAGS 8 + +@interface EntityClass : Object +{ + char *name; + esize_t esize; + vec3_t mins, maxs; + vec3_t color; + char *comments; + char flagnames[MAX_FLAGS][32]; +} + +- initFromText: (char *)text; +- (char *)classname; +- (esize_t)esize; +- (float *)mins; // only for esize_fixed +- (float *)maxs; // only for esize_fixed +- (float *)drawColor; +- (char *)comments; +- (char *)flagName: (unsigned)flagnum; + +@end + +extern id entity_classes_i; + +@interface EntityClassList : List +{ + id nullclass; + char *source_path; +} + +- initForSourceDirectory: (char *)path; +- (id)classForName: (char *)name; +- (void)scanDirectory; + +@end + diff --git a/QuakeEd/EntityClass.m b/QuakeEd/EntityClass.m new file mode 100644 index 0000000..4229cb0 --- /dev/null +++ b/QuakeEd/EntityClass.m @@ -0,0 +1,266 @@ + +#import "qedefs.h" + +@implementation EntityClass + +/* + +the classname, color triple, and bounding box are parsed out of comments +A ? size means take the exact brush size. + +/*QUAKED (0 0 0) ? +/*QUAKED (0 0 0) (-8 -8 -8) (8 8 8) + +Flag names can follow the size description: + +/*QUAKED func_door (0 .5 .8) ? START_OPEN STONE_SOUND DOOR_DONT_LINK GOLD_KEY SILVER_KEY + +*/ +char *debugname; +- initFromText: (char *)text +{ + char *t; + int len; + int r, i; + char parms[256], *p; + + [super init]; + + text += strlen("/*QUAKED "); + +// grab the name + text = COM_Parse (text); + name = malloc (strlen(com_token)+1); + strcpy (name, com_token); + debugname = name; + +// grab the color + r = sscanf (text," (%f %f %f)", &color[0], &color[1], &color[2]); + if (r != 3) + return NULL; + + while (*text != ')') + { + if (!*text) + return NULL; + text++; + } + text++; + +// get the size + text = COM_Parse (text); + if (com_token[0] == '(') + { // parse the size as two vectors + esize = esize_fixed; + r = sscanf (text,"%f %f %f) (%f %f %f)", &mins[0], &mins[1], &mins[2], &maxs[0], &maxs[1], &maxs[2]); + if (r != 6) + return NULL; + + for (i=0 ; i<2 ; i++) + { + while (*text != ')') + { + if (!*text) + return NULL; + text++; + } + text++; + } + } + else + { // use the brushes + esize = esize_model; + } + +// get the flags + + +// copy to the first /n + p = parms; + while (*text && *text != '\n') + *p++ = *text++; + *p = 0; + text++; + +// any remaining words are parm flags + p = parms; + for (i=0 ; i<8 ; i++) + { + p = COM_Parse (p); + if (!p) + break; + strcpy (flagnames[i], com_token); + } + +// find the length until close comment + for (t=text ; t[0] && !(t[0]=='*' && t[1]=='/') ; t++) + ; + +// copy the comment block out + len = t-text; + comments = malloc (len+1); + memcpy (comments, text, len); + comments[len] = 0; + + return self; +} + +- (esize_t)esize +{ + return esize; +} + +- (char *)classname +{ + return name; +} + +- (float *)mins +{ + return mins; +} + +- (float *)maxs +{ + return maxs; +} + +- (float *)drawColor +{ + return color; +} + +- (char *)comments +{ + return comments; +} + + +- (char *)flagName: (unsigned)flagnum +{ + if (flagnum >= MAX_FLAGS) + Error ("EntityClass flagName: bad number"); + return flagnames[flagnum]; +} + +@end + +//=========================================================================== + +@implementation EntityClassList + +/* +================= +insertEC: +================= +*/ +- (void)insertEC: ec +{ + char *name; + int i; + + name = [ec classname]; + for (i=0 ; id_namlen <= 3) + continue; + if (!strcmp (ent->d_name+ent->d_namlen-3,".qc")) + [self scanFile: ent->d_name]; + } +} + + +id entity_classes_i; + + +- initForSourceDirectory: (char *)path +{ + [super init]; + + source_path = path; + [self scanDirectory]; + + entity_classes_i = self; + + nullclass = [[EntityClass alloc] initFromText: +"/*QUAKED UNKNOWN_CLASS (0 0.5 0) ?"]; + + return self; +} + +- (id)classForName: (char *)name +{ + int i; + id o; + + for (i=0 ; i + +#define MINIWINICON "DoomEdIcon" + +typedef enum +{ + i_project, + i_textures, + i_things, + i_prefs, + i_settings, + i_output, + i_help, + i_end +} insp_e; + +extern id inspcontrol_i; + +@interface InspectorControl:Object +{ + id inspectorView_i; // inspector view + id inspectorSubview_i; // inspector view's current subview (gets replaced) + + id contentList; // List of contentviews (corresponds to + // insp_e enum order) + id windowList; // List of Windows (corresponds to + // insp_e enum order) + + id obj_textures_i; // TexturePalette object (for delegating) + id obj_genkeypair_i; // GenKeyPair object + + id popUpButton_i; // PopUpList title button + id popUpMatrix_i; // PopUpList matrix + id itemList; // List of popUp buttons + + insp_e currentInspectorType; // keep track of current inspector + // + // Add id's here for new inspectors + // **NOTE: Make sure PopUpList has correct TAG value that + // corresponds to the enums above! + + // Windows + id win_project_i; // project + id win_textures_i; // textures + id win_things_i; // things + id win_prefs_i; // preferences + id win_settings_i; // project settings + id win_output_i; // bsp output + id win_help_i; // documentation + + // PopUpList objs + id itemProject_i; // project + id itemTextures_i; // textures + id itemThings_i; // things + id itemPrefs_i; // preferences + id itemSettings_i; // project settings + id itemOutput_i; // bsp output + id itemHelp_i; // docs +} + +- awakeFromNib; +- changeInspector:sender; +- changeInspectorTo:(insp_e)which; +- (insp_e)getCurrentInspector; + +@end + +@protocol InspectorControl +- windowResized; +@end diff --git a/QuakeEd/InspectorControl.m b/QuakeEd/InspectorControl.m new file mode 100644 index 0000000..fcc8dd9 --- /dev/null +++ b/QuakeEd/InspectorControl.m @@ -0,0 +1,128 @@ + +#import "qedefs.h" + +// Add .h-files here for new inspectors +#import "Things.h" +#import "TexturePalette.h" +#import "Preferences.h" + +id inspcontrol_i; + +@implementation InspectorControl + +- awakeFromNib +{ + inspcontrol_i = self; + + currentInspectorType = -1; + + contentList = [[List alloc] init]; + windowList = [[List alloc] init]; + itemList = [[List alloc] init]; + + // ADD NEW INSPECTORS HERE... + + [windowList addObject:win_project_i]; + [contentList addObject:[win_project_i contentView]]; + [itemProject_i setKeyEquivalent:'1']; + [itemList addObject:itemProject_i]; + + [windowList addObject:win_textures_i]; + [contentList addObject:[win_textures_i contentView]]; + [itemTextures_i setKeyEquivalent:'2']; + [itemList addObject:itemTextures_i]; + + [windowList addObject:win_things_i]; + [contentList addObject:[win_things_i contentView]]; + [itemThings_i setKeyEquivalent:'3']; + [itemList addObject:itemThings_i]; + + [windowList addObject:win_prefs_i]; + [contentList addObject:[win_prefs_i contentView]]; + [itemPrefs_i setKeyEquivalent:'4']; + [itemList addObject:itemPrefs_i]; + + [windowList addObject:win_settings_i]; + [contentList addObject:[win_settings_i contentView]]; + [itemSettings_i setKeyEquivalent:'5']; + [itemList addObject:itemSettings_i]; + + [windowList addObject:win_output_i]; + [contentList addObject:[win_output_i contentView]]; + [itemOutput_i setKeyEquivalent:'6']; + [itemList addObject:itemOutput_i]; + + [windowList addObject:win_help_i]; + [contentList addObject:[win_help_i contentView]]; + [itemHelp_i setKeyEquivalent:'7']; + [itemList addObject:itemHelp_i]; + + // Setup inspector window with project subview first + + [inspectorView_i setAutoresizeSubviews:YES]; + + inspectorSubview_i = [contentList objectAt:i_project]; + [inspectorView_i addSubview:inspectorSubview_i]; + + currentInspectorType = -1; + [self changeInspectorTo:i_project]; + + return self; +} + + +// +// Sent by the PopUpList in the Inspector +// Each cell in the PopUpList must have the correct tag +// +- changeInspector:sender +{ + id cell; + + cell = [sender selectedCell]; + [self changeInspectorTo:[cell tag]]; + return self; +} + +// +// Change to specific Inspector +// +- changeInspectorTo:(insp_e)which +{ + id newView; + NXRect r; + id cell; + NXRect f; + + if (which == currentInspectorType) + return self; + + currentInspectorType = which; + newView = [contentList objectAt:which]; + + cell = [itemList objectAt:which]; // set PopUpButton title + [popUpButton_i setTitle:[cell title]]; + + [inspectorView_i replaceSubview:inspectorSubview_i with:newView]; + [inspectorView_i getFrame:&r]; + inspectorSubview_i = newView; + [inspectorSubview_i setAutosizing:NX_WIDTHSIZABLE | NX_HEIGHTSIZABLE]; + [inspectorSubview_i sizeTo:r.size.width - 4 :r.size.height - 4]; + + [inspectorSubview_i lockFocus]; + [inspectorSubview_i getBounds:&f]; + PSsetgray(NX_LTGRAY); + NXRectFill(&f); + [inspectorSubview_i unlockFocus]; + [inspectorView_i display]; + + return self; +} + +- (insp_e)getCurrentInspector +{ + return currentInspectorType; +} + + +@end diff --git a/QuakeEd/KeypairView.h b/QuakeEd/KeypairView.h new file mode 100644 index 0000000..c2235d9 --- /dev/null +++ b/QuakeEd/KeypairView.h @@ -0,0 +1,16 @@ + +extern id keypairview_i; + +@interface KeypairView:View +{ +} + +- calcViewSize; + +#define SPACING 4 +#define FONTSIZE 12 +#define EXTRASPC 2 + +#define LINEHEIGHT 16 + +@end diff --git a/QuakeEd/KeypairView.m b/QuakeEd/KeypairView.m new file mode 100644 index 0000000..5cbb398 --- /dev/null +++ b/QuakeEd/KeypairView.m @@ -0,0 +1,96 @@ + +#import "qedefs.h" + +id keypairview_i; + +@implementation KeypairView + +/* +================== +initFrame: +================== +*/ +- initFrame:(const NXRect *)frameRect +{ + [super initFrame:frameRect]; + keypairview_i = self; + return self; +} + + +- calcViewSize +{ + NXCoord w; + NXCoord h; + NXRect b; + NXPoint pt; + int count; + id ent; + + ent = [map_i currentEntity]; + count = [ent numPairs]; + + [superview setFlipped: YES]; + + [superview getBounds:&b]; + w = b.size.width; + h = LINEHEIGHT*count + SPACING; + [self sizeTo:w :h]; + pt.x = pt.y = 0; + [self scrollPoint: &pt]; + return self; +} + +- drawSelf:(const NXRect *)rects :(int)rectCount +{ + epair_t *pair; + int y; + + PSsetgray(NXGrayComponent(NX_COLORLTGRAY)); + PSrectfill(0,0,bounds.size.width,bounds.size.height); + + PSselectfont("Helvetica-Bold",FONTSIZE); + PSrotate(0); + PSsetgray(0); + + pair = [[map_i currentEntity] epairs]; + y = bounds.size.height - LINEHEIGHT; + for ( ; pair ; pair=pair->next) + { + PSmoveto(SPACING, y); + PSshow(pair->key); + PSmoveto(100, y); + PSshow(pair->value); + y -= LINEHEIGHT; + } + PSstroke(); + + return self; +} + +- mouseDown:(NXEvent *)theEvent +{ + NXPoint loc; + int i; + epair_t *p; + + loc = theEvent->location; + [self convertPoint:&loc fromView:NULL]; + + i = (bounds.size.height - loc.y - 4) / LINEHEIGHT; + + p = [[map_i currentEntity] epairs]; + while ( i ) + { + p=p->next; + if (!p) + return self; + i--; + } + if (p) + [things_i setSelectedKey: p]; + + return self; +} + +@end diff --git a/QuakeEd/Makefile b/QuakeEd/Makefile new file mode 100644 index 0000000..2a82751 --- /dev/null +++ b/QuakeEd/Makefile @@ -0,0 +1,62 @@ +# +# Generated by the NeXT Project Builder. +# +# NOTE: Do NOT change this file -- Project Builder maintains it. +# +# Put all of your customizations in files called Makefile.preamble +# and Makefile.postamble (both optional), and Makefile will include them. +# + +NAME = QuakeEd + +PROJECTVERSION = 1.1 +LANGUAGE = English + +APPICON = i_quakeed.tiff +LOCAL_RESOURCES = QuakeEd.nib + +GLOBAL_RESOURCES = DownArrow.tiff i_90d.tiff i_add.tiff i_brushes.tiff\ + i_fliph.tiff i_flipv.tiff i_quakeed.tiff i_sub.tiff\ + short.tiff tall.tiff UpArrow.tiff + +CLASSES = CameraView.m Clipper.m Dict.m DictList.m Entity.m\ + EntityClass.m InspectorControl.m KeypairView.m Map.m\ + PopScrollView.m Preferences.m Project.m QuakeEd.m SetBrush.m\ + TexturePalette.m TextureView.m Things.m UserPath.m XYView.m\ + ZScrollView.m ZView.m + +HFILES = CameraView.h Clipper.h Dict.h DictList.h Entity.h\ + EntityClass.h InspectorControl.h KeypairView.h Map.h\ + PopScrollView.h Preferences.h Project.h QuakeEd.h SetBrush.h\ + TexturePalette.h TextureView.h Things.h UserPath.h XYView.h\ + ZScrollView.h ZView.h render.h cmdlib.h mathlib.h + +MFILES = misc.m QuakeEd_main.m render.m + +CFILES = cmdlib.c mathlib.c + +OTHERSRCS = Makefile.preamble Makefile Makefile.postamble + + +MAKEFILEDIR = /NextDeveloper/Makefiles/app +MAKEFILE = app.make +INSTALLDIR = /LocalApps +INSTALLFLAGS = -c -s -m 755 +SOURCEMODE = 444 + +ICONSECTIONS = -sectcreate __ICON app i_quakeed.tiff + +LIBS = -lMedia_s -lNeXT_s +DEBUG_LIBS = $(LIBS) +PROF_LIBS = $(LIBS) + + + + +-include Makefile.preamble + +include $(MAKEFILEDIR)/$(MAKEFILE) + +-include Makefile.postamble + +-include Makefile.dependencies diff --git a/QuakeEd/Makefile.postamble b/QuakeEd/Makefile.postamble new file mode 100644 index 0000000..948802c --- /dev/null +++ b/QuakeEd/Makefile.postamble @@ -0,0 +1,115 @@ +############################################################################### +# NeXT Makefile.postamble Template +# Copyright 1993, NeXT Computer, Inc. +# +# This Makefile is used for configuring the standard app makefiles associated +# with ProjectBuilder. +# +# Use this template to set attributes for a project, sub-project, bundle, or +# palette. Each node in the project's tree of sub-projects and bundles +# should have it's own Makefile.preamble and Makefile.postamble. Additional +# rules (e.g., after_install) that are defined by the developer should be +# defined in this file. +# +############################################################################### +# +# Here are the variables exported by the common "app" makefiles that can be +# used in any customizations you make to the template below: +# +# PRODUCT_ROOT - Name of top-level app-wrapper (e.g., Webster.app) +# OFILE_DIR - Directory into which .o object files are generated. +# (Note that this name is calculated based on the target +# architectures specified in Project Builder). +# DERIVED_SRC_DIR - Directory used for all other derived files +# ALL_CFLAGS - All the flags passed to the cc(1) driver for compilations +# +# NAME - name of application, bundle, subproject, palette, etc. +# LANGUAGE - langage in which the project is written (default "English") +# ENGLISH - boolean flag set iff $(LANGUAGE) = "English" +# JAPANESE - boolean flag set iff $(LANGUAGE) = "Japanese" +# LOCAL_RESOURCES - localized resources (e.g. nib's, images) of project +# GLOBAL_RESOURCES - non-localized resources of project +# PROJECTVERSION - version of ProjectBuilder that output Makefile +# APPICON - application icon file +# DOCICONS - dock icon files +# ICONSECTIONS - Specifies icon sections when linking executable +# +# CLASSES - Class implementation files in project. +# HFILES - Header files in project. +# MFILES - Other Objective-C source files in project. +# CFILES - Other C source files in project. +# PSWFILES - .psw files in the project +# PSWMFILES - .pswm files in the project +# SUBPROJECTS - Subprojects of this project +# BUNDLES - Bundle subprojects of this project +# OTHERSRCS - Other miscellaneous sources of this project +# OTHERLINKED - Source files not matching a standard source extention +# +# LIBS - Libraries to link with when making app target +# DEBUG_LIBS - Libraries to link with when making debug target +# PROF_LIBS - Libraries to link with when making profile target +# OTHERLINKEDOFILES - Other relocatable files to (always) link in. +# +# APP_MAKEFILE_DIR - Directory in which to find generic set of Makefiles +# MAKEFILEDIR - Directory in which to find $(MAKEFILE) +# MAKEFILE - Top level mechanism Makefile (e.g., app.make, bundle.make) +# INSTALLDIR - Directory app will be installed into by 'install' target + + +# Change defaults assumed by the standard app makefiles here. Edit the +# following default values as appropriate. (Note that if no Makefile.postamble +# exists, these values will have defaults set in common.make). + +# Add Makefile.preamble, Makefile.postamble, and Makefile.dependencies here if +# you would like changes to them to invalidate previous builds. The project +# depends on $(MAKEFILES) so that changes to Makefiles will trigger a re-build. +#MAKEFILES = Makefile + +# Optimization flag passed to compiler: +#OPTIMIZATION_CFLAG = -O + +# Flags always passed to compiler: +#COMMON_CFLAGS = $(PROJECT_SPECIFIC_CFLAGS) -g -Wall + +# Flags passed to compiler in normal 'app' compiles: +#NORMAL_CFLAGS = $(COMMON_CFLAGS) $(OPTIMIZATION_CFLAG) + +# Flags passed to compiler in 'debug' compiles: +#DEBUG_CFLAGS = $(COMMON_CFLAGS) -DDEBUG + +# Flags passed to compiler in 'profile' compiles +#PROFILE_CFLAGS = $(COMMON_CFLAGS) -pg $(OPTIMIZATION_CFLAG) -DPROFILE + +# Flags passed to yacc +#YFLAGS = -d + +# Ownership and permissions of files installed by 'install' target +#INSTALL_AS_USER = root # User to chown app to +#INSTALL_AS_GROUP = wheel # Group to chgrp app to +#INSTALL_PERMISSIONS = # If set, 'install' chmod's executable to this + +# Options to strip for bundles, apps with bundles, and apps without bundles, +# respectively. +#RELOCATABLE_STRIP_OPTS = -x -u +#DYLD_APP_STRIP_OPTS = -A -n +#APP_STRIP_OPTS = +#TOOL_STRIP_OPTS = +#LIBRARY_STRIP_OPTS = -x -S # Note: -S strips debugging symbols +# (Note: APP_STRIP_OPTS and TOOL_STRIP_OPTS default to empty, but +# developers doing their own dynamic loading should set this to +# $(DYLD_APP_STRIP_OPTS)). + + +######################################################################### +# Put rules to extend the behavior of the standard Makefiles here. Typical +# user-defined rules are before_install and after_install (please don't +# redefine things like install or app, as they are owned by the top-level +# Makefile API), which are rules that get invoked before and after the install +# target runs. Such rules should be specified with the '::' syntax rather than +# a single colon. + + + + + + diff --git a/QuakeEd/Makefile.preamble b/QuakeEd/Makefile.preamble new file mode 100644 index 0000000..9983c5d --- /dev/null +++ b/QuakeEd/Makefile.preamble @@ -0,0 +1,108 @@ +############################################################################### +# NeXT Makefile.preamble Template +# Copyright 1993, NeXT Computer, Inc. +# +# This Makefile is used for configuring the standard app makefiles associated +# with ProjectBuilder. +# +# Use this template to set attributes for a project, sub-project, bundle, or +# palette. Each node in the project's tree of sub-projects and bundles +# should have it's own Makefile.preamble and Makefile.postamble. +# +############################################################################### +## Configure the flags passed to $(CC) here. These flags will also be +## inherited by all nested sub-projects and bundles. Put your -I, -D, -U, and +## -L flags here. To change the default flags that get passed to ${CC} +## (e.g. change -O to -O2), see Makefile.postamble. + +# Flags passed to compiler (in addition to -g, -O, etc) +OTHER_CFLAGS = +# Flags passed to ld (in addition to -ObjC, etc.) +OTHER_LDFLAGS = +BUNDLELDFLAGS = # use iff project is a bundle +PALETTELDFLAGS = # use iff project is a palette + +## Specify which headers in this project should be published to the outside +## world in a flat header directory given in PUBLIC_HEADER_DIR (which will be +## prepended by DSTROOT, below. Any subset of these public headers can be +## precompiled automatically after installation, with extra user-defined flags. +PUBLIC_HEADER_DIR = +PUBLIC_HEADERS = +PUBLIC_PRECOMPILED_HEADERS = +PUBLIC_PRECOMPILED_HEADERS_CFLAGS = + +## Configure what is linked in at each level here. Libraries are only used in +## the final 'app' linking step. Final 'app' linking is only done via the +## 'app', 'debug', and 'profile' targets when they are invoked for +## the top-level app. + +# Additional relocatables to be linked in at this level +OTHER_OFILES = +# Additional libs to link apps against ('app' target) +OTHER_LIBS = +# Additional libs to link apps against ('debug' target) +OTHER_DEBUG_LIBS = +# Additional libs to link apps against ('profile' target) +OTHER_PROF_LIBS = + +# More 'app' libraries when $(JAPANESE) = "YES" +OTHER_JAPANESE_LIBS = +# More 'debug' libraries when $(JAPANESE) = "YES" +OTHER_JAPANESE_DEBUG_LIBS = +# More 'profile' libs when $(JAPANESE) = "YES" +OTHER_JAPANESE_PROF_LIBS = + +# If this is a bundle, and you *know* the enclosing application will not +# be linking with a library which you require in your bundle code, then +# mention it here so that it gets linked into the bundle. Note that this +# is wasteful but sometimes necessary. +BUNDLE_LIBS = + +## Configure how things get built here. Additional dependencies, sourcefiles, +## derived files, and build order should be specified here. + +# Other dependencies of this project +OTHER_PRODUCT_DEPENDS = +# Built *before* building subprojects/bundles +OTHER_INITIAL_TARGETS = +# Other source files maintained by .pre/postamble +OTHER_SOURCEFILES = +# Additional files to be removed by `make clean' +OTHER_GARBAGE = +# Precompiled headers to be built before any compilation occurs (e.g., draw.p) +PRECOMPS = + +# Targets to be built before installation +OTHER_INSTALL_DEPENDS = + +# A virtual root directory (other than /) to be prepended to the $(INSTALLDIR) +# passed from ProjectBuilder. +DSTROOT = + +# Set the following to "YES" if you want the old behavior of recursively +# cleaning all nested subprojects during 'make clean'. +CLEAN_ALL_SUBPROJECTS = + +## Add more obscure source files here to cause them to be automatically +## processed by the appropriate tool. Note that these files should also be +## added to "Supporting Files" in ProjectBuilder. The desired .o files that +## result from these files should also be added to OTHER_OFILES above so they +## will be linked in. + +# .msg files that should have msgwrap run on them +MSGFILES = +# .defs files that should have mig run on them +DEFSFILES = +# .mig files (no .defs files) that should have mig run on them +MIGFILES = + +## Add additional Help directories here (add them to the project as "Other +## Resources" in Project Builder) so that they will be compressed into .store +## files and copied into the app wrapper. If the help directories themselves +## need to also be in the app wrapper, then a cp command will need to be added +## in an after_install target. +OTHER_HELP_DIRS = + +# Don't add more rules here unless you want the first one to be the default +# target for make! Put all your targets in Makefile.postamble. + diff --git a/QuakeEd/Map.h b/QuakeEd/Map.h new file mode 100644 index 0000000..57e1386 --- /dev/null +++ b/QuakeEd/Map.h @@ -0,0 +1,68 @@ + +// Map is a list of Entity objects + +extern id map_i; + +@interface Map : List +{ + id currentEntity; + id oldselection; // temp when loading a new map + float minz, maxz; +} + +- newMap; + +- writeStats; + +- readMapFile: (char *)fname; +- writeMapFile: (char *)fname useRegion: (BOOL)reg; + +- entityConnect: (vec3_t)p1 : (vec3_t)p2; + +- selectRay: (vec3_t)p1 : (vec3_t)p2 : (BOOL)ef; +- grabRay: (vec3_t)p1 : (vec3_t)p2; +- setTextureRay: (vec3_t)p1 : (vec3_t)p2 : (BOOL)allsides; +- getTextureRay: (vec3_t)p1 : (vec3_t)p2; + +- currentEntity; +- setCurrentEntity: ent; + +- (float)currentMinZ; +- setCurrentMinZ: (float)m; +- (float)currentMaxZ; +- setCurrentMaxZ: (float)m; + +- (int)numSelected; +- selectedBrush; // returns the first selected brush + +// +// operations on current selection +// +- makeSelectedPerform: (SEL)sel; +- makeUnselectedPerform: (SEL)sel; +- makeAllPerform: (SEL)sel; +- makeGlobalPerform: (SEL)sel; // in and out of region + +- cloneSelection: sender; + +- makeEntity: sender; + +- subtractSelection: sender; + +- selectCompletelyInside: sender; +- selectPartiallyInside: sender; + +- tallBrush: sender; +- shortBrush: sender; + +- rotate_x: sender; +- rotate_y: sender; +- rotate_z: sender; + +- flip_x: sender; +- flip_y: sender; +- flip_z: sender; + +- selectCompleteEntity: sender; + +@end diff --git a/QuakeEd/Map.m b/QuakeEd/Map.m new file mode 100644 index 0000000..9185e6d --- /dev/null +++ b/QuakeEd/Map.m @@ -0,0 +1,1121 @@ + +#include "qedefs.h" + +id map_i; + +@implementation Map + +/* +=============================================================================== + +FILE METHODS + +=============================================================================== +*/ + +- init +{ + [super init]; + map_i = self; + minz = 0; + maxz = 80; + + oldselection = [[List alloc] init]; + + return self; +} + +- saveSelected +{ + int i, c; + id o, w; + + [oldselection empty]; + w = [self objectAt: 0]; + c = [w count]; + sb_newowner = oldselection; + for (i=0 ; i -2048) + minz = m; + return self; +} + +- (float)currentMaxZ +{ + float grid; + + [self currentMinZ]; // grid align + + grid = [xyview_i gridsize]; + maxz = grid * rint(maxz/grid); + + if (maxz <= minz) + maxz = minz + grid; + return maxz; +} + +- setCurrentMaxZ: (float)m +{ + if (m < 2048) + maxz = m; + return self; +} + +- removeObject: o +{ + o = [super removeObject: o]; + + if (o == currentEntity) + { // select the world + [self setCurrentEntity: [self objectAt: 0]]; + } + + return o; +} + +- writeStats +{ + FILE *f; + extern int c_updateall; + struct timeval tp; + struct timezone tzp; + + gettimeofday(&tp, &tzp); + + f = fopen (FN_DEVLOG, "a"); + fprintf (f,"%i %i\n", (int)tp.tv_sec, c_updateall); + c_updateall = 0; + fclose (f); + return self; +} + +- (int)numSelected +{ + int i, c; + int num; + + num = 0; + c = [currentEntity count]; + for (i=0 ; i=0 ; i--) + { + ent = [self objectAt: i]; + c2 = [ent count]; + for (j=0 ; jbesttime) + continue; + bestent = ent; + besttime = time; + bestbrush = brush; + bestface = face; + } + if (i == 1 && ef && bestbrush) + break; // found an entity, so don't check the world + } + + if (besttime == 99999) + { + qprintf ("trace missed"); + return self; + } + + if ( [bestbrush regioned] ) + { + qprintf ("WANRING: clicked on regioned brush"); + return self; + } + + if (bestent != currentEntity) + { + [self makeSelectedPerform: @selector(deselect)]; + [self setCurrentEntity: bestent]; + } + + [quakeed_i disableFlushWindow]; + if ( ![bestbrush selected] ) + { + if ( [map_i numSelected] == 0) + { // don't grab texture if others are selected + td = [bestbrush texturedefForFace: bestface]; + [texturepalette_i setTextureDef: td]; + } + + [bestbrush setSelected: YES]; + qprintf ("selected entity %i brush %i face %i", [self indexOf:bestent], [bestent indexOf: bestbrush], bestface); + } + else + { + [bestbrush setSelected: NO]; + qprintf ("deselected entity %i brush %i face %i", [self indexOf:bestent], [bestent indexOf: bestbrush], bestface); + } + + [quakeed_i reenableFlushWindow]; + [quakeed_i updateAll]; + + return self; +} + +/* +================= +grabRay + +only checks the selected brushes +Returns the brush hit, or nil if missed. +================= +*/ +- grabRay: (vec3_t)p1 : (vec3_t)p2 +{ + int i, j, c, c2; + id ent; + id brush, bestbrush; + int face; + float time, besttime; + + bestbrush = nil; + besttime = 99999; + + c = [self count]; + for (i=0 ; ibesttime) + continue; + besttime = time; + bestbrush = brush; + } + } + + if (besttime == 99999) + return nil; + + return bestbrush; +} + +/* +================= +getTextureRay +================= +*/ +- getTextureRay: (vec3_t)p1 : (vec3_t)p2 +{ + int i, j, c, c2; + id ent, bestent; + id brush, bestbrush; + int face, bestface; + float time, besttime; + texturedef_t *td; + vec3_t mins, maxs; + + + bestbrush = nil; + bestent = nil; + besttime = 99999; + bestface = -1; + c = [self count]; + for (i=0 ; ibesttime) + continue; + bestent = ent; + bestface = face; + besttime = time; + bestbrush = brush; + } + } + + if (besttime == 99999) + return nil; + + if ( ![bestent modifiable]) + { + qprintf ("can't modify spawned entities"); + return self; + } + + td = [bestbrush texturedefForFace: bestface]; + [texturepalette_i setTextureDef: td]; + + qprintf ("grabbed texturedef and sizes"); + + [bestbrush getMins: mins maxs: maxs]; + + minz = mins[2]; + maxz = maxs[2]; + + return bestbrush; +} + +/* +================= +setTextureRay +================= +*/ +- setTextureRay: (vec3_t)p1 : (vec3_t)p2 : (BOOL)allsides; +{ + int i, j, c, c2; + id ent, bestent; + id brush, bestbrush; + int face, bestface; + float time, besttime; + texturedef_t td; + + bestent = nil; + bestface = -1; + bestbrush = nil; + besttime = 99999; + + c = [self count]; + for (i=0 ; ibesttime) + continue; + bestent = ent; + besttime = time; + bestbrush = brush; + bestface = face; + } + } + + if (besttime == 99999) + { + qprintf ("trace missed"); + return self; + } + + if ( ![bestent modifiable]) + { + qprintf ("can't modify spawned entities"); + return self; + } + + if ( [bestbrush regioned] ) + { + qprintf ("WANRING: clicked on regioned brush"); + return self; + } + + [texturepalette_i getTextureDef: &td]; + + [quakeed_i disableFlushWindow]; + if (allsides) + { + [bestbrush setTexturedef: &td]; + qprintf ("textured entity %i brush %i", [self indexOf:bestent], [bestent indexOf: bestbrush]); + } + else + { + [bestbrush setTexturedef: &td forFace: bestface]; + qprintf ("deselected entity %i brush %i face %i", [self indexOf:bestent], [bestent indexOf: bestbrush], bestface); + } + [quakeed_i reenableFlushWindow]; + + [quakeed_i updateAll]; + + return self; +} + + +/* +============================================================================== + +OPERATIONS ON SELECTIONS + +============================================================================== +*/ + +- makeSelectedPerform: (SEL)sel +{ + int i,j, c, c2; + id ent, brush; + int total; + + total = 0; + c = [self count]; + for (i=c-1 ; i>=0 ; i--) + { + ent = [self objectAt: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAt: j]; + if (! [brush selected] ) + continue; + if ([brush regioned]) + continue; + total++; + [brush perform:sel]; + } + } + +// if (!total) +// qprintf ("nothing selected"); + + return self; +} + +- makeUnselectedPerform: (SEL)sel +{ + int i,j, c, c2; + id ent, brush; + + c = [self count]; + for (i=c-1 ; i>=0 ; i--) + { + ent = [self objectAt: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAt: j]; + if ( [brush selected] ) + continue; + if ([brush regioned]) + continue; + [brush perform:sel]; + } + } + + return self; +} + +- makeAllPerform: (SEL)sel +{ + int i,j, c, c2; + id ent, brush; + + c = [self count]; + for (i=c-1 ; i>=0 ; i--) + { + ent = [self objectAt: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAt: j]; + if ([brush regioned]) + continue; + [brush perform:sel]; + } + } + + return self; +} + +- makeGlobalPerform: (SEL)sel // in and out of region +{ + int i,j, c, c2; + id ent, brush; + + c = [self count]; + for (i=c-1 ; i>=0 ; i--) + { + ent = [self objectAt: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAt: j]; + [brush perform:sel]; + } + } + + return self; +} + + +void sel_identity (void) +{ + sel_x[0]=1; sel_x[1]=0; sel_x[2]=0; + sel_y[0]=0; sel_y[1]=1; sel_y[2]=0; + sel_z[0]=0; sel_z[1]=0; sel_z[2]=1; +} + +- transformSelection +{ + if ( ![currentEntity modifiable]) + { + qprintf ("can't modify spawned entities"); + return self; + } + +// find an origin to apply the transformation to + sb_mins[0] = sb_mins[1] = sb_mins[2] = 99999; + sb_maxs[0] = sb_maxs[1] = sb_maxs[2] = -99999; + [self makeSelectedPerform: @selector(addToBBox)]; + sel_org[0] = [xyview_i snapToGrid: (sb_mins[0] + sb_maxs[0])/2]; + sel_org[1] = [xyview_i snapToGrid: (sb_mins[1] + sb_maxs[1])/2]; + sel_org[2] = [xyview_i snapToGrid: (sb_mins[2] + sb_maxs[2])/2]; + +// do it! + [self makeSelectedPerform: @selector(transform)]; + + [quakeed_i updateAll]; + return self; +} + + +void swapvectors (vec3_t a, vec3_t b) +{ + vec3_t temp; + + VectorCopy (a, temp); + VectorCopy (b, a); + VectorSubtract (vec3_origin, temp, b); +} + +/* +=============================================================================== + +UI operations + +=============================================================================== +*/ + +- rotate_x: sender +{ + sel_identity (); + swapvectors(sel_y, sel_z); + [self transformSelection]; + return self; +} + +- rotate_y: sender +{ + sel_identity (); + swapvectors(sel_x, sel_z); + [self transformSelection]; + return self; +} + +- rotate_z: sender +{ + sel_identity (); + swapvectors(sel_x, sel_y); + [self transformSelection]; + return self; +} + + +- flip_x: sender +{ + sel_identity (); + sel_x[0] = -1; + [self transformSelection]; + [map_i makeSelectedPerform: @selector(flipNormals)]; + return self; +} + +- flip_y: sender +{ + sel_identity (); + sel_y[1] = -1; + [self transformSelection]; + [map_i makeSelectedPerform: @selector(flipNormals)]; + return self; +} + + +- flip_z: sender +{ + sel_identity (); + sel_z[2] = -1; + [self transformSelection]; + [map_i makeSelectedPerform: @selector(flipNormals)]; + return self; +} + + +- cloneSelection: sender +{ + int i,j , c, originalElements; + id o, b; + id new; + + sb_translate[0] = sb_translate[1] = [xyview_i gridsize]; + sb_translate[2] = 0; + +// copy individual brushes in the world entity + o = [self objectAt: 0]; + c = [o count]; + for (i=0 ; i + +@interface PopScrollView : ScrollView +{ + id button1, button2; +} + +- initFrame:(const NXRect *)frameRect button1: b1 button2: b2; +- tile; + +@end \ No newline at end of file diff --git a/QuakeEd/PopScrollView.m b/QuakeEd/PopScrollView.m new file mode 100644 index 0000000..18edb03 --- /dev/null +++ b/QuakeEd/PopScrollView.m @@ -0,0 +1,87 @@ + +#import "qedefs.h" + +@implementation PopScrollView + +/* +==================== +initFrame: button: + +Initizes a scroll view with a button at it's lower right corner +==================== +*/ + +- initFrame:(const NXRect *)frameRect button1:b1 button2:b2 +{ + [super initFrame: frameRect]; + + [self addSubview: b1]; + [self addSubview: b2]; + + button1 = b1; + button2 = b2; + + [self setHorizScrollerRequired: YES]; + [self setVertScrollerRequired: YES]; + + [self setBorderType: NX_BEZEL]; + + return self; +} + + +/* +================ +tile + +Adjust the size for the pop up scale menu +================= +*/ + +- tile +{ + NXRect scrollerframe; + NXRect buttonframe, buttonframe2; + NXRect newframe; + + [super tile]; + [button1 getFrame: &buttonframe]; + [button2 getFrame: &buttonframe2]; + [hScroller getFrame: &scrollerframe]; + + newframe.origin.y = scrollerframe.origin.y; + newframe.origin.x = frame.size.width - buttonframe.size.width; + newframe.size.width = buttonframe.size.width; + newframe.size.height = scrollerframe.size.height; + scrollerframe.size.width -= newframe.size.width; + [button1 setFrame: &newframe]; + newframe.size.width = buttonframe2.size.width; + newframe.origin.x -= newframe.size.width; + [button2 setFrame: &newframe]; + scrollerframe.size.width -= newframe.size.width; + + [hScroller setFrame: &scrollerframe]; + + return self; +} + + +- superviewSizeChanged:(const NXSize *)oldSize +{ + [super superviewSizeChanged: oldSize]; + + [[self docView] newSuperBounds]; + + return self; +} + + +-(BOOL) acceptsFirstResponder +{ + return YES; +} + + + +@end + diff --git a/QuakeEd/Preferences.h b/QuakeEd/Preferences.h new file mode 100644 index 0000000..a27d81a --- /dev/null +++ b/QuakeEd/Preferences.h @@ -0,0 +1,78 @@ + +extern id preferences_i; + +extern float lightaxis[3]; + +// these are personal preferences saved in NeXT defaults, not project +// parameters saved in the quake.qe_project file + +@interface Preferences:Object +{ + id bspSound_i; // actual sound object + +// internal state + char projectpath[1024]; + char bspSound[1024]; + + BOOL brushOffset; + BOOL showBSP; + + float xlight; + float ylight; + float zlight; // 0.0 - 1.0 + + int startwad; // 0 - 2 + +// UI targets + id startproject_i; // TextField + + id bspSoundField_i; // TextField of bspSound + + id brushOffset_i; // Brush Offset checkbox + id showBSP_i; // Show BSP Output checkbox + + id startwad_i; // which wad to load at startup + + id xlight_i; // X-side lighting + id ylight_i; // Y-side lighting + id zlight_i; // Z-side lighting +} + +- readDefaults; + +// +// validate and set methods called by UI or defaults +// +- setProjectPath:(char *)path; +- setBspSoundPath:(char *)path; // set the path of the soundfile externally +- setShowBSP:(int)state; // set the state of ShowBSP +- setBrushOffset:(int)state; // set the state of BrushOffset +- setStartWad:(int)value; // set start wad (0-2) +- setXlight:(float)value; // set Xlight value for CameraView +- setYlight:(float)value; // set Ylight value for CameraView +- setZlight:(float)value; // set Zlight value for CameraView + +// +// UI targets +// +- setBspSound:sender; // use OpenPanel to select sound +- setCurrentProject:sender; // make current roject the default +- UIChanged: sender; // target for all checks and fields + +// +// methods used by other objects to retreive defaults +// +- playBspSound; + +- (char *)getProjectPath; +- (int)getBrushOffset; // get the state +- (int)getShowBSP; // get the state + +- (float)getXlight; // get Xlight value +- (float)getYlight; // get Ylight value +- (float)getZlight; // get Zlight value + +- (int)getStartWad; + + +@end diff --git a/QuakeEd/Preferences.m b/QuakeEd/Preferences.m new file mode 100644 index 0000000..e3611a7 --- /dev/null +++ b/QuakeEd/Preferences.m @@ -0,0 +1,330 @@ + +#import "qedefs.h" + +id preferences_i; + +#define DEFOWNER "QuakeEd2" + +float lightaxis[3] = {1, 0.6, 0.75}; + +@implementation Preferences + +- init +{ + [super init]; + preferences_i = self; + return self; +} + +int _atoi (char *c) +{ + if (!c) + return 0; + return atoi(c); +} + +int _atof (char *c) +{ + if (!c) + return 0; + return atof(c); +} + +void WriteNumericDefault (char *name, float value) +{ + char str[128]; + + sprintf (str,"%f", value); + NXWriteDefault (DEFOWNER, name, str); +} +void WriteStringDefault (char *name, char *value) +{ + NXWriteDefault (DEFOWNER, name, value); +} + +// +// Read in at start of program +// +- readDefaults +{ + char *string; + float value; + + string = (char *)NXGetDefaultValue(DEFOWNER,"ProjectPath"); + [self setProjectPath: string]; + + string = (char *)NXGetDefaultValue(DEFOWNER,"BspSoundPath"); + [self setBspSoundPath:string]; + + value = _atoi((char *)NXGetDefaultValue(DEFOWNER,"ShowBSPOutput")); + [self setShowBSP:value]; + + value = _atoi((char *)NXGetDefaultValue(DEFOWNER,"OffsetBrushCopy")); + [self setBrushOffset:value]; + + value = _atoi((char *)NXGetDefaultValue(DEFOWNER,"StartWad")); + [self setStartWad:value]; + + value = _atof((char *)NXGetDefaultValue(DEFOWNER,"Xlight")); + [self setXlight:value]; + + value = _atof((char *)NXGetDefaultValue(DEFOWNER,"Ylight")); + [self setYlight:value]; + + value = _atof((char *)NXGetDefaultValue(DEFOWNER,"Zlight")); + [self setZlight:value]; + + return self; +} + + +- setProjectPath:(char *)path +{ + if (!path) + path = ""; + strcpy (projectpath, path); + [startproject_i setStringValue: path]; + WriteStringDefault ("ProjectPath", path); + return self; +} + +- setCurrentProject:sender +{ + [startproject_i setStringValue: [project_i currentProjectFile]]; + [self UIChanged: self]; + return self; +} + +- (char *)getProjectPath +{ + return projectpath; +} + + +// +//=============================================== +// BSP sound stuff +//=============================================== +// +// Set the BSP sound using an OpenPanel +// +- setBspSound:sender +{ + id panel; + char *types[]={"snd",NULL}; + int rtn; + char **filename; + char path[1024], file[64]; + + panel = [OpenPanel new]; + + ExtractFilePath (bspSound, path); + ExtractFileBase (bspSound, file); + + rtn = [panel + runModalForDirectory:path + file: file + types: types]; + + if (rtn) + { + filename = (char **)[panel filenames]; + strcpy(bspSound,[panel directory]); + strcat(bspSound,"/"); + strcat(bspSound,filename[0]); + [self setBspSoundPath:bspSound]; + [self playBspSound]; + } + + return self; +} + + +// +// Play the BSP sound +// +- playBspSound +{ + [bspSound_i play]; + return self; +} + + +// +// Set the bspSound path +// +- setBspSoundPath:(char *)path +{ + if (!path) + path = ""; + strcpy(bspSound,path); + + if (bspSound_i) + [bspSound_i free]; + bspSound_i = [[Sound alloc] initFromSoundfile:bspSound]; + if (!bspSound_i) + { + strcpy (bspSound, "/NextLibrary/Sounds/Funk.snd"); + bspSound_i = [[Sound alloc] initFromSoundfile:bspSound]; + } + + [bspSoundField_i setStringValue:bspSound]; + + WriteStringDefault ("BspSoundPath", bspSound); + + return self; +} + +//=============================================== +// Show BSP Output management +//=============================================== + +// +// Set the state +// +- setShowBSP:(int)state +{ + showBSP = state; + [showBSP_i setIntValue:state]; + WriteNumericDefault ("ShowBSPOutput", showBSP); + + return self; +} + +// +// Get the state +// +- (int)getShowBSP +{ + return showBSP; +} + + +//=============================================== +// "Offset Brush ..." management +//=============================================== + +// +// Set the state +// +- setBrushOffset:(int)state +{ + brushOffset = state; + [brushOffset_i setIntValue:state]; + WriteNumericDefault ("OffsetBrushCopy", state); + return self; +} + +// +// Get the state +// +- (int)getBrushOffset +{ + return brushOffset; +} + +//=============================================== +// StartWad +//=============================================== + +- setStartWad:(int)value // set start wad (0-2) +{ + startwad = value; + if (startwad<0 || startwad>2) + startwad = 0; + + [startwad_i selectCellAt:startwad : 0]; + + WriteNumericDefault ("StartWad", value); + return self; +} + +- (int)getStartWad +{ + return startwad; +} + + +//=============================================== +// X,Y,Z light values +//=============================================== +// +// Set the state +// +- setXlight:(float)value +{ + xlight = value; + if (xlight < 0.25 || xlight > 1) + xlight = 0.6; + lightaxis[1] = xlight; + [xlight_i setFloatValue:xlight]; + WriteNumericDefault ("Xlight", xlight); + return self; +} +- setYlight:(float)value +{ + ylight = value; + if (ylight < 0.25 || ylight > 1) + ylight = 0.75; + lightaxis[2] = ylight; + [ylight_i setFloatValue:ylight]; + WriteNumericDefault ("Ylight", ylight); + return self; +} +- setZlight:(float)value +{ + zlight = value; + if (zlight < 0.25 || zlight > 1) + zlight = 1; + lightaxis[0] = zlight; + [zlight_i setFloatValue:zlight]; + WriteNumericDefault ("Zlight", zlight); + return self; +} + +// +// Get the state +// +- (float)getXlight +{ + return [xlight_i floatValue]; +} +- (float)getYlight +{ + return [ylight_i floatValue]; +} +- (float)getZlight +{ + return [zlight_i floatValue]; +} + + + +/* +============ +UIChanged + +Grab all the current UI state +============ +*/ +-UIChanged: sender +{ + qprintf ("defaults updated"); + + [self setProjectPath: (char *)[startproject_i stringValue]]; + [self setBspSoundPath: (char *)[bspSoundField_i stringValue]]; + [self setShowBSP: [showBSP_i intValue]]; + [self setBrushOffset: [brushOffset_i intValue]]; + [self setStartWad: [startwad_i selectedRow]]; + [self setXlight: [xlight_i floatValue]]; + [self setYlight: [ylight_i floatValue]]; + [self setZlight: [zlight_i floatValue]]; + + [map_i makeGlobalPerform: @selector(flushTextures)]; + [quakeed_i updateAll]; + + return self; +} + + +@end diff --git a/QuakeEd/Project.h b/QuakeEd/Project.h new file mode 100644 index 0000000..9dd973a --- /dev/null +++ b/QuakeEd/Project.h @@ -0,0 +1,108 @@ + +#import +#include + +#define BASEPATHKEY "basepath" +#define MAPNAMESKEY "maps" +#define DESCKEY "desc" +#define WADSKEY "wads" +#define BSPFULLVIS "bspfullvis" +#define BSPFASTVIS "bspfastvis" +#define BSPNOVIS "bspnovis" +#define BSPRELIGHT "bsprelight" +#define BSPLEAKTEST "bspleaktest" +#define BSPENTITIES "bspentities" + +#define SUBDIR_ENT "progs" // subdir names in heirarchy +#define SUBDIR_MAPS "maps" +#define SUBDIR_GFX "gfx" + +extern id project_i; + +@interface Project:Object +{ + id projectInfo; // dictionary storage of project info + + id basepathinfo_i; // outlet to base path info textfield + id mapbrowse_i; // outlet to QuakeEd Maps browser + id currentmap_i; // outlet to current map textfield + id mapList; // list of map names (Storage) + id descList; // list of map descriptions (Storage) + id wadList; // list of wad names (Storage) + + id pis_panel_i; // outlet to Project Info Settings (PIS) panel + + id pis_basepath_i; // outlet to PIS->base path + id pis_wads_i; // outlet to PIS->wad browser + id pis_fullvis_i; // outlet to PIS->full vis command + id pis_fastvis_i; // outlet to PIS->fast vis command + id pis_novis_i; // outlet to PIS->no vis command + id pis_relight_i; // outlet to PIS->relight command + id pis_leaktest_i; // outlet to PIS->leak test command + + id BSPoutput_i; // outlet to Text + + char path_projectinfo[128]; // path of QE_Project file + + char path_basepath[128]; // base path of heirarchy + + char path_progdir[128]; // derived from basepath + char path_mapdirectory[128]; // derived from basepath + char path_finalmapdir[128]; // derived from basepath + + char path_wad8[128]; // path of texture WAD for cmd-8 key + char path_wad9[128]; // path of texture WAD for cmd-9 key + char path_wad0[128]; // path of texture WAD for cmd-0 key + + char string_fullvis[1024]; // cmd-line parm + char string_fastvis[1024]; // cmd-line parm + char string_novis[1024]; // cmd-line parm + char string_relight[1024]; // cmd-line parm + char string_leaktest[1024]; // cmd-line parm + char string_entities[1024]; // cmd-line parm + + int showDescriptions; // 1 = show map descs in browser + + time_t lastModified; // last time project file was modified +} + +- initProject; +- initVars; + +- (char *)currentProjectFile; + +- setTextureWad: (char *)wf; + +- addToOutput:(char *)string; +- clearBspOutput:sender; +- initProjSettings; +- changeChar:(char)f to:(char)t in:(id)obj; +- (int)searchForString:(char *)str in:(id)obj; + +- parseProjectFile; // read defaultsdatabase for project path +- openProjectFile:(char *)path; // called by openProject and newProject +- openProject; +- clickedOnMap:sender; // called if clicked on map in browser +- clickedOnWad:sender; // called if clicked on wad in browser + +// methods to querie the project file + +- (char *)getMapDirectory; +- (char *)getFinalMapDirectory; +- (char *)getProgDirectory; + +- (char *)getWAD8; +- (char *)getWAD9; +- (char *)getWAD0; + +- (char *)getFullVisCmd; +- (char *)getFastVisCmd; +- (char *)getNoVisCmd; +- (char *)getRelightCmd; +- (char *)getLeaktestCmd; +- (char *)getEntitiesCmd; + +@end + +void changeString(char cf,char ct,char *string); + diff --git a/QuakeEd/Project.m b/QuakeEd/Project.m new file mode 100644 index 0000000..e9b36b3 --- /dev/null +++ b/QuakeEd/Project.m @@ -0,0 +1,526 @@ +//====================================== +// +// QuakeEd Project Management +// +//====================================== + +#import "qedefs.h" + + +id project_i; + +@implementation Project + +- init +{ + project_i = self; + + return self; +} + +//=========================================================== +// +// Project code +// +//=========================================================== +- initVars +{ + char *s; + + s = [preferences_i getProjectPath]; + StripFilename(s); + strcpy(path_basepath,s); + + strcpy(path_progdir,s); + strcat(path_progdir,"/"SUBDIR_ENT); + + strcpy(path_mapdirectory,s); + strcat(path_mapdirectory,"/"SUBDIR_MAPS); // source dir + + strcpy(path_finalmapdir,s); + strcat(path_finalmapdir,"/"SUBDIR_MAPS); // dest dir + + [basepathinfo_i setStringValue:s]; // in Project Inspector + + #if 0 + if ((s = [projectInfo getStringFor:BASEPATHKEY])) + { + strcpy(path_basepath,s); + + strcpy(path_progdir,s); + strcat(path_progdir,"/"SUBDIR_ENT); + + strcpy(path_mapdirectory,s); + strcat(path_mapdirectory,"/"SUBDIR_MAPS); // source dir + + strcpy(path_finalmapdir,s); + strcat(path_finalmapdir,"/"SUBDIR_MAPS); // dest dir + + [basepathinfo_i setStringValue:s]; // in Project Inspector + } + #endif + + if ((s = [projectInfo getStringFor:BSPFULLVIS])) + { + strcpy(string_fullvis,s); + changeString('@','\"',string_fullvis); + } + + if ((s = [projectInfo getStringFor:BSPFASTVIS])) + { + strcpy(string_fastvis,s); + changeString('@','\"',string_fastvis); + } + + if ((s = [projectInfo getStringFor:BSPNOVIS])) + { + strcpy(string_novis,s); + changeString('@','\"',string_novis); + } + + if ((s = [projectInfo getStringFor:BSPRELIGHT])) + { + strcpy(string_relight,s); + changeString('@','\"',string_relight); + } + + if ((s = [projectInfo getStringFor:BSPLEAKTEST])) + { + strcpy(string_leaktest,s); + changeString('@','\"',string_leaktest); + } + + if ((s = [projectInfo getStringFor:BSPENTITIES])) + { + strcpy(string_entities,s); + changeString('@','\"', string_entities); + } + + // Build list of wads + wadList = [projectInfo parseMultipleFrom:WADSKEY]; + + // Build list of maps & descriptions + mapList = [projectInfo parseMultipleFrom:MAPNAMESKEY]; + descList = [projectInfo parseMultipleFrom:DESCKEY]; + [self changeChar:'_' to:' ' in:descList]; + + [self initProjSettings]; + + return self; +} + +// +// Init Project Settings fields +// +- initProjSettings +{ + [pis_basepath_i setStringValue:path_basepath]; + [pis_fullvis_i setStringValue:string_fullvis]; + [pis_fastvis_i setStringValue:string_fastvis]; + [pis_novis_i setStringValue:string_novis]; + [pis_relight_i setStringValue:string_relight]; + [pis_leaktest_i setStringValue:string_leaktest]; + + return self; +} + +// +// Add text to the BSP Output window +// +- addToOutput:(char *)string +{ + int end; + + end = [BSPoutput_i textLength]; + [BSPoutput_i setSel:end :end]; + [BSPoutput_i replaceSel:string]; + + end = [BSPoutput_i textLength]; + [BSPoutput_i setSel:end :end]; + [BSPoutput_i scrollSelToVisible]; + + return self; +} + +- clearBspOutput:sender +{ + [BSPoutput_i selectAll:self]; + [BSPoutput_i replaceSel:"\0"]; + + return self; +} + +- print +{ + [BSPoutput_i printPSCode:self]; + return self; +} + + +- initProject +{ + [self parseProjectFile]; + if (projectInfo == NULL) + return self; + [self initVars]; + [mapbrowse_i reuseColumns:YES]; + [mapbrowse_i loadColumnZero]; + [pis_wads_i reuseColumns:YES]; + [pis_wads_i loadColumnZero]; + + [things_i initEntities]; + + return self; +} + +// +// Change a character to another in a Storage list of strings +// +- changeChar:(char)f to:(char)t in:(id)obj +{ + int i; + int max; + char *string; + + max = [obj count]; + for (i = 0;i < max;i++) + { + string = [obj elementAt:i]; + changeString(f,t,string); + } + return self; +} + +// +// Fill the QuakeEd Maps or wads browser +// (Delegate method - delegated in Interface Builder) +// +- (int)browser:sender fillMatrix:matrix inColumn:(int)column +{ + id cell, list; + int max; + char *name; + int i; + + if (sender == mapbrowse_i) + list = mapList; + else if (sender == pis_wads_i) + list = wadList; + else + { + list = nil; + Error ("Project: unknown browser to fill"); + } + + max = [list count]; + for (i = 0 ; i.QE_Project file +// +- parseProjectFile +{ + char *path; + int rtn; + + path = [preferences_i getProjectPath]; + if (!path || !path[0] || access(path,0)) + { + rtn = NXRunAlertPanel("Project Error!", + "A default project has not been found.\n" + , "Open Project", NULL, NULL); + if ([self openProject] == nil) + while (1) // can't run without a project + [NXApp terminate: self]; + return self; + } + + [self openProjectFile:path]; + return self; +} + +// +// Loads and parses a project file +// +- openProjectFile:(char *)path +{ + FILE *fp; + struct stat s; + + strcpy(path_projectinfo,path); + + projectInfo = NULL; + fp = fopen(path,"r+t"); + if (fp == NULL) + return self; + + stat(path,&s); + lastModified = s.st_mtime; + + projectInfo = [(Dict *)[Dict alloc] initFromFile:fp]; + fclose(fp); + + return self; +} + +- (char *)currentProjectFile +{ + return path_projectinfo; +} + +// +// Open a project file +// +- openProject +{ + char path[128]; + id openpanel; + int rtn; + char *projtypes[2] = {"qpr",NULL}; + char **filenames; + char *dir; + + openpanel = [OpenPanel new]; + [openpanel allowMultipleFiles:NO]; + [openpanel chooseDirectories:NO]; + rtn = [openpanel runModalForTypes:projtypes]; + if (rtn == NX_OKTAG) + { + (const char *const *)filenames = [openpanel filenames]; + dir = (char *)[openpanel directory]; + sprintf(path,"%s/%s",dir,filenames[0]); + strcpy(path_projectinfo,path); + [self openProjectFile:path]; + return self; + } + + return nil; +} + + +// +// Search for a string in a List of strings +// +- (int)searchForString:(char *)str in:(id)obj +{ + int i; + int max; + char *s; + + max = [obj count]; + for (i = 0;i < max; i++) + { + s = (char *)[obj elementAt:i]; + if (!strcmp(s,str)) + return 1; + } + return 0; +} + +- (char *)getMapDirectory +{ + return path_mapdirectory; +} + +- (char *)getFinalMapDirectory +{ + return path_finalmapdir; +} + +- (char *)getProgDirectory +{ + return path_progdir; +} + + +// +// Return the WAD name for cmd-8 +// +- (char *)getWAD8 +{ + if (!path_wad8[0]) + return NULL; + return path_wad8; +} + +// +// Return the WAD name for cmd-9 +// +- (char *)getWAD9 +{ + if (!path_wad9[0]) + return NULL; + return path_wad9; +} + +// +// Return the WAD name for cmd-0 +// +- (char *)getWAD0 +{ + if (!path_wad0[0]) + return NULL; + return path_wad0; +} + +// +// Return the FULLVIS cmd string +// +- (char *)getFullVisCmd +{ + if (!string_fullvis[0]) + return NULL; + return string_fullvis; +} + +// +// Return the FASTVIS cmd string +// +- (char *)getFastVisCmd +{ + if (!string_fastvis[0]) + return NULL; + return string_fastvis; +} + +// +// Return the NOVIS cmd string +// +- (char *)getNoVisCmd +{ + if (!string_novis[0]) + return NULL; + return string_novis; +} + +// +// Return the RELIGHT cmd string +// +- (char *)getRelightCmd +{ + if (!string_relight[0]) + return NULL; + return string_relight; +} + +// +// Return the LEAKTEST cmd string +// +- (char *)getLeaktestCmd +{ + if (!string_leaktest[0]) + return NULL; + return string_leaktest; +} + +- (char *)getEntitiesCmd +{ + if (!string_entities[0]) + return NULL; + return string_entities; +} + +@end + +//==================================================== +// C Functions +//==================================================== + +// +// Change a character to a different char in a string +// +void changeString(char cf,char ct,char *string) +{ + int j; + + for (j = 0;j < strlen(string);j++) + if (string[j] == cf) + string[j] = ct; +} + + diff --git a/QuakeEd/QuakeEd.h b/QuakeEd/QuakeEd.h new file mode 100644 index 0000000..e7a20df --- /dev/null +++ b/QuakeEd/QuakeEd.h @@ -0,0 +1,98 @@ + +extern id quakeed_i; + +extern BOOL filter_light, filter_path, filter_entities; +extern BOOL filter_clip_brushes, filter_water_brushes, filter_world; + +extern UserPath *upath; + +extern id g_cmd_out_i; + +double I_FloatTime (void); + +void NopSound (void); + +void qprintf (char *fmt, ...); // prints text to cmd_out_i + +@interface QuakeEd : Window +{ + BOOL dirty; + char filename[1024]; // full path with .map extension + +// UI objects + id brushcount_i; + id entitycount_i; + id regionbutton_i; + + id show_coordinates_i; + id show_names_i; + + id filter_light_i; + id filter_path_i; + id filter_entities_i; + id filter_clip_i; + id filter_water_i; + id filter_world_i; + + id cmd_in_i; // text fields + id cmd_out_i; + + id xy_drawmode_i; // passed over to xyview after init +} + +- setDefaultFilename; +- (char *)currentFilename; + +- updateAll; // when a model has been changed +- updateCamera; // when the camera has moved +- updateXY; +- updateZ; + +- updateAll:sender; + +- newinstance; // force next flushwindow to clear all instance drawing +- redrawInstance; // erase and redraw all instance now + +- appDidInit:sender; +- appWillTerminate:sender; + +- openProject:sender; + +- textCommand: sender; + +- applyRegion: sender; + +- (BOOL)dirty; + +- clear: sender; +- centerCamera: sender; +- centerZChecker: sender; + +- changeXYLookUp: sender; + +- setBrushRegion: sender; +- setXYRegion: sender; + +- open: sender; +- save: sender; +- saveAs: sender; + +- doOpen: (char *)fname; + +- saveBSP:(char *)cmdline dialog:(BOOL)wt; + +- BSP_Full: sender; +- BSP_FastVis: sender; +- BSP_NoVis: sender; +- BSP_relight: sender; +- BSP_stop: sender; +- BSP_entities: sender; + +// +// UI querie for other objects +// +- (BOOL)showCoordinates; +- (BOOL)showNames; + +@end + diff --git a/QuakeEd/QuakeEd.iconheader b/QuakeEd/QuakeEd.iconheader new file mode 100644 index 0000000..d0a0cd3 --- /dev/null +++ b/QuakeEd/QuakeEd.iconheader @@ -0,0 +1,2 @@ +F QuakeEd.app QuakeEd app +F QuakeEd QuakeEd app diff --git a/QuakeEd/QuakeEd.m b/QuakeEd/QuakeEd.m new file mode 100644 index 0000000..f8037d1 --- /dev/null +++ b/QuakeEd/QuakeEd.m @@ -0,0 +1,1026 @@ + +#import "qedefs.h" + +id quakeed_i; +id entclasses_i; + +id g_cmd_out_i; + +BOOL autodirty; +BOOL filter_light, filter_path, filter_entities; +BOOL filter_clip_brushes, filter_water_brushes, filter_world; + +BOOL running; + +int bsppid; + +#if 0 +// example command strings + +char *fullviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis $2\""; +char *fastviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis -fast $2\""; +char *noviscmd = "rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2\""; +char *relightcmd = "rsh satan \"/LocalApps/light $2\""; +char *leakcmd = "rsh satan \"/LocalApps/qbsp -mark -notjunc $1 $2\""; +#endif + +void NopSound (void) +{ + NXBeep (); +} + +UserPath *upath; + + +void My_Malloc_Error (int code) +{ +// recursive toast Error ("Malloc error: %i\n", code); + write (1, "malloc error!\n", strlen("malloc error!\n")+1); +} + +/* +=============== +AutoSave + +Every five minutes, save a modified map +=============== +*/ +void AutoSave(DPSTimedEntry tag, double now, void *userData) +{ +// automatic backup + if (autodirty) + { + autodirty = NO; + [map_i writeMapFile: FN_AUTOSAVE useRegion: NO]; + } + [map_i writeStats]; +} + + +void DisplayCmdOutput (void) +{ + char *buffer; + + LoadFile (FN_CMDOUT, (void **)&buffer); + unlink (FN_CMDOUT); + [project_i addToOutput:buffer]; + free (buffer); + + if ([preferences_i getShowBSP]) + [inspcontrol_i changeInspectorTo:i_output]; + + [preferences_i playBspSound]; + + NXPing (); +} + +/* +=============== +CheckCmdDone + +See if the BSP is done +=============== +*/ +DPSTimedEntry cmdte; +void CheckCmdDone(DPSTimedEntry tag, double now, void *userData) +{ + union wait statusp; + struct rusage rusage; + + if (!wait4(bsppid, &statusp, WNOHANG, &rusage)) + return; + DisplayCmdOutput (); + bsppid = 0; + DPSRemoveTimedEntry( cmdte ); +} + +//============================================================================ + +@implementation QuakeEd + +/* +=============== +init +=============== +*/ +- initContent:(const NXRect *)contentRect +style:(int)aStyle +backing:(int)backingType +buttonMask:(int)mask +defer:(BOOL)flag +{ + [super initContent:contentRect + style:aStyle + backing:backingType + buttonMask:mask + defer:flag]; + + [self addToEventMask: + NX_RMOUSEDRAGGEDMASK|NX_LMOUSEDRAGGEDMASK]; + + malloc_error(My_Malloc_Error); + + quakeed_i = self; + dirty = autodirty = NO; + + DPSAddTimedEntry(5*60, AutoSave, self, NX_BASETHRESHOLD); + + upath = newUserPath (); + + return self; +} + +- setDefaultFilename +{ + strcpy (filename, FN_TEMPSAVE); + [self setTitleAsFilename:filename]; + + return self; +} + + +- (BOOL)dirty +{ + return dirty; +} + +/* +=============================================================================== + + DISPLAY UPDATING (handles both camera and XYView) + +=============================================================================== +*/ + +BOOL updateinflight; + +BOOL clearinstance; + +BOOL updatexy; +BOOL updatez; +BOOL updatecamera; + +void postappdefined (void) +{ + NXEvent ev; + + if (updateinflight) + return; + +// post an event at the end of the que + ev.type = NX_APPDEFINED; + if (DPSPostEvent(&ev, 0) == -1) + printf ("WARNING: DPSPostEvent: full\n"); +//printf ("posted\n"); + updateinflight = YES; +} + + +int c_updateall; +- updateAll // when a model has been changed +{ + updatecamera = updatexy = updatez = YES; + c_updateall++; + postappdefined (); + return self; +} + +- updateAll:sender +{ + [self updateAll]; + return self; +} + +- updateCamera // when the camera has moved +{ + updatecamera = YES; + clearinstance = YES; + + postappdefined (); + return self; +} + +- updateXY +{ + updatexy = YES; + postappdefined (); + return self; +} + +- updateZ +{ + updatez = YES; + postappdefined (); + return self; +} + + +- newinstance +{ + clearinstance = YES; + return self; +} + +- redrawInstance +{ + clearinstance = YES; + [self flushWindow]; + return self; +} + +/* +=============== +flushWindow + +instance draw the brush after each flush +=============== +*/ +-flushWindow +{ + [super flushWindow]; + + if (!running || in_error) + return self; // don't lock focus before nib is finished loading + + if (_flushDisabled) + return self; + + [cameraview_i lockFocus]; + if (clearinstance) + { + PSnewinstance (); + clearinstance = NO; + } + + PSsetinstance (1); + linestart (0,0,0); + [map_i makeSelectedPerform: @selector(CameraDrawSelf)]; + [clipper_i cameraDrawSelf]; + lineflush (); + PSsetinstance (0); + [cameraview_i unlockFocus]; + + [xyview_i lockFocus]; + PSsetinstance (1); + linestart (0,0,0); + [map_i makeSelectedPerform: @selector(XYDrawSelf)]; + lineflush (); + [cameraview_i XYDrawSelf]; + [zview_i XYDrawSelf]; + [clipper_i XYDrawSelf]; + PSsetinstance (0); + [xyview_i unlockFocus]; + + [zview_i lockFocus]; + PSsetinstance (1); + [map_i makeSelectedPerform: @selector(ZDrawSelf)]; + [cameraview_i ZDrawSelf]; + [clipper_i ZDrawSelf]; + PSsetinstance (0); + [zview_i unlockFocus]; + + return self; +} + + +/* +============================================================================== + +App delegate methods + +============================================================================== +*/ + +- applicationDefined:(NXEvent *)theEvent +{ + NXEvent ev, *evp; + + updateinflight = NO; + +//printf ("serviced\n"); + +// update screen + evp = [NXApp peekNextEvent:-1 into:&ev]; + if (evp) + { + postappdefined(); + return self; + } + + + [self disableFlushWindow]; + + if ([map_i count] != [entitycount_i intValue]) + [entitycount_i setIntValue: [map_i count]]; + if ([[map_i currentEntity] count] != [brushcount_i intValue]) + [brushcount_i setIntValue: [[map_i currentEntity] count]]; + + if (updatecamera) + [cameraview_i display]; + if (updatexy) + [xyview_i display]; + if (updatez) + [zview_i display]; + + updatecamera = updatexy = updatez = NO; + + [self reenableFlushWindow]; + [self flushWindow]; + +// NXPing (); + + return self; +} + +- appDidInit:sender +{ + NXScreen const *screens; + int screencount; + + running = YES; + g_cmd_out_i = cmd_out_i; // for qprintf + + [preferences_i readDefaults]; + [project_i initProject]; + + [xyview_i setModeRadio: xy_drawmode_i]; // because xy view is inside + // scrollview and can't be + // connected directly in IB + + [self setFrameAutosaveName:"EditorWinFrame"]; + [self clear: self]; + +// go to my second monitor + [NXApp getScreens:&screens count:&screencount]; + if (screencount == 2) + [self moveTopLeftTo:0 : screens[1].screenBounds.size.height + screen:screens+1]; + + [self makeKeyAndOrderFront: self]; + +//[self doOpen: "/raid/quake/id1_/maps/amlev1.map"]; // DEBUG + [map_i newMap]; + + qprintf ("ready."); + +//malloc_debug(-1); // DEBUG + + return self; +} + +- appWillTerminate:sender +{ +// FIXME: save dialog if dirty + return self; +} + + +//=========================================================================== + +- textCommand: sender +{ + char const *t; + + t = [sender stringValue]; + + if (!strcmp (t, "texname")) + { + texturedef_t *td; + id b; + + b = [map_i selectedBrush]; + if (!b) + { + qprintf ("nothing selected"); + return self; + } + td = [b texturedef]; + qprintf (td->texture); + return self; + } + else + qprintf ("Unknown command\n"); + return self; +} + + +- openProject:sender +{ + [project_i openProject]; + return self; +} + + +- clear: sender +{ + [map_i newMap]; + + [self updateAll]; + [regionbutton_i setIntValue: 0]; + [self setDefaultFilename]; + + return self; +} + + +- centerCamera: sender +{ + NXRect sbounds; + + [[xyview_i superview] getBounds: &sbounds]; + + sbounds.origin.x += sbounds.size.width/2; + sbounds.origin.y += sbounds.size.height/2; + + [cameraview_i setXYOrigin: &sbounds.origin]; + [self updateAll]; + + return self; +} + +- centerZChecker: sender +{ + NXRect sbounds; + + [[xyview_i superview] getBounds: &sbounds]; + + sbounds.origin.x += sbounds.size.width/2; + sbounds.origin.y += sbounds.size.height/2; + + [zview_i setPoint: &sbounds.origin]; + [self updateAll]; + + return self; +} + +- changeXYLookUp: sender +{ + if ([sender intValue]) + { + xy_viewnormal[2] = 1; + } + else + { + xy_viewnormal[2] = -1; + } + [self updateAll]; + return self; +} + +/* +============================================================================== + +REGION MODIFICATION + +============================================================================== +*/ + + +/* +================== +applyRegion: +================== +*/ +- applyRegion: sender +{ + filter_clip_brushes = [filter_clip_i intValue]; + filter_water_brushes = [filter_water_i intValue]; + filter_light = [filter_light_i intValue]; + filter_path = [filter_path_i intValue]; + filter_entities = [filter_entities_i intValue]; + filter_world = [filter_world_i intValue]; + + if (![regionbutton_i intValue]) + { + region_min[0] = region_min[1] = region_min[2] = -9999; + region_max[0] = region_max[1] = region_max[2] = 9999; + } + + [map_i makeGlobalPerform: @selector(newRegion)]; + + [self updateAll]; + + return self; +} + +- setBrushRegion: sender +{ + id b; + +// get the bounds of the current selection + + if ([map_i numSelected] != 1) + { + qprintf ("must have a single brush selected"); + return self; + } + + b = [map_i selectedBrush]; + [b getMins: region_min maxs: region_max]; + [b remove]; + +// turn region on + [regionbutton_i setIntValue: 1]; + [self applyRegion: self]; + + return self; +} + +- setXYRegion: sender +{ + NXRect bounds; + +// get xy size + [[xyview_i superview] getBounds: &bounds]; + + region_min[0] = bounds.origin.x; + region_min[1] = bounds.origin.y; + region_min[2] = -99999; + region_max[0] = bounds.origin.x + bounds.size.width; + region_max[1] = bounds.origin.y + bounds.size.height; + region_max[2] = 99999; + +// turn region on + [regionbutton_i setIntValue: 1]; + [self applyRegion: self]; + + return self; +} + +// +// UI querie for other objects +// +- (BOOL)showCoordinates +{ + return [show_coordinates_i intValue]; +} + +- (BOOL)showNames +{ + return [show_names_i intValue]; +} + + +/* +============================================================================== + +BSP PROCESSING + +============================================================================== +*/ + +void ExpandCommand (char *in, char *out, char *src, char *dest) +{ + while (*in) + { + if (in[0] == '$') + { + if (in[1] == '1') + { + strcpy (out, src); + out += strlen(src); + } + else if (in[1] == '2') + { + strcpy (out, dest); + out += strlen(dest); + } + in += 2; + continue; + } + *out++ = *in++; + } + *out = 0; +} + + +/* +============= +saveBSP +============= +*/ +- saveBSP:(char *)cmdline dialog:(BOOL)wt +{ + char expandedcmd[1024]; + char mappath[1024]; + char bsppath[1024]; + int oldLightFilter; + int oldPathFilter; + char *destdir; + + if (bsppid) + { + NXBeep(); + return self; + } + +// +// turn off the filters so all entities get saved +// + oldLightFilter = [filter_light_i intValue]; + oldPathFilter = [filter_path_i intValue]; + [filter_light_i setIntValue:0]; + [filter_path_i setIntValue:0]; + [self applyRegion: self]; + + if ([regionbutton_i intValue]) + { + strcpy (mappath, filename); + StripExtension (mappath); + strcat (mappath, ".reg"); + [map_i writeMapFile: mappath useRegion: YES]; + wt = YES; // allways pop the dialog on region ops + } + else + strcpy (mappath, filename); + +// save the entire thing, just in case there is a problem + [self save: self]; + + [filter_light_i setIntValue:oldLightFilter]; + [filter_path_i setIntValue:oldPathFilter]; + [self applyRegion: self]; + +// +// write the command to the bsp host +// + destdir = [project_i getFinalMapDirectory]; + + strcpy (bsppath, destdir); + strcat (bsppath, "/"); + ExtractFileBase (mappath, bsppath + strlen(bsppath)); + strcat (bsppath, ".bsp"); + + ExpandCommand (cmdline, expandedcmd, mappath, bsppath); + + strcat (expandedcmd, " > "); + strcat (expandedcmd, FN_CMDOUT); + strcat (expandedcmd, "\n"); + printf ("system: %s", expandedcmd); + + [project_i addToOutput: "\n\n========= BUSY =========\n\n"]; + [project_i addToOutput: expandedcmd]; + + if ([preferences_i getShowBSP]) + [inspcontrol_i changeInspectorTo:i_output]; + + if (wt) + { + id panel; + + panel = NXGetAlertPanel("BSP In Progress",expandedcmd,NULL,NULL,NULL); + [panel makeKeyAndOrderFront:NULL]; + system(expandedcmd); + NXFreeAlertPanel(panel); + [self makeKeyAndOrderFront:NULL]; + DisplayCmdOutput (); + } + else + { + cmdte = DPSAddTimedEntry(1, CheckCmdDone, self, NX_BASETHRESHOLD); + if (! (bsppid = fork ()) ) + { + system (expandedcmd); + exit (0); + } + } + + return self; +} + + +- BSP_Full: sender +{ + [self saveBSP:[project_i getFullVisCmd] dialog: NO]; + return self; +} + +- BSP_FastVis: sender +{ + [self saveBSP:[project_i getFastVisCmd] dialog: NO]; + return self; +} + +- BSP_NoVis: sender +{ + [self saveBSP:[project_i getNoVisCmd] dialog: NO]; + return self; +} + +- BSP_relight: sender +{ + [self saveBSP:[project_i getRelightCmd] dialog: NO]; + return self; +} + +- BSP_entities: sender +{ + [self saveBSP:[project_i getEntitiesCmd] dialog: NO]; + return self; +} + +- BSP_stop: sender +{ + if (!bsppid) + { + NXBeep(); + return self; + } + + kill (bsppid, 9); + CheckCmdDone (cmdte, 0, NULL); + [project_i addToOutput: "\n\n========= STOPPED =========\n\n"]; + + return self; +} + + + +/* +============== +doOpen: + +Called by open or the project panel +============== +*/ +- doOpen: (char *)fname; +{ + strcpy (filename, fname); + + [map_i readMapFile:filename]; + + [regionbutton_i setIntValue: 0]; + [self setTitleAsFilename:fname]; + [self updateAll]; + + qprintf ("%s loaded\n", fname); + + return self; +} + + +/* +============== +open +============== +*/ +- open: sender; +{ + id openpanel; + static char *suffixlist[] = {"map", 0}; + + openpanel = [OpenPanel new]; + + if ( [openpanel + runModalForDirectory: [project_i getMapDirectory] + file: "" + types: suffixlist] != NX_OKTAG) + return self; + + [self doOpen: (char *)[openpanel filename]]; + + return self; +} + + +/* +============== +save: +============== +*/ +- save: sender; +{ + char backup[1024]; + +// force a name change if using tempname + if (!strcmp (filename, FN_TEMPSAVE) ) + return [self saveAs: self]; + + dirty = autodirty = NO; + + strcpy (backup, filename); + StripExtension (backup); + strcat (backup, ".bak"); + rename (filename, backup); // copy old to .bak + + [map_i writeMapFile: filename useRegion: NO]; + + return self; +} + + +/* +============== +saveAs +============== +*/ +- saveAs: sender; +{ + id panel_i; + char dir[1024]; + + panel_i = [SavePanel new]; + ExtractFileBase (filename, dir); + [panel_i setRequiredFileType: "map"]; + if ( [panel_i runModalForDirectory:[project_i getMapDirectory] file: dir] != NX_OKTAG) + return self; + + strcpy (filename, [panel_i filename]); + + [self setTitleAsFilename:filename]; + + [self save: self]; + + return self; +} + + +/* +=============================================================================== + + OTHER METHODS + +=============================================================================== +*/ + + +// +// AJR - added this for Project info +// +- (char *)currentFilename +{ + return filename; +} + +- deselect: sender +{ + if ([clipper_i hide]) // first click hides clipper only + return [self updateAll]; + + [map_i setCurrentEntity: [map_i objectAt: 0]]; // make world selected + [map_i makeSelectedPerform: @selector(deselect)]; + [self updateAll]; + + return self; +} + + +/* +=============== +keyDown +=============== +*/ + +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf + +- keyDown:(NXEvent *)theEvent +{ + int ch; + +// function keys + switch (theEvent->data.key.keyCode) + { + case 60: // F2 + [cameraview_i setDrawMode: dr_wire]; + qprintf ("wire draw mode"); + return self; + case 61: // F3 + [cameraview_i setDrawMode: dr_flat]; + qprintf ("flat draw mode"); + return self; + case 62: // F4 + [cameraview_i setDrawMode: dr_texture]; + qprintf ("texture draw mode"); + return self; + + case 63: // F5 + [xyview_i setDrawMode: dr_wire]; + qprintf ("wire draw mode"); + return self; + case 64: // F6 + qprintf ("texture draw mode"); + return self; + + case 66: // F8 + [cameraview_i homeView: self]; + return self; + + case 88: // F12 + [map_i subtractSelection: self]; + return self; + + case 106: // page up + [cameraview_i upFloor: self]; + return self; + + case 107: // page down + [cameraview_i downFloor: self]; + return self; + + case 109: // end + [self deselect: self]; + return self; + } + +// portable things + ch = tolower(theEvent->data.key.charCode); + + switch (ch) + { + case KEY_RIGHTARROW: + case KEY_LEFTARROW: + case KEY_UPARROW: + case KEY_DOWNARROW: + case 'a': + case 'z': + case 'd': + case 'c': + case '.': + case ',': + [cameraview_i _keyDown: theEvent]; + break; + + case 27: // escape + autodirty = dirty = YES; + [self deselect: self]; + return self; + + case 127: // delete + autodirty = dirty = YES; + [map_i makeSelectedPerform: @selector(remove)]; + [clipper_i hide]; + [self updateAll]; + break; + + case '/': + [clipper_i flipNormal]; + [self updateAll]; + break; + + case 13: // enter + [clipper_i carve]; + [self updateAll]; + qprintf ("carved brush"); + break; + + case ' ': + [map_i cloneSelection: self]; + break; + + +// +// move selection keys +// + case '2': + VectorCopy (vec3_origin, sb_translate); + sb_translate[1] = -[xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + case '8': + VectorCopy (vec3_origin, sb_translate); + sb_translate[1] = [xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + + case '4': + VectorCopy (vec3_origin, sb_translate); + sb_translate[0] = -[xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + case '6': + VectorCopy (vec3_origin, sb_translate); + sb_translate[0] = [xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + + case '-': + VectorCopy (vec3_origin, sb_translate); + sb_translate[2] = -[xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + case '+': + VectorCopy (vec3_origin, sb_translate); + sb_translate[2] = [xyview_i gridsize]; + [map_i makeSelectedPerform: @selector(translate)]; + [self updateAll]; + break; + + default: + qprintf ("undefined keypress"); + NopSound (); + break; + } + + return self; +} + + +@end diff --git a/QuakeEd/QuakeEd_main.m b/QuakeEd/QuakeEd_main.m new file mode 100644 index 0000000..b8d4077 --- /dev/null +++ b/QuakeEd/QuakeEd_main.m @@ -0,0 +1,15 @@ +/* Generated by the NeXT Project Builder + NOTE: Do NOT change this file -- Project Builder maintains it. +*/ + +#import + +void main(int argc, char *argv[]) { + + [Application new]; + if ([NXApp loadNibSection:"QuakeEd.nib" owner:NXApp withNames:NO]) + [NXApp run]; + + [NXApp free]; + exit(0); +} diff --git a/QuakeEd/README b/QuakeEd/README new file mode 100644 index 0000000..0e5e91e --- /dev/null +++ b/QuakeEd/README @@ -0,0 +1,33 @@ + +5/18/96 + +This is a dump of the current source code for QuakeEd, our map editing application. + +This does not include everything necessary to build maps. There are graphics files, prog files, and other utilities needed. I plan on releasing a full development set of tools after the game ships. This is just intended to help out anyone working on their own map editor. + +This is a NEXTSTEP application, so hardly anyone is going to be able to use the code as is. This is not an OPENSTEP application. It doesn't even use the foundation kit, so porting to gnustep or openstep-solaris/mach/nt would not be trivial. + +There are lots of mixed case and >8 character filenames, so I'm using unix gnutar (compressed) format. + +Because most people won't have access to a NEXTSTEP machine, I took pictures of some of the more important stuff from interface builder: + +mainwindow.tiff : a screenshot of the primary window +inspectors.tiff : a screenshot of the important inspector views +help.txt : a dump of the (minimal) help inspector's contents. + +I included some sample data to help you follow the code: + +quake.qpr : our current project file +jrbase1.map : a sample map +triggers.qc : a sample qc source file that includes some /*QUAKED comments + +There will not be any major changes to this code base. I am eagerly looking forward to writing a brand new editor for windows NT + open GL as soon as Quake ships. + +This application was really not a very good fit for NEXTSTEP. The display postscript model fundamentally doesn't fit very well with what we need here -- if you run in an 8 bit color mode, the line drawing runs at an ok speed, but the texture view goes half the speed it should as it dithers from 24 bit color down to 8 bit. If you run in 24 bit color mode, you get less screen real estate and significantly slower line drawing as a 3 megabyte XY view is flushed. Sigh. If anyone does actually run this on NEXTSTEP be advised that you want a fast machine. I never had the time to properly optimize QuakeEd. + +The texture view rendering code in here is crap. Anyone coding a new editor is strongly advised to just use an available optimized library, like open GL or direct 3D. + + +John Carmack +Id Software +johnc@idsoftware.com diff --git a/QuakeEd/SetBrush.h b/QuakeEd/SetBrush.h new file mode 100644 index 0000000..44e0de5 --- /dev/null +++ b/QuakeEd/SetBrush.h @@ -0,0 +1,158 @@ + + +#define MAX_FACES 16 + +typedef float vec5_t[5]; + +typedef struct +{ + int numpoints; + vec5_t points[8]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ +// implicit rep + vec3_t planepts[3]; + texturedef_t texture; + +// cached rep + plane_t plane; + qtexture_t *qtexture; + float light; // 0 - 1.0 + winding_t *w; +} face_t; + +#define ON_EPSILON 0.1 +#define FP_EPSILON 0.01 +#define VECTOR_EPSILON 0.0001 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + + +winding_t *ClipWinding (winding_t *in, plane_t *split); +winding_t *CopyWinding (winding_t *w); +winding_t *NewWinding (int points); + + +@interface SetBrush : Object +{ + BOOL regioned; // not active + BOOL selected; + + BOOL invalid; // not a proper polyhedron + + id parent; // the entity this brush is in + vec3_t bmins, bmaxs; + vec3_t entitycolor; + int numfaces; + face_t faces[MAX_FACES]; +} + +- initOwner: own mins:(float *)mins maxs:(float *)maxs texture:(texturedef_t *)tex; +- initFromTokens: own; +- setMins:(float *)mins maxs:(float *)maxs; + +- parent; +- setParent: (id)p; + +- setEntityColor: (vec3_t)color; + +- calcWindings; + +- writeToFILE: (FILE *)f region: (BOOL)reg; + +- (BOOL)selected; +- (BOOL)regioned; +- setSelected: (BOOL)s; +- setRegioned: (BOOL)s; + +- getMins: (vec3_t)mins maxs: (vec3_t)maxs; + +- (BOOL)containsPoint: (vec3_t)pt; + +- freeWindings; +- removeIfInvalid; + +extern vec3_t region_min, region_max; +- newRegion; + +- (texturedef_t *)texturedef; +- (texturedef_t *)texturedefForFace: (int)f; +- setTexturedef: (texturedef_t *)tex; +- setTexturedef: (texturedef_t *)tex forFace:(int)f; + +- XYDrawSelf; +- ZDrawSelf; +- CameraDrawSelf; +- XYRenderSelf; +- CameraRenderSelf; + +- hitByRay: (vec3_t)p1 : (vec3_t) p2 : (float *)time : (int *)face; + +// +// single brush actions +// +extern int numcontrolpoints; +extern float *controlpoints[MAX_FACES*3]; +- getZdragface: (vec3_t)dragpoint; +- getXYdragface: (vec3_t)dragpoint; +- getXYShearPoints: (vec3_t)dragpoint; + +- addFace: (face_t *)f; + +// +// multiple brush actions +// +- carveByClipper; + +extern vec3_t sb_translate; +- translate; + +extern id carve_in, carve_out; +- select; +- deselect; +- remove; +- flushTextures; + +extern vec3_t sb_mins, sb_maxs; +- addToBBox; + +extern vec3_t sel_x, sel_y, sel_z; +extern vec3_t sel_org; +- transform; + +- flipNormals; + +- carve; +- setCarveVars; + +extern id sb_newowner; +- moveToEntity; + +- takeCurrentTexture; + +extern vec3_t select_min, select_max; +- selectPartial; +- selectComplete; +- regionPartial; +- regionComplete; + +extern float sb_floor_dir, sb_floor_dist; +- feetToFloor; + +- (int) getNumBrushFaces; +- (face_t *)getBrushFace: (int)which; + +@end + diff --git a/QuakeEd/SetBrush.m b/QuakeEd/SetBrush.m new file mode 100644 index 0000000..855cb78 --- /dev/null +++ b/QuakeEd/SetBrush.m @@ -0,0 +1,2034 @@ +#import "qedefs.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) + Error ("CheckFace: no winding"); + + if (w->numpoints < 3) + Error ("CheckFace: %i points",w->numpoints); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->points[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + 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) + Error ("CheckFace: point off plane"); + + // check the edge isn't degenerate + p2 = w->points[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + 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 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->points[j], edgenormal); + if (d > edgedist) + Error ("CheckFace: non-convex"); + } + } +} + + +/* +============================================================================= + + TURN PLANES INTO GROUPS OF FACES + +============================================================================= +*/ + + +/* +================== +NewWinding +================== +*/ +winding_t *NewWinding (int points) +{ + winding_t *w; + int size; + + if (points > MAX_POINTS_ON_WINDING) + Error ("NewWinding: %i points", points); + + size = (int)((winding_t *)0)->points[points]; + w = malloc (size); + memset (w, 0, size); + + return w; +} + + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + size = (int)((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 ; inumpoints ; 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 ; inumpoints ; 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) + 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) + 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); + VectorMA (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 ; iplanepts[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 ; iw = w; + if (w) + { + CheckFace (f); + for (j=0 ; jnumpoints ; 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 ; iplanepts[i][j] = atoi(token); + } + + GetToken (false); + if (strcmp (token, ")") ) + Error ("parsing map file"); + } + + GetToken (false); + strcpy (f->texture.texture, token); + GetToken (false); + f->texture.shift[0] = atof(token); + GetToken (false); + f->texture.shift[1] = atof(token); + GetToken (false); + f->texture.rotate = atof(token); + GetToken (false); + f->texture.scale[0] = atof(token); + GetToken (false); + f->texture.scale[1] = atof(token); + +#if 0 + flags = atoi(token); + + 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 ; iplanepts[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 numfaces) + 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= 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 ; iw) + 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) + 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[64]; + + strcpy (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 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 -VECTOR_EPSILON) + continue; + + XYmoveto (w->points[w->numpoints-1]); + for (j=0 ; jnumpoints ; 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: (NXPoint *)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 objectAt: 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 ; ipoints[w->numpoints-1]); + for (j=0 ; jnumpoints ; j++) + CameraLineto (w->points[j]); + } + return self; +} + + +/* +=========== +XYRenderSelf +=========== +*/ +- XYRenderSelf +{ + int i; + + if ([self fakeBrush: @selector(XYRenderSelf)]) + return self; + + for (i=0 ; iw; + 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 ; jnumpoints ; j++) + { + onplane[j] = NO; + for (k=0 ; kpoints[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 ; jnumpoints ; 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 ( ; jnumpoints && k != 3 ; j++) + if (!onplane[j]) + { + VectorCopy (w->points[j], f->planepts[k]); + k++; + } + + for (j=0 ; jnumpoints && k != 3 ; j++) + if (!onplane[j]) + { + VectorCopy (w->points[j], f->planepts[k]); + k++; + } + + if (k != 3) + { +// 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 objectAt: 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 respondsTo:@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 sb_maxs[k]) + sb_maxs[k] = bmaxs[k]; + } + + return self; +} + +- flushTextures +{ // call when texture palette changes + int i; + + for (i=0 ; i 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) + 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 = NXMallocCheck()) ) + 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 ; iwidth); + height = LittleLong(qtex->height); + + bm = [[NXBitmapImageRep alloc] + initData: NULL + pixelsWide: width + pixelsHigh: height + bitsPerSample: 8 + samplesPerPixel:3 + hasAlpha: NO + isPlanar: NO + colorSpace: NX_RGBColorSpace + bytesPerRow: width*4 + bitsPerPixel: 32]; + + dest = (unsigned *)[bm data]; + count = width*height; + source = (byte *)qtex + LittleLong(qtex->offsets[0]); + + q = &qtextures[tex_count]; + tex_count++; + + q->width = width; + q->height = height; + q->rep = bm; + q->data = dest; + + tr = tg = tb = 0; + + for (i=0 ; ichan[0]; + tg += ((pixel32_t *)&dest[i])->chan[1]; + tb += ((pixel32_t *)&dest[i])->chan[2]; + } + + q->flatcolor.chan[0] = tr / count; + q->flatcolor.chan[1] = tg / count; + q->flatcolor.chan[2] = tb / count; + q->flatcolor.chan[3] = 0xff; +} + +//============================================================================= + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +/* +================= +TEX_InitFromWad +================= +*/ +void TEX_InitFromWad (char *path) +{ + int i; + char local[1024]; + char newpath[1024]; + byte *wadfile; + wadinfo_t *wadinfo; + lumpinfo_t *lumpinfo; + int numlumps; + float start, stop; + + start = I_FloatTime (); + + strcpy(newpath, [preferences_i getProjectPath]); + strcat(newpath,"/"); + strcat(newpath, path); + +// free any textures + for (i=0 ; inumlumps); + lumpinfo = (lumpinfo_t *)(wadfile + LittleLong (wadinfo->infotableofs)); + + if (strcmp (lumpinfo->name, "PALETTE")) + { + unlink (local); + Error ("TEX_InitFromWad: %s doesn't have palette as 0",path); + } + + TEX_InitPalette (wadfile + LittleLong(lumpinfo->filepos)); + + lumpinfo++; + for (i=1 ; itype != TYP_MIPTEX) + Error ("TEX_InitFromWad: %s is not a miptex!",lumpinfo->name); + CleanupName (lumpinfo->name,qtextures[tex_count].name); + TEX_ImageFromMiptex ( (miptex_t *)(wadfile + + LittleLong(lumpinfo->filepos) )); + } + + free (wadfile); + + stop = I_FloatTime (); + + qprintf ("loaded %s (%5.1f)", local, stop - start); +} + +/* +================= +TEX_NumForName +================= +*/ +qtexture_t *TEX_ForName (char *name) +{ + char newname[16]; + int i; + qtexture_t *q; + + CleanupName (name, newname); + + for (i=0,q = qtextures ; i< tex_count ; i++, q++) + { + if (!strcmp(name, q->name)) + return q; + } + + return &badtex; +} + + + +//=========================================================================== + +@implementation TexturePalette + +- init +{ + [super init]; + texturepalette_i = self; + selectedTexture = -1; + return self; +} + +- display +{ + [[textureView_i superview] display]; + return self; +} + + +- (char *)currentWad +{ + return currentwad; +} + +- initPaletteFromWadfile:(char *)wf +{ + int i; + texpal_t t; + qtexture_t *q; + + strcpy (currentwad, wf); + [map_i makeGlobalPerform: @selector(flushTextures)]; + selectedTexture = -1; + + // Init textures WAD + TEX_InitFromWad(wf); + + // Create STORAGE + if (textureList_i) + [textureList_i empty]; + else + textureList_i = [[Storage alloc] + initCount:0 + elementSize:sizeof(texpal_t) + description:NULL]; + + // Init STORAGE + + for (i = 0,q=qtextures;i < tex_count; i++,q++) + { + t.image = q->rep; + t.r.size.width = [t.image pixelsWide]; + if (t.r.size.width < 64) + t.r.size.width = 64; + t.r.size.height = [t.image pixelsHigh] + TEX_SPACING; + t.name = q->name; + t.index = i; + t.display = 1; + [textureList_i addElement:&t]; + } + + // Calculate size of TextureView + [self alphabetize]; + [self computeTextureViewSize]; + [textureView_i setParent:self]; + [self setSelectedTexture:0]; + + return self; +} + + + +// Return texture STORAGE list +- getList +{ + return textureList_i; +} + +// Alphabetize texture list - reverse order! +- alphabetize +{ + int i; + int max; + texpal_t *t1p; + texpal_t *t2p; + texpal_t t1; + texpal_t t2; + int found; + + max = [textureList_i count]; + found = 1; + while(found) + { + found = 0; + for (i = 0;i < max-1;i++) + { + t1p = [textureList_i elementAt:i]; + t2p = [textureList_i elementAt:i+1]; + if (strcmp(t1p->name,t2p->name) < 0) + { + t1 = *t1p; + t2 = *t2p; + [textureList_i replaceElementAt:i with:&t2]; + [textureList_i replaceElementAt:i+1 with:&t1]; + found = 1; + } + } + } + return self; +} + +- computeTextureViewSize +{ + int i; + int max; + int x; + texpal_t *t; + int y; + id view; + NXRect b; + int maxwidth; + int maxheight; + NXPoint pt; + + max = [textureList_i count]; + y = 0; + maxheight = 0; + x = TEX_INDENT; + + view = [textureView_i superview]; + [view getBounds:&b]; + maxwidth = b.size.width; + + for (i = 0;i < max; i++) + { + t = [textureList_i elementAt:i]; + if (x + t->r.size.width + TEX_INDENT > maxwidth) + { + x = TEX_INDENT; + y += maxheight; + maxheight = 0; + } + if (t->r.size.height > maxheight) + maxheight = t->r.size.height; + t->r.origin.x = x; + t->r.origin.y = y; + x += t->r.size.width + TEX_INDENT; + if (i == max - 1) + y += t->r.size.height; + } + + viewWidth = maxwidth; + viewHeight = y + TEX_SPACING; + [textureView_i sizeTo:viewWidth :viewHeight]; + pt.x = pt.y = 0; + [textureView_i scrollPoint:&pt]; + + return self; +} + +- windowResized +{ + [self computeTextureViewSize]; + return self; +} + +- texturedefChanged: sender +{ + if ([map_i numSelected]) + { + if ( [[map_i currentEntity] modifiable] ) + { + [map_i makeSelectedPerform: @selector(takeCurrentTexture)]; + [quakeed_i updateAll]; + } + else + qprintf ("can't modify spawned entities"); + } + [quakeed_i makeFirstResponder: quakeed_i]; + return self; +} + +- clearTexinfo: sender +{ + [field_Xshift_i setFloatValue:0]; + [field_Yshift_i setFloatValue:0]; + [field_Xscale_i setFloatValue:1]; + [field_Yscale_i setFloatValue:1]; + [field_Rotate_i setFloatValue:0]; + + [self texturedefChanged: self]; + + return self; +} + +// +// Set the selected texture +// +- setSelectedTexture:(int)which +{ + texpal_t *t; + NXRect r; + char string[16]; + +// wipe the fields + [self clearTexinfo: self]; + + if (which != selectedTexture) + { + [textureView_i deselect]; + selectedTexture = which; + t = [textureList_i elementAt:which]; + r = t->r; + r.size.width += TEX_INDENT*2; + r.size.height += TEX_INDENT*2; + r.origin.x -= TEX_INDENT; + r.origin.y -= TEX_INDENT; + [textureView_i scrollRectToVisible:&r]; + [textureView_i display]; + sprintf(string,"%d x %d",(int)t->r.size.width, + (int)t->r.size.height - TEX_SPACING); + [sizeField_i setStringValue:string]; + } + + [self texturedefChanged:self]; + + return self; +} + +// +// Return the selected texture index +// +- (int)getSelectedTexture +{ + return selectedTexture; +} + +// +// Return the original tex_ index of the selected texture +// so the texture info can be indexed from tex_images, etc. +// +- (int)getSelectedTexIndex +{ + texpal_t *t; + + if (selectedTexture == -1) + return -1; + t = [textureList_i elementAt:selectedTexture]; + return t->index; +} + +// +// Return the name of the selected texture +// +- (char *)getSelTextureName +{ + texpal_t *t; + + if (selectedTexture == -1) + return NULL; + t = [textureList_i elementAt:selectedTexture]; + return t->name; +} + +// +// Set selected texture by texture name +// +- setTextureByName:(char *)name +{ + texpal_t *t; + int i; + int max; + + max = [textureList_i count]; + CleanupName(name,name); + for (i = 0;i < max;i++) + { + t = [textureList_i elementAt:i]; + if (!strcmp(t->name,name)) + { + [self setSelectedTexture: i]; + return self; + } + } + return self; +} + +//=================================================== +// +// Action methods +// +//=================================================== + + +// +// Search for texture named in searchField +// +- searchForTexture:sender +{ + int i; + int max; + int len; + char name[32]; + texpal_t *t; + + if (selectedTexture == -1) + return self; + + max = [textureList_i count]; + strcpy(name,(const char *)[sender stringValue]); + [sender setStringValue:strupr(name)]; + len = strlen(name); + + for (i = selectedTexture-1;i >= 0; i--) + { + t = [textureList_i elementAt:i]; + if (!strncmp(t->name,name,len)) + { + [self setTextureByName:t->name]; + [sender selectText:sender]; + [self texturedefChanged:self]; + return self; + } + } + + for (i = max-1;i >= selectedTexture; i--) + { + t = [textureList_i elementAt:i]; + if (!strncmp(t->name,name,len)) + { + [self setTextureByName:t->name]; + [sender selectText:sender]; + [self texturedefChanged:self]; + return self; + } + } + + [self texturedefChanged:self]; + return self; +} + +// +// Set texture def from outside TexturePalette +// +- setTextureDef:(texturedef_t *)td +{ + [self setTextureByName:td->texture]; + + [field_Xshift_i setFloatValue:td->shift[0]]; + [field_Yshift_i setFloatValue:td->shift[1]]; + [field_Xscale_i setFloatValue:td->scale[0]]; + [field_Yscale_i setFloatValue:td->scale[1]]; + [field_Rotate_i setFloatValue:td->rotate]; + + [self texturedefChanged:self]; + + return self; +} + +// +// Return the current texture def to passed * +// +- getTextureDef:(texturedef_t *)td +{ + if (selectedTexture == -1) + { + memset (td, 0, sizeof(*td)); + strcpy (td->texture, "notexture"); + return self; + } + + strncpy(td->texture,[self getSelTextureName],16); + + td->shift[0] = [field_Xshift_i floatValue]; + td->shift[1] = [field_Yshift_i floatValue]; + td->scale[0] = [field_Xscale_i floatValue]; + td->scale[1] = [field_Yscale_i floatValue]; + td->rotate = [field_Rotate_i floatValue]; + + return self; +} + +//============================================================================ + +// +// Change value in a field +// +- changeField:(id)field by:(int)amount +{ + int val; + + val = [field intValue]; + val += amount; + [field setIntValue:val]; + + [self texturedefChanged:self]; + + return self; +} + +// +// Inc/Dec the XShift field +// +- incXShift:sender +{ + [self changeField:field_Xshift_i by:8]; + return self; +} +- decXShift:sender +{ + [self changeField:field_Xshift_i by:-8]; + return self; +} + +// +// Inc/Dec the YShift field +// +- incYShift:sender +{ + [self changeField:field_Yshift_i by:8]; + return self; +} +- decYShift:sender +{ + [self changeField:field_Yshift_i by:-8]; + return self; +} + +// +// Inc/Dec the Rotate field +// +- incRotate:sender +{ + [self changeField:field_Rotate_i by:90]; + return self; +} +- decRotate:sender +{ + [self changeField:field_Rotate_i by:-90]; + return self; +} + +// +// Inc/Dec the Xscale field +// +- incXScale:sender +{ + [field_Xscale_i setIntValue: 1]; + [self texturedefChanged:self]; + return self; +} +- decXScale:sender +{ + [field_Xscale_i setIntValue: -1]; + [self texturedefChanged:self]; + return self; +} + +// +// Inc/Dec the Yscale field +// +- incYScale:sender +{ + [field_Yscale_i setIntValue: 1]; + [self texturedefChanged:self]; + return self; +} +- decYScale:sender +{ + [field_Yscale_i setIntValue: -1]; + [self texturedefChanged:self]; + return self; +} + + +//============================================================================ + + +// +// Search for texture in entire palette +// Return index of texturedef, or -1 if unsuccessful +// +- (int) searchForTextureInPalette:(char *)texture +{ + int i; + int max; + char name[32]; + texpal_t *t; + + if (selectedTexture == -1) + return -1; + + max = [textureList_i count]; + strcpy(name,texture); + + for (i = 0; i < max; i++) + { + t = [textureList_i elementAt:i]; + if (!strcmp(t->name,name)) + return i; + } + return -1; +}; + +// +// Scan thru map & only display textures that are in map +// +- onlyShowMapTextures:sender +{ + int max; + int i; + int j; + id brushes; + SetBrush *b; + int numfaces; + face_t *f; + int index; + + // Turn 'em off + if ([sender intValue]) + { + max = [textureList_i count]; + for (i = 0;i < max; i++) + [self setDisplayFlag:i to:0]; + + brushes = [map_i objectAt:0]; + max = [brushes count]; + for (i = 0;i < max; i++) + { + b = (SetBrush *)[brushes objectAt:i]; + numfaces = [b getNumBrushFaces]; + for (j = 0; j < numfaces; j++) + { + f = [b getBrushFace:j]; + index = [self searchForTextureInPalette:f->texture.texture]; + if (index >= 0) + [self setDisplayFlag:index to:1]; + } + } + } + // Turn 'em on + else + { + max = [textureList_i count]; + for (i = 0;i < max; i++) + [self setDisplayFlag:i to:1]; + } + + [textureView_i display]; + + return self; +} + +- setDisplayFlag:(int)index to:(int)value +{ + texpal_t *tp; + + tp = [textureList_i elementAt:index]; + tp->display = value; + return self; +}; + +@end diff --git a/QuakeEd/TextureView.h b/QuakeEd/TextureView.h new file mode 100644 index 0000000..6b490ca --- /dev/null +++ b/QuakeEd/TextureView.h @@ -0,0 +1,12 @@ + + +@interface TextureView:View +{ + id parent_i; + int deselectIndex; +} + +- setParent:(id)from; +- deselect; + +@end diff --git a/QuakeEd/TextureView.m b/QuakeEd/TextureView.m new file mode 100644 index 0000000..feb3810 --- /dev/null +++ b/QuakeEd/TextureView.m @@ -0,0 +1,152 @@ + +#import "qedefs.h" + +/* + +NOTE: I am specifically not using cached image reps, because the data is also needed for texturing the views, and a cached rep would waste tons of space. + +*/ + +@implementation TextureView + +- init +{ + deselectIndex = -1; + return self; +} + +- setParent:(id)from +{ + parent_i = from; + return self; +} + +- (BOOL)acceptsFirstMouse +{ + return YES; +} + +- drawSelf:(const NXRect *)rects :(int)rectCount +{ + int i; + int max; + id list_i; + texpal_t *t; + int x; + int y; + NXPoint p; + NXRect r; + int selected; + + selected = [parent_i getSelectedTexture]; + list_i = [parent_i getList]; + PSselectfont("Helvetica-Medium",FONTSIZE); + PSrotate(0); + + PSsetgray(NX_LTGRAY); + PSrectfill(rects->origin.x, rects->origin.y, + rects->size.width, rects->size.height); + + if (!list_i) // WADfile didn't init + return self; + + if (deselectIndex != -1) + { + t = [list_i elementAt:deselectIndex]; + r = t->r; + r.origin.x -= TEX_INDENT; + r.origin.y -= TEX_INDENT; + r.size.width += TEX_INDENT*2; + r.size.height += TEX_INDENT*2; + + PSsetgray(NXGrayComponent(NX_COLORLTGRAY)); + PSrectfill(r.origin.x, r.origin.y, + r.size.width, r.size.height); + p = t->r.origin; + p.y += TEX_SPACING; + [t->image drawAt:&p]; + PSsetgray(0); + x = t->r.origin.x; + y = t->r.origin.y + 7; + PSmoveto(x,y); + PSshow(t->name); + PSstroke(); + deselectIndex = -1; + } + + max = [list_i count]; + PSsetgray(0); + + for (i = 0;i < max; i++) + { + t = [list_i elementAt:i]; + r = t->r; + r.origin.x -= TEX_INDENT/2; + r.size.width += TEX_INDENT; + r.origin.y += 4; + if (NXIntersectsRect(&rects[0],&r) == YES && + t->display) + { + if (selected == i) + { + PSsetgray(1); + PSrectfill(r.origin.x,r.origin.y, + r.size.width,r.size.height); + PSsetrgbcolor(1,0,0); + PSrectstroke(r.origin.x, r.origin.y, + r.size.width, r.size.height); + PSsetgray(0); + } + + p = t->r.origin; + p.y += TEX_SPACING; + [t->image drawAt:&p]; + x = t->r.origin.x; + y = t->r.origin.y + 7; + PSmoveto(x,y); + PSshow(t->name); + } + } + PSstroke(); + return self; +} + +- deselect +{ + deselectIndex = [parent_i getSelectedTexture]; + return self; +} + +- mouseDown:(NXEvent *)theEvent +{ + NXPoint loc; + int i; + int max; + int oldwindowmask; + texpal_t *t; + id list; + NXRect r; + + oldwindowmask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK]; + loc = theEvent->location; + [self convertPoint:&loc fromView:NULL]; + + list = [parent_i getList]; + max = [list count]; + for (i = 0;i < max; i++) + { + t = [list elementAt:i]; + r = t->r; + if (NXPointInRect(&loc,&r) == YES) + { + [self deselect]; + [parent_i setSelectedTexture:i]; + break; + } + } + + [window setEventMask:oldwindowmask]; + return self; +} + +@end diff --git a/QuakeEd/Things.h b/QuakeEd/Things.h new file mode 100644 index 0000000..85a8d9f --- /dev/null +++ b/QuakeEd/Things.h @@ -0,0 +1,42 @@ + +#import + +extern id things_i; + +#define ENTITYNAMEKEY "spawn" + +@interface Things:Object +{ + id entity_browser_i; // browser + id entity_comment_i; // scrolling text window + + id prog_path_i; + + int lastSelected; // last row selected in browser + + id keyInput_i; + id valueInput_i; + id flags_i; +} + +- initEntities; + +- newCurrentEntity; +- setSelectedKey:(epair_t *)ep; + +- clearInputs; +- (char *)spawnName; + +// UI targets +- reloadEntityClasses: sender; +- selectEntity: sender; +- doubleClickEntity: sender; + +// Action methods +- addPair:sender; +- delPair:sender; +- setAngle:sender; +- setFlags:sender; + + +@end diff --git a/QuakeEd/Things.m b/QuakeEd/Things.m new file mode 100644 index 0000000..72b0e3d --- /dev/null +++ b/QuakeEd/Things.m @@ -0,0 +1,317 @@ + +#import "qedefs.h" + +id things_i; + +@implementation Things + +- init +{ + [super init]; + + things_i = self; + lastSelected = 0; + + return self; +} + +// +// Load the TEXT object with the entity comment +// +- loadEntityComment:(id)obj +{ + [entity_comment_i selectAll:self]; + [entity_comment_i replaceSel:[obj comments]]; + + return self; +} + + +- initEntities +{ + char *path; + + path = [project_i getProgDirectory]; + + [prog_path_i setStringValue: path]; + + [[EntityClassList alloc] initForSourceDirectory: path]; + + [self loadEntityComment:[entity_classes_i objectAt:lastSelected]]; + [entity_browser_i loadColumnZero]; + [[entity_browser_i matrixInColumn:0] selectCellAt:lastSelected :0]; + + [entity_browser_i setDoubleAction: @selector(doubleClickEntity:)]; + + return self; +} + +- selectEntity: sender +{ + id matr; + + matr = [sender matrixInColumn: 0]; + lastSelected = [matr selectedRow]; + [self loadEntityComment:[entity_classes_i objectAt:lastSelected]]; + [quakeed_i makeFirstResponder: quakeed_i]; + + return self; +} + +- doubleClickEntity: sender +{ + [map_i makeEntity: sender]; + [quakeed_i makeFirstResponder: quakeed_i]; + return self; +} + +- (char *)spawnName +{ + return [[entity_classes_i objectAt:lastSelected] classname]; +} + + +// +// Flush entity classes & reload them! +// +- reloadEntityClasses: sender +{ + EntityClass *ent; + char *path; + + path = (char *)[prog_path_i stringValue]; + if (!path || !path[0]) + { + path = [project_i getProgDirectory]; + [prog_path_i setStringValue: path]; + } + + // Free all entity info in memory... + [entity_classes_i freeObjects]; + [entity_classes_i free]; + + // Now, RELOAD! + [[EntityClassList alloc] initForSourceDirectory: path]; + + lastSelected = 0; + ent = [entity_classes_i objectAt:lastSelected]; + [self loadEntityComment:[entity_classes_i objectAt:lastSelected]]; + + [entity_browser_i loadColumnZero]; + [[entity_browser_i matrixInColumn:0] selectCellAt:lastSelected :0]; + + [self newCurrentEntity]; // in case flags changed + + return self; +} + + +- selectClass: (char *)class +{ + id classent; + + classent = [entity_classes_i classForName:class]; + if (!classent) + return self; + lastSelected = [entity_classes_i indexOf: classent]; + + if (lastSelected < 0) + lastSelected = 0; + + [self loadEntityComment:classent]; + [[entity_browser_i matrixInColumn:0] selectCellAt:lastSelected :0]; + [[entity_browser_i matrixInColumn:0] scrollCellToVisible:lastSelected :0]; + + return self; +} + + +- newCurrentEntity +{ + id ent, classent, cell; + char *classname; + int r, c; + char *flagname; + int flags; + + ent = [map_i currentEntity]; + classname = [ent valueForQKey: "classname"]; + if (ent != [map_i objectAt: 0]) + [self selectClass: classname]; // don't reset for world + classent = [entity_classes_i classForName:classname]; + flagname = [ent valueForQKey: "spawnflags"]; + if (!flagname) + flags = 0; + else + flags = atoi(flagname); + + [flags_i setAutodisplay: NO]; + for (r=0 ; r<4 ; r++) + for (c=0 ; c<3 ; c++) + { + cell = [flags_i cellAt: r : c]; + if (c < 2) + { + flagname = [classent flagName: c*4 + r]; + [cell setTitle: flagname]; + } + [cell setIntValue: (flags & (1<< ((c*4)+r)) ) > 0]; + } + [flags_i setAutodisplay: YES]; + [flags_i display]; + +// [keyInput_i setStringValue: ""]; +// [valueInput_i setStringValue: ""]; + + [keypairview_i calcViewSize]; + [keypairview_i display]; + + [quakeed_i makeFirstResponder: quakeed_i]; + return self; +} + +// +// Clicked in the Keypair view - set as selected +// +- setSelectedKey:(epair_t *)ep; +{ + [keyInput_i setStringValue:ep->key]; + [valueInput_i setStringValue:ep->value]; + [valueInput_i selectText:self]; + return self; +} + +- clearInputs +{ +// [keyInput_i setStringValue: ""]; +// [valueInput_i setStringValue: ""]; + + [quakeed_i makeFirstResponder: quakeed_i]; + return self; +} + +// +// Action methods +// + +-addPair:sender +{ + char *key, *value; + + key = (char *)[keyInput_i stringValue]; + value = (char *)[valueInput_i stringValue]; + + [ [map_i currentEntity] setKey: key toValue: value ]; + + [keypairview_i calcViewSize]; + [keypairview_i display]; + + [self clearInputs]; + [quakeed_i updateXY]; + + return self; +} + +-delPair:sender +{ + [quakeed_i makeFirstResponder: quakeed_i]; + + [ [map_i currentEntity] removeKeyPair: (char *)[keyInput_i stringValue] ]; + + [keypairview_i calcViewSize]; + [keypairview_i display]; + + [self clearInputs]; + + [quakeed_i updateXY]; + + return self; +} + + +// +// Set the key/value fields to "angle