This commit is contained in:
watrabi
2025-10-28 14:05:46 -04:00
parent 977f1ff4b8
commit c93494f795
452 changed files with 47860 additions and 152 deletions
+275
View File
@@ -0,0 +1,275 @@
#pragma once
#include "voxel2/Grid.h"
namespace RBX { namespace Voxel2 {
template <typename BitStream> class BitSerializer
{
public:
void encodeIndex(const Vector3int32& index, BitStream& stream)
{
encodeChunkIndex(index - lastIndex, stream);
lastIndex = index;
}
void encodeContent(const Box& box, BitStream& stream)
{
encodeChunkData(box, stream);
}
void decodeIndex(Vector3int32& index, BitStream& stream)
{
Vector3int32 diff;
decodeChunkIndex(diff, stream);
index = lastIndex + diff;
lastIndex = index;
}
void decodeContent(Box& box, BitStream& stream)
{
decodeChunkData(box, stream);
}
private:
Vector3int32 lastIndex;
std::vector<Cell> cells;
void encodeChunkIndex(const Vector3int32& diff, BitStream& stream)
{
if (char(diff.x) == diff.x && char(diff.y) == diff.y && char(diff.z) == diff.z)
{
// Single-byte diffs: tag "1"
stream << true;
stream << char(diff.x);
stream << char(diff.y);
stream << char(diff.z);
}
else if (short(diff.x) == diff.x && short(diff.y) == diff.y && short(diff.z) == diff.z)
{
// Two-byte diffs: tag "01"
stream << false;
stream << true;
stream << short(diff.x);
stream << short(diff.y);
stream << short(diff.z);
}
else
{
// Four-byte diffs: tag "00"
stream << false;
stream << false;
stream << diff.x;
stream << diff.y;
stream << diff.z;
}
}
void decodeChunkIndex(Vector3int32& diff, BitStream& stream)
{
bool size1;
stream >> size1;
if (size1)
{
// Single-byte diffs: tag "1"
char x, y, z;
stream >> x;
stream >> y;
stream >> z;
diff = Vector3int32(x, y, z);
}
else
{
bool size2;
stream >> size2;
if (size2)
{
// Two-byte diffs: tag "01"
short x, y, z;
stream >> x;
stream >> y;
stream >> z;
diff = Vector3int32(x, y, z);
}
else
{
// Four-byte diffs: tag "00"
int x, y, z;
stream >> x;
stream >> y;
stream >> z;
diff = Vector3int32(x, y, z);
}
}
}
void encodeChunkData(const Box& box, BitStream& stream)
{
bool empty = box.isEmpty();
stream << empty;
if (empty)
return;
Vector3int32 size = box.getSize();
cells.resize(size.x * size.y * size.z);
unsigned int cellOffset = 0;
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
memcpy(&cells[cellOffset], box.readRow(0, y, z), size.x * sizeof(Cell));
cellOffset += size.x;
}
int lastMaterial = 0;
for (unsigned int offset = 0; offset < cells.size(); )
{
// identify run length
Cell cell = cells[offset];
unsigned int count = 0;
do offset++, count++;
while (offset < cells.size() && cells[offset] == cell && count < 512);
// serialize run length
// 00 = single cell
// xx = x groups of 3-bit values (max is 3 groups of 3-bit values = 9 bit = 512)
unsigned char groups = (count == 1) ? 0 : (count <= 8) ? 1 : (count <= 64) ? 2 : 3;
unsigned int temp = count - 1;
stream.WriteBits(&groups, 2);
stream.WriteBits((const unsigned char*)&temp, groups * 3);
// serialize material/occupancy combo
// 0 = air
// 10 = full (occupancy is assumed to be max)
// 11 = custom (followed by 8 bits with occupancy data)
// material (only if it's not air)
// 0 = last
// 1 = new (followed by 6 bits with material data)
if (cell.getMaterial() == Cell::Material_Air)
stream << false;
else
{
// solid
stream << true;
// customOccupancy
if (cell.getOccupancy() == Cell::Occupancy_Max)
stream << false;
else
{
stream << true;
unsigned char occupancy = cell.getOccupancy();
stream.WriteBits(&occupancy, Cell::Occupancy_Bits);
}
// newMaterial
if (cell.getMaterial() == lastMaterial)
stream << false;
else
{
stream << true;
unsigned char material = cell.getMaterial();
stream.WriteBits(&material, Cell::Material_Bits);
}
lastMaterial = cell.getMaterial();
}
}
}
void decodeChunkData(Box& box, BitStream& stream)
{
RBXASSERT(box.isEmpty());
bool empty;
stream >> empty;
if (empty)
return;
Vector3int32 size = box.getSize();
cells.resize(size.x * size.y * size.z);
int lastMaterial = 0;
for (unsigned int offset = 0; offset < cells.size(); )
{
// deserialize run length
unsigned char groups = 0;
stream.ReadBits(&groups, 2);
unsigned int temp = 0;
stream.ReadBits((unsigned char*)&temp, groups * 3);
unsigned int count = temp + 1;
if (offset + count > cells.size())
throw RBX::runtime_error("Error while decoding data: chunk overflow at %u cells", offset + count);
// deserialize material/occupancy
unsigned char material = Cell::Material_Air;
unsigned char occupancy = 0;
bool solid;
stream >> solid;
if (solid)
{
bool customOccupancy;
stream >> customOccupancy;
if (!customOccupancy)
occupancy = Cell::Occupancy_Max;
else
stream.ReadBits(&occupancy, Cell::Occupancy_Bits);
bool newMaterial;
stream >> newMaterial;
if (!newMaterial)
material = lastMaterial;
else
stream.ReadBits(&material, Cell::Material_Bits);
lastMaterial = material;
}
// fill cells
Cell cell(material, occupancy);
for (unsigned int i = 0; i < count; ++i)
cells[offset + i] = cell;
offset += count;
}
unsigned int cellOffset = 0;
for (int y = 0; y < size.y; ++y)
for (int z = 0; z < size.z; ++z)
{
memcpy(box.writeRow(0, y, z), &cells[cellOffset], size.x * sizeof(Cell));
cellOffset += size.x;
}
}
};
} }
+192
View File
@@ -0,0 +1,192 @@
#pragma once
#include "v8datamodel/PartInstance.h"
#include "voxel/Grid.h"
#include "voxel2/Grid.h"
namespace RBX { namespace Voxel2 { namespace Conversion {
static const int kOccupancySolid = Cell::Occupancy_Max;
static const int kOccupancyWedge = Cell::Occupancy_Max / 2;
static const int kOccupancyCorner = Cell::Occupancy_Max / 3;
static const int kOccupancyInverseCorner = Cell::Occupancy_Max * 2 / 3;
static const PartMaterial kMaterialTable[] =
{
AIR_MATERIAL,
WATER_MATERIAL,
GRASS_MATERIAL,
SLATE_MATERIAL,
CONCRETE_MATERIAL,
BRICK_MATERIAL,
SAND_MATERIAL,
WOODPLANKS_MATERIAL,
ROCK_MATERIAL,
GLACIER_MATERIAL,
SNOW_MATERIAL,
SANDSTONE_MATERIAL,
MUD_MATERIAL,
BASALT_MATERIAL,
GROUND_MATERIAL,
CRACKED_LAVA_MATERIAL,
};
static const int kMaterialDefault = 2;
inline unsigned char getOccupancyFromSolidBlock(Voxel::CellBlock block)
{
switch (block)
{
case Voxel::CELL_BLOCK_Solid:
return kOccupancySolid;
case Voxel::CELL_BLOCK_VerticalWedge:
case Voxel::CELL_BLOCK_HorizontalWedge:
return kOccupancyWedge;
case Voxel::CELL_BLOCK_CornerWedge:
return kOccupancyCorner;
case Voxel::CELL_BLOCK_InverseCornerWedge:
return kOccupancyInverseCorner;
default:
RBXASSERT(false);
return 0;
}
}
inline Voxel::CellBlock getCellBlockFromCell(const Cell& cell)
{
static const int kOccupancyRounder = Cell::Occupancy_Max / 6;
if (cell.getMaterial() == Cell::Material_Air)
return Voxel::CELL_BLOCK_Empty;
else if (cell.getOccupancy() < kOccupancyCorner - kOccupancyRounder)
return Voxel::CELL_BLOCK_Empty;
else if (cell.getOccupancy() < kOccupancyWedge - kOccupancyRounder)
return Voxel::CELL_BLOCK_CornerWedge;
else if (cell.getOccupancy() < kOccupancyInverseCorner - kOccupancyRounder)
return Voxel::CELL_BLOCK_VerticalWedge;
else if (cell.getOccupancy() < kOccupancySolid - kOccupancyRounder)
return Voxel::CELL_BLOCK_InverseCornerWedge;
else
return Voxel::CELL_BLOCK_Solid;
}
inline PartMaterial getMaterialFromVoxelMaterial(unsigned char material)
{
if (static_cast<size_t>(material) < sizeof(kMaterialTable) / sizeof(kMaterialTable[0]))
return kMaterialTable[material];
else
return kMaterialTable[kMaterialDefault];
}
inline unsigned char getVoxelMaterialFromMaterial(PartMaterial material)
{
for (size_t i = 0; i < sizeof(kMaterialTable) / sizeof(kMaterialTable[0]); ++i)
if (kMaterialTable[i] == material)
return i;
return kMaterialDefault;
}
inline PartMaterial getMaterialFromCellMaterial(Voxel::CellMaterial material)
{
switch (material)
{
case Voxel::CELL_MATERIAL_Deprecated_Empty:
return AIR_MATERIAL;
case Voxel::CELL_MATERIAL_Grass:
return GRASS_MATERIAL;
case Voxel::CELL_MATERIAL_Sand:
return SAND_MATERIAL;
case Voxel::CELL_MATERIAL_Brick:
return BRICK_MATERIAL;
case Voxel::CELL_MATERIAL_Granite:
return SLATE_MATERIAL;
case Voxel::CELL_MATERIAL_Asphalt:
return CONCRETE_MATERIAL;
case Voxel::CELL_MATERIAL_Wood_Plank:
case Voxel::CELL_MATERIAL_Wood_Log:
return WOODPLANKS_MATERIAL;
case Voxel::CELL_MATERIAL_Gravel:
return SLATE_MATERIAL;
case Voxel::CELL_MATERIAL_Cinder_Block:
return CONCRETE_MATERIAL;
case Voxel::CELL_MATERIAL_Stone_Block:
return SLATE_MATERIAL;
case Voxel::CELL_MATERIAL_Cement:
return CONCRETE_MATERIAL;
case Voxel::CELL_MATERIAL_Water:
return WATER_MATERIAL;
default:
return kMaterialTable[kMaterialDefault];
}
}
inline Voxel::CellMaterial getCellMaterialFromMaterial(PartMaterial material)
{
switch (material)
{
case AIR_MATERIAL:
return Voxel::CELL_MATERIAL_Deprecated_Empty;
case WATER_MATERIAL:
return Voxel::CELL_MATERIAL_Water;
case GRASS_MATERIAL:
return Voxel::CELL_MATERIAL_Grass;
case SLATE_MATERIAL:
return Voxel::CELL_MATERIAL_Stone_Block;
case CONCRETE_MATERIAL:
return Voxel::CELL_MATERIAL_Cement;
case BRICK_MATERIAL:
return Voxel::CELL_MATERIAL_Brick;
case SAND_MATERIAL:
return Voxel::CELL_MATERIAL_Sand;
case WOODPLANKS_MATERIAL:
return Voxel::CELL_MATERIAL_Wood_Plank;
default:
return Voxel::CELL_MATERIAL_Grass;
}
}
inline void convertToSmooth(const Voxel::Grid& oldGrid, Voxel2::Grid& grid)
{
std::vector<SpatialRegion::Id> chunks = oldGrid.getNonEmptyChunks();
for (size_t i = 0; i < chunks.size(); ++i)
{
Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(chunks[i]);
Voxel::Grid::Region region = oldGrid.getRegion(extents.getMinPos(), extents.getMaxPos());
Voxel2::Box box(Voxel::kXZ_CHUNK_SIZE, Voxel::kY_CHUNK_SIZE, Voxel::kXZ_CHUNK_SIZE);
for (int y = 0; y < Voxel::kY_CHUNK_SIZE; ++y)
for (int z = 0; z < Voxel::kXZ_CHUNK_SIZE; ++z)
for (int x = 0; x < Voxel::kXZ_CHUNK_SIZE; ++x)
{
Vector3int16 cpos = extents.getMinPos() + Vector3int16(x, y, z);
const Voxel::Cell& oldCell = region.voxelAt(cpos);
const Voxel::CellMaterial& oldMaterial = region.materialAt(cpos);
if (!oldCell.isEmpty())
{
using namespace Voxel2::Conversion;
Voxel2::Cell cell;
if (oldCell.isExplicitWaterCell())
cell = Voxel2::Cell(Voxel2::Cell::Material_Water, Voxel2::Cell::Occupancy_Max);
else
cell = Voxel2::Cell(getVoxelMaterialFromMaterial(getMaterialFromCellMaterial(oldMaterial)), getOccupancyFromSolidBlock(oldCell.solid.getBlock()));
box.set(x, y, z, cell);
}
}
grid.write(Voxel2::Region(Vector3int32(extents.getMinPos()), Vector3int32(extents.getMaxPos() + Vector3int16(1, 1, 1))), box);
}
}
} } }
+205
View File
@@ -0,0 +1,205 @@
#pragma once
#include "util/Vector3int32.h"
#include <vector>
namespace RBX { namespace Voxel2 {
class Cell
{
public:
enum Material
{
Material_Air = 0,
Material_Water = 1,
Material_Bits = 6,
Material_Max = (1 << Material_Bits) - 1
};
enum Occupancy
{
Occupancy_Bits = 8,
Occupancy_Max = (1 << Occupancy_Bits) - 1
};
Cell()
{
// we rely on Air being 0 since we use memset elsewhere
BOOST_STATIC_ASSERT(Material_Air == 0);
this->material = 0;
this->occupancy = 0;
}
Cell(unsigned char material, unsigned char occupancy)
{
RBXASSERT_VERY_FAST(material <= Material_Max && occupancy <= Occupancy_Max);
// make sure occupancy is always 0 for Air material (0)
this->material = material;
this->occupancy = occupancy & (-static_cast<int>(material) >> 31);
}
unsigned char getMaterial() const { return material; }
unsigned char getOccupancy() const { return occupancy; }
bool operator==(const Cell& other) const { return material == other.material && occupancy == other.occupancy; }
bool operator!=(const Cell& other) const { return !(*this == other); }
private:
unsigned char material;
unsigned char occupancy;
};
class Region
{
public:
Region(const Vector3int32& begin, const Vector3int32& end)
: begin_(begin)
, end_(end)
{
RBXASSERT_VERY_FAST(begin.x <= end.x && begin.y <= end.y && begin.z <= end.z);
}
Region(const Vector3int32& begin, unsigned int size)
: begin_(begin)
, end_(begin + Vector3int32(size, size, size))
{
}
static Region fromChunk(const Vector3int32& id, unsigned int chunkSizeLog2)
{
return Region(id << chunkSizeLog2, 1 << chunkSizeLog2);
}
static Region fromExtents(const Vector3& min, const Vector3& max);
const Vector3int32& begin() const { return begin_; }
const Vector3int32& end() const { return end_; }
Vector3int32 size() const { return end_ - begin_; }
bool empty() const { return begin_.x == end_.x || begin_.y == end_.y || begin_.z == end_.z; }
bool operator==(const Region& other) const { return begin_ == other.begin_ && end_ == other.end_; }
bool operator!=(const Region& other) const { return begin_ != other.begin_ || end_ != other.end_; }
bool aligned(unsigned int size) const;
bool inside(const Region& other) const;
Region intersect(const Region& other) const;
Region expand(unsigned int size) const;
Region expandToGrid(unsigned int size) const;
Region offset(const Vector3int32& offset) const;
Region downsample(unsigned int lod) const;
std::vector<Vector3int32> getChunkIds(unsigned int chunkSizeLog2) const;
unsigned long long getChunkCount(unsigned int chunkSizeLog2) const;
private:
Vector3int32 begin_;
Vector3int32 end_;
};
class Box
{
public:
Box();
Box(int sizeX, int sizeY, int sizeZ);
const Cell& get(int x, int y, int z) const
{
RBXASSERT_VERY_FAST(static_cast<unsigned>(x) < static_cast<unsigned>(sizeX) && static_cast<unsigned>(y) < static_cast<unsigned>(sizeY) && static_cast<unsigned>(z) < static_cast<unsigned>(sizeZ));
return data.get() ? data[x + sizeX * z + sliceXZ * y] : emptyCell;
}
void set(int x, int y, int z, const Cell& cell)
{
RBXASSERT_VERY_FAST(static_cast<unsigned>(x) < static_cast<unsigned>(sizeX) && static_cast<unsigned>(y) < static_cast<unsigned>(sizeY) && static_cast<unsigned>(z) < static_cast<unsigned>(sizeZ));
if (!data) allocate();
data[x + sizeX * z + sliceXZ * y] = cell;
}
const Cell* readRow(int x, int y, int z) const
{
RBXASSERT_VERY_FAST(static_cast<unsigned>(x) < static_cast<unsigned>(sizeX) && static_cast<unsigned>(y) < static_cast<unsigned>(sizeY) && static_cast<unsigned>(z) < static_cast<unsigned>(sizeZ));
RBXASSERT_VERY_FAST(data.get());
return &data[x + sizeX * z + sliceXZ * y];
}
Cell* writeRow(int x, int y, int z)
{
RBXASSERT_VERY_FAST(static_cast<unsigned>(x) < static_cast<unsigned>(sizeX) && static_cast<unsigned>(y) < static_cast<unsigned>(sizeY) && static_cast<unsigned>(z) < static_cast<unsigned>(sizeZ));
if (!data) allocate();
return &data[x + sizeX * z + sliceXZ * y];
}
int getSizeX() const { return sizeX; }
int getSizeY() const { return sizeY; }
int getSizeZ() const { return sizeZ; }
Vector3int32 getSize() const { return Vector3int32(sizeX, sizeY, sizeZ); }
bool isEmpty() const { return !data; }
Box clone() const;
private:
int sizeX;
int sizeY;
int sizeZ;
int sliceXZ;
boost::shared_ptr<Cell[]> data;
static const Cell emptyCell;
void allocate();
};
class GridListener;
class Grid
{
public:
Grid();
void connectListener(GridListener* listener);
void disconnectListener(GridListener* listener);
Box read(const Region& region, int lod = 0) const;
void write(const Region& region, const Box& box);
Cell getCell(int x, int y, int z) const;
std::vector<Region> getNonEmptyRegions() const;
std::vector<Region> getNonEmptyRegionsInside(const Region& region) const;
unsigned int getNonEmptyCellCountApprox() const;
bool isAllocated() const { return !chunks.empty(); }
void serialize(std::string& result) const;
void deserialize(const std::string& result);
private:
enum { kChunkMips = 4 };
struct Chunk
{
Box data[kChunkMips];
unsigned int volume;
Chunk();
bool isEmpty() const;
};
boost::unordered_map<Vector3int32, Chunk> chunks;
unsigned int chunksVolume;
std::vector<GridListener*> listeners;
};
} }
+15
View File
@@ -0,0 +1,15 @@
#pragma once
namespace RBX { namespace Voxel2 {
class Region;
class GridListener
{
public:
virtual ~GridListener() {}
virtual void onTerrainRegionChanged(const Region& region) = 0;
};
} }
+80
View File
@@ -0,0 +1,80 @@
#pragma once
namespace RBX { namespace Voxel2 {
class MaterialTable
{
public:
enum Type
{
Type_Soft,
Type_Hard,
Type_HardSoft,
};
enum Deformation
{
Deformation_None,
Deformation_Shift,
Deformation_Cubify,
Deformation_Quantize,
Deformation_Barrel,
Deformation_Water,
};
enum Mapping
{
Mapping_Default,
Mapping_Cube,
};
struct Material
{
std::string name;
int topLayer;
int sideLayer;
int bottomLayer;
Type type;
Mapping mapping;
Deformation deformation;
float parameter;
};
struct Layer
{
float tiling;
float detiling;
};
struct Atlas
{
int width;
int height;
int tileSize;
int tileCount;
int borderSize;
};
MaterialTable(const std::string& file, unsigned int materialCount);
~MaterialTable();
const Material& getMaterial(unsigned int index) const { return materials[index]; }
unsigned int getMaterialCount() const { return materials.size(); }
const Layer& getLayer(unsigned int index) const { return layers[index]; }
unsigned int getLayerCount() const { return layers.size(); }
const Atlas& getAtlas() const { return atlas; }
private:
Atlas atlas;
std::vector<Material> materials;
std::vector<Layer> layers;
void load(const std::string& file);
};
} }
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include "Util/G3DCore.h"
#include "Util/Vector3int32.h"
#include "voxel2/Grid.h"
namespace RBX { namespace Voxel2 {
class MaterialTable;
namespace Mesher
{
struct Vertex
{
Vector3 position;
unsigned int border: 1;
unsigned int reserved: 7;
unsigned int material: 8;
unsigned int seed: 16;
};
struct GraphicsVertex
{
Vector3 position;
Color4uint8 normal; // xyz = normal, w = vertex index (0-2)
Color4uint8 material[3]; // x = layer index, y = normal segment (0-17), zw = random seed
};
struct GraphicsVertexPacked
{
Vector3int16 position;
short id; // vertex index (0-2)
Color4uint8 normal; // xyz = normal, w = random seed 0
Color4uint8 material0; // xyz = layer index (0-?), w = random seed 1
Color4uint8 material1; // xyz = normal segment (0-17), w = random seed 2
};
struct Options
{
const MaterialTable* materials;
bool generateWater;
};
struct BasicMesh
{
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
static bool isWater(const Vertex& v0, const Vertex& v1, const Vertex& v2)
{
return (v0.material == Cell::Material_Water || v1.material == Cell::Material_Water || v2.material == Cell::Material_Water);
}
};
struct GraphicsMesh
{
std::vector<GraphicsVertex> vertices;
std::vector<unsigned int> solidIndices;
std::vector<unsigned int> waterIndices;
};
struct GraphicsMeshPacked
{
std::vector<GraphicsVertexPacked> vertices;
std::vector<unsigned int> solidIndices;
std::vector<unsigned int> waterIndices;
};
void prepareTables();
BasicMesh generateGeometry(const Box& box, const Vector3int32& offset, int lod, const Options& options);
GraphicsMesh generateGraphicsGeometry(const BasicMesh& mesh, const Options& options);
GraphicsMeshPacked generateGraphicsGeometryPacked(const BasicMesh& mesh, const Vector4& packInfo, const Options& options);
struct TriangleAdjacency
{
enum
{
None = -1,
Multiple = -2
};
int neighbor[3];
};
void generateAdjacency(std::vector<TriangleAdjacency>& result, const BasicMesh& mesh);
void generateEdgeFlags(std::vector<unsigned char>& result, const BasicMesh& mesh, float cutoff);
typedef const Vector3 TextureBasis[18];
const TextureBasis& getTextureBasisU();
const TextureBasis& getTextureBasisV();
};
} }