Index: meson.build ================================================================== --- meson.build +++ meson.build @@ -1,19 +1,17 @@ -project('Cube', ['c', 'objc', 'objcpp'], +project('Cube', ['c', 'objc'], meson_version: '>=1.5.0', default_options: { 'optimization': '2' }) -foreach lang : ['objc', 'objcpp'] - add_global_arguments( - [ - '-fobjc-arc', - '-fobjc-arc-exceptions' - ], - language: lang) -endforeach +add_global_arguments( + [ + '-fobjc-arc', + '-fobjc-arc-exceptions' + ], + language: 'objc') objfw_dep = dependency('objfw') sdl_dep = dependency('SDL2') sdlimage_dep = dependency('SDL2_image') sdlmixer_dep = dependency('SDL2_mixer') Index: src/entities.m ================================================================== --- src/entities.m +++ src/entities.m @@ -28,12 +28,12 @@ { ents = [[OFMutableArray alloc] init]; } static void -renderent(Entity *e, OFString *mdlname, float z, float yaw, int frame /* = 0*/, - int numf /* = 1*/, int basetime /* = 0*/, float speed /* = 10.0f*/) +renderent(Entity *e, OFString *mdlname, float z, float yaw, int frame, int numf, + int basetime, float speed) { rendermodel(mdlname, frame, numf, 0, 1.1f, OFMakeVector3D(e.x, z + S(e.x, e.y)->floor, e.y), yaw, 0, false, 1.0f, speed, 0, basetime); } ADDED src/init.m Index: src/init.m ================================================================== --- /dev/null +++ src/init.m @@ -0,0 +1,24 @@ +#import "cube.h" + +static void **queue; +static size_t queueCount; + +void +enqueueInit(void (^init)(void)) +{ + queue = realloc(queue, (queueCount + 1) * sizeof(void *)); + if (queue == NULL) + fatal(@"cannot allocate init queue"); + + queue[queueCount++] = (__bridge void *)init; +} + +void +processInitQueue(void) +{ + for (size_t i = 0; i < queueCount; i++) + ((__bridge void (^)())queue[i])(); + + free(queue); + queueCount = 0; +} DELETED src/init.mm Index: src/init.mm ================================================================== --- src/init.mm +++ /dev/null @@ -1,24 +0,0 @@ -#include - -#import "cube.h" -#import "protos.h" - -static std::vector *queue; - -void -enqueueInit(void (^init)(void)) -{ - if (queue == NULL) - queue = new std::vector(); - - queue->push_back(init); -} - -void -processInitQueue(void) -{ - for (auto &init : *queue) - init(); - - queue->clear(); -} Index: src/meson.build ================================================================== --- src/meson.build +++ src/meson.build @@ -27,34 +27,34 @@ 'clients2c.m', 'commands.m', 'console.m', 'editing.m', 'entities.m', - 'init.mm', + 'init.m', 'menus.m', 'monster.m', 'physics.m', 'rendercubes.m', 'renderextras.m', - 'rendergl.mm', - 'rendermd2.mm', - 'renderparticles.mm', - 'rendertext.mm', - 'rndmap.mm', - 'savegamedemo.mm', + 'rendergl.m', + 'rendermd2.m', + 'renderparticles.m', + 'rendertext.m', + 'rndmap.m', + 'savegamedemo.m', 'server.m', - 'serverbrowser.mm', + 'serverbrowser.m', 'serverms.m', 'serverutil.m', 'sound.m', 'tools.m', 'weapon.m', - 'world.mm', - 'worldio.mm', - 'worldlight.mm', - 'worldocull.mm', - 'worldrender.mm', + 'world.m', + 'worldio.m', + 'worldlight.m', + 'worldocull.m', + 'worldrender.m', ], dependencies: [ objfw_dep, sdl_dep, sdlimage_dep, ADDED src/rendergl.m Index: src/rendergl.m ================================================================== --- /dev/null +++ src/rendergl.m @@ -0,0 +1,505 @@ +// rendergl.cpp: core opengl rendering stuff + +#include "cube.h" + +#import "DynamicEntity.h" +#import "OFString+Cube.h" + +#ifdef DARWIN +# define GL_COMBINE_EXT GL_COMBINE_ARB +# define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB +# define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB +# define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB +# define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB +#endif + +extern int curvert; + +bool hasoverbright = false; + +void purgetextures(); + +GLUquadricObj *qsphere = NULL; +int glmaxtexsize = 256; + +void +gl_init(int w, int h) +{ + // #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f + + glViewport(0, 0, w, h); + glClearDepth(1.0); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + + glEnable(GL_FOG); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_DENSITY, 0.25); + glHint(GL_FOG_HINT, GL_NICEST); + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-3.0, -3.0); + + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + + char *exts = (char *)glGetString(GL_EXTENSIONS); + + if (strstr(exts, "GL_EXT_texture_env_combine")) + hasoverbright = true; + else + conoutf(@"WARNING: cannot use overbright lighting, using old " + @"lighting model!"); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); + + purgetextures(); + + if (!(qsphere = gluNewQuadric())) + fatal(@"glu sphere"); + gluQuadricDrawStyle(qsphere, GLU_FILL); + gluQuadricOrientation(qsphere, GLU_INSIDE); + gluQuadricTexture(qsphere, GL_TRUE); + glNewList(1, GL_COMPILE); + gluSphere(qsphere, 1, 12, 6); + glEndList(); +} + +void +cleangl() +{ + if (qsphere) + gluDeleteQuadric(qsphere); +} + +bool +installtex(int tnum, OFIRI *IRI, int *xs, int *ys, bool clamp) +{ + SDL_Surface *s = IMG_Load(IRI.fileSystemRepresentation.UTF8String); + if (s == NULL) { + conoutf(@"couldn't load texture %@", IRI.string); + return false; + } + + if (s->format->BitsPerPixel != 24) { + SDL_PixelFormat *format = + SDL_AllocFormat(SDL_PIXELFORMAT_RGB24); + if (format == NULL) { + conoutf(@"texture cannot be converted to 24bpp: %@", + IRI.string); + return false; + } + + @try { + SDL_Surface *converted = + SDL_ConvertSurface(s, format, 0); + if (converted == NULL) { + conoutf(@"texture cannot be converted " + @"to 24bpp: %@", + IRI.string); + return false; + } + + SDL_FreeSurface(s); + s = converted; + } @finally { + SDL_FreeFormat(format); + } + } + +#if 0 + loopi(s->w * s->h * 3) + { + uchar *p = (uchar *)s->pixels + i; + *p = 255 - *p; + } +#endif + glBindTexture(GL_TEXTURE_2D, tnum); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); // NEAREST); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + *xs = s->w; + *ys = s->h; + while (*xs > glmaxtexsize || *ys > glmaxtexsize) { + *xs /= 2; + *ys /= 2; + } + + void *scaledimg = s->pixels; + + if (*xs != s->w) { + conoutf(@"warning: quality loss: scaling %@", + IRI.string); // for voodoo cards under linux + scaledimg = OFAllocMemory(1, *xs * *ys * 3); + gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, + *xs, *ys, GL_UNSIGNED_BYTE, scaledimg); + } + + if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, *xs, *ys, GL_RGB, + GL_UNSIGNED_BYTE, scaledimg)) + fatal(@"could not build mipmaps"); + + if (*xs != s->w) + free(scaledimg); + + SDL_FreeSurface(s); + + return true; +} + +// management of texture slots +// each texture slot can have multople texture frames, of which currently only +// the first is used additional frames can be used for various shaders + +static const int MAXTEX = 1000; +static int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) +static int texy[MAXTEX]; +static OFString *texname[MAXTEX]; +static int curtex = 0; +static const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX +// std 1+, sky 14+, mdls 20+ + +static const int MAXFRAMES = 2; // increase to allow more complex shader defs +static int mapping[256] + [MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) +static OFString *mapname[256][MAXFRAMES]; + +void +purgetextures() +{ + loopi(256) loop(j, MAXFRAMES) mapping[i][j] = 0; +} + +int curtexnum = 0; + +void +texturereset() +{ + curtexnum = 0; +} +COMMAND(texturereset, ARG_NONE) + +void +texture(OFString *aframe, OFString *name) +{ + int num = curtexnum++, frame = aframe.cube_intValue; + + if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES) + return; + + mapping[num][frame] = 1; + mapname[num][frame] = [name stringByReplacingOccurrencesOfString:@"\\" + withString:@"/"]; +} +COMMAND(texture, ARG_2STR) + +int +lookuptexture(int tex, int *xs, int *ys) +{ + int frame = 0; // other frames? + int tid = mapping[tex][frame]; + + if (tid >= FIRSTTEX) { + *xs = texx[tid - FIRSTTEX]; + *ys = texy[tid - FIRSTTEX]; + return tid; + } + + *xs = *ys = 16; + if (tid == 0) + return 1; // crosshair :) + + loopi(curtex) // lazily happens once per "texture" command, basically + { + if ([mapname[tex][frame] isEqual:texname[i]]) { + mapping[tex][frame] = tid = i + FIRSTTEX; + *xs = texx[i]; + *ys = texy[i]; + return tid; + } + } + + if (curtex == MAXTEX) + fatal(@"loaded too many textures"); + + int tnum = curtex + FIRSTTEX; + texname[curtex] = mapname[tex][frame]; + + OFString *path = + [OFString stringWithFormat:@"packages/%@", texname[curtex]]; + + if (installtex(tnum, + [Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:path], + xs, ys, false)) { + mapping[tex][frame] = tnum; + texx[curtex] = *xs; + texy[curtex] = *ys; + curtex++; + return tnum; + } else { + return mapping[tex][frame] = FIRSTTEX; // temp fix + } +} + +static void +gl_setupworld() +{ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + setarraypointers(); + + if (hasoverbright) { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi( + GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + } +} + +int skyoglid; + +struct strip { + int tex, start, num; +}; +static OFMutableData *strips; + +void +renderstripssky() +{ + glBindTexture(GL_TEXTURE_2D, skyoglid); + + const struct strip *items = strips.items; + size_t count = strips.count; + for (size_t i = 0; i < count; i++) + if (items[i].tex == skyoglid) + glDrawArrays( + GL_TRIANGLE_STRIP, items[i].start, items[i].num); +} + +void +renderstrips() +{ + int lasttex = -1; + const struct strip *items = strips.items; + size_t count = strips.count; + for (size_t i = 0; i < count; i++) { + if (items[i].tex == skyoglid) + continue; + + if (items[i].tex != lasttex) { + glBindTexture(GL_TEXTURE_2D, items[i].tex); + lasttex = items[i].tex; + } + + glDrawArrays(GL_TRIANGLE_STRIP, items[i].start, items[i].num); + } +} + +void +overbright(float amount) +{ + if (hasoverbright) + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount); +} + +void +addstrip(int tex, int start, int n) +{ + if (strips == nil) + strips = [[OFMutableData alloc] + initWithItemSize:sizeof(struct strip)]; + + struct strip s = { .tex = tex, .start = start, .num = n }; + [strips addItem:&s]; +} + +VARFP(gamma, 30, 100, 300, { + float f = gamma / 100.0f; + Uint16 ramp[256]; + + SDL_CalculateGammaRamp(f, ramp); + + if (SDL_SetWindowGammaRamp( + Cube.sharedInstance.window, ramp, ramp, ramp) == -1) { + conoutf( + @"Could not set gamma (card/driver doesn't support it?)"); + conoutf(@"sdl: %s", SDL_GetError()); + } +}) + +void +transplayer() +{ + glLoadIdentity(); + + glRotated(player1.roll, 0.0, 0.0, 1.0); + glRotated(player1.pitch, -1.0, 0.0, 0.0); + glRotated(player1.yaw, 0.0, 1.0, 0.0); + + glTranslated(-player1.o.x, + (player1.state == CS_DEAD ? player1.eyeheight - 0.2f : 0) - + player1.o.z, + -player1.o.y); +} + +VARP(fov, 10, 105, 120); + +int xtraverts; + +VAR(fog, 64, 180, 1024); +VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); + +VARP(hudgun, 0, 1, 1); + +OFString *hudgunnames[] = { @"hudguns/fist", @"hudguns/shotg", + @"hudguns/chaing", @"hudguns/rocket", @"hudguns/rifle" }; + +void +drawhudmodel(int start, int end, float speed, int base) +{ + rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f, + OFMakeVector3D(player1.o.x, player1.o.z, player1.o.y), + player1.yaw + 90, player1.pitch, false, 1.0f, speed, 0, base); +} + +void +drawhudgun(float fovy, float aspect, int farplane) +{ + if (!hudgun /*|| !player1.gunselect*/) + return; + + glEnable(GL_CULL_FACE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(fovy, aspect, 0.3f, farplane); + glMatrixMode(GL_MODELVIEW); + + // glClear(GL_DEPTH_BUFFER_BIT); + int rtime = reloadtime(player1.gunselect); + if (player1.lastaction && player1.lastattackgun == player1.gunselect && + lastmillis - player1.lastaction < rtime) { + drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction); + } else + drawhudmodel(6, 1, 100, 0); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(fovy, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + glDisable(GL_CULL_FACE); +} + +void +gl_drawframe(int w, int h, float curfps) +{ + float hf = hdr.waterlevel - 0.3f; + float fovy = (float)fov * h / w; + float aspect = w / (float)h; + bool underwater = player1.o.z < hf; + + glFogi(GL_FOG_START, (fog + 64) / 8); + glFogi(GL_FOG_END, fog); + float fogc[4] = { (fogcolour >> 16) / 256.0f, + ((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f, + 1.0f }; + glFogfv(GL_FOG_COLOR, fogc); + glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); + + if (underwater) { + fovy += (float)sin(lastmillis / 1000.0) * 2.0f; + aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; + glFogi(GL_FOG_START, 0); + glFogi(GL_FOG_END, (fog + 96) / 8); + } + + glClear((player1.outsidemap ? GL_COLOR_BUFFER_BIT : 0) | + GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + int farplane = fog * 5 / 2; + gluPerspective(fovy, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + transplayer(); + + glEnable(GL_TEXTURE_2D); + + int xs, ys; + skyoglid = lookuptexture(DEFAULT_SKY, &xs, &ys); + + resetcubes(); + + curvert = 0; + [strips removeAllItems]; + + render_world(player1.o.x, player1.o.y, player1.o.z, (int)player1.yaw, + (int)player1.pitch, (float)fov, w, h); + finishstrips(); + + gl_setupworld(); + + renderstripssky(); + + glLoadIdentity(); + glRotated(player1.pitch, -1.0, 0.0, 0.0); + glRotated(player1.yaw, 0.0, 1.0, 0.0); + glRotated(90.0, 1.0, 0.0, 0.0); + glColor3f(1.0f, 1.0f, 1.0f); + glDisable(GL_FOG); + glDepthFunc(GL_GREATER); + draw_envbox(14, fog * 4 / 3); + glDepthFunc(GL_LESS); + glEnable(GL_FOG); + + transplayer(); + + overbright(2); + + renderstrips(); + + xtraverts = 0; + + renderclients(); + monsterrender(); + + renderentities(); + + renderspheres(curtime); + renderents(); + + glDisable(GL_CULL_FACE); + + drawhudgun(fovy, aspect, farplane); + + overbright(1); + int nquads = renderwater(hf); + + overbright(2); + render_particles(curtime); + overbright(1); + + glDisable(GL_FOG); + + glDisable(GL_TEXTURE_2D); + + gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); + + glEnable(GL_CULL_FACE); + glEnable(GL_FOG); +} DELETED src/rendergl.mm Index: src/rendergl.mm ================================================================== --- src/rendergl.mm +++ /dev/null @@ -1,504 +0,0 @@ -// rendergl.cpp: core opengl rendering stuff - -#include "cube.h" - -#import "DynamicEntity.h" -#import "OFString+Cube.h" - -#ifdef DARWIN -# define GL_COMBINE_EXT GL_COMBINE_ARB -# define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB -# define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB -# define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB -# define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB -#endif - -extern int curvert; - -bool hasoverbright = false; - -void purgetextures(); - -GLUquadricObj *qsphere = NULL; -int glmaxtexsize = 256; - -void -gl_init(int w, int h) -{ - // #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f - - glViewport(0, 0, w, h); - glClearDepth(1.0); - glDepthFunc(GL_LESS); - glEnable(GL_DEPTH_TEST); - glShadeModel(GL_SMOOTH); - - glEnable(GL_FOG); - glFogi(GL_FOG_MODE, GL_LINEAR); - glFogf(GL_FOG_DENSITY, 0.25); - glHint(GL_FOG_HINT, GL_NICEST); - - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(-3.0, -3.0); - - glCullFace(GL_FRONT); - glEnable(GL_CULL_FACE); - - char *exts = (char *)glGetString(GL_EXTENSIONS); - - if (strstr(exts, "GL_EXT_texture_env_combine")) - hasoverbright = true; - else - conoutf(@"WARNING: cannot use overbright lighting, using old " - @"lighting model!"); - - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); - - purgetextures(); - - if (!(qsphere = gluNewQuadric())) - fatal(@"glu sphere"); - gluQuadricDrawStyle(qsphere, GLU_FILL); - gluQuadricOrientation(qsphere, GLU_INSIDE); - gluQuadricTexture(qsphere, GL_TRUE); - glNewList(1, GL_COMPILE); - gluSphere(qsphere, 1, 12, 6); - glEndList(); -} - -void -cleangl() -{ - if (qsphere) - gluDeleteQuadric(qsphere); -} - -bool -installtex(int tnum, OFIRI *IRI, int *xs, int *ys, bool clamp) -{ - SDL_Surface *s = IMG_Load(IRI.fileSystemRepresentation.UTF8String); - if (s == NULL) { - conoutf(@"couldn't load texture %@", IRI.string); - return false; - } - - if (s->format->BitsPerPixel != 24) { - SDL_PixelFormat *format = - SDL_AllocFormat(SDL_PIXELFORMAT_RGB24); - if (format == NULL) { - conoutf(@"texture cannot be converted to 24bpp: %@", - IRI.string); - return false; - } - - @try { - SDL_Surface *converted = - SDL_ConvertSurface(s, format, 0); - if (converted == NULL) { - conoutf(@"texture cannot be converted " - @"to 24bpp: %@", - IRI.string); - return false; - } - - SDL_FreeSurface(s); - s = converted; - } @finally { - SDL_FreeFormat(format); - } - } - -#if 0 - loopi(s->w * s->h * 3) - { - uchar *p = (uchar *)s->pixels + i; - *p = 255 - *p; - } -#endif - glBindTexture(GL_TEXTURE_2D, tnum); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, - clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, - clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_LINEAR_MIPMAP_LINEAR); // NEAREST); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - - *xs = s->w; - *ys = s->h; - while (*xs > glmaxtexsize || *ys > glmaxtexsize) { - *xs /= 2; - *ys /= 2; - } - - void *scaledimg = s->pixels; - - if (*xs != s->w) { - conoutf(@"warning: quality loss: scaling %@", - IRI.string); // for voodoo cards under linux - scaledimg = OFAllocMemory(1, *xs * *ys * 3); - gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, - *xs, *ys, GL_UNSIGNED_BYTE, scaledimg); - } - - if (gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, *xs, *ys, GL_RGB, - GL_UNSIGNED_BYTE, scaledimg)) - fatal(@"could not build mipmaps"); - - if (*xs != s->w) - free(scaledimg); - - SDL_FreeSurface(s); - - return true; -} - -// management of texture slots -// each texture slot can have multople texture frames, of which currently only -// the first is used additional frames can be used for various shaders - -static const int MAXTEX = 1000; -static int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) -static int texy[MAXTEX]; -static OFString *texname[MAXTEX]; -static int curtex = 0; -static const int FIRSTTEX = 1000; // opengl id = loaded id + FIRSTTEX -// std 1+, sky 14+, mdls 20+ - -static const int MAXFRAMES = 2; // increase to allow more complex shader defs -static int mapping[256] - [MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) -static OFString *mapname[256][MAXFRAMES]; - -void -purgetextures() -{ - loopi(256) loop(j, MAXFRAMES) mapping[i][j] = 0; -} - -int curtexnum = 0; - -void -texturereset() -{ - curtexnum = 0; -} -COMMAND(texturereset, ARG_NONE) - -void -texture(OFString *aframe, OFString *name) -{ - int num = curtexnum++, frame = aframe.cube_intValue; - - if (num < 0 || num >= 256 || frame < 0 || frame >= MAXFRAMES) - return; - - mapping[num][frame] = 1; - mapname[num][frame] = [name stringByReplacingOccurrencesOfString:@"\\" - withString:@"/"]; -} -COMMAND(texture, ARG_2STR) - -int -lookuptexture(int tex, int *xs, int *ys) -{ - int frame = 0; // other frames? - int tid = mapping[tex][frame]; - - if (tid >= FIRSTTEX) { - *xs = texx[tid - FIRSTTEX]; - *ys = texy[tid - FIRSTTEX]; - return tid; - } - - *xs = *ys = 16; - if (tid == 0) - return 1; // crosshair :) - - loopi(curtex) // lazily happens once per "texture" command, basically - { - if ([mapname[tex][frame] isEqual:texname[i]]) { - mapping[tex][frame] = tid = i + FIRSTTEX; - *xs = texx[i]; - *ys = texy[i]; - return tid; - } - } - - if (curtex == MAXTEX) - fatal(@"loaded too many textures"); - - int tnum = curtex + FIRSTTEX; - texname[curtex] = mapname[tex][frame]; - - OFString *path = - [OFString stringWithFormat:@"packages/%@", texname[curtex]]; - - if (installtex(tnum, - [Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:path], - xs, ys, false)) { - mapping[tex][frame] = tnum; - texx[curtex] = *xs; - texy[curtex] = *ys; - curtex++; - return tnum; - } else { - return mapping[tex][frame] = FIRSTTEX; // temp fix - } -} - -void -setupworld() -{ - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - setarraypointers(); - - if (hasoverbright) { - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); - glTexEnvi( - GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); - } -} - -int skyoglid; - -struct strip { - int tex, start, num; -}; -static OFMutableData *strips; - -void -renderstripssky() -{ - glBindTexture(GL_TEXTURE_2D, skyoglid); - - const strip *items = (const strip *)strips.items; - size_t count = strips.count; - for (size_t i = 0; i < count; i++) - if (items[i].tex == skyoglid) - glDrawArrays( - GL_TRIANGLE_STRIP, items[i].start, items[i].num); -} - -void -renderstrips() -{ - int lasttex = -1; - const strip *items = (const strip *)strips.items; - size_t count = strips.count; - for (size_t i = 0; i < count; i++) { - if (items[i].tex == skyoglid) - continue; - - if (items[i].tex != lasttex) { - glBindTexture(GL_TEXTURE_2D, items[i].tex); - lasttex = items[i].tex; - } - - glDrawArrays(GL_TRIANGLE_STRIP, items[i].start, items[i].num); - } -} - -void -overbright(float amount) -{ - if (hasoverbright) - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount); -} - -void -addstrip(int tex, int start, int n) -{ - if (strips == nil) - strips = [[OFMutableData alloc] initWithItemSize:sizeof(strip)]; - - strip s = { .tex = tex, .start = start, .num = n }; - [strips addItem:&s]; -} - -VARFP(gamma, 30, 100, 300, { - float f = gamma / 100.0f; - Uint16 ramp[256]; - - SDL_CalculateGammaRamp(f, ramp); - - if (SDL_SetWindowGammaRamp( - Cube.sharedInstance.window, ramp, ramp, ramp) == -1) { - conoutf( - @"Could not set gamma (card/driver doesn't support it?)"); - conoutf(@"sdl: %s", SDL_GetError()); - } -}) - -void -transplayer() -{ - glLoadIdentity(); - - glRotated(player1.roll, 0.0, 0.0, 1.0); - glRotated(player1.pitch, -1.0, 0.0, 0.0); - glRotated(player1.yaw, 0.0, 1.0, 0.0); - - glTranslated(-player1.o.x, - (player1.state == CS_DEAD ? player1.eyeheight - 0.2f : 0) - - player1.o.z, - -player1.o.y); -} - -VARP(fov, 10, 105, 120); - -int xtraverts; - -VAR(fog, 64, 180, 1024); -VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); - -VARP(hudgun, 0, 1, 1); - -OFString *hudgunnames[] = { @"hudguns/fist", @"hudguns/shotg", - @"hudguns/chaing", @"hudguns/rocket", @"hudguns/rifle" }; - -void -drawhudmodel(int start, int end, float speed, int base) -{ - rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f, - OFMakeVector3D(player1.o.x, player1.o.z, player1.o.y), - player1.yaw + 90, player1.pitch, false, 1.0f, speed, 0, base); -} - -void -drawhudgun(float fovy, float aspect, int farplane) -{ - if (!hudgun /*|| !player1.gunselect*/) - return; - - glEnable(GL_CULL_FACE); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(fovy, aspect, 0.3f, farplane); - glMatrixMode(GL_MODELVIEW); - - // glClear(GL_DEPTH_BUFFER_BIT); - int rtime = reloadtime(player1.gunselect); - if (player1.lastaction && player1.lastattackgun == player1.gunselect && - lastmillis - player1.lastaction < rtime) { - drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction); - } else - drawhudmodel(6, 1, 100, 0); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(fovy, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - glDisable(GL_CULL_FACE); -} - -void -gl_drawframe(int w, int h, float curfps) -{ - float hf = hdr.waterlevel - 0.3f; - float fovy = (float)fov * h / w; - float aspect = w / (float)h; - bool underwater = player1.o.z < hf; - - glFogi(GL_FOG_START, (fog + 64) / 8); - glFogi(GL_FOG_END, fog); - float fogc[4] = { (fogcolour >> 16) / 256.0f, - ((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f, - 1.0f }; - glFogfv(GL_FOG_COLOR, fogc); - glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); - - if (underwater) { - fovy += (float)sin(lastmillis / 1000.0) * 2.0f; - aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; - glFogi(GL_FOG_START, 0); - glFogi(GL_FOG_END, (fog + 96) / 8); - } - - glClear((player1.outsidemap ? GL_COLOR_BUFFER_BIT : 0) | - GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - int farplane = fog * 5 / 2; - gluPerspective(fovy, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - transplayer(); - - glEnable(GL_TEXTURE_2D); - - int xs, ys; - skyoglid = lookuptexture(DEFAULT_SKY, &xs, &ys); - - resetcubes(); - - curvert = 0; - [strips removeAllItems]; - - render_world(player1.o.x, player1.o.y, player1.o.z, (int)player1.yaw, - (int)player1.pitch, (float)fov, w, h); - finishstrips(); - - setupworld(); - - renderstripssky(); - - glLoadIdentity(); - glRotated(player1.pitch, -1.0, 0.0, 0.0); - glRotated(player1.yaw, 0.0, 1.0, 0.0); - glRotated(90.0, 1.0, 0.0, 0.0); - glColor3f(1.0f, 1.0f, 1.0f); - glDisable(GL_FOG); - glDepthFunc(GL_GREATER); - draw_envbox(14, fog * 4 / 3); - glDepthFunc(GL_LESS); - glEnable(GL_FOG); - - transplayer(); - - overbright(2); - - renderstrips(); - - xtraverts = 0; - - renderclients(); - monsterrender(); - - renderentities(); - - renderspheres(curtime); - renderents(); - - glDisable(GL_CULL_FACE); - - drawhudgun(fovy, aspect, farplane); - - overbright(1); - int nquads = renderwater(hf); - - overbright(2); - render_particles(curtime); - overbright(1); - - glDisable(GL_FOG); - - glDisable(GL_TEXTURE_2D); - - gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); - - glEnable(GL_CULL_FACE); - glEnable(GL_FOG); -} ADDED src/rendermd2.m Index: src/rendermd2.m ================================================================== --- /dev/null +++ src/rendermd2.m @@ -0,0 +1,135 @@ +// rendermd2.cpp: loader code adapted from a nehe tutorial + +#include "cube.h" + +#import "DynamicEntity.h" +#import "MD2.h" +#import "MapModelInfo.h" +#import "OFString+Cube.h" + +static OFMutableDictionary *mdllookup = nil; +static OFMutableArray *mapmodels = nil; + +static const int FIRSTMDL = 20; + +void +delayedload(MD2 *m) +{ + if (!m.loaded) { + OFString *path = [OFString + stringWithFormat:@"packages/models/%@", m.loadname]; + OFIRI *baseIRI = [Cube.sharedInstance.gameDataIRI + IRIByAppendingPathComponent:path]; + + OFIRI *IRI1 = [baseIRI IRIByAppendingPathComponent:@"tris.md2"]; + if (![m loadWithIRI:IRI1]) + fatal(@"loadmodel: %@", IRI1.string); + + OFIRI *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"]; + int xs, ys; + installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false); + m.loaded = true; + } +} + +MD2 * +loadmodel(OFString *name) +{ + static int modelnum = 0; + + MD2 *m = mdllookup[name]; + if (m != nil) + return m; + + m = [MD2 md2]; + m.mdlnum = modelnum++; + m.mmi = [MapModelInfo infoWithRad:2 h:2 zoff:0 snap:0 name:@""]; + m.loadname = name; + + if (mdllookup == nil) + mdllookup = [[OFMutableDictionary alloc] init]; + + mdllookup[name] = m; + + return m; +} + +void +mapmodel( + OFString *rad, OFString *h, OFString *zoff, OFString *snap, OFString *name) +{ + MD2 *m = loadmodel([name stringByReplacingOccurrencesOfString:@"\\" + withString:@"/"]); + m.mmi = [MapModelInfo infoWithRad:rad.cube_intValue + h:h.cube_intValue + zoff:zoff.cube_intValue + snap:snap.cube_intValue + name:m.loadname]; + + if (mapmodels == nil) + mapmodels = [[OFMutableArray alloc] init]; + + [mapmodels addObject:m]; +} +COMMAND(mapmodel, ARG_5STR) + +void +mapmodelreset() +{ + [mapmodels removeAllObjects]; +} +COMMAND(mapmodelreset, ARG_NONE) + +MapModelInfo * +getmminfo(int i) +{ + return i < mapmodels.count ? mapmodels[i].mmi : nil; +} + +void +rendermodel(OFString *mdl, int frame, int range, int tex, float rad, + OFVector3D position, float yaw, float pitch, bool teammate, float scale, + float speed, int snap, int basetime) +{ + MD2 *m = loadmodel(mdl); + + if (isoccluded(player1.o.x, player1.o.y, position.x - rad, + position.z - rad, rad * 2)) + return; + + delayedload(m); + + int xs, ys; + glBindTexture(GL_TEXTURE_2D, + tex ? lookuptexture(tex, &xs, &ys) : FIRSTMDL + m.mdlnum); + + int ix = (int)position.x; + int iy = (int)position.z; + OFVector3D light = OFMakeVector3D(1, 1, 1); + + if (!OUTBORD(ix, iy)) { + struct sqr *s = S(ix, iy); + float ll = 256.0f; // 0.96f; + float of = 0.0f; // 0.1f; + light.x = s->r / ll + of; + light.y = s->g / ll + of; + light.z = s->b / ll + of; + } + + if (teammate) { + light.x *= 0.6f; + light.y *= 0.7f; + light.z *= 1.2f; + } + + [m renderWithLight:light + frame:frame + range:range + position:position + yaw:yaw + pitch:pitch + scale:scale + speed:speed + snap:snap + basetime:basetime]; +} DELETED src/rendermd2.mm Index: src/rendermd2.mm ================================================================== --- src/rendermd2.mm +++ /dev/null @@ -1,135 +0,0 @@ -// rendermd2.cpp: loader code adapted from a nehe tutorial - -#include "cube.h" - -#import "DynamicEntity.h" -#import "MD2.h" -#import "MapModelInfo.h" -#import "OFString+Cube.h" - -static OFMutableDictionary *mdllookup = nil; -static OFMutableArray *mapmodels = nil; - -static const int FIRSTMDL = 20; - -void -delayedload(MD2 *m) -{ - if (!m.loaded) { - OFString *path = [OFString - stringWithFormat:@"packages/models/%@", m.loadname]; - OFIRI *baseIRI = [Cube.sharedInstance.gameDataIRI - IRIByAppendingPathComponent:path]; - - OFIRI *IRI1 = [baseIRI IRIByAppendingPathComponent:@"tris.md2"]; - if (![m loadWithIRI:IRI1]) - fatal(@"loadmodel: %@", IRI1.string); - - OFIRI *IRI2 = [baseIRI IRIByAppendingPathComponent:@"skin.jpg"]; - int xs, ys; - installtex(FIRSTMDL + m.mdlnum, IRI2, &xs, &ys, false); - m.loaded = true; - } -} - -MD2 * -loadmodel(OFString *name) -{ - static int modelnum = 0; - - MD2 *m = mdllookup[name]; - if (m != nil) - return m; - - m = [MD2 md2]; - m.mdlnum = modelnum++; - m.mmi = [MapModelInfo infoWithRad:2 h:2 zoff:0 snap:0 name:@""]; - m.loadname = name; - - if (mdllookup == nil) - mdllookup = [[OFMutableDictionary alloc] init]; - - mdllookup[name] = m; - - return m; -} - -void -mapmodel( - OFString *rad, OFString *h, OFString *zoff, OFString *snap, OFString *name) -{ - MD2 *m = loadmodel([name stringByReplacingOccurrencesOfString:@"\\" - withString:@"/"]); - m.mmi = [MapModelInfo infoWithRad:rad.cube_intValue - h:h.cube_intValue - zoff:zoff.cube_intValue - snap:snap.cube_intValue - name:m.loadname]; - - if (mapmodels == nil) - mapmodels = [[OFMutableArray alloc] init]; - - [mapmodels addObject:m]; -} -COMMAND(mapmodel, ARG_5STR) - -void -mapmodelreset() -{ - [mapmodels removeAllObjects]; -} -COMMAND(mapmodelreset, ARG_NONE) - -MapModelInfo * -getmminfo(int i) -{ - return i < mapmodels.count ? mapmodels[i].mmi : nil; -} - -void -rendermodel(OFString *mdl, int frame, int range, int tex, float rad, - OFVector3D position, float yaw, float pitch, bool teammate, float scale, - float speed, int snap, int basetime) -{ - MD2 *m = loadmodel(mdl); - - if (isoccluded(player1.o.x, player1.o.y, position.x - rad, - position.z - rad, rad * 2)) - return; - - delayedload(m); - - int xs, ys; - glBindTexture(GL_TEXTURE_2D, - tex ? lookuptexture(tex, &xs, &ys) : FIRSTMDL + m.mdlnum); - - int ix = (int)position.x; - int iy = (int)position.z; - OFVector3D light = OFMakeVector3D(1, 1, 1); - - if (!OUTBORD(ix, iy)) { - sqr *s = S(ix, iy); - float ll = 256.0f; // 0.96f; - float of = 0.0f; // 0.1f; - light.x = s->r / ll + of; - light.y = s->g / ll + of; - light.z = s->b / ll + of; - } - - if (teammate) { - light.x *= 0.6f; - light.y *= 0.7f; - light.z *= 1.2f; - } - - [m renderWithLight:light - frame:frame - range:range - position:position - yaw:yaw - pitch:pitch - scale:scale - speed:speed - snap:snap - basetime:basetime]; -} ADDED src/renderparticles.m Index: src/renderparticles.m ================================================================== --- /dev/null +++ src/renderparticles.m @@ -0,0 +1,168 @@ +// renderparticles.cpp + +#include "cube.h" + +#import "DynamicEntity.h" + +const int MAXPARTICLES = 10500; +const int NUMPARTCUTOFF = 20; +struct particle { + OFVector3D o, d; + int fade, type; + int millis; + struct particle *next; +}; +struct particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; +bool parinit = false; + +VARP(maxparticles, 100, 2000, MAXPARTICLES - 500); + +static void +newparticle(const OFVector3D *o, const OFVector3D *d, int fade, int type) +{ + if (!parinit) { + loopi(MAXPARTICLES) + { + particles[i].next = parempty; + parempty = &particles[i]; + } + parinit = true; + } + if (parempty) { + struct particle *p = parempty; + parempty = p->next; + p->o = *o; + p->d = *d; + p->fade = fade; + p->type = type; + p->millis = lastmillis; + p->next = parlist; + parlist = p; + } +} + +VAR(demotracking, 0, 0, 1); +VARP(particlesize, 20, 100, 500); + +OFVector3D right, up; + +void +setorient(const OFVector3D *r, const OFVector3D *u) +{ + right = *r; + up = *u; +} + +void +render_particles(int time) +{ + if (demoplayback && demotracking) { + OFVector3D o = player1.o; + OFVector3D nom = OFMakeVector3D(0, 0, 0); + newparticle(&o, &nom, 100000000, 8); + } + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glDisable(GL_FOG); + + struct parttype { + float r, g, b; + int gr, tex; + float sz; + } parttypes[] = { + { 0.7f, 0.6f, 0.3f, 2, 3, 0.06f }, // yellow: sparks + { 0.5f, 0.5f, 0.5f, 20, 7, 0.15f }, // grey: small smoke + { 0.2f, 0.2f, 1.0f, 20, 3, + 0.08f }, // blue: edit mode entities + { 1.0f, 0.1f, 0.1f, 1, 7, 0.06f }, // red: blood spats + { 1.0f, 0.8f, 0.8f, 20, 6, 1.2f }, // yellow: fireball1 + { 0.5f, 0.5f, 0.5f, 20, 7, 0.6f }, // grey: big smoke + { 1.0f, 1.0f, 1.0f, 20, 8, 1.2f }, // blue: fireball2 + { 1.0f, 1.0f, 1.0f, 20, 9, 1.2f }, // green: fireball3 + { 1.0f, 0.1f, 0.1f, 0, 7, 0.2f }, // red: demotrack + }; + + int numrender = 0; + + for (struct particle *p, **pp = &parlist; (p = *pp) != NULL;) { + struct parttype *pt = &parttypes[p->type]; + + glBindTexture(GL_TEXTURE_2D, pt->tex); + glBegin(GL_QUADS); + + glColor3d(pt->r, pt->g, pt->b); + float sz = pt->sz * particlesize / 100.0f; + // perf varray? + glTexCoord2f(0.0, 1.0); + glVertex3d(p->o.x + (-right.x + up.x) * sz, + p->o.z + (-right.y + up.y) * sz, + p->o.y + (-right.z + up.z) * sz); + glTexCoord2f(1.0, 1.0); + glVertex3d(p->o.x + (right.x + up.x) * sz, + p->o.z + (right.y + up.y) * sz, + p->o.y + (right.z + up.z) * sz); + glTexCoord2f(1.0, 0.0); + glVertex3d(p->o.x + (right.x - up.x) * sz, + p->o.z + (right.y - up.y) * sz, + p->o.y + (right.z - up.z) * sz); + glTexCoord2f(0.0, 0.0); + glVertex3d(p->o.x + (-right.x - up.x) * sz, + p->o.z + (-right.y - up.y) * sz, + p->o.y + (-right.z - up.z) * sz); + glEnd(); + xtraverts += 4; + + if (numrender++ > maxparticles || (p->fade -= time) < 0) { + *pp = p->next; + p->next = parempty; + parempty = p; + } else { + if (pt->gr) + p->o.z -= ((lastmillis - p->millis) / 3.0f) * + curtime / (pt->gr * 10000); + OFVector3D a = p->d; + vmul(a, time); + vdiv(a, 20000.0f); + vadd(p->o, a); + pp = &p->next; + } + } + + glEnable(GL_FOG); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); +} + +void +particle_splash(int type, int num, int fade, const OFVector3D *p) +{ + loopi(num) + { + const int radius = type == 5 ? 50 : 150; + int x, y, z; + do { + x = rnd(radius * 2) - radius; + y = rnd(radius * 2) - radius; + z = rnd(radius * 2) - radius; + } while (x * x + y * y + z * z > radius * radius); + OFVector3D d = OFMakeVector3D(x, y, z); + newparticle(p, &d, rnd(fade * 3), type); + } +} + +void +particle_trail(int type, int fade, const OFVector3D *s, const OFVector3D *e) +{ + vdist(d, v, *s, *e); + vdiv(v, d * 2 + 0.1f); + OFVector3D p = *s; + loopi((int)d * 2) + { + vadd(p, v); + OFVector3D d = + OFMakeVector3D(rnd(11) - 5, rnd(11) - 5, rnd(11) - 5); + newparticle(&p, &d, rnd(fade) + fade, type); + } +} DELETED src/renderparticles.mm Index: src/renderparticles.mm ================================================================== --- src/renderparticles.mm +++ /dev/null @@ -1,168 +0,0 @@ -// renderparticles.cpp - -#include "cube.h" - -#import "DynamicEntity.h" - -const int MAXPARTICLES = 10500; -const int NUMPARTCUTOFF = 20; -struct particle { - OFVector3D o, d; - int fade, type; - int millis; - particle *next; -}; -particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; -bool parinit = false; - -VARP(maxparticles, 100, 2000, MAXPARTICLES - 500); - -static void -newparticle(const OFVector3D *o, const OFVector3D *d, int fade, int type) -{ - if (!parinit) { - loopi(MAXPARTICLES) - { - particles[i].next = parempty; - parempty = &particles[i]; - } - parinit = true; - } - if (parempty) { - particle *p = parempty; - parempty = p->next; - p->o = *o; - p->d = *d; - p->fade = fade; - p->type = type; - p->millis = lastmillis; - p->next = parlist; - parlist = p; - } -} - -VAR(demotracking, 0, 0, 1); -VARP(particlesize, 20, 100, 500); - -OFVector3D right, up; - -void -setorient(const OFVector3D *r, const OFVector3D *u) -{ - right = *r; - up = *u; -} - -void -render_particles(int time) -{ - if (demoplayback && demotracking) { - OFVector3D o = player1.o; - OFVector3D nom = OFMakeVector3D(0, 0, 0); - newparticle(&o, &nom, 100000000, 8); - } - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glDisable(GL_FOG); - - struct parttype { - float r, g, b; - int gr, tex; - float sz; - } parttypes[] = { - { 0.7f, 0.6f, 0.3f, 2, 3, 0.06f }, // yellow: sparks - { 0.5f, 0.5f, 0.5f, 20, 7, 0.15f }, // grey: small smoke - { 0.2f, 0.2f, 1.0f, 20, 3, - 0.08f }, // blue: edit mode entities - { 1.0f, 0.1f, 0.1f, 1, 7, 0.06f }, // red: blood spats - { 1.0f, 0.8f, 0.8f, 20, 6, 1.2f }, // yellow: fireball1 - { 0.5f, 0.5f, 0.5f, 20, 7, 0.6f }, // grey: big smoke - { 1.0f, 1.0f, 1.0f, 20, 8, 1.2f }, // blue: fireball2 - { 1.0f, 1.0f, 1.0f, 20, 9, 1.2f }, // green: fireball3 - { 1.0f, 0.1f, 0.1f, 0, 7, 0.2f }, // red: demotrack - }; - - int numrender = 0; - - for (particle *p, **pp = &parlist; p = *pp;) { - parttype *pt = &parttypes[p->type]; - - glBindTexture(GL_TEXTURE_2D, pt->tex); - glBegin(GL_QUADS); - - glColor3d(pt->r, pt->g, pt->b); - float sz = pt->sz * particlesize / 100.0f; - // perf varray? - glTexCoord2f(0.0, 1.0); - glVertex3d(p->o.x + (-right.x + up.x) * sz, - p->o.z + (-right.y + up.y) * sz, - p->o.y + (-right.z + up.z) * sz); - glTexCoord2f(1.0, 1.0); - glVertex3d(p->o.x + (right.x + up.x) * sz, - p->o.z + (right.y + up.y) * sz, - p->o.y + (right.z + up.z) * sz); - glTexCoord2f(1.0, 0.0); - glVertex3d(p->o.x + (right.x - up.x) * sz, - p->o.z + (right.y - up.y) * sz, - p->o.y + (right.z - up.z) * sz); - glTexCoord2f(0.0, 0.0); - glVertex3d(p->o.x + (-right.x - up.x) * sz, - p->o.z + (-right.y - up.y) * sz, - p->o.y + (-right.z - up.z) * sz); - glEnd(); - xtraverts += 4; - - if (numrender++ > maxparticles || (p->fade -= time) < 0) { - *pp = p->next; - p->next = parempty; - parempty = p; - } else { - if (pt->gr) - p->o.z -= ((lastmillis - p->millis) / 3.0f) * - curtime / (pt->gr * 10000); - OFVector3D a = p->d; - vmul(a, time); - vdiv(a, 20000.0f); - vadd(p->o, a); - pp = &p->next; - } - } - - glEnable(GL_FOG); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); -} - -void -particle_splash(int type, int num, int fade, const OFVector3D *p) -{ - loopi(num) - { - const int radius = type == 5 ? 50 : 150; - int x, y, z; - do { - x = rnd(radius * 2) - radius; - y = rnd(radius * 2) - radius; - z = rnd(radius * 2) - radius; - } while (x * x + y * y + z * z > radius * radius); - OFVector3D d = OFMakeVector3D(x, y, z); - newparticle(p, &d, rnd(fade * 3), type); - } -} - -void -particle_trail(int type, int fade, const OFVector3D *s, const OFVector3D *e) -{ - vdist(d, v, *s, *e); - vdiv(v, d * 2 + 0.1f); - OFVector3D p = *s; - loopi((int)d * 2) - { - vadd(p, v); - OFVector3D d = - OFMakeVector3D(rnd(11) - 5, rnd(11) - 5, rnd(11) - 5); - newparticle(&p, &d, rnd(fade) + fade, type); - } -} ADDED src/rendertext.m Index: src/rendertext.m ================================================================== --- /dev/null +++ src/rendertext.m @@ -0,0 +1,253 @@ +// rendertext.cpp: based on Don's gl_text.cpp + +#include "cube.h" + +short char_coords[96][4] = { + { 0, 0, 25, 64 }, //! + { 25, 0, 54, 64 }, //" + { 54, 0, 107, 64 }, // # + { 107, 0, 148, 64 }, //$ + { 148, 0, 217, 64 }, //% + { 217, 0, 263, 64 }, //& + { 263, 0, 280, 64 }, //' + { 280, 0, 309, 64 }, //( + { 309, 0, 338, 64 }, //) + { 338, 0, 379, 64 }, //* + { 379, 0, 432, 64 }, //+ + { 432, 0, 455, 64 }, //, + { 455, 0, 484, 64 }, //- + { 0, 64, 21, 128 }, //. + { 23, 64, 52, 128 }, /// + { 52, 64, 93, 128 }, // 0 + { 93, 64, 133, 128 }, // 1 + { 133, 64, 174, 128 }, // 2 + { 174, 64, 215, 128 }, // 3 + { 215, 64, 256, 128 }, // 4 + { 256, 64, 296, 128 }, // 5 + { 296, 64, 337, 128 }, // 6 + { 337, 64, 378, 128 }, // 7 + { 378, 64, 419, 128 }, // 8 + { 419, 64, 459, 128 }, // 9 + { 459, 64, 488, 128 }, //: + { 0, 128, 29, 192 }, //; + { 29, 128, 81, 192 }, //< + { 81, 128, 134, 192 }, //= + { 134, 128, 186, 192 }, //> + { 186, 128, 221, 192 }, //? + { 221, 128, 285, 192 }, //@ + { 285, 128, 329, 192 }, // A + { 329, 128, 373, 192 }, // B + { 373, 128, 418, 192 }, // C + { 418, 128, 467, 192 }, // D + { 0, 192, 40, 256 }, // E + { 40, 192, 77, 256 }, // F + { 77, 192, 127, 256 }, // G + { 127, 192, 175, 256 }, // H + { 175, 192, 202, 256 }, // I + { 202, 192, 231, 256 }, // J + { 231, 192, 275, 256 }, // K + { 275, 192, 311, 256 }, // L + { 311, 192, 365, 256 }, // M + { 365, 192, 413, 256 }, // N + { 413, 192, 463, 256 }, // O + { 1, 256, 38, 320 }, // P + { 38, 256, 89, 320 }, // Q + { 89, 256, 133, 320 }, // R + { 133, 256, 176, 320 }, // S + { 177, 256, 216, 320 }, // T + { 217, 256, 263, 320 }, // U + { 263, 256, 307, 320 }, // V + { 307, 256, 370, 320 }, // W + { 370, 256, 414, 320 }, // X + { 414, 256, 453, 320 }, // Y + { 453, 256, 497, 320 }, // Z + { 0, 320, 29, 384 }, //[ + { 29, 320, 58, 384 }, //"\" + { 59, 320, 87, 384 }, //] + { 87, 320, 139, 384 }, //^ + { 139, 320, 180, 384 }, //_ + { 180, 320, 221, 384 }, //` + { 221, 320, 259, 384 }, // a + { 259, 320, 299, 384 }, // b + { 299, 320, 332, 384 }, // c + { 332, 320, 372, 384 }, // d + { 372, 320, 411, 384 }, // e + { 411, 320, 433, 384 }, // f + { 435, 320, 473, 384 }, // g + { 0, 384, 40, 448 }, // h + { 40, 384, 56, 448 }, // i + { 58, 384, 80, 448 }, // j + { 80, 384, 118, 448 }, // k + { 118, 384, 135, 448 }, // l + { 135, 384, 197, 448 }, // m + { 197, 384, 238, 448 }, // n + { 238, 384, 277, 448 }, // o + { 277, 384, 317, 448 }, // p + { 317, 384, 356, 448 }, // q + { 357, 384, 384, 448 }, // r + { 385, 384, 417, 448 }, // s + { 417, 384, 442, 448 }, // t + { 443, 384, 483, 448 }, // u + { 0, 448, 38, 512 }, // v + { 38, 448, 90, 512 }, // w + { 90, 448, 128, 512 }, // x + { 128, 448, 166, 512 }, // y + { 166, 448, 200, 512 }, // z + { 200, 448, 241, 512 }, //{ + { 241, 448, 270, 512 }, //| + { 270, 448, 310, 512 }, //} + { 310, 448, 363, 512 }, //~ +}; + +int +text_width(OFString *string) +{ + const char *str = string.UTF8String; + size_t len = string.UTF8StringLength; + + int x = 0; + for (int i = 0; i < len; i++) { + int c = str[i]; + if (c == '\t') { + x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; + continue; + } + + if (c == '\f') + continue; + + if (c == ' ') { + x += FONTH / 2; + continue; + } + + c -= 33; + if (c < 0 || c >= 95) + continue; + + int in_width = char_coords[c][2] - char_coords[c][0]; + x += in_width + 1; + } + + return x; +} + +void +draw_textf(OFConstantString *format, int left, int top, int gl_num, ...) +{ + va_list arguments; + va_start(arguments, gl_num); + OFString *str = [[OFString alloc] initWithFormat:format + arguments:arguments]; + va_end(arguments); + draw_text(str, left, top, gl_num); +} + +void +draw_text(OFString *string, int left, int top, int gl_num) +{ + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, gl_num); + glColor3ub(255, 255, 255); + + int x = left; + int y = top; + + int i; + float in_left, in_top, in_right, in_bottom; + int in_width, in_height; + + const char *str = string.UTF8String; + size_t len = string.UTF8StringLength; + for (i = 0; i < len; i++) { + int c = str[i]; + + if (c == '\t') { + x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; + continue; + } + + if (c == '\f') { + glColor3ub(64, 255, 128); + continue; + } + + if (c == ' ') { + x += FONTH / 2; + continue; + } + + c -= 33; + if (c < 0 || c >= 95) + continue; + + in_left = ((float)char_coords[c][0]) / 512.0f; + in_top = ((float)char_coords[c][1] + 2) / 512.0f; + in_right = ((float)char_coords[c][2]) / 512.0f; + in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; + + in_width = char_coords[c][2] - char_coords[c][0]; + in_height = char_coords[c][3] - char_coords[c][1]; + + glBegin(GL_QUADS); + glTexCoord2f(in_left, in_top); + glVertex2i(x, y); + glTexCoord2f(in_right, in_top); + glVertex2i(x + in_width, y); + glTexCoord2f(in_right, in_bottom); + glVertex2i(x + in_width, y + in_height); + glTexCoord2f(in_left, in_bottom); + glVertex2i(x, y + in_height); + glEnd(); + + xtraverts += 4; + x += in_width + 1; + } +} + +// also Don's code, so goes in here too :) + +void +draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, float s1, float t1, + int x1, int y1, int z1, float s2, float t2, int x2, int y2, int z2, + float s3, float t3, int x3, int y3, int z3, int texture) +{ + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glTexCoord2f(s3, t3); + glVertex3d(x3, y3, z3); + glTexCoord2f(s2, t2); + glVertex3d(x2, y2, z2); + glTexCoord2f(s1, t1); + glVertex3d(x1, y1, z1); + glTexCoord2f(s0, t0); + glVertex3d(x0, y0, z0); + glEnd(); + xtraverts += 4; +} + +void +draw_envbox(int t, int w) +{ + glDepthMask(GL_FALSE); + + draw_envbox_aux(1.0f, 1.0f, -w, -w, w, 0.0f, 1.0f, w, -w, w, 0.0f, 0.0f, + w, -w, -w, 1.0f, 0.0f, -w, -w, -w, t); + + draw_envbox_aux(1.0f, 1.0f, +w, w, w, 0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, + -w, w, -w, 1.0f, 0.0f, +w, w, -w, t + 1); + + draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, 1.0f, 0.0f, -w, w, -w, 1.0f, + 1.0f, -w, w, w, 0.0f, 1.0f, -w, -w, w, t + 2); + + draw_envbox_aux(1.0f, 1.0f, +w, -w, w, 0.0f, 1.0f, +w, w, w, 0.0f, 0.0f, + +w, w, -w, 1.0f, 0.0f, +w, -w, -w, t + 3); + + draw_envbox_aux(0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, +w, w, w, 1.0f, 0.0f, + +w, -w, w, 1.0f, 1.0f, -w, -w, w, t + 4); + + draw_envbox_aux(0.0f, 1.0f, +w, w, -w, 0.0f, 0.0f, -w, w, -w, 1.0f, + 0.0f, -w, -w, -w, 1.0f, 1.0f, +w, -w, -w, t + 5); + + glDepthMask(GL_TRUE); +} DELETED src/rendertext.mm Index: src/rendertext.mm ================================================================== --- src/rendertext.mm +++ /dev/null @@ -1,253 +0,0 @@ -// rendertext.cpp: based on Don's gl_text.cpp - -#include "cube.h" - -short char_coords[96][4] = { - { 0, 0, 25, 64 }, //! - { 25, 0, 54, 64 }, //" - { 54, 0, 107, 64 }, // # - { 107, 0, 148, 64 }, //$ - { 148, 0, 217, 64 }, //% - { 217, 0, 263, 64 }, //& - { 263, 0, 280, 64 }, //' - { 280, 0, 309, 64 }, //( - { 309, 0, 338, 64 }, //) - { 338, 0, 379, 64 }, //* - { 379, 0, 432, 64 }, //+ - { 432, 0, 455, 64 }, //, - { 455, 0, 484, 64 }, //- - { 0, 64, 21, 128 }, //. - { 23, 64, 52, 128 }, /// - { 52, 64, 93, 128 }, // 0 - { 93, 64, 133, 128 }, // 1 - { 133, 64, 174, 128 }, // 2 - { 174, 64, 215, 128 }, // 3 - { 215, 64, 256, 128 }, // 4 - { 256, 64, 296, 128 }, // 5 - { 296, 64, 337, 128 }, // 6 - { 337, 64, 378, 128 }, // 7 - { 378, 64, 419, 128 }, // 8 - { 419, 64, 459, 128 }, // 9 - { 459, 64, 488, 128 }, //: - { 0, 128, 29, 192 }, //; - { 29, 128, 81, 192 }, //< - { 81, 128, 134, 192 }, //= - { 134, 128, 186, 192 }, //> - { 186, 128, 221, 192 }, //? - { 221, 128, 285, 192 }, //@ - { 285, 128, 329, 192 }, // A - { 329, 128, 373, 192 }, // B - { 373, 128, 418, 192 }, // C - { 418, 128, 467, 192 }, // D - { 0, 192, 40, 256 }, // E - { 40, 192, 77, 256 }, // F - { 77, 192, 127, 256 }, // G - { 127, 192, 175, 256 }, // H - { 175, 192, 202, 256 }, // I - { 202, 192, 231, 256 }, // J - { 231, 192, 275, 256 }, // K - { 275, 192, 311, 256 }, // L - { 311, 192, 365, 256 }, // M - { 365, 192, 413, 256 }, // N - { 413, 192, 463, 256 }, // O - { 1, 256, 38, 320 }, // P - { 38, 256, 89, 320 }, // Q - { 89, 256, 133, 320 }, // R - { 133, 256, 176, 320 }, // S - { 177, 256, 216, 320 }, // T - { 217, 256, 263, 320 }, // U - { 263, 256, 307, 320 }, // V - { 307, 256, 370, 320 }, // W - { 370, 256, 414, 320 }, // X - { 414, 256, 453, 320 }, // Y - { 453, 256, 497, 320 }, // Z - { 0, 320, 29, 384 }, //[ - { 29, 320, 58, 384 }, //"\" - { 59, 320, 87, 384 }, //] - { 87, 320, 139, 384 }, //^ - { 139, 320, 180, 384 }, //_ - { 180, 320, 221, 384 }, //` - { 221, 320, 259, 384 }, // a - { 259, 320, 299, 384 }, // b - { 299, 320, 332, 384 }, // c - { 332, 320, 372, 384 }, // d - { 372, 320, 411, 384 }, // e - { 411, 320, 433, 384 }, // f - { 435, 320, 473, 384 }, // g - { 0, 384, 40, 448 }, // h - { 40, 384, 56, 448 }, // i - { 58, 384, 80, 448 }, // j - { 80, 384, 118, 448 }, // k - { 118, 384, 135, 448 }, // l - { 135, 384, 197, 448 }, // m - { 197, 384, 238, 448 }, // n - { 238, 384, 277, 448 }, // o - { 277, 384, 317, 448 }, // p - { 317, 384, 356, 448 }, // q - { 357, 384, 384, 448 }, // r - { 385, 384, 417, 448 }, // s - { 417, 384, 442, 448 }, // t - { 443, 384, 483, 448 }, // u - { 0, 448, 38, 512 }, // v - { 38, 448, 90, 512 }, // w - { 90, 448, 128, 512 }, // x - { 128, 448, 166, 512 }, // y - { 166, 448, 200, 512 }, // z - { 200, 448, 241, 512 }, //{ - { 241, 448, 270, 512 }, //| - { 270, 448, 310, 512 }, //} - { 310, 448, 363, 512 }, //~ -}; - -int -text_width(OFString *string) -{ - const char *str = string.UTF8String; - size_t len = string.UTF8StringLength; - - int x = 0; - for (int i = 0; i < len; i++) { - int c = str[i]; - if (c == '\t') { - x = (x + PIXELTAB) / PIXELTAB * PIXELTAB; - continue; - } - - if (c == '\f') - continue; - - if (c == ' ') { - x += FONTH / 2; - continue; - } - - c -= 33; - if (c < 0 || c >= 95) - continue; - - int in_width = char_coords[c][2] - char_coords[c][0]; - x += in_width + 1; - } - - return x; -} - -void -draw_textf(OFConstantString *format, int left, int top, int gl_num, ...) -{ - va_list arguments; - va_start(arguments, gl_num); - OFString *str = [[OFString alloc] initWithFormat:format - arguments:arguments]; - va_end(arguments); - draw_text(str, left, top, gl_num); -} - -void -draw_text(OFString *string, int left, int top, int gl_num) -{ - glBlendFunc(GL_ONE, GL_ONE); - glBindTexture(GL_TEXTURE_2D, gl_num); - glColor3ub(255, 255, 255); - - int x = left; - int y = top; - - int i; - float in_left, in_top, in_right, in_bottom; - int in_width, in_height; - - const char *str = string.UTF8String; - size_t len = string.UTF8StringLength; - for (i = 0; i < len; i++) { - int c = str[i]; - - if (c == '\t') { - x = (x - left + PIXELTAB) / PIXELTAB * PIXELTAB + left; - continue; - } - - if (c == '\f') { - glColor3ub(64, 255, 128); - continue; - } - - if (c == ' ') { - x += FONTH / 2; - continue; - } - - c -= 33; - if (c < 0 || c >= 95) - continue; - - in_left = ((float)char_coords[c][0]) / 512.0f; - in_top = ((float)char_coords[c][1] + 2) / 512.0f; - in_right = ((float)char_coords[c][2]) / 512.0f; - in_bottom = ((float)char_coords[c][3] - 2) / 512.0f; - - in_width = char_coords[c][2] - char_coords[c][0]; - in_height = char_coords[c][3] - char_coords[c][1]; - - glBegin(GL_QUADS); - glTexCoord2f(in_left, in_top); - glVertex2i(x, y); - glTexCoord2f(in_right, in_top); - glVertex2i(x + in_width, y); - glTexCoord2f(in_right, in_bottom); - glVertex2i(x + in_width, y + in_height); - glTexCoord2f(in_left, in_bottom); - glVertex2i(x, y + in_height); - glEnd(); - - xtraverts += 4; - x += in_width + 1; - } -} - -// also Don's code, so goes in here too :) - -void -draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, float s1, float t1, - int x1, int y1, int z1, float s2, float t2, int x2, int y2, int z2, - float s3, float t3, int x3, int y3, int z3, int texture) -{ - glBindTexture(GL_TEXTURE_2D, texture); - glBegin(GL_QUADS); - glTexCoord2f(s3, t3); - glVertex3d(x3, y3, z3); - glTexCoord2f(s2, t2); - glVertex3d(x2, y2, z2); - glTexCoord2f(s1, t1); - glVertex3d(x1, y1, z1); - glTexCoord2f(s0, t0); - glVertex3d(x0, y0, z0); - glEnd(); - xtraverts += 4; -} - -void -draw_envbox(int t, int w) -{ - glDepthMask(GL_FALSE); - - draw_envbox_aux(1.0f, 1.0f, -w, -w, w, 0.0f, 1.0f, w, -w, w, 0.0f, 0.0f, - w, -w, -w, 1.0f, 0.0f, -w, -w, -w, t); - - draw_envbox_aux(1.0f, 1.0f, +w, w, w, 0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, - -w, w, -w, 1.0f, 0.0f, +w, w, -w, t + 1); - - draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, 1.0f, 0.0f, -w, w, -w, 1.0f, - 1.0f, -w, w, w, 0.0f, 1.0f, -w, -w, w, t + 2); - - draw_envbox_aux(1.0f, 1.0f, +w, -w, w, 0.0f, 1.0f, +w, w, w, 0.0f, 0.0f, - +w, w, -w, 1.0f, 0.0f, +w, -w, -w, t + 3); - - draw_envbox_aux(0.0f, 1.0f, -w, w, w, 0.0f, 0.0f, +w, w, w, 1.0f, 0.0f, - +w, -w, w, 1.0f, 1.0f, -w, -w, w, t + 4); - - draw_envbox_aux(0.0f, 1.0f, +w, w, -w, 0.0f, 0.0f, -w, w, -w, 1.0f, - 0.0f, -w, -w, -w, 1.0f, 1.0f, +w, -w, -w, t + 5); - - glDepthMask(GL_TRUE); -} ADDED src/rndmap.m Index: src/rndmap.m ================================================================== --- /dev/null +++ src/rndmap.m @@ -0,0 +1,90 @@ +// rndmap.cpp: perlin noise landscape generation and some experimental random +// map stuff, currently not used + +#include "cube.h" + +float +noise(int x, int y, int seed) +{ + int n = x + y * 57; + n = (n << 13) ^ n; + return 1.0f - + ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / + 1073741824.0f; +} + +float +smoothednoise(int x, int y, int seed) +{ + float corners = + (noise(x - 1, y - 1, seed) + noise(x + 1, y - 1, seed) + + noise(x - 1, y + 1, seed) + noise(x + 1, y + 1, seed)) / + 16; + float sides = (noise(x - 1, y, seed) + noise(x + 1, y, seed) + + noise(x, y - 1, seed) + noise(x, y + 1, seed)) / + 8; + float center = noise(x, y, seed) / 4; + return corners + sides + center; +} + +float +interpolate(float a, float b, float x) +{ + float ft = x * 3.1415927f; + float f = (1.0f - (float)cos(ft)) * 0.5f; + return a * (1 - f) + b * f; +} + +float +interpolatednoise(float x, float y, int seed) +{ + int ix = (int)x; + float fx = x - ix; + int iy = (int)y; + float fy = y - iy; + float v1 = smoothednoise(ix, iy, seed); + float v2 = smoothednoise(ix + 1, iy, seed); + float v3 = smoothednoise(ix, iy + 1, seed); + float v4 = smoothednoise(ix + 1, iy + 1, seed); + float i1 = interpolate(v1, v2, fx); + float i2 = interpolate(v3, v4, fy); + return interpolate(i1, i2, fy); +} + +float +perlinnoise_2D(float x, float y, int seedstep, float pers) +{ + float total = 0; + int seed = 0; + for (int i = 0; i < 7; i++) { + float frequency = (float)(2 ^ i); + float amplitude = (float)pow(pers, i); + total += interpolatednoise(x * frequency, y * frequency, seed) * + amplitude; + seed += seedstep; + } + return total; +} + +void +perlinarea(const struct block *b, int scale, int seed, int psize) +{ + srand(seed); + seed = rnd(10000); + if (!scale) + scale = 10; + for (int x = b->x; x <= b->x + b->xs; x++) { + for (int y = b->y; y <= b->y + b->ys; y++) { + struct sqr *s = S(x, y); + if (!SOLID(s) && x != b->x + b->xs && y != b->y + b->ys) + s->type = FHF; + s->vdelta = + (int)(perlinnoise_2D(x / ((float)scale) + seed, + y / ((float)scale) + seed, 1000, 0.01f) * + 50 + + 25); + if (s->vdelta > 128) + s->vdelta = 0; + } + } +} DELETED src/rndmap.mm Index: src/rndmap.mm ================================================================== --- src/rndmap.mm +++ /dev/null @@ -1,90 +0,0 @@ -// rndmap.cpp: perlin noise landscape generation and some experimental random -// map stuff, currently not used - -#include "cube.h" - -float -noise(int x, int y, int seed) -{ - int n = x + y * 57; - n = (n << 13) ^ n; - return 1.0f - - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / - 1073741824.0f; -} - -float -smoothednoise(int x, int y, int seed) -{ - float corners = - (noise(x - 1, y - 1, seed) + noise(x + 1, y - 1, seed) + - noise(x - 1, y + 1, seed) + noise(x + 1, y + 1, seed)) / - 16; - float sides = (noise(x - 1, y, seed) + noise(x + 1, y, seed) + - noise(x, y - 1, seed) + noise(x, y + 1, seed)) / - 8; - float center = noise(x, y, seed) / 4; - return corners + sides + center; -} - -float -interpolate(float a, float b, float x) -{ - float ft = x * 3.1415927f; - float f = (1.0f - (float)cos(ft)) * 0.5f; - return a * (1 - f) + b * f; -} - -float -interpolatednoise(float x, float y, int seed) -{ - int ix = (int)x; - float fx = x - ix; - int iy = (int)y; - float fy = y - iy; - float v1 = smoothednoise(ix, iy, seed); - float v2 = smoothednoise(ix + 1, iy, seed); - float v3 = smoothednoise(ix, iy + 1, seed); - float v4 = smoothednoise(ix + 1, iy + 1, seed); - float i1 = interpolate(v1, v2, fx); - float i2 = interpolate(v3, v4, fy); - return interpolate(i1, i2, fy); -} - -float -perlinnoise_2D(float x, float y, int seedstep, float pers) -{ - float total = 0; - int seed = 0; - for (int i = 0; i < 7; i++) { - float frequency = (float)(2 ^ i); - float amplitude = (float)pow(pers, i); - total += interpolatednoise(x * frequency, y * frequency, seed) * - amplitude; - seed += seedstep; - } - return total; -} - -void -perlinarea(const block *b, int scale, int seed, int psize) -{ - srand(seed); - seed = rnd(10000); - if (!scale) - scale = 10; - for (int x = b->x; x <= b->x + b->xs; x++) { - for (int y = b->y; y <= b->y + b->ys; y++) { - sqr *s = S(x, y); - if (!SOLID(s) && x != b->x + b->xs && y != b->y + b->ys) - s->type = FHF; - s->vdelta = - (int)(perlinnoise_2D(x / ((float)scale) + seed, - y / ((float)scale) + seed, 1000, 0.01f) * - 50 + - 25); - if (s->vdelta > 128) - s->vdelta = 0; - } - } -} ADDED src/savegamedemo.m Index: src/savegamedemo.m ================================================================== --- /dev/null +++ src/savegamedemo.m @@ -0,0 +1,561 @@ +// loading and saving of savegames & demos, dumps the spawn state of all +// mapents, the full state of all dynents (monsters + player) + +#include "cube.h" + +#import "DynamicEntity.h" +#import "Entity.h" + +#ifdef OF_BIG_ENDIAN +static const int islittleendian = 0; +#else +static const int islittleendian = 1; +#endif + +static gzFile f = NULL; +bool demorecording = false; +bool demoplayback = false; +bool demoloading = false; +static OFMutableArray *playerhistory; +int democlientnum = 0; + +extern void startdemo(); + +static void +gzput(int i) +{ + gzputc(f, i); +} + +static void +gzputi(int i) +{ + gzwrite(f, &i, sizeof(int)); +} + +static void +gzputv(const OFVector3D *v) +{ + gzwrite(f, v, sizeof(OFVector3D)); +} + +static void +gzcheck(int a, int b) +{ + if (a != b) + fatal(@"savegame file corrupt (short)"); +} + +static int +gzget() +{ + char c = gzgetc(f); + return c; +} + +static int +gzgeti() +{ + int i; + gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); + return i; +} + +static void +gzgetv(OFVector3D *v) +{ + gzcheck(gzread(f, v, sizeof(OFVector3D)), sizeof(OFVector3D)); +} + +void +stop() +{ + if (f) { + if (demorecording) + gzputi(-1); + gzclose(f); + } + f = NULL; + demorecording = false; + demoplayback = false; + demoloading = false; + [playerhistory removeAllObjects]; +} + +void +stopifrecording() +{ + if (demorecording) + stop(); +} + +void +savestate(OFIRI *IRI) +{ + stop(); + f = gzopen([IRI.fileSystemRepresentation + cStringWithEncoding:OFLocale.encoding], + "wb9"); + if (!f) { + conoutf(@"could not write %@", IRI.string); + return; + } + gzwrite(f, (void *)"CUBESAVE", 8); + gzputc(f, islittleendian); + gzputi(SAVEGAMEVERSION); + OFData *data = [player1 dataBySerializing]; + gzputi(data.count); + char map[_MAXDEFSTR] = { 0 }; + memcpy(map, getclientmap().UTF8String, + min(getclientmap().UTF8StringLength, _MAXDEFSTR - 1)); + gzwrite(f, map, _MAXDEFSTR); + gzputi(gamemode); + gzputi(ents.count); + for (Entity *e in ents) + gzputc(f, e.spawned); + gzwrite(f, data.items, data.count); + OFArray *monsters = getmonsters(); + gzputi(monsters.count); + for (DynamicEntity *monster in monsters) { + data = [monster dataBySerializing]; + gzwrite(f, data.items, data.count); + } + gzputi(players.count); + for (id player in players) { + gzput(player == [OFNull null]); + data = [player dataBySerializing]; + gzwrite(f, data.items, data.count); + } +} + +void +savegame(OFString *name) +{ + if (!m_classicsp) { + conoutf(@"can only save classic sp games"); + return; + } + + OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name]; + OFIRI *IRI = + [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; + savestate(IRI); + stop(); + conoutf(@"wrote %@", IRI.string); +} +COMMAND(savegame, ARG_1STR) + +void +loadstate(OFIRI *IRI) +{ + stop(); + if (multiplayer()) + return; + f = gzopen([IRI.fileSystemRepresentation + cStringWithEncoding:OFLocale.encoding], + "rb9"); + if (!f) { + conoutf(@"could not open %@", IRI.string); + return; + } + + char mapname[_MAXDEFSTR] = { 0 }; + char buf[8]; + gzread(f, buf, 8); + if (strncmp(buf, "CUBESAVE", 8)) + goto out; + if (gzgetc(f) != islittleendian) + goto out; // not supporting save->load accross + // incompatible architectures simpifies things + // a LOT + if (gzgeti() != SAVEGAMEVERSION || + gzgeti() != DynamicEntity.serializedSize) + goto out; + gzread(f, mapname, _MAXDEFSTR); + nextmode = gzgeti(); + // continue below once map has been loaded and client & server + // have updated + changemap(@(mapname)); + return; +out: + conoutf(@"aborting: savegame/demo from a different version of " + @"cube or cpu architecture"); + stop(); +} + +void +loadgame(OFString *name) +{ + OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name]; + OFIRI *IRI = + [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; + loadstate(IRI); +} +COMMAND(loadgame, ARG_1STR) + +void +loadgameout() +{ + stop(); + conoutf(@"loadgame incomplete: savegame from a different version of " + @"this map"); +} + +void +loadgamerest() +{ + if (demoplayback || !f) + return; + + if (gzgeti() != ents.count) + return loadgameout(); + + for (Entity *e in ents) { + e.spawned = (gzgetc(f) != 0); + + if (e.type == CARROT && !e.spawned) + trigger(e.attr1, e.attr2, true); + } + + restoreserverstate(ents); + + OFMutableData *data = + [OFMutableData dataWithCapacity:DynamicEntity.serializedSize]; + [data increaseCountBy:DynamicEntity.serializedSize]; + gzread(f, data.mutableItems, data.count); + [player1 setFromSerializedData:data]; + player1.lastaction = lastmillis; + + int nmonsters = gzgeti(); + OFArray *monsters = getmonsters(); + if (nmonsters != monsters.count) + return loadgameout(); + + for (DynamicEntity *monster in monsters) { + gzread(f, data.mutableItems, data.count); + [monster setFromSerializedData:data]; + // lazy, could save id of enemy instead + monster.enemy = player1; + // also lazy, but no real noticable effect on game + monster.lastaction = monster.trigger = lastmillis + 500; + if (monster.state == CS_DEAD) + monster.lastaction = 0; + } + restoremonsterstate(); + + int nplayers = gzgeti(); + loopi(nplayers) if (!gzget()) + { + DynamicEntity *d = getclient(i); + assert(d); + gzread(f, data.mutableItems, data.count); + [d setFromSerializedData:data]; + } + + conoutf(@"savegame restored"); + if (demoloading) + startdemo(); + else + stop(); +} + +// demo functions + +int starttime = 0; +int playbacktime = 0; +int ddamage, bdamage; +OFVector3D dorig; + +void +record(OFString *name) +{ + if (m_sp) { + conoutf(@"cannot record singleplayer games"); + return; + } + + int cn = getclientnum(); + if (cn < 0) + return; + + OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name]; + OFIRI *IRI = + [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; + savestate(IRI); + gzputi(cn); + conoutf(@"started recording demo to %@", IRI.string); + demorecording = true; + starttime = lastmillis; + ddamage = bdamage = 0; +} +COMMAND(record, ARG_1STR) + +void +demodamage(int damage, const OFVector3D *o) +{ + ddamage = damage; + dorig = *o; +} + +void +demoblend(int damage) +{ + bdamage = damage; +} + +void +incomingdemodata(uchar *buf, int len, bool extras) +{ + if (!demorecording) + return; + gzputi(lastmillis - starttime); + gzputi(len); + gzwrite(f, buf, len); + gzput(extras); + if (extras) { + gzput(player1.gunselect); + gzput(player1.lastattackgun); + gzputi(player1.lastaction - starttime); + gzputi(player1.gunwait); + gzputi(player1.health); + gzputi(player1.armour); + gzput(player1.armourtype); + loopi(NUMGUNS) gzput(player1.ammo[i]); + gzput(player1.state); + gzputi(bdamage); + bdamage = 0; + gzputi(ddamage); + if (ddamage) { + gzputv(&dorig); + ddamage = 0; + } + // FIXME: add all other client state which is not send through + // the network + } +} + +void +demo(OFString *name) +{ + OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name]; + OFIRI *IRI = + [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; + loadstate(IRI); + demoloading = true; +} +COMMAND(demo, ARG_1STR) + +void +stopreset() +{ + conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime); + stop(); + [players removeAllObjects]; + disconnect(false, false); +} + +VAR(demoplaybackspeed, 10, 100, 1000); +int +scaletime(int t) +{ + return (int)(t * (100.0f / demoplaybackspeed)) + starttime; +} + +void +readdemotime() +{ + if (gzeof(f) || (playbacktime = gzgeti()) == -1) { + stopreset(); + return; + } + playbacktime = scaletime(playbacktime); +} + +void +startdemo() +{ + democlientnum = gzgeti(); + demoplayback = true; + starttime = lastmillis; + conoutf(@"now playing demo"); + setclient(democlientnum, [player1 copy]); + readdemotime(); +} + +VAR(demodelaymsec, 0, 120, 500); + +// spline interpolation +#define catmulrom(z, a, b, c, s, dest) \ + { \ + OFVector3D t1 = b, t2 = c; \ + \ + vsub(t1, z); \ + vmul(t1, 0.5f); \ + vsub(t2, a); \ + vmul(t2, 0.5f); \ + \ + float s2 = s * s; \ + float s3 = s * s2; \ + \ + dest = a; \ + OFVector3D t = b; \ + \ + vmul(dest, 2 * s3 - 3 * s2 + 1); \ + vmul(t, -2 * s3 + 3 * s2); \ + vadd(dest, t); \ + vmul(t1, s3 - 2 * s2 + s); \ + vadd(dest, t1); \ + vmul(t2, s3 - s2); \ + vadd(dest, t2); \ + } + +void +fixwrap(DynamicEntity *a, DynamicEntity *b) +{ + while (b.yaw - a.yaw > 180) + a.yaw += 360; + while (b.yaw - a.yaw < -180) + a.yaw -= 360; +} + +void +demoplaybackstep() +{ + while (demoplayback && lastmillis >= playbacktime) { + int len = gzgeti(); + if (len < 1 || len > MAXTRANS) { + conoutf( + @"error: huge packet during demo play (%d)", len); + stopreset(); + return; + } + uchar buf[MAXTRANS]; + gzread(f, buf, len); + localservertoclient(buf, len); // update game state + + DynamicEntity *target = players[democlientnum]; + assert(target); + + int extras; + // read additional client side state not present in normal + // network stream + if ((extras = gzget())) { + target.gunselect = gzget(); + target.lastattackgun = gzget(); + target.lastaction = scaletime(gzgeti()); + target.gunwait = gzgeti(); + target.health = gzgeti(); + target.armour = gzgeti(); + target.armourtype = gzget(); + loopi(NUMGUNS) target.ammo[i] = gzget(); + target.state = gzget(); + target.lastmove = playbacktime; + if ((bdamage = gzgeti())) + damageblend(bdamage); + if ((ddamage = gzgeti())) { + gzgetv(&dorig); + particle_splash(3, ddamage, 1000, &dorig); + } + // FIXME: set more client state here + } + + // insert latest copy of player into history + if (extras && + (playerhistory.count == 0 || + playerhistory.lastObject.lastupdate != playbacktime)) { + DynamicEntity *d = [target copy]; + d.lastupdate = playbacktime; + + if (playerhistory == nil) + playerhistory = [[OFMutableArray alloc] init]; + + [playerhistory addObject:d]; + + if (playerhistory.count > 20) + [playerhistory removeObjectAtIndex:0]; + } + + readdemotime(); + } + + if (!demoplayback) + return; + + int itime = lastmillis - demodelaymsec; + // find 2 positions in history that surround interpolation time point + size_t count = playerhistory.count; + for (ssize_t i = count - 1; i >= 0; i--) { + if (playerhistory[i].lastupdate < itime) { + DynamicEntity *a = playerhistory[i]; + DynamicEntity *b = a; + + if (i + 1 < playerhistory.count) + b = playerhistory[i + 1]; + + player1 = b; + // interpolate pos & angles + if (a != b) { + DynamicEntity *c = b; + if (i + 2 < playerhistory.count) + c = playerhistory[i + 2]; + DynamicEntity *z = a; + if (i - 1 >= 0) + z = playerhistory[i - 1]; + // if(a==z || b==c) + // printf("* %d\n", lastmillis); + float bf = (itime - a.lastupdate) / + (float)(b.lastupdate - a.lastupdate); + fixwrap(a, player1); + fixwrap(c, player1); + fixwrap(z, player1); + vdist(dist, v, z.o, c.o); + // if teleport or spawn, don't interpolate + if (dist < 16) { + catmulrom( + z.o, a.o, b.o, c.o, bf, player1.o); + OFVector3D vz = OFMakeVector3D( + z.yaw, z.pitch, z.roll); + OFVector3D va = OFMakeVector3D( + a.yaw, a.pitch, a.roll); + OFVector3D vb = OFMakeVector3D( + b.yaw, b.pitch, b.roll); + OFVector3D vc = OFMakeVector3D( + c.yaw, c.pitch, c.roll); + OFVector3D vp1 = + OFMakeVector3D(player1.yaw, + player1.pitch, player1.roll); + catmulrom(vz, va, vb, vc, bf, vp1); + z.yaw = vz.x; + z.pitch = vz.y; + z.roll = vz.z; + a.yaw = va.x; + a.pitch = va.y; + a.roll = va.z; + b.yaw = vb.x; + b.pitch = vb.y; + b.roll = vb.z; + c.yaw = vc.x; + c.pitch = vc.y; + c.roll = vc.z; + player1.yaw = vp1.x; + player1.pitch = vp1.y; + player1.roll = vp1.z; + } + fixplayer1range(); + } + break; + } + } + // if(player1->state!=CS_DEAD) showscores(false); +} + +void +stopn() +{ + if (demoplayback) + stopreset(); + else + stop(); + conoutf(@"demo stopped"); +} +COMMANDN(stop, stopn, ARG_NONE) DELETED src/savegamedemo.mm Index: src/savegamedemo.mm ================================================================== --- src/savegamedemo.mm +++ /dev/null @@ -1,561 +0,0 @@ -// loading and saving of savegames & demos, dumps the spawn state of all -// mapents, the full state of all dynents (monsters + player) - -#include "cube.h" - -#import "DynamicEntity.h" -#import "Entity.h" - -#ifdef OF_BIG_ENDIAN -static const int islittleendian = 0; -#else -static const int islittleendian = 1; -#endif - -static gzFile f = NULL; -bool demorecording = false; -bool demoplayback = false; -bool demoloading = false; -static OFMutableArray *playerhistory; -int democlientnum = 0; - -void startdemo(); - -void -gzput(int i) -{ - gzputc(f, i); -} - -void -gzputi(int i) -{ - gzwrite(f, &i, sizeof(int)); -} - -void -gzputv(OFVector3D &v) -{ - gzwrite(f, &v, sizeof(OFVector3D)); -} - -void -gzcheck(int a, int b) -{ - if (a != b) - fatal(@"savegame file corrupt (short)"); -} - -int -gzget() -{ - char c = gzgetc(f); - return c; -} - -int -gzgeti() -{ - int i; - gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); - return i; -} - -void -gzgetv(OFVector3D &v) -{ - gzcheck(gzread(f, &v, sizeof(OFVector3D)), sizeof(OFVector3D)); -} - -void -stop() -{ - if (f) { - if (demorecording) - gzputi(-1); - gzclose(f); - } - f = NULL; - demorecording = false; - demoplayback = false; - demoloading = false; - [playerhistory removeAllObjects]; -} - -void -stopifrecording() -{ - if (demorecording) - stop(); -} - -void -savestate(OFIRI *IRI) -{ - stop(); - f = gzopen([IRI.fileSystemRepresentation - cStringWithEncoding:OFLocale.encoding], - "wb9"); - if (!f) { - conoutf(@"could not write %@", IRI.string); - return; - } - gzwrite(f, (void *)"CUBESAVE", 8); - gzputc(f, islittleendian); - gzputi(SAVEGAMEVERSION); - OFData *data = [player1 dataBySerializing]; - gzputi(data.count); - char map[_MAXDEFSTR] = { 0 }; - memcpy(map, getclientmap().UTF8String, - min(getclientmap().UTF8StringLength, _MAXDEFSTR - 1)); - gzwrite(f, map, _MAXDEFSTR); - gzputi(gamemode); - gzputi(ents.count); - for (Entity *e in ents) - gzputc(f, e.spawned); - gzwrite(f, data.items, data.count); - OFArray *monsters = getmonsters(); - gzputi(monsters.count); - for (DynamicEntity *monster in monsters) { - data = [monster dataBySerializing]; - gzwrite(f, data.items, data.count); - } - gzputi(players.count); - for (id player in players) { - gzput(player == [OFNull null]); - data = [player dataBySerializing]; - gzwrite(f, data.items, data.count); - } -} - -void -savegame(OFString *name) -{ - if (!m_classicsp) { - conoutf(@"can only save classic sp games"); - return; - } - - OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name]; - OFIRI *IRI = - [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; - savestate(IRI); - stop(); - conoutf(@"wrote %@", IRI.string); -} -COMMAND(savegame, ARG_1STR) - -void -loadstate(OFIRI *IRI) -{ - stop(); - if (multiplayer()) - return; - f = gzopen([IRI.fileSystemRepresentation - cStringWithEncoding:OFLocale.encoding], - "rb9"); - if (!f) { - conoutf(@"could not open %@", IRI.string); - return; - } - - char mapname[_MAXDEFSTR] = { 0 }; - char buf[8]; - gzread(f, buf, 8); - if (strncmp(buf, "CUBESAVE", 8)) - goto out; - if (gzgetc(f) != islittleendian) - goto out; // not supporting save->load accross - // incompatible architectures simpifies things - // a LOT - if (gzgeti() != SAVEGAMEVERSION || - gzgeti() != DynamicEntity.serializedSize) - goto out; - gzread(f, mapname, _MAXDEFSTR); - nextmode = gzgeti(); - // continue below once map has been loaded and client & server - // have updated - changemap(@(mapname)); - return; -out: - conoutf(@"aborting: savegame/demo from a different version of " - @"cube or cpu architecture"); - stop(); -} - -void -loadgame(OFString *name) -{ - OFString *path = [OFString stringWithFormat:@"savegames/%@.csgz", name]; - OFIRI *IRI = - [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; - loadstate(IRI); -} -COMMAND(loadgame, ARG_1STR) - -void -loadgameout() -{ - stop(); - conoutf(@"loadgame incomplete: savegame from a different version of " - @"this map"); -} - -void -loadgamerest() -{ - if (demoplayback || !f) - return; - - if (gzgeti() != ents.count) - return loadgameout(); - - for (Entity *e in ents) { - e.spawned = (gzgetc(f) != 0); - - if (e.type == CARROT && !e.spawned) - trigger(e.attr1, e.attr2, true); - } - - restoreserverstate(ents); - - OFMutableData *data = - [OFMutableData dataWithCapacity:DynamicEntity.serializedSize]; - [data increaseCountBy:DynamicEntity.serializedSize]; - gzread(f, data.mutableItems, data.count); - [player1 setFromSerializedData:data]; - player1.lastaction = lastmillis; - - int nmonsters = gzgeti(); - OFArray *monsters = getmonsters(); - if (nmonsters != monsters.count) - return loadgameout(); - - for (DynamicEntity *monster in monsters) { - gzread(f, data.mutableItems, data.count); - [monster setFromSerializedData:data]; - // lazy, could save id of enemy instead - monster.enemy = player1; - // also lazy, but no real noticable effect on game - monster.lastaction = monster.trigger = lastmillis + 500; - if (monster.state == CS_DEAD) - monster.lastaction = 0; - } - restoremonsterstate(); - - int nplayers = gzgeti(); - loopi(nplayers) if (!gzget()) - { - DynamicEntity *d = getclient(i); - assert(d); - gzread(f, data.mutableItems, data.count); - [d setFromSerializedData:data]; - } - - conoutf(@"savegame restored"); - if (demoloading) - startdemo(); - else - stop(); -} - -// demo functions - -int starttime = 0; -int playbacktime = 0; -int ddamage, bdamage; -OFVector3D dorig; - -void -record(OFString *name) -{ - if (m_sp) { - conoutf(@"cannot record singleplayer games"); - return; - } - - int cn = getclientnum(); - if (cn < 0) - return; - - OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name]; - OFIRI *IRI = - [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; - savestate(IRI); - gzputi(cn); - conoutf(@"started recording demo to %@", IRI.string); - demorecording = true; - starttime = lastmillis; - ddamage = bdamage = 0; -} -COMMAND(record, ARG_1STR) - -void -demodamage(int damage, const OFVector3D *o) -{ - ddamage = damage; - dorig = *o; -} - -void -demoblend(int damage) -{ - bdamage = damage; -} - -void -incomingdemodata(uchar *buf, int len, bool extras) -{ - if (!demorecording) - return; - gzputi(lastmillis - starttime); - gzputi(len); - gzwrite(f, buf, len); - gzput(extras); - if (extras) { - gzput(player1.gunselect); - gzput(player1.lastattackgun); - gzputi(player1.lastaction - starttime); - gzputi(player1.gunwait); - gzputi(player1.health); - gzputi(player1.armour); - gzput(player1.armourtype); - loopi(NUMGUNS) gzput(player1.ammo[i]); - gzput(player1.state); - gzputi(bdamage); - bdamage = 0; - gzputi(ddamage); - if (ddamage) { - gzputv(dorig); - ddamage = 0; - } - // FIXME: add all other client state which is not send through - // the network - } -} - -void -demo(OFString *name) -{ - OFString *path = [OFString stringWithFormat:@"demos/%@.cdgz", name]; - OFIRI *IRI = - [Cube.sharedInstance.userDataIRI IRIByAppendingPathComponent:path]; - loadstate(IRI); - demoloading = true; -} -COMMAND(demo, ARG_1STR) - -void -stopreset() -{ - conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime); - stop(); - [players removeAllObjects]; - disconnect(false, false); -} - -VAR(demoplaybackspeed, 10, 100, 1000); -int -scaletime(int t) -{ - return (int)(t * (100.0f / demoplaybackspeed)) + starttime; -} - -void -readdemotime() -{ - if (gzeof(f) || (playbacktime = gzgeti()) == -1) { - stopreset(); - return; - } - playbacktime = scaletime(playbacktime); -} - -void -startdemo() -{ - democlientnum = gzgeti(); - demoplayback = true; - starttime = lastmillis; - conoutf(@"now playing demo"); - setclient(democlientnum, [player1 copy]); - readdemotime(); -} - -VAR(demodelaymsec, 0, 120, 500); - -// spline interpolation -#define catmulrom(z, a, b, c, s, dest) \ - { \ - OFVector3D t1 = b, t2 = c; \ - \ - vsub(t1, z); \ - vmul(t1, 0.5f); \ - vsub(t2, a); \ - vmul(t2, 0.5f); \ - \ - float s2 = s * s; \ - float s3 = s * s2; \ - \ - dest = a; \ - OFVector3D t = b; \ - \ - vmul(dest, 2 * s3 - 3 * s2 + 1); \ - vmul(t, -2 * s3 + 3 * s2); \ - vadd(dest, t); \ - vmul(t1, s3 - 2 * s2 + s); \ - vadd(dest, t1); \ - vmul(t2, s3 - s2); \ - vadd(dest, t2); \ - } - -void -fixwrap(DynamicEntity *a, DynamicEntity *b) -{ - while (b.yaw - a.yaw > 180) - a.yaw += 360; - while (b.yaw - a.yaw < -180) - a.yaw -= 360; -} - -void -demoplaybackstep() -{ - while (demoplayback && lastmillis >= playbacktime) { - int len = gzgeti(); - if (len < 1 || len > MAXTRANS) { - conoutf( - @"error: huge packet during demo play (%d)", len); - stopreset(); - return; - } - uchar buf[MAXTRANS]; - gzread(f, buf, len); - localservertoclient(buf, len); // update game state - - DynamicEntity *target = players[democlientnum]; - assert(target); - - int extras; - // read additional client side state not present in normal - // network stream - if ((extras = gzget())) { - target.gunselect = gzget(); - target.lastattackgun = gzget(); - target.lastaction = scaletime(gzgeti()); - target.gunwait = gzgeti(); - target.health = gzgeti(); - target.armour = gzgeti(); - target.armourtype = gzget(); - loopi(NUMGUNS) target.ammo[i] = gzget(); - target.state = gzget(); - target.lastmove = playbacktime; - if ((bdamage = gzgeti())) - damageblend(bdamage); - if ((ddamage = gzgeti())) { - gzgetv(dorig); - particle_splash(3, ddamage, 1000, &dorig); - } - // FIXME: set more client state here - } - - // insert latest copy of player into history - if (extras && - (playerhistory.count == 0 || - playerhistory.lastObject.lastupdate != playbacktime)) { - DynamicEntity *d = [target copy]; - d.lastupdate = playbacktime; - - if (playerhistory == nil) - playerhistory = [[OFMutableArray alloc] init]; - - [playerhistory addObject:d]; - - if (playerhistory.count > 20) - [playerhistory removeObjectAtIndex:0]; - } - - readdemotime(); - } - - if (!demoplayback) - return; - - int itime = lastmillis - demodelaymsec; - // find 2 positions in history that surround interpolation time point - size_t count = playerhistory.count; - for (ssize_t i = count - 1; i >= 0; i--) { - if (playerhistory[i].lastupdate < itime) { - DynamicEntity *a = playerhistory[i]; - DynamicEntity *b = a; - - if (i + 1 < playerhistory.count) - b = playerhistory[i + 1]; - - player1 = b; - // interpolate pos & angles - if (a != b) { - DynamicEntity *c = b; - if (i + 2 < playerhistory.count) - c = playerhistory[i + 2]; - DynamicEntity *z = a; - if (i - 1 >= 0) - z = playerhistory[i - 1]; - // if(a==z || b==c) - // printf("* %d\n", lastmillis); - float bf = (itime - a.lastupdate) / - (float)(b.lastupdate - a.lastupdate); - fixwrap(a, player1); - fixwrap(c, player1); - fixwrap(z, player1); - vdist(dist, v, z.o, c.o); - // if teleport or spawn, don't interpolate - if (dist < 16) { - catmulrom( - z.o, a.o, b.o, c.o, bf, player1.o); - OFVector3D vz = OFMakeVector3D( - z.yaw, z.pitch, z.roll); - OFVector3D va = OFMakeVector3D( - a.yaw, a.pitch, a.roll); - OFVector3D vb = OFMakeVector3D( - b.yaw, b.pitch, b.roll); - OFVector3D vc = OFMakeVector3D( - c.yaw, c.pitch, c.roll); - OFVector3D vp1 = - OFMakeVector3D(player1.yaw, - player1.pitch, player1.roll); - catmulrom(vz, va, vb, vc, bf, vp1); - z.yaw = vz.x; - z.pitch = vz.y; - z.roll = vz.z; - a.yaw = va.x; - a.pitch = va.y; - a.roll = va.z; - b.yaw = vb.x; - b.pitch = vb.y; - b.roll = vb.z; - c.yaw = vc.x; - c.pitch = vc.y; - c.roll = vc.z; - player1.yaw = vp1.x; - player1.pitch = vp1.y; - player1.roll = vp1.z; - } - fixplayer1range(); - } - break; - } - } - // if(player1->state!=CS_DEAD) showscores(false); -} - -void -stopn() -{ - if (demoplayback) - stopreset(); - else - stop(); - conoutf(@"demo stopped"); -} -COMMANDN(stop, stopn, ARG_NONE) ADDED src/serverbrowser.m Index: src/serverbrowser.m ================================================================== --- /dev/null +++ src/serverbrowser.m @@ -0,0 +1,295 @@ +// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window +// management + +#include "SDL_thread.h" +#include "cube.h" + +#import "ResolverResult.h" +#import "ResolverThread.h" +#import "ServerInfo.h" + +static OFMutableArray *resolverthreads; +OFMutableArray *resolverqueries; +OFMutableArray *resolverresults; +SDL_sem *resolversem; +static int resolverlimit = 1000; + +void +resolverinit(int threads, int limit) +{ + resolverthreads = [[OFMutableArray alloc] init]; + resolverqueries = [[OFMutableArray alloc] init]; + resolverresults = [[OFMutableArray alloc] init]; + resolverlimit = limit; + resolversem = SDL_CreateSemaphore(0); + + while (threads > 0) { + ResolverThread *rt = [ResolverThread thread]; + rt.name = @"resolverthread"; + [resolverthreads addObject:rt]; + [rt start]; + --threads; + } +} + +void +resolverstop(size_t i, bool restart) +{ + @synchronized(ResolverThread.class) { + ResolverThread *rt = resolverthreads[i]; + [rt stop]; + + if (restart) { + rt = [ResolverThread thread]; + rt.name = @"resolverthread"; + + resolverthreads[i] = rt; + + [rt start]; + } else + [resolverthreads removeObjectAtIndex:i]; + } +} + +void +resolverclear() +{ + @synchronized(ResolverThread.class) { + [resolverqueries removeAllObjects]; + [resolverresults removeAllObjects]; + + while (SDL_SemTryWait(resolversem) == 0) + ; + + for (size_t i = 0; i < resolverthreads.count; i++) + resolverstop(i, true); + } +} + +void +resolverquery(OFString *name) +{ + @synchronized(ResolverThread.class) { + [resolverqueries addObject:name]; + SDL_SemPost(resolversem); + } +} + +bool +resolvercheck(OFString **name, ENetAddress *address) +{ + @synchronized(ResolverThread.class) { + if (resolverresults.count > 0) { + ResolverResult *rr = resolverresults.lastObject; + *name = rr.query; + *address = rr.address; + [resolverresults removeLastObject]; + return true; + } + + for (size_t i = 0; i < resolverthreads.count; i++) { + ResolverThread *rt = resolverthreads[i]; + + if (rt.query) { + if (lastmillis - rt.starttime > resolverlimit) { + resolverstop(i, true); + *name = rt.query; + return true; + } + } + } + } + + return false; +} + +static OFMutableArray *servers; +static ENetSocket pingsock = ENET_SOCKET_NULL; +static int lastinfo = 0; + +OFString * +getservername(int n) +{ + return servers[n].name; +} + +void +addserver(OFString *servername) +{ + for (ServerInfo *si in servers) + if ([si.name isEqual:servername]) + return; + + if (servers == nil) + servers = [[OFMutableArray alloc] init]; + + [servers addObject:[ServerInfo infoWithName:servername]]; +} + +void +pingservers() +{ + ENetBuffer buf; + uchar ping[MAXTRANS]; + uchar *p; + + for (ServerInfo *si in servers) { + if (si.address.host == ENET_HOST_ANY) + continue; + + p = ping; + putint(&p, lastmillis); + buf.data = ping; + buf.dataLength = p - ping; + ENetAddress address = si.address; + enet_socket_send(pingsock, &address, &buf, 1); + } + + lastinfo = lastmillis; +} + +void +checkresolver() +{ + OFString *name = nil; + ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; + while (resolvercheck(&name, &addr)) { + if (addr.host == ENET_HOST_ANY) + continue; + + for (ServerInfo *si in servers) { + if ([name isEqual:si.name]) { + si.address = addr; + addr.host = ENET_HOST_ANY; + break; + } + } + } +} + +void +checkpings() +{ + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + ENetBuffer buf; + ENetAddress addr; + uchar ping[MAXTRANS], *p; + char text[MAXTRANS]; + buf.data = ping; + buf.dataLength = sizeof(ping); + + while (enet_socket_wait(pingsock, &events, 0) >= 0 && events) { + if (enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) + return; + + for (ServerInfo *si in servers) { + if (addr.host == si.address.host) { + p = ping; + si.ping = lastmillis - getint(&p); + si.protocol = getint(&p); + if (si.protocol != PROTOCOL_VERSION) + si.ping = 9998; + si.mode = getint(&p); + si.numplayers = getint(&p); + si.minremain = getint(&p); + sgetstr(); + si.map = @(text); + sgetstr(); + si.sdesc = @(text); + break; + } + } + } +} + +void +refreshservers() +{ + checkresolver(); + checkpings(); + if (lastmillis - lastinfo >= 5000) + pingservers(); + [servers sort]; + + __block int maxmenu = 16; + [servers enumerateObjectsUsingBlock:^( + ServerInfo *si, size_t i, bool *stop) { + if (si.address.host != ENET_HOST_ANY && si.ping != 9999) { + if (si.protocol != PROTOCOL_VERSION) + si.full = [OFString + stringWithFormat: + @"%@ [different cube protocol]", + si.name]; + else + si.full = [OFString + stringWithFormat:@"%d\t%d\t%@, %@: %@ %@", + si.ping, si.numplayers, + si.map.length > 0 ? si.map : @"[unknown]", + modestr(si.mode), si.name, si.sdesc]; + } else + si.full = [OFString + stringWithFormat: + (si.address.host != ENET_HOST_ANY + ? @"%@ [waiting for server response]" + : @"%@ [unknown host]\t"), + si.name]; + + // cut off too long server descriptions + if (si.full.length > 50) + si.full = [si.full substringToIndex:50]; + + menumanual(1, i, si.full); + + if (!--maxmenu) + return; + }]; +} + +void +servermenu() +{ + if (pingsock == ENET_SOCKET_NULL) { + pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); + resolverinit(1, 1000); + } + + resolverclear(); + + for (ServerInfo *si in servers) + resolverquery(si.name); + + refreshservers(); + menuset(1); +} + +void +updatefrommaster() +{ + const int MAXUPD = 32000; + uchar buf[MAXUPD]; + uchar *reply = retrieveservers(buf, MAXUPD); + if (!*reply || strstr((char *)reply, "") || + strstr((char *)reply, "")) + conoutf(@"master server not replying"); + else { + [servers removeAllObjects]; + execute(@((char *)reply), true); + } + servermenu(); +} + +COMMAND(addserver, ARG_1STR) +COMMAND(servermenu, ARG_NONE) +COMMAND(updatefrommaster, ARG_NONE) + +void +writeservercfg() +{ + FILE *f = fopen("servers.cfg", "w"); + if (!f) + return; + fprintf(f, "// servers connected to are added here automatically\n\n"); + for (ServerInfo *si in servers.reversedArray) + fprintf(f, "addserver %s\n", si.name.UTF8String); + fclose(f); +} DELETED src/serverbrowser.mm Index: src/serverbrowser.mm ================================================================== --- src/serverbrowser.mm +++ /dev/null @@ -1,295 +0,0 @@ -// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window -// management - -#include "SDL_thread.h" -#include "cube.h" - -#import "ResolverResult.h" -#import "ResolverThread.h" -#import "ServerInfo.h" - -static OFMutableArray *resolverthreads; -OFMutableArray *resolverqueries; -OFMutableArray *resolverresults; -SDL_sem *resolversem; -static int resolverlimit = 1000; - -void -resolverinit(int threads, int limit) -{ - resolverthreads = [[OFMutableArray alloc] init]; - resolverqueries = [[OFMutableArray alloc] init]; - resolverresults = [[OFMutableArray alloc] init]; - resolverlimit = limit; - resolversem = SDL_CreateSemaphore(0); - - while (threads > 0) { - ResolverThread *rt = [ResolverThread thread]; - rt.name = @"resolverthread"; - [resolverthreads addObject:rt]; - [rt start]; - --threads; - } -} - -void -resolverstop(size_t i, bool restart) -{ - @synchronized(ResolverThread.class) { - ResolverThread *rt = resolverthreads[i]; - [rt stop]; - - if (restart) { - rt = [ResolverThread thread]; - rt.name = @"resolverthread"; - - resolverthreads[i] = rt; - - [rt start]; - } else - [resolverthreads removeObjectAtIndex:i]; - } -} - -void -resolverclear() -{ - @synchronized(ResolverThread.class) { - [resolverqueries removeAllObjects]; - [resolverresults removeAllObjects]; - - while (SDL_SemTryWait(resolversem) == 0) - ; - - for (size_t i = 0; i < resolverthreads.count; i++) - resolverstop(i, true); - } -} - -void -resolverquery(OFString *name) -{ - @synchronized(ResolverThread.class) { - [resolverqueries addObject:name]; - SDL_SemPost(resolversem); - } -} - -bool -resolvercheck(OFString **name, ENetAddress *address) -{ - @synchronized(ResolverThread.class) { - if (resolverresults.count > 0) { - ResolverResult *rr = resolverresults.lastObject; - *name = rr.query; - *address = rr.address; - [resolverresults removeLastObject]; - return true; - } - - for (size_t i = 0; i < resolverthreads.count; i++) { - ResolverThread *rt = resolverthreads[i]; - - if (rt.query) { - if (lastmillis - rt.starttime > resolverlimit) { - resolverstop(i, true); - *name = rt.query; - return true; - } - } - } - } - - return false; -} - -static OFMutableArray *servers; -static ENetSocket pingsock = ENET_SOCKET_NULL; -static int lastinfo = 0; - -OFString * -getservername(int n) -{ - return servers[n].name; -} - -void -addserver(OFString *servername) -{ - for (ServerInfo *si in servers) - if ([si.name isEqual:servername]) - return; - - if (servers == nil) - servers = [[OFMutableArray alloc] init]; - - [servers addObject:[ServerInfo infoWithName:servername]]; -} - -void -pingservers() -{ - ENetBuffer buf; - uchar ping[MAXTRANS]; - uchar *p; - - for (ServerInfo *si in servers) { - if (si.address.host == ENET_HOST_ANY) - continue; - - p = ping; - putint(&p, lastmillis); - buf.data = ping; - buf.dataLength = p - ping; - ENetAddress address = si.address; - enet_socket_send(pingsock, &address, &buf, 1); - } - - lastinfo = lastmillis; -} - -void -checkresolver() -{ - OFString *name = nil; - ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; - while (resolvercheck(&name, &addr)) { - if (addr.host == ENET_HOST_ANY) - continue; - - for (ServerInfo *si in servers) { - if ([name isEqual:si.name]) { - si.address = addr; - addr.host = ENET_HOST_ANY; - break; - } - } - } -} - -void -checkpings() -{ - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - ENetBuffer buf; - ENetAddress addr; - uchar ping[MAXTRANS], *p; - char text[MAXTRANS]; - buf.data = ping; - buf.dataLength = sizeof(ping); - - while (enet_socket_wait(pingsock, &events, 0) >= 0 && events) { - if (enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) - return; - - for (ServerInfo *si in servers) { - if (addr.host == si.address.host) { - p = ping; - si.ping = lastmillis - getint(&p); - si.protocol = getint(&p); - if (si.protocol != PROTOCOL_VERSION) - si.ping = 9998; - si.mode = getint(&p); - si.numplayers = getint(&p); - si.minremain = getint(&p); - sgetstr(); - si.map = @(text); - sgetstr(); - si.sdesc = @(text); - break; - } - } - } -} - -extern "C" void -refreshservers() -{ - checkresolver(); - checkpings(); - if (lastmillis - lastinfo >= 5000) - pingservers(); - [servers sort]; - - __block int maxmenu = 16; - [servers enumerateObjectsUsingBlock:^( - ServerInfo *si, size_t i, bool *stop) { - if (si.address.host != ENET_HOST_ANY && si.ping != 9999) { - if (si.protocol != PROTOCOL_VERSION) - si.full = [OFString - stringWithFormat: - @"%@ [different cube protocol]", - si.name]; - else - si.full = [OFString - stringWithFormat:@"%d\t%d\t%@, %@: %@ %@", - si.ping, si.numplayers, - si.map.length > 0 ? si.map : @"[unknown]", - modestr(si.mode), si.name, si.sdesc]; - } else - si.full = [OFString - stringWithFormat: - (si.address.host != ENET_HOST_ANY - ? @"%@ [waiting for server response]" - : @"%@ [unknown host]\t"), - si.name]; - - // cut off too long server descriptions - if (si.full.length > 50) - si.full = [si.full substringToIndex:50]; - - menumanual(1, i, si.full); - - if (!--maxmenu) - return; - }]; -} - -void -servermenu() -{ - if (pingsock == ENET_SOCKET_NULL) { - pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); - resolverinit(1, 1000); - } - - resolverclear(); - - for (ServerInfo *si in servers) - resolverquery(si.name); - - refreshservers(); - menuset(1); -} - -void -updatefrommaster() -{ - const int MAXUPD = 32000; - uchar buf[MAXUPD]; - uchar *reply = retrieveservers(buf, MAXUPD); - if (!*reply || strstr((char *)reply, "") || - strstr((char *)reply, "")) - conoutf(@"master server not replying"); - else { - [servers removeAllObjects]; - execute(@((char *)reply), true); - } - servermenu(); -} - -COMMAND(addserver, ARG_1STR) -COMMAND(servermenu, ARG_NONE) -COMMAND(updatefrommaster, ARG_NONE) - -void -writeservercfg() -{ - FILE *f = fopen("servers.cfg", "w"); - if (!f) - return; - fprintf(f, "// servers connected to are added here automatically\n\n"); - for (ServerInfo *si in servers.reversedArray) - fprintf(f, "addserver %s\n", si.name.UTF8String); - fclose(f); -} ADDED src/world.m Index: src/world.m ================================================================== --- /dev/null +++ src/world.m @@ -0,0 +1,540 @@ +// world.cpp: core map management stuff + +#include "cube.h" + +#import "DynamicEntity.h" +#import "Entity.h" + +extern OFString *entnames[]; // lookup from map entities above to strings + +struct sqr *world = NULL; +int sfactor, ssize, cubicsize, mipsize; + +struct header hdr; + +// set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes +// according to type +void +settag(int tag, int type) +{ + int maxx = 0, maxy = 0, minx = ssize, miny = ssize; + loop(x, ssize) loop(y, ssize) + { + struct sqr *s = S(x, y); + if (s->tag) { + if (tag) { + if (tag == s->tag) + s->type = SPACE; + else + continue; + } else { + s->type = type ? SOLID : SPACE; + } + if (x > maxx) + maxx = x; + if (y > maxy) + maxy = y; + if (x < minx) + minx = x; + if (y < miny) + miny = y; + } + } + struct block b = { minx, miny, maxx - minx + 1, maxy - miny + 1 }; + if (maxx) + remip(&b, 0); // remip minimal area of changed geometry +} + +void +resettagareas() +{ + settag(0, 0); +} // reset for editing or map saving +void +settagareas() +{ + settag(0, 1); + + [ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) { + if (ents[i].type == CARROT) + setspawn(i, true); + }]; +} // set for playing + +void +trigger(int tag, int type, bool savegame) +{ + if (!tag) + return; + + settag(tag, type); + + if (!savegame && type != 3) + playsound(S_RUMBLE, NULL); + + OFString *aliasname = + [OFString stringWithFormat:@"level_trigger_%d", tag]; + + if (identexists(aliasname)) + execute(aliasname, true); + + if (type == 2) + endsp(false); +} +COMMAND(trigger, ARG_2INT) + +// main geometric mipmapping routine, recursively rebuild mipmaps within block +// b. tries to produce cube out of 4 lower level mips as well as possible, sets +// defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this mip +// level indistinguishable from its constituent cubes (saves considerable +// rendering time if this is possible). + +void +remip(const struct block *b, int level) +{ + if (level >= SMALLEST_FACTOR) + return; + + int lighterr = getvar(@"lighterror") * 3; + struct sqr *w = wmip[level]; + struct sqr *v = wmip[level + 1]; + int ws = ssize >> level; + int vs = ssize >> (level + 1); + struct block s = *b; + if (s.x & 1) { + s.x--; + s.xs++; + } + if (s.y & 1) { + s.y--; + s.ys++; + } + s.xs = (s.xs + 1) & ~1; + s.ys = (s.ys + 1) & ~1; + for (int x = s.x; x < s.x + s.xs; x += 2) + for (int y = s.y; y < s.y + s.ys; y += 2) { + struct sqr *o[4]; + o[0] = SWS(w, x, y, ws); // the 4 constituent cubes + o[1] = SWS(w, x + 1, y, ws); + o[2] = SWS(w, x + 1, y + 1, ws); + o[3] = SWS(w, x, y + 1, ws); + // the target cube in the higher mip level + struct sqr *r = SWS(v, x / 2, y / 2, vs); + *r = *o[0]; + uchar nums[MAXTYPE]; + loopi(MAXTYPE) nums[i] = 0; + loopj(4) nums[o[j]->type]++; + // cube contains both solid and space, treated + // specially in the renderer + r->type = SEMISOLID; + loopk(MAXTYPE) if (nums[k] == 4) r->type = k; + if (!SOLID(r)) { + int floor = 127, ceil = -128, num = 0; + loopi(4) if (!SOLID(o[i])) + { + num++; + int fh = o[i]->floor; + int ch = o[i]->ceil; + if (r->type == SEMISOLID) { + if (o[i]->type == FHF) + // crap hack, needed + // for rendering large + // mips next to hfs + fh -= o[i]->vdelta / 4 + + 2; + if (o[i]->type == CHF) + // FIXME: needs to + // somehow take into + // account middle + // vertices on higher + // mips + ch += o[i]->vdelta / 4 + + 2; + } + if (fh < floor) + // take lowest floor and + // highest ceil, so we never + // have to see missing + // lower/upper from the side + floor = fh; + if (ch > ceil) + ceil = ch; + } + r->floor = floor; + r->ceil = ceil; + } + if (r->type == CORNER) + // special case: don't ever split even if + // textures etc are different + goto mip; + r->defer = 1; + if (SOLID(r)) { + loopi(3) + { + if (o[i]->wtex != o[3]->wtex) + // on an all solid cube, only + // thing that needs to be equal + // for a perfect mip is the + // wall texture + goto c; + } + } else { + loopi(3) + { + // perfect mip even if light is not + // exactly equal + if (o[i]->type != o[3]->type || + o[i]->floor != o[3]->floor || + o[i]->ceil != o[3]->ceil || + o[i]->ftex != o[3]->ftex || + o[i]->ctex != o[3]->ctex || + abs(o[i + 1]->r - o[0]->r) > + lighterr || + abs(o[i + 1]->g - o[0]->g) > + lighterr || + abs(o[i + 1]->b - o[0]->b) > + lighterr || + o[i]->utex != o[3]->utex || + o[i]->wtex != o[3]->wtex) + goto c; + } + + // can make a perfect mip out of a hf if slopes + // lie on one line + if (r->type == CHF || r->type == FHF) { + if (o[0]->vdelta - o[1]->vdelta != + o[1]->vdelta - + SWS(w, x + 2, y, ws) + ->vdelta || + o[0]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 2, y + 2, ws) + ->vdelta || + o[0]->vdelta - o[3]->vdelta != + o[3]->vdelta - + SWS(w, x, y + 2, ws) + ->vdelta || + o[3]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 2, y + 1, ws) + ->vdelta || + o[1]->vdelta - o[2]->vdelta != + o[2]->vdelta - + SWS(w, x + 1, y + 2, ws) + ->vdelta) + goto c; + } + } + { + // if any of the constituents is not perfect, + // then this one isn't either + loopi(4) if (o[i]->defer) goto c; + } + mip: + r->defer = 0; + c:; + } + s.x /= 2; + s.y /= 2; + s.xs /= 2; + s.ys /= 2; + remip(&s, level + 1); +} + +void +remipmore(const struct block *b, int level) +{ + struct block bb = *b; + + if (bb.x > 1) + bb.x--; + if (bb.y > 1) + bb.y--; + if (bb.xs < ssize - 3) + bb.xs++; + if (bb.ys < ssize - 3) + bb.ys++; + + remip(&bb, level); +} + +int +closestent() // used for delent and edit mode ent display +{ + if (noteditmode()) + return -1; + + __block int best; + __block float bdist = 99999; + [ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) { + if (e.type == NOTUSED) + return; + + OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); + vdist(dist, t, player1.o, v); + if (dist < bdist) { + best = i; + bdist = dist; + } + }]; + + return (bdist == 99999 ? -1 : best); +} + +void +entproperty(int prop, int amount) +{ + int e = closestent(); + if (e < 0) + return; + switch (prop) { + case 0: + ents[e].attr1 += amount; + break; + case 1: + ents[e].attr2 += amount; + break; + case 2: + ents[e].attr3 += amount; + break; + case 3: + ents[e].attr4 += amount; + break; + } +} + +void +delent() +{ + int e = closestent(); + if (e < 0) { + conoutf(@"no more entities"); + return; + } + int t = ents[e].type; + conoutf(@"%@ entity deleted", entnames[t]); + ents[e].type = NOTUSED; + addmsg(1, 10, SV_EDITENT, e, NOTUSED, 0, 0, 0, 0, 0, 0, 0); + if (t == LIGHT) + calclight(); +} + +int +findtype(OFString *what) +{ + loopi(MAXENTTYPES) if ([what isEqual:entnames[i]]) return i; + conoutf(@"unknown entity type \"%@\"", what); + return NOTUSED; +} + +Entity * +newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4) +{ + int type = findtype(what); + + PersistentEntity *e = [PersistentEntity entity]; + e.x = x; + e.y = y; + e.z = z; + e.attr1 = v1; + e.type = type; + e.attr2 = v2; + e.attr3 = v3; + e.attr4 = v4; + + switch (type) { + case LIGHT: + if (v1 > 32) + v1 = 32; + if (!v1) + e.attr1 = 16; + if (!v2 && !v3 && !v4) + e.attr2 = 255; + break; + + case MAPMODEL: + e.attr4 = e.attr3; + e.attr3 = e.attr2; + case MONSTER: + case TELEDEST: + e.attr2 = (uchar)e.attr1; + case PLAYERSTART: + e.attr1 = (int)player1.yaw; + break; + } + addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1, + e.attr2, e.attr3, e.attr4); + + [ents addObject:e]; // unsafe! + + if (type == LIGHT) + calclight(); + + return e; +} + +void +clearents(OFString *name) +{ + int type = findtype(name); + + if (noteditmode() || multiplayer()) + return; + + for (Entity *e in ents) + if (e.type == type) + e.type = NOTUSED; + + if (type == LIGHT) + calclight(); +} +COMMAND(clearents, ARG_1STR) + +static uchar +scalecomp(uchar c, int intens) +{ + int n = c * intens / 100; + if (n > 255) + n = 255; + return n; +} + +void +scalelights(int f, int intens) +{ + for (Entity *e in ents) { + if (e.type != LIGHT) + continue; + + e.attr1 = e.attr1 * f / 100; + if (e.attr1 < 2) + e.attr1 = 2; + if (e.attr1 > 32) + e.attr1 = 32; + + if (intens) { + e.attr2 = scalecomp(e.attr2, intens); + e.attr3 = scalecomp(e.attr3, intens); + e.attr4 = scalecomp(e.attr4, intens); + } + } + + calclight(); +} +COMMAND(scalelights, ARG_2INT) + +int +findentity(int type, int index) +{ + for (int i = index; i < ents.count; i++) + if (ents[i].type == type) + return i; + loopj(index) if (ents[j].type == type) return j; + return -1; +} + +struct sqr *wmip[LARGEST_FACTOR * 2]; + +void +setupworld(int factor) +{ + ssize = 1 << (sfactor = factor); + cubicsize = ssize * ssize; + mipsize = cubicsize * 134 / 100; + struct sqr *w = world = + OFAllocZeroedMemory(mipsize, sizeof(struct sqr)); + loopi(LARGEST_FACTOR * 2) + { + wmip[i] = w; + w += cubicsize >> (i * 2); + } +} + +// main empty world creation routine, if passed factor -1 will enlarge old +// world by 1 +void +empty_world(int factor, bool force) +{ + if (!force && noteditmode()) + return; + cleardlights(); + pruneundos(0); + struct sqr *oldworld = world; + bool copy = false; + if (oldworld && factor < 0) { + factor = sfactor + 1; + copy = true; + } + if (factor < SMALLEST_FACTOR) + factor = SMALLEST_FACTOR; + if (factor > LARGEST_FACTOR) + factor = LARGEST_FACTOR; + setupworld(factor); + + loop(x, ssize) loop(y, ssize) + { + struct sqr *s = S(x, y); + s->r = s->g = s->b = 150; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->wtex = s->utex = DEFAULT_WALL; + s->type = SOLID; + s->floor = 0; + s->ceil = 16; + s->vdelta = 0; + s->defer = 0; + } + + strncpy(hdr.head, "CUBE", 4); + hdr.version = MAPVERSION; + hdr.headersize = sizeof(struct header); + hdr.sfactor = sfactor; + + if (copy) { + loop(x, ssize / 2) loop(y, ssize / 2) + { + *S(x + ssize / 4, y + ssize / 4) = + *SWS(oldworld, x, y, ssize / 2); + } + + for (Entity *e in ents) { + e.x += ssize / 4; + e.y += ssize / 4; + } + } else { + char buffer[128] = "Untitled Map by Unknown"; + memcpy(hdr.maptitle, buffer, 128); + hdr.waterlevel = -100000; + loopi(15) hdr.reserved[i] = 0; + loopk(3) loopi(256) hdr.texlists[k][i] = i; + [ents removeAllObjects]; + struct block b = { 8, 8, ssize - 16, ssize - 16 }; + edittypexy(SPACE, &b); + } + + calclight(); + startmap(@"base/unnamed"); + if (oldworld) { + OFFreeMemory(oldworld); + toggleedit(); + execute(@"fullbright 1", true); + } +} + +void +mapenlarge() +{ + empty_world(-1, false); +} + +void +newmap(int i) +{ + empty_world(i, false); +} + +COMMAND(mapenlarge, ARG_NONE) +COMMAND(newmap, ARG_1INT) +COMMANDN(recalc, calclight, ARG_NONE) +COMMAND(delent, ARG_NONE) +COMMAND(entproperty, ARG_2INT) DELETED src/world.mm Index: src/world.mm ================================================================== --- src/world.mm +++ /dev/null @@ -1,539 +0,0 @@ -// world.cpp: core map management stuff - -#include "cube.h" - -#import "DynamicEntity.h" -#import "Entity.h" - -extern OFString *entnames[]; // lookup from map entities above to strings - -sqr *world = NULL; -int sfactor, ssize, cubicsize, mipsize; - -header hdr; - -// set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes -// according to type -void -settag(int tag, int type) -{ - int maxx = 0, maxy = 0, minx = ssize, miny = ssize; - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if (s->tag) { - if (tag) { - if (tag == s->tag) - s->type = SPACE; - else - continue; - } else { - s->type = type ? SOLID : SPACE; - } - if (x > maxx) - maxx = x; - if (y > maxy) - maxy = y; - if (x < minx) - minx = x; - if (y < miny) - miny = y; - } - } - block b = { minx, miny, maxx - minx + 1, maxy - miny + 1 }; - if (maxx) - remip(&b, 0); // remip minimal area of changed geometry -} - -void -resettagareas() -{ - settag(0, 0); -} // reset for editing or map saving -void -settagareas() -{ - settag(0, 1); - - [ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) { - if (ents[i].type == CARROT) - setspawn(i, true); - }]; -} // set for playing - -void -trigger(int tag, int type, bool savegame) -{ - if (!tag) - return; - - settag(tag, type); - - if (!savegame && type != 3) - playsound(S_RUMBLE, NULL); - - OFString *aliasname = - [OFString stringWithFormat:@"level_trigger_%d", tag]; - - if (identexists(aliasname)) - execute(aliasname, true); - - if (type == 2) - endsp(false); -} -COMMAND(trigger, ARG_2INT) - -// main geometric mipmapping routine, recursively rebuild mipmaps within block -// b. tries to produce cube out of 4 lower level mips as well as possible, sets -// defer to 0 if mipped cube is a perfect mip, i.e. can be rendered at this mip -// level indistinguishable from its constituent cubes (saves considerable -// rendering time if this is possible). - -void -remip(const block *b, int level) -{ - if (level >= SMALLEST_FACTOR) - return; - - int lighterr = getvar(@"lighterror") * 3; - sqr *w = wmip[level]; - sqr *v = wmip[level + 1]; - int ws = ssize >> level; - int vs = ssize >> (level + 1); - block s = *b; - if (s.x & 1) { - s.x--; - s.xs++; - } - if (s.y & 1) { - s.y--; - s.ys++; - } - s.xs = (s.xs + 1) & ~1; - s.ys = (s.ys + 1) & ~1; - for (int x = s.x; x < s.x + s.xs; x += 2) - for (int y = s.y; y < s.y + s.ys; y += 2) { - sqr *o[4]; - o[0] = SWS(w, x, y, ws); // the 4 constituent cubes - o[1] = SWS(w, x + 1, y, ws); - o[2] = SWS(w, x + 1, y + 1, ws); - o[3] = SWS(w, x, y + 1, ws); - // the target cube in the higher mip level - sqr *r = SWS(v, x / 2, y / 2, vs); - *r = *o[0]; - uchar nums[MAXTYPE]; - loopi(MAXTYPE) nums[i] = 0; - loopj(4) nums[o[j]->type]++; - // cube contains both solid and space, treated - // specially in the renderer - r->type = SEMISOLID; - loopk(MAXTYPE) if (nums[k] == 4) r->type = k; - if (!SOLID(r)) { - int floor = 127, ceil = -128, num = 0; - loopi(4) if (!SOLID(o[i])) - { - num++; - int fh = o[i]->floor; - int ch = o[i]->ceil; - if (r->type == SEMISOLID) { - if (o[i]->type == FHF) - // crap hack, needed - // for rendering large - // mips next to hfs - fh -= o[i]->vdelta / 4 + - 2; - if (o[i]->type == CHF) - // FIXME: needs to - // somehow take into - // account middle - // vertices on higher - // mips - ch += o[i]->vdelta / 4 + - 2; - } - if (fh < floor) - // take lowest floor and - // highest ceil, so we never - // have to see missing - // lower/upper from the side - floor = fh; - if (ch > ceil) - ceil = ch; - } - r->floor = floor; - r->ceil = ceil; - } - if (r->type == CORNER) - // special case: don't ever split even if - // textures etc are different - goto mip; - r->defer = 1; - if (SOLID(r)) { - loopi(3) - { - if (o[i]->wtex != o[3]->wtex) - // on an all solid cube, only - // thing that needs to be equal - // for a perfect mip is the - // wall texture - goto c; - } - } else { - loopi(3) - { - // perfect mip even if light is not - // exactly equal - if (o[i]->type != o[3]->type || - o[i]->floor != o[3]->floor || - o[i]->ceil != o[3]->ceil || - o[i]->ftex != o[3]->ftex || - o[i]->ctex != o[3]->ctex || - abs(o[i + 1]->r - o[0]->r) > - lighterr || - abs(o[i + 1]->g - o[0]->g) > - lighterr || - abs(o[i + 1]->b - o[0]->b) > - lighterr || - o[i]->utex != o[3]->utex || - o[i]->wtex != o[3]->wtex) - goto c; - } - - // can make a perfect mip out of a hf if slopes - // lie on one line - if (r->type == CHF || r->type == FHF) { - if (o[0]->vdelta - o[1]->vdelta != - o[1]->vdelta - - SWS(w, x + 2, y, ws) - ->vdelta || - o[0]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 2, y + 2, ws) - ->vdelta || - o[0]->vdelta - o[3]->vdelta != - o[3]->vdelta - - SWS(w, x, y + 2, ws) - ->vdelta || - o[3]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 2, y + 1, ws) - ->vdelta || - o[1]->vdelta - o[2]->vdelta != - o[2]->vdelta - - SWS(w, x + 1, y + 2, ws) - ->vdelta) - goto c; - } - } - { - // if any of the constituents is not perfect, - // then this one isn't either - loopi(4) if (o[i]->defer) goto c; - } - mip: - r->defer = 0; - c:; - } - s.x /= 2; - s.y /= 2; - s.xs /= 2; - s.ys /= 2; - remip(&s, level + 1); -} - -void -remipmore(const block *b, int level) -{ - block bb = *b; - - if (bb.x > 1) - bb.x--; - if (bb.y > 1) - bb.y--; - if (bb.xs < ssize - 3) - bb.xs++; - if (bb.ys < ssize - 3) - bb.ys++; - - remip(&bb, level); -} - -int -closestent() // used for delent and edit mode ent display -{ - if (noteditmode()) - return -1; - - __block int best; - __block float bdist = 99999; - [ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) { - if (e.type == NOTUSED) - return; - - OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); - vdist(dist, t, player1.o, v); - if (dist < bdist) { - best = i; - bdist = dist; - } - }]; - - return (bdist == 99999 ? -1 : best); -} - -void -entproperty(int prop, int amount) -{ - int e = closestent(); - if (e < 0) - return; - switch (prop) { - case 0: - ents[e].attr1 += amount; - break; - case 1: - ents[e].attr2 += amount; - break; - case 2: - ents[e].attr3 += amount; - break; - case 3: - ents[e].attr4 += amount; - break; - } -} - -void -delent() -{ - int e = closestent(); - if (e < 0) { - conoutf(@"no more entities"); - return; - } - int t = ents[e].type; - conoutf(@"%@ entity deleted", entnames[t]); - ents[e].type = NOTUSED; - addmsg(1, 10, SV_EDITENT, e, NOTUSED, 0, 0, 0, 0, 0, 0, 0); - if (t == LIGHT) - calclight(); -} - -int -findtype(OFString *what) -{ - loopi(MAXENTTYPES) if ([what isEqual:entnames[i]]) return i; - conoutf(@"unknown entity type \"%@\"", what); - return NOTUSED; -} - -Entity * -newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4) -{ - int type = findtype(what); - - PersistentEntity *e = [PersistentEntity entity]; - e.x = x; - e.y = y; - e.z = z; - e.attr1 = v1; - e.type = type; - e.attr2 = v2; - e.attr3 = v3; - e.attr4 = v4; - - switch (type) { - case LIGHT: - if (v1 > 32) - v1 = 32; - if (!v1) - e.attr1 = 16; - if (!v2 && !v3 && !v4) - e.attr2 = 255; - break; - - case MAPMODEL: - e.attr4 = e.attr3; - e.attr3 = e.attr2; - case MONSTER: - case TELEDEST: - e.attr2 = (uchar)e.attr1; - case PLAYERSTART: - e.attr1 = (int)player1.yaw; - break; - } - addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1, - e.attr2, e.attr3, e.attr4); - - [ents addObject:e]; // unsafe! - - if (type == LIGHT) - calclight(); - - return e; -} - -void -clearents(OFString *name) -{ - int type = findtype(name); - - if (noteditmode() || multiplayer()) - return; - - for (Entity *e in ents) - if (e.type == type) - e.type = NOTUSED; - - if (type == LIGHT) - calclight(); -} -COMMAND(clearents, ARG_1STR) - -static uchar -scalecomp(uchar c, int intens) -{ - int n = c * intens / 100; - if (n > 255) - n = 255; - return n; -} - -void -scalelights(int f, int intens) -{ - for (Entity *e in ents) { - if (e.type != LIGHT) - continue; - - e.attr1 = e.attr1 * f / 100; - if (e.attr1 < 2) - e.attr1 = 2; - if (e.attr1 > 32) - e.attr1 = 32; - - if (intens) { - e.attr2 = scalecomp(e.attr2, intens); - e.attr3 = scalecomp(e.attr3, intens); - e.attr4 = scalecomp(e.attr4, intens); - } - } - - calclight(); -} -COMMAND(scalelights, ARG_2INT) - -int -findentity(int type, int index) -{ - for (int i = index; i < ents.count; i++) - if (ents[i].type == type) - return i; - loopj(index) if (ents[j].type == type) return j; - return -1; -} - -sqr *wmip[LARGEST_FACTOR * 2]; - -void -setupworld(int factor) -{ - ssize = 1 << (sfactor = factor); - cubicsize = ssize * ssize; - mipsize = cubicsize * 134 / 100; - sqr *w = world = (sqr *)OFAllocZeroedMemory(mipsize, sizeof(sqr)); - loopi(LARGEST_FACTOR * 2) - { - wmip[i] = w; - w += cubicsize >> (i * 2); - } -} - -// main empty world creation routine, if passed factor -1 will enlarge old -// world by 1 -void -empty_world(int factor, bool force) -{ - if (!force && noteditmode()) - return; - cleardlights(); - pruneundos(0); - sqr *oldworld = world; - bool copy = false; - if (oldworld && factor < 0) { - factor = sfactor + 1; - copy = true; - } - if (factor < SMALLEST_FACTOR) - factor = SMALLEST_FACTOR; - if (factor > LARGEST_FACTOR) - factor = LARGEST_FACTOR; - setupworld(factor); - - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - s->r = s->g = s->b = 150; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->wtex = s->utex = DEFAULT_WALL; - s->type = SOLID; - s->floor = 0; - s->ceil = 16; - s->vdelta = 0; - s->defer = 0; - } - - strncpy(hdr.head, "CUBE", 4); - hdr.version = MAPVERSION; - hdr.headersize = sizeof(header); - hdr.sfactor = sfactor; - - if (copy) { - loop(x, ssize / 2) loop(y, ssize / 2) - { - *S(x + ssize / 4, y + ssize / 4) = - *SWS(oldworld, x, y, ssize / 2); - } - - for (Entity *e in ents) { - e.x += ssize / 4; - e.y += ssize / 4; - } - } else { - char buffer[128] = "Untitled Map by Unknown"; - memcpy(hdr.maptitle, buffer, 128); - hdr.waterlevel = -100000; - loopi(15) hdr.reserved[i] = 0; - loopk(3) loopi(256) hdr.texlists[k][i] = i; - [ents removeAllObjects]; - block b = { 8, 8, ssize - 16, ssize - 16 }; - edittypexy(SPACE, &b); - } - - calclight(); - startmap(@"base/unnamed"); - if (oldworld) { - OFFreeMemory(oldworld); - toggleedit(); - execute(@"fullbright 1", true); - } -} - -void -mapenlarge() -{ - empty_world(-1, false); -} - -void -newmap(int i) -{ - empty_world(i, false); -} - -COMMAND(mapenlarge, ARG_NONE) -COMMAND(newmap, ARG_1INT) -COMMANDN(recalc, calclight, ARG_NONE) -COMMAND(delent, ARG_NONE) -COMMAND(entproperty, ARG_2INT) ADDED src/worldio.m Index: src/worldio.m ================================================================== --- /dev/null +++ src/worldio.m @@ -0,0 +1,403 @@ +// worldio.cpp: loading & saving of maps and savegames + +#include "cube.h" + +#import "Entity.h" + +struct persistent_entity { + short x, y, z; // cube aligned position + short attr1; + uchar type; // type is one of the above + uchar attr2, attr3, attr4; +}; + +void +backup(OFString *name, OFString *backupname) +{ + [OFFileManager.defaultManager removeItemAtPath:backupname]; + [OFFileManager.defaultManager moveItemAtPath:name toPath:backupname]; +} + +static OFString *cgzname, *bakname, *pcfname, *mcfname; + +static void +setnames(OFString *name) +{ + OFCharacterSet *cs = + [OFCharacterSet characterSetWithCharactersInString:@"/\\"]; + OFRange range = [name rangeOfCharacterFromSet:cs]; + OFString *pakname, *mapname; + + if (range.location != OFNotFound) { + pakname = [name substringToIndex:range.location]; + mapname = [name substringFromIndex:range.location + 1]; + } else { + pakname = @"base"; + mapname = name; + } + + cgzname = [[OFString alloc] + initWithFormat:@"packages/%@/%@.cgz", pakname, mapname]; + bakname = [[OFString alloc] initWithFormat:@"packages/%@/%@_%d.BAK", + pakname, mapname, lastmillis]; + pcfname = [[OFString alloc] + initWithFormat:@"packages/%@/package.cfg", pakname]; + mcfname = [[OFString alloc] + initWithFormat:@"packages/%@/%@.cfg", pakname, mapname]; +} + +// the optimize routines below are here to reduce the detrimental effects of +// messy mapping by setting certain properties (vdeltas and textures) to +// neighbouring values wherever there is no visible difference. This allows the +// mipmapper to generate more efficient mips. the reason it is done on save is +// to reduce the amount spend in the mipmapper (as that is done in realtime). + +inline bool +nhf(struct sqr *s) +{ + return s->type != FHF && s->type != CHF; +} + +void +voptimize() // reset vdeltas on non-hf cubes +{ + loop(x, ssize) loop(y, ssize) + { + struct sqr *s = S(x, y); + if (x && y) { + if (nhf(s) && nhf(S(x - 1, y)) && + nhf(S(x - 1, y - 1)) && nhf(S(x, y - 1))) + s->vdelta = 0; + } else + s->vdelta = 0; + } +} + +static void +topt(struct sqr *s, bool *wf, bool *uf, int *wt, int *ut) +{ + struct sqr *o[4]; + o[0] = SWS(s, 0, -1, ssize); + o[1] = SWS(s, 0, 1, ssize); + o[2] = SWS(s, 1, 0, ssize); + o[3] = SWS(s, -1, 0, ssize); + *wf = true; + *uf = true; + if (SOLID(s)) { + loopi(4) if (!SOLID(o[i])) + { + *wf = false; + *wt = s->wtex; + *ut = s->utex; + return; + } + } else { + loopi(4) if (!SOLID(o[i])) + { + if (o[i]->floor < s->floor) { + *wt = s->wtex; + *wf = false; + } + if (o[i]->ceil > s->ceil) { + *ut = s->utex; + *uf = false; + } + } + } +} + +void +toptimize() // FIXME: only does 2x2, make atleast for 4x4 also +{ + bool wf[4], uf[4]; + struct sqr *s[4]; + for (int x = 2; x < ssize - 4; x += 2) { + for (int y = 2; y < ssize - 4; y += 2) { + s[0] = S(x, y); + int wt = s[0]->wtex, ut = s[0]->utex; + topt(s[0], &wf[0], &uf[0], &wt, &ut); + topt(s[1] = SWS(s[0], 0, 1, ssize), &wf[1], &uf[1], &wt, + &ut); + topt(s[2] = SWS(s[0], 1, 1, ssize), &wf[2], &uf[2], &wt, + &ut); + topt(s[3] = SWS(s[0], 1, 0, ssize), &wf[3], &uf[3], &wt, + &ut); + loopi(4) + { + if (wf[i]) + s[i]->wtex = wt; + if (uf[i]) + s[i]->utex = ut; + } + } + } +} + +// these two are used by getmap/sendmap.. transfers compressed maps directly + +void +writemap(OFString *mname, int msize, uchar *mdata) +{ + setnames(mname); + backup(cgzname, bakname); + + FILE *f = fopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb"); + if (!f) { + conoutf(@"could not write map to %@", cgzname); + return; + } + fwrite(mdata, 1, msize, f); + fclose(f); + conoutf(@"wrote map %@ as file %@", mname, cgzname); +} + +OFData * +readmap(OFString *mname) +{ + setnames(mname); + return [OFData dataWithContentsOfFile:mname]; +} + +// save map as .cgz file. uses 2 layers of compression: first does simple +// run-length encoding and leaves out data for certain kinds of cubes, then zlib +// removes the last bits of redundancy. Both passes contribute greatly to the +// miniscule map sizes. + +void +save_world(OFString *mname) +{ + resettagareas(); // wouldn't be able to reproduce tagged areas + // otherwise + voptimize(); + toptimize(); + if (mname.length == 0) + mname = getclientmap(); + setnames(mname); + backup(cgzname, bakname); + gzFile f = + gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb9"); + if (!f) { + conoutf(@"could not write map to %@", cgzname); + return; + } + hdr.version = MAPVERSION; + hdr.numents = 0; + for (Entity *e in ents) + if (e.type != NOTUSED) + hdr.numents++; + struct header tmp = hdr; + endianswap(&tmp.version, sizeof(int), 4); + endianswap(&tmp.waterlevel, sizeof(int), 16); + gzwrite(f, &tmp, sizeof(struct header)); + for (Entity *e in ents) { + if (e.type != NOTUSED) { + struct persistent_entity tmp = { e.x, e.y, e.z, e.attr1, + e.type, e.attr2, e.attr3, e.attr4 }; + endianswap(&tmp, sizeof(short), 4); + gzwrite(f, &tmp, sizeof(struct persistent_entity)); + } + } + struct sqr *t = NULL; + int sc = 0; +#define spurge \ + while (sc) { \ + gzputc(f, 255); \ + if (sc > 255) { \ + gzputc(f, 255); \ + sc -= 255; \ + } else { \ + gzputc(f, sc); \ + sc = 0; \ + } \ + } + loopk(cubicsize) + { + struct sqr *s = &world[k]; +#define c(f) (s->f == t->f) + // 4 types of blocks, to compress a bit: + // 255 (2): same as previous block + count + // 254 (3): same as previous, except light // deprecated + // SOLID (5) + // anything else (9) + + if (SOLID(s)) { + if (t && c(type) && c(wtex) && c(vdelta)) { + sc++; + } else { + spurge; + gzputc(f, s->type); + gzputc(f, s->wtex); + gzputc(f, s->vdelta); + } + } else { + if (t && c(type) && c(floor) && c(ceil) && c(ctex) && + c(ftex) && c(utex) && c(wtex) && c(vdelta) && + c(tag)) { + sc++; + } else { + spurge; + gzputc(f, s->type); + gzputc(f, s->floor); + gzputc(f, s->ceil); + gzputc(f, s->wtex); + gzputc(f, s->ftex); + gzputc(f, s->ctex); + gzputc(f, s->vdelta); + gzputc(f, s->utex); + gzputc(f, s->tag); + } + } + t = s; + } + spurge; + gzclose(f); + conoutf(@"wrote map file %@", cgzname); + settagareas(); +} +COMMANDN(savemap, save_world, ARG_1STR) + +void +load_world(OFString *mname) // still supports all map formats that have existed + // since the earliest cube betas! +{ + stopifrecording(); + cleardlights(); + pruneundos(0); + setnames(mname); + gzFile f = + gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "rb9"); + if (!f) { + conoutf(@"could not read map %@", cgzname); + return; + } + gzread(f, &hdr, sizeof(struct header) - sizeof(int) * 16); + endianswap(&hdr.version, sizeof(int), 4); + if (strncmp(hdr.head, "CUBE", 4) != 0) + fatal(@"while reading map: header malformatted"); + if (hdr.version > MAPVERSION) + fatal(@"this map requires a newer version of cube"); + if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR) + fatal(@"illegal map size"); + if (hdr.version >= 4) { + gzread(f, &hdr.waterlevel, sizeof(int) * 16); + endianswap(&hdr.waterlevel, sizeof(int), 16); + } else { + hdr.waterlevel = -100000; + } + [ents removeAllObjects]; + loopi(hdr.numents) + { + struct persistent_entity tmp; + gzread(f, &tmp, sizeof(struct persistent_entity)); + endianswap(&tmp, sizeof(short), 4); + + Entity *e = [Entity entity]; + e.x = tmp.x; + e.y = tmp.y; + e.z = tmp.z; + e.attr1 = tmp.attr1; + e.type = tmp.type; + e.attr2 = tmp.attr2; + e.attr3 = tmp.attr3; + e.attr4 = tmp.attr4; + [ents addObject:e]; + + if (e.type == LIGHT) { + if (!e.attr2) + e.attr2 = 255; // needed for MAPVERSION<=2 + if (e.attr1 > 32) + e.attr1 = 32; // 12_03 and below + } + } + free(world); + setupworld(hdr.sfactor); + char texuse[256]; + loopi(256) texuse[i] = 0; + struct sqr *t = NULL; + loopk(cubicsize) + { + struct sqr *s = &world[k]; + int type = gzgetc(f); + switch (type) { + case 255: { + int n = gzgetc(f); + for (int i = 0; i < n; i++, k++) + memcpy(&world[k], t, sizeof(struct sqr)); + k--; + break; + } + case 254: // only in MAPVERSION<=2 + { + memcpy(s, t, sizeof(struct sqr)); + s->r = s->g = s->b = gzgetc(f); + gzgetc(f); + break; + } + case SOLID: { + s->type = SOLID; + s->wtex = gzgetc(f); + s->vdelta = gzgetc(f); + if (hdr.version <= 2) { + gzgetc(f); + gzgetc(f); + } + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->utex = s->wtex; + s->tag = 0; + s->floor = 0; + s->ceil = 16; + break; + } + default: { + if (type < 0 || type >= MAXTYPE) + fatal(@"while reading map: type out of range: " + @"%d @ %d", + type, k); + s->type = type; + s->floor = gzgetc(f); + s->ceil = gzgetc(f); + if (s->floor >= s->ceil) + s->floor = s->ceil - 1; // for pre 12_13 + s->wtex = gzgetc(f); + s->ftex = gzgetc(f); + s->ctex = gzgetc(f); + if (hdr.version <= 2) { + gzgetc(f); + gzgetc(f); + } + s->vdelta = gzgetc(f); + s->utex = (hdr.version >= 2) ? gzgetc(f) : s->wtex; + s->tag = (hdr.version >= 5) ? gzgetc(f) : 0; + s->type = type; + } + } + s->defer = 0; + t = s; + texuse[s->wtex] = 1; + if (!SOLID(s)) + texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; + } + gzclose(f); + calclight(); + settagareas(); + int xs, ys; + loopi(256) if (texuse[i]) lookuptexture(i, &xs, &ys); + conoutf(@"read map %@ (%d milliseconds)", cgzname, + SDL_GetTicks() - lastmillis); + conoutf(@"%s", hdr.maptitle); + startmap(mname); + loopl(256) + { + // can this be done smarter? + OFString *aliasname = + [OFString stringWithFormat:@"level_trigger_%d", l]; + if (identexists(aliasname)) + alias(aliasname, @""); + } + OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI; + execfile([gameDataIRI + IRIByAppendingPathComponent:@"data/default_map_settings.cfg"]); + execfile([gameDataIRI IRIByAppendingPathComponent:pcfname]); + execfile([gameDataIRI IRIByAppendingPathComponent:mcfname]); +} DELETED src/worldio.mm Index: src/worldio.mm ================================================================== --- src/worldio.mm +++ /dev/null @@ -1,403 +0,0 @@ -// worldio.cpp: loading & saving of maps and savegames - -#include "cube.h" - -#import "Entity.h" - -struct persistent_entity { - short x, y, z; // cube aligned position - short attr1; - uchar type; // type is one of the above - uchar attr2, attr3, attr4; -}; - -void -backup(OFString *name, OFString *backupname) -{ - [OFFileManager.defaultManager removeItemAtPath:backupname]; - [OFFileManager.defaultManager moveItemAtPath:name toPath:backupname]; -} - -static OFString *cgzname, *bakname, *pcfname, *mcfname; - -static void -setnames(OFString *name) -{ - OFCharacterSet *cs = - [OFCharacterSet characterSetWithCharactersInString:@"/\\"]; - OFRange range = [name rangeOfCharacterFromSet:cs]; - OFString *pakname, *mapname; - - if (range.location != OFNotFound) { - pakname = [name substringToIndex:range.location]; - mapname = [name substringFromIndex:range.location + 1]; - } else { - pakname = @"base"; - mapname = name; - } - - cgzname = [[OFString alloc] - initWithFormat:@"packages/%@/%@.cgz", pakname, mapname]; - bakname = [[OFString alloc] initWithFormat:@"packages/%@/%@_%d.BAK", - pakname, mapname, lastmillis]; - pcfname = [[OFString alloc] - initWithFormat:@"packages/%@/package.cfg", pakname]; - mcfname = [[OFString alloc] - initWithFormat:@"packages/%@/%@.cfg", pakname, mapname]; -} - -// the optimize routines below are here to reduce the detrimental effects of -// messy mapping by setting certain properties (vdeltas and textures) to -// neighbouring values wherever there is no visible difference. This allows the -// mipmapper to generate more efficient mips. the reason it is done on save is -// to reduce the amount spend in the mipmapper (as that is done in realtime). - -inline bool -nhf(sqr *s) -{ - return s->type != FHF && s->type != CHF; -} - -void -voptimize() // reset vdeltas on non-hf cubes -{ - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if (x && y) { - if (nhf(s) && nhf(S(x - 1, y)) && - nhf(S(x - 1, y - 1)) && nhf(S(x, y - 1))) - s->vdelta = 0; - } else - s->vdelta = 0; - } -} - -void -topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut) -{ - sqr *o[4]; - o[0] = SWS(s, 0, -1, ssize); - o[1] = SWS(s, 0, 1, ssize); - o[2] = SWS(s, 1, 0, ssize); - o[3] = SWS(s, -1, 0, ssize); - wf = true; - uf = true; - if (SOLID(s)) { - loopi(4) if (!SOLID(o[i])) - { - wf = false; - wt = s->wtex; - ut = s->utex; - return; - } - } else { - loopi(4) if (!SOLID(o[i])) - { - if (o[i]->floor < s->floor) { - wt = s->wtex; - wf = false; - } - if (o[i]->ceil > s->ceil) { - ut = s->utex; - uf = false; - } - } - } -} - -void -toptimize() // FIXME: only does 2x2, make atleast for 4x4 also -{ - bool wf[4], uf[4]; - sqr *s[4]; - for (int x = 2; x < ssize - 4; x += 2) { - for (int y = 2; y < ssize - 4; y += 2) { - s[0] = S(x, y); - int wt = s[0]->wtex, ut = s[0]->utex; - topt(s[0], wf[0], uf[0], wt, ut); - topt(s[1] = SWS(s[0], 0, 1, ssize), wf[1], uf[1], wt, - ut); - topt(s[2] = SWS(s[0], 1, 1, ssize), wf[2], uf[2], wt, - ut); - topt(s[3] = SWS(s[0], 1, 0, ssize), wf[3], uf[3], wt, - ut); - loopi(4) - { - if (wf[i]) - s[i]->wtex = wt; - if (uf[i]) - s[i]->utex = ut; - } - } - } -} - -// these two are used by getmap/sendmap.. transfers compressed maps directly - -void -writemap(OFString *mname, int msize, uchar *mdata) -{ - setnames(mname); - backup(cgzname, bakname); - - FILE *f = fopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb"); - if (!f) { - conoutf(@"could not write map to %@", cgzname); - return; - } - fwrite(mdata, 1, msize, f); - fclose(f); - conoutf(@"wrote map %@ as file %@", mname, cgzname); -} - -OFData * -readmap(OFString *mname) -{ - setnames(mname); - return [OFData dataWithContentsOfFile:mname]; -} - -// save map as .cgz file. uses 2 layers of compression: first does simple -// run-length encoding and leaves out data for certain kinds of cubes, then zlib -// removes the last bits of redundancy. Both passes contribute greatly to the -// miniscule map sizes. - -void -save_world(OFString *mname) -{ - resettagareas(); // wouldn't be able to reproduce tagged areas - // otherwise - voptimize(); - toptimize(); - if (mname.length == 0) - mname = getclientmap(); - setnames(mname); - backup(cgzname, bakname); - gzFile f = - gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "wb9"); - if (!f) { - conoutf(@"could not write map to %@", cgzname); - return; - } - hdr.version = MAPVERSION; - hdr.numents = 0; - for (Entity *e in ents) - if (e.type != NOTUSED) - hdr.numents++; - header tmp = hdr; - endianswap(&tmp.version, sizeof(int), 4); - endianswap(&tmp.waterlevel, sizeof(int), 16); - gzwrite(f, &tmp, sizeof(header)); - for (Entity *e in ents) { - if (e.type != NOTUSED) { - struct persistent_entity tmp = { e.x, e.y, e.z, e.attr1, - e.type, e.attr2, e.attr3, e.attr4 }; - endianswap(&tmp, sizeof(short), 4); - gzwrite(f, &tmp, sizeof(persistent_entity)); - } - } - sqr *t = NULL; - int sc = 0; -#define spurge \ - while (sc) { \ - gzputc(f, 255); \ - if (sc > 255) { \ - gzputc(f, 255); \ - sc -= 255; \ - } else { \ - gzputc(f, sc); \ - sc = 0; \ - } \ - } - loopk(cubicsize) - { - sqr *s = &world[k]; -#define c(f) (s->f == t->f) - // 4 types of blocks, to compress a bit: - // 255 (2): same as previous block + count - // 254 (3): same as previous, except light // deprecated - // SOLID (5) - // anything else (9) - - if (SOLID(s)) { - if (t && c(type) && c(wtex) && c(vdelta)) { - sc++; - } else { - spurge; - gzputc(f, s->type); - gzputc(f, s->wtex); - gzputc(f, s->vdelta); - } - } else { - if (t && c(type) && c(floor) && c(ceil) && c(ctex) && - c(ftex) && c(utex) && c(wtex) && c(vdelta) && - c(tag)) { - sc++; - } else { - spurge; - gzputc(f, s->type); - gzputc(f, s->floor); - gzputc(f, s->ceil); - gzputc(f, s->wtex); - gzputc(f, s->ftex); - gzputc(f, s->ctex); - gzputc(f, s->vdelta); - gzputc(f, s->utex); - gzputc(f, s->tag); - } - } - t = s; - } - spurge; - gzclose(f); - conoutf(@"wrote map file %@", cgzname); - settagareas(); -} -COMMANDN(savemap, save_world, ARG_1STR) - -void -load_world(OFString *mname) // still supports all map formats that have existed - // since the earliest cube betas! -{ - stopifrecording(); - cleardlights(); - pruneundos(0); - setnames(mname); - gzFile f = - gzopen([cgzname cStringWithEncoding:OFLocale.encoding], "rb9"); - if (!f) { - conoutf(@"could not read map %@", cgzname); - return; - } - gzread(f, &hdr, sizeof(header) - sizeof(int) * 16); - endianswap(&hdr.version, sizeof(int), 4); - if (strncmp(hdr.head, "CUBE", 4) != 0) - fatal(@"while reading map: header malformatted"); - if (hdr.version > MAPVERSION) - fatal(@"this map requires a newer version of cube"); - if (sfactor < SMALLEST_FACTOR || sfactor > LARGEST_FACTOR) - fatal(@"illegal map size"); - if (hdr.version >= 4) { - gzread(f, &hdr.waterlevel, sizeof(int) * 16); - endianswap(&hdr.waterlevel, sizeof(int), 16); - } else { - hdr.waterlevel = -100000; - } - [ents removeAllObjects]; - loopi(hdr.numents) - { - struct persistent_entity tmp; - gzread(f, &tmp, sizeof(persistent_entity)); - endianswap(&tmp, sizeof(short), 4); - - Entity *e = [Entity entity]; - e.x = tmp.x; - e.y = tmp.y; - e.z = tmp.z; - e.attr1 = tmp.attr1; - e.type = tmp.type; - e.attr2 = tmp.attr2; - e.attr3 = tmp.attr3; - e.attr4 = tmp.attr4; - [ents addObject:e]; - - if (e.type == LIGHT) { - if (!e.attr2) - e.attr2 = 255; // needed for MAPVERSION<=2 - if (e.attr1 > 32) - e.attr1 = 32; // 12_03 and below - } - } - free(world); - setupworld(hdr.sfactor); - char texuse[256]; - loopi(256) texuse[i] = 0; - sqr *t = NULL; - loopk(cubicsize) - { - sqr *s = &world[k]; - int type = gzgetc(f); - switch (type) { - case 255: { - int n = gzgetc(f); - for (int i = 0; i < n; i++, k++) - memcpy(&world[k], t, sizeof(sqr)); - k--; - break; - } - case 254: // only in MAPVERSION<=2 - { - memcpy(s, t, sizeof(sqr)); - s->r = s->g = s->b = gzgetc(f); - gzgetc(f); - break; - } - case SOLID: { - s->type = SOLID; - s->wtex = gzgetc(f); - s->vdelta = gzgetc(f); - if (hdr.version <= 2) { - gzgetc(f); - gzgetc(f); - } - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->utex = s->wtex; - s->tag = 0; - s->floor = 0; - s->ceil = 16; - break; - } - default: { - if (type < 0 || type >= MAXTYPE) - fatal(@"while reading map: type out of range: " - @"%d @ %d", - type, k); - s->type = type; - s->floor = gzgetc(f); - s->ceil = gzgetc(f); - if (s->floor >= s->ceil) - s->floor = s->ceil - 1; // for pre 12_13 - s->wtex = gzgetc(f); - s->ftex = gzgetc(f); - s->ctex = gzgetc(f); - if (hdr.version <= 2) { - gzgetc(f); - gzgetc(f); - } - s->vdelta = gzgetc(f); - s->utex = (hdr.version >= 2) ? gzgetc(f) : s->wtex; - s->tag = (hdr.version >= 5) ? gzgetc(f) : 0; - s->type = type; - } - } - s->defer = 0; - t = s; - texuse[s->wtex] = 1; - if (!SOLID(s)) - texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; - } - gzclose(f); - calclight(); - settagareas(); - int xs, ys; - loopi(256) if (texuse) lookuptexture(i, &xs, &ys); - conoutf(@"read map %@ (%d milliseconds)", cgzname, - SDL_GetTicks() - lastmillis); - conoutf(@"%s", hdr.maptitle); - startmap(mname); - loopl(256) - { - // can this be done smarter? - OFString *aliasname = - [OFString stringWithFormat:@"level_trigger_%d", l]; - if (identexists(aliasname)) - alias(aliasname, @""); - } - OFIRI *gameDataIRI = Cube.sharedInstance.gameDataIRI; - execfile([gameDataIRI - IRIByAppendingPathComponent:@"data/default_map_settings.cfg"]); - execfile([gameDataIRI IRIByAppendingPathComponent:pcfname]); - execfile([gameDataIRI IRIByAppendingPathComponent:mcfname]); -} ADDED src/worldlight.m Index: src/worldlight.m ================================================================== --- /dev/null +++ src/worldlight.m @@ -0,0 +1,272 @@ +// worldlight.cpp + +#include "cube.h" + +#import "DynamicEntity.h" +#import "Entity.h" +#import "PersistentEntity.h" + +extern bool hasoverbright; + +VAR(lightscale, 1, 4, 100); + +// done in realtime, needs to be fast +void +lightray(float bx, float by, PersistentEntity *light) +{ + float lx = light.x + (rnd(21) - 10) * 0.1f; + float ly = light.y + (rnd(21) - 10) * 0.1f; + float dx = bx - lx; + float dy = by - ly; + float dist = (float)sqrt(dx * dx + dy * dy); + if (dist < 1.0f) + return; + int reach = light.attr1; + int steps = (int)(reach * reach * 1.6f / + dist); // can change this for speedup/quality? + const int PRECBITS = 12; + const float PRECF = 4096.0f; + int x = (int)(lx * PRECF); + int y = (int)(ly * PRECF); + int l = light.attr2 << PRECBITS; + int stepx = (int)(dx / (float)steps * PRECF); + int stepy = (int)(dy / (float)steps * PRECF); + int stepl = + fast_f2nat(l / (float)steps); // incorrect: light will fade quicker + // if near edge of the world + + if (hasoverbright) { + l /= lightscale; + stepl /= lightscale; + + if (light.attr3 || + light.attr4) // coloured light version, special case because + // most lights are white + { + int dimness = rnd( + (255 - + (light.attr2 + light.attr3 + light.attr4) / 3) / + 16 + + 1); + x += stepx * dimness; + y += stepy * dimness; + + if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) + return; + + int g = light.attr3 << PRECBITS; + int stepg = fast_f2nat(g / (float)steps); + int b = light.attr4 << PRECBITS; + int stepb = fast_f2nat(b / (float)steps); + g /= lightscale; + stepg /= lightscale; + b /= lightscale; + stepb /= lightscale; + loopi(steps) + { + struct sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int tl = (l >> PRECBITS) + s->r; + s->r = tl > 255 ? 255 : tl; + tl = (g >> PRECBITS) + s->g; + s->g = tl > 255 ? 255 : tl; + tl = (b >> PRECBITS) + s->b; + s->b = tl > 255 ? 255 : tl; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + g -= stepg; + b -= stepb; + stepl -= 25; + stepg -= 25; + stepb -= 25; + } + } else // white light, special optimized version + { + int dimness = rnd((255 - light.attr2) / 16 + 1); + x += stepx * dimness; + y += stepy * dimness; + + if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) + return; + + loopi(steps) + { + struct sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int tl = (l >> PRECBITS) + s->r; + s->r = s->g = s->b = tl > 255 ? 255 : tl; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + stepl -= 25; + } + } + } else // the old (white) light code, here for the few people with old + // video cards that don't support overbright + { + loopi(steps) + { + struct sqr *s = S(x >> PRECBITS, y >> PRECBITS); + int light = l >> PRECBITS; + if (light > s->r) + s->r = s->g = s->b = (uchar)light; + if (SOLID(s)) + return; + x += stepx; + y += stepy; + l -= stepl; + } + } +} + +void +calclightsource(PersistentEntity *l) +{ + int reach = l.attr1; + int sx = l.x - reach; + int ex = l.x + reach; + int sy = l.y - reach; + int ey = l.y + reach; + + rndreset(); + + const float s = 0.8f; + + for (float sx2 = (float)sx; sx2 <= ex; sx2 += s * 2) { + lightray(sx2, (float)sy, l); + lightray(sx2, (float)ey, l); + } + for (float sy2 = sy + s; sy2 <= ey - s; sy2 += s * 2) { + lightray((float)sx, sy2, l); + lightray((float)ex, sy2, l); + } + + rndtime(); +} + +// median filter, smooths out random noise in light and makes it more mipable +void +postlightarea(const struct block *a) +{ + loop(x, a->xs) loop(y, a->ys) // assumes area not on edge of world + { + struct sqr *s = S(x + a->x, y + a->y); +#define median(m) \ + s->m = \ + (s->m * 2 + SW(s, 1, 0)->m * 2 + SW(s, 0, 1)->m * 2 + \ + SW(s, -1, 0)->m * 2 + SW(s, 0, -1)->m * 2 + SW(s, 1, 1)->m + \ + SW(s, 1, -1)->m + SW(s, -1, 1)->m + SW(s, -1, -1)->m) / \ + 14; // median is 4/2/1 instead + median(r); + median(g); + median(b); + } + + remip(a, 0); +} + +void +calclight() +{ + loop(x, ssize) loop(y, ssize) + { + struct sqr *s = S(x, y); + s->r = s->g = s->b = 10; + } + + for (Entity *e in ents) + if (e.type == LIGHT) + calclightsource(e); + + struct block b = { 1, 1, ssize - 2, ssize - 2 }; + postlightarea(&b); + setvar(@"fullbright", 0); +} + +VARP(dynlight, 0, 16, 32); + +static OFMutableData *dlights; + +void +cleardlights() +{ + while (dlights.count > 0) { + struct block *backup = *(struct block **)[dlights lastItem]; + [dlights removeLastItem]; + blockpaste(backup); + OFFreeMemory(backup); + } +} + +void +dodynlight(const OFVector3D *vold, const OFVector3D *v, int reach, int strength, + DynamicEntity *owner) +{ + if (!reach) + reach = dynlight; + if (owner.monsterstate) + reach = reach / 2; + if (!reach) + return; + if (v->x < 0 || v->y < 0 || v->x > ssize || v->y > ssize) + return; + + int creach = reach + 16; // dependant on lightray random offsets! + struct block b = { (int)v->x - creach, (int)v->y - creach, + creach * 2 + 1, creach * 2 + 1 }; + + if (b.x < 1) + b.x = 1; + if (b.y < 1) + b.y = 1; + if (b.xs + b.x > ssize - 2) + b.xs = ssize - 2 - b.x; + if (b.ys + b.y > ssize - 2) + b.ys = ssize - 2 - b.y; + + if (dlights == nil) + dlights = [[OFMutableData alloc] + initWithItemSize:sizeof(struct block *)]; + + // backup area before rendering in dynlight + struct block *copy = blockcopy(&b); + [dlights addItem:©]; + + PersistentEntity *l = [Entity entity]; + l.x = v->x; + l.y = v->y; + l.z = v->z; + l.attr1 = reach; + l.type = LIGHT; + l.attr2 = strength; + calclightsource(l); + postlightarea(&b); +} + +// utility functions also used by editing code + +struct block * +blockcopy(const struct block *s) +{ + struct block *b = OFAllocZeroedMemory( + 1, sizeof(struct block) + s->xs * s->ys * sizeof(struct sqr)); + *b = *s; + struct sqr *q = (struct sqr *)(b + 1); + for (int x = s->x; x < s->xs + s->x; x++) + for (int y = s->y; y < s->ys + s->y; y++) + *q++ = *S(x, y); + return b; +} + +void +blockpaste(const struct block *b) +{ + struct sqr *q = (struct sqr *)(b + 1); + for (int x = b->x; x < b->xs + b->x; x++) + for (int y = b->y; y < b->ys + b->y; y++) + *S(x, y) = *q++; + remipmore(b, 0); +} DELETED src/worldlight.mm Index: src/worldlight.mm ================================================================== --- src/worldlight.mm +++ /dev/null @@ -1,272 +0,0 @@ -// worldlight.cpp - -#include "cube.h" - -#import "DynamicEntity.h" -#import "Entity.h" -#import "PersistentEntity.h" - -extern bool hasoverbright; - -VAR(lightscale, 1, 4, 100); - -// done in realtime, needs to be fast -void -lightray(float bx, float by, PersistentEntity *light) -{ - float lx = light.x + (rnd(21) - 10) * 0.1f; - float ly = light.y + (rnd(21) - 10) * 0.1f; - float dx = bx - lx; - float dy = by - ly; - float dist = (float)sqrt(dx * dx + dy * dy); - if (dist < 1.0f) - return; - int reach = light.attr1; - int steps = (int)(reach * reach * 1.6f / - dist); // can change this for speedup/quality? - const int PRECBITS = 12; - const float PRECF = 4096.0f; - int x = (int)(lx * PRECF); - int y = (int)(ly * PRECF); - int l = light.attr2 << PRECBITS; - int stepx = (int)(dx / (float)steps * PRECF); - int stepy = (int)(dy / (float)steps * PRECF); - int stepl = - fast_f2nat(l / (float)steps); // incorrect: light will fade quicker - // if near edge of the world - - if (hasoverbright) { - l /= lightscale; - stepl /= lightscale; - - if (light.attr3 || - light.attr4) // coloured light version, special case because - // most lights are white - { - int dimness = rnd( - (255 - - (light.attr2 + light.attr3 + light.attr4) / 3) / - 16 + - 1); - x += stepx * dimness; - y += stepy * dimness; - - if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) - return; - - int g = light.attr3 << PRECBITS; - int stepg = fast_f2nat(g / (float)steps); - int b = light.attr4 << PRECBITS; - int stepb = fast_f2nat(b / (float)steps); - g /= lightscale; - stepg /= lightscale; - b /= lightscale; - stepb /= lightscale; - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int tl = (l >> PRECBITS) + s->r; - s->r = tl > 255 ? 255 : tl; - tl = (g >> PRECBITS) + s->g; - s->g = tl > 255 ? 255 : tl; - tl = (b >> PRECBITS) + s->b; - s->b = tl > 255 ? 255 : tl; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - g -= stepg; - b -= stepb; - stepl -= 25; - stepg -= 25; - stepb -= 25; - } - } else // white light, special optimized version - { - int dimness = rnd((255 - light.attr2) / 16 + 1); - x += stepx * dimness; - y += stepy * dimness; - - if (OUTBORD(x >> PRECBITS, y >> PRECBITS)) - return; - - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int tl = (l >> PRECBITS) + s->r; - s->r = s->g = s->b = tl > 255 ? 255 : tl; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - stepl -= 25; - } - } - } else // the old (white) light code, here for the few people with old - // video cards that don't support overbright - { - loopi(steps) - { - sqr *s = S(x >> PRECBITS, y >> PRECBITS); - int light = l >> PRECBITS; - if (light > s->r) - s->r = s->g = s->b = (uchar)light; - if (SOLID(s)) - return; - x += stepx; - y += stepy; - l -= stepl; - } - } -} - -void -calclightsource(PersistentEntity *l) -{ - int reach = l.attr1; - int sx = l.x - reach; - int ex = l.x + reach; - int sy = l.y - reach; - int ey = l.y + reach; - - rndreset(); - - const float s = 0.8f; - - for (float sx2 = (float)sx; sx2 <= ex; sx2 += s * 2) { - lightray(sx2, (float)sy, l); - lightray(sx2, (float)ey, l); - } - for (float sy2 = sy + s; sy2 <= ey - s; sy2 += s * 2) { - lightray((float)sx, sy2, l); - lightray((float)ex, sy2, l); - } - - rndtime(); -} - -void -postlightarea(block &a) // median filter, smooths out random noise in light and - // makes it more mipable -{ - loop(x, a.xs) loop(y, a.ys) // assumes area not on edge of world - { - sqr *s = S(x + a.x, y + a.y); -#define median(m) \ - s->m = \ - (s->m * 2 + SW(s, 1, 0)->m * 2 + SW(s, 0, 1)->m * 2 + \ - SW(s, -1, 0)->m * 2 + SW(s, 0, -1)->m * 2 + SW(s, 1, 1)->m + \ - SW(s, 1, -1)->m + SW(s, -1, 1)->m + SW(s, -1, -1)->m) / \ - 14; // median is 4/2/1 instead - median(r); - median(g); - median(b); - } - - remip(&a, 0); -} - -void -calclight() -{ - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - s->r = s->g = s->b = 10; - } - - for (Entity *e in ents) - if (e.type == LIGHT) - calclightsource(e); - - block b = { 1, 1, ssize - 2, ssize - 2 }; - postlightarea(b); - setvar(@"fullbright", 0); -} - -VARP(dynlight, 0, 16, 32); - -static OFMutableData *dlights; - -void -cleardlights() -{ - while (dlights.count > 0) { - block *backup = *(block **)[dlights lastItem]; - [dlights removeLastItem]; - blockpaste(backup); - OFFreeMemory(backup); - } -} - -void -dodynlight(const OFVector3D *vold, const OFVector3D *v, int reach, int strength, - DynamicEntity *owner) -{ - if (!reach) - reach = dynlight; - if (owner.monsterstate) - reach = reach / 2; - if (!reach) - return; - if (v->x < 0 || v->y < 0 || v->x > ssize || v->y > ssize) - return; - - int creach = reach + 16; // dependant on lightray random offsets! - block b = { (int)v->x - creach, (int)v->y - creach, creach * 2 + 1, - creach * 2 + 1 }; - - if (b.x < 1) - b.x = 1; - if (b.y < 1) - b.y = 1; - if (b.xs + b.x > ssize - 2) - b.xs = ssize - 2 - b.x; - if (b.ys + b.y > ssize - 2) - b.ys = ssize - 2 - b.y; - - if (dlights == nil) - dlights = - [[OFMutableData alloc] initWithItemSize:sizeof(block *)]; - - // backup area before rendering in dynlight - block *copy = blockcopy(&b); - [dlights addItem:©]; - - PersistentEntity *l = [Entity entity]; - l.x = v->x; - l.y = v->y; - l.z = v->z; - l.attr1 = reach; - l.type = LIGHT; - l.attr2 = strength; - calclightsource(l); - postlightarea(b); -} - -// utility functions also used by editing code - -block * -blockcopy(const block *s) -{ - block *b = (block *)OFAllocZeroedMemory( - 1, sizeof(block) + s->xs * s->ys * sizeof(sqr)); - *b = *s; - sqr *q = (sqr *)(b + 1); - for (int x = s->x; x < s->xs + s->x; x++) - for (int y = s->y; y < s->ys + s->y; y++) - *q++ = *S(x, y); - return b; -} - -void -blockpaste(const block *b) -{ - sqr *q = (sqr *)(b + 1); - for (int x = b->x; x < b->xs + b->x; x++) - for (int y = b->y; y < b->ys + b->y; y++) - *S(x, y) = *q++; - remipmore(b, 0); -} ADDED src/worldocull.m Index: src/worldocull.m ================================================================== --- /dev/null +++ src/worldocull.m @@ -0,0 +1,201 @@ +// worldocull.cpp: occlusion map and occlusion test + +#include "cube.h" + +#import "DynamicEntity.h" + +#define NUMRAYS 512 + +float rdist[NUMRAYS]; +bool ocull = true; +float odist = 256; + +void +toggleocull() +{ + ocull = !ocull; +} +COMMAND(toggleocull, ARG_NONE) + +// constructs occlusion map: cast rays in all directions on the 2d plane and +// record distance. done exactly once per frame. + +void +computeraytable(float vx, float vy) +{ + if (!ocull) + return; + + odist = getvar(@"fog") * 1.5f; + + float apitch = (float)fabs(player1.pitch); + float af = getvar(@"fov") / 2 + apitch / 1.5f + 3; + float byaw = (player1.yaw - 90 + af) / 360 * PI2; + float syaw = (player1.yaw - 90 - af) / 360 * PI2; + + loopi(NUMRAYS) + { + float angle = i * PI2 / NUMRAYS; + if ((apitch > 45 // must be bigger if fov>120 + || (angle < byaw && angle > syaw) || + (angle < byaw - PI2 && angle > syaw - PI2) || + (angle < byaw + PI2 && angle > syaw + PI2)) && + !OUTBORD(vx, vy) && + !SOLID(S(fast_f2nat(vx), + fast_f2nat(vy)))) // try to avoid tracing ray if outside + // of frustrum + { + float ray = i * 8 / (float)NUMRAYS; + float dx, dy; + if (ray > 1 && ray < 3) { + dx = -(ray - 2); + dy = 1; + } else if (ray >= 3 && ray < 5) { + dx = -1; + dy = -(ray - 4); + } else if (ray >= 5 && ray < 7) { + dx = ray - 6; + dy = -1; + } else { + dx = 1; + dy = ray > 4 ? ray - 8 : ray; + } + float sx = vx; + float sy = vy; + for (;;) { + sx += dx; + sy += dy; + // 90% of time spend in this function is on this + // line + if (SOLID(S(fast_f2nat(sx), fast_f2nat(sy)))) { + rdist[i] = (float)(fabs(sx - vx) + + fabs(sy - vy)); + break; + } + } + } else + rdist[i] = 2; + } +} + +// test occlusion for a cube... one of the most computationally expensive +// functions in the engine as its done for every cube and entity, but its effect +// is more than worth it! + +inline float +ca(float x, float y) +{ + return x > y ? y / x : 2 - x / y; +} +inline float +ma(float x, float y) +{ + return x == 0 ? (y > 0 ? 2 : -2) : y / x; +} + +int +isoccluded(float vx, float vy, float cx, float cy, + float csize) // v = viewer, c = cube to test +{ + if (!ocull) + return 0; + + float + nx = vx, + ny = vy; // n = point on the border of the cube that is closest to v + if (nx < cx) + nx = cx; + else if (nx > cx + csize) + nx = cx + csize; + if (ny < cy) + ny = cy; + else if (ny > cy + csize) + ny = cy + csize; + float xdist = (float)fabs(nx - vx); + float ydist = (float)fabs(ny - vy); + if (xdist > odist || ydist > odist) + return 2; + float dist = xdist + ydist - 1; // 1 needed? + + // ABC + // D E + // FGH + + // - check middle cube? BG + + // find highest and lowest angle in the occlusion map that this cube + // spans, based on its most left and right points on the border from the + // viewer pov... I see no easier way to do this than this silly code + // below + + float h, l; + if (cx <= vx) // ABDFG + { + if (cx + csize < vx) // ADF + { + if (cy <= vy) // AD + { + if (cy + csize < vy) { + h = ca(-(cx - vx), -(cy + csize - vy)) + + 4; + l = ca(-(cx + csize - vx), -(cy - vy)) + + 4; + } // A + else { + h = ma(-(cx + csize - vx), + -(cy + csize - vy)) + + 4; + l = ma(-(cx + csize - vx), -(cy - vy)) + + 4; + } // D + } else { + h = ca(cy + csize - vy, -(cx + csize - vx)) + 2; + l = ca(cy - vy, -(cx - vx)) + 2; + } // F + } else { // BG + if (cy <= vy) { + if (cy + csize < vy) { + h = ma(-(cy + csize - vy), cx - vx) + 6; + l = ma(-(cy + csize - vy), + cx + csize - vx) + + 6; + } // B + else + return 0; + } else { + h = ma(cy - vy, -(cx + csize - vx)) + 2; + l = ma(cy - vy, -(cx - vx)) + 2; + } // G + } + } else // CEH + { + if (cy <= vy) // CE + { + if (cy + csize < vy) { + h = ca(-(cy - vy), cx - vx) + 6; + l = ca(-(cy + csize - vy), cx + csize - vx) + 6; + } // C + else { + h = ma(cx - vx, cy - vy); + l = ma(cx - vx, cy + csize - vy); + } // E + } else { + h = ca(cx + csize - vx, cy - vy); + l = ca(cx - vx, cy + csize - vy); + } // H + } + int si = fast_f2nat(h * (NUMRAYS / 8)) + + NUMRAYS; // get indexes into occlusion map from angles + int ei = fast_f2nat(l * (NUMRAYS / 8)) + NUMRAYS + 1; + if (ei <= si) + ei += NUMRAYS; + + for (int i = si; i <= ei; i++) { + if (dist < rdist[i & (NUMRAYS - 1)]) + // if any value in this segment of the occlusion map is + // further away then cube is not occluded + return 0; + } + + return 1; // cube is entirely occluded +} DELETED src/worldocull.mm Index: src/worldocull.mm ================================================================== --- src/worldocull.mm +++ /dev/null @@ -1,201 +0,0 @@ -// worldocull.cpp: occlusion map and occlusion test - -#include "cube.h" - -#import "DynamicEntity.h" - -#define NUMRAYS 512 - -float rdist[NUMRAYS]; -bool ocull = true; -float odist = 256; - -void -toggleocull() -{ - ocull = !ocull; -} -COMMAND(toggleocull, ARG_NONE) - -// constructs occlusion map: cast rays in all directions on the 2d plane and -// record distance. done exactly once per frame. - -void -computeraytable(float vx, float vy) -{ - if (!ocull) - return; - - odist = getvar(@"fog") * 1.5f; - - float apitch = (float)fabs(player1.pitch); - float af = getvar(@"fov") / 2 + apitch / 1.5f + 3; - float byaw = (player1.yaw - 90 + af) / 360 * PI2; - float syaw = (player1.yaw - 90 - af) / 360 * PI2; - - loopi(NUMRAYS) - { - float angle = i * PI2 / NUMRAYS; - if ((apitch > 45 // must be bigger if fov>120 - || (angle < byaw && angle > syaw) || - (angle < byaw - PI2 && angle > syaw - PI2) || - (angle < byaw + PI2 && angle > syaw + PI2)) && - !OUTBORD(vx, vy) && - !SOLID(S(fast_f2nat(vx), - fast_f2nat(vy)))) // try to avoid tracing ray if outside - // of frustrum - { - float ray = i * 8 / (float)NUMRAYS; - float dx, dy; - if (ray > 1 && ray < 3) { - dx = -(ray - 2); - dy = 1; - } else if (ray >= 3 && ray < 5) { - dx = -1; - dy = -(ray - 4); - } else if (ray >= 5 && ray < 7) { - dx = ray - 6; - dy = -1; - } else { - dx = 1; - dy = ray > 4 ? ray - 8 : ray; - } - float sx = vx; - float sy = vy; - for (;;) { - sx += dx; - sy += dy; - // 90% of time spend in this function is on this - // line - if (SOLID(S(fast_f2nat(sx), fast_f2nat(sy)))) { - rdist[i] = (float)(fabs(sx - vx) + - fabs(sy - vy)); - break; - } - } - } else - rdist[i] = 2; - } -} - -// test occlusion for a cube... one of the most computationally expensive -// functions in the engine as its done for every cube and entity, but its effect -// is more than worth it! - -inline float -ca(float x, float y) -{ - return x > y ? y / x : 2 - x / y; -} -inline float -ma(float x, float y) -{ - return x == 0 ? (y > 0 ? 2 : -2) : y / x; -} - -int -isoccluded(float vx, float vy, float cx, float cy, - float csize) // v = viewer, c = cube to test -{ - if (!ocull) - return 0; - - float - nx = vx, - ny = vy; // n = point on the border of the cube that is closest to v - if (nx < cx) - nx = cx; - else if (nx > cx + csize) - nx = cx + csize; - if (ny < cy) - ny = cy; - else if (ny > cy + csize) - ny = cy + csize; - float xdist = (float)fabs(nx - vx); - float ydist = (float)fabs(ny - vy); - if (xdist > odist || ydist > odist) - return 2; - float dist = xdist + ydist - 1; // 1 needed? - - // ABC - // D E - // FGH - - // - check middle cube? BG - - // find highest and lowest angle in the occlusion map that this cube - // spans, based on its most left and right points on the border from the - // viewer pov... I see no easier way to do this than this silly code - // below - - float h, l; - if (cx <= vx) // ABDFG - { - if (cx + csize < vx) // ADF - { - if (cy <= vy) // AD - { - if (cy + csize < vy) { - h = ca(-(cx - vx), -(cy + csize - vy)) + - 4; - l = ca(-(cx + csize - vx), -(cy - vy)) + - 4; - } // A - else { - h = ma(-(cx + csize - vx), - -(cy + csize - vy)) + - 4; - l = ma(-(cx + csize - vx), -(cy - vy)) + - 4; - } // D - } else { - h = ca(cy + csize - vy, -(cx + csize - vx)) + 2; - l = ca(cy - vy, -(cx - vx)) + 2; - } // F - } else { // BG - if (cy <= vy) { - if (cy + csize < vy) { - h = ma(-(cy + csize - vy), cx - vx) + 6; - l = ma(-(cy + csize - vy), - cx + csize - vx) + - 6; - } // B - else - return 0; - } else { - h = ma(cy - vy, -(cx + csize - vx)) + 2; - l = ma(cy - vy, -(cx - vx)) + 2; - } // G - } - } else // CEH - { - if (cy <= vy) // CE - { - if (cy + csize < vy) { - h = ca(-(cy - vy), cx - vx) + 6; - l = ca(-(cy + csize - vy), cx + csize - vx) + 6; - } // C - else { - h = ma(cx - vx, cy - vy); - l = ma(cx - vx, cy + csize - vy); - } // E - } else { - h = ca(cx + csize - vx, cy - vy); - l = ca(cx - vx, cy + csize - vy); - } // H - } - int si = fast_f2nat(h * (NUMRAYS / 8)) + - NUMRAYS; // get indexes into occlusion map from angles - int ei = fast_f2nat(l * (NUMRAYS / 8)) + NUMRAYS + 1; - if (ei <= si) - ei += NUMRAYS; - - for (int i = si; i <= ei; i++) { - if (dist < rdist[i & (NUMRAYS - 1)]) - // if any value in this segment of the occlusion map is - // further away then cube is not occluded - return 0; - } - - return 1; // cube is entirely occluded -} ADDED src/worldrender.m Index: src/worldrender.m ================================================================== --- /dev/null +++ src/worldrender.m @@ -0,0 +1,356 @@ +// worldrender.cpp: goes through all cubes in top down quad tree fashion, +// determines what has to be rendered and how (depending on neighbouring cubes), +// then calls functions in rendercubes.cpp + +#include "cube.h" + +#import "DynamicEntity.h" + +void +render_wall(struct sqr *o, struct sqr *s, int x1, int y1, int x2, int y2, + int mip, struct sqr *d1, struct sqr *d2, bool topleft) +{ + if (SOLID(o) || o->type == SEMISOLID) { + float c1 = s->floor; + float c2 = s->floor; + if (s->type == FHF) { + c1 -= d1->vdelta / 4.0f; + c2 -= d2->vdelta / 4.0f; + } + float f1 = s->ceil; + float f2 = s->ceil; + if (s->type == CHF) { + f1 += d1->vdelta / 4.0f; + f2 += d2->vdelta / 4.0f; + } + // if(f1-c1<=0 && f2-c2<=0) return; + render_square(o->wtex, c1, c2, f1, f2, x1 << mip, y1 << mip, + x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); + return; + } + { + float f1 = s->floor; + float f2 = s->floor; + float c1 = o->floor; + float c2 = o->floor; + if (o->type == FHF && s->type != FHF) { + c1 -= d1->vdelta / 4.0f; + c2 -= d2->vdelta / 4.0f; + } + if (s->type == FHF && o->type != FHF) { + f1 -= d1->vdelta / 4.0f; + f2 -= d2->vdelta / 4.0f; + } + if (f1 >= c1 && f2 >= c2) + goto skip; + render_square(o->wtex, f1, f2, c1, c2, x1 << mip, y1 << mip, + x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); + } +skip: { + float f1 = o->ceil; + float f2 = o->ceil; + float c1 = s->ceil; + float c2 = s->ceil; + if (o->type == CHF && s->type != CHF) { + f1 += d1->vdelta / 4.0f; + f2 += d2->vdelta / 4.0f; + } else if (s->type == CHF && o->type != CHF) { + c1 += d1->vdelta / 4.0f; + c2 += d2->vdelta / 4.0f; + } + if (c1 <= f1 && c2 <= f2) + return; + render_square(o->utex, f1, f2, c1, c2, x1 << mip, y1 << mip, x2 << mip, + y2 << mip, 1 << mip, d1, d2, topleft); +} +} + +const int MAX_MIP = 5; // 32x32 unit blocks +const int MIN_LOD = 2; +const int LOW_LOD = 25; +const int MAX_LOD = 1000; + +int lod = 40, lodtop, lodbot, lodleft, lodright; +int min_lod; + +int stats[LARGEST_FACTOR]; + +// detect those cases where a higher mip solid has a visible wall next to lower +// mip cubes (used for wall rendering below) + +bool +issemi(int mip, int x, int y, int x1, int y1, int x2, int y2) +{ + if (!(mip--)) + return true; + struct sqr *w = wmip[mip]; + int msize = ssize >> mip; + x *= 2; + y *= 2; + switch (SWS(w, x + x1, y + y1, msize)->type) { + case SEMISOLID: + if (issemi(mip, x + x1, y + y1, x1, y1, x2, y2)) + return true; + case CORNER: + case SOLID: + break; + default: + return true; + } + switch (SWS(w, x + x2, y + y2, msize)->type) { + case SEMISOLID: + if (issemi(mip, x + x2, y + y2, x1, y1, x2, y2)) + return true; + case CORNER: + case SOLID: + break; + default: + return true; + } + return false; +} + +bool render_floor, render_ceil; + +// the core recursive function, renders a rect of cubes at a certain mip level +// from a viewer perspective call itself for lower mip levels, on most modern +// machines however this function will use the higher mip levels only for +// perfect mips. + +void +render_seg_new( + float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) +{ + struct sqr *w = wmip[mip]; + int sz = ssize >> mip; + int vxx = ((int)vx + (1 << mip) / 2) >> mip; + int vyy = ((int)vy + (1 << mip) / 2) >> mip; + int lx = + vxx - lodleft; // these mark the rect inside the current rest that + // we want to render using a lower mip level + int ly = vyy - lodtop; + int rx = vxx + lodright; + int ry = vyy + lodbot; + + float fsize = (float)(1 << mip); + for (int ox = x; ox < xs; ox++) { + // first collect occlusion information for this block + for (int oy = y; oy < ys; oy++) { + SWS(w, ox, oy, sz)->occluded = + isoccluded(player1.o.x, player1.o.y, + (float)(ox << mip), (float)(oy << mip), fsize); + } + } + + int pvx = (int)vx >> mip; + int pvy = (int)vy >> mip; + if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { + // SWS(w,vxx,vyy,sz)->occluded = 0; + // player cell never occluded + SWS(w, pvx, pvy, sz)->occluded = 0; + } + +#define df(x) s->floor - (x->vdelta / 4.0f) +#define dc(x) s->ceil + (x->vdelta / 4.0f) + + // loop through the rect 3 times (for floor/ceil/walls seperately, to + // facilitate dynamic stripify) for each we skip occluded cubes + // (occlusion at higher mip levels is a big time saver!). during the + // first loop (ceil) we collect cubes that lie within the lower mip rect + // and are also deferred, and render them recursively. Anything left + // (perfect mips and higher lods) we render here. + +#define LOOPH \ + { \ + for (int xx = x; xx < xs; xx++) \ + for (int yy = y; yy < ys; yy++) { \ + struct sqr *s = SWS(w, xx, yy, sz); \ + if (s->occluded == 1) \ + continue; \ + if (s->defer && !s->occluded && mip && \ + xx >= lx && xx < rx && yy >= ly && \ + yy < ry) +#define LOOPD \ + struct sqr *t = SWS(s, 1, 0, sz); \ + struct sqr *u = SWS(s, 1, 1, sz); \ + struct sqr *v = SWS(s, 0, 1, sz); + + LOOPH // ceils + { + int start = yy; + struct sqr *next; + while (yy < ys - 1 && (next = SWS(w, xx, yy + 1, sz))->defer && + !next->occluded) + yy++; // collect 2xN rect of lower mip + render_seg_new(vx, vy, vh, mip - 1, xx * 2, start * 2, + xx * 2 + 2, yy * 2 + 2); + continue; + } + stats[mip]++; + LOOPD + if ((s->type == SPACE || s->type == FHF) && s->ceil >= vh && + render_ceil) + render_flat(s->ctex, xx << mip, yy << mip, 1 << mip, s->ceil, s, + t, u, v, true); + if (s->type == CHF) // if(s->ceil>=vh) + render_flatdelta(s->ctex, xx << mip, yy << mip, 1 << mip, dc(s), + dc(t), dc(u), dc(v), s, t, u, v, true); +} +} + +LOOPH continue; // floors +LOOPD +if ((s->type == SPACE || s->type == CHF) && s->floor <= vh && render_floor) { + render_flat(s->ftex, xx << mip, yy << mip, 1 << mip, s->floor, s, t, u, + v, false); + if (s->floor < hdr.waterlevel && !SOLID(s)) + addwaterquad(xx << mip, yy << mip, 1 << mip); +} +if (s->type == FHF) { + render_flatdelta(s->ftex, xx << mip, yy << mip, 1 << mip, df(s), df(t), + df(u), df(v), s, t, u, v, false); + if (s->floor - s->vdelta / 4.0f < hdr.waterlevel && !SOLID(s)) + addwaterquad(xx << mip, yy << mip, 1 << mip); +} +} +} + +LOOPH continue; // walls +LOOPD +// w +// zSt +// vu + +struct sqr *w = SWS(s, 0, -1, sz); +struct sqr *z = SWS(s, -1, 0, sz); +bool normalwall = true; + +if (s->type == CORNER) { + // cull also + bool topleft = true; + struct sqr *h1 = NULL; + struct sqr *h2 = NULL; + if (SOLID(z)) { + if (SOLID(w)) { + render_wall(w, h2 = s, xx + 1, yy, xx, yy + 1, mip, t, + v, false); + topleft = false; + } else if (SOLID(v)) { + render_wall(v, h2 = s, xx, yy, xx + 1, yy + 1, mip, s, + u, false); + } + } else if (SOLID(t)) { + if (SOLID(w)) { + render_wall(w, h1 = s, xx + 1, yy + 1, xx, yy, mip, u, + s, false); + } else if (SOLID(v)) { + render_wall(v, h1 = s, xx, yy + 1, xx + 1, yy, mip, v, + t, false); + topleft = false; + } + } else { + normalwall = false; + bool wv = w->ceil - w->floor < v->ceil - v->floor; + if (z->ceil - z->floor < t->ceil - t->floor) { + if (wv) { + render_wall(h1 = s, h2 = v, xx + 1, yy, xx, + yy + 1, mip, t, v, false); + topleft = false; + } else { + render_wall(h1 = s, h2 = w, xx, yy, xx + 1, + yy + 1, mip, s, u, false); + } + } else { + if (wv) { + render_wall(h2 = s, h1 = v, xx + 1, yy + 1, xx, + yy, mip, u, s, false); + } else { + render_wall(h2 = s, h1 = w, xx, yy + 1, xx + 1, + yy, mip, v, t, false); + topleft = false; + } + } + } + render_tris( + xx << mip, yy << mip, 1 << mip, topleft, h1, h2, s, t, u, v); +} + +if (normalwall) { + bool inner = xx != sz - 1 && yy != sz - 1; + + if (xx >= vxx && xx != 0 && yy != sz - 1 && !SOLID(z) && + (!SOLID(s) || z->type != CORNER) && + (z->type != SEMISOLID || issemi(mip, xx - 1, yy, 1, 0, 1, 1))) + render_wall(s, z, xx, yy, xx, yy + 1, mip, s, v, true); + if (xx <= vxx && inner && !SOLID(t) && + (!SOLID(s) || t->type != CORNER) && + (t->type != SEMISOLID || issemi(mip, xx + 1, yy, 0, 0, 0, 1))) + render_wall(s, t, xx + 1, yy, xx + 1, yy + 1, mip, t, u, false); + if (yy >= vyy && yy != 0 && xx != sz - 1 && !SOLID(w) && + (!SOLID(s) || w->type != CORNER) && + (w->type != SEMISOLID || issemi(mip, xx, yy - 1, 0, 1, 1, 1))) + render_wall(s, w, xx, yy, xx + 1, yy, mip, s, t, false); + if (yy <= vyy && inner && !SOLID(v) && + (!SOLID(s) || v->type != CORNER) && + (v->type != SEMISOLID || issemi(mip, xx, yy + 1, 0, 0, 1, 0))) + render_wall(s, v, xx, yy + 1, xx + 1, yy + 1, mip, v, u, true); +} +} +} +} + +static void +distlod(int *low, int *high, int angle, float widef) +{ + float f = 90.0f / lod / widef; + *low = (int)((90 - angle) / f); + *high = (int)(angle / f); + if (*low < min_lod) + *low = min_lod; + if (*high < min_lod) + *high = min_lod; +} + +// does some out of date view frustrum optimisation that doesn't contribute much +// anymore + +void +render_world( + float vx, float vy, float vh, int yaw, int pitch, float fov, int w, int h) +{ + loopi(LARGEST_FACTOR) stats[i] = 0; + min_lod = MIN_LOD + abs(pitch) / 12; + yaw = 360 - yaw; + float widef = fov / 75.0f; + int cdist = abs(yaw % 90 - 45); + // hack to avoid popup at high fovs at 45 yaw + if (cdist < 7) { + // less if lod worked better + min_lod = + max(min_lod, (int)(MIN_LOD + (10 - cdist) / 1.0f * widef)); + widef = 1.0f; + } + lod = MAX_LOD; + lodtop = lodbot = lodleft = lodright = min_lod; + if (yaw > 45 && yaw <= 135) { + lodleft = lod; + distlod(&lodtop, &lodbot, yaw - 45, widef); + } else if (yaw > 135 && yaw <= 225) { + lodbot = lod; + distlod(&lodleft, &lodright, yaw - 135, widef); + } else if (yaw > 225 && yaw <= 315) { + lodright = lod; + distlod(&lodbot, &lodtop, yaw - 225, widef); + } else { + lodtop = lod; + distlod(&lodright, &lodleft, yaw <= 45 ? yaw + 45 : yaw - 315, + widef); + } + float hyfov = fov * h / w / 2; + render_floor = pitch < hyfov; + render_ceil = -pitch < hyfov; + + render_seg_new( + vx, vy, vh, MAX_MIP, 0, 0, ssize >> MAX_MIP, ssize >> MAX_MIP); + mipstats(stats[0], stats[1], stats[2]); +} DELETED src/worldrender.mm Index: src/worldrender.mm ================================================================== --- src/worldrender.mm +++ /dev/null @@ -1,356 +0,0 @@ -// worldrender.cpp: goes through all cubes in top down quad tree fashion, -// determines what has to be rendered and how (depending on neighbouring cubes), -// then calls functions in rendercubes.cpp - -#include "cube.h" - -#import "DynamicEntity.h" - -void -render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, - sqr *d2, bool topleft) -{ - if (SOLID(o) || o->type == SEMISOLID) { - float c1 = s->floor; - float c2 = s->floor; - if (s->type == FHF) { - c1 -= d1->vdelta / 4.0f; - c2 -= d2->vdelta / 4.0f; - } - float f1 = s->ceil; - float f2 = s->ceil; - if (s->type == CHF) { - f1 += d1->vdelta / 4.0f; - f2 += d2->vdelta / 4.0f; - } - // if(f1-c1<=0 && f2-c2<=0) return; - render_square(o->wtex, c1, c2, f1, f2, x1 << mip, y1 << mip, - x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); - return; - } - { - float f1 = s->floor; - float f2 = s->floor; - float c1 = o->floor; - float c2 = o->floor; - if (o->type == FHF && s->type != FHF) { - c1 -= d1->vdelta / 4.0f; - c2 -= d2->vdelta / 4.0f; - } - if (s->type == FHF && o->type != FHF) { - f1 -= d1->vdelta / 4.0f; - f2 -= d2->vdelta / 4.0f; - } - if (f1 >= c1 && f2 >= c2) - goto skip; - render_square(o->wtex, f1, f2, c1, c2, x1 << mip, y1 << mip, - x2 << mip, y2 << mip, 1 << mip, d1, d2, topleft); - } -skip: { - float f1 = o->ceil; - float f2 = o->ceil; - float c1 = s->ceil; - float c2 = s->ceil; - if (o->type == CHF && s->type != CHF) { - f1 += d1->vdelta / 4.0f; - f2 += d2->vdelta / 4.0f; - } else if (s->type == CHF && o->type != CHF) { - c1 += d1->vdelta / 4.0f; - c2 += d2->vdelta / 4.0f; - } - if (c1 <= f1 && c2 <= f2) - return; - render_square(o->utex, f1, f2, c1, c2, x1 << mip, y1 << mip, x2 << mip, - y2 << mip, 1 << mip, d1, d2, topleft); -} -} - -const int MAX_MIP = 5; // 32x32 unit blocks -const int MIN_LOD = 2; -const int LOW_LOD = 25; -const int MAX_LOD = 1000; - -int lod = 40, lodtop, lodbot, lodleft, lodright; -int min_lod; - -int stats[LARGEST_FACTOR]; - -// detect those cases where a higher mip solid has a visible wall next to lower -// mip cubes (used for wall rendering below) - -bool -issemi(int mip, int x, int y, int x1, int y1, int x2, int y2) -{ - if (!(mip--)) - return true; - sqr *w = wmip[mip]; - int msize = ssize >> mip; - x *= 2; - y *= 2; - switch (SWS(w, x + x1, y + y1, msize)->type) { - case SEMISOLID: - if (issemi(mip, x + x1, y + y1, x1, y1, x2, y2)) - return true; - case CORNER: - case SOLID: - break; - default: - return true; - } - switch (SWS(w, x + x2, y + y2, msize)->type) { - case SEMISOLID: - if (issemi(mip, x + x2, y + y2, x1, y1, x2, y2)) - return true; - case CORNER: - case SOLID: - break; - default: - return true; - } - return false; -} - -bool render_floor, render_ceil; - -// the core recursive function, renders a rect of cubes at a certain mip level -// from a viewer perspective call itself for lower mip levels, on most modern -// machines however this function will use the higher mip levels only for -// perfect mips. - -void -render_seg_new( - float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) -{ - sqr *w = wmip[mip]; - int sz = ssize >> mip; - int vxx = ((int)vx + (1 << mip) / 2) >> mip; - int vyy = ((int)vy + (1 << mip) / 2) >> mip; - int lx = - vxx - lodleft; // these mark the rect inside the current rest that - // we want to render using a lower mip level - int ly = vyy - lodtop; - int rx = vxx + lodright; - int ry = vyy + lodbot; - - float fsize = (float)(1 << mip); - for (int ox = x; ox < xs; ox++) { - // first collect occlusion information for this block - for (int oy = y; oy < ys; oy++) { - SWS(w, ox, oy, sz)->occluded = - isoccluded(player1.o.x, player1.o.y, - (float)(ox << mip), (float)(oy << mip), fsize); - } - } - - int pvx = (int)vx >> mip; - int pvy = (int)vy >> mip; - if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { - // SWS(w,vxx,vyy,sz)->occluded = 0; - // player cell never occluded - SWS(w, pvx, pvy, sz)->occluded = 0; - } - -#define df(x) s->floor - (x->vdelta / 4.0f) -#define dc(x) s->ceil + (x->vdelta / 4.0f) - - // loop through the rect 3 times (for floor/ceil/walls seperately, to - // facilitate dynamic stripify) for each we skip occluded cubes - // (occlusion at higher mip levels is a big time saver!). during the - // first loop (ceil) we collect cubes that lie within the lower mip rect - // and are also deferred, and render them recursively. Anything left - // (perfect mips and higher lods) we render here. - -#define LOOPH \ - { \ - for (int xx = x; xx < xs; xx++) \ - for (int yy = y; yy < ys; yy++) { \ - sqr *s = SWS(w, xx, yy, sz); \ - if (s->occluded == 1) \ - continue; \ - if (s->defer && !s->occluded && mip && \ - xx >= lx && xx < rx && yy >= ly && \ - yy < ry) -#define LOOPD \ - sqr *t = SWS(s, 1, 0, sz); \ - sqr *u = SWS(s, 1, 1, sz); \ - sqr *v = SWS(s, 0, 1, sz); - - LOOPH // ceils - { - int start = yy; - sqr *next; - while (yy < ys - 1 && (next = SWS(w, xx, yy + 1, sz))->defer && - !next->occluded) - yy++; // collect 2xN rect of lower mip - render_seg_new(vx, vy, vh, mip - 1, xx * 2, start * 2, - xx * 2 + 2, yy * 2 + 2); - continue; - } - stats[mip]++; - LOOPD - if ((s->type == SPACE || s->type == FHF) && s->ceil >= vh && - render_ceil) - render_flat(s->ctex, xx << mip, yy << mip, 1 << mip, s->ceil, s, - t, u, v, true); - if (s->type == CHF) // if(s->ceil>=vh) - render_flatdelta(s->ctex, xx << mip, yy << mip, 1 << mip, dc(s), - dc(t), dc(u), dc(v), s, t, u, v, true); -} -} - -LOOPH continue; // floors -LOOPD -if ((s->type == SPACE || s->type == CHF) && s->floor <= vh && render_floor) { - render_flat(s->ftex, xx << mip, yy << mip, 1 << mip, s->floor, s, t, u, - v, false); - if (s->floor < hdr.waterlevel && !SOLID(s)) - addwaterquad(xx << mip, yy << mip, 1 << mip); -} -if (s->type == FHF) { - render_flatdelta(s->ftex, xx << mip, yy << mip, 1 << mip, df(s), df(t), - df(u), df(v), s, t, u, v, false); - if (s->floor - s->vdelta / 4.0f < hdr.waterlevel && !SOLID(s)) - addwaterquad(xx << mip, yy << mip, 1 << mip); -} -} -} - -LOOPH continue; // walls -LOOPD -// w -// zSt -// vu - -sqr *w = SWS(s, 0, -1, sz); -sqr *z = SWS(s, -1, 0, sz); -bool normalwall = true; - -if (s->type == CORNER) { - // cull also - bool topleft = true; - sqr *h1 = NULL; - sqr *h2 = NULL; - if (SOLID(z)) { - if (SOLID(w)) { - render_wall(w, h2 = s, xx + 1, yy, xx, yy + 1, mip, t, - v, false); - topleft = false; - } else if (SOLID(v)) { - render_wall(v, h2 = s, xx, yy, xx + 1, yy + 1, mip, s, - u, false); - } - } else if (SOLID(t)) { - if (SOLID(w)) { - render_wall(w, h1 = s, xx + 1, yy + 1, xx, yy, mip, u, - s, false); - } else if (SOLID(v)) { - render_wall(v, h1 = s, xx, yy + 1, xx + 1, yy, mip, v, - t, false); - topleft = false; - } - } else { - normalwall = false; - bool wv = w->ceil - w->floor < v->ceil - v->floor; - if (z->ceil - z->floor < t->ceil - t->floor) { - if (wv) { - render_wall(h1 = s, h2 = v, xx + 1, yy, xx, - yy + 1, mip, t, v, false); - topleft = false; - } else { - render_wall(h1 = s, h2 = w, xx, yy, xx + 1, - yy + 1, mip, s, u, false); - } - } else { - if (wv) { - render_wall(h2 = s, h1 = v, xx + 1, yy + 1, xx, - yy, mip, u, s, false); - } else { - render_wall(h2 = s, h1 = w, xx, yy + 1, xx + 1, - yy, mip, v, t, false); - topleft = false; - } - } - } - render_tris( - xx << mip, yy << mip, 1 << mip, topleft, h1, h2, s, t, u, v); -} - -if (normalwall) { - bool inner = xx != sz - 1 && yy != sz - 1; - - if (xx >= vxx && xx != 0 && yy != sz - 1 && !SOLID(z) && - (!SOLID(s) || z->type != CORNER) && - (z->type != SEMISOLID || issemi(mip, xx - 1, yy, 1, 0, 1, 1))) - render_wall(s, z, xx, yy, xx, yy + 1, mip, s, v, true); - if (xx <= vxx && inner && !SOLID(t) && - (!SOLID(s) || t->type != CORNER) && - (t->type != SEMISOLID || issemi(mip, xx + 1, yy, 0, 0, 0, 1))) - render_wall(s, t, xx + 1, yy, xx + 1, yy + 1, mip, t, u, false); - if (yy >= vyy && yy != 0 && xx != sz - 1 && !SOLID(w) && - (!SOLID(s) || w->type != CORNER) && - (w->type != SEMISOLID || issemi(mip, xx, yy - 1, 0, 1, 1, 1))) - render_wall(s, w, xx, yy, xx + 1, yy, mip, s, t, false); - if (yy <= vyy && inner && !SOLID(v) && - (!SOLID(s) || v->type != CORNER) && - (v->type != SEMISOLID || issemi(mip, xx, yy + 1, 0, 0, 1, 0))) - render_wall(s, v, xx, yy + 1, xx + 1, yy + 1, mip, v, u, true); -} -} -} -} - -void -distlod(int &low, int &high, int angle, float widef) -{ - float f = 90.0f / lod / widef; - low = (int)((90 - angle) / f); - high = (int)(angle / f); - if (low < min_lod) - low = min_lod; - if (high < min_lod) - high = min_lod; -} - -// does some out of date view frustrum optimisation that doesn't contribute much -// anymore - -void -render_world( - float vx, float vy, float vh, int yaw, int pitch, float fov, int w, int h) -{ - loopi(LARGEST_FACTOR) stats[i] = 0; - min_lod = MIN_LOD + abs(pitch) / 12; - yaw = 360 - yaw; - float widef = fov / 75.0f; - int cdist = abs(yaw % 90 - 45); - // hack to avoid popup at high fovs at 45 yaw - if (cdist < 7) { - // less if lod worked better - min_lod = - max(min_lod, (int)(MIN_LOD + (10 - cdist) / 1.0f * widef)); - widef = 1.0f; - } - lod = MAX_LOD; - lodtop = lodbot = lodleft = lodright = min_lod; - if (yaw > 45 && yaw <= 135) { - lodleft = lod; - distlod(lodtop, lodbot, yaw - 45, widef); - } else if (yaw > 135 && yaw <= 225) { - lodbot = lod; - distlod(lodleft, lodright, yaw - 135, widef); - } else if (yaw > 225 && yaw <= 315) { - lodright = lod; - distlod(lodbot, lodtop, yaw - 225, widef); - } else { - lodtop = lod; - distlod( - lodright, lodleft, yaw <= 45 ? yaw + 45 : yaw - 315, widef); - } - float hyfov = fov * h / w / 2; - render_floor = pitch < hyfov; - render_ceil = -pitch < hyfov; - - render_seg_new( - vx, vy, vh, MAX_MIP, 0, 0, ssize >> MAX_MIP, ssize >> MAX_MIP); - mipstats(stats[0], stats[1], stats[2]); -}