openspades/Sources/Core/VoxelModel.cpp
2016-12-03 18:23:47 +09:00

273 lines
7.1 KiB
C++

/*
Copyright (c) 2013 yvt
This file is part of OpenSpades.
OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cstring>
#include <vector>
#include "Debug.h"
#include "Exception.h"
#include "FileManager.h"
#include "VoxelModel.h"
#include <ScriptBindings/ScriptManager.h>
namespace spades {
VoxelModel::VoxelModel(int w, int h, int d) {
SPADES_MARK_FUNCTION();
if (w < 1 || h < 1 || d < 1 || w > 4096 || h > 4096)
SPRaise("Invalid dimension: %dx%dx%d", w, h, d);
width = w;
height = h;
depth = d;
if (d > 64) {
SPRaise("Voxel model with depth > 64 is not supported.");
}
solidBits = new uint64_t[w * h];
colors = new uint32_t[w * h * d];
std::fill(solidBits, solidBits + w * h, 0);
}
VoxelModel::~VoxelModel() {
SPADES_MARK_FUNCTION();
delete[] solidBits;
delete[] colors;
}
struct KV6Block {
uint32_t color;
uint16_t zPos;
uint8_t visFaces;
uint8_t lighting;
};
struct KV6Header {
uint32_t xsiz, ysiz, zsiz;
float xpivot, ypivot, zpivot;
uint32_t blklen;
};
static uint32_t swapColor(uint32_t col) {
union {
uint8_t bytes[4];
uint32_t c;
} u;
u.c = col;
std::swap(u.bytes[0], u.bytes[2]);
return (u.c & 0xffffff);
}
void VoxelModel::HollowFill() {
std::vector<IntVector3> stack;
std::vector<uint8_t> flags;
flags.resize(width * height * depth);
std::memset(flags.data(), 0, flags.size());
stack.reserve(width * height * depth);
auto Flag = [&](int x, int y, int z) -> uint8_t & {
return flags[x + width * (y + height * z)];
};
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++) {
auto m = GetSolidBitsAt(x, y);
for (int z = 0; z < depth; z++) {
if (m & (1ULL << z)) {
Flag(x, y, z) = 1;
}
}
}
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++) {
if (!IsSolid(x, y, 0)) {
stack.push_back(IntVector3::Make(x, y, 0));
Flag(x, y, 0) = 1;
}
if (!IsSolid(x, y, depth - 1)) {
stack.push_back(IntVector3::Make(x, y, depth - 1));
Flag(x, y, depth - 1) = 1;
}
}
for (int x = 0; x < width; x++)
for (int z = 1; z < depth - 1; z++) {
if (!IsSolid(x, 0, z)) {
stack.push_back(IntVector3::Make(x, 0, z));
Flag(x, 0, z) = 1;
}
if (!IsSolid(x, height - 1, z)) {
stack.push_back(IntVector3::Make(x, height - 1, z));
Flag(x, height - 1, z) = 1;
}
}
for (int y = 1; y < height - 1; y++)
for (int z = 1; z < depth - 1; z++) {
if (!IsSolid(0, y, z)) {
stack.push_back(IntVector3::Make(0, y, z));
Flag(0, y, z) = 1;
}
if (!IsSolid(width - 1, y, z)) {
stack.push_back(IntVector3::Make(width - 1, y, z));
Flag(width - 1, y, z) = 1;
}
}
while (!stack.empty()) {
auto v = stack.back();
stack.pop_back();
auto Visit = [&](int x, int y, int z) {
SPAssert(x >= 0);
SPAssert(x < width);
SPAssert(y >= 0);
SPAssert(y < height);
SPAssert(z >= 0);
SPAssert(z < depth);
Flag(x, y, z) = 1;
stack.push_back(IntVector3::Make(x, y, z));
};
if (v.x > 0 && !Flag(v.x - 1, v.y, v.z))
Visit(v.x - 1, v.y, v.z);
if (v.x < width - 1 && !Flag(v.x + 1, v.y, v.z))
Visit(v.x + 1, v.y, v.z);
if (v.y > 0 && !Flag(v.x, v.y - 1, v.z))
Visit(v.x, v.y - 1, v.z);
if (v.y < height - 1 && !Flag(v.x, v.y + 1, v.z))
Visit(v.x, v.y + 1, v.z);
if (v.z > 0 && !Flag(v.x, v.y, v.z - 1))
Visit(v.x, v.y, v.z - 1);
if (v.z < depth - 1 && !Flag(v.x, v.y, v.z + 1))
Visit(v.x, v.y, v.z + 1);
}
for (int z = 0, idx = 0; z < depth; z++)
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++, idx++) {
if (!flags[idx]) {
SPAssert(!IsSolid(x, y, z));
SetSolid(x, y, z, 0xddbeef);
}
}
for (int z = 0, idx = 0; z < depth; z++)
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++, idx++) {
if (!flags[idx]) {
SPAssert(IsSolid(x, y, z));
SPAssert(IsSolid(x + 1, y, z));
SPAssert(IsSolid(x - 1, y, z));
SPAssert(IsSolid(x, y + 1, z));
SPAssert(IsSolid(x, y - 1, z));
SPAssert(IsSolid(x, y, z + 1));
SPAssert(IsSolid(x, y, z - 1));
}
}
}
VoxelModel *VoxelModel::LoadKV6(spades::IStream *stream) {
SPADES_MARK_FUNCTION();
if (stream->Read(4) != "Kvxl") {
SPRaise("Invalid magic");
}
KV6Header header;
if (stream->Read(&header, sizeof(header)) < sizeof(header)) {
SPRaise("File truncated: failed to read header");
}
std::vector<KV6Block> blkdata;
blkdata.resize(header.blklen);
if (stream->Read(blkdata.data(), sizeof(KV6Block) * header.blklen) <
sizeof(KV6Block) * header.blklen) {
SPRaise("File truncated: failed to read blocks");
}
std::vector<uint32_t> xoffset;
xoffset.resize(header.xsiz);
if (stream->Read(xoffset.data(), sizeof(uint32_t) * header.xsiz) <
sizeof(uint32_t) * header.xsiz) {
SPRaise("File truncated: failed to read xoffset");
}
std::vector<uint16_t> xyoffset;
xyoffset.resize(header.xsiz * header.ysiz);
if (stream->Read(xyoffset.data(), sizeof(uint16_t) * header.xsiz * header.ysiz) <
sizeof(uint16_t) * header.xsiz * header.ysiz) {
SPRaise("File truncated: failed to read xyoffset");
}
// validate: zpos < depth
for (size_t i = 0; i < blkdata.size(); i++)
if (blkdata[i].zPos >= header.zsiz)
SPRaise("File corrupted: blkData[i].zPos >= header.zsiz");
// validate sum(xyoffset) = blkLen
{
uint64_t ttl = 0;
for (size_t i = 0; i < xyoffset.size(); i++) {
ttl += (uint32_t)xyoffset[i];
}
if (ttl != (uint64_t)blkdata.size())
SPRaise("File corrupted: sum(xyoffset) != blkdata.size()");
}
VoxelModel *model = new VoxelModel(header.xsiz, header.ysiz, header.zsiz);
model->SetOrigin(MakeVector3(-header.xpivot, -header.ypivot, -header.zpivot));
try {
int pos = 0;
for (int x = 0; x < (int)header.xsiz; x++)
for (int y = 0; y < (int)header.ysiz; y++) {
int spanBlocks = (int)xyoffset[x * header.ysiz + y];
int lastZ = -1;
// printf("pos: %d, (%d, %d): %d\n",
// pos, x, y, spanBlocks);
while (spanBlocks--) {
const KV6Block &b = blkdata[pos];
// printf("%d, %d, %d: %d, %d\n", x, y, b.zPos,
// b.visFaces, b.lighting);
if (model->IsSolid(x, y, b.zPos)) {
SPRaise("Duplicate voxel (%d, %d, %d)", x, y, b.zPos);
}
if (b.zPos <= lastZ) {
SPRaise("Not Z-sorted");
}
lastZ = b.zPos;
model->SetSolid(x, y, b.zPos, swapColor(b.color));
pos++;
}
}
SPAssert(pos == blkdata.size());
model->HollowFill();
return model;
} catch (...) {
delete model;
throw;
}
}
}