2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-06-06 08:42:35 +00:00

Addition of zombie spawns, player spawns and zones for zombies. Clipmap AABB generation largely modified.

This commit is contained in:
LJW-Dev
2026-03-27 23:51:42 +08:00
committed by Jan Laupetin
parent 98cd833206
commit 1aaf446097
9 changed files with 672 additions and 148 deletions
+40
View File
@@ -69,6 +69,9 @@ namespace BSP
std::vector<uint16_t> indices;
std::vector<BSPMaterial> materials;
std::vector<BSPXModel> xmodels;
size_t staticSurfaceCount;
std::vector<BSPSurface> 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<BSPLight> lights;
std::vector<BSPSpawnPoint> spawnpoints;
std::vector<BSPPathNode> pathnodes;
std::vector<BSPSpawnPointZM> zSpawnPoints;
std::vector<BSPZSpawnerZM> zSpawners;
std::vector<BSPZoneZM> zZones;
std::vector<BSPModel> models;
};
// BSPGameConstants:
+168 -24
View File
@@ -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)
{
+9 -9
View File
@@ -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;
@@ -120,40 +120,6 @@ namespace BSP
clipMap->ropes = m_memory.Alloc<rope_t>(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<cmodel_t>(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<int>& 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<unsigned int>(bsp->models.size() + 1);
clipMap->cmodels = m_memory.Alloc<cmodel_t>(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<int> 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<uint16_t>(firstCollAabbIndex);
clipModel->leaf.collAabbCount = static_cast<uint16_t>(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<size_t> objectIndexes;
size_t materialIndex;
std::vector<int> partitionIndexes;
};
void ClipMapLinker::addAABBTreeFromPartitions(
clipMap_t* clipMap, std::vector<int>& 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<uniqueMatData> 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<uint16_t>(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<uint16_t>(matData.materialIndex);
parentAABB.origin = BSPUtil::calcMiddleOfAABB(parentMins, parentMaxs);
parentAABB.halfSize = BSPUtil::calcHalfSizeOfAABB(parentMins, parentMaxs);
parentAABB.childCount = static_cast<uint16_t>(currChildObjectCount);
parentAABB.u.firstChildIndex = static_cast<int>(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<uniqueMatData> 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<int> 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<uint16_t>(parentCount);
leaf.firstCollAabbIndex = static_cast<uint16_t>(parentStartIndex);
leaf.terrainContents = treeContents;
@@ -514,15 +712,20 @@ namespace BSP
}
std::unique_ptr<BSPTree> tree = std::make_unique<BSPTree>(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<BSPObject> currObject =
std::make_shared<BSPObject>(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<BSPObject> currObject = std::make_shared<BSPObject>(
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<cLeaf_s>(leafVec.size());
memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size());
clipMap->aabbTreeCount = static_cast<unsigned int>(AABBTreeVec.size());
clipMap->aabbTrees = m_memory.Alloc<CollisionAabbTree>(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<uint16_t> triIndexVec;
std::vector<CollisionPartition> partitionVec;
std::vector<uint16_t> 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<int>(triIndexVec.size() / 3);
clipMap->triIndices = reinterpret_cast<uint16_t(*)[3]>(m_memory.Alloc<uint16_t>(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<CollisionPartition> partitionVec;
std::vector<uint16_t> 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<int>(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<int>(triIndexVec.size() / 3);
clipMap->triIndices = reinterpret_cast<uint16_t(*)[3]>(m_memory.Alloc<uint16_t>(triIndexVec.size()));
memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size());
clipMap->partitionCount = static_cast<int>(partitionVec.size());
clipMap->partitions = m_memory.Alloc<CollisionPartition>(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<uint16_t> 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<int>(triIndexVec.size() / 3);
// clipMap->triIndices = reinterpret_cast<uint16_t(*)[3]>(m_memory.Alloc<uint16_t>(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<CollisionPartition> partitionVec;
// std::vector<uint16_t> 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<int>(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<int>(partitionVec.size());
// clipMap->partitions = m_memory.Alloc<CollisionPartition>(clipMap->partitionCount);
// memcpy(clipMap->partitions, partitionVec.data(), sizeof(CollisionPartition) * partitionVec.size());
//
// clipMap->info.nuinds = static_cast<int>(uniqueIndicesVec.size());
// clipMap->info.uinds = m_memory.Alloc<uint16_t>(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<char>(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<unsigned int>(AABBTreeVec.size());
clipMap->aabbTrees = m_memory.Alloc<CollisionAabbTree>(AABBTreeVec.size());
memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size());
return clipMap;
}
} // namespace BSP
@@ -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<cNode_t> nodeVec;
std::vector<cLeaf_s> leafVec;
std::vector<CollisionAabbTree> AABBTreeVec;
size_t highestLeafObjectCount = 0;
std::vector<size_t> 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<ColSurface> collisionSurfaceVec;
std::vector<size_t> partitionToColSurfaceMap;
void addAABBTreeFromPartitions(
clipMap_t* clipMap, std::vector<int>& 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);
@@ -2,8 +2,7 @@
#include "../BSPUtil.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include <numbers>
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<float> / 180.0f));
light->cosHalfFovOuter = cosf(55.0f * (std::numbers::pi_v<float> / 180.0f));
light->cosHalfFovExpanded = cosf(55.0f * (std::numbers::pi_v<float> / 180.0f));
}
setLightCommonValues(light, bspLight);
}
@@ -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<int>(bsp->models.size() + 1);
gfxWorld->models = m_memory.Alloc<GfxBrushModel>(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<AssetXModel>(skyBoxName) == nullptr)
@@ -806,7 +823,7 @@ namespace BSP
loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces
loadModels(gfxWorld);
loadModels(bsp, gfxWorld); // requires surfaces
loadSunData(gfxWorld);
@@ -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);
@@ -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>();
mapEnts->name = m_memory.Dup(bsp->bspName.c_str());