diff --git a/README.md b/README.md index d45d1d9..c76b35e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # hlbsp-converter -A tool to convert bsp maps (Half-Life and other GoldSrc games) into gltf scenes. +A tool to convert bsp maps (Half-Life and other GoldSrc games) into glTF scenes. ## Key features: * Export embedded texture * Lightmaps! * Option to exclude sky polygons +* BSP31 support (Xash3D) ## Usage @@ -18,7 +19,8 @@ A tool to convert bsp maps (Half-Life and other GoldSrc games) into gltf scenes. * `-lm ` - set a lightmap atlas size * `-skip_sky` - exclude polygons with 'sky' texture from export -* `-lstyle |all` - export lightmap with a specified lightstyle index or all lightyles in one +* `-lstyle |all` - export lightmap with a specified lightstyle index or all lightyles in one. +* `-uint16` - sets index buffer type to usigned short. Useful for old mobile GPU without GL_OES_element_index_uint. Will split models into smaller meshes if required. ## Dependencies (already included) diff --git a/src/bsp-converter.cpp b/src/bsp-converter.cpp index 84f5e6c..fb202fb 100644 --- a/src/bsp-converter.cpp +++ b/src/bsp-converter.cpp @@ -5,10 +5,10 @@ int main(int argc, const char *argv[]) { - printf("bsp-converter by lewa_j v0.8 - 2022\n"); + printf("bsp-converter by lewa_j v0.9 - 2022\n"); if (argc < 2 || !strcmp(argv[1], "-h")) { - printf("Usage: bsp-converter map.bsp [-lm ] [-skip_sky] [-lstyles |all]\n"); + printf("Usage: bsp-converter map.bsp [-lm ] [-lstyles |all] [-skip_sky] [-uint16]\n"); return -1; } @@ -41,7 +41,7 @@ int main(int argc, const char *argv[]) printf("Warning: '-lm' parameter requires a number - lightmap atlas resolution\n"); } } - if (!strcmp(argv[i], "-lstyle")) + else if (!strcmp(argv[i], "-lstyle")) { if (argc > i + 1) { @@ -61,6 +61,11 @@ int main(int argc, const char *argv[]) config.skipSky = true; printf("Sky polygons will be excluded from export\n"); } + else if (!strcmp(argv[i], "-uint16")) + { + config.uint16Inds = true; + printf("Set indices type to uint16\n"); + } else { printf("Warning: unknown parameter \"%s\"\n", argv[i]); diff --git a/src/gltf_export.cpp b/src/gltf_export.cpp index 4708604..8237f09 100644 --- a/src/gltf_export.cpp +++ b/src/gltf_export.cpp @@ -26,44 +26,73 @@ bool ExportMap(const std::string &name, hlbsp::Map &map) int accessorId = 0; int modelAccessorId = 0; int bufferViewId = 0; + int meshId = 0; + int nodeId = 1; const int indsBufferOffset = map.vertices.size() * sizeof(map.vertices[0]); + const int indSize = map.indices16.size() ? sizeof(uint16_t) : sizeof(uint32_t); + const int indType = map.indices16.size() ? UNSIGNED_SHORT : UNSIGNED_INT; for (int i = 0; i < map.models.size(); i++) { - nodes[0]["children"].push_back(i + 1); - nodes[i + 1] = { {"mesh", i}, {"name",std::string("*") + std::to_string(i)} }; - - bufferViews[bufferViewId + 0] = { {"buffer", 0}, {"byteOffset", indsBufferOffset + map.models[i].offset * sizeof(map.indices[0])}, {"byteLength", map.models[i].count * sizeof(map.indices[0])}, {"target", ELEMENT_ARRAY_BUFFER} }; - bufferViews[bufferViewId + 1] = { {"buffer", 0}, {"byteOffset", map.models[i].vertOffset * sizeof(map.vertices[0])}, {"byteLength", map.models[i].vertCount * sizeof(map.vertices[0])},{"byteStride", 28}, {"target", ARRAY_BUFFER} }; - hlbsp::vec3_t bmin{ FLT_MAX, FLT_MAX, FLT_MAX }; - hlbsp::vec3_t bmax{ -FLT_MAX, -FLT_MAX, -FLT_MAX }; - for (int j = 0; j < map.models[i].vertCount; j++) + int modelNodeId = nodeId; + nodeId++; + nodes[modelNodeId] = { {"name",std::string("*") + std::to_string(i)} }; + nodes[0]["children"].push_back(modelNodeId); + + if (map.models[i].size() == 1) { - hlbsp::vec3_t v = map.vertices[map.models[i].vertOffset + j].pos; - bmin.x = fmin(bmin.x, v.x); - bmin.y = fmin(bmin.y, v.y); - bmin.z = fmin(bmin.z, v.z); - bmax.x = fmax(bmax.x, v.x); - bmax.y = fmax(bmax.y, v.y); - bmax.z = fmax(bmax.z, v.z); + nodes[modelNodeId]["mesh"] = meshId; } - accessors[accessorId + 0] = { {"bufferView",bufferViewId + 1},{"byteOffset",0},{"componentType",FLOAT},{"count",map.models[i].vertCount},{"type","VEC3"}, {"min",{bmin.x, bmin.y, bmin.z}}, {"max",{bmax.x, bmax.y, bmax.z}} }; - accessors[accessorId + 1] = { {"bufferView",bufferViewId + 1},{"byteOffset",12},{"componentType",FLOAT},{"count",map.models[i].vertCount},{"type","VEC2"} }; - accessors[accessorId + 2] = { {"bufferView",bufferViewId + 1},{"byteOffset",20},{"componentType",FLOAT},{"count",map.models[i].vertCount},{"type","VEC2"} }; - modelAccessorId = accessorId; - accessorId += 3; - - meshes[i] = { {"name", name + "_mesh" + std::to_string(i)}, {"primitives",json::array()} }; - for (int j = 0; j < map.models[i].submeshes.size(); j++) + + for (int mmi = 0; mmi < map.models[i].size(); mmi++) { - meshes[i]["primitives"][j] = { - {"attributes", {{"POSITION",modelAccessorId + 0}, {"TEXCOORD_0",modelAccessorId + 1}, {"TEXCOORD_1",modelAccessorId + 2}}}, - {"indices", accessorId}, - {"material", map.models[i].submeshes[j].material} - }; - accessors[accessorId] = { {"bufferView", bufferViewId}, { "byteOffset", (map.models[i].submeshes[j].offset - map.models[i].offset) * sizeof(map.indices[0]) }, { "componentType", UNSIGNED_INT }, { "count", map.models[i].submeshes[j].count }, { "type","SCALAR" } }; - accessorId++; + meshes[meshId] = { {"primitives",json::array()} }; + if (map.models[i].size() != 1) + { + std::string meshName = name + "_mesh" + std::to_string(i) + "_" + std::to_string(mmi); + nodes[nodeId] = { {"name", meshName}, {"mesh", meshId} }; + nodes[modelNodeId]["children"].push_back(nodeId); + nodeId++; + meshes[meshId]["name"] = meshName; + } + else + { + meshes[meshId]["name"] = name + "_mesh" + std::to_string(i); + } + const hlbsp::Map::model_t &model = map.models[i][mmi]; + + bufferViews[bufferViewId + 0] = { {"buffer", 0}, {"byteOffset", indsBufferOffset + model.offset * indSize}, {"byteLength", model.count * indSize}, {"target", ELEMENT_ARRAY_BUFFER} }; + bufferViews[bufferViewId + 1] = { {"buffer", 0}, {"byteOffset", model.vertOffset * sizeof(map.vertices[0])}, {"byteLength", model.vertCount * sizeof(map.vertices[0])},{"byteStride", 28}, {"target", ARRAY_BUFFER} }; + hlbsp::vec3_t bmin{ FLT_MAX, FLT_MAX, FLT_MAX }; + hlbsp::vec3_t bmax{ -FLT_MAX, -FLT_MAX, -FLT_MAX }; + for (int j = 0; j < model.vertCount; j++) + { + hlbsp::vec3_t v = map.vertices[model.vertOffset + j].pos; + bmin.x = fmin(bmin.x, v.x); + bmin.y = fmin(bmin.y, v.y); + bmin.z = fmin(bmin.z, v.z); + bmax.x = fmax(bmax.x, v.x); + bmax.y = fmax(bmax.y, v.y); + bmax.z = fmax(bmax.z, v.z); + } + accessors[accessorId + 0] = { {"bufferView",bufferViewId + 1},{"byteOffset",0},{"componentType",FLOAT},{"count",model.vertCount},{"type","VEC3"}, {"min",{bmin.x, bmin.y, bmin.z}}, {"max",{bmax.x, bmax.y, bmax.z}} }; + accessors[accessorId + 1] = { {"bufferView",bufferViewId + 1},{"byteOffset",12},{"componentType",FLOAT},{"count",model.vertCount},{"type","VEC2"} }; + accessors[accessorId + 2] = { {"bufferView",bufferViewId + 1},{"byteOffset",20},{"componentType",FLOAT},{"count",model.vertCount},{"type","VEC2"} }; + modelAccessorId = accessorId; + accessorId += 3; + + for (int j = 0; j < model.submeshes.size(); j++) + { + meshes[meshId]["primitives"][j] = { + {"attributes", {{"POSITION",modelAccessorId + 0}, {"TEXCOORD_0",modelAccessorId + 1}, {"TEXCOORD_1",modelAccessorId + 2}}}, + {"indices", accessorId}, + {"material", model.submeshes[j].material} + }; + accessors[accessorId] = { {"bufferView", bufferViewId}, { "byteOffset", (model.submeshes[j].offset - model.offset) * indSize }, { "componentType", indType }, { "count", model.submeshes[j].count }, { "type","SCALAR" } }; + accessorId++; + } + meshId++; + bufferViewId += 2; } - bufferViewId += 2; } _mkdir("textures"); @@ -95,17 +124,27 @@ bool ExportMap(const std::string &name, hlbsp::Map &map) images[lmapTexIndex] = { {"uri", name + "_lightmap0.png"} }; textures[lmapTexIndex] = { {"source", lmapTexIndex} }; - std::string bufferName = name + ".bin"; - int vertsLen = map.vertices.size() * sizeof(map.vertices[0]); - int indsLen = map.indices.size() * sizeof(map.indices[0]); - j["buffers"] = { { {"uri", bufferName}, {"byteLength", vertsLen + indsLen} } }; + int indsLen = 0; + + std::string bufferName = name + ".bin"; printf("Writing: %s\n", bufferName.c_str()); - std::ofstream verts(bufferName, std::ios_base::binary); - verts.write((char *)&map.vertices[0], vertsLen); - verts.write((char *)&map.indices[0], indsLen); - verts.close(); + std::ofstream bufferFile(bufferName, std::ios_base::binary); + bufferFile.write((char *)&map.vertices[0], vertsLen); + if (map.indices16.size()) + { + indsLen = map.indices16.size() * sizeof(map.indices16[0]); + bufferFile.write((char *)&map.indices16[0], indsLen); + } + else + { + indsLen = map.indices32.size() * sizeof(map.indices32[0]); + bufferFile.write((char *)&map.indices32[0], indsLen); + } + bufferFile.close(); + + j["buffers"] = { { {"uri", bufferName}, {"byteLength", vertsLen + indsLen} } }; printf("Writing: %s.gltf\n", name.c_str()); std::ofstream o(name + ".gltf"); diff --git a/src/hlbsp.cpp b/src/hlbsp.cpp index b232048..03c58f0 100644 --- a/src/hlbsp.cpp +++ b/src/hlbsp.cpp @@ -110,23 +110,48 @@ bool Map::load(const char *path, const char *name, LoadConfig *config) materialFaces[ti.miptex].push_back(fi); } - models[mi].submeshes.resize(materialFaces.size()); - models[mi].count = 0; - models[mi].offset = indicesOffset; - models[mi].vertOffset = vertices.size(); + models[mi].push_back({}); + model_t *model = &models[mi].back(); + + model->count = 0; + model->offset = indicesOffset; + model->vertOffset = vertices.size(); int indVertOffset = 0; - int submeshId = 0; for (auto &mat : materialFaces) { - models[mi].submeshes[submeshId].material = mat.first; - models[mi].submeshes[submeshId].offset = indicesOffset; - models[mi].submeshes[submeshId].count = 0; + submesh_t submesh; + submesh.material = mat.first; + submesh.offset = indicesOffset; + submesh.count = 0; for (int i = 0; i < mat.second.size(); i++) { const auto &f = faces[mat.second[i]]; const auto &ti = texinfos[f.texinfo]; + if (config->uint16Inds && (indVertOffset + f.numedges >= UINT16_MAX - 1)) + { + model->vertCount = vertices.size() - model->vertOffset; + if (submesh.count) + { + indicesOffset += submesh.count; + model->count += submesh.count; + model->submeshes.push_back(submesh); + } + + models[mi].push_back({}); + model = &models[mi].back(); + + model->count = 0; + model->offset = indicesOffset; + model->vertOffset = vertices.size(); + indVertOffset = 0; + + submesh.material = mat.first; + submesh.offset = indicesOffset; + submesh.count = 0; + } + int faceVertOffset = vertices.size(); float min_uv[2]{ FLT_MAX, FLT_MAX }; @@ -203,18 +228,29 @@ bool Map::load(const char *path, const char *name, LoadConfig *config) // turn TRIANGLE_FAN into TRIANGLES for (int j = 1; j < f.numedges - 1; j++) { - indices.push_back(indVertOffset); - indices.push_back(indVertOffset + j + 1); - indices.push_back(indVertOffset + j); + if (config->uint16Inds) + { + indices16.push_back(indVertOffset); + indices16.push_back(indVertOffset + j + 1); + indices16.push_back(indVertOffset + j); + } + else + { + indices32.push_back(indVertOffset); + indices32.push_back(indVertOffset + j + 1); + indices32.push_back(indVertOffset + j); + } } - models[mi].submeshes[submeshId].count += (f.numedges - 2) * 3; + submesh.count += (f.numedges - 2) * 3; indVertOffset += f.numedges; } - models[mi].count += models[mi].submeshes[submeshId].count; - indicesOffset += models[mi].submeshes[submeshId].count; - submeshId++; + if (submesh.count) + model->submeshes.push_back(submesh); + + model->count += submesh.count; + indicesOffset += submesh.count; } - models[mi].vertCount = vertices.size() - models[mi].vertOffset; + model->vertCount = vertices.size() - model->vertOffset; } lightmap.uploadBlock(name); diff --git a/src/hlbsp.h b/src/hlbsp.h index 41be31d..0d581d2 100644 --- a/src/hlbsp.h +++ b/src/hlbsp.h @@ -160,6 +160,7 @@ struct LoadConfig int lightmapSize = 1024; int lstyle = -1; bool lstylesAll = false; + bool uint16Inds = false; }; class Map @@ -189,8 +190,10 @@ class Map }; std::vector vertices; - std::vector indices; - std::vector models; + std::vector indices16; + std::vector indices32; + // model can contain multiple meshes + std::vector > models; std::vector faces; std::vector surfedges;