VOXELFORMAT: added cube world load and save support

the cub file format is quite easy. The first three uint32_t values are the width,
depth and height (little endian) values of the following volume data. The volume
data itself is stored in 3 bytes for RGB
master
Martin Gerhardy 2019-10-03 10:49:43 +02:00
parent aeb4ed22de
commit 9bba5c91ac
11 changed files with 174 additions and 3 deletions

View File

@ -7,5 +7,6 @@
<glob pattern="*.qbt"/>
<glob pattern="*.vxm"/>
<glob pattern="*.binvox"/>
<glob pattern="*.cub"/>
</mime-type>
</mime-info>

BIN
data/tests/cw.cub Normal file

Binary file not shown.

View File

@ -204,7 +204,7 @@ unsigned int Color::getRGBA(const glm::vec4& color) {
}
glm::u8vec4 Color::getRGBAVec(const glm::vec4& color) {
return glm::u8vec4(static_cast<int>(color.a * magnitude), static_cast<int>(color.b * magnitude), static_cast<int>(color.g * magnitude), static_cast<int>(color.r * magnitude));
return glm::u8vec4(static_cast<int>(color.r * magnitude), static_cast<int>(color.g * magnitude), static_cast<int>(color.b * magnitude), static_cast<int>(color.a * magnitude));
}
unsigned int Color::getBGRA(const glm::vec4& color) {

View File

@ -8,6 +8,7 @@ set(SRCS
VXMFormat.h VXMFormat.cpp
MeshCache.h MeshCache.cpp
MeshExporter.h MeshExporter.cpp
CubFormat.h CubFormat.cpp
Loader.h Loader.cpp
)
engine_add_module(TARGET ${LIB} SRCS ${SRCS} DEPENDENCIES voxel assimp)
@ -18,6 +19,7 @@ set(TEST_SRCS
tests/VoxFormatTest.cpp
tests/QBTFormatTest.cpp
tests/QBFormatTest.cpp
tests/CubFormatTest.cpp
tests/VXMFormatTest.cpp
tests/MeshExporterTest.cpp
)
@ -27,6 +29,7 @@ set(TEST_FILES
tests/test.binvox
tests/magicavoxel.vox
tests/test.vxm
tests/cw.cub
)
gtest_suite_files(tests ${TEST_FILES})

View File

@ -0,0 +1,112 @@
/**
* @file
*/
#include "CubFormat.h"
#include "voxel/MaterialColor.h"
#include "core/String.h"
#include "core/Log.h"
#include "core/Color.h"
namespace voxel {
#define wrap(read) \
if (read != 0) { \
Log::error("Could not load cub file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
return VoxelVolumes(); \
}
VoxelVolumes CubFormat::loadGroups(const io::FilePtr& file) {
if (!(bool)file || !file->exists()) {
Log::error("Could not load cub file: File doesn't exist");
return VoxelVolumes();
}
io::FileStream stream(file.get());
uint32_t width, depth, height;
wrap(stream.readInt(width))
wrap(stream.readInt(depth))
wrap(stream.readInt(height))
VoxelVolumes volumes;
RawVolume *volume = new RawVolume(voxel::Region(0, 0, 0, width - 1, height - 1, depth - 1));
volumes.push_back(VoxelVolume{volume, file->fileName(), true});
// TODO: support loading own palette
const MaterialColorArray& materialColors = getMaterialColors();
for (uint32_t h = 0u; h < height; ++h) {
for (uint32_t d = 0u; d < depth; ++d) {
for (uint32_t w = 0u; w < width; ++w) {
uint8_t r, g, b;
wrap(stream.readByte(r))
wrap(stream.readByte(g))
wrap(stream.readByte(b))
if (r == 0u && g == 0u && b == 0u) {
// empty voxel
continue;
}
const glm::vec4& color = core::Color::fromRGBA(r, g, b, 255);
int index = core::Color::getClosestMatch(color, materialColors);
voxel::VoxelType voxelType = voxel::VoxelType::Generic;
if (index == 0) {
voxelType = voxel::VoxelType::Air;
}
const voxel::Voxel& voxel = voxel::createVoxel(voxelType, index);
// we have to flip depth with height for our own coordinate system
volume->setVoxel(w, h, d, voxel);
}
}
}
return volumes;
}
#undef wrap
bool CubFormat::saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) {
io::FileStream stream(file.get());
RawVolume* mergedVolume = merge(volumes);
const voxel::Region& region = mergedVolume->region();
RawVolume::Sampler sampler(mergedVolume);
const glm::ivec3& lower = region.getLowerCorner();
const MaterialColorArray& materialColors = getMaterialColors();
const uint32_t width = region.getWidthInVoxels();
const uint32_t height = region.getHeightInVoxels();
const uint32_t depth = region.getDepthInVoxels();
// we have to flip depth with height for our own coordinate system
stream.addInt(width);
stream.addInt(depth);
stream.addInt(height);
for (uint32_t y = 0u; y < height; ++y) {
for (uint32_t z = 0u; z < depth; ++z) {
for (uint32_t x = 0u; x < width; ++x) {
sampler.setPosition(lower.x + x, lower.y + y, lower.z + z);
const voxel::Voxel& voxel = sampler.voxel();
if (voxel.getMaterial() == VoxelType::Air) {
stream.addByte(0);
stream.addByte(0);
stream.addByte(0);
continue;
}
const glm::vec4& color = materialColors[voxel.getColor()];
const glm::u8vec4& rgba = core::Color::getRGBAVec(color);
stream.addByte(rgba.r);
stream.addByte(rgba.g);
stream.addByte(rgba.b);
}
}
}
delete mergedVolume;
return true;
}
}

View File

@ -0,0 +1,26 @@
/**
* @file
*/
#pragma once
#include "VoxFileFormat.h"
#include "io/FileStream.h"
#include "io/File.h"
#include <string>
namespace voxel {
/**
* @brief CubeWorld cub format
*
* The first 12 bytes of the file are the width, depth and height of the volume (uint32_t little endian).
* The remaining parts are the RGB values (3 bytes)
*/
class CubFormat : public VoxFileFormat {
public:
VoxelVolumes loadGroups(const io::FilePtr& file) override;
bool saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) override;
};
}

View File

@ -8,6 +8,7 @@
#include "QBTFormat.h"
#include "QBFormat.h"
#include "VXMFormat.h"
#include "CubFormat.h"
#include "BinVoxFormat.h"
namespace voxelformat {
@ -27,6 +28,9 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
} else if (ext == "qb") {
voxel::QBFormat f;
newVolumes = f.loadGroups(filePtr);
} else if (ext == "cub") {
voxel::CubFormat f;
newVolumes = f.loadGroups(filePtr);
} else if (ext == "vxm") {
voxel::VXMFormat f;
newVolumes = f.loadGroups(filePtr);

View File

@ -40,6 +40,9 @@ RawVolume* VoxFileFormat::merge(const VoxelVolumes& volumes) const {
if (volumes.empty()) {
return nullptr;
}
if (volumes.size() == 1) {
return new RawVolume(volumes[0].volume);
}
std::vector<const RawVolume*> rawVolumes;
rawVolumes.reserve(volumes.size());
for (const auto& v : volumes) {

View File

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

View File

@ -25,8 +25,8 @@
namespace voxedit {
static const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb,vxm,binvox";
static const char *SUPPORTED_VOXEL_FORMATS_SAVE = "vox,qbt,qb";
static const char *SUPPORTED_VOXEL_FORMATS_LOAD = "vox,qbt,qb,vxm,binvox,cub";
static const char *SUPPORTED_VOXEL_FORMATS_SAVE = "vox,qbt,qb,cub";
static const struct {
const char *name;

View File

@ -25,6 +25,7 @@
#include "voxelformat/Loader.h"
#include "voxelformat/VoxFormat.h"
#include "voxelformat/QBTFormat.h"
#include "voxelformat/CubFormat.h"
#include "voxelformat/QBFormat.h"
#include "voxelformat/VXMFormat.h"
#include "video/ScopedPolygonMode.h"
@ -327,6 +328,9 @@ bool SceneManager::save(const std::string& file, bool autosave) {
} else if (ext == "qb") {
voxel::QBFormat f;
saved = f.saveGroups(volumes, filePtr);
} else if (ext == "cub") {
voxel::CubFormat f;
saved = f.saveGroups(volumes, filePtr);
} else {
Log::warn("Failed to save file with unknown type: %s - saving as vox instead", ext.c_str());
voxel::VoxFormat f;