VOXELFORMAT: added kvx loading support

master
Martin Gerhardy 2020-05-24 20:29:03 +02:00
parent 25924a7baf
commit fb656aa4ea
10 changed files with 211 additions and 4 deletions

View File

@ -1,4 +1,4 @@
[Thumbnailer Entry] [Thumbnailer Entry]
TryExec=@CMAKE_PROJECT_NAME@-@NAME@ TryExec=@CMAKE_PROJECT_NAME@-@NAME@
Exec=@CMAKE_PROJECT_NAME@-@NAME@ -s %s %i %o Exec=@CMAKE_PROJECT_NAME@-@NAME@ -s %s %i %o
MimeType=application/x-cubeworld;application/x-binvox;application/x-magicavoxel;application/x-qubicle-binary;application/x-qubicle-tree;application/x-sandbox-voxedit MimeType=application/x-build-engine;application/x-cubeworld;application/x-binvox;application/x-magicavoxel;application/x-qubicle-binary;application/x-qubicle-tree;application/x-sandbox-voxedit

View File

@ -10,7 +10,8 @@
.SH DESCRIPTION .SH DESCRIPTION
\fB@COMMANDLINE@\fP is a command line application that can convert several voxel \fB@COMMANDLINE@\fP is a command line application that can convert several voxel
volume formats into others. Supported formats are e.g. cub (CubeWorld), qb/qbt volume formats into others. Supported formats are e.g. cub (CubeWorld), qb/qbt
(Qubicle), vox (MagicaVoxel), vmx (VoxEdit Sandbox), binvox and maybe others. (Qubicle), vox (MagicaVoxel), vmx (VoxEdit Sandbox), kvx (Build engine), binvox
and maybe others.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\--trace|--debug\fR \fB\--trace|--debug\fR

View File

@ -10,4 +10,4 @@ Type=Application
Keywords=3d;modeling;game;voxel;engine;pixel;art Keywords=3d;modeling;game;voxel;engine;pixel;art
Categories=Graphics Categories=Graphics
StartupNotify=true StartupNotify=true
MimeType=application/x-cubeworld;application/x-binvox;application/x-magicavoxel;application/x-qubicle-binary;application/x-qubicle-tree;application/x-sandbox-voxedit MimeType=application/x-build-engine;application/x-cubeworld;application/x-binvox;application/x-magicavoxel;application/x-qubicle-binary;application/x-qubicle-tree;application/x-sandbox-voxedit

View File

@ -24,4 +24,8 @@
<comment>Voxel model Sandbox VoxEdit</comment> <comment>Voxel model Sandbox VoxEdit</comment>
<glob pattern="*.vxm"/> <glob pattern="*.vxm"/>
</mime-type> </mime-type>
<mime-type type="application/x-build-engine">
<comment>Voxel model used by the Build engine</comment>
<glob pattern="*.kvx"/>
</mime-type>
</mime-info> </mime-info>

BIN
data/tests/test.kvx Normal file

Binary file not shown.

View File

@ -1,6 +1,7 @@
set(LIB voxelformat) set(LIB voxelformat)
set(SRCS set(SRCS
BinVoxFormat.h BinVoxFormat.cpp BinVoxFormat.h BinVoxFormat.cpp
KVXFormat.h KVXFormat.cpp
VoxFileFormat.h VoxFileFormat.cpp VoxFileFormat.h VoxFileFormat.cpp
VoxFormat.h VoxFormat.cpp VoxFormat.h VoxFormat.cpp
QBTFormat.h QBTFormat.cpp QBTFormat.h QBTFormat.cpp
@ -21,12 +22,14 @@ set(TEST_SRCS
tests/QBTFormatTest.cpp tests/QBTFormatTest.cpp
tests/QBFormatTest.cpp tests/QBFormatTest.cpp
tests/CubFormatTest.cpp tests/CubFormatTest.cpp
tests/KVXFormatTest.cpp
tests/VXMFormatTest.cpp tests/VXMFormatTest.cpp
) )
set(TEST_FILES set(TEST_FILES
tests/qubicle.qb tests/qubicle.qb
tests/qubicle.qbt tests/qubicle.qbt
tests/test.binvox tests/test.binvox
tests/test.kvx
tests/magicavoxel.vox tests/magicavoxel.vox
tests/test.vxm tests/test.vxm
tests/cw.cub tests/cw.cub

View File

@ -0,0 +1,152 @@
/**
* @file
*/
#include "KVXFormat.h"
#include "voxel/MaterialColor.h"
#include "core/StringUtil.h"
#include "core/Log.h"
#include "core/Color.h"
#include <glm/common.hpp>
namespace voxel {
#define wrap(read) \
if (read != 0) { \
Log::error("Could not load kvx file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
delete[] xyoffset; \
delete[] xoffset; \
return false; \
}
bool KVXFormat::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
if (!(bool)file || !file->exists()) {
Log::error("Could not load kvx file: File doesn't exist");
return false;
}
io::FileStream stream(file.get());
uint32_t* xoffset = nullptr;
uint16_t* xyoffset = nullptr;
uint32_t numbytes;
wrap(stream.readInt(numbytes))
uint32_t width, height, depth;
wrap(stream.readInt(width)) wrap(stream.readInt(height)) wrap(stream.readInt(depth))
if (width > 256 || height > 256 || depth > 255) {
Log::error("Dimensions exceeded: w: %i, h: %i, d: %i", width, height, depth);
return false;
}
glm::vec3 pivot;
wrap(stream.readFloat(pivot.x))
wrap(stream.readFloat(pivot.y))
wrap(stream.readFloat(pivot.z))
pivot /= 256.0f;
if (width > MaxRegionSize || height > MaxRegionSize || depth > MaxRegionSize) {
Log::error("Volume exceeds the max allowed size: %i:%i:%i", width, height, depth);
return false;
}
const voxel::Region region(0, 0, 0, width - 1, depth - 1, height - 1);
if (!region.isValid()) {
Log::error("Invalid region: %i:%i:%i", width, depth, height);
return false;
}
xoffset = new uint32_t[width + 1];
xyoffset = new uint16_t[width * (height + 1)];
for (uint32_t i = 0u; i < width + 1; ++i) {
wrap(stream.readInt(xoffset[i]))
}
for (uint32_t x = 0u; x < width; ++x) {
for (uint32_t y = 0u; y <= height; ++y) {
wrap(stream.readShort(xyoffset[x * width + y]))
}
}
if (xoffset[0] != (width + 1) * 4 + width * (height + 1) * 2) {
delete[] xyoffset;
delete[] xoffset;
return false;
}
// Read the color palette from the end of the file and convert to our palette
const size_t currentPos = stream.pos();
_paletteSize = 256;
stream.seek(stream.size() - 3 * _paletteSize);
_palette.resize(_paletteSize);
const MaterialColorArray& materialColors = getMaterialColors();
for (size_t i = 0; i < _paletteSize; ++i) {
uint8_t r, g, b;
wrap(stream.readByte(r))
wrap(stream.readByte(g))
wrap(stream.readByte(b))
const int sr = glm::round((r * 255) / 63.0f);
const int sg = glm::round((g * 255) / 63.0f);
const int sb = glm::round((b * 255) / 63.0f);
const uint8_t nr = glm::clamp(sr, 0, 255);
const uint8_t ng = glm::clamp(sg, 0, 255);
const uint8_t nb = glm::clamp(sb, 0, 255);
const glm::vec4& color = core::Color::fromRGBA(nr, ng, nb, 255);
const int index = core::Color::getClosestMatch(color, materialColors);
_palette[i] = index;
}
stream.seek(currentPos);
RawVolume *volume = new RawVolume(region);
volumes.push_back(VoxelVolume{volume, file->fileName(), true});
uint32_t lastZ = 0;
voxel::Voxel lastCol;
for (uint32_t x = 0; x < width; x++) {
for (uint32_t y = 0; y < height; y++) {
uint16_t start = xyoffset[x * width + y];
const uint16_t end = xyoffset[x * width + y + 1];
while (start < end) {
uint8_t zpos;
uint8_t zlen;
uint8_t visfaces;
wrap(stream.readByte(zpos))
wrap(stream.readByte(zlen))
wrap(stream.readByte(visfaces))
for (uint8_t i = 0u; i < zlen; ++i) {
uint8_t index;
wrap(stream.readByte(index))
// read color->voxel mapping and add
lastCol = voxel::createVoxel(voxel::VoxelType::Generic, convertPaletteIndex(index));
volume->setVoxel(x - pivot.x, region.getHeightInCells() - (zpos + i - pivot.z), y - pivot.y, lastCol);
}
// fill in voxels by using the face info
if (!(visfaces & (1 << 4))) {
for (int i = lastZ + 1; i < zpos; i++) {
volume->setVoxel(x - pivot.x, region.getHeightInCells() - (i - pivot.z), y - pivot.y, lastCol);
}
}
if (!(visfaces & (1 << 5))) {
lastZ = zpos + zlen - 1;
}
start += zlen + 3;
}
}
}
delete[] xyoffset;
delete[] xoffset;
return true;
}
#undef wrap
bool KVXFormat::saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) {
return false;
}
}

View File

@ -0,0 +1,23 @@
/**
* @file
*/
#pragma once
#include "VoxFileFormat.h"
#include "core/io/FileStream.h"
#include "core/io/File.h"
#include "core/String.h"
namespace voxel {
/**
* @brief Voxel sprite format used by the Build engine
*/
class KVXFormat : public VoxFileFormat {
public:
bool loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) override;
bool saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) override;
};
}

View File

@ -10,10 +10,11 @@
#include "VXMFormat.h" #include "VXMFormat.h"
#include "CubFormat.h" #include "CubFormat.h"
#include "BinVoxFormat.h" #include "BinVoxFormat.h"
#include "KVXFormat.h"
namespace voxelformat { namespace voxelformat {
const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb,vxm,binvox,cub"; const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb,vxm,binvox,cub,kvx";
const char *SUPPORTED_VOXEL_FORMATS_SAVE = "vox,qbt,qb,binvox,cub"; const char *SUPPORTED_VOXEL_FORMATS_SAVE = "vox,qbt,qb,binvox,cub";
bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolumes) { bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolumes) {
@ -37,6 +38,11 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
if (!f.loadGroups(filePtr, newVolumes)) { if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes); voxelformat::clearVolumes(newVolumes);
} }
} else if (ext == "kvx") {
voxel::KVXFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "cub") { } else if (ext == "cub") {
voxel::CubFormat f; voxel::CubFormat f;
if (!f.loadGroups(filePtr, newVolumes)) { if (!f.loadGroups(filePtr, newVolumes)) {

View File

@ -0,0 +1,18 @@
/**
* @file
*/
#include "AbstractVoxFormatTest.h"
#include "voxelformat/KVXFormat.h"
namespace voxel {
class KVXFormatTest: public AbstractVoxFormatTest {
};
TEST_F(KVXFormatTest, testLoad) {
KVXFormat f;
std::unique_ptr<RawVolume> volume(load("test.kvx", f));
}
}