2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-23 05:12:05 +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,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