/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code. =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "qe3.h" #include "CityBuilder.h" #include "../../framework/DeclEntityDef.h" extern int mapModified; namespace { struct cityStats_t { int brushes; int entities; int buildings; int streets; }; struct cityBounds_t { idVec3 mins; idVec3 maxs; }; struct cityMesh_t { idStr model; idBounds bounds; idVec3 size; }; static const float CITY_DEFAULT_HALF_SIZE = 6144.0f; static const float CITY_MIN_SIZE = 4096.0f; static const float CITY_MAX_SIZE = 65536.0f; static const float CITY_ROAD_PITCH = 1408.0f; static const float CITY_ROAD_WIDTH = 384.0f; static const float CITY_SIDEWALK_WIDTH = 128.0f; static const float CITY_CURB_WIDTH = 16.0f; static const float CITY_CEILING_HEIGHT = 1536.0f; static const float CITY_TEXTURE_SIZE = 1024.0f; static const float CITY_ROAD_TEXTURE_LENGTH = 512.0f; static const float CITY_SIDEWALK_TEXTURE_LENGTH = 256.0f; static const float CITY_BUILDING_EDGE_SETBACK = 0.0f; static const float CITY_BUILDING_ALLEY = 0.0f; static const float CITY_MODEL_LOT_EXTRA = 64.0f; static const float CITY_POINT_LIGHT_RADIUS = 300000.0f; static const char* CITY_MAT_MARS_DIRT = "textures/mars_citybuilder/mars_dirt"; static const char* CITY_MAT_MARS_ROAD = "textures/mars_citybuilder/mars_road_dusty_center"; static const char* CITY_MAT_MARS_ROAD_EAST_WEST = "textures/mars_citybuilder/mars_road_dusty_center_east_west"; static const char* CITY_MAT_MARS_ROAD_INTERSECTION = "textures/mars_citybuilder/mars_road_dusty_intersection"; static const char* CITY_MAT_MARS_SIDEWALK = "textures/mars_citybuilder/mars_sidewalk_dusty"; static const char* CITY_MESH_DEF = "citybuilder_meshes"; static const char* CITY_FALLBACK_MESH = "models/offices/office.md5static"; static const idVec3 CITY_POINT_LIGHT_ORIGIN(-3200.0f, 3336.0f, 0.0f); static const idVec3 CITY_POINT_LIGHT_COLOR(242.0f / 255.0f, 160.0f / 255.0f, 130.0f / 255.0f); static float City_ClampFloat(float v, float minValue, float maxValue) { if (v < minValue) { return minValue; } if (v > maxValue) { return maxValue; } return v; } static int City_ClampInt(int v, int minValue, int maxValue) { if (v < minValue) { return minValue; } if (v > maxValue) { return maxValue; } return v; } static unsigned int City_Hash(int x, int y, int salt) { unsigned int h = (unsigned int)(x * 73856093) ^ (unsigned int)(y * 19349663) ^ (unsigned int)(salt * 83492791); h ^= h >> 13; h *= 1274126177u; h ^= h >> 16; return h; } static float City_Random01(int x, int y, int salt) { return (City_Hash(x, y, salt) & 0xffff) / 65535.0f; } static float City_TexScale(float worldUnits) { return worldUnits / CITY_TEXTURE_SIZE; } static float City_TexShift(float worldCoord, float scale) { if (scale == 0.0f) { return 0.0f; } float shift = fmod(-worldCoord / scale, CITY_TEXTURE_SIZE); if (shift < 0.0f) { shift += CITY_TEXTURE_SIZE; } return shift; } static float City_TexShiftFloorXCenter(float worldCenter, float scale) { if (scale == 0.0f) { return 0.0f; } float shift = fmod(CITY_TEXTURE_SIZE * 0.5f - worldCenter / scale, CITY_TEXTURE_SIZE); if (shift < 0.0f) { shift += CITY_TEXTURE_SIZE; } return shift; } static float City_TexShiftFloorYCenter(float worldCenter, float scale) { if (scale == 0.0f) { return 0.0f; } float shift = fmod(CITY_TEXTURE_SIZE * 0.5f + worldCenter / scale, CITY_TEXTURE_SIZE); if (shift < 0.0f) { shift += CITY_TEXTURE_SIZE; } return shift; } static void City_SetTexdef(texdef_t& td, const char* material, float scale0 = 0.5f, float scale1 = 0.5f, float rotate = 0.0f, float shift0 = 0.0f, float shift1 = 0.0f) { td.SetName(material); td.shift[0] = shift0; td.shift[1] = shift1; td.rotate = rotate; td.scale[0] = scale0; td.scale[1] = scale1; } static idEditorBrush* City_AddWorldBrushUV(const idVec3& mins, const idVec3& maxs, const char* material, float scale0, float scale1, float rotate, float shift0, float shift1, cityStats_t& stats) { if (mins.x >= maxs.x || mins.y >= maxs.y || mins.z >= maxs.z || world_entity == NULL) { return NULL; } texdef_t td; City_SetTexdef(td, material, scale0, scale1, rotate, shift0, shift1); idEditorBrush* b = Brush_Create(mins, maxs, &td); if (b == NULL) { return NULL; } b->Brush_AddToList(&active_brushes); world_entity->Entity_LinkBrush(b); b->Brush_Build(); Undo_EndBrush(b); stats.brushes++; return b; } static idEditorBrush* City_AddWorldBrush(const idVec3& mins, const idVec3& maxs, const char* material, cityStats_t& stats) { return City_AddWorldBrushUV(mins, maxs, material, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, stats); } static idEditorBrush* City_AddVerticalRoadBrush(float x, float y0, float y1, const cityBounds_t& bounds, cityStats_t& stats) { const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float scale0 = City_TexScale(CITY_ROAD_WIDTH); const float scale1 = City_TexScale(CITY_ROAD_TEXTURE_LENGTH); const idVec3 mins(x - roadHalf, y0, bounds.mins.z); const idVec3 maxs(x + roadHalf, y1, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_ROAD_EAST_WEST, scale0, scale1, 0.0f, City_TexShiftFloorXCenter(x, scale0), City_TexShift(maxs.y, scale1), stats); } static idEditorBrush* City_AddHorizontalRoadBrush(float x0, float x1, float y, const cityBounds_t& bounds, cityStats_t& stats) { const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float scale0 = City_TexScale(CITY_ROAD_TEXTURE_LENGTH); const float scale1 = City_TexScale(CITY_ROAD_WIDTH); const idVec3 mins(x0, y - roadHalf, bounds.mins.z); const idVec3 maxs(x1, y + roadHalf, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_ROAD, scale0, scale1, 0.0f, City_TexShift(mins.x, scale0), City_TexShiftFloorYCenter(y, scale1), stats); } static idEditorBrush* City_AddIntersectionRoadBrush(float x, float y, const cityBounds_t& bounds, cityStats_t& stats) { const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float scale = City_TexScale(CITY_ROAD_WIDTH); const idVec3 mins(x - roadHalf, y - roadHalf, bounds.mins.z); const idVec3 maxs(x + roadHalf, y + roadHalf, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_ROAD_INTERSECTION, scale, scale, 0.0f, City_TexShiftFloorXCenter(x, scale), City_TexShiftFloorYCenter(y, scale), stats); } static idEditorBrush* City_AddVerticalSidewalkBrush(float x0, float x1, float y0, float y1, const cityBounds_t& bounds, cityStats_t& stats) { const float scale0 = City_TexScale(CITY_SIDEWALK_WIDTH); const float scale1 = City_TexScale(CITY_SIDEWALK_TEXTURE_LENGTH); const idVec3 mins(x0, y0, bounds.mins.z); const idVec3 maxs(x1, y1, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_SIDEWALK, scale0, scale1, 0.0f, City_TexShift(mins.x, scale0), City_TexShift(mins.y, scale1), stats); } static idEditorBrush* City_AddHorizontalSidewalkBrush(float x0, float x1, float y0, float y1, const cityBounds_t& bounds, cityStats_t& stats) { const float scale0 = City_TexScale(CITY_SIDEWALK_TEXTURE_LENGTH); const float scale1 = City_TexScale(CITY_SIDEWALK_WIDTH); const idVec3 mins(x0, y0, bounds.mins.z); const idVec3 maxs(x1, y1, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_SIDEWALK, scale0, scale1, 0.0f, City_TexShift(mins.x, scale0), City_TexShift(mins.y, scale1), stats); } static idEditorBrush* City_AddSidewalkCornerBrush(float x0, float x1, float y0, float y1, const cityBounds_t& bounds, cityStats_t& stats) { const float scale = City_TexScale(CITY_SIDEWALK_WIDTH); const idVec3 mins(x0, y0, bounds.mins.z); const idVec3 maxs(x1, y1, bounds.mins.z + 8.0f); return City_AddWorldBrushUV(mins, maxs, CITY_MAT_MARS_SIDEWALK, scale, scale, 0.0f, City_TexShift(mins.x, scale), City_TexShift(maxs.y, scale), stats); } static void City_SetVecKey(idEditorEntity* ent, const char* key, const idVec3& value) { ent->SetKeyValue(key, va("%i %i %i", (int)value.x, (int)value.y, (int)value.z)); } static idEditorEntity* City_AddFixedEntity(const char* classname, const idVec3& origin, cityStats_t& stats) { idEditorEntity* ent = idEditorEntity::Entity_New(); if (ent == NULL) { return NULL; } ent->brushes.onext = ent->brushes.oprev = &ent->brushes; ent->origin = origin; ent->SetKeyValue("classname", classname); City_SetVecKey(ent, "origin", origin); ent->Entity_Name(false); ent->Entity_PostParse(&active_brushes); ent->Entity_AddToList(&entities); g_qeglobals.d_num_entities++; Undo_EndEntity(ent); for (idEditorBrush* b = ent->brushes.onext; b != &ent->brushes; b = b->onext) { b->Brush_Build(); Undo_EndBrush(b); stats.brushes++; } stats.entities++; return ent; } static idEditorEntity* City_AddModelEntity(const cityMesh_t& mesh, const idVec3& origin, cityStats_t& stats) { idEditorEntity* ent = idEditorEntity::Entity_New(); if (ent == NULL) { return NULL; } ent->brushes.onext = ent->brushes.oprev = &ent->brushes; ent->origin = origin; ent->SetKeyValue("classname", "func_static"); ent->SetKeyValue("model", mesh.model.c_str()); City_SetVecKey(ent, "origin", origin); ent->Entity_Name(false); ent->Entity_PostParse(&active_brushes); ent->Entity_AddToList(&entities); g_qeglobals.d_num_entities++; Undo_EndEntity(ent); for (idEditorBrush* b = ent->brushes.onext; b != &ent->brushes; b = b->onext) { b->Brush_Build(); Undo_EndBrush(b); stats.brushes++; } stats.entities++; stats.buildings++; return ent; } static bool City_AddMeshToCatalog(const char* modelName, idList& meshes) { if (modelName == NULL || modelName[0] == '\0') { return false; } idRenderModel* model = renderModelManager->FindModel(modelName); if (model == NULL || model->IsDefaultModel()) { common->Printf("Automatic City Builder: could not load mesh '%s'\n", modelName); return false; } cityMesh_t& mesh = meshes.Alloc(); mesh.model = modelName; mesh.bounds = model->Bounds(NULL); mesh.size = mesh.bounds[1] - mesh.bounds[0]; if (mesh.size.x <= 0.0f) { mesh.size.x = 1.0f; } if (mesh.size.y <= 0.0f) { mesh.size.y = 1.0f; } if (mesh.size.z <= 0.0f) { mesh.size.z = 1.0f; } return true; } static void City_LoadMeshCatalog(idList& meshes) { meshes.Clear(); const idDeclEntityDef* decl = static_cast(declManager->FindType(DECL_ENTITYDEF, CITY_MESH_DEF, false)); if (decl != NULL) { const idKeyValue* kv = decl->dict.MatchPrefix("mesh", NULL); while (kv != NULL) { City_AddMeshToCatalog(kv->GetValue().c_str(), meshes); kv = decl->dict.MatchPrefix("mesh", kv); } } if (meshes.Num() == 0) { City_AddMeshToCatalog(CITY_FALLBACK_MESH, meshes); } } static const cityMesh_t* City_ChooseMesh(const idList& meshes, int blockX, int blockY, int lotX, int lotY) { if (meshes.Num() == 0) { return NULL; } return &meshes[City_Hash(blockX * 41 + lotX, blockY * 43 + lotY, 97) % meshes.Num()]; } static float City_ModelLotSize(const idList& meshes) { float lotSize = 0.0f; for (int i = 0; i < meshes.Num(); i++) { float required = meshes[i].size.x; if (meshes[i].size.y > required) { required = meshes[i].size.y; } required += CITY_MODEL_LOT_EXTRA; if (required > lotSize) { lotSize = required; } } if (lotSize <= 0.0f) { lotSize = CITY_ROAD_PITCH; } return lotSize; } static float City_RequiredRoadPitch(const idList& meshes) { const float clearHalf = CITY_ROAD_WIDTH * 0.5f + CITY_SIDEWALK_WIDTH; return City_ModelLotSize(meshes) + 2.0f * (CITY_BUILDING_EDGE_SETBACK + CITY_BUILDING_ALLEY + clearHalf); } static cityBounds_t City_GetTargetBounds() { cityBounds_t bounds; bounds.mins.Set(-CITY_DEFAULT_HALF_SIZE, -CITY_DEFAULT_HALF_SIZE, 0.0f); bounds.maxs.Set(CITY_DEFAULT_HALF_SIZE, CITY_DEFAULT_HALF_SIZE, CITY_CEILING_HEIGHT); if (selected_brushes.next != NULL && selected_brushes.next != &selected_brushes) { idVec3 mins(999999.0f, 999999.0f, 999999.0f); idVec3 maxs(-999999.0f, -999999.0f, -999999.0f); for (idEditorBrush* b = selected_brushes.next; b != &selected_brushes; b = b->next) { for (int i = 0; i < 3; i++) { if (b->mins[i] < mins[i]) { mins[i] = b->mins[i]; } if (b->maxs[i] > maxs[i]) { maxs[i] = b->maxs[i]; } } } if ((maxs.x - mins.x) >= 128.0f && (maxs.y - mins.y) >= 128.0f) { bounds.mins.x = mins.x; bounds.mins.y = mins.y; bounds.mins.z = mins.z; bounds.maxs.x = maxs.x; bounds.maxs.y = maxs.y; bounds.maxs.z = mins.z + CITY_CEILING_HEIGHT; } } const float centerX = (bounds.mins.x + bounds.maxs.x) * 0.5f; const float centerY = (bounds.mins.y + bounds.maxs.y) * 0.5f; float width = City_ClampFloat(bounds.maxs.x - bounds.mins.x, CITY_MIN_SIZE, CITY_MAX_SIZE); float depth = City_ClampFloat(bounds.maxs.y - bounds.mins.y, CITY_MIN_SIZE, CITY_MAX_SIZE); width = floor(width / 64.0f) * 64.0f; depth = floor(depth / 64.0f) * 64.0f; bounds.mins.x = centerX - width * 0.5f; bounds.maxs.x = centerX + width * 0.5f; bounds.mins.y = centerY - depth * 0.5f; bounds.maxs.y = centerY + depth * 0.5f; bounds.maxs.z = bounds.mins.z + CITY_CEILING_HEIGHT; return bounds; } static void City_AddVerticalStreetEdges(float x, float y0, float y1, const cityBounds_t& bounds, cityStats_t& stats) { if (y1 <= y0) { return; } const float z0 = bounds.mins.z; const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float sideHalf = roadHalf + CITY_SIDEWALK_WIDTH; City_AddVerticalSidewalkBrush(x - sideHalf, x - roadHalf, y0, y1, bounds, stats); City_AddVerticalSidewalkBrush(x + roadHalf, x + sideHalf, y0, y1, bounds, stats); City_AddWorldBrush( idVec3(x - roadHalf - CITY_CURB_WIDTH, y0, z0 + 8.0f), idVec3(x - roadHalf, y1, z0 + 14.0f), "textures/base_wall/steel", stats); City_AddWorldBrush( idVec3(x + roadHalf, y0, z0 + 8.0f), idVec3(x + roadHalf + CITY_CURB_WIDTH, y1, z0 + 14.0f), "textures/base_wall/steel", stats); } static void City_AddHorizontalStreetEdges(float y, float x0, float x1, const cityBounds_t& bounds, cityStats_t& stats) { if (x1 <= x0) { return; } const float z0 = bounds.mins.z; const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float sideHalf = roadHalf + CITY_SIDEWALK_WIDTH; City_AddHorizontalSidewalkBrush(x0, x1, y - sideHalf, y - roadHalf, bounds, stats); City_AddHorizontalSidewalkBrush(x0, x1, y + roadHalf, y + sideHalf, bounds, stats); City_AddWorldBrush( idVec3(x0, y - roadHalf - CITY_CURB_WIDTH, z0 + 8.0f), idVec3(x1, y - roadHalf, z0 + 14.0f), "textures/base_wall/steel", stats); City_AddWorldBrush( idVec3(x0, y + roadHalf, z0 + 8.0f), idVec3(x1, y + roadHalf + CITY_CURB_WIDTH, z0 + 14.0f), "textures/base_wall/steel", stats); } static void City_AddVerticalStreetEdges(float x, const cityBounds_t& bounds, const idList& blockers, cityStats_t& stats) { const float clearHalf = CITY_ROAD_WIDTH * 0.5f + CITY_SIDEWALK_WIDTH; float y0 = bounds.mins.y; for (int i = 0; i < blockers.Num(); i++) { const float y1 = blockers[i] - clearHalf; City_AddVerticalStreetEdges(x, y0, y1, bounds, stats); y0 = blockers[i] + clearHalf; } City_AddVerticalStreetEdges(x, y0, bounds.maxs.y, bounds, stats); } static void City_AddHorizontalStreetEdges(float y, const cityBounds_t& bounds, const idList& blockers, cityStats_t& stats) { const float clearHalf = CITY_ROAD_WIDTH * 0.5f + CITY_SIDEWALK_WIDTH; float x0 = bounds.mins.x; for (int i = 0; i < blockers.Num(); i++) { const float x1 = blockers[i] - clearHalf; City_AddHorizontalStreetEdges(y, x0, x1, bounds, stats); x0 = blockers[i] + clearHalf; } City_AddHorizontalStreetEdges(y, x0, bounds.maxs.x, bounds, stats); } static void City_AddIntersectionSidewalkCorners(float x, float y, const cityBounds_t& bounds, cityStats_t& stats) { const float roadHalf = CITY_ROAD_WIDTH * 0.5f; const float sideHalf = roadHalf + CITY_SIDEWALK_WIDTH; City_AddSidewalkCornerBrush(x - sideHalf, x - roadHalf, y - sideHalf, y - roadHalf, bounds, stats); City_AddSidewalkCornerBrush(x + roadHalf, x + sideHalf, y - sideHalf, y - roadHalf, bounds, stats); City_AddSidewalkCornerBrush(x - sideHalf, x - roadHalf, y + roadHalf, y + sideHalf, bounds, stats); City_AddSidewalkCornerBrush(x + roadHalf, x + sideHalf, y + roadHalf, y + sideHalf, bounds, stats); } static void City_AddStreetSign(float x, float y, int avenue, int street, const cityBounds_t& bounds, cityStats_t& stats) { const float z0 = bounds.mins.z; const float poleX = x + CITY_ROAD_WIDTH * 0.5f + 44.0f; const float poleY = y - CITY_ROAD_WIDTH * 0.5f - 44.0f; City_AddWorldBrush(idVec3(poleX - 4.0f, poleY - 4.0f, z0 + 8.0f), idVec3(poleX + 4.0f, poleY + 4.0f, z0 + 128.0f), "textures/base_wall/steel", stats); City_AddWorldBrush(idVec3(poleX - 56.0f, poleY - 5.0f, z0 + 112.0f), idVec3(poleX + 56.0f, poleY + 5.0f, z0 + 136.0f), "textures/base_wall/greenpanel1", stats); City_AddWorldBrush(idVec3(poleX - 5.0f, poleY - 56.0f, z0 + 140.0f), idVec3(poleX + 5.0f, poleY + 56.0f, z0 + 164.0f), "textures/base_wall/greenpanel1", stats); common->Printf("Automatic City Builder: street node A%i / S%i at %.0f %.0f\n", avenue + 1, street + 1, x, y); } static void City_AddRoads(const cityBounds_t& bounds, const idList& meshes, idList& roadX, idList& roadY, cityStats_t& stats) { const float roadHalf = CITY_ROAD_WIDTH * 0.5f; float roadPitch = City_RequiredRoadPitch(meshes); if (roadPitch <= 0.0f) { roadPitch = CITY_ROAD_PITCH; } const int xRoads = City_ClampInt((int)((bounds.maxs.x - bounds.mins.x) / roadPitch), 2, 64); const int yRoads = City_ClampInt((int)((bounds.maxs.y - bounds.mins.y) / roadPitch), 2, 64); const float xStep = (bounds.maxs.x - bounds.mins.x) / (float)xRoads; const float yStep = (bounds.maxs.y - bounds.mins.y) / (float)yRoads; for (int i = 1; i < xRoads; i++) { roadX.Append(bounds.mins.x + xStep * i); } for (int i = 1; i < yRoads; i++) { roadY.Append(bounds.mins.y + yStep * i); } for (int i = 0; i < roadX.Num(); i++) { const float x = roadX[i]; for (int segment = 0; segment <= roadY.Num(); segment++) { const float y0 = (segment == 0) ? bounds.mins.y : roadY[segment - 1] + roadHalf; const float y1 = (segment == roadY.Num()) ? bounds.maxs.y : roadY[segment] - roadHalf; City_AddVerticalRoadBrush(x, y0, y1, bounds, stats); } City_AddVerticalStreetEdges(x, bounds, roadY, stats); stats.streets++; } for (int i = 0; i < roadY.Num(); i++) { const float y = roadY[i]; for (int segment = 0; segment <= roadX.Num(); segment++) { const float x0 = (segment == 0) ? bounds.mins.x : roadX[segment - 1] + roadHalf; const float x1 = (segment == roadX.Num()) ? bounds.maxs.x : roadX[segment] - roadHalf; City_AddHorizontalRoadBrush(x0, x1, y, bounds, stats); } City_AddHorizontalStreetEdges(y, bounds, roadX, stats); stats.streets++; } for (int x = 0; x < roadX.Num(); x++) { for (int y = 0; y < roadY.Num(); y++) { City_AddIntersectionRoadBrush(roadX[x], roadY[y], bounds, stats); City_AddIntersectionSidewalkCorners(roadX[x], roadY[y], bounds, stats); if (((x + y) & 1) == 0) { City_AddStreetSign(roadX[x], roadY[y], x, y, bounds, stats); } } } } static void City_AddDirtLot(const cityBounds_t& bounds, float minX, float minY, float maxX, float maxY, cityStats_t& stats) { if (maxX - minX <= 1.0f || maxY - minY <= 1.0f) { return; } City_AddWorldBrush( idVec3(minX, minY, bounds.mins.z), idVec3(maxX, maxY, bounds.mins.z + 8.0f), CITY_MAT_MARS_DIRT, stats); } static void City_AddDirtLots(const cityBounds_t& bounds, const idList& roadX, const idList& roadY, cityStats_t& stats) { const float clearHalf = CITY_ROAD_WIDTH * 0.5f + CITY_SIDEWALK_WIDTH; float y0 = bounds.mins.y; for (int y = 0; y <= roadY.Num(); y++) { const float y1 = (y == roadY.Num()) ? bounds.maxs.y : roadY[y] - clearHalf; float x0 = bounds.mins.x; for (int x = 0; x <= roadX.Num(); x++) { const float x1 = (x == roadX.Num()) ? bounds.maxs.x : roadX[x] - clearHalf; City_AddDirtLot(bounds, x0, y0, x1, y1, stats); if (x < roadX.Num()) { x0 = roadX[x] + clearHalf; } } if (y < roadY.Num()) { y0 = roadY[y] + clearHalf; } } } static void City_AddBlockBuildings(const cityBounds_t& bounds, const idList& meshes, float minX, float minY, float maxX, float maxY, int blockX, int blockY, cityStats_t& stats) { minX += CITY_BUILDING_EDGE_SETBACK; minY += CITY_BUILDING_EDGE_SETBACK; maxX -= CITY_BUILDING_EDGE_SETBACK; maxY -= CITY_BUILDING_EDGE_SETBACK; if (maxX - minX < 128.0f || maxY - minY < 128.0f) { return; } const float modelLotSize = City_ModelLotSize(meshes); const int lotsX = City_ClampInt((int)((maxX - minX) / modelLotSize), 1, 2); const int lotsY = City_ClampInt((int)((maxY - minY) / modelLotSize), 1, 2); const float stepX = (maxX - minX) / (float)lotsX; const float stepY = (maxY - minY) / (float)lotsY; for (int y = 0; y < lotsY; y++) { for (int x = 0; x < lotsX; x++) { if ((City_Hash(blockX * 17 + x, blockY * 19 + y, 7) % 100) < 8) { continue; } const cityMesh_t* mesh = City_ChooseMesh(meshes, blockX, blockY, x, y); if (mesh == NULL) { continue; } float cellMinX = minX + x * stepX + CITY_BUILDING_ALLEY; float cellMinY = minY + y * stepY + CITY_BUILDING_ALLEY; float cellMaxX = minX + (x + 1) * stepX - CITY_BUILDING_ALLEY; float cellMaxY = minY + (y + 1) * stepY - CITY_BUILDING_ALLEY; const float roomX = cellMaxX - cellMinX; const float roomY = cellMaxY - cellMinY; if (roomX < mesh->size.x + CITY_MODEL_LOT_EXTRA || roomY < mesh->size.y + CITY_MODEL_LOT_EXTRA) { continue; } const float centerX = (cellMinX + cellMaxX) * 0.5f; const float centerY = (cellMinY + cellMaxY) * 0.5f; const idVec3 modelCenter = (mesh->bounds[0] + mesh->bounds[1]) * 0.5f; idVec3 origin; origin.x = centerX - modelCenter.x; origin.y = centerY - modelCenter.y; origin.z = bounds.mins.z + 8.0f - mesh->bounds[0].z; City_AddModelEntity(*mesh, origin, stats); } } } static void City_AddBuildings(const cityBounds_t& bounds, const idList& roadX, const idList& roadY, const idList& meshes, cityStats_t& stats) { const float clearHalf = CITY_ROAD_WIDTH * 0.5f + CITY_SIDEWALK_WIDTH; float y0 = bounds.mins.y; for (int y = 0; y <= roadY.Num(); y++) { const float y1 = (y == roadY.Num()) ? bounds.maxs.y : roadY[y] - clearHalf; float x0 = bounds.mins.x; for (int x = 0; x <= roadX.Num(); x++) { const float x1 = (x == roadX.Num()) ? bounds.maxs.x : roadX[x] - clearHalf; City_AddBlockBuildings(bounds, meshes, x0, y0, x1, y1, x, y, stats); if (x < roadX.Num()) { x0 = roadX[x] + clearHalf; } } if (y < roadY.Num()) { y0 = roadY[y] + clearHalf; } } } static void City_AddPlayerStart(const cityBounds_t& bounds, cityStats_t& stats) { City_AddFixedEntity("info_player_start", idVec3(bounds.mins.x + 192.0f, bounds.mins.y + 192.0f, bounds.mins.z + 48.0f), stats); } static void City_AddPointLight(cityStats_t& stats) { idEditorEntity* ent = City_AddFixedEntity("light", CITY_POINT_LIGHT_ORIGIN, stats); if (ent == NULL) { return; } ent->SetKeyValue("_color", va("%.3f %.3f %.3f", CITY_POINT_LIGHT_COLOR.x, CITY_POINT_LIGHT_COLOR.y, CITY_POINT_LIGHT_COLOR.z)); ent->SetKeyValue("light_radius", va("%i %i %i", (int)CITY_POINT_LIGHT_RADIUS, (int)CITY_POINT_LIGHT_RADIUS, (int)CITY_POINT_LIGHT_RADIUS)); } } // namespace void Radiant_GenerateAutomaticCity() { if (world_entity == NULL) { Sys_Status("Automatic City Builder needs an open map\n"); Sys_Beep(); return; } Sys_BeginWait(); Undo_Start("automatic city"); cityStats_t stats; stats.brushes = 0; stats.entities = 0; stats.buildings = 0; stats.streets = 0; idList roadX; idList roadY; idList meshes; const cityBounds_t bounds = City_GetTargetBounds(); City_LoadMeshCatalog(meshes); Select_Deselect(); City_AddRoads(bounds, meshes, roadX, roadY, stats); City_AddDirtLots(bounds, roadX, roadY, stats); City_AddBuildings(bounds, roadX, roadY, meshes, stats); City_AddPlayerStart(bounds, stats); City_AddPointLight(stats); Map_BuildBrushData(); Sys_UpdateWindows(W_ALL); mapModified = 1; Undo_End(); Sys_EndWait(); common->Printf("Automatic City Builder: %i streets, %i buildings, %i brushes, %i entities\n", stats.streets, stats.buildings, stats.brushes, stats.entities); Sys_Status(va("Automatic City Builder: %i streets, %i buildings generated", stats.streets, stats.buildings), 0); }