VOXEL: introduced qubicle binary importer/exporter
parent
90d6ee1082
commit
10e2ef1174
|
@ -3,6 +3,7 @@ set(SRCS
|
|||
model/VoxFileFormat.h model/VoxFileFormat.cpp
|
||||
model/VoxFormat.h model/VoxFormat.cpp
|
||||
model/QBTFormat.h model/QBTFormat.cpp
|
||||
model/QBFormat.h model/QBFormat.cpp
|
||||
model/MeshExporter.h model/MeshExporter.cpp
|
||||
font/VoxelFont.h font/VoxelFont.cpp
|
||||
BiomeManager.h BiomeManager.cpp
|
||||
|
@ -53,7 +54,7 @@ target_compile_options(${LIB} PRIVATE -march=native PRIVATE -O3)
|
|||
|
||||
gtest_suite_files(tests
|
||||
tests/AbstractVoxelTest.h
|
||||
tests/AbstractVoxFormatTest.h
|
||||
tests/AbstractVoxFormatTest.h tests/AbstractVoxFormatTest.cpp
|
||||
tests/WorldTest.cpp
|
||||
tests/WorldPersisterTest.cpp
|
||||
tests/LSystemGeneratorTest.cpp
|
||||
|
@ -63,6 +64,7 @@ gtest_suite_files(tests
|
|||
tests/AmbientOcclusionTest.cpp
|
||||
tests/VoxFormatTest.cpp
|
||||
tests/QBTFormatTest.cpp
|
||||
tests/QBFormatTest.cpp
|
||||
tests/MeshExporterTest.cpp
|
||||
tests/VolumeMergerTest.cpp
|
||||
tests/VolumeRotatorTest.cpp
|
||||
|
|
|
@ -0,0 +1,386 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "QBFormat.h"
|
||||
#include "voxel/polyvox/VolumeMerger.h"
|
||||
#include "core/Common.h"
|
||||
#include "core/Zip.h"
|
||||
#include "core/Color.h"
|
||||
|
||||
namespace voxel {
|
||||
|
||||
namespace {
|
||||
const int RLE_FLAG = 2;
|
||||
const int NEXT_SLICE_FLAG = 6;
|
||||
}
|
||||
|
||||
#define wrapSave(write) \
|
||||
if (write == false) { \
|
||||
Log::error("Could not save qb file: " CORE_STRINGIFY(write) " failed"); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define wrap(read) \
|
||||
if (read != 0) { \
|
||||
Log::error("Could not load qb file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define wrapBool(read) \
|
||||
if (read == false) { \
|
||||
Log::error("Could not load qb file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define wrapColor(read) \
|
||||
if (read != 0) { \
|
||||
Log::error("Could not load qb file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
|
||||
return voxel::VoxelType::Invalid; \
|
||||
}
|
||||
|
||||
#define setBit(val, index) val &= (1 << (index))
|
||||
|
||||
bool QBFormat::save(const RawVolume* volume, const io::FilePtr& file) {
|
||||
io::FileStream stream(file.get());
|
||||
wrapSave(stream.addInt(257))
|
||||
ColorFormat colorFormat = ColorFormat::RGBA;
|
||||
ZAxisOrientation zAxisOrientation = ZAxisOrientation::Right;
|
||||
Compression compression = Compression::RLE;
|
||||
VisibilityMask visibilityMask = VisibilityMask::AlphaChannelVisibleByValue;
|
||||
wrapSave(stream.addInt((uint32_t)colorFormat))
|
||||
wrapSave(stream.addInt((uint32_t)zAxisOrientation))
|
||||
wrapSave(stream.addInt((uint32_t)compression))
|
||||
wrapSave(stream.addInt((uint32_t)visibilityMask))
|
||||
wrapSave(stream.addInt(1))
|
||||
wrapSave(stream.addByte(0)); // no name
|
||||
|
||||
const voxel::Region& region = volume->getRegion();
|
||||
const glm::ivec3 size = region.getDimensionsInVoxels();
|
||||
wrapSave(stream.addInt(size.x));
|
||||
wrapSave(stream.addInt(size.y));
|
||||
wrapSave(stream.addInt(size.z));
|
||||
|
||||
const int offset = 0;
|
||||
wrapSave(stream.addInt(offset));
|
||||
wrapSave(stream.addInt(offset));
|
||||
wrapSave(stream.addInt(offset));
|
||||
|
||||
int axisIndex1;
|
||||
int axisIndex2;
|
||||
if (zAxisOrientation == ZAxisOrientation::Right) {
|
||||
axisIndex1 = 0;
|
||||
axisIndex2 = 2;
|
||||
} else {
|
||||
axisIndex1 = 2;
|
||||
axisIndex2 = 0;
|
||||
}
|
||||
|
||||
const voxel::Voxel Empty = voxel::createVoxel(voxel::VoxelType::Air);
|
||||
const int32_t EmptyColor = core::Color::GetRGB(getColor(Empty));
|
||||
|
||||
const glm::ivec3& mins = region.getLowerCorner();
|
||||
const glm::ivec3& maxs = region.getUpperCorner();
|
||||
|
||||
int32_t currentColor = EmptyColor;
|
||||
int count = 0;
|
||||
|
||||
for (int axis1 = mins[axisIndex2]; axis1 <= maxs[axisIndex2]; ++axis1) {
|
||||
for (int y = maxs[1]; y >= mins[1]; --y) {
|
||||
for (int axis2 = mins[axisIndex1]; axis2 <= maxs[axisIndex1]; ++axis2) {
|
||||
int x, z;
|
||||
const bool rightHanded = zAxisOrientation == ZAxisOrientation::Right;
|
||||
if (rightHanded) {
|
||||
x = axis2;
|
||||
z = axis1;
|
||||
} else {
|
||||
x = axis1;
|
||||
z = axis2;
|
||||
}
|
||||
const Voxel& voxel = volume->getVoxel(x, y, z);
|
||||
int32_t newColor;
|
||||
if (voxel == Empty) {
|
||||
newColor = EmptyColor;
|
||||
} else {
|
||||
uint8_t visible;
|
||||
if (visibilityMask == VisibilityMask::AlphaChannelVisibleSidesEncoded) {
|
||||
// TODO: this looks wrong - use the Sides enum
|
||||
voxel::RawVolume::Sampler sampler(volume);
|
||||
sampler.setPosition(x, y, z);
|
||||
visible = 0;
|
||||
if (sampler.peekVoxel0px0py1pz() == Empty) {
|
||||
setBit(visible, rightHanded ? 1 : 6);
|
||||
}
|
||||
if (sampler.peekVoxel0px0py1nz() == Empty) {
|
||||
setBit(visible, rightHanded ? 2 : 5);
|
||||
}
|
||||
if (sampler.peekVoxel0px1py0pz() == Empty) {
|
||||
setBit(visible, 3);
|
||||
}
|
||||
if (sampler.peekVoxel0px1ny0pz() == Empty) {
|
||||
setBit(visible, 4);
|
||||
}
|
||||
if (sampler.peekVoxel1nx0py0pz() == Empty) {
|
||||
setBit(visible, rightHanded ? 5 : 1);
|
||||
}
|
||||
if (sampler.peekVoxel1px0py0pz() == Empty) {
|
||||
setBit(visible, rightHanded ? 6 : 2);
|
||||
}
|
||||
} else {
|
||||
visible = 255;
|
||||
}
|
||||
const int32_t voxelColor = core::Color::GetRGBA(getColor(voxel));
|
||||
const uint8_t red = (voxelColor >> 24) & 0xFF;
|
||||
const uint8_t green = (voxelColor >> 16) & 0xFF;
|
||||
const uint8_t blue = (voxelColor >> 8) & 0xFF;
|
||||
if (colorFormat == ColorFormat::RGBA) {
|
||||
newColor = ((uint32_t)red) << 24 | ((uint32_t)green) << 16 | ((uint32_t)blue) << 8 | ((uint32_t)visible) << 0;
|
||||
} else {
|
||||
newColor = ((uint32_t)blue) << 24 | ((uint32_t)green) << 16 | ((uint32_t)red) << 8 | ((uint32_t)visible) << 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (compression == Compression::RLE) {
|
||||
if (newColor != currentColor) {
|
||||
if (count == 1) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count == 2) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count == 3) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count > 3) {
|
||||
wrapSave(stream.addInt(RLE_FLAG))
|
||||
wrapSave(stream.addInt(count))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
}
|
||||
count = 0;
|
||||
}
|
||||
currentColor = newColor;
|
||||
count++;
|
||||
} else {
|
||||
wrapSave(stream.addInt(newColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compression == Compression::RLE) {
|
||||
if (count == 1) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count == 2) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count == 3) {
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
} else if (count > 3) {
|
||||
wrapSave(stream.addInt(RLE_FLAG))
|
||||
wrapSave(stream.addInt(count))
|
||||
wrapSave(stream.addInt(currentColor))
|
||||
}
|
||||
count = 0;
|
||||
wrapSave(stream.addInt(NEXT_SLICE_FLAG));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QBFormat::setVoxel(voxel::RawVolume* volume, uint32_t x, uint32_t y, uint32_t z, const glm::ivec3& offset, voxel::VoxelType type) {
|
||||
const int32_t fx = offset.x + x;
|
||||
const int32_t fy = offset.y + y;
|
||||
const int32_t fz = offset.z + z;
|
||||
Log::debug("Set voxel %i to %i:%i:%i", (int)type, fx, fy, fz);
|
||||
if (_zAxisOrientation == ZAxisOrientation::Right) {
|
||||
volume->setVoxel(fx, fy, fz, createVoxel(type));
|
||||
} else {
|
||||
volume->setVoxel(fz, fy, fx, createVoxel(type));
|
||||
}
|
||||
}
|
||||
|
||||
voxel::VoxelType QBFormat::getVoxelType(io::FileStream& stream) {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t alpha;
|
||||
wrapColor(stream.readByte(red))
|
||||
wrapColor(stream.readByte(green))
|
||||
wrapColor(stream.readByte(blue))
|
||||
wrapColor(stream.readByte(alpha))
|
||||
Log::debug("Red: %i, Green: %i, Blue: %i, Alpha: %i", (int)red, (int)green, (int)blue, (int)alpha);
|
||||
if (alpha == 0) {
|
||||
return voxel::VoxelType::Air;
|
||||
}
|
||||
glm::vec4 color;
|
||||
if (_colorFormat == ColorFormat::RGBA) {
|
||||
color = core::Color::FromRGBA(((uint32_t)red) << 24 | ((uint32_t)green) << 16 | ((uint32_t)blue) << 8 | ((uint32_t)255) << 0);
|
||||
} else {
|
||||
color = core::Color::FromRGBA(((uint32_t)blue) << 24 | ((uint32_t)green) << 16 | ((uint32_t)red) << 8 | ((uint32_t)255) << 0);
|
||||
}
|
||||
const glm::vec4& finalColor = findClosestMatch(color);
|
||||
const voxel::VoxelType type = findVoxelType(finalColor);
|
||||
return type;
|
||||
}
|
||||
|
||||
voxel::RawVolume* QBFormat::loadMatrix(io::FileStream& stream) {
|
||||
char buf[260] = "";
|
||||
uint8_t nameLength;
|
||||
wrap(stream.readByte(nameLength));
|
||||
Log::debug("Matrix name length: %u", (uint32_t)nameLength);
|
||||
wrapBool(stream.readString(nameLength, buf));
|
||||
buf[nameLength] = '\0';
|
||||
Log::debug("Matrix name: %s", buf);
|
||||
|
||||
glm::uvec3 size(glm::uninitialize);
|
||||
wrap(stream.readInt(size.x));
|
||||
wrap(stream.readInt(size.y));
|
||||
wrap(stream.readInt(size.z));
|
||||
Log::debug("Matrix size: %i:%i:%i", size.x, size.y, size.z);
|
||||
|
||||
if (size.x == 0 || size.y == 0 || size.z == 0) {
|
||||
Log::error("Invalid size");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glm::ivec3 offset(glm::uninitialize);
|
||||
wrap(stream.readInt((uint32_t&)offset.x));
|
||||
wrap(stream.readInt((uint32_t&)offset.y));
|
||||
wrap(stream.readInt((uint32_t&)offset.z));
|
||||
Log::debug("Matrix offset: %i:%i:%i", offset.x, offset.y, offset.z);
|
||||
|
||||
voxel::Region region;
|
||||
const glm::ivec3 maxs(offset.x + size.x, offset.y + size.y, offset.z + size.z);
|
||||
if (_zAxisOrientation == ZAxisOrientation::Right) {
|
||||
region = voxel::Region(offset.x, offset.y, offset.z, maxs.x, maxs.y, maxs.z);
|
||||
} else {
|
||||
region = voxel::Region(offset.z, offset.y, offset.x, maxs.z, maxs.y, maxs.x);
|
||||
}
|
||||
core_assert(region.getDimensionsInCells() == glm::ivec3(size));
|
||||
if (!region.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
voxel::RawVolume* volume = new voxel::RawVolume(region);
|
||||
if (_compressed == Compression::None) {
|
||||
Log::debug("qb matrix uncompressed");
|
||||
for (uint32_t z = 0; z < size.z; ++z) {
|
||||
for (uint32_t y = 0; y < size.y; ++y) {
|
||||
for (uint32_t x = 0; x < size.x; ++x) {
|
||||
const voxel::VoxelType type = getVoxelType(stream);
|
||||
if (type == voxel::VoxelType::Invalid) {
|
||||
return nullptr;
|
||||
}
|
||||
setVoxel(volume, x, y, z, offset, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return volume;
|
||||
}
|
||||
|
||||
Log::debug("Matrix rle compressed");
|
||||
|
||||
uint32_t z = 0u;
|
||||
while (z < size.z) {
|
||||
int index = -1;
|
||||
for (;;) {
|
||||
uint32_t data;
|
||||
wrap(stream.peekInt(data))
|
||||
if (data == NEXT_SLICE_FLAG) {
|
||||
stream.skip(sizeof(data));
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t count = 1;
|
||||
if (data == RLE_FLAG) {
|
||||
stream.skip(sizeof(data));
|
||||
wrap(stream.readInt(count))
|
||||
Log::debug("%u voxels of the same type", count);
|
||||
}
|
||||
|
||||
const voxel::VoxelType type = getVoxelType(stream);
|
||||
if (type == voxel::VoxelType::Invalid) {
|
||||
return nullptr;
|
||||
}
|
||||
for (uint32_t j = 0; j < count; ++j, ++index) {
|
||||
const int x = (index + 1) % size.x;
|
||||
const int y = (index + 1) / size.x;
|
||||
setVoxel(volume, x, y, z, offset, type);
|
||||
}
|
||||
}
|
||||
++z;
|
||||
}
|
||||
Log::debug("Matrix read");
|
||||
return volume;
|
||||
}
|
||||
|
||||
RawVolume* QBFormat::loadFromStream(io::FileStream& stream) {
|
||||
wrap(stream.readInt(_version))
|
||||
wrap(stream.readInt((uint32_t&)_colorFormat))
|
||||
wrap(stream.readInt((uint32_t&)_zAxisOrientation))
|
||||
wrap(stream.readInt((uint32_t&)_compressed))
|
||||
wrap(stream.readInt((uint32_t&)_visibilityMaskEncoded))
|
||||
|
||||
uint32_t numMatrices;
|
||||
wrap(stream.readInt(numMatrices))
|
||||
|
||||
Log::debug("Version: %u", _version);
|
||||
Log::debug("ColorFormat: %u", std::enum_value(_colorFormat));
|
||||
Log::debug("ZAxisOrientation: %u", std::enum_value(_zAxisOrientation));
|
||||
Log::debug("Compressed: %u", std::enum_value(_compressed));
|
||||
Log::debug("VisibilityMaskEncoded: %u", std::enum_value(_visibilityMaskEncoded));
|
||||
Log::debug("NumMatrices: %u", numMatrices);
|
||||
|
||||
glm::ivec3 mins = glm::ivec3(std::numeric_limits<int32_t>::max());
|
||||
glm::ivec3 maxs = glm::ivec3(std::numeric_limits<int32_t>::min());
|
||||
std::vector<voxel::RawVolume*> volumes;
|
||||
volumes.reserve(numMatrices);
|
||||
for (uint32_t i = 0; i < numMatrices; i++) {
|
||||
Log::debug("Loading matrix: %u", i);
|
||||
voxel::RawVolume* v = loadMatrix(stream);
|
||||
if (v == nullptr) {
|
||||
break;
|
||||
}
|
||||
const voxel::Region& region = v->getRegion();
|
||||
mins = glm::min(mins, region.getLowerCorner());
|
||||
maxs = glm::max(maxs, region.getUpperCorner());
|
||||
volumes.push_back(v);
|
||||
}
|
||||
if (volumes.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const voxel::Region mergedRegion(glm::ivec3(0), maxs - mins);
|
||||
Log::debug("Starting to merge volumes into one: %i:%i:%i - %i:%i:%i",
|
||||
mergedRegion.getLowerX(), mergedRegion.getLowerY(), mergedRegion.getLowerZ(),
|
||||
mergedRegion.getUpperX(), mergedRegion.getUpperY(), mergedRegion.getUpperZ());
|
||||
Log::debug("Mins: %i:%i:%i Maxs %i:%i:%i", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z);
|
||||
voxel::RawVolume* merged = new voxel::RawVolume(mergedRegion);
|
||||
const glm::ivec3& center = mergedRegion.getCentre();
|
||||
const glm::ivec3 lc(center.x, 0, center.z);
|
||||
const glm::ivec3 uc(center.x, mergedRegion.getUpperY(), center.z);
|
||||
for (voxel::RawVolume* v : volumes) {
|
||||
const voxel::Region& sr = v->getRegion();
|
||||
const glm::ivec3& destMins = lc + sr.getLowerCorner();
|
||||
const voxel::Region dr(destMins, destMins + sr.getDimensionsInCells());
|
||||
Log::debug("Merge %i:%i:%i - %i:%i:%i into %i:%i:%i - %i:%i:%i",
|
||||
sr.getLowerX(), sr.getLowerY(), sr.getLowerZ(),
|
||||
sr.getUpperX(), sr.getUpperY(), sr.getUpperZ(),
|
||||
dr.getLowerX(), dr.getLowerY(), dr.getLowerZ(),
|
||||
dr.getUpperX(), dr.getUpperY(), dr.getUpperZ());
|
||||
voxel::mergeRawVolumes(merged, v, dr, sr);
|
||||
delete v;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
RawVolume* QBFormat::load(const io::FilePtr& file) {
|
||||
if (!(bool)file || !file->exists()) {
|
||||
Log::error("Could not load qb file: File doesn't exist");
|
||||
return nullptr;
|
||||
}
|
||||
io::FileStream stream(file.get());
|
||||
voxel::RawVolume* volume = loadFromStream(stream);
|
||||
return volume;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VoxFileFormat.h"
|
||||
#include "io/FileStream.h"
|
||||
|
||||
namespace voxel {
|
||||
|
||||
/**
|
||||
* @brief Qubicle Binary (qb) format.
|
||||
*
|
||||
* http://minddesk.com/learn/article.php?id=22
|
||||
*/
|
||||
class QBFormat : public VoxFileFormat {
|
||||
private:
|
||||
uint32_t _version;
|
||||
enum class ColorFormat : uint32_t {
|
||||
RGBA = 0,
|
||||
BGRA = 1
|
||||
};
|
||||
ColorFormat _colorFormat;
|
||||
enum class ZAxisOrientation : uint32_t {
|
||||
Left = 0,
|
||||
Right = 1
|
||||
};
|
||||
ZAxisOrientation _zAxisOrientation;
|
||||
enum class Compression : uint32_t {
|
||||
None = 0,
|
||||
RLE = 1
|
||||
};
|
||||
Compression _compressed;
|
||||
|
||||
// If set to 0 the A value of RGBA or BGRA is either 0 (invisble voxel) or 255 (visible voxel).
|
||||
// If set to 1 the visibility mask of each voxel is encoded into the A value telling your software
|
||||
// which sides of the voxel are visible. You can save a lot of render time using this option.
|
||||
enum class VisibilityMask : uint32_t {
|
||||
AlphaChannelVisibleByValue,
|
||||
AlphaChannelVisibleSidesEncoded
|
||||
};
|
||||
VisibilityMask _visibilityMaskEncoded;
|
||||
// left shift values for the vis mask for the single faces
|
||||
enum class VisMaskSides : uint8_t {
|
||||
Invisble,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
Front,
|
||||
Back
|
||||
};
|
||||
|
||||
void setVoxel(voxel::RawVolume* volume, uint32_t x, uint32_t y, uint32_t z, const glm::ivec3& offset, voxel::VoxelType type);
|
||||
voxel::VoxelType getVoxelType(io::FileStream& stream);
|
||||
RawVolume* loadMatrix(io::FileStream& stream);
|
||||
RawVolume* loadFromStream(io::FileStream& stream);
|
||||
public:
|
||||
RawVolume* load(const io::FilePtr& file) override;
|
||||
bool save(const RawVolume* volume, const io::FilePtr& file) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#include "AbstractVoxFormatTest.h"
|
||||
|
||||
namespace voxel {
|
||||
|
||||
const voxel::Voxel AbstractVoxFormatTest::Empty = voxel::createVoxel(voxel::VoxelType::Air);
|
||||
|
||||
}
|
|
@ -9,6 +9,8 @@ namespace voxel {
|
|||
|
||||
class AbstractVoxFormatTest: public AbstractVoxelTest {
|
||||
protected:
|
||||
static const voxel::Voxel Empty;
|
||||
|
||||
io::FilePtr open(const std::string_view filename, io::FileMode mode = io::FileMode::Read) {
|
||||
const io::FilePtr& file = core::App::getInstance()->filesystem()->open(std::string(filename), mode);
|
||||
return file;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "AbstractVoxFormatTest.h"
|
||||
#include "voxel/model/QBFormat.h"
|
||||
|
||||
namespace voxel {
|
||||
|
||||
class QBFormatTest: public AbstractVoxFormatTest {
|
||||
};
|
||||
|
||||
TEST_F(QBFormatTest, testLoad) {
|
||||
QBFormat f;
|
||||
RawVolume* volume = load("qubicle.qb", f);
|
||||
// feets
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 0, 1));
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 0, 2));
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 0, 3));
|
||||
EXPECT_EQ(Empty, volume->getVoxel(18, 0, 4));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 0, 1));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 0, 2));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 0, 3));
|
||||
EXPECT_EQ(Empty, volume->getVoxel(22, 0, 4));
|
||||
|
||||
// legs
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 1, 3));
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 2, 3));
|
||||
EXPECT_NE(Empty, volume->getVoxel(18, 3, 3));
|
||||
EXPECT_EQ(Empty, volume->getVoxel(18, 4, 3));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 1, 3));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 2, 3));
|
||||
EXPECT_NE(Empty, volume->getVoxel(22, 3, 3));
|
||||
EXPECT_EQ(Empty, volume->getVoxel(22, 4, 3));
|
||||
|
||||
ASSERT_NE(nullptr, volume) << "Could not load qb file";
|
||||
delete volume;
|
||||
}
|
||||
|
||||
TEST_F(QBFormatTest, testSave) {
|
||||
QBFormat f;
|
||||
RawVolume* volume = load("qubicle.qb", f);
|
||||
ASSERT_NE(nullptr, volume);
|
||||
ASSERT_TRUE(f.save(volume, open("qubicle-savetest.qb", io::FileMode::Write)));
|
||||
ASSERT_TRUE(open("qubicle-savetest.qb")->length() > 177);
|
||||
delete volume;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
#include "voxel/generator/WorldGenerator.h"
|
||||
#include "voxel/model/VoxFormat.h"
|
||||
#include "voxel/model/QBTFormat.h"
|
||||
#include "voxel/model/QBFormat.h"
|
||||
#include "tool/Crop.h"
|
||||
#include "tool/Expand.h"
|
||||
#include "core/Random.h"
|
||||
|
@ -40,6 +41,12 @@ bool Model::save(std::string_view file) {
|
|||
_dirty = false;
|
||||
return true;
|
||||
}
|
||||
} else if (filePtr->extension() == "qb") {
|
||||
voxel::QBFormat f;
|
||||
if (f.save(modelVolume(), filePtr)) {
|
||||
_dirty = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -58,6 +65,9 @@ bool Model::load(std::string_view file) {
|
|||
} else if (filePtr->extension() == "vox") {
|
||||
voxel::VoxFormat f;
|
||||
newVolume = f.load(filePtr);
|
||||
} else if (filePtr->extension() == "qb") {
|
||||
voxel::QBFormat f;
|
||||
newVolume = f.load(filePtr);
|
||||
} else {
|
||||
newVolume = nullptr;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue