/*************************************************************************** bmp2map.h - header file for bmp2map modules ***************************************************************************/ /*************************************************************************** bmp2map - A conversion utility to convert 256 color .BMPs into (v.05) Quake .MAPs. It's intended use is to quickly convert 2D floor/street plans into 3D maps. Pixel colors are used to create boxes textured as solid walls, doors, windows or windows filled with sky (for outside walls). Author - Jack Perdue (aka Silicon Slick) - si_slick@cy-net.net v.01 - May 10th, 1997 v.02 - Friday, June 13th, 1997 v.03 - July 5th, 1997 v.04 - July 11th, 1997 v.05 - July 22nd, 1997 v.06 - August 23rd, 1997 Compiler - Borland TurboC 2.0 for MS-DOS Copyright(C) 1997 - Silicon Slick's Software, Supplies and Support Services http://www2.cy-net.net/~si_slick si_slick@cy-net.net This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. "Quake" is copyright id Software (tabs=4) ***************************************************************************/ typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; #define TRUE 1 #define FALSE 0 #define CFGEXT ".cfg" #define BMPEXT ".bmp" #define MP1EXT ".mp1" #define MP2EXT ".mp2" #define MP3EXT ".mp3" #define MAPEXT ".map" #define ENTEXT ".en1" #define LOGEXT ".log" #define MUSTBETYPE (((WORD) 'M' << 8) | 'B') /* 1st 2bytes must=='BM' */ #define BI_RGB 0L /* uncompressed 256 color .BMPs only */ #define BI_RLE8 1L /* not supported */ #define BI_RLE4 2L /* not supported */ #define DEFQUNITSPERPEL 4 /* Quake units per pixel (box thickness) */ #define DEFOUTERQUNITS 8 /* thickness of ceiling/floor in Qunits */ #define DEFFLOOR -64 /* z for floor */ #define DEFAUTOLIGHT TRUE /* automatically add lights? */ #define DEFAUTOLIGHTQU 128 /* interval (in Quake units) for lights */ #define DEFAUTOSTART TRUE /* put info_player_start at center? */ #define DEFWORLDSPAWN TRUE /* enclose brushes in "worldspawn"? */ #define DEFWALLTEXT "wbrick1_5" /* texture name for walls/ceilings/floors */ #define DEFDOORTEXT "door02_3" /* "" doors */ #define DEFSKYTEXT "sky4" /* "" sky for windows to the outside */ #define DEFFLOORTEXT "city2_6" /* "" the floor */ #define DEFCEILINGTEXT "comp1_7" /* "" the ceiling */ #define DEFBARROWTEXT "barrow" /* "" arrows (red/blue for CTF) */ #define DEFRARROWTEXT "rarrow" #define DEFARROWQUNITS 32 /* size (square) of arrow texture */ #define DEFCARVETEXT "carve_me" #define DEFTRIGGERTEXT "trigger" #define DEFTELEPLATTEXT "tele_top" #define DEFTELEPLATQU 64 #define DEFWADNAME "sample.wad" /*default .WAD name */ #define DEFMAPNAME "Untitled" /* default .MAP name */ #define DEFCDTRACK 0 /* no default CD track */ #define DEFJOINENTMAP TRUE /* write ents in .MAP or leave in .EN1 */ #define DEFLIGHTLEVEL 200 /* default value for lights in level */ #define DEFSEALSIDES TRUE /* slap sides on it (in case not sealed) */ #define DEFSEALTOPBOTTOM TRUE /* write ceiling and floor brushes */ #define DEFMEMBRUSHES 64 /* default number of brushes to compress at a time - smaller = faster */ #define DEFDOMP3 TRUE #define CFGNAME "bmp2map.cfg" /* name of configuration file */ #define CFGQUNITSPERPEL "qunitsperpel" #define CFGARROWQU "arrowtexturequnits" #define CFGTELEPLATQU "teleplatformtexturequnits" #define CFGFLOOR "floor" #define CFGCEILING "ceiling" #define CFGOUTERQUNITS "outerqunits" #define CFGWINBOTTOM "windowbottom" #define CFGWINTOP "windowtop" #define CFGDOORTOP "doortop" #define CFGWALLVAL "wallcolor" /* see comments in #define DEF... section */ #define CFGWINVAL "wincolor" #define CFGDOORVAL "doorcolor" #define CFGDOORFRVAL "doorframecolor" #define CFGSKYWINVAL "skywincolor" #define CFGRNARROWVAL "rednortharrowcolor" #define CFGRSARROWVAL "redsoutharrowcolor" #define CFGREARROWVAL "redeastarrowcolor" #define CFGRWARROWVAL "redwestarrowcolor" #define CFGBNARROWVAL "bluenortharrowcolor" #define CFGBSARROWVAL "bluesoutharrowcolor" #define CFGBEARROWVAL "blueeastarrowcolor" #define CFGBWARROWVAL "bluewestarrowcolor" #define CFGTELEPLATVAL "teleplatformcolor" #define CFGCARVEFVAL "carvefloorcolor" #define CFGCARVECVAL "carveceilingcolor" #define CFGCARVEBOTHVAL "carvebothcolor" #define CFGSKYCEILINGVAL "skyceilingcolor" #define CFGTRIGGERVAL "triggercolor" #define CFGWALLTEXT "walltexture" #define CFGSKYTEXT "skytexture" #define CFGDOORTEXT "doortexture" #define CFGFLOORTEXT "floortexture" #define CFGCEILINGTEXT "ceilingtexture" #define CFGCARVETEXT "carvetexture" #define CFGTRIGGERTEXT "triggertexture" #define CFGRARROWTEXT "redarrowtexture" #define CFGBARROWTEXT "bluearrowtexture" #define CFGFRAMETEXT "frametexture" #define CFGTELEPLATTEXT "teleplatformtexture" #define CFGWADNAME "wadname" #define CFGMAPNAME "mapname" #define CFGAUTOLIGHT "autolight" #define CFGAUTOLIGHTQU "autolightqunits" #define CFGAUTOSTART "autostart" #define CFGWORLDSPAWN "worldspawn" #define CFGENTITY "entity" #define CFGJOINENTMAP "joinentmap" #define CFGLIGHTLEVEL "lightlevel" #define CFGSEALSIDES "sealsides" #define CFGSEALTOPBOTTOM "sealtopbottom" #define CFGCDTRACK "cdtrack" #define CFGMEMBRUSHES "membrushes" #define CFGDOMP3 "domp3" #define TEXTURENAMESIZE 20 #define MAPNAMESIZE 40 #define WADNAMESIZE 60 #define CFGLINESIZE 256 #define ENTITYSIZE 50 /* for entity strings in config file */ #define ENTLINESIZE 80 /* for entity strings in temp entity file */ #define FILENAMESIZE 128 /* for all file names */ #define EXTREME 8192 /* for min/max tracking */ #define ABSORBED (-1) /* brush has been combined with another */ #define WALLBRUSH 1 #define DOORBRUSH 2 #define SKYBRUSH 3 #define FLOORBRUSH 4 #define CEILINGBRUSH 5 #define CARVEBRUSH 6 #define TRIGGERBRUSH 7 #define FRAMEBRUSH 8 #define RNARROWBRUSH 9 /* Make sure RNARROWRBRUSH is listed first */ #define RSARROWBRUSH 10 #define REARROWBRUSH 11 #define RWARROWBRUSH 12 #define BNARROWBRUSH 13 #define BSARROWBRUSH 14 #define BEARROWBRUSH 15 #define BWARROWBRUSH 16 /* Make sure BWARROWBRUSH is last */ #define TELEPLATBRUSH 17 #define MINBRUSH 1 #define MAXBRUSH 17 /* change this when adding brush types */ #define SPARERAM 0x8000L /* leave some RAM (~32K) available */ typedef struct entitystruct { BYTE entval; /* pixel color for entity */ char entstring[ ENTITYSIZE]; /* classname for ent (eg. info_player_start) */ } ENTITY; typedef struct cfgstruct { int floor; int ceiling; int winbottom; int wintop; int doortop; int arrowbrushqunits; int teleplatbrushqunits; int autolightqunits; int lightlevel; int wallval; int winval; /* see comments in #define DEF... section */ int doorval; int doorframeval; int skywinval; int triggerval; int carvefloorval; int carveceilingval; int carvebothval; int skyceilingval; int rnaval; int rsaval; int reaval; int rwaval; int bnaval; int bsaval; int beaval; int bwaval; int teleplatval; int qunitsperpel; int outerqunits; int autolight; int sealsides; int sealtopbottom; int autostart; int worldspawn; int numents; int joinentmap; int cdtrack; int membrushes; int domp3; char walltext[ TEXTURENAMESIZE]; char doortext[ TEXTURENAMESIZE]; char skytext[ TEXTURENAMESIZE]; char floortext[ TEXTURENAMESIZE]; char ceilingtext[ TEXTURENAMESIZE]; char rarrowtext[ TEXTURENAMESIZE]; char barrowtext[ TEXTURENAMESIZE]; char carvetext[ TEXTURENAMESIZE]; char triggertext[ TEXTURENAMESIZE]; char frametext[ TEXTURENAMESIZE]; char teleplattext[ TEXTURENAMESIZE]; char mapname[ MAPNAMESIZE]; char wadname[ WADNAMESIZE]; ENTITY *ents; } CFG; typedef struct SIXSIDES { int x1, x2, y1, y2, z1, z2; /* Quake coordinates of brush */ int texture; /* wall, door, sky, etc. */ } BRUSH; typedef struct EXTREMES { int xbase, ybase; int minx, maxx; /* for tracking extreme values encountered */ int miny, maxy; int zmiddle; } QEXTREMES; typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; /* 14 bytes */ typedef struct tagBITMAPINFOHEADER { DWORD biSize; /* 15-18 */ DWORD biWidth; /* 19-22 */ DWORD biHeight; /* 23-26 */ WORD biPlanes; WORD biBitCount; DWORD biCompression; /* 31-34 */ DWORD biSizeImage; /* 35-38 */ DWORD biXPelsPerMeter; /* 39-42 */ DWORD biYPelsPerMeter; /* 43-46 */ DWORD biClrUsed; /* 47-50 */ DWORD biClrImportant; /* 51-54 */ } BITMAPINFOHEADER; typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; /*** Prototypes ***/ /*** in BMP2MAP.C */ /* used throughout */ void logprintf( const char *format, ...); /* used throughout */ void error( const char *format, ...); /* used throughout */ void myfprintf( FILE *fp, const char *format, ...); /* used throughout */ void printbrush( long seq, BRUSH huge *b); /* used by main() */ void initcfg( char *cfgname, char *bmpname, CFG *cfg); /*** in BMP2MP1.C */ /* used by boundmap() & pel2brush() */ void savebrush( FILE *fp, QEXTREMES *qe, long *brushcnt, int x1, int x2, int y1, int y2, int z1, int z2, int texture); /* used by bmp2mp1() */ void boundmap( CFG *cfg, FILE *mp1fp, QEXTREMES *qe, long *brushcnt); /* used by bmp2mp1() */ void pel2brush( CFG *cfg, FILE *fp, QEXTREMES *qe, long *brushcnt, int x, int y, int zbot, int ztop, int texture); /* used by bmp2mp1() */ void saveent( CFG *cfg, FILE *entfp, QEXTREMES *qe, int x, int y, char *entstring); /* used by bmp2mp1() */ void initbmp( FILE *bmpfp, BITMAPINFOHEADER *bmih); /* used by main() */ void bmp2mp1( char *inname, char *outname, CFG *cfg, QEXTREMES *qe, long *mp1brushcnt, long *mp3brushcnt); /*** in MP12MAP.C ***/ /* used by mp12mp2() */ long readbrushes( FILE *mp1fp, BRUSH huge *b, long inmem, long toread); /* used by mp12mp2() */ long compressxybrushes( BRUSH huge *b, long brushcnt); /* used by mp12mp2() */ long compresszbrushes( BRUSH huge *b, long brushcnt); /* used by main() */ void mp12mp2( char *filename, long mp1brushcnt, long mp3brushcnt, CFG *cfg); /* used mp22map() */ void writebrush( FILE *fp, int x1, int x2, int y1, int y2, int z1, int z2, int xshift, int yshift, int zrot, char *texture, char *frametexture); /* used by main() */ void mp22map( char *filename, CFG *cfg); /*** EOF ***/ /*************************************************************************** bmp2mp1.c - phase one of bmp2map Copyright(C) 1997 - Silicon Slick's Software, Supplies and Support Services http://www2.cy-net.net/~si_slick si_slick@cy-net.net This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. "Quake" is copyright id Software (tabs=4) ***************************************************************************/ #include #include #include #include "bmp2map.h" /*** savebrush() - save coorinates and texture for brush ***/ void savebrush( FILE *fp, QEXTREMES *qe, long *brushcnt, int x1, int x2, int y1, int y2, int z1, int z2, int texture) { BRUSH b; /* track extremes */ if( x1 < qe->minx) qe->minx = x1; if( x2 > qe->maxx) qe->maxx = x2; if( y1 < qe->miny) qe->miny = y1; if( y2 > qe->maxy) qe->maxy = y1; b.x1 = x1; b.x2 = x2; b.y1 = y1; b.y2 = y2; b.z1 = z1; b.z2 = z2; b.texture = texture; /* save the brush in the .MP1/2 file */ fwrite( &b, sizeof(BRUSH), 1, fp); ++(*brushcnt); return; } /*** savebrush() ***/ /*** boundmap() - write brushes for box around map (make map leak-proof) ***/ void boundmap( CFG *cfg, FILE *mp1fp, QEXTREMES *qe, long *brushcnt) { if( cfg->sealtopbottom) { /* write floor and ceiling brushes */ /* Z */ savebrush( mp1fp, qe, brushcnt, qe->minx, qe->maxx, qe->miny, qe->maxy, cfg->ceiling, cfg->ceiling + cfg->outerqunits, CEILINGBRUSH); savebrush( mp1fp, qe, brushcnt, qe->minx, qe->maxx, qe->miny, qe->maxy, cfg->floor - cfg->outerqunits, cfg->floor, FLOORBRUSH); } if( cfg->sealsides) { /* surround the whole dang thing (leakproof) */ /* X */ savebrush( mp1fp, qe, brushcnt, qe->minx - cfg->outerqunits, qe->minx, qe->miny, qe->maxy, cfg->floor - cfg->outerqunits, cfg->ceiling + cfg->outerqunits, SKYBRUSH); savebrush( mp1fp, qe, brushcnt, qe->maxx, qe->maxx + cfg->outerqunits, qe->miny, qe->maxy, cfg->floor - cfg->outerqunits, cfg->ceiling + cfg->outerqunits, SKYBRUSH); /* Y */ savebrush( mp1fp, qe, brushcnt, qe->minx, qe->maxx, qe->miny - cfg->outerqunits, qe->miny, cfg->floor - cfg->outerqunits, cfg->ceiling + cfg->outerqunits, SKYBRUSH); savebrush( mp1fp, qe, brushcnt, qe->minx, qe->maxx, qe->maxy, qe->maxy + cfg->outerqunits, cfg->floor - cfg->outerqunits, cfg->ceiling + cfg->outerqunits, SKYBRUSH); } return; } /*** boundmap() ***/ /*** pel2brush() - calculate Quake units given pixels location in .BMP ***/ void pel2brush( CFG *cfg, FILE *fp, QEXTREMES *qe, long *brushcnt, int x, int y, int zbot, int ztop, int texture) { int qx1, qx2, qy1, qy2; /* calculate quake coordinates from bitmap coordinates */ qx1 = qe->xbase + cfg->qunitsperpel * x; qx2 = qe->xbase + cfg->qunitsperpel * (x+1); qy1 = qe->ybase + cfg->qunitsperpel * y; qy2 = qe->ybase + cfg->qunitsperpel * (y+1); /* if brush is an arrow brush or a teleport platform brush then do a calculation to place at even (not odd) values */ if( (texture >= RNARROWBRUSH) && (texture <= BWARROWBRUSH)) { qx1 = qx1 - ( (cfg->arrowbrushqunits - cfg->qunitsperpel) / 4 ) * 2; qx2 = qx1 + cfg->arrowbrushqunits; qy1 = qy1 - ( (cfg->arrowbrushqunits - cfg->qunitsperpel) / 4 ) * 2; qy2 = qy1 + cfg->arrowbrushqunits; } else if( texture == TELEPLATBRUSH) { qx1 = qx1 - ( (cfg->teleplatbrushqunits - cfg->qunitsperpel) / 4 ) * 2; qx2 = qx1 + cfg->teleplatbrushqunits; qy1 = qy1 - ( (cfg->teleplatbrushqunits - cfg->qunitsperpel) / 4 ) * 2; qy2 = qy1 + cfg->teleplatbrushqunits; } /* save brush to .MP1/.MP2 file */ savebrush( fp, qe, brushcnt, qx1, qx2, qy1, qy2, zbot, ztop, texture); return; } /*** pel2brush() ***/ /*** saveent() - calculate Quake units and save entity string ***/ void saveent( CFG *cfg, FILE *entfp, QEXTREMES *qe, int x, int y, char *entstring) { int qx, qy; /* calculate quake coordinates from bitmap coordinates */ qx = qe->xbase + cfg->qunitsperpel * x + (cfg->qunitsperpel/2); qy = qe->ybase + cfg->qunitsperpel * y + (cfg->qunitsperpel/2); myfprintf( entfp, "{\n\"classname\" \"%s\"\n",entstring); if( ! strcmp( "light", entstring)) myfprintf( entfp, "\"light\" \"%d\"\n", cfg->lightlevel); myfprintf( entfp, "\"origin\" \"%d %d %d\"\n}\n", qx, qy, qe->zmiddle); return; } /*** saveent() ***/ /*** initbmp() - read bitmap header and palette ***/ void initbmp( FILE *bmpfp, BITMAPINFOHEADER *bmih) { BITMAPFILEHEADER bmfh; /* bit map file header */ /* read file header */ if( fread( &bmfh, sizeof( BITMAPFILEHEADER), 1, bmpfp) != 1) { logprintf("Error reading .BMP.\n"); exit(1); } /* check to see if it is a valid bitmap file */ if( bmfh.bfType != MUSTBETYPE) { logprintf(".BMP is not a valid bitmap.\n"); exit(1); } if( bmfh.bfReserved1 || bmfh.bfReserved2) { logprintf("Reserved fields are not 0?\n"); exit(1); } logprintf(".BMP is %ld bytes long. Image offset is %ld.\n", bmfh.bfSize, bmfh.bfOffBits); /* read info header */ if( fread( bmih, sizeof( BITMAPINFOHEADER), 1, bmpfp) != 1) { logprintf("Error reading .BMP\n"); exit(1); } if( bmih->biPlanes != 1) { logprintf(".BMP has incorrect value (%d) for biPlanes.\n", bmih->biPlanes); exit(1); } /* check for 256 colors */ if( bmih->biBitCount != 8) { logprintf("Not a 8-bit (256 color) .BMP\n"); exit(1); } /* make sure not .RLE */ if( bmih->biCompression != BI_RGB) { logprintf("Only uncompressed .BMPs allowed.\n"); exit(1); } logprintf("Header: %ld bytes Image: %ld bytes\n", bmih->biSize, bmih->biSizeImage); logprintf("%ld x %ld pixels w/ %ld colors, %ld of which are \"important\"\n", bmih->biWidth, bmih->biHeight, bmih->biClrUsed, bmih->biClrImportant); logprintf("Pixels/Meter = %ld x %ld\n", bmih->biXPelsPerMeter, bmih->biYPelsPerMeter); /* skip palette entries - fseek to offset (don't count on palette info!!! - Thanks, Dirk!) */ fseek( bmpfp, bmfh.bfOffBits, SEEK_SET); return; } /*** initbmp() ***/ /*** bmp2mp1() - read bitmap recording brushes encountered returns the number of brushes recorded ***/ void bmp2mp1( char *inname, char *outname, CFG *cfg, QEXTREMES *qe, long *mp1brushcnt, long *mp3brushcnt) { FILE *bmpfp, *mp1fp, *mp3fp, *entfp; int x, y, i; BYTE pel; WORD xWidth; char bmpfile[FILENAMESIZE], mp1file[FILENAMESIZE], entfile[FILENAMESIZE], mp3file[FILENAMESIZE]; BITMAPINFOHEADER bmih; /* bit map information header */ int entcnt = 0; /* "" entities */ int lightcnt = 0; /* "" lights (auto) */ /* initialize brush counters */ *mp1brushcnt = *mp3brushcnt = 0L; /* prepare file names */ strcpy( bmpfile, inname); strcat( bmpfile, BMPEXT); strcpy( mp1file, outname); strcat( mp1file, MP1EXT); strcpy( mp3file, outname); strcat( mp3file, MP3EXT); strcpy( entfile, outname); strcat( entfile, ENTEXT); /* open the file */ logprintf("Reading file %s\n", bmpfile); if ((bmpfp = fopen(bmpfile,"rb")) == NULL) { logprintf("Error opening file %s.\n",bmpfile); exit(1); } /* read bitmap information header */ initbmp( bmpfp, &bmih); /* open brush output files */ if( ( mp1fp = fopen( mp1file, "wb")) == NULL) error("Unable to open file %s for output.\n", mp1file); if( ( mp3fp = fopen( mp3file, "wb")) == NULL) error("Unable to open file %s for output.\n", mp3file); /* open entity output file */ if( ( entfp = fopen( entfile, "w")) == NULL) { logprintf("Unable to open file %s for output.\n", entfile); exit(1); } /* calculate base quake coordinates so map is centered at origin */ qe->xbase = ((int) bmih.biWidth/2) * cfg->qunitsperpel; qe->xbase += cfg->qunitsperpel - (qe->xbase%cfg->qunitsperpel); qe->xbase = - (qe->xbase); qe->ybase = ((int) bmih.biHeight/2) * cfg->qunitsperpel; qe->ybase += cfg->qunitsperpel - (qe->ybase%cfg->qunitsperpel); qe->ybase = - (qe->ybase); qe->minx = EXTREME; /* Smallest X (in Quake units) encountered */ qe->maxx = -EXTREME; /* Largest "" */ qe->miny = EXTREME; /* Like above for Y */ qe->maxy = -EXTREME; /* "" */ /* calculate number of bytes for each line (padding to `long's) */ xWidth = bmih.biWidth; if( xWidth % 4) xWidth += 4 - (xWidth%4); for( y = 0; y < bmih.biHeight; y++) { logprintf("."); /* progress indicator - one per line read */ for( x = 0; x < xWidth; x++) { pel = (BYTE) fgetc(bmpfp); /* if not padding */ if( x < bmih.biWidth) { /* write brush depending upon pixels color */ if( pel == cfg->wallval) /* wall */ { /* write walls in parts - in hopes of good brush reduction with windows/doors - go with FRAMEBRUSH since top/bottom of wall is never visible (?) - also, write FRAMEBRUSHes from top to bottom in hopes of getting better brush compression */ /* if door top is higher than window top, write these three */ if( cfg->doortop > cfg->wintop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->doortop, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->winbottom, cfg->wintop, FRAMEBRUSH); } /* else if window top is higher than door top, write these three */ else if( cfg->doortop < cfg->wintop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->wintop, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->winbottom, cfg->doortop, FRAMEBRUSH); } /* otherwise, they're equal, write these two */ else { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->winbottom, cfg->wintop, FRAMEBRUSH); } /* write floor to window bottom */ pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->floor, cfg->winbottom, FRAMEBRUSH); } else if( pel == cfg->winval) /* window */ { if( cfg->doortop > cfg->wintop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->doortop, FRAMEBRUSH); } else { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->ceiling, FRAMEBRUSH); } pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->floor, cfg->winbottom, FRAMEBRUSH); } else if( pel == cfg->doorval) /* door */ { if( cfg->wintop > cfg->doortop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->wintop, FRAMEBRUSH); } else { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); } pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->floor, cfg->doortop, DOORBRUSH); } else if( pel == cfg->doorframeval) /* door frame */ { if( cfg->wintop > cfg->doortop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->wintop, FRAMEBRUSH); } else { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); } } else if( pel == cfg->skywinval) /* skyed window */ { if( cfg->doortop > cfg->wintop) { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->doortop, cfg->ceiling, FRAMEBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->doortop, FRAMEBRUSH); } else { pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->wintop, cfg->ceiling, FRAMEBRUSH); } pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->winbottom, cfg->wintop, SKYBRUSH); pel2brush( cfg, mp3fp, qe, mp3brushcnt, x, y, cfg->floor, cfg->winbottom, FRAMEBRUSH); } else if( pel == cfg->carvefloorval) /* carve floor */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->floor-cfg->outerqunits-1, cfg->floor+1, CARVEBRUSH); } else if( pel == cfg->carveceilingval) /* carve ceiling */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling+cfg->outerqunits+1, CARVEBRUSH); } else if( pel == cfg->carvebothval) /* carve both */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->floor-cfg->outerqunits-1, cfg->floor+1, CARVEBRUSH); pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling+cfg->outerqunits+1, CARVEBRUSH); } else if( pel == cfg->skyceilingval) /* skyed ceiling */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling+cfg->outerqunits+1, SKYBRUSH); } else if( pel == cfg->triggerval) /* trigger */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->floor + cfg->outerqunits, cfg->ceiling - cfg->outerqunits, TRIGGERBRUSH); } else if( pel == cfg->rnaval) /* red north arrow */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, RNARROWBRUSH); } else if( pel == cfg->rsaval) /* red south */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, RSARROWBRUSH); } else if( pel == cfg->reaval) /* red east */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, REARROWBRUSH); } else if( pel == cfg->rwaval) /* etc. */ { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, RWARROWBRUSH); } else if( pel == cfg->bnaval) { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, BNARROWBRUSH); } else if( pel == cfg->bsaval) { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, BSARROWBRUSH); } else if( pel == cfg->beaval) { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, BEARROWBRUSH); } else if( pel == cfg->bwaval) { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->ceiling-1, cfg->ceiling, BWARROWBRUSH); } else if( pel == cfg->teleplatval) { pel2brush( cfg, mp1fp, qe, mp1brushcnt, x, y, cfg->floor, cfg->floor+4, TELEPLATBRUSH); } /* colors can be shared between brush types and entities. eg. color XXX could be a teleport platform and an info_player_deathmatch */ /* search entity list for a match */ for( i = 0; i < cfg->numents; i++) { if( pel == cfg->ents[i].entval) { entcnt++; saveent( cfg, entfp, qe, x, y, cfg->ents[i].entstring); break; } } } } } /* put start at origin */ /* not 0,0 because it may cause problems (??? empty hulls ???) */ if( cfg->autostart) { myfprintf( entfp, "{\n\"classname\" \"info_player_start\"\n\"origin\" \"2 2 %d\"\n}\n", qe->zmiddle); entcnt++; } /* if autolight write light entities using interval specified */ if( cfg->autolight) { for( x = qe->minx+cfg->autolightqunits; x < qe->maxx; x += cfg->autolightqunits) { for( y = qe->miny+cfg->autolightqunits; y < qe->maxy; y+=cfg->autolightqunits) { myfprintf( entfp, "{\n\"classname\" \"light\"\n"); myfprintf( entfp, "\"light\" \"%d\"\n", cfg->lightlevel); myfprintf( entfp,"\"origin\" \"%d %d %d\"\n}\n", x,y,qe->zmiddle); lightcnt++; entcnt++; } } } /* show extremes and close files */ logprintf("\nX: %d to %d by Y: %d to %d\n", qe->minx, qe->maxx, qe->miny, qe->maxy); fclose( bmpfp); fclose( entfp); fclose( mp3fp); boundmap( cfg, mp1fp, qe, mp1brushcnt); /* seal map in a box (leak-proof) */ fclose( mp1fp); logprintf("%ld brushes written to %s.\n", *mp1brushcnt, mp1file); logprintf("%ld brushes written to %s.\n", *mp3brushcnt, mp3file); logprintf("%d entities written to %s.\n", entcnt, entfile); } /*** bmp2mp1() ***/ /*** EOF ***/ /*************************************************************************** mp12map.c - phases two and three of BMP2MAP Copyright(C) 1997 - Silicon Slick's Software, Supplies and Support Services http://www2.cy-net.net/~si_slick si_slick@cy-net.net This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. "Quake" is copyright id Software ***************************************************************************/ #include #include #include #include #include "bmp2map.h" /*** readbrushes() - read brushes until allocated memory is full or all brushes are read ***/ long readbrushes( FILE *mp1fp, BRUSH huge *b, long inmem, long toread) { long i; /* load brushes into memory */ for( i = 0L; i < toread; i++) { if( fread( (void *)(b+(inmem)), sizeof(BRUSH), 1, mp1fp) !=1) error("Error reading .MP1/3 file\n"); ++inmem; } return(inmem); } /*** readbrushes() ***/ /*** compressbrushes() - join similarly aligned brushes ***/ long compressbrushes( BRUSH huge *b, long brushcnt) { long newcnt; register long i, j; register int itext; /* for each brush in memory up to the last one... */ for( i = 0L; i < (brushcnt-1); i++) { itext = b[i].texture; /* remember current "texture" (actually brush type) */ if( itext > 0) /* if brush not combined already */ { for( j = i+1; j < brushcnt; j++) { if( itext == b[j].texture) { if( ( b[i].z1 == b[j].z1) && ( b[i].z2 == b[j].z2)) { if( (b[i].y1 == b[j].y1) && (b[i].y2 == b[j].y2)) { /* if common x's then combine */ if( b[i].x1 == b[j].x2) { b[i].x1 = b[j].x1; b[j].texture = ABSORBED; } else if( b[i].x2 == b[j].x1) { b[i].x2 = b[j].x2; b[j].texture = ABSORBED; } } else if( (b[i].x1 == b[j].x1) && (b[i].x2 == b[j].x2)) { /* if common y's then combine */ if( b[i].y1 == b[j].y2) { b[i].y1 = b[j].y1; b[j].texture = ABSORBED; } else if( b[i].y2 == b[j].y1) { b[i].y2 = b[j].y2; b[j].texture = ABSORBED; } } } /* end if z's line up */ else { /* does a z1 equal a z2, if so, are other conditions met so that the brush can be combined in the z direction */ /* if common z's then test other conditions */ if( b[i].z1 == b[j].z2) { if( ( b[i].x1 == b[j].x1) && ( b[i].x2 == b[j].x2)) { if( (b[i].y1 == b[j].y1) && (b[i].y2 == b[j].y2)) { b[i].z1 = b[j].z1; b[j].texture = ABSORBED; } } } else if( b[i].z2 == b[j].z1) { if( ( b[i].x1 == b[j].x1) && ( b[i].x2 == b[j].x2)) { if( (b[i].y1 == b[j].y1) && (b[i].y2 == b[j].y2)) { b[i].z2 = b[j].z2; b[j].texture = ABSORBED; } } } } } /* end if texture's match */ } } } /* compress and count */ newcnt = 0L; for( i = 0L; i < brushcnt; i++) { if( b[i].texture != ABSORBED) { if( i != newcnt) { memcpy( ( void *)( b + newcnt), ( void *) ( b + i), sizeof(BRUSH)); } newcnt++; } } return( newcnt); } /*** compressbrushes() ***/ /*** readcompresswrite() - read brushes from .MP1/MP3, compress, and then write to .MP2 file for later conversion to .MAP ***/ void readcompresswrite( FILE *infp, FILE *outfp, long brushcnt, CFG *cfg) { BRUSH huge *b; /* must be HUGE on 8088 */ long mostmem; long brushmem; /* number of brushes that will fit into memory */ long inmem; /* number of brushes in memory */ long brushesread; /* number of brushes read thus far */ long toread; /* number of brushes to read on this pass */ long newcnt; long i; /* let user know of memory requirements */ logprintf("%ld brushes - brush structure is %d bytes\n", brushcnt, sizeof(BRUSH)); logprintf("%ld bytes of available RAM.\n", i = farcoreleft()); /* calculate number of brushes that will fit into RAM */ mostmem = brushmem = ( i / sizeof( BRUSH)) - ( SPARERAM / sizeof(BRUSH)); if( brushcnt < brushmem) brushmem = brushcnt; /* attempt some speed on n-squared problem ??? */ if( brushmem > cfg->membrushes) brushmem = cfg->membrushes; logprintf("Working with %ld brushes at a time\n", brushmem); /* allocate memory from far heap */ if( (b = (BRUSH huge *) farmalloc( (long) brushmem*sizeof(BRUSH))) == NULL) { logprintf("Unable to allocate memory for %ld brushes.\n", brushmem); error("%ld bytes of available RAM.\n", farcoreleft()); } logprintf("%ld bytes of available RAM.\n", farcoreleft()); /* read some brushes, compress them, then read some more... repeat until all brushes have been read/compressed */ brushesread = 0; inmem = 0; while( brushesread < brushcnt) { /* calculate number of brushes to read -- it's the lesser of a) the number of brushes left in the file, or, b) the number of brushes that will fit into memory */ toread = brushcnt - brushesread; if( toread > ( brushmem - inmem)) toread = brushmem - inmem; /* read some brushes */ inmem = readbrushes( infp, b, inmem, toread); brushesread += toread; /* compress brushes read thus far */ inmem = compressbrushes( b, inmem); /* if we approach 50% capacity, attempt to get more memory */ if( inmem > (brushmem/2)) { logprintf("Brushes: %ld / Capacity: %ld > 50\%", inmem, brushmem); brushmem = brushmem*2; /* this seems right??? */ if( brushmem > (mostmem-brushmem)) brushmem = mostmem-brushmem; logprintf(" - increasing capacity to %ld\n", brushmem); b = (BRUSH huge *) farrealloc( (void far *) b, (unsigned long) brushmem*sizeof(BRUSH)); if( ! b) error("Failed to increase capacity of brushes in memory.\n\n"); else logprintf("%ld brushes left to read.\n", brushcnt-brushesread); } } /* finish compression */ logprintf("Finishing brush compression.\n"); while( (newcnt=compressbrushes( b,inmem)) != inmem) inmem = newcnt; /* remember new brush count */ brushcnt = inmem; logprintf("Writing %ld brushes to .MP2 file.\n", brushcnt); for( i = 0L; i < brushcnt; i++) { if( b[i].texture != ABSORBED) { fwrite( ( void *) ( b + i), sizeof(BRUSH), 1, outfp); } } farfree( ( void *) b); return; } /*** readcompresswrite() ***/ /*** mp12mp2() - join similarly aligned brushes and write to new temp file ***/ void mp12mp2( char *filename, long mp1brushcnt, long mp3brushcnt, CFG *cfg) { FILE *mp1fp, *mp2fp, *mp3fp; char mp1file[FILENAMESIZE], mp2file[FILENAMESIZE], mp3file[ FILENAMESIZE]; /* prepare filenames */ strcpy( mp1file, filename); strcat( mp1file, MP1EXT); strcpy( mp2file, filename); strcat( mp2file, MP2EXT); strcpy( mp3file, filename); strcat( mp3file, MP3EXT); /* open files */ if( (mp2fp = fopen(mp2file,"wb")) == NULL) error("Unable to open file %s for output.\n", mp2file); logprintf("Reading file %s\n", mp1file); if ((mp1fp = fopen(mp1file,"rb")) == NULL) error("Error opening file %s.\n",mp1file); readcompresswrite( mp1fp, mp2fp, mp1brushcnt, cfg); if( fclose( mp1fp) == EOF) error( "Error closing .MP1 file.\n"); logprintf("Deleting temporary file %s.\n", mp1file); remove( mp1file); /* delete temp file */ if( cfg->domp3) { logprintf("Reading file %s\n", mp3file); if ((mp3fp = fopen(mp3file,"rb")) == NULL) error("Error opening file %s.\n",mp3file); readcompresswrite( mp3fp, mp2fp, mp3brushcnt, cfg); if( fclose( mp3fp) == EOF) error( "Error closing .MP3 file.\n"); } logprintf("Deleting temporary file %s.\n", mp3file); remove( mp3file); /* delete temp file */ if( fclose( mp2fp) == EOF) error( "Error closing .MP2 file\n"); return; } /*** mp12mp2() ***/ /*** writebrush() - given coorinates bounding a box, write a brush for the box using texture specified. ***/ void writebrush( FILE *fp, int x1, int x2, int y1, int y2, int z1, int z2, int xshift, int yshift, int zrot, char *texture, char *frametexture) { /* begin brush */ myfprintf( fp, "{\n"); if( ! frametexture) /* all six sides are same texture */ { /* top */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x1, y2, z2, x2, y2, z2, x2, y1, z2, strupr( texture), zrot); /* bottom */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x1, y1, z1, x2, y1, z1, x2, y2, z1, strupr( texture), zrot); } else /* top/bottom texture is different from sides */ { /* top */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s %d %d %d 1.000000 1.000000\n", x1, y2, z2, x2, y2, z2, x2, y1, z2, strupr( frametexture), xshift, yshift, zrot); /* bottom */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s %d %d %d 1.000000 1.000000\n", x1, y1, z1, x2, y1, z1, x2, y2, z1, strupr( frametexture), xshift, yshift, zrot); } /* left */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x1, y2, z2, x1, y1, z2, x1, y1, z1, strupr( texture), zrot); /* right */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x2, y2, z1, x2, y1, z1, x2, y1, z2, strupr( texture), zrot); /* back */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x2, y2, z2, x1, y2, z2, x1, y2, z1, strupr( texture), zrot); /* front */ myfprintf( fp, "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s 0 0 %d 1.000000 1.000000\n", x2, y1, z1, x1, y1, z1, x1, y1, z2, strupr( texture), zrot); /* finish brush */ myfprintf( fp, "}\n"); return; } /*** writebrush() ***/ /*** mp22map() - convert compressed brushes into Quake .MAP file ***/ void mp22map( char *filename, CFG *cfg) { FILE *mp2fp, *mapfp, *entfp; long brushcnt; int xshift, yshift; char mp2file[FILENAMESIZE], mapfile[FILENAMESIZE], entfile[FILENAMESIZE]; char entline[ENTLINESIZE]; BRUSH b; strcpy( mp2file, filename); strcat( mp2file, MP2EXT); strcpy( entfile, filename); strcat( entfile, ENTEXT); strcpy( mapfile, filename); strcat( mapfile, MAPEXT); /* open the file */ logprintf("Reading file %s\n", mp2file); if ((mp2fp = fopen(mp2file,"rb")) == NULL) error("Error opening file %s.\n",mp2file); /* open output file */ if( ( mapfp = fopen( mapfile, "w")) == NULL) error("Unable to open file %s for output.\n", mapfile); /* prepare to write map */ if( cfg->worldspawn) { #ifdef SAMPLE { "classname" "worldspawn" "worldtype" "0" "sounds" "13" "message" "Map Name" "wad" "hrbbctf.wad" [brushes here] } [entities here] #endif myfprintf( mapfp, "{\n\"classname\" \"worldspawn\"\n"); myfprintf( mapfp, "\"worldtype\" \"0\"\n"); if( strlen( cfg->mapname)) myfprintf( mapfp, "\"message\" \"%s\"\n", cfg->mapname); if( strlen( cfg->wadname)) myfprintf( mapfp, "\"wad\" \"%s\"\n", cfg->wadname); if( cfg->cdtrack) myfprintf( mapfp, "\"sounds\" \"%d\"\n",cfg->cdtrack); } brushcnt = 0L; while( fread( &b, sizeof(BRUSH),1,mp2fp) == 1) { /* if arrow brush calculate x/yshift to center arrow */ if( (b.texture >= RNARROWBRUSH) && (b.texture <= BWARROWBRUSH)) { xshift = b.x1%cfg->arrowbrushqunits; yshift = b.y1%cfg->arrowbrushqunits; } /* if platform brush calculate x/yshift to center arrow */ if( b.texture == TELEPLATBRUSH) { xshift = b.x1%cfg->teleplatbrushqunits; yshift = b.y1%cfg->teleplatbrushqunits; } switch( b.texture) { case WALLBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->walltext, NULL); break; case DOORBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->doortext, NULL); break; case SKYBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->skytext, NULL); break; case FLOORBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->walltext, cfg->floortext); break; case CEILINGBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->walltext, cfg->ceilingtext); break; case CARVEBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->carvetext, NULL); break; case TRIGGERBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->triggertext, NULL); break; case FRAMEBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, 0, 0, 0, cfg->walltext,cfg->frametext); break; /*** NOTE: x/yshifts below vary with rotation! ***/ case RNARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, -yshift, -xshift, 90, cfg->ceilingtext, cfg->rarrowtext); break; case RSARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, yshift, xshift, 270, cfg->ceilingtext, cfg->rarrowtext); break; case REARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, -xshift, yshift, 0, cfg->ceilingtext, cfg->rarrowtext); break; case RWARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, xshift, -yshift, 180, cfg->ceilingtext, cfg->rarrowtext); break; case BNARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, -yshift, -xshift, 90, cfg->ceilingtext, cfg->barrowtext); break; case BSARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, yshift, xshift, 270, cfg->ceilingtext, cfg->barrowtext); break; case BEARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, -xshift, yshift, 0, cfg->ceilingtext, cfg->barrowtext); break; case BWARROWBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, xshift, -yshift, 180, cfg->ceilingtext, cfg->barrowtext); break; case TELEPLATBRUSH: writebrush( mapfp, b.x1, b.x2, b.y1, b.y2, b.z1, b.z2, -xshift, yshift, 0, cfg->floortext, cfg->teleplattext); break; default: logprintf("Unknown texture (%d) encountered... brush ignored\n", b.texture); brushcnt--; } brushcnt++; } if( fclose( mp2fp) == EOF) error( "Error closing .MP2 file\n"); logprintf("Deleting temporary file %s.\n", mp2file); remove( mp2file); /* delete temporary file */ /* finish brush section */ if( cfg->worldspawn) myfprintf( mapfp, "}\n"); /* copy entities from first pass into map */ if( cfg->joinentmap) { /* open the file */ logprintf("Reading entities from %s and putting in %s\n", entfile, mapfile); if ((entfp = fopen(entfile,"r")) == NULL) error("Error opening file %s.\n",entfile); while( fgets( entline,ENTLINESIZE,entfp)) { fputs( entline, mapfp); } if( fclose( entfp) == EOF) error( "Error closing .EN1 file\n"); } if( fclose( mapfp) == EOF) error( "Error closing .MAP file\n"); logprintf("\nDone... %ld brushes written to %s.\n\n", brushcnt, mapfile); return; } /*** mp22map() ***/ /*** EOF ***/ /*************************************************************************** bmp2map.c - A conversion utility to convert 256 color .BMPs into (v.06) Quake .MAPs. See bmp2map.h for program information. Copyright(C) 1997 - Silicon Slick's Software, Supplies and Support Services http://www2.cy-net.net/~si_slick si_slick@cy-net.net This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. "Quake" is copyright id Software (tabs=4) ***************************************************************************/ char *pgmname="BMP2MAP (v.06) - converts 256 color .BMPs into Quake(tm) .MAPs"; char *copyright1="Copyright(C) 1997 - Silicon Slick's Software, Supplies and Support Services"; char *copyright2="BMP2MAP comes with ABSOLUTELY NO WARRANTY. See GPL.TXT for details"; #include #include #include /* for pause during configuration initialization */ #include #include #include /* for getch() */ #include #include #include "bmp2map.h" FILE *logfp = NULL; /* dreaded global */ /*** logprintf() - echo output to console and logfile ***/ void logprintf( const char *format, ...) { va_list arg_list; va_start( arg_list, format); vprintf( format, arg_list); if( logfp) { if( vfprintf( logfp, format, arg_list) == EOF) error( "Error writing to log file.\n"); if( fflush( logfp) == EOF) error( "Error flushing log file.\n"); } va_end( arg_list); return; } /*** error() - print error message then quit ***/ void error( const char *format, ...) { va_list arg_list; va_start( arg_list, format); vprintf( format, arg_list); if( logfp) { if( vfprintf( logfp, format, arg_list) == EOF) error( "Error writing to log file.\n"); if( fflush( logfp) == EOF) error( "Error flushing log file.\n"); } va_end( arg_list); exit( 1); } /*** error() ***/ /*** myfprintf() - fprintf with check for write error ***/ void myfprintf( FILE *fp, const char *format, ...) { va_list arg_list; va_start( arg_list, format); if( vfprintf( fp, format, arg_list) == EOF) error( "Error writing file\n"); va_end( arg_list); return; } /*** myfprintf() ***/ /*** printbrush() - for debugging purposes ***/ void printbrush( long seq, BRUSH huge *b) { logprintf("%ld - X: %d to %d Y: %d to %d Z: %d to %d Texture: %d\n", seq, b->x1, b->x2, b->y1, b->y2, b->z1, b->z2, b->texture); return; } /*** printbrush() ***/ /*** initcfg() - initialize configuration ***/ void initcfg( char *cfgname, char *bmpname, CFG *cfg) { FILE *fp; char line[ CFGLINESIZE], *opt, *val, *s; int ival, i; BYTE used[256]; time_t startpause, now; /* time pause started and now */ for( i = 0; i < 256; i++) used[i] = FALSE; cfg->wallval = -1; cfg->winval = -1; cfg->doorval = -1; cfg->doorframeval = -1; cfg->skywinval = -1; cfg->carvefloorval = -1; cfg->carveceilingval = -1; cfg->carvebothval = -1; cfg->triggerval = -1; cfg->skyceilingval = -1; cfg->rnaval = -1; cfg->rsaval = -1; cfg->reaval = -1; cfg->rwaval = -1; cfg->bnaval = -1; cfg->bsaval = -1; cfg->beaval = -1; cfg->bwaval = -1; cfg->teleplatval = -1; cfg->floor = EXTREME; cfg->ceiling = EXTREME; cfg->winbottom = EXTREME; cfg->wintop = EXTREME; cfg->doortop = EXTREME; cfg->qunitsperpel = DEFQUNITSPERPEL; cfg->outerqunits = DEFOUTERQUNITS; cfg->autolight = DEFAUTOLIGHT; cfg->autolightqunits = DEFAUTOLIGHTQU; cfg->autostart = DEFAUTOSTART; cfg->sealsides = DEFSEALSIDES; cfg->sealtopbottom=DEFSEALTOPBOTTOM; cfg->worldspawn = DEFWORLDSPAWN; cfg->joinentmap = DEFJOINENTMAP; cfg->lightlevel = DEFLIGHTLEVEL; cfg->arrowbrushqunits = DEFARROWQUNITS; cfg->teleplatbrushqunits = DEFTELEPLATQU; cfg->cdtrack = DEFCDTRACK; cfg->membrushes = DEFMEMBRUSHES; strcpy( cfg->walltext, DEFWALLTEXT); strcpy( cfg->frametext, cfg->walltext); strcpy( cfg->doortext, DEFDOORTEXT); strcpy( cfg->skytext, DEFSKYTEXT); strcpy( cfg->floortext, DEFFLOORTEXT); strcpy( cfg->ceilingtext, DEFCEILINGTEXT); strcpy( cfg->rarrowtext, DEFRARROWTEXT); strcpy( cfg->barrowtext, DEFBARROWTEXT); strcpy( cfg->carvetext, DEFCARVETEXT); strcpy( cfg->triggertext, DEFTRIGGERTEXT); strcpy( cfg->teleplattext, DEFTELEPLATTEXT); strcpy( cfg->wadname, DEFWADNAME); strcpy( cfg->mapname, DEFMAPNAME); cfg->numents=0; cfg->ents = NULL; strcat( cfgname, CFGEXT); logprintf("Initializing configuration from %s.\n", cfgname); if( ( fp = fopen( cfgname, "r")) == NULL) { logprintf("Unable to open %s for configuration info... trying %s%s.\n", cfgname, bmpname, CFGEXT); strcpy( cfgname, bmpname); strcat( cfgname, CFGEXT); if( ( fp = fopen( cfgname, "r")) == NULL) { logprintf("Unable to open %s for configuration info... trying %s.\n", cfgname, CFGNAME); strcpy( cfgname, CFGNAME); if( ( fp = fopen( cfgname, "r")) == NULL) error("Unable to open %s for configuration info... exiting.\n", cfgname); } } logprintf("\n"); if( fp) { while( fgets( line, CFGLINESIZE, fp)) { #ifdef DEBUG puts(line); #endif opt = strtok( line, "="); val = strtok( NULL, ",\n"); ival = atoi( val); #ifdef DEBUG logprintf("Option: %s (%02x) Value: %s (%3d)\n", opt, *opt, val, ival); #endif if( *opt == ';') /* ignore comments */; else if( ! strcmp( opt, CFGQUNITSPERPEL)) cfg->qunitsperpel = ival; else if( ! strcmp( opt, CFGOUTERQUNITS)) cfg->outerqunits = ival; else if( ! strcmp( opt, CFGARROWQU)) cfg->arrowbrushqunits = ival; else if( ! strcmp( opt, CFGTELEPLATQU)) cfg->teleplatbrushqunits = ival; else if( ! strcmp( opt, CFGFLOOR)) cfg->floor = ival; else if( ! strcmp( opt, CFGCEILING)) cfg->ceiling = ival; else if( ! strcmp( opt, CFGWINBOTTOM)) cfg->winbottom = ival; else if( ! strcmp( opt, CFGWINTOP)) cfg->wintop = ival; else if( ! strcmp( opt, CFGDOORTOP)) cfg->doortop = ival; else if( ! strcmp( opt, CFGWALLVAL)) cfg->wallval = ival; else if( ! strcmp( opt, CFGWINVAL)) cfg->winval = ival; else if( ! strcmp( opt, CFGDOORVAL)) cfg->doorval = ival; else if( ! strcmp( opt, CFGDOORFRVAL)) cfg->doorframeval = ival; else if( ! strcmp( opt, CFGSKYWINVAL)) cfg->skywinval = ival; else if( ! strcmp( opt, CFGRNARROWVAL)) cfg->rnaval = ival; else if( ! strcmp( opt, CFGRSARROWVAL)) cfg->rsaval = ival; else if( ! strcmp( opt, CFGREARROWVAL)) cfg->reaval = ival; else if( ! strcmp( opt, CFGRWARROWVAL)) cfg->rwaval = ival; else if( ! strcmp( opt, CFGBNARROWVAL)) cfg->bnaval = ival; else if( ! strcmp( opt, CFGBSARROWVAL)) cfg->bsaval = ival; else if( ! strcmp( opt, CFGBEARROWVAL)) cfg->beaval = ival; else if( ! strcmp( opt, CFGBWARROWVAL)) cfg->bwaval = ival; else if( ! strcmp( opt, CFGTELEPLATVAL)) cfg->teleplatval = ival; else if( ! strcmp( opt, CFGCARVEFVAL)) cfg->carvefloorval = ival; else if( ! strcmp( opt, CFGCARVECVAL)) cfg->carveceilingval = ival; else if( ! strcmp( opt, CFGCARVEBOTHVAL)) cfg->carvebothval = ival; else if( ! strcmp( opt, CFGSKYCEILINGVAL)) cfg->skyceilingval = ival; else if( ! strcmp( opt, CFGTRIGGERVAL)) cfg->triggerval = ival; else if( ! strcmp( opt, CFGAUTOLIGHT)) cfg->autolight = ival; else if( ! strcmp( opt, CFGSEALSIDES)) cfg->sealsides = ival; else if( ! strcmp( opt, CFGSEALTOPBOTTOM)) cfg->sealtopbottom = ival; else if( ! strcmp( opt, CFGAUTOLIGHTQU)) cfg->autolightqunits = ival; else if( ! strcmp( opt, CFGAUTOSTART)) cfg->autostart = ival; else if( ! strcmp( opt, CFGWORLDSPAWN)) cfg->worldspawn = ival; else if( ! strcmp( opt, CFGJOINENTMAP)) cfg->joinentmap = ival; else if( ! strcmp( opt, CFGLIGHTLEVEL)) cfg->lightlevel = ival; else if( ! strcmp( opt, CFGCDTRACK)) cfg->cdtrack = ival; else if( ! strcmp( opt, CFGMEMBRUSHES)) cfg->membrushes = ival; else if( ! strcmp( opt, CFGDOMP3)) cfg->domp3 = ival; else if( ! strcmp( opt, CFGWALLTEXT)) strncpy( cfg->walltext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGSKYTEXT)) strncpy( cfg->skytext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGDOORTEXT)) strncpy( cfg->doortext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGFLOORTEXT)) strncpy( cfg->floortext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGCEILINGTEXT)) strncpy( cfg->ceilingtext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGCARVETEXT)) strncpy( cfg->carvetext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGTRIGGERTEXT)) strncpy( cfg->triggertext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGRARROWTEXT)) strncpy( cfg->rarrowtext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGBARROWTEXT)) strncpy( cfg->barrowtext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGFRAMETEXT)) strncpy( cfg->frametext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGTELEPLATTEXT)) strncpy( cfg->teleplattext, val, TEXTURENAMESIZE); else if( ! strcmp( opt, CFGWADNAME)) strncpy( cfg->wadname, val, WADNAMESIZE); else if( ! strcmp( opt, CFGMAPNAME)) { strncpy( cfg->mapname, val, MAPNAMESIZE); /* change underscores to spaces */ while( ( s = (char *) strchr( cfg->mapname, '_')) != NULL) *s = ' '; } else if( ! strcmp( opt, CFGENTITY)) { /* record pixel number and text for entity */ if( ! cfg->numents) { cfg->ents = malloc( sizeof( ENTITY)); cfg->numents = 1; } else { ++(cfg->numents); cfg->ents = realloc( cfg->ents, sizeof(ENTITY) * cfg->numents); } cfg->ents[cfg->numents-1].entval = ival; strncpy(cfg->ents[cfg->numents-1].entstring, strtok(NULL," \n"), ENTITYSIZE); } else { logprintf("Unknown option ignored-> %s=%s\n", opt, val); logprintf("\nPress any key to continue...\n"); getch(); } } fclose(fp); } if( cfg->floor == EXTREME) cfg->floor = DEFFLOOR; if( cfg->ceiling == EXTREME) cfg->ceiling = cfg->floor + 112; if( cfg->winbottom == EXTREME) cfg->winbottom = cfg->floor+32; if( cfg->wintop == EXTREME) cfg->wintop = cfg->winbottom+60; if( cfg->doortop == EXTREME) cfg->doortop = cfg->floor + 86; logprintf(" Auto lighting: %d\n",cfg->autolight); logprintf(" Auto light interval in Qunits: %d\n", cfg->autolightqunits); logprintf(" Intensity for light entities: %d\n", cfg->lightlevel); logprintf(" Auto info_player_start: %d\n",cfg->autostart); logprintf(" Put brushes in \"worldspawn\": %d\n", cfg->worldspawn); logprintf(" Put entities in .MAP: %d\n",cfg->joinentmap); logprintf(" CD track to play: %d\n", cfg->cdtrack); logprintf(" Seal sides with walls of sky: %d\n", cfg->sealsides); logprintf(" Seal with ceiling and floor: %d\n\n",cfg->sealtopbottom); /* allow user to digest options so far */ time( &startpause); while( difftime( time( &now), startpause) < 5.0); logprintf(" Beginning message (map name): %s\n",cfg->mapname); logprintf(" WAD name containing textures: %s\n", cfg->wadname); logprintf(" Wall/Ceiling/Floor texture: %s\n",cfg->walltext); logprintf(" Sky texture: %s\n",cfg->skytext); logprintf(" Door texture: %s\n",cfg->doortext); logprintf("Frame texture for windows/doors: %s\n\n",cfg->frametext); logprintf(" Quake units per pixel: %d\n", cfg->qunitsperpel); logprintf(" Quake units for outer brushes: %d\n", cfg->outerqunits); logprintf(" Floor to ceiling: %d to %d\n", cfg->floor, cfg->ceiling); logprintf(" Window bottom/top: %d to %d\n", cfg->winbottom, cfg->wintop); logprintf(" Door top: %d\n\n",cfg->doortop); logprintf(" Palette index for walls: %d\n",cfg->wallval); logprintf("Palette index for blank windows: %d\n",cfg->winval); logprintf(" Palette index for doors: %d\n",cfg->doorval); logprintf(" Palette index for door frames: %d\n", cfg->doorframeval); logprintf("Palette index for skyed windows: %d\n",cfg->skywinval); logprintf("Palette index for skyed ceiling: %d\n\n",cfg->skyceilingval); logprintf(" Index for teleport platforms: %d\n",cfg->teleplatval); logprintf(" Teleport platform texture: %s (%d x %d)\n\n", cfg->teleplattext, cfg->teleplatbrushqunits, cfg->teleplatbrushqunits); /* allow user to digest options so far */ time( &startpause); while( difftime( time( &now), startpause) < 5.0); logprintf(" Texture of brushes to carve: %s\n", cfg->carvetext); logprintf(" Trigger texture: %s\n", cfg->triggertext); logprintf(" Carve Floor:%3d Carve Ceiling:%3d Carve Both:%3d Trigger:%3d\n\n", cfg->carvefloorval, cfg->carveceilingval, cfg->carvebothval, cfg->triggerval); logprintf(" Arrow textures are: %s & %s (%d x %d)\n", cfg->barrowtext, cfg->rarrowtext, cfg->arrowbrushqunits, cfg->arrowbrushqunits); logprintf(" Palette indices for arrows:\n"); logprintf(" RN:%3d RS:%3d RE:%3d RW:%3d BN:%3d BS:%3d BE:%3d BW:%3d\n\n", cfg->rnaval, cfg->rsaval, cfg->reaval, cfg->rwaval, cfg->bnaval, cfg->bsaval, cfg->beaval, cfg->bwaval); /* do some basic checking on options selected */ if( cfg->qunitsperpel < 2) error("\n Error: Minimum of two quake units per pixel. \n"); if( cfg->floor >= cfg->winbottom) error("\n Error: Floor is above window bottom. \n"); else if( cfg->winbottom >= cfg->wintop) error("\n Error: Window bottom is above window top. \n"); else if( cfg->wintop >= cfg->ceiling) error("\n Error: Window top is above ceiling. \n"); else if( cfg->floor >= cfg->doortop) error("\n Error: Floor is above door top. \n"); else if( cfg->doortop >= cfg->ceiling) error("\n Error: Doortop is above ceiling. \n"); else if( cfg->floor >= cfg->ceiling) { logprintf("Don't know how you got past the window checks,but...\n"); error("\n Error: Floor is above the ceiling\n"); } logprintf(" WAD name containing textures: %s\n", cfg->wadname); logprintf(" %3d entities using colors: \n\n", cfg->numents); for( i = 0; i < cfg->numents; i++) { if( ! (i%20)) { /* allow user to digest options so far */ time( &startpause); while( difftime( time( &now), startpause) < 5.0); } logprintf(" Pixel color: %3d = Entity: %s\n", cfg->ents[i].entval, cfg->ents[i].entstring); } logprintf("\n"); return; } /*** initcfg() ***/ /*** main() - convert 256 color .BMP to Quake .MAP file ***/ void main( int argc, char **argv) { CFG cfg; char bmpname[ FILENAMESIZE]; /* for .BMP file */ char cfgname[ FILENAMESIZE]; /* for .CFG file */ char mapname[ FILENAMESIZE]; /* for .MP1/.MP2/.EN1/.MAP files */ char logname[ FILENAMESIZE]; /* for .LOG file */ long mp1brushcnt, mp3brushcnt; QEXTREMES qe; int i; struct date today; struct time now; time_t start, end; /* prepare file names */ if( argc < 2) { printf("No .BMP file specified on command line\n\n"); printf("Command line is: BMP2MAP <.BMP file> [<.CFG file>]\n\n"); exit(1); } strcpy( bmpname, strtok( argv[1], ".\n")); if( argc == 2) /* use .BMP name for .CFG/.MAP/.LOG names */ { strcpy( cfgname, bmpname); strcpy( mapname, bmpname); strcpy( logname, mapname); } else /* argc > 2 - use .CFG name for .MAP/.LOG name */ { strcpy( cfgname, strtok( argv[2], ".\n")); strcpy( mapname, cfgname); strcpy( logname, cfgname); } time( &start); strcat( logname, LOGEXT); logfp = fopen( logname,"a"); if( ! logfp) { logfp = fopen( logname,"w"); if( ! logfp) logprintf("Unable to open %s for logging... continuing anyway.\n\n", logname); } /* display program name, copyright and command line */ logprintf("\n%s\n",pgmname); logprintf("%s\n", copyright1); logprintf("%s\n", copyright2); getdate( &today); gettime(&now); logprintf("Currently: %2d/%2d/%2d - %02d:%02d:%02d.%02d (last compiled:%s %s)\n\n", today.da_mon, today.da_day, today.da_year, now.ti_hour,now.ti_min, now.ti_sec, now.ti_hund, __DATE__,__TIME__); for( i = 0; i < argc; i++) logprintf("%s ",argv[i]); logprintf("\n\n"); logprintf(" Source file: %s%s\n", bmpname, BMPEXT); logprintf("Configuration file: %s%s\n", cfgname, CFGEXT); logprintf(" Output files: %s%s and %s%s\n", mapname, MAPEXT, mapname, ENTEXT); logprintf(" Logging to file: %s.\n", logname); /* initialize configuration */ initcfg( cfgname, bmpname, &cfg); qe.zmiddle = (cfg.ceiling - cfg.floor)/2 + cfg.floor; bmp2mp1( bmpname, mapname, &cfg, &qe, &mp1brushcnt, &mp3brushcnt); /* convert pixels to brushes */ mp12mp2( mapname, mp1brushcnt, mp3brushcnt, &cfg); /* combine similarly aligned brushes */ mp22map( mapname, &cfg); /* write brushes in .MAP format */ getdate( &today); gettime(&now); time(&end); logprintf("Finished - %2d/%2d/%2d - %02d:%02d:%02d.%02d - Time = %d seconds\n\n", today.da_mon, today.da_day, today.da_year, now.ti_hour,now.ti_min, now.ti_sec, now.ti_hund, end-start); if( logfp) fclose( logfp); exit(0); } /*** main() ***/ /*** EOF ***/