This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
+797
View File
@@ -0,0 +1,797 @@
#include "stdafx.h"
#include "voxel2/Grid.h"
#include "voxel2/GridListener.h"
#include "voxel/Util.h"
#include "rbx/Profiler.h"
namespace RBX { namespace Voxel2 {
const unsigned int kChunkSizeLog2 = 5;
const unsigned int kChunkSize = 1 << kChunkSizeLog2;
static bool hasSolidCells(const Cell* row, int size)
{
for (int i = 0; i < size; ++i)
if (row[i].getMaterial() != Cell::Material_Air)
return true;
return false;
}
static bool hasSolidCells(const Box& box)
{
if (box.isEmpty())
return false;
Vector3int32 size = box.getSize();
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
if (hasSolidCells(box.readRow(0, y, z), size.x))
return true;
return false;
}
static bool copyCells(Box& targetBox, const Region& targetRegion, const Box& sourceBox, const Region& sourceRegion)
{
Region region = sourceRegion.intersect(targetRegion);
if (region.empty())
return false;
if (sourceBox.isEmpty() && targetBox.isEmpty())
return false;
Vector3int32 sourceOffset = region.begin() - sourceRegion.begin();
Vector3int32 targetOffset = region.begin() - targetRegion.begin();
Vector3int32 size = region.size();
bool dirty = false;
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
if (sourceBox.isEmpty())
{
Cell* targetRow = targetBox.writeRow(targetOffset.x, targetOffset.y + y, targetOffset.z + z);
if (dirty || hasSolidCells(targetRow, size.x))
{
memset(targetRow, 0, size.x * sizeof(Cell));
dirty = true;
}
}
else
{
const Cell* sourceRow = sourceBox.readRow(sourceOffset.x, sourceOffset.y + y, sourceOffset.z + z);
if (!targetBox.isEmpty() || hasSolidCells(sourceRow, size.x))
{
Cell* targetRow = targetBox.writeRow(targetOffset.x, targetOffset.y + y, targetOffset.z + z);
if (dirty || memcmp(targetRow, sourceRow, size.x * sizeof(Cell)) != 0)
{
memcpy(targetRow, sourceRow, size.x * sizeof(Cell));
dirty = true;
}
}
}
}
return dirty;
}
static unsigned int countCells(const Box& box, unsigned int lod)
{
if (box.isEmpty())
return 0;
unsigned int result = 0;
Vector3int32 size = box.getSize();
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
const Cell* row = box.readRow(0, y, z);
for (int x = 0; x < size.x; ++x)
if (row[x].getMaterial() != Cell::Material_Air)
result += (row[x].getOccupancy() + 1) << (lod * 3) >> Cell::Occupancy_Bits;
}
return result;
}
struct MergedCell
{
unsigned char material;
unsigned int occupancy;
MergedCell(const Cell& c)
: material(c.getMaterial())
, occupancy(c.getOccupancy())
{
}
MergedCell(const MergedCell& c0, const MergedCell& c1)
{
if (c0.material == c1.material)
{
material = c0.material;
occupancy = c0.occupancy + c1.occupancy;
}
else if (c0.occupancy != c1.occupancy)
{
// Cell with higher occupancy wins
*this = (c0.occupancy > c1.occupancy) ? c0 : c1;
}
else
{
// Cell with higher material wins - this is important to make sure any solid cell wins over air
*this = (c0.material > c1.material) ? c0 : c1;
}
}
};
static Cell downsampleCell(Cell c000, Cell c001, Cell c010, Cell c011, Cell c100, Cell c101, Cell c110, Cell c111)
{
unsigned char occupancy =
(c000.getOccupancy() + c001.getOccupancy() + c010.getOccupancy() + c011.getOccupancy() +
c100.getOccupancy() + c101.getOccupancy() + c110.getOccupancy() + c111.getOccupancy() + 7) / 8;
MergedCell m00 = MergedCell(c000, c001);
MergedCell m01 = MergedCell(c010, c011);
MergedCell m10 = MergedCell(c100, c101);
MergedCell m11 = MergedCell(c110, c111);
MergedCell m0 = MergedCell(m00, m01);
MergedCell m1 = MergedCell(m10, m11);
MergedCell m = MergedCell(m0, m1);
return Cell(m.material, occupancy);
}
static void downsampleCells(Box& targetBox, const Region& targetRegion, const Box& sourceBox)
{
if (targetRegion.empty())
return;
if (sourceBox.isEmpty() && targetBox.isEmpty())
return;
Vector3int32 size = targetRegion.size();
Vector3int32 sourceOffset = targetRegion.begin() * 2;
Vector3int32 targetOffset = targetRegion.begin();
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
if (sourceBox.isEmpty())
{
Cell* targetRow = targetBox.writeRow(targetOffset.x, targetOffset.y + y, targetOffset.z + z);
memset(targetRow, 0, size.x * sizeof(Cell));
}
else
{
const Cell* sourceRow00 = sourceBox.readRow(sourceOffset.x, sourceOffset.y + y * 2 + 0, sourceOffset.z + z * 2 + 0);
const Cell* sourceRow10 = sourceBox.readRow(sourceOffset.x, sourceOffset.y + y * 2 + 1, sourceOffset.z + z * 2 + 0);
const Cell* sourceRow01 = sourceBox.readRow(sourceOffset.x, sourceOffset.y + y * 2 + 0, sourceOffset.z + z * 2 + 1);
const Cell* sourceRow11 = sourceBox.readRow(sourceOffset.x, sourceOffset.y + y * 2 + 1, sourceOffset.z + z * 2 + 1);
Cell* targetRow = targetBox.writeRow(targetOffset.x, targetOffset.y + y, targetOffset.z + z);
for (int x = 0; x < size.x; ++x)
{
targetRow[x] = downsampleCell(
sourceRow00[x * 2 + 0], sourceRow00[x * 2 + 1],
sourceRow10[x * 2 + 0], sourceRow10[x * 2 + 1],
sourceRow01[x * 2 + 0], sourceRow01[x * 2 + 1],
sourceRow11[x * 2 + 0], sourceRow11[x * 2 + 1]);
}
}
}
}
static unsigned char readUInt8(const std::string& data, unsigned int& readOffset)
{
if (readOffset >= data.size())
throw RBX::runtime_error("Error while decoding data: unexpected end at offset %u", readOffset);
return data[readOffset++];
}
const int kEncodingCountBit = 7;
const int kEncodingOccupancyBit = 6;
const int kEncodingMaterialMask = (1 << kEncodingOccupancyBit) - 1;
static void encodeCellRun(std::string& result, const Cell& cell, unsigned int count)
{
// material has two top bits free and we want byte-wise encoding that minimizes extra bytes
BOOST_STATIC_ASSERT(Cell::Material_Max <= kEncodingMaterialMask);
// 1xxx: single cell vs multiple cells (1 byte for count)
// x1xx: trivial occupancy value (full for solid, empty for air) vs explicit occupancy
// after material we have optional occupancy and optional count; count is 2+ but we'll store count-1 to have a max of 256 and reserve "0" for smth special just in case
bool needCount = (count != 1);
bool needOccupancy = (cell.getMaterial() != Cell::Material_Air && cell.getOccupancy() != Cell::Occupancy_Max);
result += cell.getMaterial() | (needOccupancy << kEncodingOccupancyBit) | (needCount << kEncodingCountBit);
if (needOccupancy)
result += cell.getOccupancy();
if (needCount)
result += count - 1;
}
static void encodeChunk(std::string& result, const Box& data, std::vector<Cell>& cells)
{
Vector3int32 size = data.getSize();
cells.resize(size.x * size.y * size.z);
unsigned int offset = 0;
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
memcpy(&cells[offset], data.readRow(0, y, z), size.x * sizeof(Cell));
offset += size.x;
}
Cell lastCell;
unsigned int lastCount = 0;
for (size_t i = 0; i < cells.size(); ++i)
{
if (lastCount < 256 && (cells[i] == lastCell || lastCount == 0))
{
lastCell = cells[i];
lastCount++;
}
else
{
encodeCellRun(result, lastCell, lastCount);
lastCell = cells[i];
lastCount = 1;
}
}
if (lastCount)
{
encodeCellRun(result, lastCell, lastCount);
}
}
static std::pair<Cell, unsigned int> decodeCellRun(const std::string& data, unsigned int& readOffset)
{
int meta = readUInt8(data, readOffset);
int occupancy = (meta & (1 << kEncodingOccupancyBit)) ? readUInt8(data, readOffset) : Cell::Occupancy_Max;
int count = (meta & (1 << kEncodingCountBit)) ? readUInt8(data, readOffset) + 1 : 1;
return std::make_pair(Cell(meta & kEncodingMaterialMask, occupancy), count);
}
static void decodeChunk(const std::string& data, unsigned int& readOffset, Box& result, std::vector<Cell>& cells)
{
Vector3int32 size = result.getSize();
cells.resize(size.x * size.y * size.z);
unsigned int offset = 0;
while (offset < cells.size())
{
std::pair<Cell, unsigned int> run = decodeCellRun(data, readOffset);
if (offset + run.second > cells.size())
throw RBX::runtime_error("Error while decoding data: chunk overflow at %u cells", offset + run.second);
for (unsigned int i = 0; i < run.second; ++i)
cells[offset + i] = run.first;
offset += run.second;
}
unsigned int cellOffset = 0;
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
memcpy(result.writeRow(0, y, z), &cells[cellOffset], size.x * sizeof(Cell));
cellOffset += size.x;
}
}
struct DeallocateCells
{
DeallocateCells(size_t size)
: size(size)
{
}
void operator()(Cell* cells)
{
RBXPROFILER_COUNTER_SUB("memory/terrain/voxel", size);
::operator delete(cells);
}
size_t size;
};
Region Region::fromExtents(const Vector3& min, const Vector3& max)
{
Vector3 vmin = Voxel::worldSpaceToCellSpace(min);
Vector3 vmax = Voxel::worldSpaceToCellSpace(max);
Vector3int32 ibegin(floorf(vmin.x), floorf(vmin.y), floorf(vmin.z));
Vector3int32 iend(ceilf(vmax.x), ceilf(vmax.y), ceilf(vmax.z));
return Region(ibegin, ibegin.max(iend));
}
bool Region::aligned(unsigned int size) const
{
RBXASSERT(size != 0 && (size & (size - 1)) == 0);
return ((begin_.x | begin_.y | begin_.z | end_.x | end_.y | end_.z) & (size - 1)) == 0;
}
bool Region::inside(const Region& other) const
{
Vector3int32 db = begin_ - other.begin_;
Vector3int32 de = other.end_ - end_;
return (db.x | db.y | db.z | de.x | de.y | de.z) >= 0;
}
Region Region::intersect(const Region& other) const
{
Vector3int32 ibegin = begin_.max(other.begin_);
Vector3int32 iend = end_.min(other.end_);
return Region(ibegin, ibegin.max(iend));
}
Region Region::expand(unsigned int size) const
{
Vector3int32 vsize(size, size, size);
return Region(begin_ - vsize, end_ + vsize);
}
Region Region::expandToGrid(unsigned int size) const
{
RBXASSERT(size != 0 && (size & (size - 1)) == 0);
int mask = size - 1;
return Region(
Vector3int32(begin_.x & ~mask, begin_.y & ~mask, begin_.z & ~mask),
Vector3int32((end_.x + mask) & ~mask, (end_.y + mask) & ~mask, (end_.z + mask) & ~mask));
}
Region Region::offset(const Vector3int32& offset) const
{
return Region(begin_ + offset, end_ + offset);
}
Region Region::downsample(unsigned int lod) const
{
Region ar = expandToGrid(1 << lod);
return Region(ar.begin_ >> lod, ar.end_ >> lod);
}
std::vector<Vector3int32> Region::getChunkIds(unsigned int chunkSizeLog2) const
{
if (empty())
return std::vector<Vector3int32>();
std::vector<Vector3int32> result;
Vector3int32 min = begin() >> int(chunkSizeLog2);
Vector3int32 max = (end() - Vector3int32(1, 1, 1)) >> int(chunkSizeLog2);
for (int z = min.z; z <= max.z; ++z)
for (int y = min.y; y <= max.y; ++y)
for (int x = min.x; x <= max.x; ++x)
result.push_back(Vector3int32(x, y, z));
return result;
}
unsigned long long Region::getChunkCount(unsigned int chunkSizeLog2) const
{
if (empty())
return 0;
Vector3int32 min = begin() >> int(chunkSizeLog2);
Vector3int32 max = (end() - Vector3int32(1, 1, 1)) >> int(chunkSizeLog2);
unsigned long long result = 1;
result *= max.x - min.x + 1;
result *= max.y - min.y + 1;
result *= max.z - min.z + 1;
return result;
}
const Cell Box::emptyCell;
Box::Box()
: sizeX(0)
, sizeY(0)
, sizeZ(0)
, sliceXZ(0)
{
}
Box::Box(int sizeX, int sizeY, int sizeZ)
: sizeX(sizeX)
, sizeY(sizeY)
, sizeZ(sizeZ)
, sliceXZ(sizeX * sizeZ)
{
}
void Box::allocate()
{
size_t size = sizeX * sizeY * sizeZ * sizeof(Cell);
// Cell can be zero-initialized for performance
void* cells = ::operator new(size);
memset(cells, 0, size);
RBXPROFILER_COUNTER_ADD("memory/terrain/voxel", size);
data.reset(static_cast<Cell*>(cells), DeallocateCells(size));
}
Box Box::clone() const
{
Box result(sizeX, sizeY, sizeZ);
if (data)
{
size_t size = sizeX * sizeY * sizeZ * sizeof(Cell);
void* cells = ::operator new(size);
memcpy(cells, data.get(), size);
RBXPROFILER_COUNTER_ADD("memory/terrain/voxel", size);
result.data.reset(static_cast<Cell*>(cells), DeallocateCells(size));
}
return result;
}
Grid::Chunk::Chunk()
: volume(0)
{
for (int mip = 0; mip < kChunkMips; ++mip)
{
int size = kChunkSize >> mip;
data[mip] = Box(size, size, size);
}
}
bool Grid::Chunk::isEmpty() const
{
return data[0].isEmpty();
}
Grid::Grid()
: chunksVolume(0)
{
BOOST_STATIC_ASSERT(kChunkMips <= kChunkSizeLog2);
}
void Grid::connectListener(GridListener* listener)
{
RBXASSERT(std::find(listeners.begin(), listeners.end(), listener) == listeners.end());
listeners.push_back(listener);
}
void Grid::disconnectListener(GridListener* listener)
{
std::vector<GridListener*>::iterator it = std::find(listeners.begin(), listeners.end(), listener);
RBXASSERT(it != listeners.end());
listeners.erase(it);
}
Box Grid::read(const Region& region, int lod) const
{
RBXPROFILER_SCOPE("Voxel", "read");
RBXASSERT(region.aligned(1 << lod));
if (lod < kChunkMips)
{
Region regionLod = region.downsample(lod);
Box result(regionLod.size().x, regionLod.size().y, regionLod.size().z);
std::vector<Vector3int32> chunkIds = region.getChunkIds(kChunkSizeLog2);
for (auto cid: chunkIds)
{
auto cit = chunks.find(cid);
if (cit != chunks.end())
{
const Chunk& chunk = cit->second;
Region chunkRegionLod = Region::fromChunk(cid, kChunkSizeLog2 - lod);
copyCells(result, regionLod, chunk.data[lod], chunkRegionLod);
}
}
return result;
}
else
{
Box result = read(region, kChunkMips - 1);
for (int i = kChunkMips - 1; i < lod; ++i)
{
Box next(result.getSizeX() / 2, result.getSizeY() / 2, result.getSizeZ() / 2);
downsampleCells(next, Region(Vector3int32(), next.getSize()), result);
result = next;
}
return result;
}
}
void Grid::write(const Region& region, const Box& box)
{
RBXPROFILER_SCOPE("Voxel", "write");
RBXASSERT(region.size() == box.getSize());
std::vector<Vector3int32> chunkIds = region.getChunkIds(kChunkSizeLog2);
std::vector<Region> dirtyRegions;
for (auto cid: chunkIds)
{
auto cit = chunks.find(cid);
// don't create new chunks during clears
if (box.isEmpty() && cit == chunks.end())
continue;
Region chunkRegion = Region::fromChunk(cid, kChunkSizeLog2);
// quick-erase chunks if we're clearing them with a single write
if (box.isEmpty() && chunkRegion.inside(region))
{
dirtyRegions.push_back(chunkRegion);
chunksVolume -= cit->second.volume;
chunks.erase(cit);
continue;
}
// we have to create a chunk on demand
if (cit == chunks.end())
cit = chunks.insert(std::make_pair(cid, Chunk())).first;
// copy data from box to chunk
Chunk& chunk = cit->second;
bool dirty = copyCells(chunk.data[0], chunkRegion, box, region);
if (dirty)
{
Region updatedRegion = region.intersect(chunkRegion);
dirtyRegions.push_back(updatedRegion);
// regenerate mipmap chain for the part of the chunk we updated
Region updatedRegionChunk = updatedRegion.offset(-chunkRegion.begin());
for (int mip = 1; mip < kChunkMips; ++mip)
{
Region mipRegion = updatedRegionChunk.downsample(mip);
downsampleCells(chunk.data[mip], mipRegion, chunk.data[mip - 1]);
}
// update approximate chunk volume based on last mip
unsigned int cells = countCells(chunk.data[kChunkMips - 1], kChunkMips - 1);
chunksVolume -= chunk.volume;
chunksVolume += cells;
chunk.volume = cells;
}
// if we did a lot of partial writes chunk may be empty; we can quickly check the low mip to make sure
if (!hasSolidCells(chunk.data[kChunkMips - 1]))
{
RBXASSERT(chunk.volume == 0);
chunks.erase(cit);
}
}
// Update all listeners
for (auto& l: listeners)
for (auto& r: dirtyRegions)
l->onTerrainRegionChanged(r);
}
Cell Grid::getCell(int x, int y, int z) const
{
Vector3int32 chunkId = Vector3int32(x, y, z) >> int(kChunkSizeLog2);
Vector3int32 chunkOffset = chunkId << int(kChunkSizeLog2);
auto it = chunks.find(chunkId);
if (it == chunks.end())
return Cell();
return it->second.data[0].get(x - chunkOffset.x, y - chunkOffset.y, z - chunkOffset.z);
}
std::vector<Region> Grid::getNonEmptyRegions() const
{
std::vector<Region> result;
result.reserve(chunks.size());
for (auto& c: chunks)
{
Region chunkRegion = Region::fromChunk(c.first, kChunkSizeLog2);
result.push_back(chunkRegion);
}
return result;
}
std::vector<Region> Grid::getNonEmptyRegionsInside(const Region& region) const
{
std::vector<Region> result;
// chunkMap.find() is more expensive than isBetweenInclusive
if (region.getChunkCount(kChunkSizeLog2) < chunks.size() * 2)
{
// We're querying a relatively small area, let's just iterate through all regions
std::vector<Vector3int32> chunkIds = region.getChunkIds(kChunkSizeLog2);
for (auto cid: chunkIds)
{
if (chunks.find(cid) == chunks.end())
continue;
Region chunkRegion = Region::fromChunk(cid, kChunkSizeLog2);
Region r = region.intersect(chunkRegion);
result.push_back(r);
}
}
else
{
// We're querying a relatively large area, let's scan through filled regions inside the grid
for (auto& chunk: chunks)
{
Region chunkRegion = Region::fromChunk(chunk.first, kChunkSizeLog2);
Region r = region.intersect(chunkRegion);
if (!r.empty() && !chunk.second.isEmpty())
result.push_back(r);
}
}
return result;
}
unsigned int Grid::getNonEmptyCellCountApprox() const
{
return chunksVolume;
}
void Grid::serialize(std::string& result) const
{
int version = 1;
result += version;
result += kChunkSizeLog2;
// get chunk ids sorted for stability and LZ efficiency
std::vector<Vector3int32> ids;
for (auto& c: chunks)
if (!c.second.isEmpty())
ids.push_back(c.first);
std::sort(ids.begin(), ids.end());
// encode chunks
Vector3int32 lastIndex;
std::vector<Cell> cells;
for (size_t i = 0; i < ids.size(); ++i)
{
Vector3int32 id = ids[i];
Vector3int32 diff = id - lastIndex;
// encode chunk id (delta-encoding for LZ efficiency)
for (int i = 3; i >= 0; --i)
{
result += diff.x >> (i * 8);
result += diff.y >> (i * 8);
result += diff.z >> (i * 8);
}
// encode chunk data
auto cit = chunks.find(id);
RBXASSERT(cit != chunks.end());
encodeChunk(result, cit->second.data[0], cells);
lastIndex = id;
}
}
void Grid::deserialize(const std::string& data)
{
if (data.empty())
return;
unsigned int readOffset = 0;
int version = static_cast<char>(readUInt8(data, readOffset));
if (version != 1)
throw RBX::runtime_error("Error while decoding data: unsupported version");
int chunkSizeLog2 = readUInt8(data, readOffset);
int chunkSize = 1 << chunkSizeLog2;
if (chunkSizeLog2 > 8)
throw RBX::runtime_error("Error while decoding data: malformed chunk size");
Vector3int32 lastIndex;
std::vector<Cell> cells;
Box box(chunkSize, chunkSize, chunkSize);
while (readOffset < data.size())
{
// decode chunk id
for (int i = 3; i >= 0; --i)
{
lastIndex.x += static_cast<int>(readUInt8(data, readOffset) << (i * 8));
lastIndex.y += static_cast<int>(readUInt8(data, readOffset) << (i * 8));
lastIndex.z += static_cast<int>(readUInt8(data, readOffset) << (i * 8));
}
// decode chunk data
decodeChunk(data, readOffset, box, cells);
write(Region(lastIndex << chunkSizeLog2, chunkSize), box);
}
}
} }
+185
View File
@@ -0,0 +1,185 @@
#include "stdafx.h"
#include "voxel2/MaterialTable.h"
#include <fstream>
#include <iterator>
#include <rapidjson/document.h>
#include "StringConv.h"
namespace RBX { namespace Voxel2 {
static double getNumberOr(const rapidjson::Value& node, double def)
{
return node.IsNumber() ? node.GetDouble() : def;
}
static const char* getStringOr(const rapidjson::Value& node, const char* def)
{
return node.IsString() ? node.GetString() : def;
}
static void parseDeformation(MaterialTable::Material& material, MaterialTable::Deformation deformation, const rapidjson::Value& node)
{
if (node.IsNumber())
{
material.deformation = deformation;
material.parameter = node.GetDouble();
}
}
static MaterialTable::Type parseType(const char* value)
{
if (strcmp(value, "hard") == 0)
return MaterialTable::Type_Hard;
if (strcmp(value, "hardsoft") == 0)
return MaterialTable::Type_HardSoft;
return MaterialTable::Type_Soft;
}
static MaterialTable::Mapping parseMapping(const char* value)
{
if (strcmp(value, "cube") == 0)
return MaterialTable::Mapping_Cube;
return MaterialTable::Mapping_Default;
}
static unsigned int parseMaterialLayer(std::vector<MaterialTable::Layer>& layers, const rapidjson::Value& node)
{
MaterialTable::Layer desc;
desc.tiling = getNumberOr(node["tiling"], 1);
desc.detiling = getNumberOr(node["detiling"], 0);
layers.push_back(desc);
return layers.size() - 1;
}
MaterialTable::MaterialTable(const std::string& file, unsigned int materialCount)
{
try
{
load(file);
}
catch (RBX::base_exception& e)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "MaterialTable: failed to load %s: %s", file.c_str(), e.what());
Atlas dummy = {};
dummy.width = 1;
dummy.height = 1;
dummy.tileCount = 1;
atlas = dummy;
}
if (layers.size() < 1)
{
Layer dummy = {};
dummy.tiling = 1;
layers.resize(1, dummy);
}
if (materials.size() < materialCount)
{
Material dummy = {};
materials.resize(materialCount, dummy);
}
}
MaterialTable::~MaterialTable()
{
}
void MaterialTable::load(const std::string& file)
{
using namespace rapidjson;
std::ifstream in(utf8_decode(file).c_str(), std::ios::in | std::ios::binary);
if (!in)
throw RBX::runtime_error("Error opening file %s", file.c_str());
std::ostringstream data;
data << in.rdbuf();
std::string datastring = data.str();
Document root;
root.Parse<kParseDefaultFlags>(datastring.c_str());
if (root.HasParseError())
throw RBX::runtime_error("Failed to parse JSON: %s at %d", root.GetParseError(), int(root.GetErrorOffset()));
std::string platform = getStringOr(root["platform"], "");
const Value& atlasJson = root["atlas"][platform.c_str()];
if (atlasJson.IsObject())
{
atlas.width = getNumberOr(atlasJson["width"], 1);
atlas.height = getNumberOr(atlasJson["height"], 1);
atlas.tileSize = getNumberOr(atlasJson["tileSize"], 0);
atlas.tileCount = getNumberOr(atlasJson["tileCount"], 1);
atlas.borderSize = getNumberOr(atlasJson["borderSize"], 0);
}
else
{
throw RBX::runtime_error("Failed to find atlas definition for platform %s", platform.c_str());
}
const Value& materialsJson = root["materials"];
for (Value::ConstValueIterator it = materialsJson.Begin(); it != materialsJson.End(); ++it)
{
const Value& m = *it;
Material desc;
desc.name = m["name"].GetString();
if (m["texture_top"].IsObject() && m["texture_side"].IsObject())
{
desc.topLayer = parseMaterialLayer(layers, m["texture_top"]);
desc.sideLayer = parseMaterialLayer(layers, m["texture_side"]);
if (m["texture_bottom"].IsObject())
{
desc.bottomLayer = parseMaterialLayer(layers, m["texture_bottom"]);
}
else
{
desc.bottomLayer = desc.topLayer;
}
}
else if (m["texture"].IsObject())
{
desc.topLayer = desc.sideLayer = desc.bottomLayer = parseMaterialLayer(layers, m["texture"]);
}
else
{
desc.topLayer = desc.sideLayer = desc.bottomLayer = -1;
}
desc.type = parseType(getStringOr(m["type"], ""));
desc.mapping = parseMapping(getStringOr(m["mapping"], ""));
desc.deformation = Deformation_None;
desc.parameter = 0.f;
parseDeformation(desc, Deformation_Shift, m["shift"]);
parseDeformation(desc, Deformation_Cubify, m["cubify"]);
parseDeformation(desc, Deformation_Quantize, m["quantize"]);
parseDeformation(desc, Deformation_Barrel, m["barrel"]);
parseDeformation(desc, Deformation_Water, m["water"]);
materials.push_back(desc);
}
}
} }
+914
View File
@@ -0,0 +1,914 @@
#include "stdafx.h"
#include "voxel2/Mesher.h"
#include "voxel2/Grid.h"
#include "voxel2/MaterialTable.h"
#include "rbx/Profiler.h"
namespace RBX { namespace Voxel2 { namespace Mesher {
static const unsigned char kVertexIndexTable[][3] =
{
{0, 0, 0},
{1, 0, 0},
{1, 1, 0},
{0, 1, 0},
{0, 0, 1},
{1, 0, 1},
{1, 1, 1},
{0, 1, 1},
};
static const unsigned char kEdgeVertexTable[12][2][3] =
{
{ {0, 0, 0}, {1, 0, 0} },
{ {1, 0, 0}, {1, 1, 0} },
{ {1, 1, 0}, {0, 1, 0} },
{ {0, 1, 0}, {0, 0, 0} },
{ {0, 0, 1}, {1, 0, 1} },
{ {1, 0, 1}, {1, 1, 1} },
{ {1, 1, 1}, {0, 1, 1} },
{ {0, 1, 1}, {0, 0, 1} },
{ {0, 0, 0}, {0, 0, 1} },
{ {1, 0, 0}, {1, 0, 1} },
{ {1, 1, 0}, {1, 1, 1} },
{ {0, 1, 0}, {0, 1, 1} },
};
static const Vector3 kTextureBasisU[18] =
{
Vector3(0, 0, -1),
Vector3(0, 0, 1),
Vector3(1, 0, 0),
Vector3(-1, 0, 0),
Vector3(0.7, 0, -0.7),
Vector3(-0.7, 0, -0.7),
Vector3(0.7, 0, 0.7),
Vector3(-0.7, 0, 0.7),
Vector3(1, 0, 0),
Vector3(0.7, -0.7, 0),
Vector3(0.7, 0.7, 0),
Vector3(1, 0, 0),
Vector3(1, 0, 0),
Vector3(-1, 0, 0),
Vector3(-0.7, -0.7, 0),
Vector3(-0.7, 0.7, 0),
Vector3(-1, 0, 0),
Vector3(-1, 0, 0),
};
static const Vector3 kTextureBasisV[18] =
{
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, -1, 0),
Vector3(0, 0, 1),
Vector3(0, 0, 1),
Vector3(0, 0, 1),
Vector3(0, -0.7, 0.7),
Vector3(0, 0.7, 0.7),
Vector3(0, 0, -1),
Vector3(0, 0, -1),
Vector3(0, 0, -1),
Vector3(0, -0.7, -0.7),
Vector3(0, 0.7, -0.7),
};
static unsigned short gEdgeTable[3*3*3*3*3*3*3*3];
void prepareTables()
{
for (int i0 = 0; i0 < 81; ++i0)
for (int i1 = 0; i1 < 81; ++i1)
{
int t[2][2][2] =
{
{
{ i0 % 3, (i0 / 3) % 3 },
{ (i0 / 9) % 3, (i0 / 27) % 3 }
},
{
{ i1 % 3, (i1 / 3) % 3 },
{ (i1 / 9) % 3, (i1 / 27) % 3 }
}
};
int edgemask = 0;
for (int i = 0; i < 12; ++i)
{
const unsigned char (&e)[2][3] = kEdgeVertexTable[i];
int p0x = e[0][0], p0y = e[0][1], p0z = e[0][2], p1x = e[1][0], p1y = e[1][1], p1z = e[1][2];
if (t[p0x][p0y][p0z] != t[p1x][p1y][p1z])
edgemask |= 1 << i;
}
gEdgeTable[i0 + 81 * i1] = edgemask;
}
}
struct GridVertex
{
unsigned char tag;
unsigned char occupancy;
};
static void pushQuad(std::vector<unsigned int>& ib,
const std::vector<Vertex>& vb,
unsigned int v0, unsigned int v1, unsigned int v2, unsigned int v3,
bool flip)
{
RBXASSERT(v0 < vb.size() && v1 < vb.size() && v2 < vb.size() && v3 < vb.size());
unsigned int m0 = vb[v0].material;
unsigned int m1 = vb[v1].material;
unsigned int m2 = vb[v2].material;
unsigned int m3 = vb[v3].material;
// For quads with a material transition we pick the diagonal that minimizes interpolation artifacts
bool flipdiag = (m1 == m3) && (m1 == m2 || m1 == m0);
unsigned int v[] = {v0, v1, v2, v3};
// Note: indices here are arranged in the order "ABC CBD" ("strip order") to make it easy to extract diagonal later
static const unsigned int kOffsetTable[2][2][6] =
{
{
{ 1, 0, 2, 2, 0, 3 },
{ 1, 2, 0, 0, 2, 3 },
},
{
{ 0, 3, 1, 1, 3, 2 },
{ 0, 1, 3, 3, 1, 2 },
}
};
const unsigned int* offsets = kOffsetTable[flipdiag][flip];
ib.push_back(v[offsets[0]]);
ib.push_back(v[offsets[1]]);
ib.push_back(v[offsets[2]]);
ib.push_back(v[offsets[3]]);
ib.push_back(v[offsets[4]]);
ib.push_back(v[offsets[5]]);
}
static Vector3 round(const Vector3& v)
{
int x = (v.x < 0) ? int(v.x - 0.5) : int(v.x + 0.5);
int y = (v.y < 0) ? int(v.y - 0.5) : int(v.y + 0.5);
int z = (v.z < 0) ? int(v.z - 0.5) : int(v.z + 0.5);
return Vector3(x, y, z);
}
static size_t avalanche(size_t v)
{
v += ~(v << 15);
v ^= (v >> 10);
v += (v << 3);
v ^= (v >> 6);
v += ~(v << 11);
v ^= (v >> 16);
return v;
}
static unsigned int computeSeed(const Vector3int32& p)
{
size_t result = 0;
boost::hash_combine(result, p.x);
boost::hash_combine(result, p.y);
boost::hash_combine(result, p.z);
return avalanche(result);
}
static Vector3 computePoint(const Vector3& smooth, float cellSize, const MaterialTable* materials, unsigned char material, size_t seed)
{
const MaterialTable::Material& m = materials->getMaterial(material);
Vector3 center = Vector3(cellSize * 0.5f);
switch (m.deformation)
{
case MaterialTable::Deformation_Shift:
return smooth + (Vector3((seed & 255) / 255.f, ((seed >> 8) & 255) / 255.f, ((seed >> 16) & 255) / 255.f) * 2.f - Vector3(1.f)) * (m.parameter * cellSize);
case MaterialTable::Deformation_Cubify:
return lerp(smooth, center, m.parameter);
case MaterialTable::Deformation_Quantize:
return round((smooth - center) / m.parameter) * m.parameter + center;
case MaterialTable::Deformation_Barrel:
return Vector3(smooth.x, G3D::lerp(smooth.y, center.y, m.parameter), smooth.z);
case MaterialTable::Deformation_Water:
return Vector3(smooth.x, smooth.y - m.parameter, smooth.z);
default:
return smooth;
}
}
inline std::pair<unsigned int, unsigned char> reduceMaterials(const std::pair<unsigned int, unsigned char>& m0, const std::pair<unsigned int, unsigned char>& m1)
{
if (m0.second == m1.second)
return std::make_pair(m0.first + m1.first, m0.second);
else if (m0.first != m1.first)
return m0.first > m1.first ? m0 : m1;
else
return m0.second < m1.second ? m0 : m1;
}
static void extractGridVertices(GridVertex* gv, const Box& box, const Options& options)
{
int sizeX = box.getSizeX(), sizeY = box.getSizeY(), sizeZ = box.getSizeZ();
int sizeXZ = sizeX * sizeZ;
unsigned int tagCutoff = options.generateWater ? Cell::Material_Air : Cell::Material_Water;
for (int y = 0; y < sizeY; ++y)
for (int z = 0; z < sizeZ; ++z)
{
GridVertex* gvrow = gv + sizeXZ * y + sizeX * z;
const Cell* row = box.readRow(0, y, z);
for (int x = 0; x < sizeX; ++x)
{
const Cell& c = row[x];
GridVertex& v = gvrow[x];
v.tag = (c.getMaterial() <= tagCutoff) ? 0 : (c.getMaterial() == Cell::Material_Water) ? 1 : 2;
v.occupancy = c.getOccupancy();
}
}
}
static Vertex generateVertex(GridVertex* gv, const Box& box, const Vector3int32& offset, int lod, const Options& options, int x, int y, int z, int edgemask)
{
float cellSize = 1 << lod;
int sizeX = box.getSizeX(), sizeY = box.getSizeY(), sizeZ = box.getSizeZ();
int sizeXZ = sizeX * sizeZ;
Vector3 corner = Vector3(offset.x, offset.y, offset.z) + Vector3(x, y, z) * cellSize;
size_t ecount = 0;
Vector3 eavg;
// add vertices
for (int i = 0; i < 12; ++i)
{
if (edgemask & (1 << i))
{
const unsigned char (&e)[2][3] = kEdgeVertexTable[i];
int p0x = e[0][0], p0y = e[0][1], p0z = e[0][2], p1x = e[1][0], p1y = e[1][1], p1z = e[1][2];
const GridVertex& g0 = gv[(x + p0x) + sizeXZ * (y + p0y) + sizeX * (z + p0z)];
const GridVertex& g1 = gv[(x + p1x) + sizeXZ * (y + p1y) + sizeX * (z + p1z)];
float occScale = 1.f / (Cell::Occupancy_Max + 1);
float t = g0.tag > g1.tag ? (g0.occupancy + 1) * occScale : 1 - (g1.occupancy + 1) * occScale;
eavg += lerp(Vector3(p0x, p0y, p0z) * cellSize, Vector3(p1x, p1y, p1z) * cellSize, t);
ecount++;
}
}
// compute materials
unsigned int vcount = 0;
std::pair<unsigned int, unsigned char> vmat[8] = {};
for (int i = 0; i < 8; ++i)
{
const Cell& c = box.get(x + kVertexIndexTable[i][0], y + kVertexIndexTable[i][1], z + kVertexIndexTable[i][2]);
if (c.getMaterial() > Cell::Material_Water)
{
vmat[vcount] = std::make_pair(c.getOccupancy() + 1, c.getMaterial());
vcount++;
}
}
// reduce materials
std::pair<unsigned int, unsigned char> vmr;
if (vcount > 0)
{
vmr = reduceMaterials(reduceMaterials(vmat[0], vmat[1]), reduceMaterials(vmat[2], vmat[3]));
if (vcount > 4)
vmr = reduceMaterials(vmr, reduceMaterials(reduceMaterials(vmat[4], vmat[5]), reduceMaterials(vmat[6], vmat[7])));
}
else
{
vmr.second = Cell::Material_Water;
}
unsigned char material = vmr.second;
bool border = (x == 0 || x == sizeX - 2 || y == 0 || y == sizeY - 2 || z == 0 || z == sizeZ - 2);
unsigned int seed = computeSeed(Vector3int32(x, y, z) + (offset >> lod));
Vector3 point = computePoint(eavg / float(ecount), cellSize, options.materials, material, seed);
Vector3 position = corner + G3D::clamp(point, Vector3(), Vector3(cellSize)) + Vector3(0.5f);
Vertex v = { position, border, 0, material, seed };
return v;
}
static void generateIndices(std::vector<unsigned int>& ib, const std::vector<Vertex>& vb, const GridVertex* gv, const unsigned int* gp, int sizeX, int sizeY, int sizeZ)
{
int sizeXZ = sizeX * sizeZ;
for (int y = 1; y + 1 < sizeY; ++y)
for (int z = 1; z + 1 < sizeZ; ++z)
for (int x = 1; x + 1 < sizeX; ++x)
{
const GridVertex& v000 = gv[(x + 0) + sizeXZ * (y + 0) + sizeX * (z + 0)];
const GridVertex& v100 = gv[(x + 1) + sizeXZ * (y + 0) + sizeX * (z + 0)];
const GridVertex& v010 = gv[(x + 0) + sizeXZ * (y + 1) + sizeX * (z + 0)];
const GridVertex& v001 = gv[(x + 0) + sizeXZ * (y + 0) + sizeX * (z + 1)];
// add quads
if (v000.tag != v100.tag)
{
pushQuad(ib, vb,
gp[(x + 0) + sizeXZ * (y + 0) + sizeX * (z + 0)],
gp[(x + 0) + sizeXZ * (y - 1) + sizeX * (z + 0)],
gp[(x + 0) + sizeXZ * (y - 1) + sizeX * (z - 1)],
gp[(x + 0) + sizeXZ * (y + 0) + sizeX * (z - 1)],
v000.tag > v100.tag);
}
if (v000.tag != v010.tag)
{
pushQuad(ib, vb,
gp[(x + 0) + sizeXZ * (y + 0) + sizeX * (z + 0)],
gp[(x - 1) + sizeXZ * (y + 0) + sizeX * (z + 0)],
gp[(x - 1) + sizeXZ * (y + 0) + sizeX * (z - 1)],
gp[(x + 0) + sizeXZ * (y + 0) + sizeX * (z - 1)],
v000.tag < v010.tag);
}
if (v000.tag != v001.tag)
{
pushQuad(ib, vb,
gp[(x + 0) + sizeXZ * (y + 0) + sizeX * (z + 0)],
gp[(x - 1) + sizeXZ * (y + 0) + sizeX * (z + 0)],
gp[(x - 1) + sizeXZ * (y - 1) + sizeX * (z + 0)],
gp[(x + 0) + sizeXZ * (y - 1) + sizeX * (z + 0)],
v000.tag > v001.tag);
}
}
}
BasicMesh generateGeometry(const Box& box, const Vector3int32& offset, int lod, const Options& options)
{
if (box.isEmpty())
return BasicMesh();
RBXPROFILER_SCOPE("Voxel", "generateGeometry");
int sizeX = box.getSizeX(), sizeY = box.getSizeY(), sizeZ = box.getSizeZ();
RBXASSERT(sizeX > 2 && sizeY > 2 && sizeZ > 2);
int sizeXZ = sizeX * sizeZ;
boost::scoped_array<GridVertex> gv(new GridVertex[sizeX * sizeZ * sizeY]);
extractGridVertices(gv.get(), box, options);
BasicMesh result;
boost::scoped_array<unsigned int> gp(new unsigned int[sizeX * sizeZ * sizeY]);
for (int y = 0; y + 1 < sizeY; ++y)
for (int z = 0; z + 1 < sizeZ; ++z)
{
int offsetYZ = sizeXZ * y + sizeX * z;
int tagi0 = gv[offsetYZ].tag + 3 * (gv[offsetYZ + sizeX].tag + 3 * (gv[offsetYZ + sizeXZ].tag + 3 * gv[offsetYZ + sizeXZ + sizeX].tag));
for (int x = 0; x + 1 < sizeX; ++x)
{
int offsetNextXYZ = offsetYZ + x + 1;
int tagi1 = gv[offsetNextXYZ].tag + 3 * gv[offsetNextXYZ + sizeX].tag + 9 * gv[offsetNextXYZ + sizeXZ].tag + 27 * gv[offsetNextXYZ + sizeXZ + sizeX].tag;
int edgemask = gEdgeTable[tagi0 + 81 * tagi1];
tagi0 = tagi1;
if (edgemask != 0)
{
Vertex v = generateVertex(gv.get(), box, offset, lod, options, x, y, z, edgemask);
gp[x + sizeXZ * y + sizeX * z] = result.vertices.size();
result.vertices.push_back(v);
}
}
}
generateIndices(result.indices, result.vertices, gv.get(), gp.get(), sizeX, sizeY, sizeZ);
return result;
}
inline std::pair<Vector3, Vector3> computeNormals(const Vector3& hard, const Vector3& soft, const MaterialTable* materials, unsigned char material)
{
const MaterialTable::Material& m = materials->getMaterial(material);
switch (m.type)
{
case MaterialTable::Type_Hard:
return std::make_pair(hard, hard);
case MaterialTable::Type_HardSoft:
if (hard.dot(soft) > 0.75)
return std::make_pair(hard, soft);
else
return std::make_pair(hard, hard);
default:
return std::make_pair(soft, soft);
}
}
inline int getNormalSegment2D_4(float x, float z)
{
if (fabsf(x) > fabsf(z))
return 0 + (x < 0);
else
return 2 + (z < 0);
}
inline int getNormalSegment2D_8(float x, float z)
{
float ax = fabsf(x);
float az = fabsf(z);
if (ax > az * 2)
return 0 + (x < 0);
else if (az > ax * 2)
return 2 + (z < 0);
else
return 4 + 2 * (x < 0) + (z < 0);
}
inline int getNormalSegmentDefault(const Vector3& normal)
{
if (normal.y > 0.9)
return 8;
else if (normal.y > 0.4)
return 9 + getNormalSegment2D_4(normal.x, normal.z);
else if (normal.y < -0.8)
return 13;
else if (normal.y < -0.6)
return 14 + getNormalSegment2D_4(normal.x, normal.z);
else
return getNormalSegment2D_8(normal.x, normal.z);
}
inline int getNormalSegmentCube(const Vector3& normal)
{
float ax = fabsf(normal.x);
float az = fabsf(normal.z);
if (normal.y > ax && normal.y > az)
return 8;
else if (normal.y < -ax && normal.y < -az)
return 13;
else if (ax > az)
return 0 + (normal.x < 0);
else
return 2 + (normal.z < 0);
}
inline int getNormalSegment(const Vector3& normal, MaterialTable::Mapping mapping)
{
switch (mapping)
{
case MaterialTable::Mapping_Cube:
return getNormalSegmentCube(normal);
default:
return getNormalSegmentDefault(normal);
}
}
inline Color3uint8 packNormal(const Vector3& normal)
{
float x = normal.x * 127.f + 127.5f;
float y = normal.y * 127.f + 127.5f;
float z = normal.z * 127.f + 127.5f;
return Color3uint8(int(x), int(y), int(z));
}
inline Color4uint8 packMaterial(const MaterialTable* materials, unsigned int material, const Vector3& normal, unsigned int seed)
{
const MaterialTable::Material& desc = materials->getMaterial(material);
int ns = getNormalSegment(normal, desc.mapping);
int layer = (ns >= 13) ? desc.bottomLayer : (ns >= 8) ? desc.topLayer : desc.sideLayer;
return Color4uint8(layer, ns, seed, seed >> 8);
}
GraphicsMesh generateGraphicsGeometry(const BasicMesh& mesh, const Options& options)
{
if (mesh.indices.empty())
return GraphicsMesh();
RBXPROFILER_SCOPE("Voxel", "generateGraphicsGeometry");
size_t triangleCount = mesh.indices.size() / 3;
GraphicsMesh result;
result.vertices.resize(mesh.indices.size());
result.solidIndices.reserve(mesh.indices.size());
result.waterIndices.reserve(mesh.indices.size());
// water flags
std::vector<char> iswater(triangleCount);
// build normals
std::vector<Vector3> softnormals(mesh.vertices.size());
std::vector<Vector3> hardnormals(triangleCount);
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
const Vertex& v0 = mesh.vertices[i0];
const Vertex& v1 = mesh.vertices[i1];
const Vertex& v2 = mesh.vertices[i2];
Vector3 vn = cross(v1.position - v0.position, v2.position - v0.position);
softnormals[i0] += vn;
softnormals[i1] += vn;
softnormals[i2] += vn;
hardnormals[i] = normalize(vn);
iswater[i] = BasicMesh::isWater(v0, v1, v2);
}
for (size_t i = 0; i < mesh.vertices.size(); ++i)
{
softnormals[i] = normalize(softnormals[i]);
}
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
const Vertex& v0 = mesh.vertices[i0];
const Vertex& v1 = mesh.vertices[i1];
const Vertex& v2 = mesh.vertices[i2];
Vector3 hn = hardnormals[i];
std::pair<Vector3, Vector3> n0 = computeNormals(hn, softnormals[i0], options.materials, v0.material);
std::pair<Vector3, Vector3> n1 = computeNormals(hn, softnormals[i1], options.materials, v1.material);
std::pair<Vector3, Vector3> n2 = computeNormals(hn, softnormals[i2], options.materials, v2.material);
Color3uint8 pn0 = packNormal(n0.first);
Color3uint8 pn1 = packNormal(n1.first);
Color3uint8 pn2 = packNormal(n2.first);
Color4uint8 m0 = packMaterial(options.materials, v0.material, n0.second, v0.seed);
Color4uint8 m1 = packMaterial(options.materials, v1.material, n1.second, v1.seed);
Color4uint8 m2 = packMaterial(options.materials, v2.material, n2.second, v2.seed);
GraphicsVertex gv0 = { v0.position, Color4uint8(pn0, 0), m0, m1, m2 };
GraphicsVertex gv1 = { v1.position, Color4uint8(pn1, 1), m0, m1, m2 };
GraphicsVertex gv2 = { v2.position, Color4uint8(pn2, 2), m0, m1, m2 };
result.vertices[3 * i + 0] = gv0;
result.vertices[3 * i + 1] = gv1;
result.vertices[3 * i + 2] = gv2;
if (v0.border + v1.border + v2.border == 0)
{
if (iswater[i])
{
result.waterIndices.push_back(3 * i + 0);
result.waterIndices.push_back(3 * i + 1);
result.waterIndices.push_back(3 * i + 2);
}
else
{
result.solidIndices.push_back(3 * i + 0);
result.solidIndices.push_back(3 * i + 1);
result.solidIndices.push_back(3 * i + 2);
RBXASSERT((i ^ 1) < triangleCount);
if (iswater[i ^ 1])
{
result.solidIndices.push_back(3 * i + 0);
result.solidIndices.push_back(3 * i + 2);
result.solidIndices.push_back(3 * i + 1);
}
}
}
}
return result;
}
inline Vector3int16 packPosition(const Vector3& position, const Vector4& packInfo)
{
float x = position.x * packInfo.w + packInfo.x + 0.5f;
float y = position.y * packInfo.w + packInfo.y + 0.5f;
float z = position.z * packInfo.w + packInfo.z + 0.5f;
return Vector3int16(int(x), int(y), int(z));
}
GraphicsMeshPacked generateGraphicsGeometryPacked(const BasicMesh& mesh, const Vector4& packInfo, const Options& options)
{
if (mesh.indices.empty())
return GraphicsMeshPacked();
RBXPROFILER_SCOPE("Voxel", "generateGraphicsGeometryPacked");
size_t triangleCount = mesh.indices.size() / 3;
GraphicsMeshPacked result;
result.vertices.reserve(mesh.indices.size());
result.solidIndices.reserve(mesh.indices.size());
result.waterIndices.reserve(mesh.indices.size());
// water flags
std::vector<char> iswater(triangleCount);
// build normals
std::vector<Vector3> softnormals(mesh.vertices.size());
std::vector<Vector3> hardnormals(triangleCount);
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
const Vertex& v0 = mesh.vertices[i0];
const Vertex& v1 = mesh.vertices[i1];
const Vertex& v2 = mesh.vertices[i2];
Vector3 vn = cross(v1.position - v0.position, v2.position - v0.position);
softnormals[i0] += vn;
softnormals[i1] += vn;
softnormals[i2] += vn;
hardnormals[i] = normalize(vn);
iswater[i] = BasicMesh::isWater(v0, v1, v2);
}
for (size_t i = 0; i < mesh.vertices.size(); ++i)
{
softnormals[i] = normalize(softnormals[i]);
}
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
const Vertex& v0 = mesh.vertices[i0];
const Vertex& v1 = mesh.vertices[i1];
const Vertex& v2 = mesh.vertices[i2];
if (v0.border + v1.border + v2.border != 0)
continue;
Vector3 hn = hardnormals[i];
std::pair<Vector3, Vector3> n0 = computeNormals(hn, softnormals[i0], options.materials, v0.material);
std::pair<Vector3, Vector3> n1 = computeNormals(hn, softnormals[i1], options.materials, v1.material);
std::pair<Vector3, Vector3> n2 = computeNormals(hn, softnormals[i2], options.materials, v2.material);
Color3uint8 pn0 = packNormal(n0.first);
Color3uint8 pn1 = packNormal(n1.first);
Color3uint8 pn2 = packNormal(n2.first);
Color4uint8 m0 = packMaterial(options.materials, v0.material, n0.second, 0);
Color4uint8 m1 = packMaterial(options.materials, v1.material, n1.second, 0);
Color4uint8 m2 = packMaterial(options.materials, v2.material, n2.second, 0);
Color4uint8 mp0 = Color4uint8(m0.r, m1.r, m2.r, v1.seed);
Color4uint8 mp1 = Color4uint8(m0.g, m1.g, m2.g, v2.seed);
Vector3int16 p0 = packPosition(v0.position, packInfo);
Vector3int16 p1 = packPosition(v1.position, packInfo);
Vector3int16 p2 = packPosition(v2.position, packInfo);
GraphicsVertexPacked gv0 = { p0, 0, Color4uint8(pn0, v0.seed), mp0, mp1 };
GraphicsVertexPacked gv1 = { p1, 1, Color4uint8(pn1, v0.seed), mp0, mp1 };
GraphicsVertexPacked gv2 = { p2, 2, Color4uint8(pn2, v0.seed), mp0, mp1 };
size_t gi0 = result.vertices.size();
result.vertices.push_back(gv0);
size_t gi1 = result.vertices.size();
result.vertices.push_back(gv1);
size_t gi2 = result.vertices.size();
result.vertices.push_back(gv2);
if (iswater[i])
{
result.waterIndices.push_back(gi0);
result.waterIndices.push_back(gi1);
result.waterIndices.push_back(gi2);
}
else
{
result.solidIndices.push_back(gi0);
result.solidIndices.push_back(gi1);
result.solidIndices.push_back(gi2);
RBXASSERT((i ^ 1) < triangleCount);
if (iswater[i ^ 1])
{
result.solidIndices.push_back(gi0);
result.solidIndices.push_back(gi2);
result.solidIndices.push_back(gi1);
}
}
}
return result;
}
void generateAdjacency(std::vector<TriangleAdjacency>& result, const BasicMesh& mesh)
{
size_t triangleCount = mesh.indices.size() / 3;
std::vector<unsigned int> triangleCounts(mesh.vertices.size());
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
triangleCounts[i0]++;
triangleCounts[i1]++;
triangleCounts[i2]++;
}
std::vector<unsigned int> triangleOffsets(mesh.vertices.size());
size_t triangleOffset = 0;
for (size_t i = 0; i < mesh.vertices.size(); ++i)
{
triangleOffsets[i] = triangleOffset;
triangleOffset += triangleCounts[i];
}
std::vector<unsigned int> triangles(triangleOffset);
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
// Encode the next vertex index in triangle so that following loop is faster
triangles[triangleOffsets[i0]++] = (i << 2) | 1;
triangles[triangleOffsets[i1]++] = (i << 2) | 2;
triangles[triangleOffsets[i2]++] = (i << 2) | 0;
}
result.resize(triangleCount);
for (size_t i = 0; i < triangleCount; ++i)
{
TriangleAdjacency& adj = result[i];
adj.neighbor[0] = adj.neighbor[1] = adj.neighbor[2] = TriangleAdjacency::None;
for (size_t e = 0; e < 3; ++e)
{
unsigned int i0 = mesh.indices[3 * i + (e == 2 ? 0 : e + 1)];
unsigned int i1 = mesh.indices[3 * i + e];
size_t count = triangleCounts[i0];
size_t offset = triangleOffsets[i0] - count;
for (size_t j = 0; j < triangleCounts[i0]; ++j)
{
unsigned int trix = triangles[offset + j];
unsigned int tri = trix >> 2;
if (mesh.indices[3 * tri + (trix & 3)] == i1)
{
if (adj.neighbor[e] == TriangleAdjacency::None)
adj.neighbor[e] = tri;
else
adj.neighbor[e] = TriangleAdjacency::Multiple;
}
}
}
}
}
void generateEdgeFlags(std::vector<unsigned char>& result, const BasicMesh& mesh, float cutoff)
{
size_t triangleCount = mesh.indices.size() / 3;
std::vector<Vector3> hardnormals(triangleCount);
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned int i0 = mesh.indices[3 * i + 0];
unsigned int i1 = mesh.indices[3 * i + 1];
unsigned int i2 = mesh.indices[3 * i + 2];
Vector3 a = mesh.vertices[i0].position;
Vector3 b = mesh.vertices[i1].position;
Vector3 c = mesh.vertices[i2].position;
Vector3 vn = cross(b - a, c - a);
hardnormals[i] = normalize(vn);
}
std::vector<TriangleAdjacency> triangleAdj;
generateAdjacency(triangleAdj, mesh);
result.resize(triangleCount);
for (size_t i = 0; i < triangleCount; ++i)
{
unsigned char flag = 0;
const TriangleAdjacency& adj = triangleAdj[i];
if (adj.neighbor[0] >= 0 && dot(hardnormals[i], hardnormals[adj.neighbor[0]]) > cutoff)
flag |= 1;
if (adj.neighbor[1] >= 0 && dot(hardnormals[i], hardnormals[adj.neighbor[1]]) > cutoff)
flag |= 2;
if (adj.neighbor[2] >= 0 && dot(hardnormals[i], hardnormals[adj.neighbor[2]]) > cutoff)
flag |= 4;
result[i] = flag;
}
}
const TextureBasis& getTextureBasisU()
{
return kTextureBasisU;
}
const TextureBasis& getTextureBasisV()
{
return kTextureBasisV;
}
} } }