VOXELFORMAT: added vxm loading support

master
Martin Gerhardy 2019-03-19 20:37:27 +01:00
parent 5cf96c0c7d
commit d77bb4f6d1
8 changed files with 241 additions and 2 deletions

BIN
data/tests/test.vxm Normal file

Binary file not shown.

View File

@ -4,6 +4,7 @@ set(SRCS
VoxFormat.h VoxFormat.cpp
QBTFormat.h QBTFormat.cpp
QBFormat.h QBFormat.cpp
VXMFormat.h VXMFormat.cpp
MeshExporter.h MeshExporter.cpp
)
engine_add_module(TARGET ${LIB} SRCS ${SRCS} DEPENDENCIES voxel assimp)
@ -13,13 +14,16 @@ set(TEST_SRCS
tests/VoxFormatTest.cpp
tests/QBTFormatTest.cpp
tests/QBFormatTest.cpp
tests/VXMFormatTest.cpp
tests/MeshExporterTest.cpp
)
set(TEST_FILES
tests/qubicle.qb
tests/qubicle.qbt
tests/magicavoxel.vox
tests/test.vxm
)
gtest_suite_files(tests ${TEST_FILES})
gtest_suite_sources(tests ${TEST_SRCS})
gtest_suite_deps(tests ${LIB})

View File

@ -0,0 +1,190 @@
/**
* @file
*/
#include "VXMFormat.h"
#include "core/Common.h"
#include "core/Color.h"
#include "core/GLM.h"
#include "voxel/MaterialColor.h"
namespace voxel {
#define wrap(read) \
if (read != 0) { \
Log::error("Could not load vmx file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left (line %i)", (int)stream.remaining(), (int)__LINE__); \
return VoxelVolumes(); \
}
#define wrapBool(read) \
if (read != true) { \
Log::error("Could not load vmx file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left (line %i)", (int)stream.remaining(), (int)__LINE__); \
return VoxelVolumes(); \
}
bool VXMFormat::saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) {
return false;
}
VoxelVolumes VXMFormat::loadGroups(const io::FilePtr& file) {
if (!(bool)file || !file->exists()) {
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
}

View File

@ -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;
};
}

View File

@ -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<RawVolume> volume(load("test.vmx", f));
ASSERT_NE(nullptr, volume) << "Could not load vmx file";
}
}

View File

@ -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

View File

@ -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;

View File

@ -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 {