diff --git a/README.md b/README.md index f36af8a..f0f11ef 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Random single-file programs I've written in my spare time for small tasks. in DDS volume maps. Successor to mkvolume. Used for MariENB. * memrd/memsk/memwr: Quick 'n dirty tools for memory manipulation on running programs. +* mkfont: A tool I use to convert UE fonts exported with UTPT into fonts for + GZDoom. * mkgauss: Make an array of gaussian blur kernel values with passed radius and sigma. Used for shader development. * mksoundwad: Program used during the early days of Tim Allen Doom. Deprecated @@ -28,6 +30,7 @@ Random single-file programs I've written in my spare time for small tasks. can be generated in bulk. * totty: Sends text from stdin to tty1. Used to send certain commands when remoting into a Raspberry Pi. +* udmfvis: dmvis clone in C for UDMF maps. No external dependencies. * umxunpack: Extractor for music in UE archives, with support for Unreal 227's UMX files containing vorbis audio. * vc2sdl: Passes the contents of the VC4 framebuffer to a SDL window. Was used diff --git a/mkfont.c b/mkfont.c new file mode 100644 index 0000000..763c767 --- /dev/null +++ b/mkfont.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +int main( int argc, char **argv ) +{ + if ( argc < 3 ) return 1; + char tname[256] = {0}; + int cell = 0, x = 0, y = 0, w = 0, h = 0; + char cropargs[256] = {0}; + char cname[256] = {0}; + char* hargs[7] = + { + "convert", + tname, + "-crop", + cropargs, + "+repage", + cname, + 0 + }; + mkdir(argv[1],S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH); + while ( !feof(stdin) ) + { + int ret = scanf("0x%x: %s (%d,%d)-(%d,%d)\n",&cell,tname,&x,&y,&w,&h); + if ( ret != 6 ) + { + printf("%d != 6\n",ret); + return 2; + } + strcat(tname,".png"); + if ( (w <= 0) || (h <= 0) ) continue; + printf("%d, %s %+d%+d,%d,%d\n",cell,tname,x,y,w,h); + sprintf(cname,"%s/%s_%03d.png",argv[1],argv[2],cell); + sprintf(cropargs,"%dx%d%+d%+d",w,h,x,y); + int pid = fork(); + if ( !pid ) execvp(hargs[0],hargs); + else waitpid(pid,0,0); + } + return 0; +} diff --git a/udmfvis.c b/udmfvis.c new file mode 100644 index 0000000..0193c60 --- /dev/null +++ b/udmfvis.c @@ -0,0 +1,635 @@ +/* + 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; +}