VOXELFORMAT: added gox format reading support
parent
21034b3179
commit
7deedf0e36
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ®ion = 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 ®ion = 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
|
|
@ -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
|
|
@ -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") {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue