diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index d2b1dfb1..0fb6934c 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Utils/Logging/Log.h" diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp new file mode 100644 index 00000000..de6d6007 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -0,0 +1,80 @@ +#include "BSPLinker.h" + +#include "ComWorldLinker.h" +#include "ClipMapLinker.h" +#include "GameWorldMpLinker.h" +#include "GfxWorldLinker.h" +#include "MapEntsLinker.h" +#include "SkinnedVertsLinker.h" + +namespace BSP +{ + FootstepTableDef* BSPLinker::addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return nullptr; + + FootstepTableDef* footstepTable = m_memory.Alloc(); + footstepTable->name = m_memory.Dup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + bool BSPLinker::addDefaultRequiredAssets(BSPData* bsp) + { + if (m_context.LoadDependency("maps/mp/" + bsp->name + ".gsc") == nullptr) + return false; + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_amb.gsc") == nullptr) + return false; + if (m_context.LoadDependency("maps/mp/" + bsp->name + "_fx.gsc") == nullptr) + return false; + + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + ".csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr) + return false; + if (m_context.LoadDependency("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr) + return false; + + addEmptyFootstepTableAsset("default_1st_person"); + addEmptyFootstepTableAsset("default_3rd_person"); + addEmptyFootstepTableAsset("default_1st_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_quiet"); + addEmptyFootstepTableAsset("default_3rd_person_loud"); + addEmptyFootstepTableAsset("default_ai"); + + if (m_context.LoadDependency("animtrees/fxanim_props.atr") == nullptr) + return false; + } + + BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult BSPLinker::linkBSP(BSPData* bsp) + { + if (!addDefaultRequiredAssets(bsp)) + return AssetCreationResult::Failure(); + + ComWorldLinker comWorldLinker(m_memory, m_search_path, m_context); + ClipMapLinker clipMapLinker(m_memory, m_search_path, m_context); + GameWorldMpLinker gameWorldMpLinker(m_memory, m_search_path, m_context); + GfxWorldLinker gfxWorldLinker(m_memory, m_search_path, m_context); + MapEntsLinker mapEntsLinker(m_memory, m_search_path, m_context); + SkinnedVertsLinker skinnedVertsLinker(m_memory, m_search_path, m_context); + + comWorldLinker.linkComWorld(bsp); + mapEntsLinker.linkMapEnts(bsp); + gameWorldMpLinker.linkGameWorldMp(bsp); + skinnedVertsLinker.linkSkinnedVerts(bsp); + gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h new file mode 100644 index 00000000..be986f97 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class BSPLinker + { + public: + BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkBSP(BSPData* bsp); + + private: + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName); + bool addDefaultRequiredAssets(BSPData* bsp); + + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp new file mode 100644 index 00000000..8710f93c --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.cpp @@ -0,0 +1,763 @@ +#include "ClipMapLinker.h" +#include "../BSPUtil.h" + + +namespace +{ + +} + +namespace BSP +{ + ClipMapLinker::ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + void ClipMapLinker::loadDynEnts(clipMap_t* clipMap) + { + int dynEntCount = 0; + clipMap->originalDynEntCount = dynEntCount; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + clipMap->dynEntCount[1] = 0; + clipMap->dynEntCount[2] = 0; + clipMap->dynEntCount[3] = 0; + + clipMap->dynEntClientList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntClientList[1] = nullptr; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = nullptr; + clipMap->dynEntServerList[1] = nullptr; + + clipMap->dynEntCollList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntCollList[1] = nullptr; + clipMap->dynEntCollList[2] = nullptr; + clipMap->dynEntCollList[3] = nullptr; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntPoseList[1] = nullptr; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = m_memory.Alloc(clipMap->dynEntCount[0]); + clipMap->dynEntDefList[1] = nullptr; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + } + + void ClipMapLinker::loadVisibility(clipMap_t* clipMap) + { + // Only use one visbility cluster for the entire map + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = m_memory.Alloc(clipMap->clusterBytes); + // Official maps set visibility to all 0xFF + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + } + + void ClipMapLinker::loadBoxData(clipMap_t* clipMap) + { + // box_model and box_brush are what are used by game traces as "temporary" collision when + // no brush or model is specified to do the trace with. + // All values in this function are taken from official map BSPs + + // for some reason the maxs are negative, and mins are positive + // float box_mins = 3.4028235e38; + // float box_maxs = -3.4028235e38; + // hack: the floats above can't be safely converted to 32 bit floats, and the game requires them to be exact + // so we use the hex representation and set it using int pointers. + unsigned int box_mins = 0x7F7FFFFF; + unsigned int box_maxs = 0xFF7FFFFF; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.x)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.y)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.mins.z)) = box_mins; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.x)) = box_maxs; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.y)) = box_maxs; + *(reinterpret_cast(&clipMap->box_model.leaf.maxs.z)) = box_maxs; + + clipMap->box_model.leaf.brushContents = -1; + clipMap->box_model.leaf.terrainContents = 0; + clipMap->box_model.leaf.cluster = 0; + clipMap->box_model.leaf.collAabbCount = 0; + clipMap->box_model.leaf.firstCollAabbIndex = 0; + clipMap->box_model.leaf.leafBrushNode = 0; + clipMap->box_model.mins.x = 0.0f; + clipMap->box_model.mins.y = 0.0f; + clipMap->box_model.mins.z = 0.0f; + clipMap->box_model.maxs.x = 0.0f; + clipMap->box_model.maxs.y = 0.0f; + clipMap->box_model.maxs.z = 0.0f; + clipMap->box_model.radius = 0.0f; + clipMap->box_model.info = nullptr; + + clipMap->box_brush = m_memory.Alloc(); + clipMap->box_brush->axial_sflags[0][0] = -1; + clipMap->box_brush->axial_sflags[0][1] = -1; + clipMap->box_brush->axial_sflags[0][2] = -1; + clipMap->box_brush->axial_sflags[1][0] = -1; + clipMap->box_brush->axial_sflags[1][1] = -1; + clipMap->box_brush->axial_sflags[1][2] = -1; + clipMap->box_brush->axial_cflags[0][0] = -1; + clipMap->box_brush->axial_cflags[0][1] = -1; + clipMap->box_brush->axial_cflags[0][2] = -1; + clipMap->box_brush->axial_cflags[1][0] = -1; + clipMap->box_brush->axial_cflags[1][1] = -1; + clipMap->box_brush->axial_cflags[1][2] = -1; + clipMap->box_brush->contents = -1; + clipMap->box_brush->mins.x = 0.0f; + clipMap->box_brush->mins.y = 0.0f; + clipMap->box_brush->mins.z = 0.0f; + clipMap->box_brush->maxs.x = 0.0f; + clipMap->box_brush->maxs.y = 0.0f; + clipMap->box_brush->maxs.z = 0.0f; + clipMap->box_brush->numsides = 0; + clipMap->box_brush->numverts = 0; + clipMap->box_brush->sides = nullptr; + clipMap->box_brush->verts = nullptr; + } + + void ClipMapLinker::loadRopesAndConstraints(clipMap_t* clipMap) + { + clipMap->num_constraints = 0; // max 511 + clipMap->constraints = NULL; + + // The game allocates 32 empty ropes + clipMap->max_ropes = 32; // max 300 + clipMap->ropes = m_memory.Alloc (clipMap->max_ropes); + memset(clipMap->ropes, 0, sizeof(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) + auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); + assert(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + // Right now there is only one submodel, the world sub model + assert(gfxWorld->modelCount == 1); + + clipMap->numSubModels = 1; + clipMap->cmodels = m_memory.Alloc(clipMap->numSubModels); + + GfxBrushModel* gfxModel = &gfxWorld->models[0]; + clipMap->cmodels[0].mins.x = gfxModel->bounds[0].x; + clipMap->cmodels[0].mins.y = gfxModel->bounds[0].y; + clipMap->cmodels[0].mins.z = gfxModel->bounds[0].z; + clipMap->cmodels[0].maxs.x = gfxModel->bounds[1].x; + clipMap->cmodels[0].maxs.y = gfxModel->bounds[1].y; + clipMap->cmodels[0].maxs.z = gfxModel->bounds[1].z; + 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 = NULL; // always set to 0 + } + + void ClipMapLinker::loadXModelCollision(clipMap_t* clipMap) + { + // Right now XModels aren't supported + clipMap->numStaticModels = 0; + clipMap->staticModelList = nullptr; + + // WIP code left in for future support + /* + auto gfxWorldAsset = m_context.LoadDependency(bsp->bspName); + assert(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; + clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; + + for (unsigned int i = 0; i < clipMap->numStaticModels; i++) + { + GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; + GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; + cStaticModel_s* currModel = &clipMap->staticModelList[i]; + + memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); + currModel->xmodel = gfxModelDrawInst->model; + currModel->contents = gfxModelDrawInst->model->contents; + currModel->origin.x = gfxModelDrawInst->placement.origin.x; + currModel->origin.y = gfxModelDrawInst->placement.origin.y; + currModel->origin.z = gfxModelDrawInst->placement.origin.z; + + // TODO: this does not account for model rotation or scale + currModel->absmin.x = gfxModelInst->mins.x; + currModel->absmin.y = gfxModelInst->mins.y; + currModel->absmin.z = gfxModelInst->mins.z; + currModel->absmax.x = gfxModelInst->maxs.x; + currModel->absmax.y = gfxModelInst->maxs.y; + currModel->absmax.z = gfxModelInst->maxs.z; + + BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); + currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; + currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; + currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; + currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; + currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; + currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; + currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; + currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; + currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; + } + */ + } + + void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + out_origin->x = temp.x * 0.5f; + out_origin->y = temp.y * 0.5f; + out_origin->z = temp.z * 0.5f; + + // Half-size is half the difference: (max - min) / 2 + temp.x = maxs->x - mins->x; + temp.y = maxs->y - mins->y; + temp.z = maxs->z - mins->z; + out_halfSize->x = temp.x * 0.5f; + out_halfSize->y = temp.y * 0.5f; + out_halfSize->z = temp.z * 0.5f; + } + + void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->leaf->getObjectCount() > 0) + { + *numAABBTrees += node->leaf->getObjectCount() + 1; + + if (node->leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + } + } + + vec3_t normalX = { 1.0f, 0.0f, 0.0f }; + vec3_t normalY = { 0.0f, 1.0f, 0.0f }; + vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; + + int currPlaneCount = 0; + int currNodeCount = 0; + int currLeafCount = 0; + int currAABBCount = 0; + + int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) + { + assert(node->isLeaf); + + int objectCount = node->leaf->getObjectCount(); + int firstAABBIndex = currAABBCount; + currAABBCount += objectCount + 1; + + // calculate root AABB node mins and maxs + // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs + // so we have to recompute every min and max, not hard just tedious + int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; + auto firstPartition = &clipMap->partitions[firstPartitionIndex]; + uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t aabbMins; + vec3_t aabbMaxs; + aabbMins.x = firstVert->x; + aabbMins.y = firstVert->y; + aabbMins.z = firstVert->z; + aabbMaxs.x = firstVert->x; + aabbMaxs.y = firstVert->y; + aabbMaxs.z = firstVert->z; + for (int i = 0; i < objectCount; i++) + { + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + auto currPartition = &clipMap->partitions[currPartitionIndex]; + + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = clipMap->verts[vertIndex]; + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; + aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); + rootAABB->materialIndex = 0; + rootAABB->childCount = objectCount; + rootAABB->u.firstChildIndex = firstAABBIndex + 1; + + // populate child AABB nodes + for (int i = 0; i < objectCount; i++) + { + CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; + int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; + + currAabbTree->materialIndex = 0; + currAabbTree->childCount = 0; + currAabbTree->u.partitionIndex = currPartitionIndex; + + // calculate partition origin and half size + CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; + uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; + vec3_t* firstVertex = &clipMap->verts[firstUind]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVertex->x; + mins.y = firstVertex->y; + mins.z = firstVertex->z; + maxs.x = firstVertex->x; + maxs.y = firstVertex->y; + maxs.z = firstVertex->z; + for (int i = 1; i < aabbPartition->nuinds; i++) + { + uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; + vec3_t* currVertex = &clipMap->verts[currUind]; + + BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); + } + + aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); + } + + return firstAABBIndex; + } + + // returns the index corresponding to the BSPTree* node parsed + int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) + { + if (node->isLeaf) + { + int currLeafIndex = currLeafCount; + currLeafCount++; + cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; + + currLeaf->cluster = 0; + currLeaf->brushContents = 0; // no brushes used so contents is 0 + currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence + + // unused when leafBrushNode == 0 + currLeaf->mins.x = 0.0f; + currLeaf->mins.y = 0.0f; + currLeaf->mins.z = 0.0f; + currLeaf->maxs.x = 0.0f; + currLeaf->maxs.y = 0.0f; + currLeaf->maxs.z = 0.0f; + currLeaf->leafBrushNode = 0; + + if (node->leaf->getObjectCount() > 0) + { + currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); + currLeaf->collAabbCount = 1; + } + else + { + currLeaf->firstCollAabbIndex = 0; + currLeaf->collAabbCount = 0; + } + + return -1 - currLeafIndex; + } + else + { + cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; + currPlaneCount++; + + if (node->node->axis == AXIS_X) + { + // X is unchanged when going from OGL x -> BO2 x + currPlane->normal = normalX; + + // converting OGL -> BO2 X coords doesn't change the x coords at all, so + // the dist stays the same + currPlane->dist = (float)node->node->distance; + } + else + { + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // convert the z normal to the y normal, but don't negate it. Negative normals don't do + // what is expected when the game uses them + assert(node->node->axis == AXIS_Z); + currPlane->normal = normalY; + + // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. + // just negate it here as it is just the distance from the orgin along the axis + currPlane->dist = (float)(-node->node->distance); + } + + bool foundType = false; + if (currPlane->normal.x == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 0; + } + else if (currPlane->normal.y == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 1; + } + else if (currPlane->normal.z == 1.0f) + { + assert(!foundType); + foundType = true; + currPlane->type = 2; + } + else + assert(foundType); + + currPlane->signbits = 0; + if (currPlane->normal.x < 0.0f) + currPlane->signbits |= 1; + if (currPlane->normal.y < 0.0f) + currPlane->signbits |= 2; + if (currPlane->normal.z < 0.0f) + currPlane->signbits |= 4; + + currPlane->pad[0] = 0; + currPlane->pad[1] = 0; + + int currNodeIndex = currNodeCount; + currNodeCount++; + cNode_t* currNode = &clipMap->nodes[currNodeIndex]; + + currNode->plane = currPlane; + // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): + // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, + // the normal vector flips and objects behind the plane are now in front, and vise versa + // so the back node now represents the front, and the front node represents the back. + // Do the OGL -> Bo2 coord change on paper and it will make sense + if (currPlane->type == 1) + { + currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); + currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); + } + + return currNodeIndex; + } + } + + void ClipMapLinker::populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + size_t numPlanes = 0; + size_t numNodes = 0; + size_t numLeafs = 0; + size_t numAABBTrees = 0; + size_t maxObjsPerLeaf = 0; + + traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); + + printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); + + clipMap->info.planeCount = numPlanes; + clipMap->info.planes = m_memory.Alloc(clipMap->info.planeCount); + clipMap->numNodes = numNodes; + clipMap->nodes = m_memory.Alloc(clipMap->numNodes); + // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. + // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. + clipMap->aabbTreeCount = numAABBTrees; + clipMap->aabbTrees = m_memory.Alloc(clipMap->aabbTreeCount); + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = m_memory.Alloc(clipMap->numLeafs); + memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); + currLeafCount = 1; + + populateBSPTree_r(clipMap, tree); + + assert(clipMap->info.planeCount == currPlaneCount); + assert(clipMap->numNodes == currNodeCount); + assert(clipMap->numLeafs == currLeafCount); + assert(clipMap->aabbTreeCount == currAABBCount); + } + + bool ClipMapLinker::createPartitions(clipMap_t* clipMap, BSPData* bsp) + { + int collisionVertexCount = bsp->colWorld.vertices.size(); + std::vector collisionVertVec; + for (int i = 0; i < collisionVertexCount; i++) + { + collisionVertVec.push_back(BSPUtil::convertToBO2Coords(bsp->colWorld.vertices[i].pos)); + //collisionVertVec.push_back(bsp->colWorld.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = m_memory.Alloc(collisionVertexCount); + memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); + + // due to tris using uint16_t as the type for indexing the vert array, + // any vertex count over the uint16_t max means the vertices above it can't be indexed + if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); + return false; + } + + std::vector triIndexVec; + for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + { + BSPSurface* currSurface = &bsp->colWorld.surfaces[i]; + int triCount = currSurface->triCount; + + for (int k = 0; k < triCount * 3; k += 3) + { + int firstIndex_Index = currSurface->indexOfFirstIndex; + int firstVertexIndex = currSurface->indexOfFirstVertex; + + // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire + // clipmap verts buffer, so this code updates the indexes to follow that. + int triIndex0 = bsp->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = bsp->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = bsp->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; + + // triangle index ordering is opposite to blenders, so its converted here + triIndexVec.push_back(triIndex2); + triIndexVec.push_back(triIndex1); + triIndexVec.push_back(triIndex0); + } + } + assert(triIndexVec.size() % 3 == 0); + + // 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 = triIndexVec.size() / 3; + clipMap->triIndices = reinterpret_cast(m_memory.Alloc(triIndexVec.size())); + memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); + + // 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 + std::vector partitionVec; + for (size_t i = 0; i < bsp->colWorld.surfaces.size(); i++) + { + int triCount = bsp->colWorld.surfaces[i].triCount; + int firstTriIndex = bsp->colWorld.surfaces[i].indexOfFirstIndex / 3; + for (int k = 0; k < triCount; k++) + { + CollisionPartition newPartition; + newPartition.nuinds = 0; // initialised later + newPartition.fuind = 0; // initialised later + newPartition.triCount = 1; + newPartition.firstTri = firstTriIndex; + firstTriIndex += 1; + + partitionVec.push_back(newPartition); + } + } + clipMap->partitionCount = partitionVec.size(); + clipMap->partitions = m_memory.Alloc(clipMap->partitionCount); + memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); + + int totalUindCount = 0; + std::vector uindVec; + for (int i = 0; i < clipMap->partitionCount; i++) + { + CollisionPartition* currPartition = &clipMap->partitions[i]; + std::vector uniqueVertVec; + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + bool isVertexIndexUnique = true; + uint16_t vertIndex = tri[l]; + + for (size_t m = 0; m < uniqueVertVec.size(); m++) + { + if (uniqueVertVec[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + uniqueVertVec.push_back(vertIndex); + } + } + + currPartition->fuind = totalUindCount; + currPartition->nuinds = (int)uniqueVertVec.size(); + uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); + totalUindCount += currPartition->nuinds; + } + clipMap->info.nuinds = totalUindCount; + clipMap->info.uinds = m_memory.Alloc(totalUindCount); + memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); + + return true; + } + + bool ClipMapLinker::loadBrushCollision(clipMap_t* clipMap, BSPData* bsp) + { + // No support for brushes, only tris right now + clipMap->info.numBrushSides = 0; + clipMap->info.brushsides = NULL; + clipMap->info.leafbrushNodesCount = 0; + clipMap->info.leafbrushNodes = NULL; + clipMap->info.numLeafBrushes = 0; + clipMap->info.leafbrushes = NULL; + clipMap->info.numBrushVerts = 0; + clipMap->info.brushVerts = NULL; + clipMap->info.numBrushes = NULL; + clipMap->info.brushes = NULL; + clipMap->info.brushBounds = NULL; + clipMap->info.brushContents = NULL; + + // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated + // HACK: + // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. + // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, + // and then convert them back when it is being parsed into the clipmap. Requires some hacky + // logic, check populateBSPTree_r and addAABBTreeFromLeaf + + if (!createPartitions(clipMap, bsp)) + return false; + + vec3_t* firstVert = &clipMap->verts[0]; + vec3_t clipMins; + vec3_t clipMaxs; + clipMins.x = firstVert->x; + clipMins.y = firstVert->y; + clipMins.z = firstVert->z; + clipMaxs.x = firstVert->x; + clipMaxs.y = firstVert->y; + clipMaxs.z = firstVert->z; + clipMins = BSPUtil::convertFromBO2Coords(clipMins); + clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); + for (unsigned int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + + assert(!tree->isLeaf); + + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; + vec3_t* firstVert = &clipMap->verts[firstTri[0]]; + vec3_t mins; + vec3_t maxs; + mins.x = firstVert->x; + mins.y = firstVert->y; + mins.z = firstVert->z; + maxs.x = firstVert->x; + maxs.y = firstVert->y; + maxs.z = firstVert->z; + mins = BSPUtil::convertFromBO2Coords(mins); + maxs = BSPUtil::convertFromBO2Coords(maxs); + for (int k = 0; k < currPartition->triCount; k++) + { + uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; + for (int l = 0; l < 3; l++) + { + uint16_t vertIndex = tri[l]; + vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObjectToTree(std::move(currObject)); + } + + populateBSPTree(clipMap, tree); + + return true; + } + + AssetCreationResult ClipMapLinker::linkClipMap(BSPData* bsp) + { + clipMap_t* clipMap = m_memory.Alloc(); + clipMap->name = m_memory.Dup(bsp->bspName.c_str()); + + clipMap->isInUse = true; + clipMap->checksum = 0; + clipMap->pInfo = nullptr; + + std::string mapEntsName = bsp->bspName; + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + assert(mapEntsAsset != nullptr); + clipMap->mapEnts = mapEntsAsset->Asset(); + + loadBoxData(clipMap); + + loadVisibility(clipMap); + + loadRopesAndConstraints(clipMap); + + loadSubModelCollision(clipMap, bsp); + + loadDynEnts(clipMap); + + loadXModelCollision(clipMap); + + // Clipmap materials define the properties of a material (bullet penetration, no collision, water, etc) + // Right now there is no way to define properties per material so only one material is used + clipMap->info.numMaterials = 1; + clipMap->info.materials = m_memory.Alloc(clipMap->info.numMaterials); + clipMap->info.materials[0].name = m_memory.Dup(BSPLinkingConstants::MISSING_IMAGE_NAME); + clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; + + // 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 = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + + if (!loadBrushCollision(clipMap, bsp)) + return AssetCreationResult::Failure(); + + m_context.AddAsset(clipMap->name, clipMap); + + auto clipMapAsset = m_context.AddAsset(clipMap->name, clipMap); + return AssetCreationResult::Success(clipMapAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h new file mode 100644 index 00000000..9a08ebd7 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ClipMapLinker.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "../BSPCalculation.h" + +namespace BSP +{ + class ClipMapLinker + { + public: + ClipMapLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkClipMap(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + + void loadBoxData(clipMap_t* clipMap); + void loadVisibility(clipMap_t* clipMap); + void loadDynEnts(clipMap_t* clipMap); + void loadRopesAndConstraints(clipMap_t* clipMap); + void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); + void loadXModelCollision(clipMap_t* clipMap); + + bool loadBrushCollision(clipMap_t* clipMap, BSPData* bsp); + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree); + bool createPartitions(clipMap_t* clipMap, BSPData* bsp); + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp new file mode 100644 index 00000000..db15bd4c --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -0,0 +1,40 @@ +#include "ComWorldLinker.h" + +namespace BSP +{ + ComWorldLinker::ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult ComWorldLinker::linkComWorld(BSPData* bsp) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = m_memory.Alloc(); + comWorld->name = m_memory.Dup(bsp->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = m_memory.Alloc(comWorld->primaryLightCount); + + // static light is always empty + ComPrimaryLight* staticLight = &comWorld->primaryLights[0]; + memset(staticLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + sunLight->diffuseColor.r = 0.75f; + sunLight->diffuseColor.g = 0.75f; + sunLight->diffuseColor.b = 0.75f; + sunLight->diffuseColor.a = 1.0f; + sunLight->dir.x = 0.0f; + sunLight->dir.y = 0.0f; + sunLight->dir.z = 0.0f; + + auto comWorldAsset = m_context.AddAsset(comWorld->name, comWorld); + return AssetCreationResult::Success(comWorldAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h new file mode 100644 index 00000000..21be6a16 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class ComWorldLinker + { + public: + ComWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkComWorld(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp new file mode 100644 index 00000000..1286837b --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.cpp @@ -0,0 +1,38 @@ +#include "GameWorldMpLinker.h" + +namespace BSP +{ + GameWorldMpLinker::GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult GameWorldMpLinker::linkGameWorldMp(BSPData* bsp) + { + GameWorldMp* gameWorldMp = m_memory.Alloc(); + + gameWorldMp->name = m_memory.Dup(bsp->bspName.c_str()); + + gameWorldMp->path.nodeCount = 0; + gameWorldMp->path.originalNodeCount = 0; + gameWorldMp->path.visBytes = 0; + gameWorldMp->path.smoothBytes = 0; + gameWorldMp->path.nodeTreeCount = 0; + + int nodeCount = gameWorldMp->path.nodeCount + 128; + gameWorldMp->path.nodes = m_memory.Alloc(nodeCount); + gameWorldMp->path.basenodes = m_memory.Alloc(nodeCount); + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = nullptr; + gameWorldMp->path.smoothCache = nullptr; + gameWorldMp->path.nodeTree = nullptr; + + auto gameWorldMpAsset = m_context.AddAsset(gameWorldMp->name, gameWorldMp); + return AssetCreationResult::Success(gameWorldMpAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h new file mode 100644 index 00000000..a1ca459e --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GameWorldMpLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class GameWorldMpLinker + { + public: + GameWorldMpLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGameWorldMp(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp new file mode 100644 index 00000000..8d42b256 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -0,0 +1,17 @@ +#include "GfxWorldLinker.h" + +namespace BSP +{ + GfxWorldLinker::GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult GfxWorldLinker::linkGfxWorld(BSPData* bsp) + { + return AssetCreationResult::Failure(); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h new file mode 100644 index 00000000..7d219320 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class GfxWorldLinker + { + public: + GfxWorldLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkGfxWorld(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp new file mode 100644 index 00000000..5ba3145b --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -0,0 +1,128 @@ +#include "MapEntsLinker.h" +#include "../BSPUtil.h" + +#include +using namespace nlohmann; + +namespace +{ + bool parseMapEntsJSON(json& entArrayJs, std::string& entityString) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + if (i == 0) + { + std::string className = currEntity["classname"]; + if (className.compare("worldspawn") != 0) + { + con::error("ERROR: first entity in the map entity string must be the worldspawn class!"); + return false; + } + } + + entityString.append("{\n"); + + for (auto& element : currEntity.items()) + { + std::string key = element.key(); + std::string value = element.value(); + entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); + } + + entityString.append("}\n"); + } + + return true; + } + + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + std::string origin = currEntity["origin"]; + std::string angles = currEntity["angles"]; + + for (int k = 0; k < nameCount; k++) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", origin)); + entityString.append(std::format("\"angles\" \"{}\"\n", angles)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); + entityString.append("}\n"); + } + } + } +} + +namespace BSP +{ + MapEntsLinker::MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult MapEntsLinker::linkMapEnts(BSPData* bsp) + { + std::string entityString; + + json entJs; + std::string entityFilePath = BSPUtil::getFileNameForBSPAsset("entities.json"); + const auto entFile = m_search_path.Open(entityFilePath); + if (!entFile.IsOpen()) + { + con::warn("Can't find entity file {}, using default entities instead", entityFilePath); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + } + else + { + entJs = json::parse(*entFile.m_stream); + } + if (!parseMapEntsJSON(entJs["entities"], entityString)) + return AssetCreationResult::Failure(); + + json spawnJs; + std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset("spawns.json"); + const auto spawnFile = m_search_path.Open(spawnFilePath); + if (!spawnFile.IsOpen()) + { + con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); + spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + int defenderNameCount = std::extent::value; + int attackerNameCount = std::extent::value; + int ffaNameCount = std::extent::value; + parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); + parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); + parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + + MapEnts* mapEnts = m_memory.Alloc(); + mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); + + mapEnts->entityString = m_memory.Dup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = nullptr; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = nullptr; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = nullptr; + + auto mapEntsAsset = m_context.AddAsset(mapEnts->name, mapEnts); + return AssetCreationResult::Success(mapEntsAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h new file mode 100644 index 00000000..921af16d --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class MapEntsLinker + { + public: + MapEntsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkMapEnts(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp new file mode 100644 index 00000000..2647a14b --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.cpp @@ -0,0 +1,26 @@ +#include "SkinnedVertsLinker.h" + +namespace BSP +{ + SkinnedVertsLinker::SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_context(context) + { + } + + AssetCreationResult SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) + { + std::string assetName = "skinnedverts"; + + // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have + // But setting it to the world vertex count seems to work + SkinnedVertsDef* skinnedVerts = m_memory.Alloc(); + skinnedVerts->name = m_memory.Dup(assetName.c_str()); + skinnedVerts->maxSkinnedVerts = static_cast(bsp->gfxWorld.vertices.size()); + + auto skinnedVertsAsset = m_context.AddAsset(assetName, skinnedVerts); + return AssetCreationResult::Success(skinnedVertsAsset); + } +} + diff --git a/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h new file mode 100644 index 00000000..db7f03b7 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/Linker/SkinnedVertsLinker.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../BSP.h" +#include "Asset/IAssetCreator.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +namespace BSP +{ + class SkinnedVertsLinker + { + public: + SkinnedVertsLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context); + AssetCreationResult linkSkinnedVerts(BSPData* bsp); + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + AssetCreationContext& m_context; + }; +} \ No newline at end of file