From d77bb4f6d1c28331ccd9431d958ef5eb278f53ec Mon Sep 17 00:00:00 2001 From: Martin Gerhardy Date: Tue, 19 Mar 2019 20:37:27 +0100 Subject: [PATCH] VOXELFORMAT: added vxm loading support --- data/tests/test.vxm | Bin 0 -> 14062 bytes src/modules/voxelformat/CMakeLists.txt | 4 + src/modules/voxelformat/VXMFormat.cpp | 190 ++++++++++++++++++ src/modules/voxelformat/VXMFormat.h | 22 ++ .../voxelformat/tests/VXMFormatTest.cpp | 19 ++ src/tools/voxedit/README.md | 2 +- src/tools/voxedit/SceneManager.cpp | 4 + src/tools/voxedit/ui/VoxEditWindow.cpp | 2 +- 8 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 data/tests/test.vxm create mode 100644 src/modules/voxelformat/VXMFormat.cpp create mode 100644 src/modules/voxelformat/VXMFormat.h create mode 100644 src/modules/voxelformat/tests/VXMFormatTest.cpp diff --git a/data/tests/test.vxm b/data/tests/test.vxm new file mode 100644 index 0000000000000000000000000000000000000000..bacfeacd8f5142ee06bb7de1b05c149ff19e4b8e GIT binary patch literal 14062 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 vSm~Ri^V)|cd2C1G7gyBd@SF3exists()) { + Log::error("Could not load vmx file: File doesn't exist"); + return VoxelVolumes(); + } + io::FileStream stream(file.get()); + + uint32_t header; + wrap(stream.readInt(header)) + constexpr uint32_t headerMagic5 = FourCC('V','X','M','5'); + constexpr uint32_t headerMagic4 = FourCC('V','X','M','4'); + if (header != headerMagic5 && header != headerMagic4) { + uint8_t buf[4]; + FourCCRev(buf, header); + Log::error("Could not load vxm file: Invalid magic found (%s)", (const char *)buf); + return VoxelVolumes(); + } + + if (header == headerMagic4) { + Log::debug("Found vxm4"); + } else if (header == headerMagic5) { + Log::debug("Found vxm5"); + glm::vec3 pivot; + wrap(stream.readFloat(pivot.x)); + wrap(stream.readFloat(pivot.y)); + wrap(stream.readFloat(pivot.z)); + } + + glm::uvec2 textureDim; + wrap(stream.readInt(textureDim.x)); + wrap(stream.readInt(textureDim.y)); + if (glm::any(glm::greaterThan(textureDim, glm::uvec2(2048)))) { + Log::warn("Size of texture exceeds the max allowed value"); + return VoxelVolumes(); + } + + uint32_t texAmount; + wrap(stream.readInt(texAmount)); + if (texAmount > 0xFFFF) { + Log::warn("Size of textures exceeds the max allowed value: %i", texAmount); + return VoxelVolumes(); + } + + Log::debug("texAmount: %i", (int)texAmount); + for (uint32_t t = 0u; t < texAmount; t++) { + char textureId[1024]; + wrapBool(stream.readString(sizeof(textureId), textureId, true)); + Log::debug("tex: %i: %s", (int)t, textureId); + uint32_t px = 0u; + for (;;) { + uint8_t rleStride; + wrap(stream.readByte(rleStride)); + if (rleStride == 0u) { + break; + } + + struct TexColor { + glm::u8vec3 rgb; + }; + static_assert(sizeof(TexColor) == 3); + stream.skip(sizeof(TexColor)); + px += rleStride; + if (px > textureDim.x * textureDim.y * sizeof(TexColor)) { + Log::error("RLE texture chunk exceeds max allowed size"); + } + } + } + + for (int i = 0; i < 6; ++i) { + uint32_t quadAmount; + wrap(stream.readInt(quadAmount)); + if (quadAmount > 0x40000U) { + Log::warn("Size of quads exceeds the max allowed value"); + return VoxelVolumes(); + } + struct QuadVertex { + glm::vec3 pos; + glm::ivec2 uv; + }; + static_assert(sizeof(QuadVertex) == 20); + stream.skip(quadAmount * 4 * sizeof(QuadVertex)); + } + + glm::uvec3 size; + wrap(stream.readInt(size.x)); + wrap(stream.readInt(size.y)); + wrap(stream.readInt(size.z)); + + if (glm::any(glm::greaterThan(size, glm::uvec3(2048)))) { + Log::warn("Size of volume exceeds the max allowed value"); + return VoxelVolumes(); + } + if (glm::any(glm::lessThan(size, glm::uvec3(1)))) { + Log::warn("Size of volume results in empty space"); + return VoxelVolumes(); + } + + Log::debug("Volume of size %u:%u:%u", size.x, size.y, size.z); + + uint8_t materialAmount; + wrap(stream.readByte(materialAmount)); + Log::debug("Palette of size %i", (int)materialAmount); + + uint8_t *palette = new uint8_t[materialAmount]; + for (int i = 0; i < (int) materialAmount; ++i) { + uint8_t blue; + wrap(stream.readByte(blue)); + uint8_t green; + wrap(stream.readByte(green)); + uint8_t red; + wrap(stream.readByte(red)); + uint8_t alpha; + wrap(stream.readByte(alpha)); + uint8_t emissive; + wrap(stream.readByte(emissive)); + const glm::vec4& rgbaColor = core::Color::fromRGBA(red, green, blue, alpha); + palette[i] = findClosestIndex(rgbaColor); + } + + const Region region(glm::ivec3(0), glm::ivec3(size) - 1); + RawVolume* volume = new RawVolume(region); + + int idx = 0; + for (;;) { + uint8_t length; + wrap(stream.readByte(length)); + if (length == 0u) { + break; + } + + uint8_t matIdx; + wrap(stream.readByte(matIdx)); + if (matIdx == 0xFFU) { + idx += length; + continue; + } + if (matIdx >= materialAmount) { + // at least try to load the rest + idx += length; + continue; + } + + // left to right, bottom to top, front to back + for (int i = idx; i < idx + length; i++) { + const int xx = i / (size.y * size.z); + const int yy = (i / size.z) % size.y; + const int zz = i % size.z; + const Voxel voxel = createColorVoxel(VoxelType::Generic, palette[matIdx]); + volume->setVoxel(size.x - 1 - xx, yy, zz, voxel); + } + idx += length; + } + delete[] palette; + VoxelVolumes volumes; + volumes.push_back(VoxelVolume{volume, "", true}); + return volumes; +} + +#undef wrap +#undef wrapBool + +} diff --git a/src/modules/voxelformat/VXMFormat.h b/src/modules/voxelformat/VXMFormat.h new file mode 100644 index 000000000..3873d3124 --- /dev/null +++ b/src/modules/voxelformat/VXMFormat.h @@ -0,0 +1,22 @@ +/** + * @file + */ + +#pragma once + +#include "VoxFileFormat.h" +#include "io/FileStream.h" + +namespace voxel { + +/** + * @brief VoxEdit (Sandbox) (vmx) + */ +class VXMFormat : public VoxFileFormat { +private: +public: + VoxelVolumes loadGroups(const io::FilePtr& file) override; + bool saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) override; +}; + +} diff --git a/src/modules/voxelformat/tests/VXMFormatTest.cpp b/src/modules/voxelformat/tests/VXMFormatTest.cpp new file mode 100644 index 000000000..f36aedc42 --- /dev/null +++ b/src/modules/voxelformat/tests/VXMFormatTest.cpp @@ -0,0 +1,19 @@ +/** + * @file + */ + +#include "AbstractVoxFormatTest.h" +#include "voxelformat/VXMFormat.h" + +namespace voxel { + +class VXMFormatTest: public AbstractVoxFormatTest { +}; + +TEST_F(VXMFormatTest, DISABLED_testLoad) { + VXMFormat f; + std::unique_ptr volume(load("test.vmx", f)); + ASSERT_NE(nullptr, volume) << "Could not load vmx file"; +} + +} diff --git a/src/tools/voxedit/README.md b/src/tools/voxedit/README.md index 509f3a52d..89ec089b9 100644 --- a/src/tools/voxedit/README.md +++ b/src/tools/voxedit/README.md @@ -8,7 +8,7 @@ my own engine and evolved into something that others might find useful, too. # Features * Large scene support -* Load vox, qbt, qb +* Load vox, qbt, qb, vxm * Save to vox, qbt, qb * Exporting to a lot of formats (dae, obj, fbx, gltf, ...) * Auto-saving diff --git a/src/tools/voxedit/SceneManager.cpp b/src/tools/voxedit/SceneManager.cpp index 99fe6100c..ed8940f6b 100644 --- a/src/tools/voxedit/SceneManager.cpp +++ b/src/tools/voxedit/SceneManager.cpp @@ -23,6 +23,7 @@ #include "voxelformat/VoxFormat.h" #include "voxelformat/QBTFormat.h" #include "voxelformat/QBFormat.h" +#include "voxelformat/VXMFormat.h" #include "video/ScopedPolygonMode.h" #include "video/ScopedLineWidth.h" #include "video/ScopedBlendMode.h" @@ -284,6 +285,9 @@ bool SceneManager::load(const std::string& file) { } else if (ext == "qb") { voxel::QBFormat f; newVolumes = f.loadGroups(filePtr); + } else if (ext == "vxm") { + voxel::VXMFormat f; + newVolumes = f.loadGroups(filePtr); } else { Log::error("Failed to load model file %s - unsupported file format", file.c_str()); return false; diff --git a/src/tools/voxedit/ui/VoxEditWindow.cpp b/src/tools/voxedit/ui/VoxEditWindow.cpp index 999acc27e..fba87d511 100644 --- a/src/tools/voxedit/ui/VoxEditWindow.cpp +++ b/src/tools/voxedit/ui/VoxEditWindow.cpp @@ -21,7 +21,7 @@ namespace voxedit { -static const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb"; +static const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb,vxm"; static const char *SUPPORTED_VOXEL_FORMATS_SAVE = "vox,qbt,qb"; static const struct {