quakeforge/tools/Forge/Bundles/MapEdit/Map.m

1098 lines
20 KiB
Objective-C

#include <sys/time.h>
#include <time.h>
#include "QF/quakeio.h"
#include "QF/script.h"
#include "QF/sys.h"
#include "Map.h"
#include "Entity.h"
#include "TexturePalette.h"
#include "SetBrush.h"
#include "XYView.h"
#include "CameraView.h"
#include "QuakeEd.h"
#include "Things.h"
#include "InspectorControl.h"
#include "Project.h"
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 < c; i++) {
o =[w objectAtIndex:0];
if ([o selected])
[o moveToEntity];
else {
[w removeObjectAtIndex:0];
[o release];
}
}
c =[self count];
for (i = 0; i < c; i++) {
o =[self objectAtIndex:0];
[self removeObjectAtIndex:0];
[o removeAllObjects];
[o release];
}
return self;
}
-addSelected
{
int i, c;
id n, w;
c =[oldselection count];
w =[self objectAtIndex:0]; // world object
sb_newowner = w;
for (i = 0; i < c; i++) {
n =[oldselection objectAtIndex:i];
[n moveToEntity];
i--;
c--;
}
[oldselection removeAllObjects];
return self;
}
-newMap
{
id ent;
[self saveSelected];
ent =[[Entity alloc] initClass:"worldspawn"];
[self addObject:ent];
currentEntity = NULL;
[self setCurrentEntity:ent];
[self addSelected];
return self;
}
-currentEntity
{
return currentEntity;
}
-setCurrentEntity:ent
{
id old;
old = currentEntity;
currentEntity = ent;
if (old != ent) {
[things_i newCurrentEntity]; // update inspector
[inspcontrol_i changeInspectorTo:i_things];
}
return self;
}
-(float) currentMinZ
{
float grid;
grid =[xyview_i gridsize];
minz = grid * rint (minz / grid);
return minz;
}
-setCurrentMinZ:(float) m
{
if (m > -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;
}
#define FN_DEVLOG "/qcache/devlog"
-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 < c; i++)
if ([[currentEntity objectAtIndex:i] selected])
num++;
return num;
}
-selectedBrush
{
int i, c;
int num;
num = 0;
c =[currentEntity count];
for (i = 0; i < c; i++)
if ([[currentEntity objectAtIndex:i] selected])
return[currentEntity objectAtIndex:i];
return nil;
}
/*
=================
readMapFile
=================
*/
-readMapFile:(char *) fname
{
char *dat, *cl;
id new;
id ent;
int i, c;
vec3_t org;
float angle;
QFile *file;
script_t *script;
size_t size;
[self saveSelected];
Sys_Printf ("loading %s\n", fname);
file = Qopen (fname, "rt");
size = Qfilesize (file);
dat = malloc (size + 1);
size = Qread (file, dat, size);
Qclose (file);
dat[size] = 0;
script = Script_New ();
Script_Start (script, fname, dat);
do {
new =[[Entity alloc] initFromScript:script];
if (!new)
break;
[self addObject:new];
} while (1);
free (dat);
[self setCurrentEntity: [self objectAtIndex:0]];
[self addSelected];
// load the apropriate texture wad
dat =[currentEntity valueForQKey:"wad"];
if (dat && dat[0]) {
if (dat[0] == '/') // remove old style fullpaths
[currentEntity removeKeyPair:"wad"];
else {
if (strcmp ([texturepalette_i currentWad], dat))
[project_i setTextureWad:dat];
}
}
// center the camera and XY view on the playerstart
c =[self count];
for (i = 1; i < c; i++) {
ent =[self objectAtIndex:i];
cl =[ent valueForQKey:"classname"];
if (cl && !strcasecmp (cl, "info_player_start")) {
angle = atof ([ent valueForQKey:"angle"]);
angle = angle / 180 * M_PI;
[ent getVector: org forKey:"origin"];
[cameraview_i setOrigin: org angle:angle];
[xyview_i centerOn:org];
break;
}
}
return self;
}
/*
=================
writeMapFile
=================
*/
-writeMapFile:(char *)
fname useRegion:(BOOL) reg
{
FILE *f;
int i;
Sys_Printf ("writeMapFile: %s", fname);
f = fopen (fname, "w");
if (!f)
Sys_Error ("couldn't write %s", fname);
for (i = 0; i <[self count]; i++)
[[self objectAtIndex: i] writeToFILE: f region:reg];
fclose (f);
return self;
}
/*
==============================================================================
DRAWING
==============================================================================
*/
-ZDrawSelf
{
int i, count;
count =[self count];
for (i = 0; i < count; i++)
[[self objectAtIndex:i] ZDrawSelf];
return self;
}
-RenderSelf:(void (*)(face_t *)) callback
{
int i, count;
count =[self count];
for (i = 0; i < count; i++)
[[self objectAtIndex: i] RenderSelf:callback];
return self;
}
//============================================================================
/*
===================
entityConnect
A command-shift-click on an entity while an entity is selected will
make a target connection from the original entity.
===================
*/
-entityConnect: (vec3_t) p1:(vec3_t) p2
{
id oldent, ent;
oldent =[self currentEntity];
if (oldent ==[self objectAtIndex:0]) {
Sys_Printf ("Must have a non-world entity selected to connect");
return self;
}
[self selectRay: p1: p2:YES];
ent =[self currentEntity];
if (ent == oldent) {
Sys_Printf ("Must click on a different entity to connect");
return self;
}
if (ent ==[self objectAtIndex:0]) {
Sys_Printf ("Must click on a non-world entity to connect");
return self;
}
[oldent setKey: "target" toValue:[ent targetname]];
[quakeed_i updateAll];
return self;
}
/*
=================
selectRay
If ef is true, any entity brush along the ray will be selected in preference
to intervening world brushes
=================
*/
-selectRay: (vec3_t) p1: (vec3_t) p2:(BOOL) ef
{
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 = c - 1; i >= 0; i--) {
ent =[self objectAtIndex:i];
c2 =[ent count];
for (j = 0; j < c2; j++) {
brush =[ent objectAtIndex:j];
[brush hitByRay: p1: p2: &time:&face];
if (time < 0 || time > besttime)
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) {
Sys_Printf ("trace missed");
return self;
}
if ([bestbrush regioned]) {
Sys_Printf ("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];
Sys_Printf ("selected entity %i brush %i face %i", (int)[self indexOfObject: bestent], (int)[bestent indexOfObject:bestbrush], bestface);
} else {
[bestbrush setSelected:NO];
Sys_Printf ("deselected entity %i brush %i face %i", (int)[self indexOfObject: bestent], (int)[bestent indexOfObject:bestbrush], bestface);
}
[quakeed_i enableFlushWindow];
[quakeed_i updateAll];
return self;
}
/*
=================
grabRay
checks only 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; i < c; i++) {
ent =[self objectAtIndex:i];
c2 =[ent count];
for (j = 0; j < c2; j++) {
brush =[ent objectAtIndex:j];
if (![brush selected])
continue;
[brush hitByRay: p1: p2: &time:&face];
if (time < 0 || time > besttime)
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; i < c; i++) {
ent =[self objectAtIndex:i];
c2 =[ent count];
for (j = 0; j < c2; j++) {
brush =[ent objectAtIndex:j];
[brush hitByRay: p1: p2: &time:&face];
if (time < 0 || time > besttime)
continue;
bestent = ent;
bestface = face;
besttime = time;
bestbrush = brush;
}
}
if (besttime == 99999)
return nil;
if (![bestent modifiable]) {
Sys_Printf ("can't modify spawned entities");
return self;
}
td =[bestbrush texturedefForFace:bestface];
[texturepalette_i setTextureDef:td];
Sys_Printf ("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; i < c; i++) {
ent =[self objectAtIndex:i];
c2 =[ent count];
for (j = 0; j < c2; j++) {
brush =[ent objectAtIndex:j];
[brush hitByRay: p1: p2: &time:&face];
if (time < 0 || time > besttime)
continue;
bestent = ent;
besttime = time;
bestbrush = brush;
bestface = face;
}
}
if (besttime == 99999) {
Sys_Printf ("trace missed");
return self;
}
if (![bestent modifiable]) {
Sys_Printf ("can't modify spawned entities");
return self;
}
if ([bestbrush regioned]) {
Sys_Printf ("WANRING: clicked on regioned brush");
return self;
}
[texturepalette_i getTextureDef:&td];
[quakeed_i disableFlushWindow];
if (allsides) {
[bestbrush setTexturedef:&td];
Sys_Printf ("textured entity %i brush %i", (int)[self indexOfObject: bestent], (int)[bestent indexOfObject:bestbrush]);
} else {
[bestbrush setTexturedef: &td forFace:bestface];
Sys_Printf ("deselected entity %i brush %i face %i", (int)[self indexOfObject: bestent], (int)[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 performSelector:sel];
}
}
// if (!total)
// Sys_Printf ("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 performSelector: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 performSelector: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 performSelector: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]) {
Sys_Printf ("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 < c; i++) {
b =[o objectAtIndex:i];
if (![b selected])
continue;
// copy the brush, then translate the original
new =[b copy];
[new setSelected:YES];
[new translate];
[b setSelected:NO];
[o addObject:new];
}
// copy entire entities otherwise
originalElements =[self count]; // don't copy the new ones
for (i = 1; i < originalElements; i++) {
o =[self objectAtIndex:i];
if (![[o objectAtIndex:0] selected])
continue;
new =[o copy];
[self addObject:new];
c =[o count];
for (j = 0; j < c; j++)
[[o objectAtIndex: j] setSelected:NO];
c =[new count];
for (j = 0; j < c; j++) {
b =[new objectAtIndex:j];
[b translate];
[b setSelected:YES];
}
}
[quakeed_i updateAll];
return self;
}
-selectCompleteEntity:sender
{
id o;
int i, c;
o =[self selectedBrush];
if (!o) {
Sys_Printf ("nothing selected");
return self;
}
o =[o parent];
c =[o count];
for (i = 0; i < c; i++)
[[o objectAtIndex: i] setSelected:YES];
Sys_Printf ("%i brushes selected", c);
[quakeed_i updateAll];
return self;
}
-makeEntity:sender
{
if (currentEntity !=[self objectAtIndex:0]) {
Sys_Printf ("ERROR: can't makeEntity inside an entity");
NSBeep ();
return self;
}
if ([self numSelected] == 0) {
Sys_Printf ("ERROR: must have a seed brush to make an entity");
NSBeep ();
return self;
}
sb_newowner =[[Entity alloc] initClass:[things_i spawnName]];
if ([sb_newowner modifiable])
[self makeSelectedPerform:@selector (moveToEntity)];
else { // throw out seed brush and select
// entity fixed brush
[self makeSelectedPerform:@selector (remove)];
[[sb_newowner objectAtIndex: 0] setSelected:YES];
}
[self addObject:sb_newowner];
[self setCurrentEntity:sb_newowner];
[quakeed_i updateAll];
return self;
}
-selbox:(SEL) selector
{
id b;
if ([self numSelected] != 1) {
Sys_Printf ("must have a single brush selected");
return self;
}
b =[self selectedBrush];
[b getMins: select_min maxs:select_max];
[b remove];
[self makeUnselectedPerform:selector];
Sys_Printf ("identified contents");
[quakeed_i updateAll];
return self;
}
-selectCompletelyInside:sender
{
return[self selbox:@selector (selectComplete)];
}
-selectPartiallyInside:sender
{
return[self selbox:@selector (selectPartial)];
}
-tallBrush:sender
{
id b;
vec3_t mins, maxs;
texturedef_t td;
if ([self numSelected] != 1) {
Sys_Printf ("must have a single brush selected");
return self;
}
b =[self selectedBrush];
td = *[b texturedef];
[b getMins: mins maxs:maxs];
[b remove];
mins[2] = -2048;
maxs[2] = 2048;
b =[[SetBrush alloc] initOwner: [map_i objectAtIndex: 0] mins: mins maxs: maxs texture:&td];
[[map_i objectAtIndex: 0] addObject:b];
[b setSelected:YES];
[quakeed_i updateAll];
return self;
}
-shortBrush:sender
{
id b;
vec3_t mins, maxs;
texturedef_t td;
if ([self numSelected] != 1) {
Sys_Printf ("must have a single brush selected");
return self;
}
b =[self selectedBrush];
td = *[b texturedef];
[b getMins: mins maxs:maxs];
[b remove];
mins[2] = 0;
maxs[2] = 16;
b =[[SetBrush alloc] initOwner: [map_i objectAtIndex: 0] mins: mins maxs: maxs texture:&td];
[[map_i objectAtIndex: 0] addObject:b];
[b setSelected:YES];
[quakeed_i updateAll];
return self;
}
/*
==================
subtractSelection
==================
*/
-subtractSelection:semder
{
int i, j, c, c2;
id o, o2;
id sellist, sourcelist;
Sys_Printf ("performing brush subtraction...");
sourcelist =[[NSMutableArray alloc] init];
sellist =[[NSMutableArray alloc] init];
carve_in =[[NSMutableArray alloc] init];
carve_out =[[NSMutableArray alloc] init];
c =[currentEntity count];
for (i = 0; i < c; i++) {
o =[currentEntity objectAtIndex:i];
if ([o selected])
[sellist addObject:o];
else
[sourcelist addObject:o];
}
c =[sellist count];
for (i = 0; i < c; i++) {
o =[sellist objectAtIndex:i];
[o setCarveVars];
c2 =[sourcelist count];
for (j = 0; j < c2; j++) {
o2 =[sourcelist objectAtIndex:j];
[o2 carve];
[carve_in removeAllObjects];
}
[sourcelist release]; // the individual have been moved/freed
sourcelist = carve_out;
carve_out =[[NSMutableArray alloc] init];
}
// add the selection back to the remnants
[currentEntity removeAllObjects];
[currentEntity addObjectsFromArray:sourcelist];
[currentEntity addObjectsFromArray:sellist];
[sourcelist release];
[sellist release];
[carve_in release];
[carve_out release];
if (![currentEntity count]) {
o = currentEntity;
[self removeObject:o];
[o release];
}
Sys_Printf ("subtracted selection");
[quakeed_i updateAll];
return self;
}
@end