/** * @brief The incoming list will be freed before exiting */ tree_t *BuildTree (bspbrush_t *brushlist, const vec3_t mins, const vec3_t maxs) { node_t *node; tree_t *tree; vec3_t blmins, blmaxs; Verb_Printf(VERB_EXTRA, "--- BrushBSP ---\n"); tree = AllocTree(); ClearBounds(blmins, blmaxs); BrushlistCalcStats(brushlist, blmins, blmaxs); tree->aabb.add(blmins); tree->aabb.add(blmaxs); c_nodes = 0; c_nonvis = 0; node = AllocNode(); node->volume = BrushFromBounds(mins, maxs); tree->headnode = node; node = BuildTree_r(node, brushlist); Verb_Printf(VERB_EXTRA, "%5i visible nodes\n", c_nodes / 2 - c_nonvis); Verb_Printf(VERB_EXTRA, "%5i nonvis nodes\n", c_nonvis); Verb_Printf(VERB_EXTRA, "%5i leafs\n", (c_nodes + 1) / 2); return tree; }
/** * @sa PruneNodes_r */ void PruneNodes (node_t *node) { Verb_Printf(VERB_EXTRA, "--- PruneNodes ---\n"); c_pruned = 0; PruneNodes_r(node); Verb_Printf(VERB_EXTRA, "%5i pruned nodes\n", c_pruned); }
void SplitBrushList (bspbrush_t* brushes, uint16_t planenum, bspbrush_t** front, bspbrush_t** back) { bspbrush_t* brush; *front = *back = nullptr; for (brush = brushes; brush; brush = brush->next) { const int sides = brush->side; bspbrush_t* newbrush; if (sides == PSIDE_BOTH) { /* split into two brushes */ bspbrush_t* newbrush2; SplitBrush(brush, planenum, &newbrush, &newbrush2); if (newbrush) { newbrush->next = *front; Verb_Printf(VERB_DUMP, "SplitBrushList: Adding brush %i to front list.\n", newbrush->original->brushnum); *front = newbrush; } if (newbrush2) { newbrush2->next = *back; Verb_Printf(VERB_DUMP, "SplitBrushList: Adding brush %i to back list.\n", newbrush2->original->brushnum); *back = newbrush2; } continue; } newbrush = CopyBrush(brush); /* if the planenum is actually a part of the brush * find the plane and flag it as used so it won't be tried * as a splitter again */ if (sides & PSIDE_FACING) { int i; for (i = 0; i < newbrush->numsides; i++) { side_t* side = newbrush->sides + i; if ((side->planenum & ~1) == planenum) side->texinfo = TEXINFO_NODE; } } if (sides & PSIDE_FRONT) { newbrush->next = *front; *front = newbrush; Verb_Printf(VERB_DUMP, "SplitBrushList: Adding brush %i to front list.\n", newbrush->original->brushnum); continue; } if (sides & PSIDE_BACK) { newbrush->next = *back; Verb_Printf(VERB_DUMP, "SplitBrushList: Adding brush %i to back list.\n", newbrush->original->brushnum); *back = newbrush; continue; } Verb_Printf(VERB_DUMP, "SplitBrushList: Brush %i fell off the map.\n", newbrush->original->brushnum); } }
/** * @sa LoadMapFile * @sa FixErrors */ void WriteMapFile (const char* filename) { int removed; Verb_Printf(VERB_NORMAL, "writing map: '%s'\n", filename); ScopedFile f; FS_OpenFile(filename, &f, FILE_WRITE); if (!f) Sys_Error("Could not open %s for writing", filename); removed = 0; FS_Printf(&f, "\n"); for (int i = 0; i < num_entities; i++) { const entity_t* mapent = &entities[i]; const epair_t* e = mapent->epairs; /* maybe we don't want to write it back into the file */ if (mapent->skip) { removed++; continue; } FS_Printf(&f, "// entity %i\n{\n", i - removed); WriteMapEntities(&f, e); /* need 2 counters. j counts the brushes in the source entity. * jc counts the brushes written back. they may differ if some are skipped, * eg they are microbrushes */ int j, jc; for (j = 0, jc = 0; j < mapent->numbrushes; j++) { const mapbrush_t* brush = &mapbrushes[mapent->firstbrush + j]; if (brush->skipWriteBack) continue; WriteMapBrush(brush, jc++, &f); } /* add brushes from func_groups with single members to worldspawn */ if (i == 0) { int numToAdd; mapbrush_t** brushesToAdd = Check_ExtraBrushesForWorldspawn(&numToAdd); if (brushesToAdd != nullptr) { for (int k = 0; k < numToAdd; k++) { if (brushesToAdd[k]->skipWriteBack) continue; WriteMapBrush(brushesToAdd[k], j++, &f); } Mem_Free(brushesToAdd); } } FS_Printf(&f, "}\n"); } if (removed) Verb_Printf(VERB_NORMAL, "removed %i entities\n", removed); }
/** * @brief Writes the draw nodes * @note Called after a drawing hull is completed */ static int EmitDrawNode_r (node_t* node) { const char* side[2] = {"front", "back"}; dBspNode_t* n; const face_t* f; int i; if (node->planenum == PLANENUM_LEAF) { Verb_Printf(VERB_DUMP, "EmitDrawNode_r: creating singleton leaf.\n"); EmitLeaf(node); return -curTile->numleafs; } /* emit a node */ if (curTile->numnodes >= MAX_MAP_NODES) Sys_Error("MAX_MAP_NODES (%i)", curTile->numnodes); n = &curTile->nodes[curTile->numnodes]; Verb_Printf(VERB_DUMP, "EmitDrawNode_r: creating bsp node %i\n", curTile->numnodes); curTile->numnodes++; VectorCopy(node->mins, n->mins); VectorCopy(node->maxs, n->maxs); if (node->planenum & 1) Sys_Error("EmitDrawNode_r: odd planenum: %i", node->planenum); n->planenum = node->planenum; n->firstface = curTile->numfaces; if (!node->faces) c_nofaces++; else c_facenodes++; for (f = node->faces; f; f = f->next) EmitFace(f); n->numfaces = curTile->numfaces - n->firstface; /* recursively output the other nodes */ for (i = 0; i < 2; i++) { if (node->children[i]->planenum == PLANENUM_LEAF) { Verb_Printf(VERB_DUMP, "EmitDrawNode_r: creating child leaf for %s of bsp node " UFO_SIZE_T ".\n", side[i], n - curTile->nodes); n->children[i] = -(curTile->numleafs + 1); EmitLeaf(node->children[i]); } else { Verb_Printf(VERB_DUMP, "EmitDrawNode_r: adding child node for bsp node " UFO_SIZE_T ".\n", n - curTile->nodes); n->children[i] = curTile->numnodes; EmitDrawNode_r(node->children[i]); } } return n - curTile->nodes; }
/** * @brief Calculates the texture color that is used for light emitting surfaces */ void CalcTextureReflectivity (void) { int i, j, texels = 0; char path[MAX_QPATH]; int color[3]; SDL_Surface* surf; /* always set index 0 even if no textures */ VectorSet(texture_reflectivity[0], 0.5, 0.5, 0.5); VectorSet(color, 0, 0, 0); for (i = 0; i < curTile->numtexinfo; i++) { /* see if an earlier texinfo already got the value */ for (j = 0; j < i; j++) { if (Q_streq(curTile->texinfo[i].texture, curTile->texinfo[j].texture)) { VectorCopy(texture_reflectivity[j], texture_reflectivity[i]); break; } } if (j != i) /* earlier texinfo found, continue */ continue; /* load the image file */ Com_sprintf(path, sizeof(path), "textures/%s", curTile->texinfo[i].texture); if (!(surf = Img_LoadImage(path))) { Verb_Printf(VERB_NORMAL, "Couldn't load %s\n", path); VectorSet(texture_reflectivity[i], 0.5, 0.5, 0.5); continue; } /* calculate average color */ texels = surf->w * surf->h; color[0] = color[1] = color[2] = 0; for(j = 0; j < texels; j++){ const byte* pos = (byte*)surf->pixels + j * 4; color[0] += *pos++; /* r */ color[1] += *pos++; /* g */ color[2] += *pos++; /* b */ } Verb_Printf(VERB_EXTRA, "Loaded %s (%dx%d)\n", curTile->texinfo[i].texture, surf->w, surf->h); SDL_FreeSurface(surf); for(j = 0; j < 3; j++){ const float r = color[j] / texels / 255.0; texture_reflectivity[i][j] = r; } } }
void MakeFaces (node_t* node) { Verb_Printf(VERB_EXTRA, "--- MakeFaces ---\n"); c_merge = 0; c_subdivide = 0; c_nodefaces = 0; MakeFaces_r(node); Verb_Printf(VERB_EXTRA, "%5i makefaces\n", c_nodefaces); Verb_Printf(VERB_EXTRA, "%5i merged\n", c_merge); Verb_Printf(VERB_EXTRA, "%5i subdivided\n", c_subdivide); }
/** * @brief Entry point for all thread work requests. */ void RunThreadsOn (void (*func)(unsigned int), int unsigned workcount, qboolean progress, const char *id) { int start, end; if (threadstate.numthreads < 1) /* ensure safe thread counts */ threadstate.numthreads = 1; if (threadstate.numthreads > MAX_THREADS) threadstate.numthreads = MAX_THREADS; threadstate.workindex = 0; threadstate.workcount = workcount; threadstate.workfrac = -1; threadstate.progress = progress; threadstate.worktick = sqrt(workcount) + 1; WorkFunction = func; start = time(NULL); if (threadstate.progress) { fprintf(stdout, "%10s: ", id); fflush(stdout); } RunThreads(); end = time(NULL); if (threadstate.progress) { Verb_Printf(VERB_NORMAL, " (time: %6is, #: %i)\n", end - start, workcount); } }
/** * @brief makes basewindigs for sides and mins/maxs for the brush * @returns false if the brush doesn't enclose a valid volume */ static void CreateBrushWindings (bspbrush_t* brush) { int i; for (i = 0; i < brush->numsides; i++) { side_t* side = &brush->sides[i]; const plane_t* plane = &mapplanes[side->planenum]; int j; /* evidence that winding_t represents a hessian normal plane */ winding_t* w = BaseWindingForPlane(plane->normal, plane->dist); for (j = 0; j < brush->numsides && w; j++) { if (i == j) continue; /* back side clipaway */ if (brush->sides[j].planenum == (side->planenum ^ 1)) continue; if (brush->sides[j].bevel) continue; plane = &mapplanes[brush->sides[j].planenum ^ 1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); /*CLIP_EPSILON); */ /* fix broken windings that would generate trifans */ if (!FixWinding(w)) Verb_Printf(VERB_EXTRA, "removed degenerated edge(s) from winding\n"); } side->winding = w; } BoundBrush(brush); }
static node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) { node_t *newnode; side_t *bestside; int i; bspbrush_t *children[2]; if (threadstate.numthreads == 1) c_nodes++; /* find the best plane to use as a splitter */ bestside = SelectSplitSide(brushes, node->volume); if (!bestside) { /* leaf node */ LeafNode(node, brushes); Verb_Printf(VERB_DUMP, "BuildTree_r: Created a leaf node.\n"); return node; } /* make sure the selected plane hasn't been used before. */ CheckPlaneAgainstParents(bestside->planenum, node); Verb_Printf(VERB_DUMP, "BuildTree_r: splitting along plane %i\n", (int)bestside->planenum); /* this is a splitplane node */ node->side = bestside; node->planenum = bestside->planenum & ~1; /* always use front facing */ SplitBrushList(brushes, node->planenum, &children[0], &children[1]); FreeBrushList(brushes); /* allocate children before recursing */ for (i = 0; i < 2; i++) { newnode = AllocNode(); newnode->parent = node; node->children[i] = newnode; } SplitBrush(node->volume, node->planenum, &node->children[0]->volume, &node->children[1]->volume); /* recursively process children */ for (i = 0; i < 2; i++) { node->children[i] = BuildTree_r(node->children[i], children[i]); } return node; }
/** * @brief Finds a brush side to use for texturing the given portal */ static void FindPortalSide (portal_t *p) { uint32_t viscontents; bspbrush_t *bb; int i, j, planenum; side_t *bestside; float bestdot; /* decide which content change is strongest * solid > water, etc */ viscontents = VisibleContents(p->nodes[0]->contentFlags ^ p->nodes[1]->contentFlags); if (!viscontents) return; planenum = p->onnode->planenum; bestside = NULL; bestdot = 0; for (j = 0; j < 2; j++) { const node_t *n = p->nodes[j]; const plane_t *p1 = &mapplanes[p->onnode->planenum]; for (bb = n->brushlist; bb; bb = bb->next) { const mapbrush_t *brush = bb->original; if (!(brush->contentFlags & viscontents)) continue; for (i = 0; i < brush->numsides; i++) { side_t *side = &brush->original_sides[i]; float dot; const plane_t *p2; if (side->bevel) continue; /* non-visible */ if (side->texinfo == TEXINFO_NODE) continue; /* exact match */ if ((side->planenum &~ 1) == planenum) { bestside = &brush->original_sides[i]; goto gotit; } /* see how close the match is */ p2 = &mapplanes[side->planenum &~ 1]; dot = DotProduct(p1->normal, p2->normal); if (dot > bestdot) { bestdot = dot; bestside = side; } } } } gotit: if (!bestside) Verb_Printf(VERB_EXTRA, "WARNING: side not found for portal\n"); p->sidefound = qtrue; p->side = bestside; }
/** * @brief copies working data for a bsp tree into the structures used to create the bsp file. * @param[in] headnode the top-most node in this bsp tree * @return the index to the head node created. */ int WriteBSP (node_t* headnode) { int oldfaces, emittedHeadnode; c_nofaces = 0; c_facenodes = 0; Verb_Printf(VERB_EXTRA, "--- WriteBSP ---\n"); oldfaces = curTile->numfaces; emittedHeadnode = EmitDrawNode_r(headnode); Verb_Printf(VERB_EXTRA, "%5i nodes with faces\n", c_facenodes); Verb_Printf(VERB_EXTRA, "%5i nodes without faces\n", c_nofaces); Verb_Printf(VERB_EXTRA, "%5i faces\n", curTile->numfaces - oldfaces); return emittedHeadnode; }
/** * @param[in] n The node nums * @sa R_ModLoadNodes */ static int32_t BuildNodeChildren (const int n[3]) { int32_t node = LEAFNODE; int i; for (i = 0; i < 3; i++) { dBspNode_t *newnode; vec3_t newmins, newmaxs, addvec; if (n[i] == LEAFNODE) continue; if (node == LEAFNODE) { /* store the valid node */ node = n[i]; ClearBounds(newmins, newmaxs); VectorCopy(curTile->nodes[node].mins, addvec); AddPointToBounds(addvec, newmins, newmaxs); VectorCopy(curTile->nodes[node].maxs, addvec); AddPointToBounds(addvec, newmins, newmaxs); } else { /* add a new "special" dnode and store it */ newnode = &curTile->nodes[curTile->numnodes]; curTile->numnodes++; newnode->planenum = PLANENUM_LEAF; newnode->children[0] = node; newnode->children[1] = n[i]; newnode->firstface = 0; newnode->numfaces = 0; ClearBounds(newmins, newmaxs); VectorCopy(curTile->nodes[node].mins, addvec); AddPointToBounds(addvec, newmins, newmaxs); VectorCopy(curTile->nodes[node].maxs, addvec); AddPointToBounds(addvec, newmins, newmaxs); VectorCopy(curTile->nodes[n[i]].mins, addvec); AddPointToBounds(addvec, newmins, newmaxs); VectorCopy(curTile->nodes[n[i]].maxs, addvec); AddPointToBounds(addvec, newmins, newmaxs); VectorCopy(newmins, newnode->mins); VectorCopy(newmaxs, newnode->maxs); node = curTile->numnodes - 1; } AddPointToBounds(newmins, worldMins, worldMaxs); AddPointToBounds(newmaxs, worldMins, worldMaxs); Verb_Printf(VERB_DUMP, "BuildNodeChildren: node=%i (%i %i %i) (%i %i %i)\n", node, curTile->nodes[node].mins[0], curTile->nodes[node].mins[1], curTile->nodes[node].mins[2], curTile->nodes[node].maxs[0], curTile->nodes[node].maxs[1], curTile->nodes[node].maxs[2]); } /* return the last stored node */ return node; }
static void LeafNode (node_t *node, bspbrush_t *brushes) { node->side = nullptr; node->planenum = PLANENUM_LEAF; Verb_Printf(VERB_DUMP, "LeafNode: scanning brushes.\n"); node->contentFlags = BrushListCalcContents(brushes); node->brushlist = brushes; }
/** * @brief Finishes a new bsp and writes to disk * @sa BeginBSPFile */ void EndBSPFile (const char* filename) { EmitBrushes(); EmitPlanes(); UnparseEntities(); /* write the map */ Verb_Printf(VERB_LESS, "Writing %s\n", filename); WriteBSPFile(filename); }
/** * @brief Counts the faces and calculate the aabb */ void BrushlistCalcStats (bspbrush_t* brushlist, vec3_t mins, vec3_t maxs) { bspbrush_t* b; int c_faces = 0, c_nonvisfaces = 0, c_brushes = 0; for (b = brushlist; b; b = b->next) { int i; vec_t volume; c_brushes++; volume = BrushVolume(b); if (volume < config.microvolume) { Com_Printf("\nWARNING: entity %i, brush %i: microbrush, volume %.3g\n", b->original->entitynum, b->original->brushnum, volume); } for (i = 0; i < b->numsides; i++) { side_t* side = &b->sides[i]; if (side->bevel) continue; if (!side->winding) continue; if (side->texinfo == TEXINFO_NODE) continue; if (side->visible) c_faces++; else c_nonvisfaces++; } AddPointToBounds(b->mins, mins, maxs); AddPointToBounds(b->maxs, mins, maxs); } Verb_Printf(VERB_EXTRA, "%5i brushes\n", c_brushes); Verb_Printf(VERB_EXTRA, "%5i visible faces\n", c_faces); Verb_Printf(VERB_EXTRA, "%5i nonvisible faces\n", c_nonvisfaces); }
/** * @brief Get the content flags for a given brush * @param b The mapbrush to get the content flags for * @return The calculated content flags */ static int BrushContents (mapbrush_t* b) { int contentFlags, i; const side_t* s; s = &b->original_sides[0]; contentFlags = s->contentFlags; for (i = 1; i < b->numsides; i++, s++) { if (s->contentFlags != contentFlags) { Verb_Printf(VERB_EXTRA, "Entity %i, Brush %i: mixed face contents (f: %i, %i)\n" , b->entitynum, b->brushnum, s->contentFlags, contentFlags); break; } } return contentFlags; }
void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush) { int i; Verb_Printf(VERB_EXTRA, "--- MarkVisibleSides ---\n"); /* clear all the visible flags */ for (i = startbrush; i < endbrush; i++) { mapbrush_t *mb = &mapbrushes[i]; const int numsides = mb->numsides; int j; for (j = 0; j < numsides; j++) mb->original_sides[j].visible = qfalse; } /* set visible flags on the sides that are used by portals */ MarkVisibleSides_r(tree->headnode); }
/** * @sa ProcessSubModel * @sa ConstructLevelNodes_r */ void FixTjuncs (node_t* headnode) { /* snap and merge all vertexes */ Verb_Printf(VERB_EXTRA, "---- snap verts ----\n"); OBJZERO(hashverts); c_totalverts = 0; c_uniqueverts = 0; c_faceoverflows = 0; EmitVertexes_r(headnode); Verb_Printf(VERB_EXTRA, "%i unique from %i\n", c_uniqueverts, c_totalverts); /* break edges on tjunctions */ Verb_Printf(VERB_EXTRA, "---- tjunc ----\n"); c_degenerate = 0; c_facecollapse = 0; c_tjunctions = 0; if (!config.notjunc) FixEdges_r(headnode); Verb_Printf(VERB_EXTRA, "%5i edges degenerated\n", c_degenerate); Verb_Printf(VERB_EXTRA, "%5i faces degenerated\n", c_facecollapse); Verb_Printf(VERB_EXTRA, "%5i edges added by tjunctions\n", c_tjunctions); Verb_Printf(VERB_EXTRA, "%5i faces added by tjunctions\n", c_faceoverflows); Verb_Printf(VERB_EXTRA, "%5i bad start verts\n", c_badstartverts); }
/** * @brief Emits a leafnode to the bsp file */ static void EmitLeaf (const node_t* node) { dBspLeaf_t* leaf_p; int i; const bspbrush_t* b; /* emit a leaf */ if (curTile->numleafs >= MAX_MAP_LEAFS) Sys_Error("MAX_MAP_LEAFS (%i)", curTile->numleafs); leaf_p = &curTile->leafs[curTile->numleafs]; curTile->numleafs++; leaf_p->contentFlags = node->contentFlags; leaf_p->area = node->area; /* write bounding box info */ VectorCopy(node->mins, leaf_p->mins); VectorCopy(node->maxs, leaf_p->maxs); /* write the leafbrushes */ leaf_p->firstleafbrush = curTile->numleafbrushes; for (b = node->brushlist; b; b = b->next) { if (curTile->numleafbrushes >= MAX_MAP_LEAFBRUSHES) Sys_Error("MAX_MAP_LEAFBRUSHES (%i)", curTile->numleafbrushes); int brushnum = b->original - mapbrushes; for (i = leaf_p->firstleafbrush; i < curTile->numleafbrushes; i++) if (curTile->leafbrushes[i] == brushnum) break; if (i == curTile->numleafbrushes) { Verb_Printf(VERB_DUMP, "EmitLeaf: adding brush %i to leaf %i\n", brushnum, curTile->numleafs - 1); curTile->leafbrushes[curTile->numleafbrushes] = brushnum; curTile->numleafbrushes++; } } leaf_p->numleafbrushes = curTile->numleafbrushes - leaf_p->firstleafbrush; }
/** * @sa DoRouting */ static void CheckConnectionsThread (unsigned int unitnum) { /* get coordinates of that unit */ const int numDirs = CORE_DIRECTIONS / (1 + RT_IS_BIDIRECTIONAL); const int dir = (unitnum % numDirs) * (RT_IS_BIDIRECTIONAL ? 2 : 1); const int y = (unitnum / numDirs) % PATHFINDING_WIDTH; const int x = (unitnum / numDirs / PATHFINDING_WIDTH) % PATHFINDING_WIDTH; const int actorSize = unitnum / numDirs / PATHFINDING_WIDTH / PATHFINDING_WIDTH; /* test bounds - the size adjustment is needed because large actor cells occupy multiple cell units. */ if (x > wpMaxs[0] - actorSize || y > wpMaxs[1] - actorSize || x < wpMins[0] || y < wpMins[1] ) { /* don't enter - outside world */ /* Com_Printf("x%i y%i z%i dir%i size%i (%i, %i, %i) (%i, %i, %i)\n", x, y, z, dir, size, wpMins[0], wpMins[1], wpMins[2], wpMaxs[0], wpMaxs[1], wpMaxs[2]); */ return; } Verb_Printf(VERB_EXTRA, "%i %i %i %i (%i, %i, %i) (%i, %i, %i)\n", x, y, dir, actorSize, wpMins[0], wpMins[1], wpMins[2], wpMaxs[0], wpMaxs[1], wpMaxs[2]); RT_UpdateConnectionColumn(&mapTiles, Nmap, actorSize + 1, x, y, dir); /* Com_Printf("z:%i nz:%i\n", z, new_z); */ }
/** * @sa ProcessWorldModel * @sa ProcessSubModel */ void ProcessModels (const char* filename) { int entity_num; BeginBSPFile(); for (entity_num = 0; entity_num < num_entities; entity_num++) { if (!entities[entity_num].numbrushes) continue; Verb_Printf(VERB_EXTRA, "############### model %i ###############\n", curTile->nummodels); if (entity_num == 0) ProcessWorldModel(entity_num); else ProcessSubModel(entity_num); if (!config.verboseentities) config.verbose = false; /* don't bother printing submodels */ } EndBSPFile(filename); }
/** * @brief process brushes with that level mask * @param[in] levelnum is the level mask * @note levelnum * 256: weaponclip-level * 257: actorclip-level * 258: stepon-level * 259: tracing structure * @sa ProcessWorldModel * @sa ConstructLevelNodes_r */ void ProcessLevel (unsigned int levelnum) { vec3_t mins, maxs; dBspModel_t *dm; /* oversizing the blocks guarantees that all the boundaries will also get nodes. */ /* get maxs */ mins[0] = (config.block_xl) * 512.0 + 1.0; mins[1] = (config.block_yl) * 512.0 + 1.0; mins[2] = -MAX_WORLD_WIDTH + 1.0; maxs[0] = (config.block_xh + 1.0) * 512.0 - 1.0; maxs[1] = (config.block_yh + 1.0) * 512.0 - 1.0f; maxs[2] = MAX_WORLD_WIDTH - 1.0; Verb_Printf(VERB_EXTRA, "Process levelnum %i (curTile->nummodels: %i)\n", levelnum, curTile->nummodels); /* Com_Printf("Process levelnum %i (curTile->nummodels: %i)\n", levelnum, curTile->nummodels); */ /** @note Should be reentrant as each thread has a unique levelnum at any given time */ dm = &curTile->models[levelnum]; OBJZERO(*dm); /** @todo Check what happens if two threads do the memcpy */ /* back copy backup brush sides structure */ /* to reset all the changed values (especialy "finished") */ memcpy(mapbrushes, mapbrushes + nummapbrushes, sizeof(mapbrush_t) * nummapbrushes); /* Store face number for later use */ dm->firstface = curTile->numfaces; dm->headnode = ConstructLevelNodes_r(levelnum, mins, maxs, entityNum); /* This here replaces the calls to EndModel */ dm->numfaces = curTile->numfaces - dm->firstface; /* if (!dm->numfaces) Com_Printf("level: %i -> %i : f %i\n", levelnum, dm->headnode, dm->numfaces); */ }
/** * @brief print map stats on -stats */ void Check_Stats(void) { vec3_t worldSize; int j; int* entNums; Check_InitEntityDefs(); entNums = Mem_AllocTypeN(int, numEntityDefs); Check_MapSize(worldSize); Verb_Printf(VERB_NORMAL, " Number of brushes: %i\n",nummapbrushes); Verb_Printf(VERB_NORMAL, " Number of planes: %i\n",nummapplanes); Verb_Printf(VERB_NORMAL, " Number of brush sides: %i\n",nummapbrushsides); Verb_Printf(VERB_NORMAL, " Map size (units): %.0f %.0f %.0f\n", worldSize[0], worldSize[1], worldSize[2]); Verb_Printf(VERB_NORMAL, " Map size (fields): %.0f %.0f %.0f\n", worldSize[0] / UNIT_SIZE, worldSize[1] / UNIT_SIZE, worldSize[2] / UNIT_HEIGHT); Verb_Printf(VERB_NORMAL, " Map size (tiles): %.0f %.0f %.0f\n", worldSize[0] / (MIN_TILE_SIZE), worldSize[1] / (MIN_TILE_SIZE), worldSize[2] / UNIT_HEIGHT); Verb_Printf(VERB_NORMAL, " Number of entities: %i\n", num_entities); /* count number of each type of entity */ for (int i = 0; i < num_entities; i++) { const char* name = ValueForKey(&entities[i], "classname"); for (j = 0; j < numEntityDefs; j++) if (Q_streq(name, entityDefs[j].classname)) { entNums[j]++; break; } if (j == numEntityDefs) { Com_Printf("Check_Stats: entity '%s' not recognised\n", name); } } /* print number of each type of entity */ for (j = 0; j < numEntityDefs; j++) if (entNums[j]) Com_Printf("%27s: %i\n", entityDefs[j].classname, entNums[j]); Mem_Free(entNums); }
/** * @brief Fills in texorg, worldtotex. and textoworld */ static void CalcLightinfoVectors (lightinfo_t* l) { const dBspTexinfo_t* tex; int i; vec3_t texnormal; vec_t distscale, dist; tex = &curTile->texinfo[l->face->texinfo]; for (i = 0; i < 2; i++) VectorCopy(tex->vecs[i], l->worldtotex[i]); /* calculate a normal to the texture axis. points can be moved along this * without changing their S/T */ texnormal[0] = tex->vecs[1][1] * tex->vecs[0][2] - tex->vecs[1][2] * tex->vecs[0][1]; texnormal[1] = tex->vecs[1][2] * tex->vecs[0][0] - tex->vecs[1][0] * tex->vecs[0][2]; texnormal[2] = tex->vecs[1][0] * tex->vecs[0][1] - tex->vecs[1][1] * tex->vecs[0][0]; VectorNormalize(texnormal); /* flip it towards plane normal */ distscale = DotProduct(texnormal, l->facenormal); if (!distscale) { Verb_Printf(VERB_EXTRA, "WARNING: Texture axis perpendicular to face\n"); distscale = 1.0; } if (distscale < 0.0) { distscale = -distscale; VectorSubtract(vec3_origin, texnormal, texnormal); } /* distscale is the ratio of the distance along the texture normal to * the distance along the plane normal */ distscale = 1.0 / distscale; for (i = 0; i < 2; i++) { const vec_t len = VectorLength(l->worldtotex[i]); const vec_t distance = DotProduct(l->worldtotex[i], l->facenormal) * distscale; VectorMA(l->worldtotex[i], -distance, texnormal, l->textoworld[i]); VectorScale(l->textoworld[i], (1.0f / len) * (1.0f / len), l->textoworld[i]); } /* calculate texorg on the texture plane */ for (i = 0; i < 3; i++) l->texorg[i] = -tex->vecs[0][3] * l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; /* project back to the face plane */ dist = DotProduct(l->texorg, l->facenormal) - l->facedist - 1; dist *= distscale; VectorMA(l->texorg, -dist, texnormal, l->texorg); /* compensate for org'd bmodels */ VectorAdd(l->texorg, l->modelorg, l->texorg); /* total sample count */ l->numsurfpt = l->texsize[0] * l->texsize[1]; l->surfpt = Mem_AllocTypeN(vec3_t, l->numsurfpt); if (!l->surfpt) Sys_Error("Surface too large to light (" UFO_SIZE_T ")", l->numsurfpt * sizeof(*l->surfpt)); /* distance between samples */ l->step = 1 << config.lightquant; }
/** * @sa WriteMapFile * @sa ParseMapEntity */ void LoadMapFile (const char* filename) { Verb_Printf(VERB_EXTRA, "--- LoadMapFile ---\n"); LoadScriptFile(filename); OBJZERO(mapbrushes); nummapbrushes = 0; OBJZERO(brushsides); nummapbrushsides = 0; OBJZERO(side_brushtextures); OBJZERO(mapplanes); num_entities = 0; /* Create this shortcut to mapTiles[0] */ curTile = &mapTiles.mapTiles[0]; /* Set the number of tiles to 1. This is fix for ufo2map right now. */ mapTiles.numTiles = 1; char entityString[MAX_TOKEN_CHARS]; const char* ump = GetUMPName(filename); if (ump != nullptr) ParseUMP(ump, entityString, false); while (ParseMapEntity(filename, entityString)); int subdivide = atoi(ValueForKey(&entities[0], "subdivide")); if (subdivide >= 256 && subdivide <= 2048) { Verb_Printf(VERB_EXTRA, "Using subdivide %d from worldspawn.\n", subdivide); config.subdivideSize = subdivide; } if (footstepsCnt) Com_Printf("Generated footstep file with %i entries\n", footstepsCnt); if (materialsCnt) Com_Printf("Generated material file with %i entries\n", materialsCnt); vec3_t map_mins, map_maxs; ClearBounds(map_mins, map_maxs); for (int i = 0; i < entities[0].numbrushes; i++) { if (mapbrushes[i].mbBox.mins[0] > MAX_WORLD_WIDTH) continue; /* no valid points */ AddPointToBounds(mapbrushes[i].mbBox.mins, map_mins, map_maxs); AddPointToBounds(mapbrushes[i].mbBox.maxs, map_mins, map_maxs); } /* save a copy of the brushes */ memcpy(mapbrushes + nummapbrushes, mapbrushes, sizeof(mapbrush_t) * nummapbrushes); Verb_Printf(VERB_EXTRA, "%5i brushes\n", nummapbrushes); Verb_Printf(VERB_EXTRA, "%5i total sides\n", nummapbrushsides); Verb_Printf(VERB_EXTRA, "%5i boxbevels\n", c_boxbevels); Verb_Printf(VERB_EXTRA, "%5i edgebevels\n", c_edgebevels); Verb_Printf(VERB_EXTRA, "%5i entities\n", num_entities); Verb_Printf(VERB_EXTRA, "%5i planes\n", nummapplanes); Verb_Printf(VERB_EXTRA, "size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0], map_mins[1], map_mins[2], map_maxs[0], map_maxs[1], map_maxs[2]); }
/** * @brief Generates two new brushes, leaving the original unchanged */ void SplitBrush (const bspbrush_t* brush, uint16_t planenum, bspbrush_t** front, bspbrush_t** back) { bspbrush_t* b[2]; int i, j; winding_t* w, *cw[2], *midwinding; plane_t* plane; float d_front, d_back; *front = *back = nullptr; plane = &mapplanes[planenum]; /* check all points */ d_front = d_back = 0; for (i = 0; i < brush->numsides; i++) { w = brush->sides[i].winding; if (!w) continue; for (j = 0; j < w->numpoints; j++) { const float d = DotProduct(w->p[j], plane->normal) - plane->dist; if (d > 0 && d > d_front) d_front = d; else if (d < 0 && d < d_back) d_back = d; } } if (d_front < 0.1) { /* PLANESIDE_EPSILON) */ /* only on back */ *back = CopyBrush(brush); return; } if (d_back > -0.1) { /* PLANESIDE_EPSILON) */ /* only on front */ *front = CopyBrush(brush); return; } /* create a new winding from the split plane */ w = BaseWindingForPlane(plane->normal, plane->dist); for (i = 0; i < brush->numsides && w; i++) { plane_t* plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); /* PLANESIDE_EPSILON); */ } /* the brush isn't really split */ if (!w || WindingIsTiny(w)) { const int side = BrushMostlyOnSide(brush, plane); if (side == PSIDE_FRONT) *front = CopyBrush(brush); else if (side == PSIDE_BACK) *back = CopyBrush(brush); return; } if (WindingIsHuge(w)) { /** @todo Print brush and entnum either of the brush that was splitted * or the plane that was used as splitplane */ Com_Printf("WARNING: Large winding\n"); } midwinding = w; /* split it for real */ for (i = 0; i < 2; i++) { b[i] = AllocBrush(brush->numsides + 1); b[i]->original = brush->original; } /* split all the current windings */ for (i = 0; i < brush->numsides; i++) { const side_t* s = &brush->sides[i]; w = s->winding; if (!w) continue; ClipWindingEpsilon(w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); for (j = 0; j < 2; j++) { side_t* cs; if (!cw[j]) continue; cs = &b[j]->sides[b[j]->numsides]; b[j]->numsides++; *cs = *s; cs->winding = cw[j]; cs->tested = false; } } /* see if we have valid polygons on both sides */ for (i = 0; i < 2; i++) { BoundBrush(b[i]); for (j = 0; j < 3; j++) { if (b[i]->mins[j] < -MAX_WORLD_WIDTH || b[i]->maxs[j] > MAX_WORLD_WIDTH) { /** @todo Print brush and entnum either of the brush that was split * or the plane that was used as splitplane */ Verb_Printf(VERB_EXTRA, "bogus brush after clip\n"); break; } } if (b[i]->numsides < 3 || j < 3) { FreeBrush(b[i]); b[i] = nullptr; } } if (!(b[0] && b[1])) { /** @todo Print brush and entnum either of the brush that was splitted * or the plane that was used as splitplane */ if (!b[0] && !b[1]) Verb_Printf(VERB_EXTRA, "split removed brush\n"); else Verb_Printf(VERB_EXTRA, "split not on both sides\n"); if (b[0]) { FreeBrush(b[0]); *front = CopyBrush(brush); } if (b[1]) { FreeBrush(b[1]); *back = CopyBrush(brush); } return; } /* add the midwinding to both sides */ for (i = 0; i < 2; i++) { side_t* cs = &b[i]->sides[b[i]->numsides]; b[i]->numsides++; cs->planenum = planenum ^ i ^ 1; cs->texinfo = TEXINFO_NODE; cs->visible = false; cs->tested = false; if (i == 0) cs->winding = CopyWinding(midwinding); else cs->winding = midwinding; } for (i = 0; i < 2; i++) { const vec_t v1 = BrushVolume(b[i]); if (v1 < 1.0) { FreeBrush(b[i]); b[i] = nullptr; /** @todo Print brush and entnum either of the brush that was splitted * or the plane that was used as splitplane */ Verb_Printf(VERB_EXTRA, "tiny volume after clip\n"); } } *front = b[0]; *back = b[1]; }
/** * @brief Parses a brush from the map file * @sa FindMiptex * @param[in] mapent The entity the brush to parse belongs to * @param[in] filename The map filename, used to derive the name for the footsteps file */ static void ParseBrush (entity_t* mapent, const char* filename) { int j, k; brush_texture_t td; vec3_t planepts[3]; const int checkOrFix = config.performMapCheck || config.fixMap ; if (nummapbrushes == MAX_MAP_BRUSHES) Sys_Error("nummapbrushes == MAX_MAP_BRUSHES (%i)", nummapbrushes); mapbrush_t* b = &mapbrushes[nummapbrushes]; OBJZERO(*b); b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = num_entities - 1; b->brushnum = nummapbrushes - mapent->firstbrush; do { if (Q_strnull(GetToken())) break; if (*parsedToken == '}') break; if (nummapbrushsides == MAX_MAP_BRUSHSIDES) Sys_Error("nummapbrushsides == MAX_MAP_BRUSHSIDES (%i)", nummapbrushsides); side_t* side = &brushsides[nummapbrushsides]; /* read the three point plane definition */ for (int i = 0; i < 3; i++) { if (i != 0) GetToken(); if (*parsedToken != '(') Sys_Error("parsing brush"); for (j = 0; j < 3; j++) { GetToken(); planepts[i][j] = atof(parsedToken); } GetToken(); if (*parsedToken != ')') Sys_Error("parsing brush"); } /* read the texturedef */ GetToken(); if (strlen(parsedToken) >= MAX_TEXPATH) { if (config.performMapCheck || config.fixMap) Com_Printf(" ");/* hack to make this look like output from Check_Printf() */ Com_Printf("ParseBrush: texture name too long (limit %i): %s\n", MAX_TEXPATH, parsedToken); if (config.fixMap) Sys_Error("Aborting, as -fix is active and saving might corrupt *.map by truncating texture name"); } Q_strncpyz(td.name, parsedToken, sizeof(td.name)); td.shift[0] = atof(GetToken()); td.shift[1] = atof(GetToken()); td.rotate = atof(GetToken()); td.scale[0] = atof(GetToken()); td.scale[1] = atof(GetToken()); /* find default flags and values */ const int mt = FindMiptex(td.name); side->surfaceFlags = td.surfaceFlags = side->contentFlags = td.value = 0; if (TokenAvailable()) { side->contentFlags = atoi(GetToken()); side->surfaceFlags = td.surfaceFlags = atoi(GetToken()); td.value = atoi(GetToken()); } /* if in check or fix mode, let them choose to do this (with command line options), * and then call is made elsewhere */ if (!checkOrFix) { SetImpliedFlags(side, &td, b); /* if no other content flags are set - make this solid */ if (!checkOrFix && side->contentFlags == 0) side->contentFlags = CONTENTS_SOLID; } /* translucent objects are automatically classified as detail and window */ if (side->surfaceFlags & (SURF_BLEND33 | SURF_BLEND66 | SURF_ALPHATEST)) { side->contentFlags |= CONTENTS_DETAIL; side->contentFlags |= CONTENTS_TRANSLUCENT; side->contentFlags |= CONTENTS_WINDOW; side->contentFlags &= ~CONTENTS_SOLID; } if (config.fulldetail) side->contentFlags &= ~CONTENTS_DETAIL; if (!checkOrFix) { if (!(side->contentFlags & ((LAST_VISIBLE_CONTENTS - 1) | CONTENTS_ACTORCLIP | CONTENTS_WEAPONCLIP | CONTENTS_LIGHTCLIP))) side->contentFlags |= CONTENTS_SOLID; /* hints and skips are never detail, and have no content */ if (side->surfaceFlags & (SURF_HINT | SURF_SKIP)) { side->contentFlags = 0; side->surfaceFlags &= ~CONTENTS_DETAIL; } } /* check whether the flags are ok */ CheckFlags(side, b); /* generate a list of textures that should have footsteps when walking on them */ if (mt > 0 && (side->surfaceFlags & SURF_FOOTSTEP)) GenerateFootstepList(filename, mt); GenerateMaterialFile(filename, mt, side); /* find the plane number */ int planenum = PlaneFromPoints(b, planepts[0], planepts[1], planepts[2]); if (planenum == PLANENUM_LEAF) { Com_Printf("Entity %i, Brush %i: plane with no normal\n", b->entitynum, b->brushnum); continue; } for (j = 0; j < 3; j++) VectorCopy(planepts[j], mapplanes[planenum].planeVector[j]); /* see if the plane has been used already */ for (k = 0; k < b->numsides; k++) { const side_t* s2 = b->original_sides + k; if (s2->planenum == planenum) { Com_Printf("Entity %i, Brush %i: duplicate plane\n", b->entitynum, b->brushnum); break; } if (s2->planenum == (planenum ^ 1)) { Com_Printf("Entity %i, Brush %i: mirrored plane\n", b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; /* duplicated */ /* keep this side */ side = b->original_sides + b->numsides; side->planenum = planenum; side->texinfo = TexinfoForBrushTexture(&mapplanes[planenum], &td, vec3_origin, side->contentFlags & CONTENTS_TERRAIN); side->brush = b; /* save the td off in case there is an origin brush and we * have to recalculate the texinfo */ side_brushtextures[nummapbrushsides] = td; Verb_Printf(VERB_DUMP, "Brush %i Side %i (%f %f %f) (%f %f %f) (%f %f %f) texinfo:%i[%s] plane:%i\n", nummapbrushes, nummapbrushsides, planepts[0][0], planepts[0][1], planepts[0][2], planepts[1][0], planepts[1][1], planepts[1][2], planepts[2][0], planepts[2][1], planepts[2][2], side->texinfo, td.name, planenum); nummapbrushsides++; b->numsides++; } while (1); /* get the content for the entire brush */ b->contentFlags = BrushContents(b); /* copy all set face contentflags to the brush contentflags */ for (int m = 0; m < b->numsides; m++) b->contentFlags |= b->original_sides[m].contentFlags; /* set DETAIL, TRANSLUCENT contentflags on all faces, if they have been set on any. * called separately, if in check/fix mode */ if (!checkOrFix) CheckPropagateParserContentFlags(b); /* allow detail brushes to be removed */ if (config.nodetail && (b->contentFlags & CONTENTS_DETAIL)) { b->numsides = 0; return; } /* allow water brushes to be removed */ if (config.nowater && (b->contentFlags & CONTENTS_WATER)) { b->numsides = 0; return; } /* create windings for sides and bounds for brush */ MakeBrushWindings(b); Verb_Printf(VERB_DUMP, "Brush %i mins (%f %f %f) maxs (%f %f %f)\n", nummapbrushes, b->mbBox.mins[0], b->mbBox.mins[1], b->mbBox.mins[2], b->mbBox.maxs[0], b->mbBox.maxs[1], b->mbBox.maxs[2]); /* origin brushes are removed, but they set * the rotation origin for the rest of the brushes (like func_door) * in the entity. After the entire entity is parsed, the planenums * and texinfos will be adjusted for the origin brush */ if (!checkOrFix && (b->contentFlags & CONTENTS_ORIGIN)) { char string[32]; vec3_t origin; if (num_entities == 1) { Sys_Error("Entity %i, Brush %i: origin brushes not allowed in world" , b->entitynum, b->brushnum); return; } b->mbBox.getCenter(origin); Com_sprintf(string, sizeof(string), "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); SetKeyValue(&entities[b->entitynum], "origin", string); Verb_Printf(VERB_EXTRA, "Entity %i, Brush %i: set origin to %s\n", b->entitynum, b->brushnum, string); VectorCopy(origin, entities[b->entitynum].origin); /* don't keep this brush */ b->numsides = 0; return; } if (!checkOrFix) AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; }
/** * @sa ProcessLevel * @return The node num */ static int32_t ConstructLevelNodes_r (const int levelnum, const vec3_t cmins, const vec3_t cmaxs, int entityNum) { bspbrush_t *list; tree_t *tree; vec3_t diff, bmins, bmaxs; int32_t nn[3]; node_t *node; /* calculate bounds, stop if no brushes are available */ if (!MapBrushesBounds(brush_start, brush_end, levelnum, cmins, cmaxs, bmins, bmaxs)) return LEAFNODE; Verb_Printf(VERB_DUMP, "ConstructLevelNodes_r: lv=%i (%f %f %f) (%f %f %f)\n", levelnum, cmins[0], cmins[1], cmins[2], cmaxs[0], cmaxs[1], cmaxs[2]); VectorSubtract(bmaxs, bmins, diff); /* Com_Printf("(%i): %i %i: (%i %i) (%i %i) -> (%i %i) (%i %i)\n", levelnum, (int)diff[0], (int)diff[1], (int)cmins[0], (int)cmins[1], (int)cmaxs[0], (int)cmaxs[1], (int)bmins[0], (int)bmins[1], (int)bmaxs[0], (int)bmaxs[1]); */ if (diff[0] > SPLIT_BRUSH_SIZE || diff[1] > SPLIT_BRUSH_SIZE) { /* continue subdivision */ /* split the remaining hull at the middle of the longer axis */ vec3_t nmins, nmaxs; int n; if (diff[1] > diff[0]) n = 1; else n = 0; VectorCopy(bmins, nmins); VectorCopy(bmaxs, nmaxs); nmaxs[n] -= diff[n] / 2; /* Com_Printf(" (%i %i) (%i %i)\n", (int)nmins[0], (int)nmins[1], (int)nmaxs[0], (int)nmaxs[1]); */ nn[0] = ConstructLevelNodes_r(levelnum, nmins, nmaxs, entityNum); nmins[n] += diff[n] / 2; nmaxs[n] += diff[n] / 2; /* Com_Printf(" (%i %i) (%i %i)\n", (int)nmins[0], (int)nmins[1], (int)nmaxs[0], (int)nmaxs[1]); */ nn[1] = ConstructLevelNodes_r(levelnum, nmins, nmaxs, entityNum); } else { /* no children */ nn[0] = LEAFNODE; nn[1] = LEAFNODE; } /* add v_epsilon to avoid clipping errors */ VectorSubtract(bmins, v_epsilon, bmins); VectorAdd(bmaxs, v_epsilon, bmaxs); /* Call BeginModel only to initialize brush pointers */ BeginModel(entityNum); list = MakeBspBrushList(brush_start, brush_end, levelnum, bmins, bmaxs); if (!list) { nn[2] = LEAFNODE; return BuildNodeChildren(nn); } if (!config.nocsg) list = ChopBrushes(list); /* begin model creation now */ tree = BuildTree(list, bmins, bmaxs); MakeTreePortals(tree); MarkVisibleSides(tree, brush_start, brush_end); MakeFaces(tree->headnode); FixTjuncs(tree->headnode); if (!config.noprune) PruneNodes(tree->headnode); /* correct bounds */ node = tree->headnode; VectorAdd(bmins, v_epsilon, node->mins); VectorSubtract(bmaxs, v_epsilon, node->maxs); /* finish model */ nn[2] = WriteBSP(tree->headnode); FreeTree(tree); /* Store created nodes */ return BuildNodeChildren(nn); }
/** * @brief Create lights out of patches and entity lights * @sa LightWorld * @sa BuildPatch */ void BuildLights (void) { int i; light_t* l; /* surfaces */ for (i = 0; i < MAX_MAP_FACES; i++) { /* iterate subdivided patches */ for(const patch_t* p = face_patches[i]; p; p = p->next) { if (VectorEmpty(p->light)) continue; numlights[config.compile_for_day]++; l = Mem_AllocType(light_t); VectorCopy(p->origin, l->origin); l->next = lights[config.compile_for_day]; lights[config.compile_for_day] = l; l->type = emit_surface; l->intensity = ColorNormalize(p->light, l->color); l->intensity *= p->area * config.surface_scale; } } /* entities (skip the world) */ for (i = 1; i < num_entities; i++) { float intensity; const char* color; const char* target; const entity_t* e = &entities[i]; const char* name = ValueForKey(e, "classname"); if (!Q_strstart(name, "light")) continue; /* remove those lights that are only for the night version */ if (config.compile_for_day) { const int spawnflags = atoi(ValueForKey(e, "spawnflags")); if (!(spawnflags & 1)) /* day */ continue; } numlights[config.compile_for_day]++; l = Mem_AllocType(light_t); GetVectorForKey(e, "origin", l->origin); /* link in */ l->next = lights[config.compile_for_day]; lights[config.compile_for_day] = l; intensity = FloatForKey(e, "light"); if (!intensity) intensity = 300.0; color = ValueForKey(e, "_color"); if (color && color[0] != '\0'){ if (sscanf(color, "%f %f %f", &l->color[0], &l->color[1], &l->color[2]) != 3) Sys_Error("Invalid _color entity property given: %s", color); ColorNormalize(l->color, l->color); } else VectorSet(l->color, 1.0, 1.0, 1.0); l->intensity = intensity * config.entity_scale; l->type = emit_point; target = ValueForKey(e, "target"); if (target[0] != '\0' || Q_streq(name, "light_spot")) { l->type = emit_spotlight; l->stopdot = FloatForKey(e, "_cone"); if (!l->stopdot) l->stopdot = 10; l->stopdot = cos(l->stopdot * torad); if (target[0] != '\0') { /* point towards target */ entity_t* e2 = FindTargetEntity(target); if (!e2) Com_Printf("WARNING: light at (%i %i %i) has missing target '%s' - e.g. create an info_null that has a 'targetname' set to '%s'\n", (int)l->origin[0], (int)l->origin[1], (int)l->origin[2], target, target); else { vec3_t dest; GetVectorForKey(e2, "origin", dest); VectorSubtract(dest, l->origin, l->normal); VectorNormalize(l->normal); } } else { /* point down angle */ const float angle = FloatForKey(e, "angle"); if (angle == ANGLE_UP) { l->normal[0] = l->normal[1] = 0.0; l->normal[2] = 1.0; } else if (angle == ANGLE_DOWN) { l->normal[0] = l->normal[1] = 0.0; l->normal[2] = -1.0; } else { l->normal[2] = 0; l->normal[0] = cos(angle * torad); l->normal[1] = sin(angle * torad); } } } } /* handle worldspawn light settings */ { const entity_t* e = &entities[0]; const char* ambient, *light, *angles, *color; float f; int i; if (config.compile_for_day) { ambient = ValueForKey(e, "ambient_day"); light = ValueForKey(e, "light_day"); angles = ValueForKey(e, "angles_day"); color = ValueForKey(e, "color_day"); } else { ambient = ValueForKey(e, "ambient_night"); light = ValueForKey(e, "light_night"); angles = ValueForKey(e, "angles_night"); color = ValueForKey(e, "color_night"); } if (light[0] != '\0') sun_intensity = atoi(light); if (angles[0] != '\0') { VectorClear(sun_angles); if (sscanf(angles, "%f %f", &sun_angles[0], &sun_angles[1]) != 2) Sys_Error("wrong angles values given: '%s'", angles); AngleVectors(sun_angles, sun_normal, nullptr, nullptr); } if (color[0] != '\0') { GetVectorFromString(color, sun_color); ColorNormalize(sun_color, sun_color); } if (ambient[0] != '\0') GetVectorFromString(ambient, sun_ambient_color); /* optionally pull brightness from worldspawn */ f = FloatForKey(e, "brightness"); if (f > 0.0) config.brightness = f; /* saturation as well */ f = FloatForKey(e, "saturation"); if (f > 0.0) config.saturation = f; else Verb_Printf(VERB_EXTRA, "Invalid saturation setting (%f) in worldspawn found\n", f); f = FloatForKey(e, "contrast"); if (f > 0.0) config.contrast = f; else Verb_Printf(VERB_EXTRA, "Invalid contrast setting (%f) in worldspawn found\n", f); /* lightmap resolution downscale (e.g. 4 = 1 << 4) */ i = atoi(ValueForKey(e, "quant")); if (i >= 1 && i <= 6) config.lightquant = i; else Verb_Printf(VERB_EXTRA, "Invalid quant setting (%i) in worldspawn found\n", i); } Verb_Printf(VERB_EXTRA, "light settings:\n * intensity: %i\n * sun_angles: pitch %f yaw %f\n * sun_color: %f:%f:%f\n * sun_ambient_color: %f:%f:%f\n", sun_intensity, sun_angles[0], sun_angles[1], sun_color[0], sun_color[1], sun_color[2], sun_ambient_color[0], sun_ambient_color[1], sun_ambient_color[2]); Verb_Printf(VERB_NORMAL, "%i direct lights for %s lightmap\n", numlights[config.compile_for_day], (config.compile_for_day ? "day" : "night")); }