diff --git a/.gitmodules b/.gitmodules index f81e342cc..51dd07929 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,3 +20,9 @@ [submodule "extlib/angle"] path = extlib/angle url = https://github.com/solvespace/angle +[submodule "extlib/flatbuffers"] + path = extlib/flatbuffers + url = https://github.com/google/flatbuffers +[submodule "extlib/q3d"] + path = extlib/q3d + url = https://github.com/q3k/q3d diff --git a/CMakeLists.txt b/CMakeLists.txt index 83a0a5179..adec69e1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ set(ENABLE_COVERAGE OFF CACHE BOOL "Whether code coverage information will be collected") set(ENABLE_SANITIZERS OFF CACHE BOOL "Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer") +set(ENABLE_Q3D ON CACHE BOOL + "Whether to enable Q3D file export") set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)") @@ -298,6 +300,29 @@ if(ENABLE_COVERAGE) set(COVERAGE_LIBRARY --coverage) endif() +if(ENABLE_Q3D) + set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "") + set(FLATBUFFERS_BUILD_FLATC ON CACHE BOOL "") + set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "") + set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "") + add_subdirectory(extlib/flatbuffers + ${CMAKE_CURRENT_BINARY_DIR}/extlib/flatbuffers + EXCLUDE_FROM_ALL) + + set(q3d_schema ${CMAKE_CURRENT_SOURCE_DIR}/extlib/q3d/object.fbs) + set(q3d_outdir ${CMAKE_CURRENT_BINARY_DIR}/extlib/q3d) + set(q3d_header ${q3d_outdir}/object_generated.h) + + add_custom_command( + OUTPUT ${q3d_header} + COMMAND $ --cpp --no-includes -o ${q3d_outdir} + ${q3d_schema} + DEPENDS flatc) + + add_custom_target(gen_q3d_header DEPENDS ${q3d_header}) + set(Q3D_INCLUDE_DIR ${q3d_outdir}) +endif() + # application components add_subdirectory(res) diff --git a/extlib/flatbuffers b/extlib/flatbuffers new file mode 160000 index 000000000..a1f14005a --- /dev/null +++ b/extlib/flatbuffers @@ -0,0 +1 @@ +Subproject commit a1f14005ab823adc1300754fd37c01e9842ed4bc diff --git a/extlib/q3d b/extlib/q3d new file mode 160000 index 000000000..43f618744 --- /dev/null +++ b/extlib/q3d @@ -0,0 +1 @@ +Subproject commit 43f61874464ea61e80004b4571bb77e518854041 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2c7944249..b013578bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,8 @@ endif() set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) +set(HAVE_Q3D ${ENABLE_Q3D}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) @@ -221,11 +223,20 @@ target_link_libraries(solvespace-core if(Backtrace_FOUND) target_link_libraries(solvespace-core ${Backtrace_LIBRARY}) + add_dependencies(solvespace-core gen_q3d_header) +endif() + +if(ENABLE_Q3D) + target_link_libraries(solvespace-core + flatbuffers) + add_dependencies(solvespace-core gen_q3d_header) + include_directories(${Q3D_INCLUDE_DIR}) endif() target_compile_options(solvespace-core PRIVATE ${COVERAGE_FLAGS}) + # solvespace translations if(HAVE_GETTEXT) diff --git a/src/config.h.in b/src/config.h.in index 2f210d74a..15a596c6d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -16,4 +16,7 @@ #cmakedefine HAVE_BACKTRACE #define BACKTRACE_HEADER @BACKTRACE_HEADER@ +/* Do we have Q3D C++ stubs and the FlatBuffer library? */ +#cmakedefine HAVE_Q3D + #endif diff --git a/src/export.cpp b/src/export.cpp index d68dce05e..9efb7478e 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -7,6 +7,7 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" +#include "config.h" void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); @@ -826,6 +827,8 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) { ExportMeshAsObjTo(f, fMtl, m); fclose(fMtl); + } else if(filename.HasExtension("q3do")) { + ExportMeshAsQ3doTo(f, m); } else if(filename.HasExtension("js") || filename.HasExtension("html")) { SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); @@ -877,6 +880,63 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) { } } +//----------------------------------------------------------------------------- +// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file. +//----------------------------------------------------------------------------- +#ifndef HAVE_Q3D + +void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) { + Error("SolveSpace has been built without Q3D(O) support."); +} + +#else // HAVE_Q3D + +#include "object_generated.h" +void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) { + flatbuffers::FlatBufferBuilder builder(1024); + double s = SS.exportScale; + + // Create a material for every colour used, keep note of triangles belonging to color/material. + std::map, RgbaColorCompare> materials; + std::map>, RgbaColorCompare> materialTriangles; + for (const STriangle &t : sm->l) { + auto color = t.meta.color; + if (materials.find(color) == materials.end()) { + auto name = builder.CreateString(ssprintf("Color #%02x%02x%02x%02x", color.red, color.green, color.blue, color.alpha)); + auto co = q3d::CreateColor(builder, color.red, color.green, color.blue, color.alpha); + auto mo = q3d::CreateMaterial(builder, name, co); + materials.emplace(color, mo); + } + + Vector faceNormal = t.Normal(); + auto a = q3d::Vector3(t.a.x/s, t.a.y/s, t.a.z/s); + auto b = q3d::Vector3(t.b.x/s, t.b.y/s, t.b.z/s); + auto c = q3d::Vector3(t.c.x/s, t.c.y/s, t.c.z/s); + auto fn = q3d::Vector3(faceNormal.x, faceNormal.y, faceNormal.x); + auto n1 = q3d::Vector3(t.normals[0].x, t.normals[0].y, t.normals[0].z); + auto n2 = q3d::Vector3(t.normals[1].x, t.normals[1].y, t.normals[1].z); + auto n3 = q3d::Vector3(t.normals[2].x, t.normals[2].y, t.normals[2].z); + auto tri = q3d::CreateTriangle(builder, &a, &b, &c, &fn, &n1, &n2, &n3); + materialTriangles[color].push_back(tri); + } + + // Build all meshes sorted by material. + std::vector> meshes; + for (auto &it : materials) { + auto &mato = it.second; + auto to = builder.CreateVector(materialTriangles[it.first]); + auto mo = q3d::CreateMesh(builder, to, mato); + meshes.push_back(mo); + } + + auto mo = builder.CreateVector(meshes); + auto o = q3d::CreateObject(builder, mo); + q3d::FinishObjectBuffer(builder, o); + fwrite(builder.GetBufferPointer(), builder.GetSize(), 1, f); +} + +#endif // HAVE_Q3D + //----------------------------------------------------------------------------- // Export the mesh as Wavefront OBJ format. This requires us to reduce all the // identical vertices to the same identifier, so do that first. diff --git a/src/platform/gui.cpp b/src/platform/gui.cpp index e881f3012..59eaac3ee 100644 --- a/src/platform/gui.cpp +++ b/src/platform/gui.cpp @@ -4,6 +4,7 @@ // Copyright 2018 whitequark //----------------------------------------------------------------------------- #include "solvespace.h" +#include "config.h" namespace SolveSpace { namespace Platform { @@ -94,6 +95,9 @@ std::vector MeshFileFilters = { { CN_("file-type", "Wavefront OBJ mesh"), { "obj" } }, { CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } }, { CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } }, +#ifdef HAVE_Q3D + { CN_("file-type", "Q3D Object file"), { "q3do" } }, +#endif }; std::vector SurfaceFileFilters = { diff --git a/src/solvespace.h b/src/solvespace.h index 9c7885670..dce1d17cf 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -693,6 +693,7 @@ class SolveSpaceUI { void ExportAsPngTo(const Platform::Path &filename); void ExportMeshTo(const Platform::Path &filename); void ExportMeshAsStlTo(FILE *f, SMesh *sm); + void ExportMeshAsQ3doTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, SMesh *sm, SOutlineList *sol);