/* UDMF map visualizer - Kinda like dmvis but for UDMF maps specifically. (C)2018 Marisa Kirisame, UnSX Team. Released under the GNU General Public License version 3 (or later). */ #include #include #include #include #include #include /* At the moment output is broken on most image editors, but it can be fixed by running it through gifsicle. Things left to do: - Figure out why output is bugged. */ typedef struct { double x, y; } vertex_t; typedef struct { int v1, v2, type; int fs, bs; } line_t; typedef struct { int s; int v1, v2, type; } side_t; vertex_t *verts = 0; line_t *lines = 0; side_t *sides = 0; int nverts = 0, nlines = 0, nsides = 0; #define LN_ONESIDED 0 #define LN_TWOSIDED 1 #define LN_ASPECIAL 2 int cmp_sides_by_vertex( const void *a, const void *b ) { side_t *sa = (side_t*)a; side_t *sb = (side_t*)b; return sa->v2 - sb->v1; } int cmp_sides_by_sector( const void *a, const void *b ) { side_t *sa = (side_t*)a; side_t *sb = (side_t*)b; return sa->s - sb->s; } #define LAST_NONE 0 #define LAST_IDENT 1 #define LAST_EQUALS 2 static int bx, by, bpitch; static uint8_t *b = 0; #define swap(t,x,y) {t tmp=x;x=y;y=tmp;} static void bresenham( int col, int x0, int y0, int x1, int y1 ) { // copied from proto-agl x0 -= bx; x1 -= bx; y0 -= by; y1 -= by; int steep = 0; if ( abs(x0-x1) < abs(y0-y1) ) { swap(int,x0,y0); swap(int,x1,y1); steep = 1; } if ( x0 > x1 ) { swap(int,x0,x1); swap(int,y0,y1); } int dx = x1-x0; int dy = y1-y0; int der = abs(dy)*2; int er = 0; int y = y0; for ( int x=x0; x<=x1; x++ ) { int px = x, py = y; if ( steep ) swap(int,px,py); b[px+py*bpitch] = col; er += der; if ( er > dx ) { y += (y1>y0)?1:-1; er -= dx*2; } } } void getsidebounds( int i, int *x, int *y, int *w, int *h ) { int minx = 0, miny = 0, maxx = 0, maxy = 0; if ( i < nsides ) { if ( i > 0 ) { minx = verts[sides[i-1].v1].x; if ( verts[sides[i-1].v2].x < minx ) minx = verts[sides[i-1].v2].x; if ( verts[sides[i].v1].x < minx ) minx = verts[sides[i].v1].x; if ( verts[sides[i].v2].x < minx ) minx = verts[sides[i].v2].x; maxx = verts[sides[i-1].v1].x; if ( verts[sides[i-1].v2].x > maxx ) maxx = verts[sides[i-1].v2].x; if ( verts[sides[i].v1].x > maxx ) maxx = verts[sides[i].v1].x; if ( verts[sides[i].v2].x > maxx ) maxx = verts[sides[i].v2].x; miny = verts[sides[i-1].v1].y; if ( verts[sides[i-1].v2].y < miny ) miny = verts[sides[i-1].v2].y; if ( verts[sides[i].v1].y < miny ) miny = verts[sides[i].v1].y; if ( verts[sides[i].v2].y < miny ) miny = verts[sides[i].v2].y; maxy = verts[sides[i-1].v1].y; if ( verts[sides[i-1].v2].y > maxy ) maxy = verts[sides[i-1].v2].y; if ( verts[sides[i].v1].y > maxy ) maxy = verts[sides[i].v1].y; if ( verts[sides[i].v2].y > maxy ) maxy = verts[sides[i].v2].y; } else { minx = verts[sides[i].v1].x; if ( verts[sides[i].v2].x < minx ) minx = verts[sides[i].v2].x; maxx = verts[sides[i].v1].x; if ( verts[sides[i].v2].x > maxx ) maxx = verts[sides[i].v2].x; miny = verts[sides[i].v1].y; if ( verts[sides[i].v2].y < miny ) miny = verts[sides[i].v2].y; maxy = verts[sides[i].v1].y; if ( verts[sides[i].v2].y > maxy ) maxy = verts[sides[i].v2].y; } } else if ( i > 0 ) { minx = verts[sides[i-1].v1].x; if ( verts[sides[i-1].v2].x < minx ) minx = verts[sides[i-1].v2].x; maxx = verts[sides[i-1].v1].x; if ( verts[sides[i-1].v2].x > maxx ) maxx = verts[sides[i-1].v2].x; miny = verts[sides[i-1].v1].y; if ( verts[sides[i-1].v2].y < miny ) miny = verts[sides[i-1].v2].y; maxy = verts[sides[i-1].v1].y; if ( verts[sides[i-1].v2].y > maxy ) maxy = verts[sides[i-1].v2].y; } *x = minx; *y = miny; *w = (maxx-minx) + 1; *h = (maxy-miny) + 1; } void drawside( int i ) { // draw previous side normally (if any) if ( i > 0 ) { int c = 3; if ( sides[i-1].type&LN_ASPECIAL ) c = 2; else if ( sides[i-1].type&LN_TWOSIDED ) c = 4; bresenham(c,verts[sides[i-1].v1].x,verts[sides[i-1].v1].y, verts[sides[i-1].v2].x,verts[sides[i-1].v2].y); } // draw current line highlighted (if any) if ( i < nsides ) { bresenham(1,verts[sides[i].v1].x,verts[sides[i].v1].y, verts[sides[i].v2].x,verts[sides[i].v2].y); } } uint8_t gifpal[8][3] = { {255,255,255}, // background {220,0,0}, // cursor {220,130,50}, // special line {0,0,0}, // one-sided line {144,144,144}, // two-sided line {0,0,0}, {0,0,0}, {0,0,0}, }; #define LZW_MAXCODLEN 12 #define LZW_MAXNCODES (1< (1<= 8 ) { int out = lzwenc.bits&0xff; lzwenc.bits >>= 8; lzwenc.nbits -= 8; return out; } else if ( flush && (lzwenc.nbits > 0) ) { int out = lzwenc.bits&0xff; lzwenc.bits = 0; lzwenc.nbits = 0; return out; } else return -1; } void drainlzw( FILE *f, uint8_t *buf, int flush ) { for ( ; ; ) { int b = nextlzwob(flush); if ( b < 0 ) break; buf[0]++; buf[buf[0]] = b; if ( buf[0] == 255 ) { fwrite(buf,1,256,f); buf[0] = 0; } } if ( flush ) { if ( buf[0] != 0 ) fwrite(buf,1,buf[0]+1,f); fputc(0,f); buf[0] = 0; } } void writelzw( uint8_t *data, int siz, int bits, FILE *f ) { fputc(bits,f); int nlit = 1< maxx ) maxx = verts[i].x; if ( -verts[i].y < miny ) miny = -verts[i].y; if ( -verts[i].y > maxy ) maxy = -verts[i].y; } mapw = maxx-minx; maph = maxy-miny; double mx = (mapw > maph) ? mapw : maph; scale = (imgw-border*2)/mx; minx *= scale; miny *= scale; maxx *= scale; maxy *= scale; imgh = maxy-miny+2*border; for ( int i=0; i>8, imgh&0xff,imgh>>8, 0xF2,0x00,0x00, }; fwrite(ghead,sizeof(ghead),1,gif); fwrite(gifpal,3,8,gif); uint8_t appext[] = { 0x21,0xff,0x0b, 'N','E','T','S','C','A','P','E', '2','.','0', 0x03,0x01,0x00,0x00,0x00, }; fwrite(appext,sizeof(appext),1,gif); /* allocate base layer */ b = calloc(imgw*imgh,1); uint8_t gce[] = { 0x21,0xf9,0x04,0x04, 0x02,0x00, 0x00,0x00, }; fwrite(gce,sizeof(gce),1,gif); uint8_t ihead[] = { 0x2c, 0x00,0x00, 0x00,0x00, imgw&0xff,imgw>>8, imgh&0xff,imgh>>8, 0x00, }; fwrite(ihead,sizeof(ihead),1,gif); writelzw(b,imgw*imgh,3,gif); for ( int i=0; i<=nsides; i++ ) { printf("\rwriting frame %d of %d",i,nsides); int delay = (i>8, 0x00,0x00, }; fwrite(gce,sizeof(gce),1,gif); /* calculate bounds */ int x, y, w, h; getsidebounds(i,&x,&y,&w,&h); bx = x; by = y; bpitch = w; uint8_t ihead[] = { 0x2c, x&0xff,x>>8, y&0xff,y>>8, w&0xff,w>>8, h&0xff,h>>8, 0x00, }; fwrite(ihead,sizeof(ihead),1,gif); /* render */ memset(b,0,w*h); drawside(i); /* write frame */ writelzw(b,w*h,3,gif); } free(b); putchar('\n'); fputc(0x3b,gif); fclose(gif); } void process_startblock( char *blk ) { if ( !strcasecmp(blk,"vertex") ) { if ( !verts ) verts = malloc(sizeof(vertex_t)); else verts = realloc(verts,sizeof(vertex_t)*(nverts+1)); memset(verts+nverts,0,sizeof(vertex_t)); } else if ( !strcasecmp(blk,"linedef") ) { if ( !lines ) lines = malloc(sizeof(line_t)); else lines = realloc(lines,sizeof(line_t)*(nlines+1)); memset(lines+nlines,0,sizeof(line_t)); lines[nlines].bs = -1; } else if ( !strcasecmp(blk,"sidedef") ) { if ( !sides ) sides = malloc(sizeof(side_t)); else sides = realloc(sides,sizeof(side_t)*(nsides+1)); memset(sides+nsides,0,sizeof(side_t)); } } void process_assignment( char *blk, char *id, char *val ) { if ( !strcasecmp(blk,"vertex") ) { if ( !strcasecmp(id,"x") ) sscanf(val,"%lf",&verts[nverts].x); else if ( !strcasecmp(id,"y") ) sscanf(val,"%lf",&verts[nverts].y); } else if ( !strcasecmp(blk,"linedef") ) { if ( !strcasecmp(id,"v1") ) sscanf(val,"%d",&lines[nlines].v1); else if ( !strcasecmp(id,"v2") ) sscanf(val,"%d",&lines[nlines].v2); else if ( !strcasecmp(id,"sidefront") ) sscanf(val,"%d",&lines[nlines].fs); else if ( !strcasecmp(id,"sideback") ) { sscanf(val,"%d",&lines[nlines].bs); lines[nlines].type |= LN_TWOSIDED; } else if ( !strcasecmp(id,"special") ) lines[nlines].type |= LN_ASPECIAL; } else if ( !strcasecmp(blk,"sidedef") ) { if ( !strcasecmp(id,"sector") ) sscanf(val,"%d",&sides[nsides].s); } } void process_endblock( char *blk ) { if ( !strcasecmp(blk,"vertex") ) { nverts++; } else if ( !strcasecmp(blk,"linedef") ) { nlines++; } else if ( !strcasecmp(blk,"sidedef") ) { nsides++; } } int main( int argc, char **argv ) { if ( argc < 2 ) { fprintf(stderr,"usage: %s [width]\n",argv[0]); return 1; } FILE *tmap = fopen(argv[1],"r"); if ( !tmap ) { fprintf(stderr,"couldn't open textmap: %s\n",strerror(errno)); return 2; } int ch; int last = 0; char id[256] = {0}; char val[256] = {0}; char blk[256] = {0}; while ( !feof(tmap) ) { /* check for comments */ if ( (ch = fgetc(tmap)) == '/' ) { if ( (ch = fgetc(tmap)) == '/' ) { /* single line comment */ while ( (ch = fgetc(tmap)) != '\n' ); } else if ( (ch = fgetc(tmap)) == '*' ) { /* multi-block comment (like this one) */ while ( !feof(tmap) ) { if ( (ch = fgetc(tmap)) != '*' ) continue; if ( (ch = fgetc(tmap)) == '/' ) break; } } } if ( (ch == ' ') || (ch == '\t') || (ch == '\n') ) continue; switch ( last ) { case LAST_NONE: /* check for block end */ if ( ch == '}' ) { process_endblock(blk); blk[0] = 0; break; } /* read identifier */ fseek(tmap,-1,SEEK_CUR); fscanf(tmap,"%[^ \t\n=/]",id); last = LAST_IDENT; break; case LAST_IDENT: /* assignment or block? */ if ( ch == '=' ) last = LAST_EQUALS; else if ( ch == '{' ) { strcpy(blk,id); process_startblock(blk); last = LAST_NONE; } break; case LAST_EQUALS: /* read the value */ fseek(tmap,-1,SEEK_CUR); fscanf(tmap,"%[^;]",val); process_assignment(blk,id,val); ch = fgetc(tmap); last = LAST_NONE; break; } } fclose(tmap); /* transfer info to sides */ for ( int i=0; i 2 ) sscanf(argv[2],"%d",&iw); mkanim(iw); if ( verts ) free(verts); if ( lines ) free(lines); if ( sides ) free(sides); return 0; }