/* Copyright (C) 1996-1997 Id Software, Inc. 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 See file, 'COPYING', for details. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include "QF/dstring.h" #include "QF/hash.h" #include "QF/quakefs.h" #include "QF/script.h" #include "QF/sys.h" #include "QF/va.h" #include "compat.h" #include "tools/qfbsp/include/map.h" /** \addtogroup qfbsp_map */ //@{ int nummapbrushfaces; int nummapbrushes; #define ENTITIES_CHUNK 16 int num_entities; int max_entities; entity_t *entities; #define MIPTEXNAME_CHUNK 16 int nummiptexnames; int maxmiptexnames; const char **miptexnames; hashtab_t *miptex_hash; int numdetailbrushes; script_t *map_script; static void __attribute__ ((format (printf, 1, 2), noreturn)) map_error (const char *fmt, ...) { va_list args; va_start (args, fmt); fprintf (stderr, "%s:%d: ", map_script->file, map_script->line); vfprintf (stderr, fmt, args); fprintf (stderr, "\n"); va_end (args); exit (1); } static const char * miptex_getkey (const void *key, void *unused) { intptr_t index = (intptr_t) key; return miptexnames[index - 1]; } int FindMiptex (const char *name) { intptr_t index; char mpname[MIPTEXNAME]; strncpy (mpname, name, MIPTEXNAME - 1); mpname[MIPTEXNAME - 1] = 0; if (strcmp (name, "hint") == 0) return TEX_HINT; if (strcmp (name, "skip") == 0) return TEX_SKIP; if (!miptex_hash) miptex_hash = Hash_NewTable (1023, miptex_getkey, 0, 0, 0); if (miptexnames) { index = (intptr_t) Hash_Find (miptex_hash, mpname); if (index) return index - 1; } if (nummiptexnames == maxmiptexnames) { maxmiptexnames += MIPTEXNAME_CHUNK; miptexnames = realloc (miptexnames, maxmiptexnames * sizeof (const char *)); } index = nummiptexnames++; miptexnames[index] = strdup (mpname); Hash_Add (miptex_hash, (void *) (index + 1)); return index; } /* FindTexinfo Returns a global texinfo number */ static int FindTexinfo (texinfo_t *t) { int i, j; texinfo_t *tex; if (t->miptex < 0) return t->miptex; // it's HINT or SKIP // set the special flag if (miptexnames[t->miptex][0] == '*' || !strncasecmp (miptexnames[t->miptex], "sky", 3)) t->flags |= TEX_SPECIAL; tex = bsp->texinfo; for (i = 0; i < bsp->numtexinfo; i++, tex++) { if (t->miptex != tex->miptex) continue; if (t->flags != tex->flags) continue; for (j = 0; j < 8; j++) if (t->vecs[j / 4][j % 4] != tex->vecs[j / 4][j % 4]) break; if (j != 8) continue; return i; } // allocate a new texture BSP_AddTexinfo (bsp, t); return i; } entity_t *mapent; static void ParseEpair (void) { epair_t *e; e = malloc (sizeof (epair_t)); memset (e, 0, sizeof (epair_t)); e->next = mapent->epairs; mapent->epairs = e; e->key = strdup (map_script->token->str); Script_GetToken (map_script, false); e->value = strdup (map_script->token->str); } vec3_t baseaxis[18] = { {0, 0, 1}, {1, 0, 0}, {0, -1, 0}, // floor {0, 0, -1}, {1, 0, 0}, {0, -1, 0}, // ceiling {1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // west wall {-1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // east wall {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, // south wall {0, -1, 0}, {1, 0, 0}, {0, 0, -1} // north wall }; static void TextureAxisFromPlane (plane_t *pln, vec3_t xv, vec3_t yv) { float dot, best; int bestaxis, i; best = 0; bestaxis = 0; for (i = 0; i < 6; i++) { dot = DotProduct (pln->normal, baseaxis[i * 3]); if (dot > best) { best = dot; bestaxis = i; } } VectorCopy (baseaxis[bestaxis * 3 + 1], xv); VectorCopy (baseaxis[bestaxis * 3 + 2], yv); } static vec3_t * ParseVerts (int *n_verts) { vec3_t *verts; int i; const char *token; token = Script_Token (map_script); if (token[0] != ':') map_error ("parsing brush"); // It's normally ":count", but somebody might have done ": count" if (!token[1]) { Script_GetToken (map_script, false); token = Script_Token (map_script); } else { token++; } *n_verts = atoi (token); verts = malloc (sizeof (vec3_t) * *n_verts); for (i = 0; i < *n_verts; i++) { Script_GetToken (map_script, true); verts[i][0] = atof (map_script->token->str); Script_GetToken (map_script, true); verts[i][1] = atof (map_script->token->str); Script_GetToken (map_script, true); verts[i][2] = atof (map_script->token->str); } return verts; } static void ParseBrush (void) { float rotate, scale[2]; float ang, sinv, cosv, ns, nt; int sv, tv; int i, j, n_verts = 0, hltexdef; mbrush_t *b; mface_t *f, *f2; plane_t plane; texinfo_t tx; vec3_t planepts[3]; vec3_t t1, t2, *verts = 0; vec_t d, vecs[2][4]; nummapbrushes++; b = calloc (1, sizeof (mbrush_t)); b->next = mapent->brushes; mapent->brushes = b; Script_GetToken (map_script, true); if (strcmp (map_script->token->str, "(") != 0) { verts = ParseVerts (&n_verts); } else { Script_UngetToken (map_script); } do { if (!Script_GetToken (map_script, true)) break; if (!strcmp (map_script->token->str, "}")) break; if (verts) { int n_v, v; n_v = atoi (map_script->token->str); Script_GetToken (map_script, false); for (i = 0; i < n_v; i++) { Script_GetToken (map_script, false); v = atof (map_script->token->str); if (i < 3) VectorCopy (verts[v], planepts[i]); } Script_GetToken (map_script, false); } else { // read the three point plane definition for (i = 0; i < 3; i++) { if (i != 0) Script_GetToken (map_script, true); if (strcmp (map_script->token->str, "(")) map_error ("parsing brush"); for (j = 0; j < 3; j++) { Script_GetToken (map_script, false); planepts[i][j] = atof (map_script->token->str); } Script_GetToken (map_script, false); if (strcmp (map_script->token->str, ")")) map_error ("parsing brush"); } } // convert points to a plane VectorSubtract (planepts[0], planepts[1], t1); VectorSubtract (planepts[2], planepts[1], t2); CrossProduct(t1, t2, plane.normal); VectorNormalize (plane.normal); plane.dist = DotProduct(planepts[1], plane.normal); // read the texturedef memset (&tx, 0, sizeof (tx)); Script_GetToken (map_script, false); tx.miptex = FindMiptex (map_script->token->str); Script_GetToken (map_script, false); if ((hltexdef = !strcmp (map_script->token->str, "["))) { // S vector Script_GetToken (map_script, false); vecs[0][0] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[0][1] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[0][2] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[0][3] = atof (map_script->token->str); Script_GetToken (map_script, false); if (strcmp (map_script->token->str, "]")) map_error ("missing ]"); Script_GetToken (map_script, false); if (strcmp (map_script->token->str, "[")) map_error ("missing ["); // T vector Script_GetToken (map_script, false); vecs[1][0] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[1][1] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[1][2] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[1][3] = atof (map_script->token->str); Script_GetToken (map_script, false); if (strcmp (map_script->token->str, "]")) map_error ("missing ]"); } else { vecs[0][3] = atof (map_script->token->str); Script_GetToken (map_script, false); vecs[1][3] = atof (map_script->token->str); } Script_GetToken (map_script, false); rotate = atof (map_script->token->str); Script_GetToken (map_script, false); scale[0] = atof (map_script->token->str); Script_GetToken (map_script, false); scale[1] = atof (map_script->token->str); while (Script_TokenAvailable (map_script, false)) { Script_GetToken (map_script, false); if (!strcmp (map_script->token->str, "detail")) b->detail = 1; else map_error ("parse error"); } // if the three points are all on a previous plane, it is a duplicate // plane for (f2 = b->faces; f2; f2 = f2->next) { for (i = 0; i < 3; i++) { d = DotProduct (planepts[i], f2->plane.normal) - f2->plane.dist; if (d < -ON_EPSILON || d > ON_EPSILON) break; } if (i == 3) break; } if (f2) { printf ("WARNING: brush with duplicate plane\n"); continue; } if (DotProduct (plane.normal, plane.normal) < 0.1) { printf ("WARNING: brush plane with no normal on line %d\n", map_script->line); continue; } if (!hltexdef) { // fake proper texture vectors from QuakeEd style TextureAxisFromPlane (&plane, vecs[0], vecs[1]); } if (!scale[0]) scale[0] = 1; if (!scale[1]) scale[1] = 1; // rotate axis if (rotate == 0) { sinv = 0; cosv = 1; } else if (rotate == 90) { sinv = 1; cosv = 0; } else if (rotate == 180) { sinv = 0; cosv = -1; } else if (rotate == 270) { sinv = -1; cosv = 0; } else { ang = rotate / 180 * M_PI; sinv = sin (ang); cosv = cos (ang); } if (vecs[0][0]) sv = 0; else if (vecs[0][1]) sv = 1; else sv = 2; if (vecs[1][0]) tv = 0; else if (vecs[1][1]) tv = 1; else tv = 2; for (i = 0; i < 2; i++) { // rotate ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; vecs[i][sv] = ns; vecs[i][tv] = nt; // scale and store into texinfo for (j = 0; j < 3; j++) tx.vecs[i][j] = vecs[i][j] / scale[i]; tx.vecs[i][3] = vecs[i][3]; } f = calloc (1, sizeof (mface_t)); f->next = b->faces; b->faces = f; f->plane = plane; // unique the texinfo f->texinfo = FindTexinfo (&tx); nummapbrushfaces++; } while (1); if (verts) free (verts); } static qboolean ParseEntity (void) { if (!Script_GetToken (map_script, true)) return false; if (strcmp (map_script->token->str, "{")) map_error ("ParseEntity: { not found"); if (num_entities == max_entities) { max_entities += ENTITIES_CHUNK; entities = realloc (entities, max_entities * sizeof (entity_t)); } mapent = &entities[num_entities]; num_entities++; memset (mapent, 0, sizeof (entity_t)); mapent->line = map_script->line; do { if (!Script_GetToken (map_script, true)) map_error ("ParseEntity: EOF without closing brace"); if (!strcmp (map_script->token->str, "}")) break; if (!strcmp (map_script->token->str, "{")) ParseBrush (); else ParseEpair (); } while (1); if (!strcmp ("am_detail", ValueForKey (mapent, "classname"))) { mbrush_t *b, *lb; // set detail flag for (lb = b = mapent->brushes; b; lb = b, b = b->next) { b->detail = 1; numdetailbrushes++; } // add to worldspawn lb->next = entities->brushes; entities->brushes = mapent->brushes; num_entities--; memset (mapent, 0, sizeof (entity_t)); return true; } else { mbrush_t *b; for (b = mapent->brushes; b; b = b->next) if (b->detail) numdetailbrushes++; } GetVectorForKey (mapent, "origin", mapent->origin); return true; } void LoadMapFile (const char *filename) { char *buf; QFile *file; int bytes; file = Qopen (filename, "rt"); if (!file) Sys_Error ("couldn't open %s. %s", filename, strerror(errno)); bytes = Qfilesize (file); buf = malloc (bytes + 1); bytes = Qread (file, buf, bytes); buf[bytes] = 0; Qclose (file); map_script = Script_New (); map_script->single = ""; Script_Start (map_script, filename, buf); num_entities = 0; if (Script_GetToken (map_script, 1)) { if (strequal (map_script->token->str, "QuakeForge-map")) { if (!Script_TokenAvailable (map_script, 1)) map_error ("Unexpected EOF reading %s\n", filename); } else { Script_UngetToken (map_script); while (ParseEntity ()) ; } } Script_Delete (map_script); map_script = 0; free (buf); qprintf ("--- LoadMapFile ---\n"); qprintf ("%s\n", filename); qprintf ("%5i faces\n", nummapbrushfaces); qprintf ("%5i brushes (%i detail)\n", nummapbrushes, numdetailbrushes); qprintf ("%5i entities\n", num_entities); qprintf ("%5i textures\n", nummiptexnames); qprintf ("%5i texinfo\n", bsp->numtexinfo); } void PrintEntity (const entity_t *ent) { const epair_t *ep; printf ("%20s : %d\n", "map source line", ent->line); for (ep = ent->epairs; ep; ep = ep->next) printf ("%20s : %s\n", ep->key, ep->value); } const char * ValueForKey (const entity_t *ent, const char *key) { const epair_t *ep; for (ep = ent->epairs; ep; ep = ep->next) if (!strcmp (ep->key, key)) return ep->value; return ""; } void SetKeyValue (entity_t *ent, const char *key, const char *value) { epair_t *ep; for (ep = ent->epairs; ep; ep = ep->next) if (!strcmp (ep->key, key)) { free (ep->value); ep->value = strdup (value); return; } ep = malloc (sizeof (*ep)); ep->next = ent->epairs; ent->epairs = ep; ep->key = strdup (key); ep->value = strdup (value); } void GetVectorForKey (const entity_t *ent, const char *key, vec3_t vec) { const char *k; double v1, v2, v3; k = ValueForKey (ent, key); v1 = v2 = v3 = 0; // scanf into doubles, then assign, so it is vec_t size independent sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); vec[0] = v1; vec[1] = v2; vec[2] = v3; } void WriteEntitiesToString (void) { dstring_t *buf; const epair_t *ep; int i; buf = dstring_newstr (); for (i = 0; i < num_entities; i++) { ep = entities[i].epairs; if (!ep) continue; // ent got removed dstring_appendstr (buf, "{\n"); for (ep = entities[i].epairs; ep; ep = ep->next) { dstring_appendstr (buf, va (0, "\"%s\" \"%s\"\n", ep->key, ep->value)); } dstring_appendstr (buf, "}\n"); } BSP_AddEntities (bsp, buf->str, buf->size); } //@}