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]
TryExec=@CMAKE_PROJECT_NAME@-@NAME@
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
\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), binvox and maybe others.
(Qubicle), vox (MagicaVoxel), vmx (VoxEdit Sandbox), kvx (Build engine), 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-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>
<glob pattern="*.vxm"/>
</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>

BIN
data/tests/test.kvx Normal file

Binary file not shown.

View File

@ -1,6 +1,7 @@
set(LIB voxelformat)
set(SRCS
BinVoxFormat.h BinVoxFormat.cpp
KVXFormat.h KVXFormat.cpp
VoxFileFormat.h VoxFileFormat.cpp
VoxFormat.h VoxFormat.cpp
QBTFormat.h QBTFormat.cpp
@ -21,12 +22,14 @@ set(TEST_SRCS
tests/QBTFormatTest.cpp
tests/QBFormatTest.cpp
tests/CubFormatTest.cpp
tests/KVXFormatTest.cpp
tests/VXMFormatTest.cpp
)
set(TEST_FILES
tests/qubicle.qb
tests/qubicle.qbt
tests/test.binvox
tests/test.kvx
tests/magicavoxel.vox
tests/test.vxm
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 "CubFormat.h"
#include "BinVoxFormat.h"
#include "KVXFormat.h"
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";
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)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "kvx") {
voxel::KVXFormat 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/KVXFormat.h"
namespace voxel {
class KVXFormatTest: public AbstractVoxFormatTest {
};
TEST_F(KVXFormatTest, testLoad) {
KVXFormat f;
std::unique_ptr<RawVolume> volume(load("test.kvx", f));
}
}