VOXELFORMAT: added gox format reading support

master
Martin Gerhardy 2021-12-08 17:42:43 +01:00
parent 21034b3179
commit 7deedf0e36
9 changed files with 685 additions and 0 deletions

BIN
data/tests/test.gox Normal file

Binary file not shown.

1
debian/changelog vendored
View File

@ -4,6 +4,7 @@ vengi (0.0.15.0-1) UNRELEASED; urgency=low
* Fixed missing vxm (version 4) saving support
* Fixed missing palette value for vxm saving
* Added support for loading only the palettes
* Added support for goxel gox file format
* VoxConvert
* Added option to keep the input file palette and don't perform quantization

View File

@ -9,6 +9,7 @@ General:
- Fixed missing vxm (version 4) saving support
- Fixed missing palette value for vxm saving
- Added support for loading only the palettes
- Added support for goxel gox file format
VoxConvert:

View File

@ -12,6 +12,7 @@
| Sandbox VoxEdit | vxm | X | X | X | X |
| Sandbox VoxEdit | vxr | X | | X | |
| BinVox | binvox | X | X | X | |
| Goxel | gox | X | | X | |
| CubeWorld | cub | X | X | X | |
| Build engine | kvx | X | | X | X |
| SLAB6 | kv6 | X | | X | X |

View File

@ -6,6 +6,7 @@ set(SRCS
CSMFormat.h CSMFormat.cpp
KVXFormat.h KVXFormat.cpp
KV6Format.h KV6Format.cpp
GoxFormat.h GoxFormat.cpp
QBTFormat.h QBTFormat.cpp
QBFormat.h QBFormat.cpp
QBCLFormat.h QBCLFormat.cpp
@ -30,6 +31,7 @@ set(TEST_SRCS
tests/AoSVXLFormatTest.cpp
tests/BinVoxFormatTest.cpp
tests/VoxFormatTest.cpp
tests/GoxFormatTest.cpp
tests/QBTFormatTest.cpp
tests/QBFormatTest.cpp
tests/QBCLFormatTest.cpp
@ -56,6 +58,7 @@ set(TEST_FILES
tests/test.kv6
tests/minecraft_113.mca
tests/magicavoxel.vox
tests/test.gox
tests/test.vxm
tests/test2.vxm
tests/cw.cub

View File

@ -0,0 +1,527 @@
/**
* @file
*/
#include "GoxFormat.h"
#include "core/Color.h"
#include "core/FourCC.h"
#include "core/Log.h"
#include "core/StringUtil.h"
#include "image/Image.h"
#include "math/Axis.h"
#include "math/Math.h"
#include "voxel/MaterialColor.h"
#include "voxel/Voxel.h"
#include "voxelformat/VoxelVolumes.h"
#include "voxelutil/VolumeMerger.h"
#include "voxelutil/VolumeRotator.h"
#include "voxelutil/VolumeVisitor.h"
#include "voxelutil/VoxelUtil.h"
#include <glm/gtc/type_ptr.hpp>
namespace voxel {
#define wrap(read) \
if ((read) != 0) { \
Log::error("Could not load gox file: Failure at " CORE_STRINGIFY(read)); \
return false; \
}
#define wrapBool(read) \
if (!(read)) { \
Log::error("Could not load gox file: Failure at " CORE_STRINGIFY(read)); \
return false; \
}
#define wrapImg(read) \
if ((read) != 0) { \
Log::error("Could not load gox file: Failure at " CORE_STRINGIFY(read)); \
return image::ImagePtr(); \
}
#define wrapSave(write) \
if ((write) == false) { \
Log::error("Could not save gox file: " CORE_STRINGIFY(write) " failed"); \
return false; \
}
class GoxScopedChunkWriter {
private:
io::SeekableWriteStream& _stream;
int64_t _chunkSizePos;
uint32_t _chunkId;
public:
GoxScopedChunkWriter(io::SeekableWriteStream& stream, uint32_t chunkId) : _stream(stream), _chunkId(chunkId) {
uint8_t buf[4];
FourCCRev(buf, chunkId);
Log::debug("Saving %c%c%c%c", buf[0], buf[1], buf[2], buf[3]);
stream.writeInt(chunkId);
_chunkSizePos = stream.pos();
stream.writeInt(0);
}
~GoxScopedChunkWriter() {
const int64_t chunkStart = _chunkSizePos + sizeof(uint32_t);
const int64_t currentPos = _stream.pos();
core_assert_msg(chunkStart <= currentPos, "%u should be <= %u", (uint32_t)chunkStart, (uint32_t)currentPos);
const uint64_t chunkSize = currentPos - chunkStart;
_stream.seek(_chunkSizePos);
_stream.writeInt(chunkSize);
_stream.seek(currentPos);
_stream.writeInt(0); // CRC - not calculated
uint8_t buf[4];
FourCCRev(buf, _chunkId);
Log::debug("Chunk size for %c%c%c%c: %i", buf[0], buf[1], buf[2], buf[3], (int)chunkSize);
}
};
bool GoxFormat::loadChunk_Header(GoxChunk &c, io::SeekableReadStream &stream) {
if (stream.eos()) {
return false;
}
core_assert_msg(stream.remaining() >= 8, "stream should at least contain 8 more bytes, but only has %i", (int)stream.remaining());
wrap(stream.readInt(c.type))
wrap(stream.readInt(c.length))
c.streamStartPos = stream.pos();
return true;
}
bool GoxFormat::loadChunk_ReadData(io::SeekableReadStream &stream, char *buff, int size) {
if (size == 0) {
return true;
}
if (stream.read(buff, size) != 0) {
return false;
}
return true;
}
void GoxFormat::loadChunk_ValidateCRC(io::SeekableReadStream &stream) {
uint32_t crc;
stream.readInt(crc);
}
bool GoxFormat::loadChunk_DictEntry(const GoxChunk &c, io::SeekableReadStream &stream, char *key, char *value) {
const int64_t endPos = c.streamStartPos + c.length;
if (stream.pos() >= endPos) {
return false;
}
if (stream.eos()) {
Log::error("Unexpected end of stream in reading a dict entry");
return false;
}
int keySize;
wrap(stream.readInt(keySize));
if (keySize == 0) {
Log::warn("Empty string for key in dict");
return false;
}
if (keySize >= 256) {
Log::error("Max size of 256 exceeded for dict key: %i", keySize);
return false;
}
loadChunk_ReadData(stream, key, keySize);
key[keySize] = '\0';
int valueSize;
wrap(stream.readInt(valueSize));
if (valueSize >= 256) {
Log::error("Max size of 256 exceeded for dict value: %i", valueSize);
return false;
}
// the values are floats, ints, strings, ... - but nevertheless the null byte for strings
loadChunk_ReadData(stream, value, valueSize);
value[valueSize] = '\0';
Log::debug("Dict entry '%s'", key);
return true;
}
image::ImagePtr GoxFormat::loadScreenshot(const core::String &filename, io::SeekableReadStream& stream) {
uint32_t magic;
wrapImg(stream.readInt(magic))
if (magic != FourCC('G', 'O', 'X', ' ')) {
Log::error("Invalid magic");
return image::ImagePtr();
}
uint32_t version;
wrapImg(stream.readInt(version))
if (version != 2) {
Log::error("Unknown gox format version found: %u", version);
return image::ImagePtr();
}
GoxChunk c;
while (loadChunk_Header(c, stream)) {
if (c.type == FourCC('B', 'L', '1', '6') || c.type == FourCC('L', 'A', 'Y', 'R')) {
break;
} else if (c.type == FourCC('P', 'R', 'E', 'V')) {
uint8_t *png = (uint8_t *)core_malloc(c.length);
wrapImg(loadChunk_ReadData(stream, (char *)png, c.length))
image::ImagePtr img = image::createEmptyImage("gox-preview");
img->load(png, c.length);
core_free(png);
return img;
} else {
stream.seek(c.length, SEEK_CUR);
}
loadChunk_ValidateCRC(stream);
}
return image::ImagePtr();
}
bool GoxFormat::loadChunk_LAYR(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
const int size = (int)volumes.size();
core::String name = core::string::format("layer %i", size);
voxel::RawVolume *layerVolume = new voxel::RawVolume(voxel::Region(0, 0, 0, 1, 1, 1));
uint32_t blockCount;
wrap(stream.readInt(blockCount))
for (uint32_t i = 0; i < blockCount; ++i) {
uint32_t index;
wrap(stream.readInt(index))
if (index > state.images.size()) {
Log::error("Index out of bounds: %u", index);
return false;
}
const image::ImagePtr &img = state.images[index];
if (!img) {
Log::error("Invalid image index: %u", index);
return false;
}
const uint8_t *rgba = img->data();
int bpp = img->depth();
int w = img->width();
int h = img->height();
core_assert(w == 64 && h == 64 && bpp == 4);
int32_t x, y, z;
wrap(stream.readInt(x))
wrap(stream.readInt(y))
wrap(stream.readInt(z))
// Previous version blocks pos.
if (state.version == 1) {
x -= 8;
y -= 8;
z -= 8;
}
wrap(stream.skip(4))
voxel::Region blockRegion(x, z, y, x + (BlockSize - 1), z + (BlockSize - 1), y + (BlockSize - 1));
voxel::RawVolume *blockVolume = new voxel::RawVolume(blockRegion);
const uint8_t *v = rgba;
bool empty = true;
for (int y1 = blockRegion.getLowerY(); y1 <= blockRegion.getUpperY(); ++y1) {
for (int z1 = blockRegion.getLowerZ(); z1 <= blockRegion.getUpperZ(); ++z1) {
for (int x1 = blockRegion.getLowerX(); x1 <= blockRegion.getUpperX(); ++x1) {
const glm::vec4 &color = core::Color::fromRGBA(v[0], v[1], v[2], v[3]);
voxel::VoxelType voxelType = voxel::VoxelType::Generic;
uint8_t index;
if (v[3] == 0u) {
voxelType = voxel::VoxelType::Air;
index = 0;
} else {
index = findClosestIndex(color);
}
const voxel::Voxel voxel = voxel::createVoxel(voxelType, index);
blockVolume->setVoxel(x1, y1, z1, voxel);
if (!voxel::isAir(voxel.getMaterial())) {
empty = false;
}
v += 4;
}
}
}
if (!empty) {
voxel::Region destReg(layerVolume->region());
if (!destReg.containsRegion(blockRegion)) {
destReg.accumulate(blockRegion);
voxel::RawVolume *newVolume = new voxel::RawVolume(destReg);
voxelutil::copyIntoRegion(*layerVolume, *newVolume, layerVolume->region());
delete layerVolume;
layerVolume = newVolume;
}
voxel::mergeVolumes(layerVolume, blockVolume, blockRegion, blockRegion);
}
delete blockVolume;
}
bool visible = true;
char dictKey[256];
char dictValue[256];
while (loadChunk_DictEntry(c, stream, dictKey, dictValue)) {
// "name" 255 chars max
if (!strcmp(dictKey, "name")) {
name = dictValue;
}
// "mat" (4x4 matrix)
// "visible" (bool)
// "id" unique id
// "img-path" layer texture path
// "base_id" int
// "box" 4x4 bounding box float
// "shape" layer layer - currently unsupported TODO
// "color" 4xbyte
// "visible" bool
// "material" int (index)
}
volumes.push_back(VoxelVolume{voxel::mirrorAxis(layerVolume, math::Axis::Z), name, visible});
delete layerVolume;
return true;
}
bool GoxFormat::loadChunk_BL16(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
uint8_t* png = (uint8_t*)core_malloc(c.length);
wrapBool(loadChunk_ReadData(stream, (char *)png, c.length))
image::ImagePtr img = image::createEmptyImage("gox-voxeldata");
bool success = img->load(png, c.length);
core_free(png);
if (!success) {
Log::error("Failed to load png chunk");
return false;
}
state.images[state.imageIndex++] = img;
return true;
}
bool GoxFormat::loadChunk_MATE(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
char dictKey[256];
char dictValue[256];
while (loadChunk_DictEntry(c, stream, dictKey, dictValue)) {
// "name" 127 chars max
// "color" 4xfloat
// "metallic" float
// "roughness" float
// "emission" 3xfloat
}
return true;
}
bool GoxFormat::loadChunk_CAMR(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
char dictKey[256];
char dictValue[256];
while (loadChunk_DictEntry(c, stream, dictKey, dictValue)) {
// "name" 127 chars max
// "dist" float
// "ortho" bool
// "mat" 4x4 float
// "active" no value - active scene camera if this key is available
}
return true;
}
bool GoxFormat::loadChunk_IMG(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
char dictKey[256];
char dictValue[256];
while (loadChunk_DictEntry(c, stream, dictKey, dictValue)) {
// "box" 4x4 float bounding box
}
return true;
}
bool GoxFormat::loadChunk_LIGH(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
char dictKey[256];
char dictValue[256];
while (loadChunk_DictEntry(c, stream, dictKey, dictValue)) {
// "pitch" float
// "yaw" float
// "intensity" float
// "fixed" bool
// "ambient" float
// "shadow" float
}
return true;
}
bool GoxFormat::loadGroups(const core::String &filename, io::SeekableReadStream &stream, VoxelVolumes &volumes) {
uint32_t magic;
wrap(stream.readInt(magic))
if (magic != FourCC('G', 'O', 'X', ' ')) {
Log::error("Invalid magic");
return false;
}
State state;
wrap(stream.readInt(state.version))
if (state.version > 2) {
Log::error("Unknown gox format version found: %u", state.version);
return false;
}
GoxChunk c;
while (loadChunk_Header(c, stream)) {
if (c.type == FourCC('B', 'L', '1', '6')) {
wrapBool(loadChunk_BL16(state, c, stream, volumes))
} else if (c.type == FourCC('L', 'A', 'Y', 'R')) {
wrapBool(loadChunk_LAYR(state, c, stream, volumes))
} else if (c.type == FourCC('C', 'A', 'M', 'R')) {
wrapBool(loadChunk_CAMR(state, c, stream, volumes))
} else if (c.type == FourCC('M', 'A', 'T', 'E')) {
wrapBool(loadChunk_MATE(state, c, stream, volumes))
} else if (c.type == FourCC('I', 'M', 'G', ' ')) {
wrapBool(loadChunk_IMG(state, c, stream, volumes))
} else if (c.type == FourCC('L', 'I', 'G', 'H')) {
wrapBool(loadChunk_LIGH(state, c, stream, volumes))
} else {
stream.seek(c.length, SEEK_CUR);
}
loadChunk_ValidateCRC(stream);
}
return !volumes.empty();
}
bool GoxFormat::saveChunk_DictEntry(io::SeekableWriteStream &stream, const char *key, const void *value, size_t valueSize) {
const int keyLength = (int)SDL_strlen(key);
wrapBool(stream.writeInt(keyLength))
wrap(stream.write(key, keyLength))
wrapBool(stream.writeInt(valueSize))
wrap(stream.write(value, valueSize))
return true;
}
bool GoxFormat::saveChunk_IMG(io::SeekableWriteStream& stream) {
return true; // not used
}
bool GoxFormat::saveChunk_PREV(io::SeekableWriteStream& stream) {
return true; // not used
}
bool GoxFormat::saveChunk_CAMR(io::SeekableWriteStream& stream) {
return true; // not used
}
bool GoxFormat::saveChunk_LIGH(io::SeekableWriteStream& stream) {
return true; // not used
}
bool GoxFormat::saveChunk_MATE(io::SeekableWriteStream& stream) {
GoxScopedChunkWriter scoped(stream, FourCC('M', 'A', 'T', 'E'));
const MaterialColorArray& materialColors = getMaterialColors();
const int numColors = (int)materialColors.size();
for (int i = 0; i < numColors; ++i) {
const core::String name = core::string::format("mat%i", i);
const float value[3] = {0.0f, 0.0f, 0.0f};
wrapBool(saveChunk_DictEntry(stream, "name", name.c_str(), name.size()))
wrapBool(saveChunk_DictEntry(stream, "color", materialColors[i]))
wrapBool(saveChunk_DictEntry(stream, "metallic", value[0]))
wrapBool(saveChunk_DictEntry(stream, "roughness", value[0]))
wrapBool(saveChunk_DictEntry(stream, "emission", value))
}
return true;
}
bool GoxFormat::saveChunk_LAYR(io::SeekableWriteStream& stream, const VoxelVolumes &volumes) {
int blockUid = 0;
for (const VoxelVolume &v : volumes) {
GoxScopedChunkWriter scoped(stream, FourCC('L', 'A', 'Y', 'R'));
int blocks;
// TODO: we also write empty blocks
voxelutil::visitVolume(*v.volume, BlockSize, BlockSize, BlockSize, [&] (int, int, int, const voxel::Voxel &) {
++blocks;
});
wrapBool(stream.writeInt(blocks))
const voxel::Region &region = v.volume->region();
const glm::ivec3 &lower = region.getLowerCorner();
glm::ivec3 mins;
mins[0] = lower[0] & ~(BlockSize - 1);
mins[1] = lower[1] & ~(BlockSize - 1);
mins[2] = lower[2] & ~(BlockSize - 1);
const glm::ivec3 &upper = region.getUpperCorner();
glm::ivec3 maxs;
maxs[0] = upper[0] & ~(BlockSize - 1);
maxs[1] = upper[1] & ~(BlockSize - 1);
maxs[2] = upper[2] & ~(BlockSize - 1);
for (int y = mins.y; y <= maxs.y; y += BlockSize) {
for (int z = mins.z; z <= maxs.z; z += BlockSize) {
for (int x = mins.x; x <= maxs.x; x += BlockSize) {
wrapBool(stream.writeInt(blockUid++))
wrapBool(stream.writeInt(x))
wrapBool(stream.writeInt(y))
wrapBool(stream.writeInt(z))
wrapBool(stream.writeInt(0))
}
}
}
wrapBool(saveChunk_DictEntry(stream, "name", v.name.c_str(), v.name.size()))
glm::mat4 mat(0.0f);
wrapBool(saveChunk_DictEntry(stream, "mat", (const uint8_t*)glm::value_ptr(mat), sizeof(mat)))
#if 0
wrapBool(saveChunk_DictEntry(stream, "id", &layer->id))
wrapBool(saveChunk_DictEntry(stream, "base_id", &layer->base_id))
// material_idx = get_material_idx(img, layer->material);
wrapBool(saveChunk_DictEntry(stream, "material", &material_idx))
#endif
wrapBool(saveChunk_DictEntry(stream, "visible", v.visible))
}
return true;
}
bool GoxFormat::saveChunk_BL16(io::SeekableWriteStream& stream, const VoxelVolumes &volumes) {
for (const VoxelVolume &v : volumes) {
GoxScopedChunkWriter scoped(stream, FourCC('B', 'L', '1', '6'));
const voxel::Region &region = v.volume->region();
const glm::ivec3 &lower = region.getLowerCorner();
glm::ivec3 mins;
mins[0] = lower[0] & ~(BlockSize - 1);
mins[1] = lower[1] & ~(BlockSize - 1);
mins[2] = lower[2] & ~(BlockSize - 1);
const glm::ivec3 &upper = region.getUpperCorner();
glm::ivec3 maxs;
maxs[0] = upper[0] & ~(BlockSize - 1);
maxs[1] = upper[1] & ~(BlockSize - 1);
maxs[2] = upper[2] & ~(BlockSize - 1);
for (int by = mins.y; by <= maxs.y; by += BlockSize) {
for (int bz = mins.z; bz <= maxs.z; bz += BlockSize) {
for (int bx = mins.x; bx <= maxs.x; bx += BlockSize) {
const voxel::Region blockRegion(bx, by, bz, bx + BlockSize - 1, by + BlockSize - 1, bz + BlockSize - 1);
const size_t size = (size_t)BlockSize * BlockSize * BlockSize * 4;
uint32_t *data = (uint32_t*)core_malloc(size);
int offset = 0;
const MaterialColorArray& materialColors = getMaterialColors();
voxelutil::visitVolume(*v.volume, blockRegion, [&](int, int, int, const voxel::Voxel& voxel) {
data[offset++] = core::Color::getRGBA(materialColors[voxel.getColor()]);
});
int pngSize;
uint8_t *png = image::createPng(data, 64, 64, 4, &pngSize);
wrap(stream.write(png, pngSize))
free(png);
free(data);
}
}
}
}
return true;
}
bool GoxFormat::saveGroups(const VoxelVolumes &volumes, const core::String &filename, io::SeekableWriteStream &stream) {
wrapSave(stream.writeInt(FourCC('G', 'O', 'X', ' ')))
wrapSave(stream.writeInt(2))
wrapBool(saveChunk_IMG(stream))
wrapBool(saveChunk_PREV(stream))
wrapBool(saveChunk_BL16(stream, volumes))
wrapBool(saveChunk_MATE(stream))
wrapBool(saveChunk_LAYR(stream, volumes))
wrapBool(saveChunk_CAMR(stream))
wrapBool(saveChunk_LIGH(stream))
return true;
}
#undef wrapBool
#undef wrapImg
#undef wrap
#undef wrapSave
} // namespace voxel

View File

@ -0,0 +1,123 @@
/**
* @file
*/
#pragma once
#include "Format.h"
#include "io/Stream.h"
namespace voxel {
/**
* @brief Taken from gox
*
* File format, version 2:
*
* This is inspired by the png format, where the file consists of a list of
* chunks with different types.
*
* 4 bytes magic string : "GOX "
* 4 bytes version : 2
* List of chunks:
* 4 bytes: type
* 4 bytes: data length
* n bytes: data
* 4 bytes: CRC
*
* The layer can end with a DICT:
* for each entry:
* 4 byte : key size (0 = end of dict)
* n bytes: key
* 4 bytes: value size
* n bytes: value
*
* chunks types:
*
* IMG : a dict of info:
* - box: the image gox.
*
* PREV: a png image for preview.
*
* BL16: a 16^3 block saved as a 64x64 png image.
*
* LAYR: a layer:
* 4 bytes: number of blocks.
* for each block:
* 4 bytes: block index
* 4 bytes: x
* 4 bytes: y
* 4 bytes: z
* 4 bytes: 0
* [DICT]
*
* CAMR: a camera:
* [DICT] containing the following entries:
* name: string
* dist: float
* rot: quaternion
* ofs: offset
* ortho: bool
*
* LIGH: the light:
* [DICT] containing the following entries:
* pitch: radian
* yaw: radian
* intensity: float
*/
class GoxFormat : public Format {
private:
static constexpr int BlockSize = 16;
struct GoxChunk {
uint32_t type = 0u;
int64_t streamStartPos = 0u;
int32_t length = 0u;
};
struct State {
int32_t version = 0;
core::Array<image::ImagePtr, 64> images;
int imageIndex = 0;
};
bool loadChunk_Header(GoxChunk &c, io::SeekableReadStream &stream);
bool loadChunk_ReadData(io::SeekableReadStream &stream, char *buff, int size);
void loadChunk_ValidateCRC(io::SeekableReadStream &stream);
bool loadChunk_DictEntry(const GoxChunk &c, io::SeekableReadStream &stream, char *key, char *value);
bool loadChunk_LAYR(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool loadChunk_BL16(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool loadChunk_MATE(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool loadChunk_CAMR(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool loadChunk_IMG(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool loadChunk_LIGH(State& state, const GoxChunk &c, io::SeekableReadStream &stream, VoxelVolumes &volumes);
bool saveChunk_DictEntry(io::SeekableWriteStream &stream, const char *key, const void *value, size_t valueSize);
template<class T>
bool saveChunk_DictEntry(io::SeekableWriteStream &stream, const char *key, const T &value) {
return saveChunk_DictEntry(stream, key, &value, sizeof(T));
}
// Write image info and preview pic - not used.
bool saveChunk_IMG(io::SeekableWriteStream &stream);
bool saveChunk_PREV(io::SeekableWriteStream &stream);
// Write all the cameras - not used.
bool saveChunk_CAMR(io::SeekableWriteStream &stream);
// Write all the lights - not used.
bool saveChunk_LIGH(io::SeekableWriteStream &stream);
// Write all the blocks chunks.
bool saveChunk_BL16(io::SeekableWriteStream &stream, const VoxelVolumes &volumes);
// Write all the materials.
bool saveChunk_MATE(io::SeekableWriteStream &stream);
// Write all the layers.
bool saveChunk_LAYR(io::SeekableWriteStream &stream, const VoxelVolumes &volumes);
public:
bool loadGroups(const core::String &filename, io::SeekableReadStream &stream, VoxelVolumes &volumes) override;
bool saveGroups(const VoxelVolumes &volumes, const core::String &filename,
io::SeekableWriteStream &stream) override;
image::ImagePtr loadScreenshot(const core::String &filename, io::SeekableReadStream &stream) override;
};
} // namespace voxel

View File

@ -22,6 +22,7 @@
#include "voxelformat/VXRFormat.h"
#include "voxelformat/VXLFormat.h"
#include "voxelformat/CubFormat.h"
#include "voxelformat/GoxFormat.h"
#include "voxelformat/BinVoxFormat.h"
#include "voxelformat/KVXFormat.h"
#include "voxelformat/KV6Format.h"
@ -50,6 +51,7 @@ const io::FormatDescription SUPPORTED_VOXEL_FORMATS_LOAD[] = {
|| magic == FourCC('V','X','R','3') || magic == FourCC('V','X','R','2')
|| magic == FourCC('V','X','R','1');}, 0u},
{"BinVox", "binvox", [] (uint32_t magic) {return magic == FourCC('#','b','i','n');}, 0u},
{"Goxel", "gox", [] (uint32_t magic) {return magic == FourCC('G','O','X',' ');}, VOX_FORMAT_FLAG_SCREENSHOT_EMBEDDED},
{"CubeWorld", "cub", nullptr, 0u},
{"Build engine", "kvx", nullptr, VOX_FORMAT_FLAG_PALETTE_EMBEDDED},
{"Ace of Spades", "kv6", [] (uint32_t magic) {return magic == FourCC('K','v','x','l');}, VOX_FORMAT_FLAG_PALETTE_EMBEDDED},
@ -70,6 +72,7 @@ const io::FormatDescription SUPPORTED_VOXEL_FORMATS_SAVE[] = {
//{"Qubicle Project", "qbcl", nullptr, 0u},
{"Sandbox VoxEdit", "vxm", nullptr, 0u},
{"BinVox", "binvox", nullptr, 0u},
//{"Goxel", "gox", nullptr, 0u},
{"CubeWorld", "cub", nullptr, 0u},
//{"Build engine", "kvx", nullptr, 0u},
{"Tiberian Sun", "vxl", nullptr, 0u},
@ -128,6 +131,8 @@ static core::SharedPtr<voxel::Format> getFormat(const io::FormatDescription *des
format = core::make_shared<voxel::KV6Format>();
} else if (ext == "cub") {
format = core::make_shared<voxel::CubFormat>();
} else if (ext == "gox") {
format = core::make_shared<voxel::GoxFormat>();
} else if (ext == "vxm") {
format = core::make_shared<voxel::VXMFormat>();
} else if (ext == "vxr") {

View File

@ -0,0 +1,24 @@
/**
* @file
*/
#include "AbstractVoxFormatTest.h"
#include "voxelformat/GoxFormat.h"
namespace voxel {
class GoxFormatTest: public AbstractVoxFormatTest {
};
TEST_F(GoxFormatTest, testLoad) {
GoxFormat f;
std::unique_ptr<RawVolume> volume(load("test.gox", f));
ASSERT_NE(nullptr, volume) << "Could not load volume";
}
TEST_F(GoxFormatTest, DISABLED_testSaveSmallVoxel) {
GoxFormat f;
testSaveLoadVoxel("goxel-smallvolumesavetest.gox", &f);
}
}