VOXELFORMAT: added kvx loading support
parent
25924a7baf
commit
fb656aa4ea
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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)) {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue