From 5dcbb03f18237112b0732fb51c9237db44709e2f Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:21:05 +0800 Subject: [PATCH] feat: zone volumes now use brushes instead of terrain to define their boundaries --- src/ObjLoading/Game/T6/BSP/BSP.h | 15 +- src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 93 +++++++- .../Game/T6/BSP/Linker/ClipMapLinker.cpp | 210 +++++++++++------- .../Game/T6/BSP/Linker/ClipMapLinker.h | 3 + .../Game/T6/BSP/Linker/GfxWorldLinker.cpp | 2 +- 5 files changed, 243 insertions(+), 80 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index 670df6a7..6c1c6612 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -62,9 +62,17 @@ namespace BSP bool doesCastShadow; }; + struct BSPBoxBrush + { + vec3_t localMins; + vec3_t localMaxs; + + int surfaceFlags; + int contentFlags; + }; + struct BSPWorld { - std::vector staticSurfaces; std::vector scriptSurfaces; @@ -72,6 +80,8 @@ namespace BSP std::vector indices; std::vector materials; std::vector xmodels; + + std::vector scriptBoxBrushes; }; enum BSPLightType @@ -145,6 +155,9 @@ namespace BSP size_t surfaceIndex; size_t surfaceCount; + + bool hasBrush; + size_t brushIndex; }; struct BSPData diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index e14bcb90..6fe760e8 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -665,7 +665,94 @@ namespace return true; } - size_t addScriptModel(const JsonRoot& jRoot, const gltf::JsonNode& node) + size_t addScriptBrushModel(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 = 0; + model.surfaceCount = 0; + model.hasBrush = true; + model.brushIndex = m_curr_bsp_world->scriptBoxBrushes.size(); + m_bsp->models.emplace_back(model); + + BSPBoxBrush boxBrush; + boxBrush.contentFlags = 1; + boxBrush.surfaceFlags = 1; + vec3_t worldMins; + vec3_t worldMaxs; + for (size_t primIdx = 0; primIdx < mesh.primitives.size(); primIdx++) + { + const auto& primitive = mesh.primitives.at(primIdx); + + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + + // clang-format off + const auto* positionAccessor = GetAccessorForIndex( + "POSITION", + primitive.attributes.POSITION, + { JsonAccessorType::VEC3 }, + { JsonAccessorComponentType::FLOAT } + ).value_or(nullptr); + // clang-format on + assert(positionAccessor != nullptr); + + if (positionAccessor->GetCount() == 0) + throw GltfLoadException("positionAccessor has count of 0"); + + for (size_t vertexIndex = 0u; vertexIndex < positionAccessor->GetCount(); vertexIndex++) + { + vec3_t vertex; + if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.v)) + assert(false); + + Eigen::Vector4f position(vertex.x, vertex.y, vertex.z, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + vertex.x = transformedPosition.x(); + vertex.y = transformedPosition.y(); + vertex.z = transformedPosition.z(); + RhcToLhcCoordinates(vertex.v); + + if (vertexIndex == 0 && primIdx == 0) + { + worldMins = vertex; + worldMaxs = vertex; + } + else + BSPUtil::updateAABBWithPoint(vertex, worldMins, worldMaxs); + } + } + + // convert world position to local position + 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); + boxBrush.localMins.x = worldMins.x - origin.x; + boxBrush.localMins.y = worldMins.y - origin.y; + boxBrush.localMins.z = worldMins.z - origin.z; + boxBrush.localMaxs.x = worldMaxs.x - origin.x; + boxBrush.localMaxs.y = worldMaxs.y - origin.y; + boxBrush.localMaxs.z = worldMaxs.z - origin.z; + + m_curr_bsp_world->scriptBoxBrushes.emplace_back(boxBrush); + + return m_bsp->models.size(); // script model index starts at 1 + } + + size_t addScriptTerrainModel(const JsonRoot& jRoot, const gltf::JsonNode& node) { if (!node.mesh || !jRoot.meshes) throw new GltfLoadException("Script model created with no mesh data"); @@ -680,6 +767,8 @@ namespace model.isGfxModel = m_is_world_gfx; model.surfaceIndex = m_curr_bsp_world->scriptSurfaces.size(); model.surfaceCount = mesh.primitives.size(); + model.hasBrush = false; + model.brushIndex = 0; m_bsp->models.emplace_back(model); for (const auto& primitive : mesh.primitives) @@ -735,7 +824,7 @@ namespace 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); + zone.modelIndex = addScriptBrushModel(jRoot, node); m_bsp->zZones.emplace_back(zone); return true; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp index 7d2f3075..4b867869 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -243,17 +243,98 @@ namespace BSP for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) { auto clipModel = &clipMap->cmodels[modelIdx + 1]; - auto& bspMpdel = bsp->models.at(modelIdx); + auto& bspModel = bsp->models.at(modelIdx); - if (bspMpdel.isGfxModel) + if (bspModel.isGfxModel) + { + memset(clipModel, 0, sizeof(cmodel_t)); + continue; + } + + if (bspModel.surfaceCount != 0) + { + std::vector partitionIndexes; + for (size_t surfIdx = 0; surfIdx < bspModel.surfaceCount; surfIdx++) + { + ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + bspModel.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); + } + } + + 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); + } + else + { + clipModel->leaf.terrainContents = 0; + clipModel->leaf.firstCollAabbIndex = 0; + clipModel->leaf.collAabbCount = 0; + } + + if (bspModel.hasBrush) + { + BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); + clipModel->leaf.mins = bspBrush.localMins; + clipModel->leaf.maxs = bspBrush.localMaxs; + clipModel->leaf.brushContents = bspBrush.contentFlags; + clipModel->leaf.leafBrushNode = static_cast(brushNodeVec.size()); + assert(clipModel->leaf.leafBrushNode != 0); + + cLeafBrushNode_s brushNode; + brushNode.axis = 0; + brushNode.contents = bspBrush.contentFlags; + brushNode.leafBrushCount = 1; + brushNode.data.leaf.brushes = m_memory.Alloc(1); + brushNode.data.leaf.brushes[0] = static_cast(brushVec.size()); + brushNodeVec.emplace_back(brushNode); + + cbrush_array_t brush; + memset(&brush, 0, sizeof(cbrush_array_t)); // if not sides or verts are given, the mins/maxs are used instead + brush.contents = bspBrush.contentFlags; + brush.mins = bspBrush.localMins; + brush.maxs = bspBrush.localMaxs; + brush.axial_cflags[0][0] = bspBrush.contentFlags; + brush.axial_cflags[0][1] = bspBrush.contentFlags; + brush.axial_cflags[0][2] = bspBrush.contentFlags; + brush.axial_cflags[1][0] = bspBrush.contentFlags; + brush.axial_cflags[1][1] = bspBrush.contentFlags; + brush.axial_cflags[1][2] = bspBrush.contentFlags; + brush.axial_sflags[0][0] = bspBrush.surfaceFlags; + brush.axial_sflags[0][1] = bspBrush.surfaceFlags; + brush.axial_sflags[0][2] = bspBrush.surfaceFlags; + brush.axial_sflags[1][0] = bspBrush.surfaceFlags; + brush.axial_sflags[1][1] = bspBrush.surfaceFlags; + brush.axial_sflags[1][2] = bspBrush.surfaceFlags; + brushVec.emplace_back(brush); + + if (bspModel.surfaceCount != 0) + BSPUtil::updateAABB(bspBrush.localMins, bspBrush.localMaxs, clipModel->mins, clipModel->maxs); + else + { + clipModel->mins = bspBrush.localMins; + clipModel->maxs = bspBrush.localMaxs; + } + } + else { - 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; @@ -262,50 +343,16 @@ namespace BSP 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++) + if (bspModel.surfaceCount == 0 && !bspModel.hasBrush) { - ColSurface& surf = collisionSurfaceVec.at(bsp->colWorld.staticSurfaces.size() + 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->mins = {0.0f, 0.0f, 0.0f}; + clipModel->maxs = {0.0f, 0.0f, 0.0f}; + clipModel->radius = 0.0f; } - 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; + else + clipModel->radius = BSPUtil::distBetweenPoints(clipModel->mins, clipModel->maxs) / 2; clipModel->leaf.cluster = 0; clipModel->info = nullptr; } @@ -607,22 +654,6 @@ namespace BSP // load planes, nodes, leafs, and AABB trees loadBSPNode(clipMap, tree.get(), true); - clipMap->info.planeCount = static_cast(planeVec.size()); - clipMap->info.planes = m_memory.Alloc(planeVec.size()); - memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); - - clipMap->numNodes = static_cast(nodeVec.size()); - clipMap->nodes = m_memory.Alloc(nodeVec.size()); - memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); - - clipMap->numLeafs = static_cast(leafVec.size()); - clipMap->leafs = m_memory.Alloc(leafVec.size()); - memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.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 AABB tree partition count: {}", highestPartitionCountForAABB); return true; @@ -724,17 +755,21 @@ namespace BSP // No support for brushes, only tris right now clipMap->info.numBrushSides = 0; clipMap->info.brushsides = nullptr; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = nullptr; clipMap->info.numLeafBrushes = 0; clipMap->info.leafbrushes = nullptr; clipMap->info.numBrushVerts = 0; clipMap->info.brushVerts = nullptr; - clipMap->info.numBrushes = 0; - clipMap->info.brushes = nullptr; clipMap->info.brushBounds = nullptr; clipMap->info.brushContents = nullptr; + // first brush node is always empty + cLeafBrushNode_s tempNode; + memset(&tempNode, 0, sizeof(cLeafBrushNode_s)); + brushNodeVec.emplace_back(tempNode); + + clipMap->info.numBrushes = 0; + clipMap->info.brushes = nullptr; + // load verts, tris, uinds and partitions if (!loadPartitions(clipMap, bsp)) return false; @@ -742,6 +777,36 @@ namespace BSP if (!loadBSPTree(clipMap, bsp)) return false; + loadSubModelCollision(clipMap, bsp); // requires tri verts + + clipMap->info.planeCount = static_cast(planeVec.size()); + clipMap->info.planes = m_memory.Alloc(planeVec.size()); + memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size()); + + clipMap->numNodes = static_cast(nodeVec.size()); + clipMap->nodes = m_memory.Alloc(nodeVec.size()); + memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size()); + + clipMap->numLeafs = static_cast(leafVec.size()); + 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()); + + clipMap->info.leafbrushNodesCount = static_cast(brushNodeVec.size()); + clipMap->info.leafbrushNodes = m_memory.Alloc(brushNodeVec.size()); + memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size()); + + clipMap->info.numBrushes = static_cast(brushVec.size()); + clipMap->info.brushes = m_memory.Alloc(brushVec.size()); + memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.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]; + return true; } @@ -801,19 +866,12 @@ namespace BSP if (!loadWorldCollision(clipMap, bsp)) // requires materials return nullptr; - loadSubModelCollision(clipMap, bsp); // requires tri verts - // set all edges to walkable // might do weird stuff on walls, but from testing doesnt seem to do anything int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; 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 74498886..53feb5fd 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -39,6 +39,9 @@ namespace BSP std::vector AABBTreeVec; size_t highestPartitionCountForAABB = 0; + std::vector brushNodeVec; + std::vector brushVec; + std::vector collisionSurfaceVec; std::vector partitionToColSurfaceMap; diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 43ff731f..259a61b7 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -587,7 +587,7 @@ namespace BSP auto currEntModel = &gfxWorld->models[modelIdx + 1]; auto& bspModel = bsp->models.at(modelIdx); - if (bspModel.isGfxModel) + if (bspModel.isGfxModel && bspModel.surfaceCount != 0) { currEntModel->startSurfIndex = bsp->gfxWorld.staticSurfaces.size() + bspModel.surfaceIndex; currEntModel->surfaceCount = bspModel.surfaceCount;