VOXELFORMAT: added kv6 support

master
Martin Gerhardy 2020-05-24 21:35:02 +02:00
parent 6e4720dde4
commit 686caa9461
12 changed files with 240 additions and 9 deletions

View File

@ -1,4 +1,4 @@
[Thumbnailer Entry]
TryExec=@CMAKE_PROJECT_NAME@-@NAME@
Exec=@CMAKE_PROJECT_NAME@-@NAME@ -s %s %i %o
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
MimeType=application/x-slab6;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,8 +10,8 @@
.SH DESCRIPTION
\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
(Qubicle), vox (MagicaVoxel), vmx (VoxEdit Sandbox), kvx (Build engine), binvox
and maybe others.
(Qubicle), vox (MagicaVoxel), vmx (VoxEdit Sandbox), kvx (Build engine), kv6 (SLAB6),
binvox and maybe others.
.SH OPTIONS
.TP
\fB\--trace|--debug\fR

View File

@ -10,4 +10,4 @@ Type=Application
Keywords=3d;modeling;game;voxel;engine;pixel;art
Categories=Graphics
StartupNotify=true
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
MimeType=application/x-slab6;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,6 +10,9 @@
</mime-type>
<mime-type type="application/x-magicavoxel">
<comment>Voxel model MagicaVoxel</comment>
<magic priority="50">
<match type="string" offset="0" value="VOX "/>
</magic>
<glob pattern="*.vox"/>
</mime-type>
<mime-type type="application/x-qubicle-binary">
@ -18,6 +21,9 @@
</mime-type>
<mime-type type="application/x-qubicle-tree">
<comment>Voxel model Qubicle tree</comment>
<magic priority="50">
<match type="string" offset="0" value="QB 2"/>
</magic>
<glob pattern="*.qbt"/>
</mime-type>
<mime-type type="application/x-sandbox-voxedit">
@ -28,4 +34,11 @@
<comment>Voxel model used by the Build engine</comment>
<glob pattern="*.kvx"/>
</mime-type>
<mime-type type="application/x-slab6">
<comment>Voxel model used by the SLAB6 editor</comment>
<magic priority="50">
<match type="string" offset="0" value="Kvxl"/>
</magic>
<glob pattern="*.kv6"/>
</mime-type>
</mime-info>

BIN
data/tests/test.kv6 Normal file

Binary file not shown.

View File

@ -2,6 +2,7 @@ set(LIB voxelformat)
set(SRCS
BinVoxFormat.h BinVoxFormat.cpp
KVXFormat.h KVXFormat.cpp
KV6Format.h KV6Format.cpp
VoxFileFormat.h VoxFileFormat.cpp
VoxFormat.h VoxFormat.cpp
QBTFormat.h QBTFormat.cpp
@ -23,6 +24,7 @@ set(TEST_SRCS
tests/QBFormatTest.cpp
tests/CubFormatTest.cpp
tests/KVXFormatTest.cpp
tests/KV6FormatTest.cpp
tests/VXMFormatTest.cpp
)
set(TEST_FILES
@ -30,6 +32,7 @@ set(TEST_FILES
tests/qubicle.qbt
tests/test.binvox
tests/test.kvx
tests/test.kv6
tests/magicavoxel.vox
tests/test.vxm
tests/cw.cub

View File

@ -0,0 +1,172 @@
/**
* @file
*/
#include "KV6Format.h"
#include "voxel/MaterialColor.h"
#include "core/io/FileStream.h"
#include "core/StringUtil.h"
#include "core/Log.h"
#include "core/Color.h"
#include "core/FourCC.h"
#include <glm/common.hpp>
namespace voxel {
#define wrap(read) \
if (read != 0) { \
Log::error("Could not load kv6 file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
return false; \
}
bool KV6Format::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
if (!(bool)file || !file->exists()) {
Log::error("Could not load kv6 file: File doesn't exist");
return false;
}
io::FileStream stream(file.get());
uint32_t magic;
wrap(stream.readInt(magic))
if (magic != FourCC('K','v','x','l')) {
Log::error("Invalid magic");
return false;
}
// Dimensions of voxel. (our depth is kvx height)
uint32_t xsiz, ysiz, zsiz;
wrap(stream.readInt(xsiz))
wrap(stream.readInt(ysiz))
wrap(stream.readInt(zsiz))
if (xsiz > 256 || ysiz > 256 || zsiz > 255) {
Log::error("Dimensions exceeded: w: %i, h: %i, d: %i", xsiz, zsiz, ysiz);
return false;
}
/**
* Centroid of voxel. For extra precision, this location has been shifted up by 8 bits.
*/
glm::vec3 pivot;
wrap(stream.readFloat(pivot.x))
wrap(stream.readFloat(pivot.y))
wrap(stream.readFloat(pivot.z))
if (xsiz > MaxRegionSize || ysiz > MaxRegionSize || zsiz > MaxRegionSize) {
Log::error("Volume exceeds the max allowed size: %i:%i:%i", xsiz, zsiz, ysiz);
return false;
}
const voxel::Region region(0, 0, 0, xsiz - 1, zsiz - 1, ysiz - 1);
if (!region.isValid()) {
Log::error("Invalid region: %i:%i:%i", xsiz, zsiz, ysiz);
return false;
}
uint32_t numvoxs;
wrap(stream.readInt(numvoxs))
Log::debug("numvoxs: %u", numvoxs);
constexpr uint32_t MAXVOXS = 1048576;
if (numvoxs > MAXVOXS) {
Log::error("Max allowed voxels exceeded: %u (max is %u)", numvoxs, MAXVOXS);
return false;
}
core_assert(stream.pos() == 32);
if (stream.seek(32 + numvoxs * 8 + (xsiz << 2) + ((xsiz * ysiz) << 1)) != -1) {
if (stream.remaining() != 0) {
uint32_t palMagic;
wrap(stream.readInt(palMagic))
if (palMagic == FourCC('S','P','a','l')) {
_paletteSize = 256;
_palette.resize(_paletteSize);
const MaterialColorArray& materialColors = getMaterialColors();
for (size_t i = 0; i < _paletteSize; ++i) {
uint8_t r, g, b;
wrap(stream.readByte(b))
wrap(stream.readByte(g))
wrap(stream.readByte(r))
const uint8_t nr = glm::clamp((uint32_t)glm::round((r * 255) / 63.0f), 0u, 255u);
const uint8_t ng = glm::clamp((uint32_t)glm::round((g * 255) / 63.0f), 0u, 255u);
const uint8_t nb = glm::clamp((uint32_t)glm::round((b * 255) / 63.0f), 0u, 255u);
const glm::vec4& color = core::Color::fromRGBA(nr, ng, nb, 255);
const int index = core::Color::getClosestMatch(color, materialColors);
_palette[i] = index;
}
}
}
}
stream.seek(32);
RawVolume *volume = new RawVolume(region);
volumes.push_back(VoxelVolume{volume, file->fileName(), true});
typedef struct {
uint8_t z, col, vis, dir;
} voxtype;
voxtype voxdata[MAXVOXS];
for (uint32_t c = 0u; c < numvoxs; ++c) {
uint8_t palr, palg, palb, pala;
wrap(stream.readByte(palb))
wrap(stream.readByte(palg))
wrap(stream.readByte(palr))
wrap(stream.readByte(pala))
const glm::vec4& color = core::Color::fromRGBA(palr, palg, palb, pala);
voxdata[c].col = findClosestIndex(color);
uint16_t zpos;
wrap(stream.readShort(zpos))
voxdata[c].z = zpos;
wrap(stream.readByte(voxdata[c].vis))
wrap(stream.readByte(voxdata[c].dir))
}
stream.skip(xsiz * sizeof(uint32_t));
uint16_t xyoffset[256][256];
for (uint32_t x = 0u; x < xsiz; ++x) {
for (uint32_t y = 0u; y < ysiz; ++y) {
wrap(stream.readShort(xyoffset[x][y]))
}
}
int idx = 0;
for (uint32_t x = 0; x < xsiz; ++x) {
for (uint32_t y = 0; y < ysiz; ++y) {
for (int end = idx + xyoffset[x][y]; idx < end; ++idx) {
const voxtype& vox = voxdata[idx];
const voxel::Voxel col = voxel::createVoxel(voxel::VoxelType::Generic, vox.col);
volume->setVoxel(x, (zsiz - 1) - vox.z, y, col);
}
}
}
uint32_t lastZ = 256;
voxel::Voxel lastCol;
idx = 0;
for (uint32_t x = 0; x < xsiz; ++x) {
for (uint32_t y = 0; y < ysiz; ++y) {
for (int end = idx + xyoffset[x][y]; idx < end; ++idx) {
const voxtype& vox = voxdata[idx];
if (vox.vis & (1 << 4)) {
lastZ = vox.z;
lastCol = voxel::createVoxel(voxel::VoxelType::Generic, vox.col);
}
if (vox.vis & (1 << 5)) {
for (; lastZ < vox.z; ++lastZ) {
volume->setVoxel(x, (zsiz - 1) - lastZ, y, lastCol);
}
}
}
}
}
return true;
}
#undef wrap
bool KV6Format::saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) {
return false;
}
}

View File

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

View File

@ -4,6 +4,7 @@
#include "KVXFormat.h"
#include "voxel/MaterialColor.h"
#include "core/io/FileStream.h"
#include "core/StringUtil.h"
#include "core/Log.h"
#include "core/Color.h"
@ -87,7 +88,6 @@ bool KVXFormat::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
_paletteSize = 256;
stream.seek(stream.size() - 3 * _paletteSize);
_palette.resize(_paletteSize);
const MaterialColorArray& materialColors = getMaterialColors();
/**
* The last 768 bytes of the KVX file is a standard 256-color VGA palette.
@ -105,8 +105,7 @@ bool KVXFormat::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
const uint8_t nb = glm::clamp((uint32_t)glm::round((b * 255) / 63.0f), 0u, 255u);
const glm::vec4& color = core::Color::fromRGBA(nr, ng, nb, 255);
const int index = core::Color::getClosestMatch(color, materialColors);
_palette[i] = index;
_palette[i] = findClosestIndex(color);
}
stream.seek(currentPos);

View File

@ -5,7 +5,6 @@
#pragma once
#include "VoxFileFormat.h"
#include "core/io/FileStream.h"
#include "core/io/File.h"
#include "core/String.h"

View File

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

View File

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