From 35efc0967c41efe3b759e42b09a6b51af5ff1516 Mon Sep 17 00:00:00 2001 From: HellAngel18 <1179426268lxr@gmail.com> Date: Wed, 22 Apr 2026 15:58:22 +0800 Subject: [PATCH 1/2] fix: preserve material IDs and image data in GLB export - CMakeLists: add binary dir include path and link VCGSimplifier for CLI target - SaveGLB: keep images in BIN chunk instead of Base64 data URI encoding - main: recover material IDs after simplification via nearest-centroid matching - Fixes issue where simplified GLB lost all textures (all faces mapped to mat 0) and output file was larger than input due to Base64 image bloat Made-with: Cursor --- CMakeLists.txt | 3 +- src/cli/glb_loader.cpp | 67 ++++++++++++++++++++++++++++++++---------- src/cli/main.cpp | 50 ++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d098c7..a9186f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,9 +26,10 @@ target_compile_options(VCGCoreStatic PRIVATE $<$:/utf-8>) add_executable(vcg-simplifier ${CLI_SOURCES}) -target_link_libraries(vcg-simplifier PRIVATE VCGCoreStatic) +target_link_libraries(vcg-simplifier PRIVATE VCGCoreStatic VCGSimplifier) target_include_directories(vcg-simplifier PRIVATE ${SRC_DIR}/cli/Public) target_include_directories(vcg-simplifier PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(vcg-simplifier PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(vcg-simplifier PRIVATE ${TINYGLTF_INCLUDE_DIRS}) target_compile_definitions(vcg-simplifier PRIVATE _CRT_SECURE_NO_WARNINGS) target_compile_options(vcg-simplifier PRIVATE $<$:/utf-8>) diff --git a/src/cli/glb_loader.cpp b/src/cli/glb_loader.cpp index 2576459..137561f 100644 --- a/src/cli/glb_loader.cpp +++ b/src/cli/glb_loader.cpp @@ -229,18 +229,11 @@ bool SaveGLB(MyMesh &m, const tinygltf::Model &originalModel, const std::string outModel.asset = originalModel.asset; outModel.asset.generator = "VCG-Simplifier"; - // 【关键修复】处理图片和纹理引用 outModel.materials = originalModel.materials; outModel.textures = originalModel.textures; outModel.samplers = originalModel.samplers; outModel.images = originalModel.images; - // 清除 BufferView 索引,强制 TinyGLTF 重新打包图片数据 - for (auto &img : outModel.images) { - img.bufferView = -1; - img.uri = ""; - } - // 2. 准备几何数据容器 std::vector rawPos; std::vector rawNor; @@ -350,7 +343,7 @@ bool SaveGLB(MyMesh &m, const tinygltf::Model &originalModel, const std::string allIndices.insert(allIndices.end(), pair.second.begin(), pair.second.end()); } - // 3. 构建二进制 Buffer + // 3. 构建二进制 Buffer (几何 + 原始图片数据) tinygltf::Buffer buffer; size_t lenPos = rawPos.size() * sizeof(float); @@ -365,23 +358,46 @@ bool SaveGLB(MyMesh &m, const tinygltf::Model &originalModel, const std::string size_t offsetCol = offsetUV + lenUV; size_t offsetInd = offsetCol + lenCol; - size_t totalSize = offsetInd + lenInd; - - // 【关键修复】Buffer 总长度必须是 4 的倍数 (Padding) - size_t padding = 0; - if (totalSize % 4 != 0) { - padding = 4 - (totalSize % 4); + size_t geoEnd = offsetInd + lenInd; + if (geoEnd % 4 != 0) geoEnd += 4 - (geoEnd % 4); + + // Collect raw image bytes from original buffer + struct ImgEntry { size_t srcOffset; size_t length; std::string mimeType; }; + std::vector imgEntries; + size_t imgTotalSize = 0; + for (const auto &img : originalModel.images) { + if (img.bufferView >= 0 && !originalModel.buffers.empty()) { + const auto &bv = originalModel.bufferViews[img.bufferView]; + size_t aligned = bv.byteLength; + if (aligned % 4 != 0) aligned += 4 - (aligned % 4); + imgEntries.push_back({bv.byteOffset, bv.byteLength, img.mimeType}); + imgTotalSize += aligned; + } else { + imgEntries.push_back({0, 0, ""}); + } } - buffer.data.resize(totalSize + padding); + buffer.data.resize(geoEnd + imgTotalSize, 0); - // 写入数据 std::memcpy(buffer.data.data() + offsetPos, rawPos.data(), lenPos); std::memcpy(buffer.data.data() + offsetNor, rawNor.data(), lenNor); std::memcpy(buffer.data.data() + offsetUV, rawUV.data(), lenUV); std::memcpy(buffer.data.data() + offsetCol, rawColorUB.data(), lenCol); std::memcpy(buffer.data.data() + offsetInd, allIndices.data(), lenInd); + // Copy image data and update image references + size_t imgWriteOffset = geoEnd; + const auto &srcBuf = originalModel.buffers[0].data; + for (size_t i = 0; i < imgEntries.size(); ++i) { + if (imgEntries[i].length == 0) continue; + std::memcpy(buffer.data.data() + imgWriteOffset, + srcBuf.data() + imgEntries[i].srcOffset, + imgEntries[i].length); + imgWriteOffset += imgEntries[i].length; + if (imgWriteOffset % 4 != 0) + imgWriteOffset += 4 - (imgWriteOffset % 4); + } + outModel.buffers.push_back(buffer); int bufferId = 0; @@ -402,6 +418,25 @@ bool SaveGLB(MyMesh &m, const tinygltf::Model &originalModel, const std::string int bvCol = addBufferView(offsetCol, lenCol, TINYGLTF_TARGET_ARRAY_BUFFER); int bvInd = addBufferView(offsetInd, lenInd, TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER); + // Image bufferViews — keep images in BIN chunk + size_t imgBvOffset = geoEnd; + for (size_t i = 0; i < imgEntries.size(); ++i) { + if (imgEntries[i].length == 0) continue; + tinygltf::BufferView bv; + bv.buffer = bufferId; + bv.byteOffset = imgBvOffset; + bv.byteLength = imgEntries[i].length; + bv.target = 0; + int bvIdx = (int)outModel.bufferViews.size(); + outModel.bufferViews.push_back(bv); + outModel.images[i].bufferView = bvIdx; + outModel.images[i].mimeType = imgEntries[i].mimeType; + outModel.images[i].uri = ""; + imgBvOffset += imgEntries[i].length; + if (imgBvOffset % 4 != 0) + imgBvOffset += 4 - (imgBvOffset % 4); + } + // 5. 创建 Accessors // 【关键修复】参数 typeEnum 改为 int,并接收 Min/Max auto addAccessor = [&](int bv, int count, int compType, int typeEnum, diff --git a/src/cli/main.cpp b/src/cli/main.cpp index d8ce309..4d027cc 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -3,6 +3,9 @@ #include "mymesh.h" #include "obj_loader.h" #include +#include +#include +#include // --- 主程序 --- void LogStatus(MyMesh &m, const char *stage) { printf("[%s] V:%d F:%d\n", stage, m.VN(), m.FN()); } @@ -125,6 +128,17 @@ int main(int argc, char **argv) { vcgData.wedge_tangents = wtangents.data(); vcgData.wedge_binormal_signs = wb_signs.data(); + // Save original face centroids + matId for post-simplification recovery + struct OrigFaceInfo { float cx, cy, cz; int matId; }; + std::vector origFaces(vcgData.num_faces); + for (int i = 0; i < vcgData.num_faces; ++i) { + int i0 = idx[i*3+0], i1 = idx[i*3+1], i2 = idx[i*3+2]; + origFaces[i].cx = (pos[i0*3+0]+pos[i1*3+0]+pos[i2*3+0]) / 3.0f; + origFaces[i].cy = (pos[i0*3+1]+pos[i1*3+1]+pos[i2*3+1]) / 3.0f; + origFaces[i].cz = (pos[i0*3+2]+pos[i1*3+2]+pos[i2*3+2]) / 3.0f; + origFaces[i].matId = mat_id[i]; + } + printf("Calling CreateMesh...\n"); // ======= C API 调用 ======= VCGMeshHandle handle = VCG_CreateMesh(); @@ -142,7 +156,41 @@ int main(int argc, char **argv) { printf("Calling GetMeshData...\n"); fflush(stdout); VCG_GetMeshData(handle, &vcgData); - printf("API done. Unpacking data...\n"); + printf("API done. Recovering material IDs...\n"); + printf(" Result: V=%d F=%d\n", vcgData.num_vertices, vcgData.num_faces); + + // Recover matId by nearest-centroid matching + for (int i = 0; i < vcgData.num_faces; ++i) { + int i0 = vcgData.indices[i*3+0]; + int i1 = vcgData.indices[i*3+1]; + int i2 = vcgData.indices[i*3+2]; + float cx = (vcgData.positions[i0*3+0]+vcgData.positions[i1*3+0]+vcgData.positions[i2*3+0]) / 3.0f; + float cy = (vcgData.positions[i0*3+1]+vcgData.positions[i1*3+1]+vcgData.positions[i2*3+1]) / 3.0f; + float cz = (vcgData.positions[i0*3+2]+vcgData.positions[i1*3+2]+vcgData.positions[i2*3+2]) / 3.0f; + + float bestDist = 1e30f; + int bestMat = 0; + for (int j = 0; j < (int)origFaces.size(); ++j) { + float dx = cx - origFaces[j].cx; + float dy = cy - origFaces[j].cy; + float dz = cz - origFaces[j].cz; + float d = dx*dx + dy*dy + dz*dz; + if (d < bestDist) { + bestDist = d; + bestMat = origFaces[j].matId; + if (d == 0.0f) break; + } + } + vcgData.material_ids[i] = bestMat; + } + + { + std::map matCount; + for (int i = 0; i < vcgData.num_faces; ++i) + matCount[vcgData.material_ids[i]]++; + for (auto &p : matCount) + printf(" matId=%d count=%d\n", p.first, p.second); + } fflush(stdout); // ======= 更新 MyMesh ======= m.Clear(); From c25abf887be007110fb56d3f315824e78cd345ec Mon Sep 17 00:00:00 2001 From: HellAngel18 <1179426268lxr@gmail.com> Date: Wed, 22 Apr 2026 16:03:27 +0800 Subject: [PATCH 2/2] refactor: move matId recovery from CLI to core Simplifier layer - Add nearest-centroid matId recovery in SimplifyDetailed() so all callers (CLI, DLL API, engine plugins) benefit from material preservation - Remove CLI-only workaround from main.cpp - matId is now saved before simplification and restored after via centroid matching, compensating for VCG QEM not propagating custom face attributes Made-with: Cursor --- src/cli/main.cpp | 50 +---------------------------------------- src/core/simplifier.cpp | 30 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 4d027cc..78ecce4 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -3,9 +3,6 @@ #include "mymesh.h" #include "obj_loader.h" #include -#include -#include -#include // --- 主程序 --- void LogStatus(MyMesh &m, const char *stage) { printf("[%s] V:%d F:%d\n", stage, m.VN(), m.FN()); } @@ -128,17 +125,6 @@ int main(int argc, char **argv) { vcgData.wedge_tangents = wtangents.data(); vcgData.wedge_binormal_signs = wb_signs.data(); - // Save original face centroids + matId for post-simplification recovery - struct OrigFaceInfo { float cx, cy, cz; int matId; }; - std::vector origFaces(vcgData.num_faces); - for (int i = 0; i < vcgData.num_faces; ++i) { - int i0 = idx[i*3+0], i1 = idx[i*3+1], i2 = idx[i*3+2]; - origFaces[i].cx = (pos[i0*3+0]+pos[i1*3+0]+pos[i2*3+0]) / 3.0f; - origFaces[i].cy = (pos[i0*3+1]+pos[i1*3+1]+pos[i2*3+1]) / 3.0f; - origFaces[i].cz = (pos[i0*3+2]+pos[i1*3+2]+pos[i2*3+2]) / 3.0f; - origFaces[i].matId = mat_id[i]; - } - printf("Calling CreateMesh...\n"); // ======= C API 调用 ======= VCGMeshHandle handle = VCG_CreateMesh(); @@ -156,41 +142,7 @@ int main(int argc, char **argv) { printf("Calling GetMeshData...\n"); fflush(stdout); VCG_GetMeshData(handle, &vcgData); - printf("API done. Recovering material IDs...\n"); - printf(" Result: V=%d F=%d\n", vcgData.num_vertices, vcgData.num_faces); - - // Recover matId by nearest-centroid matching - for (int i = 0; i < vcgData.num_faces; ++i) { - int i0 = vcgData.indices[i*3+0]; - int i1 = vcgData.indices[i*3+1]; - int i2 = vcgData.indices[i*3+2]; - float cx = (vcgData.positions[i0*3+0]+vcgData.positions[i1*3+0]+vcgData.positions[i2*3+0]) / 3.0f; - float cy = (vcgData.positions[i0*3+1]+vcgData.positions[i1*3+1]+vcgData.positions[i2*3+1]) / 3.0f; - float cz = (vcgData.positions[i0*3+2]+vcgData.positions[i1*3+2]+vcgData.positions[i2*3+2]) / 3.0f; - - float bestDist = 1e30f; - int bestMat = 0; - for (int j = 0; j < (int)origFaces.size(); ++j) { - float dx = cx - origFaces[j].cx; - float dy = cy - origFaces[j].cy; - float dz = cz - origFaces[j].cz; - float d = dx*dx + dy*dy + dz*dz; - if (d < bestDist) { - bestDist = d; - bestMat = origFaces[j].matId; - if (d == 0.0f) break; - } - } - vcgData.material_ids[i] = bestMat; - } - - { - std::map matCount; - for (int i = 0; i < vcgData.num_faces; ++i) - matCount[vcgData.material_ids[i]]++; - for (auto &p : matCount) - printf(" matId=%d count=%d\n", p.first, p.second); - } + printf("API done. V=%d F=%d\n", vcgData.num_vertices, vcgData.num_faces); fflush(stdout); // ======= 更新 MyMesh ======= m.Clear(); diff --git a/src/core/simplifier.cpp b/src/core/simplifier.cpp index 0920148..5b05876 100644 --- a/src/core/simplifier.cpp +++ b/src/core/simplifier.cpp @@ -45,6 +45,16 @@ Simplifier::Result Simplifier::SimplifyDetailed(MyMesh &m, const Params ¶ms) } } + // Save face centroids + matId before simplification for recovery + struct FaceCentroidMatId { float cx, cy, cz; int matId; }; + std::vector origFaceMats; + origFaceMats.reserve(m.fn); + for (const auto &f : m.face) { + if (f.IsD()) continue; + auto c = vcg::Barycenter(f); + origFaceMats.push_back({c.X(), c.Y(), c.Z(), f.matId}); + } + Clean(m); result.postCleanVertices = m.vn; result.postCleanFaces = m.fn; @@ -172,6 +182,26 @@ Simplifier::Result Simplifier::SimplifyDetailed(MyMesh &m, const Params ¶ms) vcg::tri::UpdateBounding::Box(m); core::detail::UpdateWedgeNormalsFromFace(m); + // Recover matId by nearest-centroid matching + for (auto &f : m.face) { + if (f.IsD()) continue; + auto c = vcg::Barycenter(f); + float bestDist = std::numeric_limits::max(); + int bestMat = 0; + for (const auto &orig : origFaceMats) { + float dx = c.X() - orig.cx; + float dy = c.Y() - orig.cy; + float dz = c.Z() - orig.cz; + float d = dx*dx + dy*dy + dz*dz; + if (d < bestDist) { + bestDist = d; + bestMat = orig.matId; + if (d == 0.0f) break; + } + } + f.matId = bestMat; + } + result.outputVertices = m.vn; result.outputFaces = m.fn; result.maxError = core::detail::ComputeApproxVertexHausdorff(m, originalPoints);