VOXELFORMAT: added vxm loading support
parent
5cf96c0c7d
commit
d77bb4f6d1
Binary file not shown.
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue