diff --git a/tools/Forge/Bundles/MapEdit/Brush.h b/tools/Forge/Bundles/MapEdit/Brush.h new file mode 100644 index 000000000..04f359e52 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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: (NSPoint *)pt; // return YES if brush handled +- (BOOL)ZmouseDown: (NSPoint *)pt; // return YES if brush handled + +- _keyDown:(NSEvent *)theEvent; + +- (NSPoint)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/tools/Forge/Bundles/MapEdit/CameraView.h b/tools/Forge/Bundles/MapEdit/CameraView.h new file mode 100644 index 000000000..b6569d608 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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 : NSView +{ + float xa, ya, za; + float move; + + float *zbuffer; + unsigned *imagebuffer; + + BOOL angleChange; // JR 6.8.95 + + vec3_t origin; + vec3_t matrix[3]; + + NSPoint dragspot; + + drawmode_t drawmode; + +// UI links + id mode_radio_i; + +} + +- setXYOrigin: (NSPoint *)pt; +- setZOrigin: (float)pt; + +- setOrigin: (vec3_t)org angle: (float)angle; +- getOrigin: (vec3_t)org; + +- (float)yawAngle; + +- matrixFromAngles; +- _keyDown: (NSEvent *)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: (NSPoint *)pt flags:(int)flags; // return YES if brush handled +- (BOOL)ZmouseDown: (NSPoint *)pt flags:(int)flags; // return YES if brush handled + +- upFloor:sender; +- downFloor: sender; + +@end + diff --git a/tools/Forge/Bundles/MapEdit/CameraView.m b/tools/Forge/Bundles/MapEdit/CameraView.m new file mode 100644 index 000000000..56638f086 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/CameraView.m @@ -0,0 +1,971 @@ +#import "qedefs.h" + +id cameraview_i; + +BOOL timedrawing = 0; + +@implementation CameraView + +/* +================== +initWithFrame: +================== +*/ +- initWithFrame:(NSRect)frameRect +{ + int size; + + [super initWithFrame: 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: (NSPoint *)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 selectedColumn]; + [quakeed_i updateCamera]; + return self; +} + +- setDrawMode: (drawmode_t)mode +{ + drawmode = mode; + //XXX[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:NSBackingStoreRetained]; + + planes[0] = (unsigned char *)imagebuffer; + NSDrawBitmap( + _bounds, + r_width, + r_height, + 8, + 3, + 32, + r_width*4, + NO, + NO, + @"RGB", //FIXME what should this be? + (const unsigned char **const)planes + ); + + //XXX NSPing (); + [[self window] setBackingType:NSBackingStoreBuffered]; + + + + return self; +} + + +/* +=================== +drawWire +=================== +*/ +- drawWire: (NSRect)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 + NSEraseRect (rect); + +// draw all entities + linestart (0,0,0); + [map_i makeUnselectedPerform: @selector(CameraDrawSelf)]; + lineflush (); + + return self; +} + +/* +=================== +drawSelf +=================== +*/ +- drawSelf:(NSRect)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) + { + //XXX NSPing (); + 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: (NSPoint *)basept :(vec3_t)movemod : converter +{ + vec3_t originbase; + NSEvent *event = 0; //XXX + NSPoint newpt; +// NSPoint 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] != NSLeftMouseUp && [event type] != NSRightMouseUp) + { + // + // calculate new point + // + newpt = [event locationInWindow]; + newpt = [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 & NS_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]; +/*XXX + event = [NSApp nextEventMatchingMask: NSLeftMouseUpMask + | NSLeftMouseDraggedMask | NSRightMouseUpMask + | NSRightMouseDraggedMask | NSApplicationDefinedMask]; +*/ + if ([event type] == NSKeyDown) + { + [self _keyDown: event]; + [self display]; + goto drawentry; + } + + } + + return self; +} + +//============================================================================ + +/* +=============== +XYmouseDown +=============== +*/ +- (BOOL)XYmouseDown: (NSPoint *)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 & NSAlternateKeyMask) + { // 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: (NSPoint *)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:(NSPoint *)pt +{ + float dx,dy; + NSEvent *event = 0; //XXX + NSPoint newpt; + +// +// modal event loop using instance drawing +// + goto drawentry; + + while ([event type] != NSRightMouseUp) + { + // + // calculate new point + // + newpt = [event locationInWindow]; + newpt = [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]; +/*XXX + event = [NSApp getNextEvent: + NSKeyDownMask | NSRightMouseUpMask | NSRightMouseDraggedMask]; +*/ + if ([event type] == NSKeyDown) + { + [self _keyDown: event]; + [self display]; + goto drawentry; + } + + } + + return self; +} + + +//============================================================================= + +/* +=================== +mouseDown +=================== +*/ +- (void) mouseDown:(NSEvent *)theEvent +{ + NSPoint pt; + int i; + vec3_t p1, p2; + float forward, right, up; + int flags; + + pt = [theEvent locationInWindow]; + + pt = [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 modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask); + +// +// bare click to select a texture +// + if (flags == 0) + { + [map_i getTextureRay: p1 : p2]; + return; + } + +// +// shift click to select / deselect a brush from the world +// + if (flags == NSShiftKeyMask) + { + [map_i selectRay: p1 : p2 : NO]; + return; + } + + +// +// cmd-shift click to set a target/targetname entity connection +// + if (flags == (NSShiftKeyMask|NSCommandKeyMask) ) + { + [map_i entityConnect: p1 : p2]; + return; + } + +// +// alt click = set entire brush texture +// + if (flags == NSAlternateKeyMask) + { + if (drawmode != dr_texture) + { + qprintf ("No texture setting except in texture mode!\n"); + NopSound (); + return; + } + [map_i setTextureRay: p1 : p2 : YES]; + [quakeed_i updateAll]; + return; + } + +// +// ctrl-alt click = set single face texture +// + if (flags == (NSControlKeyMask | NSAlternateKeyMask) ) + { + if (drawmode != dr_texture) + { + qprintf ("No texture setting except in texture mode!\n"); + NopSound (); + return; + } + [map_i setTextureRay: p1 : p2 : NO]; + [quakeed_i updateAll]; + return; + } + + + qprintf ("bad flags for click"); + NopSound (); + + return; +} + +/* +=================== +rightMouseDown +=================== +*/ +-(void) rightMouseDown:(NSEvent *)theEvent +{ + NSPoint pt; + int flags; + + pt = [theEvent locationInWindow]; + + [self convertPoint:pt fromView:NULL]; + + flags = [theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask); + +// +// click = drag camera +// + if (flags == 0) + { + qprintf ("looking"); + [self viewDrag: &pt]; + qprintf (""); + return; + } + + qprintf ("bad flags for click"); + NopSound (); + + return; +} + +/* +=============== +keyDown +=============== +*/ + +#define KEY_RIGHTARROW 0xae +#define KEY_LEFTARROW 0xac +#define KEY_UPARROW 0xad +#define KEY_DOWNARROW 0xaf + + +- _keyDown: (NSEvent *)theEvent +{ + int ch; + + ch = tolower([[theEvent characters] characterAtIndex: 0]); + + 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/tools/Forge/Bundles/MapEdit/Clipper.h b/tools/Forge/Bundles/MapEdit/Clipper.h new file mode 100644 index 000000000..5fec08642 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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: (NSPoint)pt; +- (BOOL)XYDrag: (NSPoint *)pt; +- ZClick: (NSPoint)pt; +- carve; +- flipNormal; +- (BOOL)getFace: (face_t *)pl; + +- cameraDrawSelf; +- XYDrawSelf; +- ZDrawSelf; + +@end + diff --git a/tools/Forge/Bundles/MapEdit/Clipper.m b/tools/Forge/Bundles/MapEdit/Clipper.m new file mode 100644 index 000000000..cb944d28c --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/Clipper.m @@ -0,0 +1,230 @@ + +#include "qedefs.h" + +#import +#import + +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"); + NSBeep (); + } + + 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: (NSPoint)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: (NSPoint)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/tools/Forge/Bundles/MapEdit/Dict.m b/tools/Forge/Bundles/MapEdit/Dict.m new file mode 100644 index 000000000..f0e8aab38 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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:(NSZone *)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/tools/Forge/Bundles/MapEdit/DictList.h b/tools/Forge/Bundles/MapEdit/DictList.h new file mode 100644 index 000000000..1ebfba8eb --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/DictList.h @@ -0,0 +1,12 @@ + +#import + +@interface DictList:List +{ +} + +- initListFromFile:(FILE *)fp; +- writeListFile:(char *)filename; +- (id) findDictKeyword:(char *)key; + +@end diff --git a/tools/Forge/Bundles/MapEdit/DictList.m b/tools/Forge/Bundles/MapEdit/DictList.m new file mode 100644 index 000000000..0d24b358e --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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/tools/Forge/Bundles/MapEdit/Entity.h b/tools/Forge/Bundles/MapEdit/Entity.h new file mode 100644 index 000000000..d4d5191c4 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/Entity.h @@ -0,0 +1,40 @@ + +#define MAX_KEY 64 +#define MAX_VALUE 128 +typedef struct epair_s +{ + struct epair_s *next; + char key[MAX_KEY]; + char value[MAX_VALUE]; +} epair_t; + +// an Entity is a list of brush objects, with additional key / value info + +@interface Entity : NSObject +{ + epair_t *epairs; + BOOL modifiable; +} + +- initClass: (char *)classname; +- initFromTokens; + +- free; + +- (BOOL)modifiable; +- setModifiable: (BOOL)m; + +- (char *)targetname; + +- writeToFILE: (FILE *)f region:(BOOL)reg; + +- (char *)valueForQKey: (char *)k; +- getVector: (vec3_t)v forKey: (char *)k; +- setKey:(char *)k toValue:(char *)v; +- (int)numPairs; +- (epair_t *)epairs; +- removeKeyPair: (char *)key; + +@end + + diff --git a/tools/Forge/Bundles/MapEdit/Entity.m b/tools/Forge/Bundles/MapEdit/Entity.m new file mode 100644 index 000000000..5e33340e9 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/Entity.m @@ -0,0 +1,485 @@ + +#include "qedefs.h" + +@implementation Entity + +vec3_t bad_mins = {-8, -8, -8}; +vec3_t bad_maxs = {8, 8, 8}; + +- createFixedBrush: (vec3_t)org +{ + vec3_t emins, emaxs; + float *v, *v2, *color; + id new; + texturedef_t td; + +// get class + new = [entity_classes_i classForName: [self valueForQKey: "classname"]]; + if (new) + { + v = [new mins]; + v2 = [new maxs]; + } + else + { + v = bad_mins; + v2 = bad_maxs; + } + + color = [new drawColor]; + + modifiable = NO; + memset(&td,0,sizeof(td)); + strcpy (td.texture,"entity"); + + VectorAdd (org, v, emins); + VectorAdd (org, v2, emaxs); + new = [[SetBrush alloc] initOwner: self mins:emins maxs:emaxs + texture: &td]; + [new setEntityColor: color]; + + [self addObject: new]; + + return self; +} + +- copyWithZone:(NSZone *)zone +{ + id new, nb; + epair_t *e; + int i; + + new = [[Entity alloc] init]; + [new setModifiable: modifiable]; + + for (e=epairs ; e ; e=e->next) + { // 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 : NSMutableArray +{ + id nullclass; + char *source_path; +} + +- initForSourceDirectory: (char *)path; +- (id)classForName: (char *)name; +- (void)scanDirectory; + +@end + diff --git a/tools/Forge/Bundles/MapEdit/EntityClass.m b/tools/Forge/Bundles/MapEdit/EntityClass.m new file mode 100644 index 000000000..bb98561a3 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/EntityClass.m @@ -0,0 +1,265 @@ + +#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 ; i<[self count] ; i++) + { + if (strcasecmp (name, [[self objectAtIndex: i] classname]) < 0) + { + [self insertObject: ec atIndex:i]; + return; + } + } + [self addObject: ec]; +} + + +/* +================= +scanFile +================= +*/ +- (void)scanFile: (char *)filename +{ + int size; + char *data; + id cl; + int i; + char path[1024]; + + sprintf (path,"%s/%s", source_path, filename); + + size = LoadFile (path, (void *)&data); + + for (i=0 ; id_name); + if (len <= 3) + continue; + if (!strcmp (ent->d_name+len-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<[self count] ; i++) + { + o = [self objectAtIndex: i]; + if (!strcmp (name,[o classname]) ) + return o; + } + + return nullclass; +} + + +@end + diff --git a/tools/Forge/Bundles/MapEdit/GNUmakefile b/tools/Forge/Bundles/MapEdit/GNUmakefile new file mode 100644 index 000000000..ea70af447 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/GNUmakefile @@ -0,0 +1,27 @@ +include $(GNUSTEP_MAKEFILES)/common.make + +BUNDLE_NAME= MapEdit +BUNDLE_EXTENSION= .forgeb +# +# We don't install this bundle, it goes inside the app. +# +BUNDLE_INSTALL_DIR= none +MapEdit_STANDARD_INSTALL= no + +MapEdit_RESOURCE_FILES= \ + MapEdit.gorm + +MapEdit_OBJC_FILES= \ + CameraView.m Clipper.m EntityClass.m KeypairView.m PopScrollView.m ZView.m misc.m render.m + +MapEdit_HEADERS= \ + EntityClass.h + +MapEdit_PRINCIPAL_CLASS= \ + MapEdit + +-include GNUmakefile.preamble + +include $(GNUSTEP_MAKEFILES)/bundle.make + +-include GNUmakefile.postamble diff --git a/tools/Forge/Bundles/MapEdit/GNUmakefile.preamble b/tools/Forge/Bundles/MapEdit/GNUmakefile.preamble new file mode 100644 index 000000000..f97e93f39 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/GNUmakefile.preamble @@ -0,0 +1,39 @@ +# Additional flags to pass to the preprocessor +ADDITIONAL_CPPFLAGS += + +# Additional flags to pass to the Objective-C compiler +ADDITIONAL_OBJCFLAGS += -DUSING_NIBS -Wall -Werror + +# Additional flags to pass to the C compiler +ADDITIONAL_CFLAGS += -Wall -Werror + +# Additional include directories the compiler should search +ADDITIONAL_INCLUDE_DIRS += -I ../.. + +# Additional LDFLAGS to pass to the linker +ADDITIONAL_LDFLAGS += + +# Additional library directories the linker should search +ADDITIONAL_LIB_DIRS += + +# Additional libraries + +# GNUstepWeb +ADDITIONAL_GSW_LIBS += +# GUI apps +ADDITIONAL_GUI_LIBS += +# Libraries +ADDITIONAL_LIBRARY_LIBS += +# ObjC stuff +ADDITIONAL_OBJC_LIBS += +# Tools +ADDITIONAL_TOOL_LIBS += +# WebObjects +ADDITIONAL_WO_LIBS += + +# +# Flags dealing with installing and uninstalling +# + +# Additional directories to be created during installation +ADDITIONAL_INSTALL_DIRS += diff --git a/tools/Forge/Bundles/MapEdit/InspectorControl.h b/tools/Forge/Bundles/MapEdit/InspectorControl.h new file mode 100644 index 000000000..5b90778dc --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/InspectorControl.h @@ -0,0 +1,71 @@ + +#import + +#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/tools/Forge/Bundles/MapEdit/InspectorControl.m b/tools/Forge/Bundles/MapEdit/InspectorControl.m new file mode 100644 index 000000000..9a97995f4 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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; + NSRect r; + id cell; + NSRect 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:NS_WIDTHSIZABLE | NS_HEIGHTSIZABLE]; + [inspectorSubview_i sizeTo:r.size.width - 4 :r.size.height - 4]; + + [inspectorSubview_i lockFocus]; + [inspectorSubview_i getBounds:&f]; + PSsetgray(NS_LTGRAY); + NSRectFill(&f); + [inspectorSubview_i unlockFocus]; + [inspectorView_i display]; + + return self; +} + +- (insp_e)getCurrentInspector +{ + return currentInspectorType; +} + + +@end diff --git a/tools/Forge/Bundles/MapEdit/KeypairView.h b/tools/Forge/Bundles/MapEdit/KeypairView.h new file mode 100644 index 000000000..3bb3543cd --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/KeypairView.h @@ -0,0 +1,16 @@ + +extern id keypairview_i; + +@interface KeypairView:NSView +{ +} + +- calcViewSize; + +#define SPACING 4 +#define FONTSIZE 12 +#define EXTRASPC 2 + +#define LINEHEIGHT 16 + +@end diff --git a/tools/Forge/Bundles/MapEdit/KeypairView.m b/tools/Forge/Bundles/MapEdit/KeypairView.m new file mode 100644 index 000000000..e83d0ccb4 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/KeypairView.m @@ -0,0 +1,93 @@ + +#import "qedefs.h" + +id keypairview_i; + +@implementation KeypairView + +/* +================== +initWithFrame: +================== +*/ +- initWithFrame:(NSRect)frameRect +{ + [super initWithFrame:frameRect]; + keypairview_i = self; + return self; +} + + +- calcViewSize +{ + NSRect b; + NSPoint pt; + int count; + id ent; + + ent = [map_i currentEntity]; + count = [ent numPairs]; + + //XXX[_super_view setFlipped: YES]; + + b = [_super_view bounds]; + b.size.height = LINEHEIGHT*count + SPACING; + [self setBounds: b]; + pt.x = pt.y = 0; + [self scrollPoint: pt]; + return self; +} + +- drawSelf:(const NSRect *)rects :(int)rectCount +{ + epair_t *pair; + int y; + + //XXX PSsetgray(NSGrayComponent(NS_COLORLTGRAY)); + PSrectfill(0,0,_bounds.size.width,_bounds.size.height); + + //XXX 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; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + NSPoint loc; + int i; + epair_t *p; + + loc = [theEvent locationInWindow]; + loc = [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; + i--; + } + if (p) + [things_i setSelectedKey: p]; + + return; +} + +@end diff --git a/tools/Forge/Bundles/MapEdit/Map.h b/tools/Forge/Bundles/MapEdit/Map.h new file mode 100644 index 000000000..15c4f581c --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/Map.h @@ -0,0 +1,68 @@ + +// Map is a list of Entity objects + +extern id map_i; + +@interface Map : NSMutableArray +{ + 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/tools/Forge/Bundles/MapEdit/Map.m b/tools/Forge/Bundles/MapEdit/Map.m new file mode 100644 index 000000000..2c2b29faa --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/Map.m @@ -0,0 +1,1125 @@ + +#include "qedefs.h" + +#include +#include + + +id map_i; + +@implementation Map + +/* +=============================================================================== + +FILE METHODS + +=============================================================================== +*/ + +- init +{ + [super init]; + map_i = self; + minz = 0; + maxz = 80; + + oldselection = [[NSMutableArray alloc] init]; + + return self; +} + +- saveSelected +{ + int i, c; + id o, w; + + [oldselection removeAllObjects]; + w = [self objectAtIndex: 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; +} + +- (void) removeObject: o +{ + [super removeObject: o]; + + if (o == currentEntity) + { // select the world + [self setCurrentEntity: [self objectAtIndex: 0]]; + } + + return; +} + +- 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 objectAtIndex: 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 indexOfObject:bestent], [bestent indexOfObject: bestbrush], bestface); + } + else + { + [bestbrush setSelected: NO]; + qprintf ("deselected entity %i brush %i face %i", [self indexOfObject:bestent], [bestent indexOfObject: bestbrush], bestface); + } + + [quakeed_i enableFlushWindow]; + [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 indexOfObject:bestent], [bestent indexOfObject: bestbrush]); + } + else + { + [bestbrush setTexturedef: &td forFace: bestface]; + qprintf ("deselected entity %i brush %i face %i", [self indexOfObject:bestent], [bestent indexOfObject: bestbrush], bestface); + } + [quakeed_i enableFlushWindow]; + + [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 objectAtIndex: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAtIndex: 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 objectAtIndex: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAtIndex: 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 objectAtIndex: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAtIndex: 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 objectAtIndex: i]; + c2 = [ent count]; + for (j = c2-1 ; j >=0 ; j--) + { + brush = [ent objectAtIndex: 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 objectAtIndex: 0]; + c = [o count]; + for (i=0 ; i + +@interface PopScrollView : NSScrollView +{ + id button1, button2; +} + +- initWithFrame:(NSRect)frameRect button1: b1 button2: b2; +- tile; + +@end diff --git a/tools/Forge/Bundles/MapEdit/PopScrollView.m b/tools/Forge/Bundles/MapEdit/PopScrollView.m new file mode 100644 index 000000000..203297bb4 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/PopScrollView.m @@ -0,0 +1,87 @@ + +#import "qedefs.h" + +@implementation PopScrollView + +/* +==================== +initWithFrame: button: + +Initizes a scroll view with a button at it's lower right corner +==================== +*/ + +- initWithFrame:(NSRect)frameRect button1:b1 button2:b2 +{ + [super initWithFrame: frameRect]; + + [self addSubview: b1]; + [self addSubview: b2]; + + button1 = b1; + button2 = b2; + + [self setHasHorizontalScroller: YES]; + [self setHasVerticalScroller: YES]; + + [self setBorderType: NSBezelBorder]; + + return self; +} + + +/* +================ +tile + +Adjust the size for the pop up scale menu +================= +*/ + +- tile +{ + NSRect scrollerframe; + NSRect buttonframe, buttonframe2; + NSRect newframe; + + [super tile]; + buttonframe = [button1 frame]; + buttonframe2 = [button2 frame]; + scrollerframe = [_horizScroller frame]; + + newframe.origin.y = scrollerframe.origin.y; + newframe.origin.x = scrollerframe.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; + + [_horizScroller setFrame: scrollerframe]; + + return self; +} + +/* +- superviewSizeChanged:(const NSSize *)oldSize +{ + [super superviewSizeChanged: oldSize]; + + [[self docView] newSuperBounds]; + + return self; +} +*/ + +-(BOOL) acceptsFirstResponder +{ + return YES; +} + + + +@end + diff --git a/tools/Forge/Bundles/MapEdit/Preferences.h b/tools/Forge/Bundles/MapEdit/Preferences.h new file mode 100644 index 000000000..a27d81ae2 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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/tools/Forge/Bundles/MapEdit/Preferences.m b/tools/Forge/Bundles/MapEdit/Preferences.m new file mode 100644 index 000000000..9ff7343cf --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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); + NSWriteDefault (DEFOWNER, name, str); +} +void WriteStringDefault (char *name, char *value) +{ + NSWriteDefault (DEFOWNER, name, value); +} + +// +// Read in at start of program +// +- readDefaults +{ + char *string; + float value; + + string = (char *)NSGetDefaultValue(DEFOWNER,"ProjectPath"); + [self setProjectPath: string]; + + string = (char *)NSGetDefaultValue(DEFOWNER,"BspSoundPath"); + [self setBspSoundPath:string]; + + value = _atoi((char *)NSGetDefaultValue(DEFOWNER,"ShowBSPOutput")); + [self setShowBSP:value]; + + value = _atoi((char *)NSGetDefaultValue(DEFOWNER,"OffsetBrushCopy")); + [self setBrushOffset:value]; + + value = _atoi((char *)NSGetDefaultValue(DEFOWNER,"StartWad")); + [self setStartWad:value]; + + value = _atof((char *)NSGetDefaultValue(DEFOWNER,"Xlight")); + [self setXlight:value]; + + value = _atof((char *)NSGetDefaultValue(DEFOWNER,"Ylight")); + [self setYlight:value]; + + value = _atof((char *)NSGetDefaultValue(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/tools/Forge/Bundles/MapEdit/Project.h b/tools/Forge/Bundles/MapEdit/Project.h new file mode 100644 index 000000000..2f88e2151 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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/tools/Forge/Bundles/MapEdit/Project.m b/tools/Forge/Bundles/MapEdit/Project.m new file mode 100644 index 000000000..2bbee4b33 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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 = NSRunAlertPanel("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 + [NSApp 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 == NS_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/tools/Forge/Bundles/MapEdit/QuakeEd.h b/tools/Forge/Bundles/MapEdit/QuakeEd.h new file mode 100644 index 000000000..3c221d762 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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 : NSWindow +{ + 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/tools/Forge/Bundles/MapEdit/QuakeEd.m b/tools/Forge/Bundles/MapEdit/QuakeEd.m new file mode 100644 index 000000000..c1b288dd7 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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) +{ + NSBeep (); +} + +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]; + + NSPing (); +} + +/* +=============== +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 NSRect *)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: + NS_RMOUSEDRAGGEDMASK|NS_LMOUSEDRAGGEDMASK]; + + malloc_error(My_Malloc_Error); + + quakeed_i = self; + dirty = autodirty = NO; + + DPSAddTimedEntry(5*60, AutoSave, self, NS_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) +{ + NSEvent ev; + + if (updateinflight) + return; + +// post an event at the end of the que + ev.type = NS_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:(NSEvent *)theEvent +{ + NSEvent ev, *evp; + + updateinflight = NO; + +//printf ("serviced\n"); + +// update screen + evp = [NSApp 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]; + +// NSPing (); + + return self; +} + +- appDidInit:sender +{ + NSScreen 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 + [NSApp 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 +{ + NSRect sbounds; + + [[xyview_i _super_view] 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 +{ + NSRect sbounds; + + [[xyview_i _super_view] 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 +{ + NSRect bounds; + +// get xy size + [[xyview_i _super_view] 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) + { + NSBeep(); + 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 = NSGetAlertPanel("BSP In Progress",expandedcmd,NULL,NULL,NULL); + [panel makeKeyAndOrderFront:NULL]; + system(expandedcmd); + NSFreeAlertPanel(panel); + [self makeKeyAndOrderFront:NULL]; + DisplayCmdOutput (); + } + else + { + cmdte = DPSAddTimedEntry(1, CheckCmdDone, self, NS_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) + { + NSBeep(); + 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] != NS_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] != NS_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:(NSEvent *)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/tools/Forge/Bundles/MapEdit/QuakeEd_main.m b/tools/Forge/Bundles/MapEdit/QuakeEd_main.m new file mode 100644 index 000000000..d3be18e56 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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 ([NSApp loadNibSection:"QuakeEd.nib" owner:NSApp withNames:NO]) + [NSApp run]; + + [NSApp free]; + exit(0); +} diff --git a/tools/Forge/Bundles/MapEdit/SetBrush.h b/tools/Forge/Bundles/MapEdit/SetBrush.h new file mode 100644 index 000000000..44e0de503 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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/tools/Forge/Bundles/MapEdit/SetBrush.m b/tools/Forge/Bundles/MapEdit/SetBrush.m new file mode 100644 index 000000000..f210ba2ea --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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: (NSPoint *)p1]; + + for (i=0 ; i<2 ; i++) + if (bmins[i] >= p1[i] || bmaxs[i] <= p1[i]) + return self; + + p1[2] = 4096; + p2[0] = p1[0]; + p2[1] = p1[1]; + p2[2] = -4096; + + [self clipRay: p1 : p2 : frontpoint: &frontface : backpoint : &backface]; + + if (frontface == -1 || backface == -1) + return self; + + q = TEX_ForName (faces[frontface].texture.texture); + + PSmoveto (-8, frontpoint[2]); + PSlineto (8, frontpoint[2]); + PSlineto (8, backpoint[2]); + PSlineto (-8, backpoint[2]); + PSlineto (-8, frontpoint[2]); + + PSsetrgbcolor (q->flatcolor.chan[0]/255.0 + , q->flatcolor.chan[1]/255.0 + , q->flatcolor.chan[2]/255.0); + PSfill (); + + PSmoveto (-12, frontpoint[2]); + PSlineto (12, frontpoint[2]); + PSlineto (12, backpoint[2]); + PSlineto (-12, backpoint[2]); + PSlineto (-12, frontpoint[2]); + + PSsetrgbcolor (0,0,0); + PSstroke (); + + return self; +} + +/* +=========== +CameraDrawSelf +=========== +*/ +- CameraDrawSelf +{ + int i, j; + winding_t *w; + id worldent, currentent; + + if ([self fakeBrush: @selector(CameraDrawSelf)]) + return self; + + worldent = [map_i 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 = NSMallocCheck()) ) + 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 = [[NSBitmapImageRep alloc] + initData: NULL + pixelsWide: width + pixelsHigh: height + bitsPerSample: 8 + samplesPerPixel:3 + hasAlpha: NO + isPlanar: NO + colorSpace: NS_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; + NSRect b; + int maxwidth; + int maxheight; + NSPoint 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; + NSRect 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/tools/Forge/Bundles/MapEdit/TextureView.h b/tools/Forge/Bundles/MapEdit/TextureView.h new file mode 100644 index 000000000..2f4af37af --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/TextureView.h @@ -0,0 +1,12 @@ + + +@interface TextureView:NSView +{ + id parent_i; + int deselectIndex; +} + +- setParent:(id)from; +- deselect; + +@end diff --git a/tools/Forge/Bundles/MapEdit/TextureView.m b/tools/Forge/Bundles/MapEdit/TextureView.m new file mode 100644 index 000000000..ab82ff283 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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 NSRect *)rects :(int)rectCount +{ + int i; + int max; + id list_i; + texpal_t *t; + int x; + int y; + NSPoint p; + NSRect r; + int selected; + + selected = [parent_i getSelectedTexture]; + list_i = [parent_i getList]; + PSselectfont("Helvetica-Medium",FONTSIZE); + PSrotate(0); + + PSsetgray(NS_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(NSGrayComponent(NS_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 (NSIntersectsRect(&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:(NSEvent *)theEvent +{ + NSPoint loc; + int i; + int max; + int oldwindowmask; + texpal_t *t; + id list; + NSRect r; + + oldwindowmask = [window addToEventMask:NS_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 (NSPointInRect(&loc,&r) == YES) + { + [self deselect]; + [parent_i setSelectedTexture:i]; + break; + } + } + + [window setEventMask:oldwindowmask]; + return self; +} + +@end diff --git a/tools/Forge/Bundles/MapEdit/Things.h b/tools/Forge/Bundles/MapEdit/Things.h new file mode 100644 index 000000000..a5b007525 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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/tools/Forge/Bundles/MapEdit/Things.m b/tools/Forge/Bundles/MapEdit/Things.m new file mode 100644 index 000000000..72b0e3d81 --- /dev/null +++ b/tools/Forge/Bundles/MapEdit/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