From 1aaf446097b5f16f7006075d6f57c7f950effba1 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:51:42 +0800 Subject: [PATCH] Addition of zombie spawns, player spawns and zones for zombies. Clipmap AABB generation largely modified. --- src/ObjLoading/Game/T6/BSP/BSP.h | 40 ++ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 192 +++++++- src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 18 +- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 430 ++++++++++++++---- .../Game/T6/BSP/Linker/ClipMapLinker.h | 17 +- .../Game/T6/BSP/Linker/ComWorldLinker.cpp | 9 +- .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 61 ++- .../Game/T6/BSP/Linker/GfxWorldLinker.h | 2 +- .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 51 ++- 9 files changed, 672 insertions(+), 148 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 8e49b62e..95e94ebc 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -69,6 +69,9 @@ namespace BSP std::vector indices; std::vector materials; std::vector xmodels; + + size_t staticSurfaceCount; + std::vector internal_scriptSurfaces; }; enum BSPLightType @@ -113,6 +116,37 @@ namespace BSP vec3_t origin; }; + struct BSPZoneZM + { + vec3_t origin; + std::string zoneName; + std::string zSpawnerGroupName; + std::string spawnpointGroupName; + size_t modelIndex; + }; + + struct BSPSpawnPointZM + { + std::string spawnpointGroupName; + vec3_t origin; + vec3_t forward; + }; + + struct BSPZSpawnerZM + { + std::string zSpawnerGroupName; + vec3_t origin; + vec3_t forward; + }; + + struct BSPModel + { + bool isGfxModel; + + size_t surfaceIndex; + size_t surfaceCount; + }; + struct BSPData { std::string name; @@ -125,6 +159,12 @@ namespace BSP std::vector lights; std::vector spawnpoints; std::vector pathnodes; + + std::vector zSpawnPoints; + std::vector zSpawners; + std::vector zZones; + + std::vector models; }; // BSPGameConstants: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e801fae6..a5365d5c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -612,26 +612,14 @@ namespace assert(node.extras->spawnpoint); Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - BSPSpawnPoint spawnPoint; - - if (!node.extras->spawnpoint->compare("attacker")) - spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; - else if (!node.extras->spawnpoint->compare("defender")) - spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; - else if (!node.extras->spawnpoint->compare("all")) - spawnPoint.type = SPAWNPOINT_TYPE_ALL; - else - { - con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); - return false; - } Eigen::Vector4f position(0, 0, 0, 1.0f); Eigen::Vector4f transformedPosition = nodeMatrix * position; - spawnPoint.origin.x = transformedPosition.x(); - spawnPoint.origin.y = transformedPosition.y(); - spawnPoint.origin.z = transformedPosition.z(); - RhcToLhcCoordinates(spawnPoint.origin.v); + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); // GLTF default direction is +Y up Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); @@ -639,12 +627,152 @@ namespace Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; outputDirection.normalize(); - spawnPoint.forward.x = outputDirection.x(); - spawnPoint.forward.y = outputDirection.y(); - spawnPoint.forward.z = outputDirection.z(); - RhcToLhcCoordinates(spawnPoint.forward.v); + vec3_t forward; + forward.x = outputDirection.x(); + forward.y = outputDirection.y(); + forward.z = outputDirection.z(); + RhcToLhcCoordinates(forward.v); - m_bsp->spawnpoints.emplace_back(spawnPoint); + if (m_bsp->isZombiesMap) + { + BSPSpawnPointZM spawnPoint; + spawnPoint.origin = origin; + spawnPoint.forward = forward; + spawnPoint.spawnpointGroupName = *node.extras->spawnpoint; + m_bsp->zSpawnPoints.emplace_back(spawnPoint); + } + else + { + BSPSpawnPoint spawnPoint; + spawnPoint.origin = origin; + spawnPoint.forward = forward; + + if (!node.extras->spawnpoint->compare("attacker")) + spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; + else if (!node.extras->spawnpoint->compare("defender")) + spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; + else if (!node.extras->spawnpoint->compare("all")) + spawnPoint.type = SPAWNPOINT_TYPE_ALL; + else + { + con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); + return false; + } + + m_bsp->spawnpoints.emplace_back(spawnPoint); + } + + return true; + } + + size_t addScriptModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + if (!node.mesh || !jRoot.meshes) + throw new GltfLoadException("Script model created with no mesh data"); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + + if (mesh.primitives.size() == 0) + throw new GltfLoadException("Script model created with no mesh data"); + + BSPModel model; + model.isGfxModel = m_is_world_gfx; + model.surfaceIndex = m_curr_bsp_world->internal_scriptSurfaces.size(); + model.surfaceCount = mesh.primitives.size(); + m_bsp->models.emplace_back(model); + + for (const auto& primitive : mesh.primitives) + { + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); + + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; + + BSPSurface surface; + if (primitive.material) + surface.materialIndex = *primitive.material; + else + surface.materialIndex = m_emptyMaterialIndex; + vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; + CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); + + m_curr_bsp_world->internal_scriptSurfaces.emplace_back(surface); + } + + return m_bsp->models.size(); // script model index starts at 1 + } + + bool addZoneNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->zone); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + BSPZoneZM zone; + zone.origin = origin; + zone.zoneName = *node.extras->zone; + zone.zSpawnerGroupName = node.extras->zspawner_group.value_or(""); + zone.spawnpointGroupName = node.extras->spawnpoint_group.value_or(""); + zone.modelIndex = addScriptModel(jRoot, node); + m_bsp->zZones.emplace_back(zone); + + return true; + } + + bool addZSpawnerNode(const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->zspawner); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vec3_t origin; + origin.x = transformedPosition.x(); + origin.y = transformedPosition.y(); + origin.z = transformedPosition.z(); + RhcToLhcCoordinates(origin.v); + + // GLTF default direction is +Y up + Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + vec3_t forward; + forward.x = outputDirection.x(); + forward.y = outputDirection.y(); + forward.z = outputDirection.z(); + RhcToLhcCoordinates(forward.v); + + BSPZSpawnerZM spawner; + spawner.origin = origin; + spawner.forward = forward; + spawner.zSpawnerGroupName = *node.extras->zspawner; + m_bsp->zSpawners.emplace_back(spawner); return true; } @@ -659,11 +787,20 @@ namespace if (node.extras->xmodel) return addXModelNode(jRoot, node); - if (m_is_world_gfx && node.extras->spawnpoint) + if (!m_is_world_gfx && node.extras->spawnpoint) return addSpawnPointNode(node); - if (m_is_world_gfx && node.extras->pathnode) + if (!m_is_world_gfx && node.extras->pathnode) return addPathNode_Node(node); + + if (!m_is_world_gfx && m_bsp->isZombiesMap) + { + if (node.extras->zone) + return addZoneNode(jRoot, node); + + if (node.extras->zspawner) + return addZSpawnerNode(node); + } } if (node.mesh) @@ -1017,6 +1154,13 @@ namespace LoadMaterials(jRoot); TraverseNodes(jRoot); // requires materials and lights + + size_t staticSurfaceCount = m_curr_bsp_world->surfaces.size(); + for (auto& model : m_bsp->models) + model.surfaceIndex += staticSurfaceCount; + m_curr_bsp_world->staticSurfaceCount = staticSurfaceCount; + m_curr_bsp_world->surfaces.insert( + m_curr_bsp_world->surfaces.end(), m_curr_bsp_world->internal_scriptSurfaces.begin(), m_curr_bsp_world->internal_scriptSurfaces.end()); } catch (const GltfLoadException& e) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 00002025..feb8cb50 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -167,17 +167,17 @@ namespace BSP float quatZ = quat->v[2]; float quatW = quat->v[3]; - float xx = (quatX * 2.0) * quatX; - float xy = (quatX * 2.0) * quatY; - float xz = (quatX * 2.0) * quatZ; - float xw = (quatX * 2.0) * quatW; + float xx = (quatX * 2.0f) * quatX; + float xy = (quatX * 2.0f) * quatY; + float xz = (quatX * 2.0f) * quatZ; + float xw = (quatX * 2.0f) * quatW; - float yy = (quatY * 2.0) * quatY; - float yz = (quatY * 2.0) * quatZ; - float yw = (quatY * 2.0) * quatW; + float yy = (quatY * 2.0f) * quatY; + float yz = (quatY * 2.0f) * quatZ; + float yw = (quatY * 2.0f) * quatW; - float zz = (quatZ * 2.0) * quatZ; - float zw = (quatZ * 2.0) * quatW; + float zz = (quatZ * 2.0f) * quatZ; + float zw = (quatZ * 2.0f) * quatW; axis->x = 1.0f - (zz + yy); axis->y = zw + xy; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index b471fd3b..d957d0f8 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -120,40 +120,6 @@ namespace BSP clipMap->ropes = m_memory.Alloc(clipMap->max_ropes); } - void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) - { - // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) - clipMap->numSubModels = 1; - clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); - for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) - { - vec3_t vertex = clipMap->verts[vertIdx]; - if (vertIdx == 0) - { - clipMap->cmodels[0].mins = vertex; - clipMap->cmodels[0].maxs = vertex; - } - else - BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); - } - clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; - - // The world sub model has no leafs associated with it - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = 0; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = nullptr; - } - bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) { // it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid. @@ -236,6 +202,115 @@ namespace BSP return true; } + void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs); + void addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); + + void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp) + { + // Submodels are used for the world and map ent collision (triggers, bomb zones, etc) + clipMap->numSubModels = static_cast(bsp->models.size() + 1); + clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + + // first model is always the world model + for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) + { + vec3_t vertex = clipMap->verts[vertIdx]; + if (vertIdx == 0) + { + clipMap->cmodels[0].mins = vertex; + clipMap->cmodels[0].maxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs); + } + clipMap->cmodels[0].radius = BSPUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + // The world sub model has no data apart from the bounds + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = 0; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = nullptr; + + for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) + { + auto clipModel = &clipMap->cmodels[modelIdx + 1]; + auto& bspMpdel = bsp->models.at(modelIdx); + + if (bspMpdel.isGfxModel) + { + clipModel->mins.x = 0.0f; + clipModel->mins.y = 0.0f; + clipModel->mins.z = 0.0f; + clipModel->maxs.x = 0.0f; + clipModel->maxs.y = 0.0f; + clipModel->maxs.z = 0.0f; + clipModel->radius = 0.0f; + clipModel->leaf.brushContents = 0; + clipModel->leaf.mins.x = 0.0f; + clipModel->leaf.mins.y = 0.0f; + clipModel->leaf.mins.z = 0.0f; + clipModel->leaf.maxs.x = 0.0f; + clipModel->leaf.maxs.y = 0.0f; + clipModel->leaf.maxs.z = 0.0f; + clipModel->leaf.leafBrushNode = 0; + clipModel->leaf.cluster = 0; + clipModel->info = nullptr; + continue; + } + + std::vector partitionIndexes; + for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + { + ColSurface& surf = collisionSurfaceVec.at(bspMpdel.surfaceIndex + surfIdx); + for (size_t partitionIdx = 0; partitionIdx < surf.partitionCount; partitionIdx++) + { + size_t clipMapPartitionIndex = surf.partitionStartIndex + partitionIdx; + partitionIndexes.emplace_back(clipMapPartitionIndex); + vec3_t mins; + vec3_t maxs; + calculatePartitionAABB(clipMap, &clipMap->partitions[clipMapPartitionIndex], mins, maxs); + if (surfIdx == 0 && partitionIdx == 0) + { + clipModel->mins = mins; + clipModel->maxs = maxs; + } + else + BSPUtil::updateAABB(mins, maxs, clipModel->mins, clipModel->maxs); + } + } + clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; + + int terrainContents = 0; + size_t firstCollAabbIndex = 0; + size_t collAabbCount = 0; + addAABBTreeFromPartitions(clipMap, partitionIndexes, &collAabbCount, &firstCollAabbIndex, &terrainContents); + clipModel->leaf.terrainContents = terrainContents; + clipModel->leaf.firstCollAabbIndex = static_cast(firstCollAabbIndex); + clipModel->leaf.collAabbCount = static_cast(collAabbCount); + + // no brush + clipModel->leaf.brushContents = 0; + clipModel->leaf.mins.x = 0.0f; + clipModel->leaf.mins.y = 0.0f; + clipModel->leaf.mins.z = 0.0f; + clipModel->leaf.maxs.x = 0.0f; + clipModel->leaf.maxs.y = 0.0f; + clipModel->leaf.maxs.z = 0.0f; + clipModel->leaf.leafBrushNode = 0; + clipModel->leaf.cluster = 0; + clipModel->info = nullptr; + } + } + // out_mins and out_maxs are initialised in the function void calculatePartitionAABB(clipMap_t* clipMap, CollisionPartition* partition, vec3_t& out_mins, vec3_t& out_maxs) { @@ -257,10 +332,129 @@ namespace BSP struct uniqueMatData { - unsigned int materialIndex; - std::vector objectIndexes; + size_t materialIndex; + std::vector partitionIndexes; }; + void ClipMapLinker::addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) + { + size_t partitionCount = partitions.size(); + assert(partitionCount > 0); + if (partitionCount > highestPartitionCountForAABB) + highestPartitionCountForAABB = partitionCount; + + std::vector uniqueMaterials; + for (size_t partArrayIdx = 0; partArrayIdx < partitionCount; partArrayIdx++) + { + int partitionIdx = partitions.at(partArrayIdx); + size_t materialIndex = collisionSurfaceVec.at(partitionToColSurfaceMap.at(partitionIdx)).materialIndex; + bool foundIdx = false; + for (auto& uniqueMat : uniqueMaterials) + { + if (uniqueMat.materialIndex == materialIndex) + { + uniqueMat.partitionIndexes.emplace_back(partitionIdx); + foundIdx = true; + break; + } + } + if (!foundIdx) + { + uniqueMatData data; + data.materialIndex = materialIndex; + data.partitionIndexes.emplace_back(partitionIdx); + uniqueMaterials.emplace_back(data); + } + } + + // BO2 has a maximum limit of 128 children per AABB tree (essentially), + // so this is fixed by adding multiple parent AABB trees that hold 128 children each + size_t totalParentCount = 0; + for (auto& matData : uniqueMaterials) + { + size_t objCount = matData.partitionIndexes.size(); + size_t result = objCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = objCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + result++; + totalParentCount += result; + } + + // every parent node needs to be contiguous in memory + size_t parentAABBArrayIndex = AABBTreeVec.size(); + AABBTreeVec.resize(AABBTreeVec.size() + totalParentCount); + *out_parentCount = totalParentCount; + *out_parentStartIndex = parentAABBArrayIndex; + + for (auto& matData : uniqueMaterials) + { + size_t matPartCount = matData.partitionIndexes.size(); + size_t parentCount = matPartCount / BSPGameConstants::MAX_AABB_TREE_CHILDREN; + size_t remainder = matPartCount % BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (remainder > 0) + parentCount++; + + size_t unaddedObjectCount = matPartCount; + size_t addedObjectCount = 0; + for (size_t parentIdx = 0; parentIdx < parentCount; parentIdx++) + { + size_t currChildObjectCount = BSPGameConstants::MAX_AABB_TREE_CHILDREN; + if (unaddedObjectCount <= BSPGameConstants::MAX_AABB_TREE_CHILDREN) + currChildObjectCount = unaddedObjectCount; + else + unaddedObjectCount -= BSPGameConstants::MAX_AABB_TREE_CHILDREN; + + vec3_t parentMins; + vec3_t parentMaxs; + size_t childObjectStartIndex = AABBTreeVec.size(); + for (size_t objectIdx = 0; objectIdx < currChildObjectCount; objectIdx++) + { + // create a child AABBTree with the partition and add it to AABBTreeVec + int partitionIndex = matData.partitionIndexes.at(addedObjectCount + objectIdx); + CollisionPartition* partition = &clipMap->partitions[partitionIndex]; + vec3_t childMins; + vec3_t childMaxs; + calculatePartitionAABB(clipMap, partition, childMins, childMaxs); + + CollisionAabbTree childAABBTree; + childAABBTree.materialIndex = static_cast(matData.materialIndex); + childAABBTree.childCount = 0; + childAABBTree.u.partitionIndex = partitionIndex; + childAABBTree.origin = BSPUtil::calcMiddleOfAABB(childMins, childMaxs); + childAABBTree.halfSize = BSPUtil::calcHalfSizeOfAABB(childMins, childMaxs); + AABBTreeVec.emplace_back(childAABBTree); + + // update the parent AABB with the child AABB + if (objectIdx == 0) + { + parentMins = childMins; + parentMaxs = childMaxs; + } + else + BSPUtil::updateAABB(childMins, childMaxs, parentMins, parentMaxs); + } + + CollisionAabbTree parentAABB; + parentAABB.materialIndex = static_cast(matData.materialIndex); + parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs); + parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs); + parentAABB.childCount = static_cast(currChildObjectCount); + parentAABB.u.firstChildIndex = static_cast(childObjectStartIndex); + AABBTreeVec.at(parentAABBArrayIndex + parentIdx) = parentAABB; + + addedObjectCount += currChildObjectCount; + } + + parentAABBArrayIndex += parentCount; + } + + *out_treeContents = 0; + for (auto& matData : uniqueMaterials) + *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; + } + + /* void ClipMapLinker::addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents) { assert(tree->isLeaf); @@ -268,15 +462,15 @@ namespace BSP size_t leafObjectCount = bspLeaf->getObjectCount(); assert(leafObjectCount > 0); - if (leafObjectCount > highestLeafObjectCount) - highestLeafObjectCount = leafObjectCount; + if (leafObjectCount > highestPartitionCountForAABB) + highestPartitionCountForAABB = leafObjectCount; // the material index of the AABB tree is only checked for the parent node, so each parent has only children with the same material std::vector uniqueMaterials; for (size_t objIdx = 0; objIdx < leafObjectCount; objIdx++) { int partitionIdx = bspLeaf->getObject(objIdx)->partitionIndex; - unsigned materialIndex = partitionToMaterialMap[partitionIdx]; + size_t materialIndex = collisionSurfaceVec.at(partitionToColSurfaceMap.at(partitionIdx)).materialIndex; bool foundIdx = false; for (auto& uniqueMat : uniqueMaterials) { @@ -382,6 +576,7 @@ namespace BSP for (auto& matData : uniqueMaterials) *out_treeContents |= clipMap->info.materials[matData.materialIndex].contentFlags; } + */ constexpr vec3_t normalX = {1.0f, 0.0f, 0.0f}; constexpr vec3_t normalY = {0.0f, 1.0f, 0.0f}; @@ -432,7 +627,10 @@ namespace BSP size_t parentCount = 0; size_t parentStartIndex = 0; int treeContents = 0; - addAABBTreeFromLeaf(clipMap, tree, &parentCount, &parentStartIndex, &treeContents); + std::vector partitions; + for (size_t objIdx = 0; objIdx < tree->leaf->getObjectCount(); objIdx++) + partitions.emplace_back(tree->leaf->getObject(objIdx)->partitionIndex); + addAABBTreeFromPartitions(clipMap, partitions, &parentCount, &parentStartIndex, &treeContents); leaf.collAabbCount = static_cast(parentCount); leaf.firstCollAabbIndex = static_cast(parentStartIndex); leaf.terrainContents = treeContents; @@ -514,15 +712,20 @@ namespace BSP } std::unique_ptr tree = std::make_unique(worldMins.x, worldMins.y, worldMins.z, worldMaxs.x, worldMaxs.y, worldMaxs.z, 0); - for (int partitionIdx = 0; partitionIdx < clipMap->partitionCount; partitionIdx++) + for (size_t surfIdx = 0; surfIdx < bsp->colWorld.staticSurfaceCount; surfIdx++) // only add the surfaces the player will collide with { - vec3_t partitionMins; - vec3_t partitionMaxs; - CollisionPartition* partition = &clipMap->partitions[partitionIdx]; - calculatePartitionAABB(clipMap, partition, partitionMins, partitionMaxs); - std::shared_ptr currObject = - std::make_shared(partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, partitionIdx); - tree->addObjectToTree(std::move(currObject)); + ColSurface& colSurface = collisionSurfaceVec.at(surfIdx); + for (size_t partitionIdx = 0; partitionIdx < colSurface.partitionCount; partitionIdx++) + { + vec3_t partitionMins; + vec3_t partitionMaxs; + size_t clipMapPartitionIdx = colSurface.partitionStartIndex + partitionIdx; + CollisionPartition* partition = &clipMap->partitions[clipMapPartitionIdx]; + calculatePartitionAABB(clipMap, partition, partitionMins, partitionMaxs); + std::shared_ptr currObject = std::make_shared( + partitionMins.x, partitionMins.y, partitionMins.z, partitionMaxs.x, partitionMaxs.y, partitionMaxs.z, clipMapPartitionIdx); + tree->addObjectToTree(std::move(currObject)); + } } // load planes, nodes, leafs, and AABB trees @@ -540,15 +743,11 @@ namespace BSP clipMap->leafs = m_memory.Alloc(leafVec.size()); memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size()); - clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); - clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); - memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); - // The plane of each node have the same index for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++) clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx]; - con::info("Highest leaf object count: {}", highestLeafObjectCount); + con::info("Highest AABB tree partition count: {}", highestPartitionCountForAABB); return true; } @@ -568,38 +767,36 @@ namespace BSP for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) clipMap->verts[vertIdx] = bsp->colWorld.vertices[vertIdx].pos; - // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's - // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. std::vector triIndexVec; + std::vector partitionVec; + std::vector uniqueIndicesVec; for (BSPSurface& surface : bsp->colWorld.surfaces) { int indexOfFirstIndex = surface.indexOfFirstIndex; - int indexOfFirstVertex = surface.indexOfFirstVertex; - for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) - { - int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; - triIndexVec.emplace_back(triIndex); - } - } - // the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion - clipMap->triCount = static_cast(triIndexVec.size() / 3); - clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); - memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - - // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. - std::vector partitionVec; - std::vector uniqueIndicesVec; - for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) - { - BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; - - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower int indexOfFirstTri = surface.indexOfFirstIndex / 3; int indexOfFirstVertex = surface.indexOfFirstVertex; + + ColSurface colSurface; + colSurface.materialIndex = surface.materialIndex; + colSurface.partitionCount = surface.triCount; + colSurface.partitionStartIndex = partitionVec.size(); + collisionSurfaceVec.emplace_back(colSurface); + for (int triIdx = 0; triIdx < surface.triCount; triIdx++) { + // The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's + // index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. + int triIndex0 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 0] + indexOfFirstVertex; + int triIndex1 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 1] + indexOfFirstVertex; + int triIndex2 = bsp->colWorld.indices[indexOfFirstIndex + (triIdx * 3) + 2] + indexOfFirstVertex; + triIndexVec.emplace_back(triIndex0); + triIndexVec.emplace_back(triIndex1); + triIndexVec.emplace_back(triIndex2); + + // partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. + // partitions are made for each triangle, not one for each surface. + // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower CollisionPartition partition; partition.triCount = 1; partition.firstTri = indexOfFirstTri + triIdx; @@ -608,16 +805,20 @@ namespace BSP partition.fuind = static_cast(uniqueIndicesVec.size()); // All tri indices are unique since there is only one tri per partition - uint16_t* tri = clipMap->triIndices[partition.firstTri]; - uniqueIndicesVec.emplace_back(tri[0]); - uniqueIndicesVec.emplace_back(tri[1]); - uniqueIndicesVec.emplace_back(tri[2]); + int indexOfTriIndex = partition.firstTri * 3; + uniqueIndicesVec.emplace_back(triIndex0); + uniqueIndicesVec.emplace_back(triIndex1); + uniqueIndicesVec.emplace_back(triIndex2); partitionVec.emplace_back(partition); - - partitionToMaterialMap.emplace_back(surface.materialIndex); + partitionToColSurfaceMap.emplace_back(collisionSurfaceVec.size() - 1); // -1 as the colSurface has already been added } } + + clipMap->triCount = static_cast(triIndexVec.size() / 3); + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + clipMap->partitionCount = static_cast(partitionVec.size()); clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); @@ -628,6 +829,64 @@ namespace BSP return true; + //// The clipmap index buffer has a unique index for each vertex in the world, compared to the gfxworld's + //// index buffer having a unique index for each vertex on a surface. This code converts gfxworld indices to clipmap indices. + // std::vector triIndexVec; + // for (BSPSurface& surface : bsp->colWorld.surfaces) + //{ + // int indexOfFirstIndex = surface.indexOfFirstIndex; + // int indexOfFirstVertex = surface.indexOfFirstVertex; + // for (int indexIdx = 0; indexIdx < surface.triCount * 3; indexIdx++) + // { + // int triIndex = bsp->colWorld.indices[indexOfFirstIndex + indexIdx] + indexOfFirstVertex; + // triIndexVec.emplace_back(triIndex); + // } + // } + //// the reinterpret_cast is used as triIndices is just a pointer to an array of indicies, and static_cast can't safely do the conversion + // clipMap->triCount = static_cast(triIndexVec.size() / 3); + // clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + // memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + // + //// partitions are "containers" for vertices. BSP tree leafs contain a list of these partitions to determine the collision within a leaf. + // std::vector partitionVec; + // std::vector uniqueIndicesVec; + // for (size_t surfIdx = 0; surfIdx < bsp->colWorld.surfaces.size(); surfIdx++) + //{ + // BSPSurface& surface = bsp->colWorld.surfaces[surfIdx]; + // + // // partitions are made for each triangle, not one for each surface. + // // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). + // // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower + // int indexOfFirstTri = surface.indexOfFirstIndex / 3; + // int indexOfFirstVertex = surface.indexOfFirstVertex; + // for (int triIdx = 0; triIdx < surface.triCount; triIdx++) + // { + // CollisionPartition partition; + // partition.triCount = 1; + // partition.firstTri = indexOfFirstTri + triIdx; + // + // partition.nuinds = 3; + // partition.fuind = static_cast(uniqueIndicesVec.size()); + // + // // All tri indices are unique since there is only one tri per partition + // uint16_t* tri = clipMap->triIndices[partition.firstTri]; + // uniqueIndicesVec.emplace_back(tri[0]); + // uniqueIndicesVec.emplace_back(tri[1]); + // uniqueIndicesVec.emplace_back(tri[2]); + // + // partitionVec.emplace_back(partition); + // + // partitionToMaterialMap.emplace_back(surface.materialIndex); + // } + // } + // clipMap->partitionCount = static_cast(partitionVec.size()); + // clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); + // memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size()); + // + // clipMap->info.nuinds = static_cast(uniqueIndicesVec.size()); + // clipMap->info.uinds = m_memory.Alloc(uniqueIndicesVec.size()); + // memcpy(clipMap->info.uinds, uniqueIndicesVec.data(), sizeof(uint16_t) * uniqueIndicesVec.size()); + /* // Proper unique index creation code kept for future use int totalUindCount = 0; @@ -759,6 +1018,11 @@ namespace BSP clipMap->triEdgeIsWalkable = m_memory.Alloc(walkableEdgeSize); memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + // multiple functions add to AABBTreeVec, so it is added to the clipmap last + clipMap->aabbTreeCount = static_cast(AABBTreeVec.size()); + clipMap->aabbTrees = m_memory.Alloc(AABBTreeVec.size()); + memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size()); + return clipMap; } } // namespace BSP diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h index 5c8d4842..74498886 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -8,6 +8,13 @@ namespace BSP { + struct ColSurface + { + size_t materialIndex; + size_t partitionCount; + size_t partitionStartIndex; + }; + class ClipMapLinker { public: @@ -30,9 +37,13 @@ namespace BSP std::vector nodeVec; std::vector leafVec; std::vector AABBTreeVec; - size_t highestLeafObjectCount = 0; - std::vector partitionToMaterialMap; - void addAABBTreeFromLeaf(clipMap_t* clipMap, BSPTree* tree, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); + size_t highestPartitionCountForAABB = 0; + + std::vector collisionSurfaceVec; + std::vector partitionToColSurfaceMap; + + void addAABBTreeFromPartitions( + clipMap_t* clipMap, std::vector& partitions, size_t* out_parentCount, size_t* out_parentStartIndex, int* out_treeContents); int16_t loadBSPNode(clipMap_t* clipMap, BSPTree* tree, bool isRoot); bool loadBSPTree(clipMap_t* clipMap, BSPData* bsp); bool loadPartitions(clipMap_t* clipMap, BSPData* bsp); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 9bec483e..c52de71e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -2,8 +2,7 @@ #include "../BSPUtil.h" -#define _USE_MATH_DEFINES -#include +#include namespace BSP { @@ -184,9 +183,9 @@ namespace BSP light->type = GFX_LIGHT_TYPE_OMNI; light->defName = "white_light_cube"; light->roundness = 0.0f; - light->cosHalfFovInner = cosf(30.0f * (M_PI / 180.0f)); - light->cosHalfFovOuter = cosf(55.0f * (M_PI / 180.0f)); - light->cosHalfFovExpanded = cosf(55.0f * (M_PI / 180.0f)); + light->cosHalfFovInner = cosf(30.0f * (std::numbers::pi_v / 180.0f)); + light->cosHalfFovOuter = cosf(55.0f * (std::numbers::pi_v / 180.0f)); + light->cosHalfFovExpanded = cosf(55.0f * (std::numbers::pi_v / 180.0f)); } setLightCommonValues(light, bspLight); } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 4139885e..03e10c02 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -541,11 +541,10 @@ namespace BSP } } - void GfxWorldLinker::loadModels(GfxWorld* gfxWorld) + void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld) { // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) - // Right now there is only one submodel, the world sub model - gfxWorld->modelCount = 1; + gfxWorld->modelCount = static_cast(bsp->models.size() + 1); gfxWorld->models = m_memory.Alloc(gfxWorld->modelCount); // first model is always the world model @@ -559,23 +558,40 @@ namespace BSP gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - // Other models aren't implemented yet - // Code kept for future use - // for (size_t i = 0; i < entityModelList.size(); i++) - //{ - // auto currEntModel = &gfxWorld->models[i + 1]; - // entModelBounds currEntModelBounds = entityModelList[i]; - // - // currEntModel->startSurfIndex = 0; - // currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces - // currEntModel->bounds[0].x = currEntModelBounds.mins.x; - // currEntModel->bounds[0].y = currEntModelBounds.mins.y; - // currEntModel->bounds[0].z = currEntModelBounds.mins.z; - // currEntModel->bounds[1].x = currEntModelBounds.maxs.x; - // currEntModel->bounds[1].y = currEntModelBounds.maxs.y; - // currEntModel->bounds[1].z = currEntModelBounds.maxs.z; - // memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - //} + for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) + { + auto currEntModel = &gfxWorld->models[modelIdx + 1]; + auto& bspMpdel = bsp->models.at(modelIdx); + + if (bspMpdel.isGfxModel) + { + currEntModel->startSurfIndex = bspMpdel.surfaceIndex; + currEntModel->surfaceCount = bspMpdel.surfaceCount; + for (size_t surfIdx = 0; surfIdx < bspMpdel.surfaceCount; surfIdx++) + { + GfxSurface* surf = &gfxWorld->dpvs.surfaces[bspMpdel.surfaceIndex + surfIdx]; + if (surfIdx == 0) + { + currEntModel->bounds[0] = surf->bounds[0]; + currEntModel->bounds[1] = surf->bounds[1]; + } + else + BSPUtil::updateAABB(surf->bounds[0], surf->bounds[1], currEntModel->bounds[0], currEntModel->bounds[1]); + } + } + else + { + currEntModel->startSurfIndex = 0; + currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->bounds[0].x = 0.0f; + currEntModel->bounds[0].y = 0.0f; + currEntModel->bounds[0].z = 0.0f; + currEntModel->bounds[1].x = 0.0f; + currEntModel->bounds[1].y = 0.0f; + currEntModel->bounds[1].z = 0.0f; + } + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + } } void GfxWorldLinker::loadSunData(GfxWorld* gfxWorld) @@ -687,7 +703,8 @@ namespace BSP void GfxWorldLinker::loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) { - std::string skyBoxName = "skybox_" + projInfo->name; + // std::string skyBoxName = "skybox_" + projInfo->name; + std::string skyBoxName = "skybox_zm_transit"; gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); if (m_context.LoadDependency(skyBoxName) == nullptr) @@ -806,7 +823,7 @@ namespace BSP loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces - loadModels(gfxWorld); + loadModels(bsp, gfxWorld); // requires surfaces loadSunData(gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 6baf4f2e..e594cd01 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -25,7 +25,7 @@ namespace BSP void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); void loadLightGrid(GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld); - void loadModels(GfxWorld* gfxWorld); + void loadModels(BSPData* bsp, GfxWorld* gfxWorld); bool loadReflectionProbeData(GfxWorld* gfxWorld); bool loadLightmapData(GfxWorld* gfxWorld); void loadSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 062f2fb9..3b90c6f9 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -67,7 +67,7 @@ namespace void addSpawnsToEntString(BSP::BSPData* bsp, std::string& entityString) { - if (bsp->spawnpoints.size() == 0) + if (!bsp->isZombiesMap && bsp->spawnpoints.size() == 0) { con::info("No spawnpoints found, setting all spawns to (0, 0, 0)"); BSP::BSPSpawnPoint defaultSpawnPoint; @@ -127,6 +127,52 @@ namespace } } + void addZombiesEntitiesToEntString(BSP::BSPData* bsp, std::string& entityString) + { + for (auto& zone : bsp->zZones) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"info_volume\"\n"); + entityString.append("\"script_noteworthy\" \"player_volume\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", zone.zoneName)); + entityString.append(std::format("\"target\" \"{}\"\n", zone.zSpawnerGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zone.origin))); + entityString.append(std::format("\"model\" \"*{}\"\n", zone.modelIndex)); + entityString.append("}\n"); + + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append(std::format("\"targetname\" \"player_respawn_point\"\n")); + entityString.append(std::format("\"script_noteworthy\" \"{}\"\n", zone.zoneName)); + entityString.append(std::format("\"target\" \"{}\"\n", zone.spawnpointGroupName)); + entityString.append("}\n"); + } + + for (auto& zSpawner : bsp->zSpawners) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append("\"script_noteworthy\" \"riser_location\"\n"); + entityString.append("\"script_string\" \"find_flesh\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", zSpawner.zSpawnerGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(zSpawner.origin))); + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(zSpawner.forward); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + entityString.append("}\n"); + } + + for (auto& spawnPoint : bsp->zSpawnPoints) + { + entityString.append("{\n"); + entityString.append("\"classname\" \"script_struct\"\n"); + entityString.append(std::format("\"targetname\" \"{}\"\n", spawnPoint.spawnpointGroupName)); + entityString.append(std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(spawnPoint.origin))); + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); + entityString.append(std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles))); + entityString.append("}\n"); + } + } + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ "entities": [ { @@ -180,6 +226,9 @@ namespace BSP addPathNodesToEntString(bsp, entityString); + if (bsp->isZombiesMap) + addZombiesEntitiesToEntString(bsp, entityString); + MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str());