mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-07 13:57:48 +00:00
GEEKING
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
} } }
|
||||
Reference in New Issue
Block a user