From e53779517d5d5d18c4b7f5b3c4c8db707c3a461d Mon Sep 17 00:00:00 2001 From: LJW-Dev Date: Wed, 22 Oct 2025 16:06:59 +0800 Subject: [PATCH] WIP: - Updated file structure to use BSP naming scheme - Re-wrote BSP creator to use c++ more efficiently --- src/ObjCommon/Game/T6/Maps/CustomMaps.h | 53 - src/ObjLoading/Game/T6/BSP/BSP.h | 185 ++ src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp | 151 ++ src/ObjLoading/Game/T6/BSP/BSPCalculation.h | 72 + src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 269 +++ src/ObjLoading/Game/T6/BSP/BSPCreator.h | 9 + .../Game/T6/{CustomMap => BSP}/BSPUtil.cpp | 0 .../Game/T6/{CustomMap => BSP}/BSPUtil.h | 4 +- src/ObjLoading/Game/T6/BSP/CustomMapLinker.h | 1987 +++++++++++++++++ .../LoaderBSP_T6.cpp} | 36 +- .../LoaderBSP_T6.h} | 4 +- .../Game/T6/{CustomMap => BSP}/fbx/ufbx.cpp | 0 .../Game/T6/{CustomMap => BSP}/fbx/ufbx.h | 0 .../Game/T6/CustomMap/BSPCalculation.cpp | 147 -- .../Game/T6/CustomMap/BSPCalculation.h | 75 - .../Game/T6/CustomMap/BSPConstants.h | 127 -- .../Game/T6/CustomMap/BSPCreator.cpp | 301 --- src/ObjLoading/Game/T6/CustomMap/BSPCreator.h | 10 - .../Game/T6/CustomMap/CustomMapLinker.h | 1983 ---------------- .../Game/T6/CustomMap/CustomMapOptions.h | 17 - src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 4 +- 21 files changed, 2693 insertions(+), 2741 deletions(-) delete mode 100644 src/ObjCommon/Game/T6/Maps/CustomMaps.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSP.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCalculation.h create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCreator.cpp create mode 100644 src/ObjLoading/Game/T6/BSP/BSPCreator.h rename src/ObjLoading/Game/T6/{CustomMap => BSP}/BSPUtil.cpp (100%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/BSPUtil.h (92%) create mode 100644 src/ObjLoading/Game/T6/BSP/CustomMapLinker.h rename src/ObjLoading/Game/T6/{CustomMap/LoaderCustomMapT6.cpp => BSP/LoaderBSP_T6.cpp} (55%) rename src/ObjLoading/Game/T6/{CustomMap/LoaderCustomMapT6.h => BSP/LoaderBSP_T6.h} (86%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/fbx/ufbx.cpp (100%) rename src/ObjLoading/Game/T6/{CustomMap => BSP}/fbx/ufbx.h (100%) delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPConstants.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp delete mode 100644 src/ObjLoading/Game/T6/CustomMap/BSPCreator.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h delete mode 100644 src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h diff --git a/src/ObjCommon/Game/T6/Maps/CustomMaps.h b/src/ObjCommon/Game/T6/Maps/CustomMaps.h deleted file mode 100644 index 9ed6c753..00000000 --- a/src/ObjCommon/Game/T6/Maps/CustomMaps.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include - -#include "Game/T6/T6.h" -using namespace T6; - -struct CustomMapVertex -{ - vec3_t pos; - vec4_t color; - vec2_t texCoord; - vec3_t normal; - vec3_t tangent; -}; - -enum CustomMapMaterialType -{ - MATERIAL_TYPE_COLOUR, - MATERIAL_TYPE_TEXTURE, - MATERIAL_TYPE_EMPTY -}; - -struct CustomMapMaterial -{ - CustomMapMaterialType materialType; - std::string materialName; -}; - -struct CustomMapSurface -{ - CustomMapMaterial material; - int triCount; - int indexOfFirstVertex; - int indexOfFirstIndex; -}; - -struct CustomMapWorld -{ - std::vector surfaces; - std::vector vertices; - std::vector indices; -}; - -struct CustomMapBSP -{ - std::string name; - std::string bspName; - - CustomMapWorld gfxWorld; - CustomMapWorld colWorld; -}; diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h new file mode 100644 index 00000000..d2b1dfb1 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include + +#include "Utils/Logging/Log.h" + +#include "Game/T6/T6.h" +using namespace T6; + +namespace BSP +{ + enum BSPMaterialType + { + MATERIAL_TYPE_COLOUR, + MATERIAL_TYPE_TEXTURE, + MATERIAL_TYPE_EMPTY + }; + + struct BSPVertex + { + vec3_t pos; + vec4_t color; + vec2_t texCoord; + vec3_t normal; + vec3_t tangent; + }; + + struct BSPMaterial + { + BSPMaterialType materialType; + std::string materialName; + }; + + struct BSPSurface + { + BSPMaterial material; + int triCount; + int indexOfFirstVertex; + int indexOfFirstIndex; + }; + + struct BSPWorld + { + std::vector surfaces; + std::vector vertices; + std::vector indices; + }; + + struct BSPData + { + std::string name; + std::string bspName; + + BSPWorld gfxWorld; + BSPWorld colWorld; + }; + + // BSPGameConstants: + // These values are hardcoded ingame and will break the map if they are changed + namespace BSPGameConstants + { + constexpr int MAX_COLLISION_VERTS = UINT16_MAX; + + constexpr int STATIC_LIGHT_INDEX = 0; + constexpr int SUN_LIGHT_INDEX = 1; + + inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start" + }; + + inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { + "mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start" + }; + + inline const char* FFA_SPAWN_POINT_NAMES[] = { + "mp_tdm_spawn", + "mp_dm_spawn", + "mp_dom_spawn" + }; + } + + // BSPLinkingConstants: + // These values are BSP linking constants that are required for the link to be successful + namespace BSPLinkingConstants + { + constexpr const char* MISSING_IMAGE_NAME = "missing_image"; + constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; + + constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ + "attackers": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "defenders": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ], + "FFA": [ + { + "origin": "0 0 0", + "angles": "0 0 0" + } + ] + })"; + + + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ + "entities": [ + { + "classname": "worldspawn" + }, + { + "angles": "0 0 0", + "classname": "info_player_start", + "origin": "0 0 0" + }, + { + "angles": "0 0 0", + "classname": "mp_global_intermission", + "origin": "0 0 0" + } + ] + })"; + } + + // BSPEditableConstants: + // These values are BSP constants that can be edited and won't break the linker/game if changed + namespace BSPEditableConstants + { + // Default xmodel values + // Unused as there is no support for xmodels right now + constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; + constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; + constexpr int DEFAULT_SMODEL_LIGHT = 1; + constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; + + // Default surface values + constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; + constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; + constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; + constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); + + // material flags determine the features of a surface + // unsure which flag type changes what right now + // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast + // 1 results in: normal surface features, grenades work, seems normal + constexpr int MATERIAL_SURFACE_FLAGS = 1; + constexpr int MATERIAL_CONTENT_FLAGS = 1; + + // terrain/world flags: does not change the type of terrain or what features they have + // from testing, as long at it isn't 0 things will work correctly + constexpr int LEAF_TERRAIN_CONTENTS = 1; + constexpr int WORLD_TERRAIN_CONTENTS = 1; + + // lightgrid (global) lighting colour + // since lightgrids are not well understood, this colour is used for the R, G and B values right now + constexpr unsigned char LIGHTGRID_COLOUR = 128; + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp new file mode 100644 index 00000000..60ee89cd --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.cpp @@ -0,0 +1,151 @@ +#include "BSPCalculation.h" + +namespace BSP +{ + constexpr int MAX_NODE_SIZE = 512; // maximum size a BSP node can be before it becomes a leaf + + BSPObject::BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) + { + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + partitionIndex = objPartitionIndex; + } + + void BSPLeaf::addObject(std::shared_ptr object) + { + objectList.emplace_back(std::move(object)); + } + + BSPObject* BSPLeaf::getObject(int index) + { + return objectList.at(index).get(); + } + + size_t BSPLeaf::getObjectCount() + { + return objectList.size(); + } + + BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance) + { + front = std::move(frontTree); + back = std::move(backTree); + axis = nodeAxis; + distance = nodeDistance; + } + + PlaneSide BSPNode::objectIsInFront(BSPObject* object) + { + float minCoord, maxCoord; + + // Select the relevant coordinate based on the plane's axis + if (axis == AXIS_X) + { + minCoord = object->min.x; + maxCoord = object->max.x; + } + else if (axis == AXIS_Y) + { + minCoord = object->min.y; + maxCoord = object->max.y; + } + else // axis == AXIS_Z + { + minCoord = object->min.z; + maxCoord = object->max.z; + } + + // Compare with the plane's distance + if (maxCoord < distance) + { + return SIDE_BACK; // Object is entirely on the negative side + } + else if (minCoord > distance) + { + return SIDE_FRONT; // Object is entirely on the positive side + } + else + { + return SIDE_INTERSECTS; + } + } + + BSPTree::BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel) + { + min.x = xMin; + min.y = yMin; + min.z = zMin; + max.x = xMax; + max.y = yMax; + max.z = zMax; + level = treeLevel; + splitTree(); + } + + // For simplicity, only split across the X and Z axis. + // It is unlikely that there are many layers to a map, and is instead mostly flat + void BSPTree::splitTree() + { + std::unique_ptr front; + std::unique_ptr back; + float halfLength; + + if (max.x - min.x > MAX_NODE_SIZE) + { + // split along the x axis + halfLength = (min.x + max.x) * 0.5f; + front = std::make_unique(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); + leaf = nullptr; + } + else if (max.z - min.z > MAX_NODE_SIZE) + { + // split along the z axis + halfLength = (min.z + max.z) * 0.5f; + front = std::make_unique(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1); + back = std::make_unique(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1); + + isLeaf = false; + node = std::make_unique(std::move(front), std::move(back), AXIS_Z, halfLength); + leaf = nullptr; + } + else + { + isLeaf = true; + node = nullptr; + leaf = std::make_unique(); + } + } + + void BSPTree::addObjectToTree(std::shared_ptr object) + { + if (isLeaf) + { + leaf->addObject(std::move(object)); + } + else + { + PlaneSide side = node->objectIsInFront(object.get()); + if (side == SIDE_FRONT) + { + node->front->addObjectToTree(std::move(object)); + } + else if (side == SIDE_BACK) + { + node->back->addObjectToTree(std::move(object)); + } + else // intersects + { + node->front->addObjectToTree(object); + node->back->addObjectToTree(object); + } + } + } +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCalculation.h b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h new file mode 100644 index 00000000..61b8de07 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCalculation.h @@ -0,0 +1,72 @@ +#pragma once + +#include "BSP.h" + +namespace BSP +{ + enum PlaneAxis + { + AXIS_X, + AXIS_Y, + AXIS_Z + }; + + enum PlaneSide + { + SIDE_FRONT, + SIDE_BACK, + SIDE_INTERSECTS + }; + + class BSPObject + { + public: + vec3_t min; + vec3_t max; + int partitionIndex; // index of the partition the object is contained in + + BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); + }; + + + class BSPLeaf + { + public: + std::vector> objectList; + + void addObject(std::shared_ptr object); + BSPObject* getObject(int index); + size_t getObjectCount(); + }; + + class BSPTree; + + class BSPNode + { + public: + std::unique_ptr front; + std::unique_ptr back; + + PlaneAxis axis; // axis that the split plane is on + float distance; // distance from the origin (0, 0, 0) to the plane + + BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); + PlaneSide objectIsInFront(BSPObject* object); + }; + + class BSPTree + { + public: + bool isLeaf; + std::unique_ptr leaf; + std::unique_ptr node; + + int level; // level in the BSP tree + vec3_t min; + vec3_t max; + + BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); + void splitTree(); + void addObjectToTree(std::shared_ptr object); + }; +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp new file mode 100644 index 00000000..322d940f --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -0,0 +1,269 @@ +#include "BSPCreator.h" +#include "fbx/ufbx.h" + +namespace +{ + using namespace BSP; + + void addFBXMeshToWorld(ufbx_node* node, + std::vector& surfaceVec, + std::vector& vertexVec, + std::vector& indexVec, + bool& hasTangentSpace) + { + ufbx_mesh* mesh = node->mesh; + + assert(node->attrib_type == UFBX_ELEMENT_MESH); + + if (mesh->instances.count != 1) + con::warn("mesh {} has {} instances, only the 1st instace will be used.", node->name.data, mesh->instances.count); + + if (mesh->num_triangles == 0) + { + con::warn("ignoring mesh {}: triangle count is 0.", node->name.data); + return; + } + + if (mesh->num_indices % 3 != 0) + { + con::warn("ignoring mesh {}: it is not triangulated.", node->name.data); + return; + } + + for (size_t k = 0; k < mesh->num_indices; k++) + { + if (mesh->vertex_indices[k] > UINT16_MAX) + { + con::warn("ignoring mesh {}, it has more than {} indices.", node->name.data, UINT16_MAX); + return; + } + } + + if (mesh->vertex_tangent.exists == false) + hasTangentSpace = false; + + // Fix the target_unit_meters ufbx opt not working + // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back + ufbx_transform origTransform = node->local_transform; + origTransform.translation.x /= 100.0f; + origTransform.translation.y /= 100.0f; + origTransform.translation.z /= 100.0f; + origTransform.scale.x /= 100.0f; + origTransform.scale.y /= 100.0f; + origTransform.scale.z /= 100.0f; + ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); + + for (ufbx_mesh_part& meshPart : mesh->material_parts) + { + if (meshPart.num_faces == 0) + continue; + + surfaceVec.emplace_back(); + BSPSurface* surface = &surfaceVec[surfaceVec.size() - 1]; + + size_t partTriangleNum = meshPart.num_triangles; + surface->triCount = static_cast(partTriangleNum); + surface->indexOfFirstVertex = vertexVec.size(); + surface->indexOfFirstIndex = indexVec.size(); + + if (mesh->materials.count == 0) + { + surface->material.materialType = MATERIAL_TYPE_EMPTY; + surface->material.materialName = ""; + } + else + { + surface->material.materialType = MATERIAL_TYPE_TEXTURE; + surface->material.materialName = mesh->materials.data[meshPart.index]->name.data; + } + + std::vector vertices; + std::vector indices; + indices.resize(mesh->max_face_triangles * 3); + for (uint32_t faceIndex : meshPart.face_indices) + { + ufbx_face* face = &mesh->faces.data[faceIndex]; + + // Triangulate the face into the indices vector + uint32_t triangluatedTriCount = ufbx_triangulate_face(indices.data(), indices.size(), mesh, *face); + + // Iterate over each triangle corner contiguously. + for (uint32_t idxOfIndex = 0; idxOfIndex < triangluatedTriCount * 3; idxOfIndex++) + { + vertices.emplace_back(); + BSPVertex* vertex = &vertices[vertices.size() - 1]; + uint32_t index = indices[idxOfIndex]; + + ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); + vertex->pos.x = static_cast(transformedPos.x); + vertex->pos.y = static_cast(transformedPos.y); + vertex->pos.z = static_cast(transformedPos.z); + + if (surface->material.materialType == MATERIAL_TYPE_TEXTURE || surface->material.materialType == MATERIAL_TYPE_EMPTY) + { + vertex->color.x = 1.0f; + vertex->color.y = 1.0f; + vertex->color.z = 1.0f; + vertex->color.w = 1.0f; + } + else // surface->material.materialType == MATERIAL_TYPE_COLOUR + { + float factor = static_cast(mesh->materials.data[meshPart.index]->fbx.diffuse_factor.value_real); + ufbx_vec4 diffuse = mesh->materials.data[meshPart.index]->fbx.diffuse_color.value_vec4; + vertex->color.x = static_cast(diffuse.x * factor); + vertex->color.y = static_cast(diffuse.y * factor); + vertex->color.z = static_cast(diffuse.z * factor); + vertex->color.w = static_cast(diffuse.w * factor); + } + + // 1.0f - uv.y reason: https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange + ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); + vertex->texCoord.x = static_cast(uv.x); + vertex->texCoord.y = static_cast(1.0f - uv.y); + + ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); + vertex->normal.x = static_cast(normal.x); + vertex->normal.y = static_cast(normal.y); + vertex->normal.z = static_cast(normal.z); + + if (mesh->vertex_tangent.exists) + { + ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); + vertex->tangent.x = static_cast(tangent.x); + vertex->tangent.y = static_cast(tangent.y); + vertex->tangent.z = static_cast(tangent.z); + } + else + { + vertex->tangent.x = 0.0f; + vertex->tangent.y = 0.0f; + vertex->tangent.z = 0.0f; + } + } + } + + // Generate the index buffer. + // ufbx_generate_indices will deduplicate vertices, modifying the arrays passed in streams, + // indices are written to outIndices and the number of unique vertices is returned. + ufbx_vertex_stream streams[1] = { + {vertices.data(), vertices.size(), sizeof(BSPVertex)}, + }; + std::vector outIndices; + outIndices.resize(partTriangleNum * 3); + size_t numGeneratedVertices = ufbx_generate_indices(streams, 1, outIndices.data(), outIndices.size(), nullptr, nullptr); + assert(numGeneratedVertices != 0); + + // trim non-unique vertexes and add to the world vertex vector + vertices.resize(numGeneratedVertices); + vertexVec.insert(vertexVec.end(), vertices.begin(), vertices.end()); + + // T6 uses unsigned shorts as their index type so we have to loop and convert them from an unsigned int + for (size_t idx = 0; idx < outIndices.size(); idx++) + { + indexVec.emplace_back(static_cast(outIndices[idx])); + } + } + } + + void loadWorldData(ufbx_scene* scene, BSPData* bsp, bool isGfxData) + { + bool hasTangentSpace = true; + for (ufbx_node* node : scene->nodes) + { + if (node->attrib_type == UFBX_ELEMENT_MESH) + { + if (isGfxData) + addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); + else + addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); + } + else + { + con::debug("ignoring node type {}: {}", static_cast(node->attrib_type), node->name.data); + } + } + + if (hasTangentSpace == false) + con::warn("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX."); + } +} + +namespace BSP +{ + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath) + { + std::string gfxFbxPath = "BSP/map_gfx.fbx"; + auto gfxFile = searchPath.Open(gfxFbxPath); + if (!gfxFile.IsOpen()) + { + con::error("Failed to open map gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + std::unique_ptr gfxMapData(new char[static_cast(gfxFile.m_length)]); + gfxFile.m_stream->read(gfxMapData.get(), gfxFile.m_length); + if (gfxFile.m_stream->gcount() != gfxFile.m_length) + { + con::error("Read error of gfx fbx file: {}", gfxFbxPath); + return nullptr; + } + + ufbx_error errorGfx; + ufbx_load_opts optsGfx {}; + optsGfx.target_axes = ufbx_axes_right_handed_y_up; + optsGfx.generate_missing_normals = true; + optsGfx.allow_missing_vertex_position = false; + ufbx_scene* gfxScene = ufbx_load_memory(gfxMapData.get(), static_cast(gfxFile.m_length), &optsGfx, &errorGfx); + if (!gfxScene) + { + con::error("Failed to load map gfx fbx file: {}", errorGfx.description.data); + return nullptr; + } + + ufbx_scene* colScene; + std::string colFbxPath = "BSP/map_col.fbx"; + auto colFile = searchPath.Open(colFbxPath); + if (!colFile.IsOpen()) + { + con::warn("Failed to open map collison fbx file: {}. map gfx will be used for collision instead.", colFbxPath); + colScene = gfxScene; + } + else + { + std::unique_ptr colMapData(new char[static_cast(colFile.m_length)]); + colFile.m_stream->seekg(0); + colFile.m_stream->read(colMapData.get(), colFile.m_length); + if (colFile.m_stream->gcount() != colFile.m_length) + { + con::error("Read error of collision fbx file: {}", colFbxPath); + return nullptr; + } + + ufbx_error errorCol; + ufbx_load_opts optsCol {}; + optsCol.target_axes = ufbx_axes_right_handed_y_up; + optsCol.generate_missing_normals = true; + optsCol.allow_missing_vertex_position = false; + colScene = ufbx_load_memory(colMapData.get(), static_cast(colFile.m_length), &optsCol, &errorCol); + if (!colScene) + { + con::error("Failed to load map collision fbx file: {}", errorCol.description.data); + return nullptr; + } + } + + std::unique_ptr bsp; + + bsp->name = mapName; + bsp->bspName = "maps/mp/" + mapName + ".d3dbsp"; + + loadWorldData(gfxScene, bsp.get(), true); + loadWorldData(colScene, bsp.get(), false); + + ufbx_free_scene(gfxScene); + if (gfxScene != colScene) + ufbx_free_scene(colScene); + + return bsp; + } +} // namespace BSP \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.h b/src/ObjLoading/Game/T6/BSP/BSPCreator.h new file mode 100644 index 00000000..7a8395da --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.h @@ -0,0 +1,9 @@ +#pragma once + +#include "BSP.h" +#include "SearchPath/ISearchPath.h" + +namespace BSP +{ + std::unique_ptr createBSPData(std::string& mapName, ISearchPath& searchPath); +}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/BSPUtil.cpp rename to src/ObjLoading/Game/T6/BSP/BSPUtil.cpp diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h similarity index 92% rename from src/ObjLoading/Game/T6/CustomMap/BSPUtil.h rename to src/ObjLoading/Game/T6/BSP/BSPUtil.h index 63961986..ddbcd58e 100644 --- a/src/ObjLoading/Game/T6/CustomMap/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -1,9 +1,7 @@ #pragma once -#include +#include "BSP.h" -#include "Game/T6/T6.h" -using namespace T6; class BSPUtil { diff --git a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h new file mode 100644 index 00000000..9b11e348 --- /dev/null +++ b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h @@ -0,0 +1,1987 @@ +#pragma once + +#include "BSP.h" +#include "BSPCalculation.h" +#include "BSPUtil.h" + +#include "Game/T6/Material/JsonMaterialLoaderT6.h" +#include "Utils/Pack.h" + +#include +using namespace nlohmann; + +namespace BSP +{ + class CustomMapLinker + { + public: + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone), + m_context(context) + { + hasLinkFailed = false; + } + + bool linkCustomMap(BSPData* projInfo) + { + _ASSERT(projInfo != NULL); + + checkAndAddDefaultRequiredAssets(projInfo); + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + + createComWorld(projInfo); + createMapEnts(projInfo); + createGameWorldMp(projInfo); + createSkinnedVerts(projInfo); + createGfxWorld(projInfo); // requires mapents asset + createClipMap(projInfo); // must go last (requires gfx and mapents asset) + + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + return true; + } + + private: + struct entModelBounds + { + vec3_t mins; + vec3_t maxs; + }; + + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + AssetCreationContext& m_context; + + bool hasLinkFailed; + std::vector entityModelList; + + json materialTemplateJson; + + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + bool overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxWorld.vertices.size(); + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); + //GfxVertex->xyz = WorldVertex->pos; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); + //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); + //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); + + // unknown use variables + // binormalSign may be bitangent of the vertex + // lmapCoord may be the lightmap coordinate of the vertex + GfxVertex->binormalSign = 0.0f; + GfxVertex->lmapCoord.packed = 0; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxWorld.indices.size(); + _ASSERT(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = new uint16_t[indexCount]; + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; + } + + return true; + } + + struct s_sortedSurf + { + int surfaceIndex; + int vertexCount; + }; + + bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) + { + return surf0.vertexCount > surf1.vertexCount; + } + + Material* loadImageIntoMaterial(std::string& imageName) + { + std::string imagePath = std::format("images/{}.iwi", imageName); + auto imageFile = m_search_path.Open(imagePath); + if (!imageFile.IsOpen()) + { + printf("WARN: failed to find image %s.\n", imageName.c_str()); + return NULL; + } + + Material* material = new Material; + material->info.name = m_memory.Dup(imageName.c_str()); + + // parse the template file and replace the image name + materialTemplateJson["textures"][1]["image"] = imageName; + + AssetRegistration registration(imageName, material); + if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) + { + printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); + return NULL; + } + + m_context.AddAsset(std::move(registration)); + + return material; + } + + void overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) + { + bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); + if (!overwriteResult) + return; + + unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; + for (unsigned int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxWorld.surfaces[i]; + + currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; + + currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + std::string surfMaterialName; + switch (objSurface->material.materialType) + { + case MATERIAL_TYPE_TEXTURE: + surfMaterialName = objSurface->material.materialName; + break; + + case MATERIAL_TYPE_COLOUR: + case MATERIAL_TYPE_EMPTY: + surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + break; + + default: + _ASSERT(false); + } + Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); + if (surfMaterial == NULL) + { + std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); + surfMaterial = loadImageIntoMaterial(missingImageName); + if (surfMaterial == NULL) + { + printf("Error: unable to find the missing image texture!\n"); + hasLinkFailed = true; + return; + } + } + currSurface->material = surfMaterial; + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; + BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // doesn't seem to matter what order the sorted surfs go in + gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; + for (unsigned int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = i; + } + + gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // all visdata is alligned by 128 + gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; + gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; + gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; + memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); + + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + } + + void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) + { + /* + Models are unsupported right now + Code is left in in case it is supported later on + */ + //unsigned int modelCount = projInfo->modelCount; + //gfxWorld->dpvs.smodelCount = modelCount; + //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + // + //for (unsigned int i = 0; i < modelCount; i++) + //{ + // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + // customMapModel* inModel = &projInfo->models[i]; + // + // auto xModelAsset = m_context.LoadDependency(inModel->name); + // if (xModelAsset == NULL) + // { + // printf("XModel %s not found!\n", inModel->name.c_str()); + // currModel->model = NULL; + // } + // else + // currModel->model = (XModel*)xModelAsset->Asset(); + // + // currModel->placement.origin.x = inModel->origin.x; + // currModel->placement.origin.y = inModel->origin.y; + // currModel->placement.origin.z = inModel->origin.z; + // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.scale = inModel->scale; + // + // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // + // // mins and maxs are calculated in world space not local space + // // TODO: this does not account for model rotation or scale + // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + // + // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + // currModel->flags = DEFAULT_SMODEL_FLAGS; + // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + // + // // unknown use / unused + // currModel->smid = i; + // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + // currModel->invScaleSq = 0.0f; + // currModel->lightingHandle = 0; + // currModel->colorsIndex = 0; + // currModel->visibility = 0; + // + // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // // TODO: calculate lighting and store it here + // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + //} + + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + + // all visdata is alligned by 128 + int allignedModelCount = BSPUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; + memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + + // always set to 0 + gfxWorld->dpvs.usageCount = 0; + } + + void cleanGfxWorld(GfxWorld* gfxWorld) + { + // checksum is generated by the game + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = new GfxLight; + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + + gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; + memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); + } + + gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; + memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + // the lightgrid is used to light models in a dynamic way and is precomputed + void overwriteLightGrid(GfxWorld* gfxWorld) + { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value + + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); + + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); + GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 + { + row->lookupTable[i] = 0; + } + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + + // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; + memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + + // we use the colours array instead of coeffs array + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = new GfxCell[cellCount]; + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; + reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + + // planes are overwritten by the clipmap loading code ingame + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void overwriteModels(GfxWorld* gfxWorld) + { + // these models are the collision for the entities defined in the mapents asset + // used for triggers and stuff + + gfxWorld->modelCount = entityModelList.size() + 1; + gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; + + // first model is always the world model + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + 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)); + } + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = (float)0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + void updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + } + + void overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; + gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); + hasLinkFailed = true; + return; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + } + + void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) + { + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void updateDynEntData(GfxWorld* gfxWorld) + { + int dynEntCount = 0; + gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + void updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + printf("ERROR! unable to find image $outdoor!\n"); + hasLinkFailed = true; + return; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + } + + void createGfxWorld(BSPData* projInfo) + { + GfxWorld* gfxWorld = new GfxWorld; + gfxWorld->baseName = _strdup(projInfo->name.c_str()); + gfxWorld->name = _strdup(projInfo->bspName.c_str()); + + // Default values taken from mp_dig + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + overwriteMapSurfaces(projInfo, gfxWorld); + + overwriteMapSModels(projInfo, gfxWorld); + + overwriteLightmapData(gfxWorld); + + overwriteSkyBox(projInfo, gfxWorld); + + updateReflectionProbeData(gfxWorld); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + updateOutdoors(gfxWorld); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + m_context.AddAsset(gfxWorld->name, gfxWorld); + } + + void addXModelsToCollision(BSPData* projInfo, clipMap_t* clipMap) + { + auto gfxWorldAsset = m_context.LoadDependency(projInfo->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; + } + + bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) + { + + unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); + + return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; + } + + 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) + { + new cplane_s; + new cNode_t; + new cLeaf_s; + new CollisionAabbTree; + + 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->u.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 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 = new cplane_s[clipMap->info.planeCount]; + clipMap->numNodes = numNodes; + clipMap->nodes = new cNode_t[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 = new CollisionAabbTree[clipMap->aabbTreeCount]; + + currPlaneCount = 0; + currNodeCount = 0; + currAABBCount = 0; + + // first leaf is always empty + clipMap->numLeafs = numLeafs + 1; + clipMap->leafs = new cLeaf_s[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); + } + + void createPartitions(BSPData* projInfo, clipMap_t* clipMap) + { + int collisionVertexCount = projInfo->colWorld.vertices.size(); + std::vector collisionVertVec; + for (int i = 0; i < collisionVertexCount; i++) + { + collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); + //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = new vec3_t[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); + hasLinkFailed = true; + return; + } + + std::vector triIndexVec; + for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) + { + BSPSurface* currSurface = &projInfo->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 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; + int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; + int triIndex2 = projInfo->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); + clipMap->triCount = triIndexVec.size() / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[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 < projInfo->colWorld.surfaces.size(); i++) + { + int triCount = projInfo->colWorld.surfaces[i].triCount; + int firstTriIndex = projInfo->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 = new CollisionPartition[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 = new uint16_t[totalUindCount]; + memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); + } + + void createClipMap(BSPData* projInfo) + { + clipMap_t* clipMap = new clipMap_t; + + clipMap->name = _strdup(projInfo->bspName.c_str()); + + std::string mapEntsName = projInfo->bspName.c_str(); + auto mapEntsAsset = m_context.LoadDependency(mapEntsName); + _ASSERT(mapEntsAsset != NULL); + clipMap->mapEnts = mapEntsAsset->Asset(); + + clipMap->pInfo = NULL; + + 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 = NULL; + + // 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 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; + *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; + *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; + *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; + *((unsigned int*)&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_brush = new cbrush_t; + 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 = NULL; + clipMap->box_brush->verts = NULL; + + clipMap->numClusters = 1; + clipMap->vised = 0; + clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; + clipMap->visibility = new char[clipMap->clusterBytes]; + memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); + + clipMap->isInUse = true; + clipMap->checksum = 0; + + clipMap->num_constraints = 0; + clipMap->constraints = NULL; + clipMap->max_ropes = 32; + clipMap->ropes = new rope_t[clipMap->max_ropes]; + memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); + + 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; + + 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; + + // assume that there are 0 dyn ents from here on + clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; + clipMap->dynEntClientList[1] = NULL; + memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); + + clipMap->dynEntServerList[0] = NULL; + clipMap->dynEntServerList[1] = NULL; + + clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; + clipMap->dynEntCollList[1] = NULL; + clipMap->dynEntCollList[2] = NULL; + clipMap->dynEntCollList[3] = NULL; + memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); + + clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; + clipMap->dynEntPoseList[1] = NULL; + memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); + + clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; + clipMap->dynEntDefList[1] = NULL; + memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); + + // cmodels is the collision for mapents + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + clipMap->numSubModels = gfxWorld->modelCount; + clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; + for (unsigned int i = 0; i < clipMap->numSubModels; i++) + { + // bomb triggers use leafs, not world terrain so that might be an issue + + GfxBrushModel* gfxModel = &gfxWorld->models[i]; + cmodel_t* cmModel = &clipMap->cmodels[i]; + + cmModel->leaf.firstCollAabbIndex = 0; + cmModel->leaf.collAabbCount = 0; + cmModel->leaf.brushContents = 0; + cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; + cmModel->leaf.mins.x = 0.0f; + cmModel->leaf.mins.y = 0.0f; + cmModel->leaf.mins.z = 0.0f; + cmModel->leaf.maxs.x = 0.0f; + cmModel->leaf.maxs.y = 0.0f; + cmModel->leaf.maxs.z = 0.0f; + cmModel->leaf.leafBrushNode = 0; + cmModel->leaf.cluster = 0; + cmModel->info = NULL; + cmModel->mins.x = gfxModel->bounds[0].x; + cmModel->mins.y = gfxModel->bounds[0].y; + cmModel->mins.z = gfxModel->bounds[0].z; + cmModel->maxs.x = gfxModel->bounds[1].x; + cmModel->maxs.y = gfxModel->bounds[1].y; + cmModel->maxs.z = gfxModel->bounds[1].z; + cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; + } + + addXModelsToCollision(projInfo, clipMap); + + clipMap->info.numMaterials = 1; + clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; + clipMap->info.materials[0].name = _strdup(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 (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility + // might do weird stuff on walls, but from testing doesnt seem to do much + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); + + // 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 + + createPartitions(projInfo, clipMap); + + 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); + + m_context.AddAsset(clipMap->name, clipMap); + } + + void createComWorld(BSPData* projInfo) + { + // all lights that aren't the sunlight or default light need their own GfxLightDef asset + ComWorld* comWorld = new ComWorld; + comWorld->name = _strdup(projInfo->bspName.c_str()); + comWorld->isInUse = 1; + comWorld->primaryLightCount = 2; + comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; + + // default light is always empty + ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; + memset(defaultLight, 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; + + m_context.AddAsset(comWorld->name, comWorld); + } + + void 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) + { + printf("ERROR: first entity in the map entity string must be the worldspawn class!"); + hasLinkFailed = true; + return; + } + } + + 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"); + } + } + + 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"); + } + } + } + + void parseBombJSON(json& bombJs, std::string& entityString) + { + // add the bomb model + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + entityString.append("{\n"); + entityString.append("\"classname\" \"script_model\"\n"); + entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); + entityString.append("\"targetname\" \"sd_bomb\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append("\"spawnflags\" \"4\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append("}\n"); + } + if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) + { + hasLinkFailed = true; + printf("ERROR: unable to find s&d bomb xmodel\n"); + return; + } + + // add the bomb pickup trigger + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); + entModelBounds bounds; + bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig + bounds.mins.y = bomboriginV3.y - 32.0f; + bounds.mins.z = bomboriginV3.z - 8.0f; + bounds.maxs.x = bomboriginV3.x + 32.0f; + bounds.maxs.y = bomboriginV3.y + 32.0f; + bounds.maxs.z = bomboriginV3.z + 28.0f; + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_multiple\"\n"); + entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add A site bomb + { + std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; + std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; + vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); + vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteAPoint1V3.x; + bounds.mins.y = siteAPoint1V3.y; + bounds.mins.z = siteAPoint1V3.z; + bounds.maxs.x = siteAPoint1V3.x; + bounds.maxs.y = siteAPoint1V3.y; + bounds.maxs.z = siteAPoint1V3.z; + BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_a\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add B site bomb + { + std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; + std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; + vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); + vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteBPoint1V3.x; + bounds.mins.y = siteBPoint1V3.y; + bounds.mins.z = siteBPoint1V3.z; + bounds.maxs.x = siteBPoint1V3.x; + bounds.maxs.y = siteBPoint1V3.y; + bounds.maxs.z = siteBPoint1V3.z; + BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_b\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + } + + void createMapEnts(BSPData* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + std::string entityString; + + const auto entFile = m_search_path.Open("BSP/entities.json"); + json entJs; + if (!entFile.IsOpen()) + { + printf("WARN: can't find entity json BSP/entities.json, using default entities\n"); + entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + } + else + { + entJs = json::parse(*entFile.m_stream); + } + parseMapEntsJSON(entJs["entities"], entityString); + + const auto spawnFile = m_search_path.Open("BSP/spawns.json"); + json spawnJs; + if (!spawnFile.IsOpen()) + { + printf("WARN: cant find BSP/spawns.json, setting spawns to 0 0 0\n"); + 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); + + //const auto objectiveFile = m_search_path.Open("objectives.json"); + //if (!spawnFile.IsOpen()) + //{ + // printf("WARN: no objectives given\n"); + //} + //else + //{ + // json objectiveJs = json::parse(*objectiveFile.m_stream); + // parseBombJSON(objectiveJs, entityString); + //} + + mapEnts->entityString = _strdup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + m_context.AddAsset(mapEnts->name, mapEnts); + } + + void createGameWorldMp(BSPData* projInfo) + { + GameWorldMp* gameWorldMp = new GameWorldMp; + + gameWorldMp->name = _strdup(projInfo->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 = new pathnode_t[nodeCount]; + gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; + memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); + memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); + + gameWorldMp->path.pathVis = NULL; + gameWorldMp->path.smoothCache = NULL; + gameWorldMp->path.nodeTree = NULL; + + m_context.AddAsset(gameWorldMp->name, gameWorldMp); + } + + void createSkinnedVerts(BSPData* projInfo) + { + SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; + skinnedVerts->name = "skinnedverts"; + skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); + // 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 + + m_context.AddAsset("skinnedverts", skinnedVerts); + } + + FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) + { + if (assetName.length() == 0) + return NULL; + + FootstepTableDef* footstepTable = new FootstepTableDef; + footstepTable->name = _strdup(assetName.c_str()); + memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); + + m_context.AddAsset(assetName, footstepTable); + + return footstepTable; + } + + void checkAndAddDefaultRequiredAssets(BSPData* projectInfo) + { + auto templateFile = m_search_path.Open("materials/material_template.json"); + if (!templateFile.IsOpen()) + { + printf("ERROR: failed to open materials/material_template.json\n"); + hasLinkFailed = true; + return; + } + materialTemplateJson = json::parse(*templateFile.m_stream); + + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) + { + hasLinkFailed = true; + return; + } + + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) + { + hasLinkFailed = true; + return; + } + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) + { + hasLinkFailed = true; + return; + } + + 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") == NULL) + { + hasLinkFailed = true; + return; + } + } + }; + +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp similarity index 55% rename from src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp rename to src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index 58e4bd55..708602f1 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,21 +1,15 @@ -#include "Game/T6/Maps/CustomMaps.h" - -#include "LoaderCustomMapT6.h" +#include "LoaderBSP_T6.h" #include "BSPCreator.h" #include "CustomMapLinker.h" -#include "Game/T6/T6.h" - -#include - -using namespace T6; - namespace { - class CustomMapLoader final : public AssetCreator + using namespace BSP; + + class BSPLoader final : public AssetCreator { public: - CustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) : m_memory(memory), m_search_path(searchPath), m_zone(zone) @@ -25,21 +19,21 @@ namespace AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { // custom maps must have a map_gfx file - auto mapGfxFile = m_search_path.Open("custom_map/map_gfx.fbx"); + auto mapGfxFile = m_search_path.Open("BSP/map_gfx.fbx"); if (!mapGfxFile.IsOpen()) return AssetCreationResult::NoAction(); - CustomMapBSP* mapBSP = BSPCreator::createCustomMapBSP(m_zone.m_name, m_search_path); - if (mapBSP == NULL) + BSPData* BSP = BSP::createBSPData(m_zone.m_name, m_search_path); + if (BSP == nullptr) return AssetCreationResult::Failure(); - CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone, context); - bool result = linker->linkCustomMap(mapBSP); + CustomMapLinker linker(m_memory, m_search_path, m_zone, context); + bool result = linker.linkCustomMap(BSP); if (result) { - auto gfxWorldAsset = context.LoadDependency(mapBSP->bspName); - _ASSERT(gfxWorldAsset != NULL); + auto gfxWorldAsset = context.LoadDependency(BSP->bspName); + _ASSERT(gfxWorldAsset != nullptr); return AssetCreationResult::Success(gfxWorldAsset); } else @@ -53,10 +47,10 @@ namespace }; } // namespace -namespace custom_map +namespace BSP { std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) { - return std::make_unique(memory, searchPath, zone); + return std::make_unique(memory, searchPath, zone); } -} // namespace custom_map +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h similarity index 86% rename from src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h rename to src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h index 5cf0e007..4cacf2f2 100644 --- a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.h @@ -7,7 +7,7 @@ #include -namespace custom_map +namespace BSP { std::unique_ptr> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); -} // namespace custom_map +} // namespace BSP diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.cpp rename to src/ObjLoading/Game/T6/BSP/fbx/ufbx.cpp diff --git a/src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h b/src/ObjLoading/Game/T6/BSP/fbx/ufbx.h similarity index 100% rename from src/ObjLoading/Game/T6/CustomMap/fbx/ufbx.h rename to src/ObjLoading/Game/T6/BSP/fbx/ufbx.h diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp deleted file mode 100644 index 56396ab5..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "BSPCalculation.h" - - -BSPObject::BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex) -{ - min.x = xMin; - min.y = yMin; - min.z = zMin; - max.x = xMax; - max.y = yMax; - max.z = zMax; - partitionIndex = objPartitionIndex; -} - -void BSPLeaf::addObject(std::shared_ptr object) -{ - objectList.emplace_back(std::move(object)); -} - -BSPObject* BSPLeaf::getObject(int index) -{ - return objectList.at(index).get(); -} - -int BSPLeaf::getObjectCount() -{ - return objectList.size(); -} - -BSPNode::BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance) -{ - front = std::move(frontTree); - back = std::move(backTree); - axis = nodeAxis; - distance = nodeDistance; -} - -PlaneSide BSPNode::objectIsInfront(BSPObject* object) -{ - float minCoord, maxCoord; - - // Select the relevant coordinate based on the plane's axis - if (axis == AXIS_X) - { - minCoord = object->min.x; - maxCoord = object->max.x; - } - else if (axis == AXIS_Y) - { - minCoord = object->min.y; - maxCoord = object->max.y; - } - else // axis == AXIS_Z - { - minCoord = object->min.z; - maxCoord = object->max.z; - } - - // Compare with the plane's distance - if (maxCoord < distance) - { - return SIDE_BACK; // Object is entirely on the negative side - } - else if (minCoord > distance) - { - return SIDE_FRONT; // Object is entirely on the positive side - } - else - { - return SIDE_INTERSECTS; - } -} - -BSPTree::BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel) -{ - min.x = xMin; - min.y = yMin; - min.z = zMin; - max.x = xMax; - max.y = yMax; - max.z = zMax; - level = treeLevel; - splitTree(); -} - -// For simplicity, only split across the X and Z axis. -// It is unlikely that there are many layers to a map, and is instead mostly flat -void BSPTree::splitTree() -{ - std::unique_ptr front; - std::unique_ptr back; - float halfLength; - - if (max.x - min.x > MAX_NODE_SIZE) - { - // split along the x axis - halfLength = (min.x + max.x) * 0.5f; - front = std::make_unique(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1); - back = std::make_unique(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1); - - isLeaf = false; - node = std::make_unique(std::move(front), std::move(back), AXIS_X, halfLength); - leaf = nullptr; - } - else if (max.z - min.z > MAX_NODE_SIZE) - { - // split along the z axis - halfLength = (min.z + max.z) * 0.5f; - front = std::make_unique(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1); - back = std::make_unique(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1); - - isLeaf = false; - node = std::make_unique(std::move(front), std::move(back), AXIS_Z, halfLength); - leaf = nullptr; - } - else - { - isLeaf = true; - node = nullptr; - leaf = std::make_unique(); - } -} - -void BSPTree::addObjectToTree(std::shared_ptr object) -{ - if (isLeaf) - { - leaf->addObject(std::move(object)); - } - else - { - PlaneSide side = node->objectIsInfront(object.get()); - if (side == SIDE_FRONT) - { - node->front->addObjectToTree(std::move(object)); - } - else if (side == SIDE_BACK) - { - node->back->addObjectToTree(std::move(object)); - } - else // intersects - { - node->front->addObjectToTree(object); - node->back->addObjectToTree(object); - } - } -} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h b/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h deleted file mode 100644 index 8f15fee7..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCalculation.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include - -#include "Game/T6/T6.h" -using namespace T6; - -constexpr int MAX_NODE_SIZE = 512; // maximum size a BSP node can be before it becomes a leaf - -enum PlaneAxis -{ - AXIS_X, - AXIS_Y, - AXIS_Z -}; - -enum PlaneSide -{ - SIDE_FRONT, - SIDE_BACK, - SIDE_INTERSECTS -}; - -class BSPObject -{ -public: - vec3_t min; - vec3_t max; - int partitionIndex; // index of the partition the object is contained in - - BSPObject(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int objPartitionIndex); -}; - - -class BSPLeaf -{ -public: - std::vector> objectList; - - void addObject(std::shared_ptr object); - BSPObject* getObject(int index); - int getObjectCount(); -}; - -class BSPTree; - -class BSPNode -{ -public: - std::unique_ptr front; - std::unique_ptr back; - - PlaneAxis axis; // axis that the split plane is on - float distance; // distance from the origin (0, 0, 0) to the plane - - BSPNode(std::unique_ptr frontTree, std::unique_ptr backTree, PlaneAxis nodeAxis, float nodeDistance); - PlaneSide objectIsInfront(BSPObject* object); -}; - -class BSPTree -{ -public: - bool isLeaf; - std::unique_ptr leaf; - std::unique_ptr node; - - int level; // level in the BSP tree - vec3_t min; - vec3_t max; - - BSPTree(float xMin, float yMin, float zMin, float xMax, float yMax, float zMax, int treeLevel); - void splitTree(); - void addObjectToTree(std::shared_ptr object); -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h b/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h deleted file mode 100644 index 959ca376..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPConstants.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -// BSPGameConstants: -// These values are hardcoded ingame and will break the map if they are changed -namespace BSPGameConstants -{ - constexpr int MAX_COLLISION_VERTS = UINT16_MAX; - - constexpr int STATIC_LIGHT_INDEX = 0; - constexpr int SUN_LIGHT_INDEX = 1; - - inline const char* DEFENDER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_allies", - "mp_ctf_spawn_allies_start", - "mp_sd_spawn_defender", - "mp_dom_spawn_allies_start", - "mp_dem_spawn_defender_start", - "mp_dem_spawn_defenderOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_allies_start", - "mp_tdm_spawn_team1_start", - "mp_tdm_spawn_team2_start", - "mp_tdm_spawn_team3_start" - }; - - inline const char* ATTACKER_SPAWN_POINT_NAMES[] = { - "mp_ctf_spawn_axis", - "mp_ctf_spawn_axis_start", - "mp_sd_spawn_attacker", - "mp_dom_spawn_axis_start", - "mp_dem_spawn_attacker_start", - "mp_dem_spawn_attackerOT_start", - "mp_dem_spawn_defender", - "mp_tdm_spawn_axis_start", - "mp_tdm_spawn_team4_start", - "mp_tdm_spawn_team5_start", - "mp_tdm_spawn_team6_start" - }; - - inline const char* FFA_SPAWN_POINT_NAMES[] = { - "mp_tdm_spawn", - "mp_dm_spawn", - "mp_dom_spawn" - }; -} - -// BSPLinkingConstants: -// These values are BSP linking constants that are required for the link to be successful -namespace BSPLinkingConstants -{ - constexpr const char* MISSING_IMAGE_NAME = "missing_image"; - constexpr const char* COLOR_ONLY_IMAGE_NAME = "color_only_image"; - - constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ - "attackers": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "defenders": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ], - "FFA": [ - { - "origin": "0 0 0", - "angles": "0 0 0" - } - ] - })"; - - - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ - "entities": [ - { - "classname": "worldspawn" - }, - { - "angles": "0 0 0", - "classname": "info_player_start", - "origin": "0 0 0" - }, - { - "angles": "0 0 0", - "classname": "mp_global_intermission", - "origin": "0 0 0" - } - ] - })"; -} - -// BSPEditableConstants: -// These values are BSP constants that can be edited and won't break the linker/game if changed -namespace BSPEditableConstants -{ - // Default xmodel values - // Unused as there is no support for xmodels right now - constexpr float DEFAULT_SMODEL_CULL_DIST = 10000.0f; - constexpr int DEFAULT_SMODEL_FLAGS = STATIC_MODEL_FLAG_NO_SHADOW; - constexpr int DEFAULT_SMODEL_LIGHT = 1; - constexpr int DEFAULT_SMODEL_REFLECTION_PROBE = 0; - - // Default surface values - constexpr int DEFAULT_SURFACE_LIGHT = BSPGameConstants::SUN_LIGHT_INDEX; - constexpr int DEFAULT_SURFACE_LIGHTMAP = 0; - constexpr int DEFAULT_SURFACE_REFLECTION_PROBE = 0; - constexpr int DEFAULT_SURFACE_FLAGS = (GFX_SURFACE_CASTS_SUN_SHADOW | GFX_SURFACE_CASTS_SHADOW); - - // material flags determine the features of a surface - // unsure which flag type changes what right now - // -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast - // 1 results in: normal surface features, grenades work, seems normal - constexpr int MATERIAL_SURFACE_FLAGS = 1; - constexpr int MATERIAL_CONTENT_FLAGS = 1; - - // terrain/world flags: does not change the type of terrain or what features they have - // from testing, as long at it isn't 0 things will work correctly - constexpr int LEAF_TERRAIN_CONTENTS = 1; - constexpr int WORLD_TERRAIN_CONTENTS = 1; - - // lightgrid (global) lighting colour - // since lightgrids are not well understood, this colour is used for the R, G and B values right now - constexpr unsigned char LIGHTGRID_COLOUR = 128; -}; \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp deleted file mode 100644 index 64aad2b2..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "BSPCreator.h" - -#include "fbx/ufbx.h" -#include "Game/T6/T6.h" -using namespace T6; - -bool addFBXMeshToWorld(ufbx_node* node, - std::vector& surfaceVec, - std::vector& vertexVec, - std::vector& indexVec, - bool& hasTangentSpace) -{ - if (node->attrib_type != UFBX_ELEMENT_MESH) - { - printf("ignoring non-mesh node \"%s\"\n", node->name.data); - return false; - } - ufbx_mesh* mesh = node->mesh; - - if (mesh->instances.count != 1) - printf("mesh %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); - - if (mesh->num_triangles == 0) - { - printf("ignoring mesh %s: triangle count is 0.\n", node->name.data); - return false; - } - - if (mesh->num_indices % 3 != 0) - { - printf("ignoring mesh %s: it is not triangulated.\n", node->name.data); - return false; - } - - for (size_t k = 0; k < mesh->num_indices; k++) - { - if (mesh->vertex_indices[k] > UINT16_MAX) - { - printf("ignoring mesh %s, it has more than %i indices.\n", node->name.data, UINT16_MAX); - return false; - } - } - - if (mesh->vertex_tangent.exists == false) - hasTangentSpace = false; - - // Fix the target_unit_meters opt not working - // UFBX stores the transform data in units that are 100x larger than what blender uses, so this converts them back - ufbx_transform origTransform = node->local_transform; - origTransform.translation.x /= 100.0f; - origTransform.translation.y /= 100.0f; - origTransform.translation.z /= 100.0f; - origTransform.scale.x /= 100.0f; - origTransform.scale.y /= 100.0f; - origTransform.scale.z /= 100.0f; - ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - - // FBX loading code modified from https://ufbx.github.io/elements/meshes/ - // Seems like I have to use this exact way of loading, otherwise some values become incorrect - for (size_t i = 0; i < mesh->material_parts.count; i++) - { - ufbx_mesh_part* meshPart = &mesh->material_parts.data[i]; - - if (meshPart->num_faces == 0) - continue; - - CustomMapSurface surface; - surface.triCount = meshPart->num_triangles; - surface.indexOfFirstVertex = vertexVec.size(); - surface.indexOfFirstIndex = indexVec.size(); - - CustomMapMaterialType meshMaterialType; - if (mesh->materials.count == 0) - { - meshMaterialType = MATERIAL_TYPE_EMPTY; - } - //else if (mesh->materials.data[i]->textures.count != 0) - //{ - // meshMaterialType = CM_MATERIAL_TEXTURE; - // surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - //} - //else - //{ - // meshMaterialType = CM_MATERIAL_COLOUR; - // surface.material.materialName = ""; - //} - else - { - meshMaterialType = MATERIAL_TYPE_TEXTURE; - surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - } - surface.material.materialType = meshMaterialType; - - size_t num_triangles = meshPart->num_triangles; - CustomMapVertex* vertices = (CustomMapVertex*)calloc(num_triangles * 3, sizeof(CustomMapVertex)); - size_t num_vertices = 0; - - // Reserve space for the maximum triangle indices. - size_t num_tri_indices = mesh->max_face_triangles * 3; - uint32_t* tri_indices = (uint32_t*)calloc(num_tri_indices, sizeof(uint32_t)); - - _ASSERT(meshPart->num_triangles == meshPart->num_faces); - for (size_t face_ix = 0; face_ix < meshPart->num_faces; face_ix++) - { - ufbx_face face = mesh->faces.data[meshPart->face_indices.data[face_ix]]; - - // Triangulate the face into `tri_indices[]`. - uint32_t num_tris = ufbx_triangulate_face(tri_indices, num_tri_indices, mesh, face); - - // Iterate over each triangle corner contiguously. - for (size_t q = 0; q < num_tris * 3; q++) - { - uint32_t index = tri_indices[q]; - - CustomMapVertex* vertex = &vertices[num_vertices++]; - - //ufbx_vec3 pos = ufbx_get_vertex_vec3(&mesh->vertex_position, index); - //vertex->pos.x = static_cast(pos.x); - //vertex->pos.y = static_cast(pos.y); - //vertex->pos.z = static_cast(pos.z); - // Fix the target_unit_meters opt not working - ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex->pos.x = static_cast(transformedPos.x); - vertex->pos.y = static_cast(transformedPos.y); - vertex->pos.z = static_cast(transformedPos.z); - - - switch (meshMaterialType) - { - case MATERIAL_TYPE_TEXTURE: - case MATERIAL_TYPE_EMPTY: - vertex->color.x = 1.0f; - vertex->color.y = 1.0f; - vertex->color.z = 1.0f; - vertex->color.w = 1.0f; - break; - case MATERIAL_TYPE_COLOUR: - { - float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); - ufbx_vec4 diffuse = mesh->materials.data[i]->fbx.diffuse_color.value_vec4; - vertex->color.x = static_cast(diffuse.x * factor); - vertex->color.y = static_cast(diffuse.y * factor); - vertex->color.z = static_cast(diffuse.z * factor); - vertex->color.w = static_cast(diffuse.w * factor); - break; - } - - default: - _ASSERT(false); - } - - - - // 1.0f - uv.y reason: - // https://gamedev.stackexchange.com/questions/92886/fbx-uv-coordinates-is-strange - ufbx_vec2 uv = ufbx_get_vertex_vec2(&mesh->vertex_uv, index); - vertex->texCoord.x = (float)(uv.x); - vertex->texCoord.y = (float)(1.0f - uv.y); - - ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index); - vertex->normal.x = static_cast(normal.x); - vertex->normal.y = static_cast(normal.y); - vertex->normal.z = static_cast(normal.z); - - if (mesh->vertex_tangent.exists) - { - ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index); - vertex->tangent.x = static_cast(tangent.x); - vertex->tangent.y = static_cast(tangent.y); - vertex->tangent.z = static_cast(tangent.z); - } - else - { - vertex->tangent.x = 0.0f; - vertex->tangent.y = 0.0f; - vertex->tangent.z = 0.0f; - } - } - } - - _ASSERT(num_vertices == num_triangles * 3); - - // Generate the index buffer. - ufbx_vertex_stream streams[1] = { - {vertices, num_vertices, sizeof(CustomMapVertex)}, - }; - size_t num_indices = num_triangles * 3; - uint32_t* indices = (uint32_t*)calloc(num_indices, sizeof(uint32_t)); - - // This call will deduplicate vertices, modifying the arrays passed in `streams[]`, - // indices are written in `indices[]` and the number of unique vertices is returned. - num_vertices = ufbx_generate_indices(streams, 1, indices, num_indices, NULL, NULL); - _ASSERT(num_vertices != 0); - - vertexVec.insert(vertexVec.end(), &vertices[0], &vertices[num_vertices]); - - size_t currIndexVecSize = indexVec.size(); - indexVec.resize(indexVec.size() + num_indices); - for (size_t m = 0; m < num_indices; m++) - { - indexVec[currIndexVecSize + m] = (uint16_t)indices[m]; - } - - surfaceVec.push_back(surface); - } - - return true; -} - -void loadWorldData(ufbx_scene* scene, CustomMapBSP* bsp, bool isGfxData) -{ - bool hasTangentSpace = true; - - for (size_t i = 0; i < scene->nodes.count; i++) - { - ufbx_node* node = scene->nodes.data[i]; - - if (node->attrib_type == UFBX_ELEMENT_MESH) - { - if (isGfxData) - addFBXMeshToWorld(node, bsp->gfxWorld.surfaces, bsp->gfxWorld.vertices, bsp->gfxWorld.indices, hasTangentSpace); - else - addFBXMeshToWorld(node, bsp->colWorld.surfaces, bsp->colWorld.vertices, bsp->colWorld.indices, hasTangentSpace); - } - else - { - //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); - } - } - - if (hasTangentSpace == false) - printf("warning: one or more meshes have no tangent space. Be sure to select the tangent space box when exporting the FBX from blender.\n"); -} - -CustomMapBSP* BSPCreator::createCustomMapBSP(std::string& mapName, ISearchPath& searchPath) -{ - ufbx_scene* gfxScene; - ufbx_scene* colScene; - - std::string gfxFbxPath = "custom_map/map_gfx.fbx"; - auto gfxFile = searchPath.Open(gfxFbxPath); - if (!gfxFile.IsOpen()) - { - printf("Failed to open map gfx fbx file: %s\n", gfxFbxPath.c_str()); - return NULL; - } - - char* gfxMapData = new char[static_cast(gfxFile.m_length)]; - gfxFile.m_stream->seekg(0); - gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); - - ufbx_error error; - ufbx_load_opts opts; // IDK why but opts don't seem to be working correctly, target_unit_meters isn't being used - memset(&opts, 0, sizeof(ufbx_load_opts)); - opts.target_axes = ufbx_axes_right_handed_y_up; - opts.generate_missing_normals = true; - opts.allow_missing_vertex_position = false; - opts.target_unit_meters = 100.0f; - //gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), &opts, &error); - gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), NULL, &error); - if (!gfxScene) - { - fprintf(stderr, "Failed to load map gfx fbx file: %s\n", error.description.data); - return NULL; - } - - std::string colFbxPath = "custom_map/map_col.fbx"; - auto colFile = searchPath.Open(colFbxPath); - if (!colFile.IsOpen()) - { - printf("Failed to open map collison fbx file: %s. map gfx will be used for collision instead.\n", colFbxPath.c_str()); - colScene = gfxScene; - } - else - { - char* colMapData = new char[static_cast(colFile.m_length)]; - colFile.m_stream->seekg(0); - colFile.m_stream->read(colMapData, colFile.m_length); - - colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), &opts, &error); - if (!colScene) - { - fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); - return NULL; - } - } - - CustomMapBSP* projInfo = new CustomMapBSP; - - projInfo->name = mapName; - projInfo->bspName = "maps/mp/" + mapName + ".d3dbsp"; - - loadWorldData(gfxScene, projInfo, true); - loadWorldData(colScene, projInfo, false); - - ufbx_free_scene(gfxScene); - if (gfxScene != colScene) - ufbx_free_scene(colScene); - - return projInfo; -} diff --git a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h b/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h deleted file mode 100644 index 2ba85171..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/BSPCreator.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "Game/T6/Maps/CustomMaps.h" -#include "SearchPath/ISearchPath.h" - -class BSPCreator -{ -public: - static CustomMapBSP* createCustomMapBSP(std::string& mapName, ISearchPath& searchPath); -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h deleted file mode 100644 index c46af5f7..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ /dev/null @@ -1,1983 +0,0 @@ -#pragma once - -#include "BSPCalculation.h" -#include "BSPConstants.h" -#include "BSPUtil.h" - -#include "Game/T6/Material/JsonMaterialLoaderT6.h" -#include "Utils/Pack.h" - -#include -using namespace nlohmann; - -class CustomMapLinker -{ -public: - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_zone(zone), - m_context(context) - { - hasLinkFailed = false; - } - - bool linkCustomMap(CustomMapBSP* projInfo) - { - _ASSERT(projInfo != NULL); - - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - - createComWorld(projInfo); - createMapEnts(projInfo); - createGameWorldMp(projInfo); - createSkinnedVerts(projInfo); - createGfxWorld(projInfo); // requires mapents asset - createClipMap(projInfo); // must go last (requires gfx and mapents asset) - - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - return true; - } - -private: - struct entModelBounds - { - vec3_t mins; - vec3_t maxs; - }; - - MemoryManager& m_memory; - ISearchPath& m_search_path; - Zone& m_zone; - AssetCreationContext& m_context; - - bool hasLinkFailed; - std::vector entityModelList; - - json materialTemplateJson; - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - bool overwriteDrawData(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - int vertexCount = projInfo->gfxWorld.vertices.size(); - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); - GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { - CustomMapVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); - //GfxVertex->xyz = WorldVertex->pos; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); - //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); - //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - - // unknown use variables - // binormalSign may be bitangent of the vertex - // lmapCoord may be the lightmap coordinate of the vertex - GfxVertex->binormalSign = 0.0f; - GfxVertex->lmapCoord.packed = 0; - } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; - - // we don't use vd1 but still needs to be initialised - // the data type varies and 0x20 is enough for all types - gfxWorld->draw.vertexDataSize1 = 0x20; - gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - - int indexCount = projInfo->gfxWorld.indices.size(); - _ASSERT(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; - gfxWorld->draw.indices = new uint16_t[indexCount]; - for (int i = 0; i < indexCount; i += 3) - { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; - } - - return true; - } - - struct s_sortedSurf - { - int surfaceIndex; - int vertexCount; - }; - - bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) - { - return surf0.vertexCount > surf1.vertexCount; - } - - Material* loadImageIntoMaterial(std::string& imageName) - { - std::string imagePath = std::format("images/{}.iwi", imageName); - auto imageFile = m_search_path.Open(imagePath); - if (!imageFile.IsOpen()) - { - printf("WARN: failed to find image %s.\n", imageName.c_str()); - return NULL; - } - - Material* material = new Material; - material->info.name = m_memory.Dup(imageName.c_str()); - - // parse the template file and replace the image name - materialTemplateJson["textures"][1]["image"] = imageName; - - AssetRegistration registration(imageName, material); - if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) - { - printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); - return NULL; - } - - m_context.AddAsset(std::move(registration)); - - return material; - } - - void overwriteMapSurfaces(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); - if (!overwriteResult) - return; - - unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; - gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxWorld.surfaces[i]; - - currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; - - currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; - - std::string surfMaterialName; - switch (objSurface->material.materialType) - { - case MATERIAL_TYPE_TEXTURE: - surfMaterialName = objSurface->material.materialName; - break; - - case MATERIAL_TYPE_COLOUR: - case MATERIAL_TYPE_EMPTY: - surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - break; - - default: - _ASSERT(false); - } - Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); - if (surfMaterial == NULL) - { - std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); - surfMaterial = loadImageIntoMaterial(missingImageName); - if (surfMaterial == NULL) - { - printf("Error: unable to find the missing image texture!\n"); - hasLinkFailed = true; - return; - } - } - currSurface->material = surfMaterial; - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) - { - uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); - } - - // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; - } - - // doesn't seem to matter what order the sorted surfs go in - gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = i; - } - - gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); - - // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; - memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); - - gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; - } - - void overwriteMapSModels(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - /* - Models are unsupported right now - Code is left in in case it is supported later on - */ - //unsigned int modelCount = projInfo->modelCount; - //gfxWorld->dpvs.smodelCount = modelCount; - //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - // - //for (unsigned int i = 0; i < modelCount; i++) - //{ - // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - // customMapModel* inModel = &projInfo->models[i]; - // - // auto xModelAsset = m_context.LoadDependency(inModel->name); - // if (xModelAsset == NULL) - // { - // printf("XModel %s not found!\n", inModel->name.c_str()); - // currModel->model = NULL; - // } - // else - // currModel->model = (XModel*)xModelAsset->Asset(); - // - // currModel->placement.origin.x = inModel->origin.x; - // currModel->placement.origin.y = inModel->origin.y; - // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - // currModel->placement.scale = inModel->scale; - // - // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - // - // // mins and maxs are calculated in world space not local space - // // TODO: this does not account for model rotation or scale - // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - // - // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - // currModel->flags = DEFAULT_SMODEL_FLAGS; - // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - // - // // unknown use / unused - // currModel->smid = i; - // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - // currModel->invScaleSq = 0.0f; - // currModel->lightingHandle = 0; - // currModel->colorsIndex = 0; - // currModel->visibility = 0; - // - // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // // TODO: calculate lighting and store it here - // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - //} - - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - - // all visdata is alligned by 128 - int allignedModelCount = BSPUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; - gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; - memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); - for (unsigned int i = 0; i < modelCount; i++) - { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) - gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; - } - - // always set to 0 - gfxWorld->dpvs.usageCount = 0; - } - - void cleanGfxWorld(GfxWorld* gfxWorld) - { - // checksum is generated by the game - gfxWorld->checksum = 0; - - // Remove Coronas - gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; - - // Remove exposure volumes - gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; - gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; - - // Remove hero lights - gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; - gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; - - // remove LUT data - gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; - gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; - - // remove occluders - gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; - - // remove Siege Skins - gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; - - // remove outdoor bounds - gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; - - // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; - - // remove shadow maps - gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; - gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; - - // remove stream info - gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; - gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; - - // remove sun data - memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); - gfxWorld->sun.hasValidData = false; - - // Remove Water - gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; - - // Remove Fog - gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; - gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; - gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; - gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; - - // materialMemory is unused - gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; - - // sunLight is overwritten by the game, just needs to be a valid pointer - gfxWorld->sunLight = new GfxLight; - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); - } - - void overwriteGfxLights(GfxWorld* gfxWorld) - { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - - gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; - memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); - } - - gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; - } - - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; - if (lightEntShadowVisSize != 0) - { - gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); - } - else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } - } - - // the lightgrid is used to light models in a dynamic way and is precomputed - void overwriteLightGrid(GfxWorld* gfxWorld) - { - // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. - // it works and that is all thats needed :) - - // mins and maxs define the range that the lightgrid will work in - // idk how these values are calculated, but the below values are larger - // than official map values - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 200; - gfxWorld->lightGrid.maxs[1] = 200; - gfxWorld->lightGrid.maxs[2] = 50; - - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - gfxWorld->lightGrid.offset = 0.0f; // default value - - // this will make the lookup into rawRowData always return the first row - int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; - gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; - memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - - gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); - GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); - row->colStart = 0; - row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game - row->zStart = 0; - row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks - row->firstEntry = 0; - for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 - { - row->lookupTable[i] = 0; - } - gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; - - // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow - gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data - GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; - for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) - { - entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - entryArray[i].visibility = 0; - } - gfxWorld->lightGrid.entries = entryArray; - - // colours are looked up by an entries colourindex - gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index - gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; - memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); - - // we use the colours array instead of coeffs array - gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; - } - - void updateGfxCells(GfxWorld* gfxWorld) - { - // Cells are basically data used to determine what can be seen and what cant be seen - // Right now custom maps have no optimisation so there is only 1 cell - int cellCount = 1; - - gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; - - int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); - gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); - - gfxWorld->cells = new GfxCell[cellCount]; - gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; - - // there is only 1 reflection probe - gfxWorld->cells[0].reflectionProbeCount = 1; - char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; - reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; - - // AABB trees are used to detect what should be rendered and what shouldn't - // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn - gfxWorld->cells[0].aabbTreeCount = 1; - gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; - gfxWorld->cells[0].aabbTree[0].childCount = 0; - gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; - gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) - { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; - } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node - // Nodes mnode_t.cellIndex indexes gfxWorld->cells - // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes - gfxWorld->nodeCount = 1; - gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; - gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 - - // planes are overwritten by the clipmap loading code ingame - gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.planes = NULL; - } - - void updateWorldBounds(GfxWorld* gfxWorld) - { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - - for (int i = 0; i < gfxWorld->surfaceCount; i++) - { - BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); - } - } - - void overwriteModels(GfxWorld* gfxWorld) - { - // these models are the collision for the entities defined in the mapents asset - // used for triggers and stuff - - gfxWorld->modelCount = entityModelList.size() + 1; - gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - - // first model is always the world model - gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - - 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)); - } - } - - void updateSunData(GfxWorld* gfxWorld) - { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = (float)0.001; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; - - gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; - gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; - gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; - gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; - gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; - gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; - gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; - gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; - gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; - gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; - gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; - gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; - gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; - gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; - gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; - gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; - } - - void updateReflectionProbeData(GfxWorld* gfxWorld) - { - gfxWorld->draw.reflectionProbeCount = 1; - - gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; - - // default values taken from mp_dig - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; - - gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - - std::string probeImageName = "*reflection_probe0"; - auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) - { - printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); - } - - void overwriteLightmapData(GfxWorld* gfxWorld) - { - gfxWorld->draw.lightmapCount = 1; - - gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - - std::string secondaryTexture = "*lightmap0_secondary"; - auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) - { - printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL - gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); - } - - void overwriteSkyBox(CustomMapBSP* projInfo, GfxWorld* gfxWorld) - { - std::string skyBoxName = "skybox_" + projInfo->name; - gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); - - if (m_context.LoadDependency(skyBoxName) == NULL) - { - printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); - } - - // default skybox values from mp_dig - gfxWorld->skyDynIntensity.angle0 = 0.0f; - gfxWorld->skyDynIntensity.angle1 = 0.0f; - gfxWorld->skyDynIntensity.factor0 = 1.0f; - gfxWorld->skyDynIntensity.factor1 = 1.0f; - } - - void updateDynEntData(GfxWorld* gfxWorld) - { - int dynEntCount = 0; - gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. - // Making the value much larger than required fixes it, but idk what the root cause is - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; - gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; - gfxWorld->dpvsDyn.usageCount = 0; - - int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; - gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); - - int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; - gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); - gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); - - gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); - } - - void updateOutdoors(GfxWorld* gfxWorld) - { - float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); - float xScale = -(xRecip * gfxWorld->mins.x); - - float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); - float yScale = -(yRecip * gfxWorld->mins.y); - - float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); - float zScale = -(zRecip * gfxWorld->mins.z); - - memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); - - gfxWorld->outdoorLookupMatrix[0].x = xRecip; - gfxWorld->outdoorLookupMatrix[1].y = yRecip; - gfxWorld->outdoorLookupMatrix[2].z = zRecip; - gfxWorld->outdoorLookupMatrix[3].x = xScale; - gfxWorld->outdoorLookupMatrix[3].y = yScale; - gfxWorld->outdoorLookupMatrix[3].z = zScale; - gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - - std::string outdoorImageName = std::string("$outdoor"); - auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) - { - printf("ERROR! unable to find image $outdoor!\n"); - hasLinkFailed = true; - return; - } - gfxWorld->outdoorImage = outdoorImageAsset->Asset(); - } - - void createGfxWorld(CustomMapBSP* projInfo) - { - GfxWorld* gfxWorld = new GfxWorld; - gfxWorld->baseName = _strdup(projInfo->name.c_str()); - gfxWorld->name = _strdup(projInfo->bspName.c_str()); - - // Default values taken from mp_dig - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; - - cleanGfxWorld(gfxWorld); - - overwriteMapSurfaces(projInfo, gfxWorld); - - overwriteMapSModels(projInfo, gfxWorld); - - overwriteLightmapData(gfxWorld); - - overwriteSkyBox(projInfo, gfxWorld); - - updateReflectionProbeData(gfxWorld); - - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); - - updateOutdoors(gfxWorld); - - // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); - - overwriteLightGrid(gfxWorld); - - overwriteGfxLights(gfxWorld); - - overwriteModels(gfxWorld); - - updateSunData(gfxWorld); - - updateDynEntData(gfxWorld); - - m_context.AddAsset(gfxWorld->name, gfxWorld); - } - - void addXModelsToCollision(CustomMapBSP* projInfo, clipMap_t* clipMap) - { - auto gfxWorldAsset = m_context.LoadDependency(projInfo->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; - } - - bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) - { - - unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); - - return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; - } - - void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* 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) - { - new cplane_s; - new cNode_t; - new cLeaf_s; - new CollisionAabbTree; - - 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->u.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 populateBSPTree(clipMap_t* clipMap, BSPTree* tree) - { - int numPlanes = 0; - int numNodes = 0; - int numLeafs = 0; - int numAABBTrees = 0; - int maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; - clipMap->numNodes = numNodes; - clipMap->nodes = new cNode_t[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 = new CollisionAabbTree[clipMap->aabbTreeCount]; - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = new cLeaf_s[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); - } - - void createPartitions(CustomMapBSP* projInfo, clipMap_t* clipMap) - { - int collisionVertexCount = projInfo->colWorld.vertices.size(); - std::vector collisionVertVec; - for (int i = 0; i < collisionVertexCount; i++) - { - collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); - //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = new vec3_t[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); - hasLinkFailed = true; - return; - } - - std::vector triIndexVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - CustomMapSurface* currSurface = &projInfo->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 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = projInfo->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); - clipMap->triCount = triIndexVec.size() / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[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 < projInfo->colWorld.surfaces.size(); i++) - { - int triCount = projInfo->colWorld.surfaces[i].triCount; - int firstTriIndex = projInfo->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 = new CollisionPartition[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 = new uint16_t[totalUindCount]; - memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - } - - void createClipMap(CustomMapBSP* projInfo) - { - clipMap_t* clipMap = new clipMap_t; - - clipMap->name = _strdup(projInfo->bspName.c_str()); - - std::string mapEntsName = projInfo->bspName.c_str(); - auto mapEntsAsset = m_context.LoadDependency(mapEntsName); - _ASSERT(mapEntsAsset != NULL); - clipMap->mapEnts = mapEntsAsset->Asset(); - - clipMap->pInfo = NULL; - - 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 = NULL; - - // 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 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; - *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; - *((unsigned int*)&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_brush = new cbrush_t; - 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 = NULL; - clipMap->box_brush->verts = NULL; - - clipMap->numClusters = 1; - clipMap->vised = 0; - clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; - clipMap->visibility = new char[clipMap->clusterBytes]; - memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); - - clipMap->isInUse = true; - clipMap->checksum = 0; - - clipMap->num_constraints = 0; - clipMap->constraints = NULL; - clipMap->max_ropes = 32; - clipMap->ropes = new rope_t[clipMap->max_ropes]; - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); - - 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; - - 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; - - // assume that there are 0 dyn ents from here on - clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; - clipMap->dynEntClientList[1] = NULL; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); - - clipMap->dynEntServerList[0] = NULL; - clipMap->dynEntServerList[1] = NULL; - - clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; - clipMap->dynEntCollList[1] = NULL; - clipMap->dynEntCollList[2] = NULL; - clipMap->dynEntCollList[3] = NULL; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); - - clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; - clipMap->dynEntPoseList[1] = NULL; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); - - clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; - clipMap->dynEntDefList[1] = NULL; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - - // cmodels is the collision for mapents - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - clipMap->numSubModels = gfxWorld->modelCount; - clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - for (unsigned int i = 0; i < clipMap->numSubModels; i++) - { - // bomb triggers use leafs, not world terrain so that might be an issue - - GfxBrushModel* gfxModel = &gfxWorld->models[i]; - cmodel_t* cmModel = &clipMap->cmodels[i]; - - cmModel->leaf.firstCollAabbIndex = 0; - cmModel->leaf.collAabbCount = 0; - cmModel->leaf.brushContents = 0; - cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; - cmModel->leaf.mins.x = 0.0f; - cmModel->leaf.mins.y = 0.0f; - cmModel->leaf.mins.z = 0.0f; - cmModel->leaf.maxs.x = 0.0f; - cmModel->leaf.maxs.y = 0.0f; - cmModel->leaf.maxs.z = 0.0f; - cmModel->leaf.leafBrushNode = 0; - cmModel->leaf.cluster = 0; - cmModel->info = NULL; - cmModel->mins.x = gfxModel->bounds[0].x; - cmModel->mins.y = gfxModel->bounds[0].y; - cmModel->mins.z = gfxModel->bounds[0].z; - cmModel->maxs.x = gfxModel->bounds[1].x; - cmModel->maxs.y = gfxModel->bounds[1].y; - cmModel->maxs.z = gfxModel->bounds[1].z; - cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; - } - - addXModelsToCollision(projInfo, clipMap); - - clipMap->info.numMaterials = 1; - clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(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 (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls, but from testing doesnt seem to do much - int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - - // 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 - - createPartitions(projInfo, clipMap); - - 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); - - m_context.AddAsset(clipMap->name, clipMap); - } - - void createComWorld(CustomMapBSP* projInfo) - { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset - ComWorld* comWorld = new ComWorld; - comWorld->name = _strdup(projInfo->bspName.c_str()); - comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; - comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; - - // default light is always empty - ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; - memset(defaultLight, 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; - - m_context.AddAsset(comWorld->name, comWorld); - } - - void 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) - { - printf("ERROR: first entity in the map entity string must be the worldspawn class!"); - hasLinkFailed = true; - return; - } - } - - 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"); - } - } - - 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"); - } - } - } - - void parseBombJSON(json& bombJs, std::string& entityString) - { - // add the bomb model - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - entityString.append("{\n"); - entityString.append("\"classname\" \"script_model\"\n"); - entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); - entityString.append("\"targetname\" \"sd_bomb\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append("\"spawnflags\" \"4\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append("}\n"); - } - if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) - { - hasLinkFailed = true; - printf("ERROR: unable to find s&d bomb xmodel\n"); - return; - } - - // add the bomb pickup trigger - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); - entModelBounds bounds; - bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig - bounds.mins.y = bomboriginV3.y - 32.0f; - bounds.mins.z = bomboriginV3.z - 8.0f; - bounds.maxs.x = bomboriginV3.x + 32.0f; - bounds.maxs.y = bomboriginV3.y + 32.0f; - bounds.maxs.z = bomboriginV3.z + 28.0f; - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_multiple\"\n"); - entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add A site bomb - { - std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; - std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; - vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); - vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteAPoint1V3.x; - bounds.mins.y = siteAPoint1V3.y; - bounds.mins.z = siteAPoint1V3.z; - bounds.maxs.x = siteAPoint1V3.x; - bounds.maxs.y = siteAPoint1V3.y; - bounds.maxs.z = siteAPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_a\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add B site bomb - { - std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; - std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; - vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); - vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteBPoint1V3.x; - bounds.mins.y = siteBPoint1V3.y; - bounds.mins.z = siteBPoint1V3.z; - bounds.maxs.x = siteBPoint1V3.x; - bounds.maxs.y = siteBPoint1V3.y; - bounds.maxs.z = siteBPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_b\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - } - - void createMapEnts(CustomMapBSP* projInfo) - { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - std::string entityString; - - const auto entFile = m_search_path.Open("custom_map/entities.json"); - json entJs; - if (!entFile.IsOpen()) - { - printf("WARN: can't find entity json custom_map/entities.json, using default entities\n"); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); - } - else - { - entJs = json::parse(*entFile.m_stream); - } - parseMapEntsJSON(entJs["entities"], entityString); - - const auto spawnFile = m_search_path.Open("custom_map/spawns.json"); - json spawnJs; - if (!spawnFile.IsOpen()) - { - printf("WARN: cant find custom_map/spawns.json, setting spawns to 0 0 0\n"); - 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); - - //const auto objectiveFile = m_search_path.Open("objectives.json"); - //if (!spawnFile.IsOpen()) - //{ - // printf("WARN: no objectives given\n"); - //} - //else - //{ - // json objectiveJs = json::parse(*objectiveFile.m_stream); - // parseBombJSON(objectiveJs, entityString); - //} - - mapEnts->entityString = _strdup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - m_context.AddAsset(mapEnts->name, mapEnts); - } - - void createGameWorldMp(CustomMapBSP* projInfo) - { - GameWorldMp* gameWorldMp = new GameWorldMp; - - gameWorldMp->name = _strdup(projInfo->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 = new pathnode_t[nodeCount]; - gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - - gameWorldMp->path.pathVis = NULL; - gameWorldMp->path.smoothCache = NULL; - gameWorldMp->path.nodeTree = NULL; - - m_context.AddAsset(gameWorldMp->name, gameWorldMp); - } - - void createSkinnedVerts(CustomMapBSP* projInfo) - { - SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; - skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); - // 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 - - m_context.AddAsset("skinnedverts", skinnedVerts); - } - - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) - { - if (assetName.length() == 0) - return NULL; - - FootstepTableDef* footstepTable = new FootstepTableDef; - footstepTable->name = _strdup(assetName.c_str()); - memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); - - m_context.AddAsset(assetName, footstepTable); - - return footstepTable; - } - - void checkAndAddDefaultRequiredAssets(CustomMapBSP* projectInfo) - { - auto templateFile = m_search_path.Open("materials/material_template.json"); - if (!templateFile.IsOpen()) - { - printf("ERROR: failed to open materials/material_template.json\n"); - hasLinkFailed = true; - return; - } - materialTemplateJson = json::parse(*templateFile.m_stream); - - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) - { - hasLinkFailed = true; - return; - } - - 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") == NULL) - { - hasLinkFailed = true; - return; - } - } -}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h deleted file mode 100644 index b18a97c6..00000000 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapOptions.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -// These options can be edited - -// material flags determine the features of the surface -// unsure which flag type changes what right now -// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast -// 1 results in: normal surface features, grenades work, seems normal -#define MATERIAL_SURFACE_FLAGS 1 -#define MATERIAL_CONTENT_FLAGS 1 - -// terrain/world flags: does not change the type of terrain or what features they have -// from testing, as long at it isn't 0 things will work correctly -#define LEAF_TERRAIN_CONTENTS 1 -#define WORLD_TERRAIN_CONTENTS 1 - -#define LIGHTGRID_COLOUR 128 // global lighting colour diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 6abdaaab..cc391d98 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -42,7 +42,7 @@ #include "Weapon/WeaponRawLoaderT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" -#include "CustomMap/LoaderCustomMapT6.h" +#include "BSP/LoaderBSP_T6.h" #include "TechniqueSet/LoaderTechniqueSetT6.h" #include @@ -442,7 +442,7 @@ namespace T6 collection.AddAssetCreator(z_barrier::CreateRawLoaderT6(memory, searchPath, zone)); collection.AddAssetCreator(z_barrier::CreateGdtLoaderT6(memory, searchPath, gdt, zone)); - collection.AddAssetCreator(custom_map::CreateLoaderT6(memory, searchPath, zone)); + collection.AddAssetCreator(BSP::CreateLoaderT6(memory, searchPath, zone)); } } // namespace