2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-26 22:42:06 +00:00
- Updated file structure to use BSP naming scheme
- Re-wrote BSP creator to use c++ more efficiently
This commit is contained in:
LJW-Dev
2025-10-22 16:06:59 +08:00
parent b4073d74d9
commit e53779517d
21 changed files with 2693 additions and 2741 deletions

View File

@@ -0,0 +1,185 @@
#pragma once
#include <vector>
#include <string>
#include <memory>
#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<BSPSurface> surfaces;
std::vector<BSPVertex> vertices;
std::vector<uint16_t> 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;
};
}

View File

@@ -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<BSPObject> 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<BSPTree> frontTree, std::unique_ptr<BSPTree> 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<BSPTree> front;
std::unique_ptr<BSPTree> 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<BSPTree>(halfLength, min.y, min.z, max.x, max.y, max.z, level + 1);
back = std::make_unique<BSPTree>(min.x, min.y, min.z, halfLength, max.y, max.z, level + 1);
isLeaf = false;
node = std::make_unique<BSPNode>(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<BSPTree>(min.x, min.y, halfLength, max.x, max.y, max.z, level + 1);
back = std::make_unique<BSPTree>(min.x, min.y, min.z, max.x, max.y, halfLength, level + 1);
isLeaf = false;
node = std::make_unique<BSPNode>(std::move(front), std::move(back), AXIS_Z, halfLength);
leaf = nullptr;
}
else
{
isLeaf = true;
node = nullptr;
leaf = std::make_unique<BSPLeaf>();
}
}
void BSPTree::addObjectToTree(std::shared_ptr<BSPObject> 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);
}
}
}
}

View File

@@ -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<std::shared_ptr<BSPObject>> objectList;
void addObject(std::shared_ptr<BSPObject> object);
BSPObject* getObject(int index);
size_t getObjectCount();
};
class BSPTree;
class BSPNode
{
public:
std::unique_ptr<BSPTree> front;
std::unique_ptr<BSPTree> 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<BSPTree> frontTree, std::unique_ptr<BSPTree> backTree, PlaneAxis nodeAxis, float nodeDistance);
PlaneSide objectIsInFront(BSPObject* object);
};
class BSPTree
{
public:
bool isLeaf;
std::unique_ptr<BSPLeaf> leaf;
std::unique_ptr<BSPNode> 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<BSPObject> object);
};
}

View File

@@ -0,0 +1,269 @@
#include "BSPCreator.h"
#include "fbx/ufbx.h"
namespace
{
using namespace BSP;
void addFBXMeshToWorld(ufbx_node* node,
std::vector<BSPSurface>& surfaceVec,
std::vector<BSPVertex>& vertexVec,
std::vector<uint16_t>& 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<int>(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<BSPVertex> vertices;
std::vector<uint32_t> 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<float>(transformedPos.x);
vertex->pos.y = static_cast<float>(transformedPos.y);
vertex->pos.z = static_cast<float>(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<float>(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<float>(diffuse.x * factor);
vertex->color.y = static_cast<float>(diffuse.y * factor);
vertex->color.z = static_cast<float>(diffuse.z * factor);
vertex->color.w = static_cast<float>(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<float>(uv.x);
vertex->texCoord.y = static_cast<float>(1.0f - uv.y);
ufbx_vec3 normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index);
vertex->normal.x = static_cast<float>(normal.x);
vertex->normal.y = static_cast<float>(normal.y);
vertex->normal.z = static_cast<float>(normal.z);
if (mesh->vertex_tangent.exists)
{
ufbx_vec3 tangent = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index);
vertex->tangent.x = static_cast<float>(tangent.x);
vertex->tangent.y = static_cast<float>(tangent.y);
vertex->tangent.z = static_cast<float>(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<uint32_t> 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<uint16_t>(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<int>(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<BSPData> 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<char> gfxMapData(new char[static_cast<size_t>(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<size_t>(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<char> colMapData(new char[static_cast<size_t>(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<size_t>(colFile.m_length), &optsCol, &errorCol);
if (!colScene)
{
con::error("Failed to load map collision fbx file: {}", errorCol.description.data);
return nullptr;
}
}
std::unique_ptr<BSPData> 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

View File

@@ -0,0 +1,9 @@
#pragma once
#include "BSP.h"
#include "SearchPath/ISearchPath.h"
namespace BSP
{
std::unique_ptr<BSPData> createBSPData(std::string& mapName, ISearchPath& searchPath);
};

View File

@@ -0,0 +1,161 @@
#pragma once
#include <cmath>
#include <format>
#include "BSPUtil.h"
// BO2 uses a different coordinate system, so this converts it back from OpenGLs default
vec3_t BSPUtil::convertToBO2Coords(vec3_t OGL_coordinate)
{
vec3_t result;
result.x = OGL_coordinate.x;
result.y = -OGL_coordinate.z;
result.z = OGL_coordinate.y;
return result;
}
// BO2 uses a weird coordinate system, so this converts it to OpenGLs default
vec3_t BSPUtil::convertFromBO2Coords(vec3_t bo2_coordinate)
{
vec3_t result;
result.x = bo2_coordinate.x;
result.y = bo2_coordinate.z;
result.z = -bo2_coordinate.y;
return result;
}
void BSPUtil::calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs)
{
if (currmins->x > newmins->x)
currmins->x = newmins->x;
if (newmaxs->x > currmaxs->x)
currmaxs->x = newmaxs->x;
if (currmins->y > newmins->y)
currmins->y = newmins->y;
if (newmaxs->y > currmaxs->y)
currmaxs->y = newmaxs->y;
if (currmins->z > newmins->z)
currmins->z = newmins->z;
if (newmaxs->z > currmaxs->z)
currmaxs->z = newmaxs->z;
}
void BSPUtil::calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs)
{
if (currmins->x > point->x)
currmins->x = point->x;
if (point->x > currmaxs->x)
currmaxs->x = point->x;
if (currmins->y > point->y)
currmins->y = point->y;
if (point->y > currmaxs->y)
currmaxs->y = point->y;
if (currmins->z > point->z)
currmins->z = point->z;
if (point->z > currmaxs->z)
currmaxs->z = point->z;
}
vec3_t BSPUtil::calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs)
{
// 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;
temp.x *= 0.5f;
temp.y *= 0.5f;
temp.z *= 0.5f;
return temp;
}
int BSPUtil::allignBy128(int size)
{
return ((size + 127) & 0xFFFFFF80);
}
float BSPUtil::distBetweenPoints(vec3_t p1, vec3_t p2)
{
float x = p2.x - p1.x;
float y = p2.y - p1.y;
float z = p2.z - p1.z;
return sqrtf((x * x) + (y * y) + (z * z));
}
// angles are in euler degrees
void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis)
{
float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f
float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f
float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f
float cosX = cos(xRadians);
float sinX = sin(xRadians);
float cosY = cos(yRadians);
float sinY = sin(yRadians);
float cosZ = cos(zRadians);
float sinZ = sin(zRadians);
axis[0].x = cosX * cosY;
axis[0].y = cosX * sinY;
axis[0].z = -sinX;
axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY);
axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY);
axis[1].z = sinZ * cosX;
axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY);
axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY);
axis[2].z = cosZ * cosX;
}
void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out)
{
out[0].x = in[0].x;
out[0].y = in[1].x;
out[0].z = in[2].x;
out[1].x = in[0].y;
out[1].y = in[1].y;
out[1].z = in[2].y;
out[2].x = in[0].z;
out[2].y = in[1].z;
out[2].z = in[2].z;
}
vec3_t BSPUtil::convertStringToVec3(std::string str)
{
std::string v1Str = str;
int nextValIndex = 0;
while (v1Str[nextValIndex] != ' ')
nextValIndex++;
nextValIndex++; // skip past space
std::string v2Str = &v1Str[nextValIndex];
nextValIndex = 0;
while (v2Str[nextValIndex] != ' ')
nextValIndex++;
nextValIndex++; // skip past space
std::string v3Str = &v2Str[nextValIndex];
vec3_t result;
result.x = static_cast<float>(atof(v1Str.c_str()));
result.y = static_cast<float>(atof(v2Str.c_str()));
result.z = static_cast<float>(atof(v3Str.c_str()));
return result;
}
std::string BSPUtil::convertVec3ToString(vec3_t vec)
{
std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z);
return result;
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "BSP.h"
class BSPUtil
{
public:
static vec3_t convertToBO2Coords(vec3_t OGL_coordinate);
static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate);
static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs);
static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs);
static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs);
static int allignBy128(int size);
static float distBetweenPoints(vec3_t p1, vec3_t p2);
static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis);
static void matrixTranspose3x3(const vec3_t* in, vec3_t* out);
static vec3_t convertStringToVec3(std::string str);
static std::string convertVec3ToString(vec3_t vec);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
#include "LoaderBSP_T6.h"
#include "BSPCreator.h"
#include "CustomMapLinker.h"
namespace
{
using namespace BSP;
class BSPLoader final : public AssetCreator<AssetGfxWorld>
{
public:
BSPLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
: m_memory(memory),
m_search_path(searchPath),
m_zone(zone)
{
}
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
{
// custom maps must have a map_gfx file
auto mapGfxFile = m_search_path.Open("BSP/map_gfx.fbx");
if (!mapGfxFile.IsOpen())
return AssetCreationResult::NoAction();
BSPData* BSP = BSP::createBSPData(m_zone.m_name, m_search_path);
if (BSP == nullptr)
return AssetCreationResult::Failure();
CustomMapLinker linker(m_memory, m_search_path, m_zone, context);
bool result = linker.linkCustomMap(BSP);
if (result)
{
auto gfxWorldAsset = context.LoadDependency<AssetGfxWorld>(BSP->bspName);
_ASSERT(gfxWorldAsset != nullptr);
return AssetCreationResult::Success(gfxWorldAsset);
}
else
return AssetCreationResult::Failure();
}
private:
MemoryManager& m_memory;
ISearchPath& m_search_path;
Zone& m_zone;
};
} // namespace
namespace BSP
{
std::unique_ptr<AssetCreator<AssetGfxWorld>> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
{
return std::make_unique<BSPLoader>(memory, searchPath, zone);
}
} // namespace BSP

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Asset/IAssetCreator.h"
#include "Game/T6/T6.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include <memory>
namespace BSP
{
std::unique_ptr<AssetCreator<T6::AssetGfxWorld>> CreateLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone);
} // namespace BSP

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff