mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-05 05:07:48 +00:00
fahhh
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Voxel/Cell.h"
|
||||
#include "Voxel/Region.h"
|
||||
#include "Voxel/Water.h"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
// Helper object for tasks that need fast access to voxel cells across
|
||||
// SpatialRegion boundaries. Keeps an internal buffer of cells, size determined
|
||||
// by template parameters. Buffer can be refreshed from another voxel storage
|
||||
// mechanism (or another AreaCopy) repeatedly.
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
class AreaCopy {
|
||||
// Helper object to comply with the Region API
|
||||
class Chunk {
|
||||
static const int kSize = XDim * YDim * ZDim;
|
||||
|
||||
std::vector<Cell> cells;
|
||||
std::vector<unsigned char> materials;
|
||||
Vector3int16 firstCellLocation;
|
||||
bool isAllEmpty;
|
||||
|
||||
bool contains(const Vector3int16& cellLoc) const;
|
||||
void fillEmpty(const Vector3int16& minLoc, const Vector3int16& maxLoc);
|
||||
template<class RegionType>
|
||||
void fillFromRegion(const RegionType& region);
|
||||
|
||||
public:
|
||||
// Chunk API
|
||||
static const int kXOffsetMultiplier = 1;
|
||||
static const int kYOffsetMultiplier = XDim * ZDim;
|
||||
static const int kZOffsetMultiplier = XDim;
|
||||
|
||||
static int kFaceDirectionToPointerOffset[7];
|
||||
static int voxelCoordOffsetToIndexOffset(const Vector3int16& offsetCoord);
|
||||
int voxelCoordToArrayIndex(const Vector3int16& globalCoord) const;
|
||||
const std::vector<Cell>& getConstData() const;
|
||||
const std::vector<unsigned char>& getConstMaterial() const;
|
||||
void fillLocalAreaInfo(const Vector3int16& loc,
|
||||
const Water::RelevantNeighbors& neighbors, Water::LocalAreaInfo* out)
|
||||
const;
|
||||
|
||||
// other methods
|
||||
template<class Source>
|
||||
void loadData(const Source* source, const Vector3int16& firstCellLocation);
|
||||
bool getIsAllEmpty() const;
|
||||
};
|
||||
|
||||
Chunk storage;
|
||||
|
||||
public:
|
||||
typedef RBX::Voxel::Region<Chunk> Region;
|
||||
static const Region kStaticEndRegion;
|
||||
|
||||
Region getRegion(const Vector3int16& minCoords, const Vector3int16& maxCoords) const;
|
||||
|
||||
template<class Source>
|
||||
void loadData(const Source* source, const Vector3int16& rootCell);
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
#include "AreaCopy.inl"
|
||||
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
bool AreaCopy<XDim, YDim, ZDim>::Chunk::contains(const Vector3int16& cellLocation) const {
|
||||
return cellLocation.isBetweenInclusive(firstCellLocation,
|
||||
firstCellLocation + Vector3int16(XDim, YDim, ZDim) - Vector3int16::one());
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
void AreaCopy<XDim, YDim, ZDim>::Chunk::fillEmpty(
|
||||
const Vector3int16& minLoc, const Vector3int16& maxLoc) {
|
||||
|
||||
unsigned int xWidth = maxLoc.x - minLoc.x + 1;
|
||||
RBXASSERT((xWidth & 0x1) == 0);
|
||||
|
||||
Vector3int16 counter;
|
||||
for (counter.y = minLoc.y; counter.y <= maxLoc.y; ++counter.y) {
|
||||
for (counter.z = minLoc.z; counter.z <= maxLoc.z; ++counter.z) {
|
||||
counter.x = minLoc.x;
|
||||
unsigned int index = voxelCoordToArrayIndex(counter);
|
||||
memset(&cells[index],
|
||||
Cell::convertToUnsignedCharForFile(Constants::kUniqueEmptyCellRepresentation),
|
||||
xWidth * sizeof(Cell));
|
||||
// technically material doesn't need to be set for empty cells
|
||||
memset(&materials[index / 2], 0xff, xWidth / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
template<class RegionType>
|
||||
void AreaCopy<XDim, YDim, ZDim>::Chunk::fillFromRegion(const RegionType& region) {
|
||||
for (typename RegionType::xline_iterator itr = region.xLineBegin();
|
||||
itr != region.xLineEnd(); ++itr) {
|
||||
const size_t lineSize = itr.getLineSize();
|
||||
RBXASSERT(contains(itr.getCurrentLocation()));
|
||||
RBXASSERT(contains(itr.getCurrentLocation() + Vector3int16(lineSize - 1, 0, 0)));
|
||||
|
||||
unsigned int index = voxelCoordToArrayIndex(itr.getCurrentLocation());
|
||||
if (lineSize == 32) {
|
||||
memcpy(&cells[index], itr.getLineCells(), 32 * sizeof(Cell));
|
||||
memcpy(&materials[index/2], itr.getLineMaterials(), 32 / 2);
|
||||
} else {
|
||||
const Cell* cellSrc = itr.getLineCells();
|
||||
const unsigned char* materialSrc = itr.getLineMaterials();
|
||||
for (size_t i = 0; i < lineSize; ++i) {
|
||||
cells[index + i] = cellSrc[i];
|
||||
materials[(index + i) / 2] = materialSrc[i / 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
int AreaCopy<XDim, YDim, ZDim>::Chunk::kFaceDirectionToPointerOffset[7] = {
|
||||
1,
|
||||
XDim,
|
||||
-1,
|
||||
-((int)XDim),
|
||||
XDim * ZDim,
|
||||
-(int)(XDim * ZDim),
|
||||
0
|
||||
};
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
int AreaCopy<XDim, YDim, ZDim>::Chunk::voxelCoordOffsetToIndexOffset(
|
||||
const Vector3int16& localCoord) {
|
||||
return localCoord.x + (XDim * localCoord.z) + (XDim * ZDim * localCoord.y);
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
int AreaCopy<XDim, YDim, ZDim>::Chunk::voxelCoordToArrayIndex(
|
||||
const Vector3int16& globalCoord) const {
|
||||
return voxelCoordOffsetToIndexOffset(globalCoord - firstCellLocation);
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
const std::vector<Cell>& AreaCopy<XDim, YDim, ZDim>::Chunk::getConstData() const {
|
||||
return cells;
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
const std::vector<unsigned char>& AreaCopy<XDim, YDim, ZDim>::Chunk::getConstMaterial() const {
|
||||
return materials;
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
void AreaCopy<XDim, YDim, ZDim>::Chunk::fillLocalAreaInfo(
|
||||
const Vector3int16& globalCoord,
|
||||
const Water::RelevantNeighbors& relevantNeighbors,
|
||||
Water::LocalAreaInfo* out) const {
|
||||
|
||||
RBXASSERT(contains(globalCoord));
|
||||
RBXASSERT(contains(globalCoord + relevantNeighbors.aboveNeighbor));
|
||||
RBXASSERT(contains(globalCoord + relevantNeighbors.primaryNeighbor));
|
||||
RBXASSERT(contains(globalCoord + relevantNeighbors.secondaryNeighbor));
|
||||
RBXASSERT(contains(globalCoord + relevantNeighbors.diagonalNeighbor));
|
||||
RBXASSERT(contains(globalCoord + relevantNeighbors.diagonalUpNeighbor));
|
||||
|
||||
unsigned int centerIndex = voxelCoordToArrayIndex(globalCoord);
|
||||
|
||||
out->aboveNeighbor = cells[centerIndex +
|
||||
voxelCoordOffsetToIndexOffset(relevantNeighbors.aboveNeighbor)];
|
||||
out->primaryNeighbor = cells[centerIndex +
|
||||
voxelCoordOffsetToIndexOffset(relevantNeighbors.primaryNeighbor)];
|
||||
out->secondaryNeighbor = cells[centerIndex +
|
||||
voxelCoordOffsetToIndexOffset(relevantNeighbors.secondaryNeighbor)];
|
||||
out->diagonalNeighbor = cells[centerIndex +
|
||||
voxelCoordOffsetToIndexOffset(relevantNeighbors.diagonalNeighbor)];
|
||||
out->diagonalUpNeighbor = cells[centerIndex +
|
||||
voxelCoordOffsetToIndexOffset(relevantNeighbors.diagonalUpNeighbor)];
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
template<class Source>
|
||||
void AreaCopy<XDim, YDim, ZDim>::Chunk::loadData(const Source* source,
|
||||
const Vector3int16& rootCell) {
|
||||
|
||||
if (cells.empty()) {
|
||||
std::vector<Cell> cellsSwap(kSize, Constants::kUniqueEmptyCellRepresentation);
|
||||
std::vector<unsigned char> materialsSwap((kSize + 1) / 2, 0xff);
|
||||
cells.swap(cellsSwap);
|
||||
materials.swap(materialsSwap);
|
||||
}
|
||||
|
||||
firstCellLocation = rootCell;
|
||||
|
||||
const Vector3int16 maxCell = rootCell +
|
||||
Vector3int16(XDim, YDim, ZDim) - Vector3int16::one();
|
||||
|
||||
const SpatialRegion::Id minRegion =
|
||||
SpatialRegion::regionContainingVoxel(rootCell);
|
||||
const SpatialRegion::Id maxRegion =
|
||||
SpatialRegion::regionContainingVoxel(maxCell);
|
||||
|
||||
isAllEmpty = true;
|
||||
Vector3int16 regionIdCounter;
|
||||
for (regionIdCounter.y = minRegion.value().y; regionIdCounter.y <= maxRegion.value().y; ++regionIdCounter.y) {
|
||||
for (regionIdCounter.z = minRegion.value().z; regionIdCounter.z <= maxRegion.value().z; ++regionIdCounter.z) {
|
||||
for (regionIdCounter.x = minRegion.value().x; regionIdCounter.x <= maxRegion.value().x; ++regionIdCounter.x) {
|
||||
SpatialRegion::Id id(regionIdCounter);
|
||||
Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(id);
|
||||
const Vector3int16 queryMin = extents.getMinPos().max(rootCell);
|
||||
const Vector3int16 queryMax = extents.getMaxPos().min(maxCell);
|
||||
|
||||
// for material alignment issues, all x segments must be even, and
|
||||
// start from an even location in the region
|
||||
RBXASSERT(((queryMax.x - queryMin.x + 1) & 0x1) == 0);
|
||||
RBXASSERT(((queryMin.x - firstCellLocation.x) & 0x1) == 0);
|
||||
|
||||
typename Source::Region region = source->getRegion(queryMin, queryMax);
|
||||
if (region.isGuaranteedAllEmpty()) {
|
||||
fillEmpty(queryMin, queryMax);
|
||||
} else {
|
||||
isAllEmpty = false;
|
||||
fillFromRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
bool AreaCopy<XDim, YDim, ZDim>::Chunk::getIsAllEmpty() const {
|
||||
return isAllEmpty;
|
||||
}
|
||||
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
typename AreaCopy<XDim, YDim, ZDim>::Region AreaCopy<XDim, YDim, ZDim>::getRegion(
|
||||
const Vector3int16& minCoords, const Vector3int16& maxCoords) const {
|
||||
return Region(storage.getIsAllEmpty() ? NULL : &storage, minCoords, maxCoords);
|
||||
}
|
||||
|
||||
template<unsigned int XDim, unsigned int YDim, unsigned int ZDim>
|
||||
template<class Source>
|
||||
void AreaCopy<XDim, YDim, ZDim>::loadData(const Source* source,
|
||||
const Vector3int16& rootCell) {
|
||||
storage.loadData(source, rootCell);
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <ostream>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Defines voxels at the cellular and sub-cellular level
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
enum CellMaterial
|
||||
{
|
||||
CELL_MATERIAL_Deprecated_Empty = 0,
|
||||
CELL_MATERIAL_Grass = 1,
|
||||
CELL_MATERIAL_Sand = 2,
|
||||
CELL_MATERIAL_Brick = 3,
|
||||
CELL_MATERIAL_Granite = 4,
|
||||
CELL_MATERIAL_Asphalt = 5,
|
||||
CELL_MATERIAL_Iron = 6,
|
||||
CELL_MATERIAL_Aluminum = 7,
|
||||
CELL_MATERIAL_Gold = 8,
|
||||
CELL_MATERIAL_Wood_Plank = 9,
|
||||
CELL_MATERIAL_Wood_Log = 10,
|
||||
CELL_MATERIAL_Gravel = 11,
|
||||
CELL_MATERIAL_Cinder_Block = 12,
|
||||
CELL_MATERIAL_Stone_Block = 13,
|
||||
CELL_MATERIAL_Cement = 14,
|
||||
CELL_MATERIAL_Red_Plastic = 15,
|
||||
CELL_MATERIAL_Blue_Plastic = 16,
|
||||
CELL_MATERIAL_Water = 17,
|
||||
CELL_MATERIAL_Unspecified = 255,
|
||||
MAX_CELL_MATERIALS = 18,
|
||||
};
|
||||
|
||||
enum CellBlock
|
||||
{
|
||||
CELL_BLOCK_Solid = 0,
|
||||
CELL_BLOCK_VerticalWedge = 1,
|
||||
CELL_BLOCK_CornerWedge = 2,
|
||||
CELL_BLOCK_InverseCornerWedge = 3,
|
||||
CELL_BLOCK_HorizontalWedge = 4,
|
||||
|
||||
// Enum values below this line are intentionally not reflected!
|
||||
// Talk with dignatoff@ before exposing these enums!
|
||||
CELL_BLOCK_Empty = 5,
|
||||
|
||||
//InverseVerticalWedge = 4,
|
||||
//TopCornerWedge = 5,
|
||||
MAX_CELL_BLOCKS = 8,
|
||||
};
|
||||
|
||||
// Viewed from a downwards vertical perspective,
|
||||
// orientation defines the corner that the block starts in in a clockwise fashion
|
||||
enum CellOrientation
|
||||
{
|
||||
CELL_ORIENTATION_NegZ = 0, // upper left
|
||||
CELL_ORIENTATION_X = 1, // upper right
|
||||
CELL_ORIENTATION_Z = 2, // lower right
|
||||
CELL_ORIENTATION_NegX = 3, // lower left
|
||||
MAX_CELL_ORIENTATIONS = 4
|
||||
};
|
||||
|
||||
enum WaterCellForce
|
||||
{
|
||||
WATER_CELL_FORCE_None = 0,
|
||||
WATER_CELL_FORCE_Small = 1,
|
||||
WATER_CELL_FORCE_Medium = 2,
|
||||
WATER_CELL_FORCE_Strong = 3,
|
||||
WATER_CELL_FORCE_MaxForce = 4,
|
||||
MAX_WATER_CELL_FORCES = 5
|
||||
};
|
||||
|
||||
enum WaterCellDirection
|
||||
{
|
||||
WATER_CELL_DIRECTION_NegX = 0,
|
||||
WATER_CELL_DIRECTION_X = 1,
|
||||
WATER_CELL_DIRECTION_NegY = 2,
|
||||
WATER_CELL_DIRECTION_Y = 3,
|
||||
WATER_CELL_DIRECTION_NegZ = 4,
|
||||
WATER_CELL_DIRECTION_Z = 5,
|
||||
MAX_WATER_CELL_DIRECTIONS = 6
|
||||
};
|
||||
|
||||
class SolidTerrainCell {
|
||||
// Material used to be stored in solid terrain voxel; it has
|
||||
// subsequently been moved to a completely separate storage mechanism.
|
||||
// The field is here to preserve memory layout with the earlier version.
|
||||
unsigned char DEPRECATED_material : 3;
|
||||
unsigned char block : 3;
|
||||
unsigned char orientation : 2;
|
||||
|
||||
public:
|
||||
CellBlock getBlock() const { return (CellBlock) block; }
|
||||
CellOrientation getOrientation() const {
|
||||
return (CellOrientation) orientation;
|
||||
}
|
||||
|
||||
void setBlock(CellBlock block) { this->block = block; }
|
||||
void setOrientation(CellOrientation orientation) {
|
||||
this->orientation = orientation;
|
||||
}
|
||||
};
|
||||
|
||||
class WaterCell {
|
||||
// Water voxels are implemented to be bit-compatible with solid voxels,
|
||||
// so this bit layout matches the bit layout of SolidTerrainCell.
|
||||
unsigned char dataPart2 : 3;
|
||||
unsigned char blockMustBeEmpty : 3;
|
||||
unsigned char dataPart1 : 2;
|
||||
|
||||
unsigned int getWaterData() const {
|
||||
return ((dataPart1 << 3) | dataPart2) - 1;
|
||||
}
|
||||
public:
|
||||
WaterCellForce getForce() const {
|
||||
return (WaterCellForce) ((getWaterData() / MAX_WATER_CELL_DIRECTIONS) % MAX_WATER_CELL_FORCES);
|
||||
}
|
||||
WaterCellDirection getDirection() const {
|
||||
return (WaterCellDirection) (getWaterData() % MAX_WATER_CELL_DIRECTIONS);
|
||||
}
|
||||
|
||||
void setForceAndDirection(WaterCellForce force, WaterCellDirection direction) {
|
||||
unsigned int rawData = (force * MAX_WATER_CELL_DIRECTIONS + direction) + 1;
|
||||
dataPart2 = rawData & 0x7;
|
||||
dataPart1 = (rawData >> 3) & 0x3;
|
||||
}
|
||||
};
|
||||
|
||||
// Data structure that represents one voxel cell. The cell can either be water or solid terrain,
|
||||
// so this class is a union of WaterCell and SolidTerrainCell.
|
||||
union Cell {
|
||||
SolidTerrainCell solid;
|
||||
WaterCell water;
|
||||
|
||||
Cell() {
|
||||
// There isn't a simple way to make sure all of the members are
|
||||
// zero by going through setter methods. SolidTerrainCell, for example,
|
||||
// has no way to control the bits in DEPRECATED_material.
|
||||
// Performance testing has shown this to not be a perf hit compared to
|
||||
// casting this to unsigned char* and setting that to zero.
|
||||
memset(this, 0, sizeof(Cell));
|
||||
}
|
||||
|
||||
// True if the voxel is completely empty (no solid terrain and no water)
|
||||
inline bool isEmpty() const;
|
||||
|
||||
// Indicates if this cell has been set to water explicitly by the user.
|
||||
// Note that water can also exist in wedge cells. Use Region and/or
|
||||
// Region::iterator methods for a way to detect all kinds of water
|
||||
// simultaneously.
|
||||
inline bool isExplicitWaterCell() const { return !isEmpty() && solid.getBlock() == CELL_BLOCK_Empty; }
|
||||
|
||||
inline bool operator==(const Cell& other) const {
|
||||
return ((const unsigned char*)this)[0] == ((const unsigned char*)&other)[0];
|
||||
}
|
||||
inline bool operator!=(const Cell& other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
// Convert to/from unsigned char, for networking and saving to file
|
||||
static inline unsigned char serializeAsUnsignedChar(const Cell v) {
|
||||
return ((unsigned char*)&v)[0];
|
||||
}
|
||||
static inline Cell deserializeFromUnsignedChar(unsigned char cell) {
|
||||
return ((Cell*)&cell)[0];
|
||||
}
|
||||
static inline unsigned char convertToUnsignedCharForFile(const Cell v) {
|
||||
return ((unsigned char*)&v)[0];
|
||||
}
|
||||
static inline Cell readUnsignedCharFromFile(unsigned char cell) {
|
||||
return ((Cell*)&cell)[0];
|
||||
}
|
||||
// Old style voxel access. Avoid using these methods where possible.
|
||||
static inline unsigned char asUnsignedCharForDeprecatedUses(const Cell v) {
|
||||
return ((unsigned char*)&v)[0];
|
||||
}
|
||||
static inline Cell readUnsignedCharFromDeprecatedUse(unsigned char cell) {
|
||||
return ((Cell*)&cell)[0];
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_STATIC_ASSERT(sizeof(Cell) == 1);
|
||||
|
||||
namespace Constants {
|
||||
// There is exactly one way to represent an empty cell. This constant stores that representation.
|
||||
extern const Cell kUniqueEmptyCellRepresentation;
|
||||
// When there is water in a cell that has a solid wedge, the water state is always the same. This
|
||||
// constant stores the water state for water-on-wedge cells.
|
||||
extern const Cell kWaterOnWedgeCell;
|
||||
}
|
||||
|
||||
bool Cell::isEmpty() const {
|
||||
return (*this) == Constants::kUniqueEmptyCellRepresentation;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const RBX::Voxel::Cell& v);
|
||||
|
||||
const int kXZ_CHUNK_SIZE = 32;
|
||||
const int kY_CHUNK_SIZE = 16;
|
||||
|
||||
const int kCELL_SIZE = 4;
|
||||
const int kHALF_CELL = kCELL_SIZE / 2;
|
||||
const int kCELL_SIZE_AS_BIT_SHIFT = 2;
|
||||
|
||||
} }
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Voxel/Cell.h"
|
||||
|
||||
namespace RBX {
|
||||
|
||||
namespace Voxel {
|
||||
|
||||
struct CellChangeInfo {
|
||||
const Vector3int16 position;
|
||||
|
||||
Cell beforeCell;
|
||||
Cell afterCell;
|
||||
bool hadWaterBefore;
|
||||
bool hasWaterAfter;
|
||||
CellMaterial afterMaterial;
|
||||
|
||||
CellChangeInfo(const Vector3int16& position,
|
||||
Cell beforeCell, Cell afterCell,
|
||||
bool hadWaterBefore, bool hasWaterAfter,
|
||||
CellMaterial afterMaterial)
|
||||
: position(position)
|
||||
, beforeCell(beforeCell)
|
||||
, afterCell(afterCell)
|
||||
, hadWaterBefore(hadWaterBefore)
|
||||
, hasWaterAfter(hasWaterAfter)
|
||||
, afterMaterial(afterMaterial)
|
||||
{ }
|
||||
};
|
||||
|
||||
// Callback interface for components that want to be notified when terrain
|
||||
// cells change
|
||||
class CellChangeListener {
|
||||
public:
|
||||
virtual void terrainCellChanged(const CellChangeInfo& info) = 0;
|
||||
};
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/SpatialRegion.h"
|
||||
|
||||
#include <vector>
|
||||
#include <boost/unordered_map.hpp>
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
// Associative container for mapping SpatialRegion::Id to a value type.
|
||||
// ValueType should implement no-arg constructor and assignment operator.
|
||||
template<class ValueType>
|
||||
class ChunkMap {
|
||||
typedef boost::unordered_map<SpatialRegion::Id, ValueType, SpatialRegion::Id::boost_compatible_hash_value> ValueMap;
|
||||
|
||||
public:
|
||||
ChunkMap();
|
||||
|
||||
// Mutating accessor, will insert a new ValueType if the id wasn't already
|
||||
// contained in this container.
|
||||
ValueType& insert(const SpatialRegion::Id& id);
|
||||
|
||||
// Constant accessor, returns NULL if id is not contained.
|
||||
const ValueType* find(const SpatialRegion::Id& id) const;
|
||||
ValueType* find(const SpatialRegion::Id& id);
|
||||
|
||||
// Removes the key/value pair for the given id. Does nothing if the key
|
||||
// is not present.
|
||||
void erase(const SpatialRegion::Id& id);
|
||||
|
||||
// Get all chunks
|
||||
std::vector<SpatialRegion::Id> getChunks() const;
|
||||
|
||||
// Get number of chunks
|
||||
size_t size() const;
|
||||
|
||||
private:
|
||||
ValueMap values;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
#include "ChunkMap.inl"
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "Voxel/Cell.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
template <class ValueType> ChunkMap<ValueType>::ChunkMap()
|
||||
{
|
||||
}
|
||||
|
||||
template <class ValueType> ValueType& ChunkMap<ValueType>::insert(const SpatialRegion::Id& id)
|
||||
{
|
||||
return values[id];
|
||||
}
|
||||
|
||||
template <class ValueType> const ValueType* ChunkMap<ValueType>::find(const SpatialRegion::Id& id) const
|
||||
{
|
||||
typename ValueMap::const_iterator it = values.find(id);
|
||||
|
||||
return (it == values.end()) ? NULL : &it->second;
|
||||
}
|
||||
|
||||
template <class ValueType> ValueType* ChunkMap<ValueType>::find(const SpatialRegion::Id& id)
|
||||
{
|
||||
typename ValueMap::iterator it = values.find(id);
|
||||
|
||||
return (it == values.end()) ? NULL : &it->second;
|
||||
}
|
||||
|
||||
template <class ValueType> void ChunkMap<ValueType>::erase(const SpatialRegion::Id& id)
|
||||
{
|
||||
values.erase(id);
|
||||
}
|
||||
|
||||
template <class ValueType> std::vector<SpatialRegion::Id> ChunkMap<ValueType>::getChunks() const
|
||||
{
|
||||
std::vector<SpatialRegion::Id> result;
|
||||
result.reserve(values.size());
|
||||
|
||||
for (typename ValueMap::const_iterator it = values.begin(); it != values.end(); ++it)
|
||||
result.push_back(it->first);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class ValueType> size_t ChunkMap<ValueType>::size() const
|
||||
{
|
||||
return values.size();
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
// suffix header file for Grid.h
|
||||
|
||||
#include "Util/SpatialRegion.h"
|
||||
#include "Voxel/Water.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
// Private storage structure supporting Grid.
|
||||
// NOT TO BE USED ANYWHERE EXCEPT Grid.cpp AND Grid.h.
|
||||
// Stores a contiguous 3-D box of terrain contents. Namespace contains
|
||||
// constants and helper methods for accessing the data.
|
||||
class Grid::Chunk {
|
||||
|
||||
private:
|
||||
|
||||
bool initialized;
|
||||
unsigned int countOfNonEmptyCells;
|
||||
std::vector<Cell> data;
|
||||
std::vector<unsigned char> material;
|
||||
const Grid* owner;
|
||||
|
||||
static const int kXOffsetMultiplier = 1;
|
||||
static const int kZOffsetMultiplier =
|
||||
SpatialRegion::Constants::kRegionXDimensionInVoxels;
|
||||
static const int kYOffsetMultiplier =
|
||||
SpatialRegion::Constants::kRegionXDimensionInVoxels *
|
||||
SpatialRegion::Constants::kRegionZDimensionInVoxels;
|
||||
|
||||
public:
|
||||
|
||||
static const int kFaceDirectionToPointerOffset[7];
|
||||
|
||||
static inline int voxelCoordOffsetToIndexOffset(const Vector3int16& offset) {
|
||||
return (offset * Vector3int16(kXOffsetMultiplier, kYOffsetMultiplier, kZOffsetMultiplier)).sum();
|
||||
}
|
||||
|
||||
static inline unsigned int voxelCoordToArrayIndex(const Vector3int16& coord) {
|
||||
return voxelCoordOffsetToIndexOffset(
|
||||
SpatialRegion::voxelCoordinateRelativeToEnclosingRegion(coord));
|
||||
}
|
||||
|
||||
Chunk();
|
||||
~Chunk();
|
||||
|
||||
// Initialization method. Safe to call multiple times. This object owns
|
||||
// a significant amount of memory, so a separate init method was made to
|
||||
// allow explicit control over when that memory is allocated.
|
||||
void init(const Grid* owner);
|
||||
|
||||
std::vector<Cell>& getData() { return data; }
|
||||
const std::vector<Cell>& getConstData() const { return data; }
|
||||
std::vector<unsigned char>& getMaterial() { return material; }
|
||||
const std::vector<unsigned char>& getConstMaterial() const { return material; }
|
||||
|
||||
void updateCountOfNonEmptyCells(int delta) {
|
||||
countOfNonEmptyCells += delta;
|
||||
RBXASSERT((int)(countOfNonEmptyCells) >= 0);
|
||||
}
|
||||
bool hasNoUsefulData() const {
|
||||
return countOfNonEmptyCells == 0;
|
||||
}
|
||||
|
||||
// for water
|
||||
void fillLocalAreaInfo(const Vector3int16& centerCoord,
|
||||
const Water::RelevantNeighbors& relevantNeighbors,
|
||||
Water::LocalAreaInfo* out) const {
|
||||
return owner->fillLocalAreaInfo(centerCoord, relevantNeighbors, out);
|
||||
}
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Util/SpatialRegion.h"
|
||||
#include "Voxel/CellChangeListener.h"
|
||||
#include "Voxel/ChunkMap.h"
|
||||
#include "Voxel/Region.h"
|
||||
#include "Voxel/Water.h"
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
// Storage class for terrain Voxels. Has methods for reading and writing
|
||||
// voxels. The voxels for different areas of the terrain may be stored
|
||||
// internally in separate sub-containers. Frequently allocates and re-
|
||||
// allocates memory, and does not take any data model locks, so do not store
|
||||
// VoxelRegions for later use (e.g. storing for a later job run).
|
||||
class Grid {
|
||||
class Chunk;
|
||||
|
||||
typedef ChunkMap<Chunk> ChunkMapType;
|
||||
ChunkMapType chunkMap;
|
||||
unsigned int countOfNonEmptyCells;
|
||||
|
||||
// Whenever a cell is changed, the cellChangedSignal will be notified.
|
||||
// Note that this doesn't necessarily happen every time setCell is called:
|
||||
// if setCell would set a cell to be the same value it currently it has,
|
||||
// then the cellChangedSignal won't fire for that setCell call.
|
||||
std::vector<CellChangeListener*> cellChangeListeners;
|
||||
|
||||
const Cell& getVoxelLikelyThisChunk(const SpatialRegion::Id& id,
|
||||
const Chunk& chunk, const Vector3int16& coord) const;
|
||||
|
||||
void fillLocalAreaInfo(const Vector3int16& globalCoord,
|
||||
const Water::RelevantNeighbors& neighbors,
|
||||
Water::LocalAreaInfo* out) const;
|
||||
|
||||
public:
|
||||
typedef RBX::Voxel::Region<Chunk> Region;
|
||||
|
||||
Grid();
|
||||
|
||||
// returns the number of cells in the terrain that are not empty
|
||||
inline unsigned int getNonEmptyCellCount() const { return countOfNonEmptyCells; }
|
||||
|
||||
// subscribe and unsubscribe from cell change events
|
||||
void connectListener(CellChangeListener* listener);
|
||||
void disconnectListener(CellChangeListener* listener);
|
||||
|
||||
// Updates one cell. Will notify listeners of the cellChanged signal if the
|
||||
// targeted cell is actually altered (new values != old values) after the
|
||||
// new value is put in place.
|
||||
void setCell(const Vector3int16& location, Cell newCell,
|
||||
CellMaterial newMaterial);
|
||||
|
||||
// Get a Cell region covering the extents specified. Does not support
|
||||
// extents that span SpatialRegion boundaries.
|
||||
Region getRegion(const Vector3int16& extent1, const Vector3int16& extent2) const;
|
||||
|
||||
// Gets information about one cell
|
||||
Cell getCell(const Vector3int16& pos) const;
|
||||
CellMaterial getCellMaterial(const Vector3int16& pos) const;
|
||||
Cell getWaterCell(const Vector3int16& pos) const;
|
||||
|
||||
// Gets live chunk ids
|
||||
std::vector<SpatialRegion::Id> getNonEmptyChunks() const;
|
||||
std::vector<SpatialRegion::Id> getNonEmptyChunksInRegion(const Region3int16& extents) const;
|
||||
|
||||
bool isAllocated() const;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
#include "Voxel/Grid.Chunk.h"
|
||||
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Voxel/Util.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
// Read-only view of a contiguous, axis-aligned subsection of the entire voxel
|
||||
// grid.
|
||||
template<class InternalStorageType>
|
||||
class Region {
|
||||
public:
|
||||
class iterator;
|
||||
class xline_iterator;
|
||||
|
||||
Region();
|
||||
Region(const InternalStorageType* internalStorage,
|
||||
const Vector3int16& minCoords, const Vector3int16& maxCoords);
|
||||
|
||||
// Returns true if all cells in this iteration are definitely empty.
|
||||
// May return false if all cells are empty, but will never return true
|
||||
// if some cells are set.
|
||||
bool isGuaranteedAllEmpty() const;
|
||||
|
||||
// returns true if the global coordinate is queryable in this region
|
||||
bool contains(const Vector3int16& globalCoord) const;
|
||||
|
||||
// methods for querying voxel related data for a global voxel coordinate.
|
||||
inline const Cell& voxelAt(const Vector3int16& globalCoord) const;
|
||||
inline CellMaterial materialAt(const Vector3int16& globalCoord) const;
|
||||
inline bool hasWaterAt(const Vector3int16& globalCoord) const;
|
||||
|
||||
// methods to make this an iterable container
|
||||
iterator begin() const;
|
||||
const iterator& end() const;
|
||||
|
||||
xline_iterator xLineBegin() const;
|
||||
const xline_iterator& xLineEnd() const;
|
||||
|
||||
// Support methods
|
||||
Region& operator=(const Region& other);
|
||||
bool operator==(const Region& other) const;
|
||||
|
||||
private:
|
||||
static const Region kEndRegion;
|
||||
static const iterator kEndIterator;
|
||||
static const xline_iterator kEndXLineIterator;
|
||||
|
||||
const InternalStorageType* internalStorage;
|
||||
Vector3int16 minCoords;
|
||||
Vector3int16 maxCoords;
|
||||
|
||||
inline const Cell& voxelAtSkipAllEmptyCheck(const Vector3int16& globalCoord) const;
|
||||
inline bool hasWaterAtSkipAllEmptyCheck(const Cell& cell,
|
||||
const Vector3int16& globalCoord) const;
|
||||
|
||||
};
|
||||
|
||||
// Iterator for accessing all voxels inside a Region sequentially.
|
||||
// Iterates in Y-Z-X order (x axis is least significant ordered, y axis is
|
||||
// most significant).
|
||||
template<class InternalStorageType>
|
||||
class Region<InternalStorageType>::iterator {
|
||||
private:
|
||||
const Region<InternalStorageType>& owningRegion;
|
||||
|
||||
const Vector3int16 rangeSize;
|
||||
unsigned int pointerSkipAtEndOfXLine;
|
||||
unsigned int pointerSkipAtEndOfZLine;
|
||||
|
||||
// Internal iteration counters
|
||||
unsigned int xCounter, zCounter;
|
||||
bool reachedEnd;
|
||||
|
||||
// cached values (saved so that they aren't re-derived on each access)
|
||||
Vector3int16 currentLocation;
|
||||
unsigned int currentIndex;
|
||||
const Cell* currentCell;
|
||||
|
||||
public:
|
||||
iterator(const Region<InternalStorageType>& owningRegion);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Reading data
|
||||
|
||||
// Read at current location
|
||||
inline const Vector3int16& getCurrentLocation() const;
|
||||
inline const Cell& getCellAtCurrentLocation() const;
|
||||
inline bool hasWaterAtCurrentLocation() const;
|
||||
inline CellMaterial getMaterialAtCurrentLocation() const;
|
||||
|
||||
// Read neighbors of current location.
|
||||
// These methods should only be used when the caller knows that it is safe
|
||||
// to query those cells (that the cells are contained in the Region)
|
||||
inline const Cell& getNeighborCell(FaceDirection direction) const;
|
||||
inline const Cell& getNeighborCell(FaceDirection direction1, FaceDirection direction2) const;
|
||||
inline CellMaterial getNeighborMaterial(FaceDirection direction) const;
|
||||
inline CellMaterial getNeighborMaterial(FaceDirection direction1, FaceDirection direction2) const;
|
||||
inline const Cell& getArbitraryNeighborCell(const Vector3int16& neighborOffsets) const;
|
||||
inline bool hasWaterAtNeighbor(const FaceDirection& direction) const;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Normal iterator business
|
||||
|
||||
// ++prefix form
|
||||
inline iterator& operator++();
|
||||
|
||||
// operator== for terminating condition
|
||||
inline bool operator==(const iterator& other);
|
||||
inline bool operator!=(const iterator& other);
|
||||
};
|
||||
|
||||
// Limited iterator. Iterates over the region, in increments equal to the
|
||||
// X-Axis dimension of the region (aka over the the "xline"s of the region).
|
||||
// Allows working with the entire line in one shot, to enhance performance of
|
||||
// bulk operations like copying.
|
||||
template<class InternalStorageType>
|
||||
class Region<InternalStorageType>::xline_iterator {
|
||||
const Region& owningRegion;
|
||||
const unsigned int lineSize;
|
||||
const int minZ;
|
||||
const unsigned int zDimSize;
|
||||
const int maxY;
|
||||
|
||||
unsigned int pointerSkipAtEndOfXLine;
|
||||
unsigned int pointerSkipAtEndOfZLine;
|
||||
|
||||
unsigned int zDimCounter;
|
||||
|
||||
Vector3int16 currentLocation;
|
||||
unsigned int currentIndex;
|
||||
const Cell* currentCell;
|
||||
bool reachedEnd;
|
||||
public:
|
||||
|
||||
xline_iterator(const Region& owningRegion);
|
||||
|
||||
const Vector3int16& getCurrentLocation() const;
|
||||
unsigned int getLineSize() const;
|
||||
|
||||
// Returns a contiguous array of Cells that has exactly lineSize elements.
|
||||
// The first cell corresponds to the current location, and progress along
|
||||
// the positive X axis.
|
||||
const Cell* getLineCells() const;
|
||||
// Returns a contiguous array of unsigned char with exactly lineSize/2
|
||||
// elements. This is half-byte material information for the x line.
|
||||
const unsigned char* getLineMaterials() const;
|
||||
|
||||
bool operator==(const xline_iterator& other) const;
|
||||
bool operator!=(const xline_iterator& other) const;
|
||||
inline xline_iterator& operator++();
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
#include "Voxel/Region.inl"
|
||||
#include "Voxel/Region.iterator.inl"
|
||||
#include "Voxel/Region.xline_iterator.inl"
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include "Voxel/Water.h"
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// template implementation file for Region.h
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Region<InternalStorageType> Region<InternalStorageType>::kEndRegion(NULL, Vector3int16::one(), Vector3int16::zero());
|
||||
|
||||
template<class InternalStorageType>
|
||||
const typename Region<InternalStorageType>::iterator Region<InternalStorageType>::kEndIterator(Region<InternalStorageType>::kEndRegion);
|
||||
|
||||
template<class InternalStorageType>
|
||||
const typename Region<InternalStorageType>::xline_iterator Region<InternalStorageType>::kEndXLineIterator(Region<InternalStorageType>::kEndRegion);
|
||||
|
||||
template<class InternalStorageType>
|
||||
Region<InternalStorageType>::Region() :
|
||||
internalStorage(NULL), minCoords(Vector3int16::zero()), maxCoords(Vector3int16::zero()) {}
|
||||
|
||||
template<class InternalStorageType>
|
||||
Region<InternalStorageType>::Region(const InternalStorageType* internalStorage,
|
||||
const Vector3int16& minCoords, const Vector3int16& maxCoords) :
|
||||
internalStorage(internalStorage), minCoords(minCoords), maxCoords(maxCoords) {}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::isGuaranteedAllEmpty() const {
|
||||
return internalStorage == NULL;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::contains(const Vector3int16& globalCoord) const {
|
||||
return globalCoord.isBetweenInclusive(minCoords, maxCoords);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::voxelAt(
|
||||
const Vector3int16& globalCoord) const {
|
||||
RBXASSERT_SLOW(contains(globalCoord));
|
||||
|
||||
if (isGuaranteedAllEmpty()) {
|
||||
return Constants::kUniqueEmptyCellRepresentation;
|
||||
} else {
|
||||
return voxelAtSkipAllEmptyCheck(globalCoord);
|
||||
}
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
CellMaterial Region<InternalStorageType>::materialAt(
|
||||
const Vector3int16& globalCoord) const {
|
||||
RBXASSERT_SLOW(contains(globalCoord));
|
||||
|
||||
if (isGuaranteedAllEmpty()) {
|
||||
return CELL_MATERIAL_Water;
|
||||
} else {
|
||||
unsigned int index = internalStorage->voxelCoordToArrayIndex(globalCoord);
|
||||
return readMaterial(&internalStorage->getConstMaterial()[0],
|
||||
index, internalStorage->getConstData()[index]);
|
||||
}
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::hasWaterAt(
|
||||
const Vector3int16& globalCoord) const {
|
||||
RBXASSERT_SLOW(contains(globalCoord));
|
||||
|
||||
if (isGuaranteedAllEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
return hasWaterAtSkipAllEmptyCheck(
|
||||
voxelAtSkipAllEmptyCheck(globalCoord), globalCoord);
|
||||
}
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
typename Region<InternalStorageType>::iterator
|
||||
Region<InternalStorageType>::begin() const {
|
||||
return iterator(*this);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const typename Region<InternalStorageType>::iterator&
|
||||
Region<InternalStorageType>::end() const {
|
||||
return kEndIterator;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
typename Region<InternalStorageType>::xline_iterator
|
||||
Region<InternalStorageType>::xLineBegin() const {
|
||||
return xline_iterator(*this);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const typename Region<InternalStorageType>::xline_iterator&
|
||||
Region<InternalStorageType>::xLineEnd() const {
|
||||
return kEndXLineIterator;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
Region<InternalStorageType>& Region<InternalStorageType>::operator=(
|
||||
const Region<InternalStorageType>& other) {
|
||||
internalStorage = other.internalStorage;
|
||||
minCoords = other.minCoords;
|
||||
maxCoords = other.maxCoords;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::operator==(
|
||||
const Region<InternalStorageType>& other) const {
|
||||
return internalStorage == other.internalStorage &&
|
||||
minCoords == other.minCoords &&
|
||||
maxCoords == other.maxCoords;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::voxelAtSkipAllEmptyCheck(
|
||||
const Vector3int16& globalCoord) const {
|
||||
RBXASSERT_SLOW(!isGuaranteedAllEmpty());
|
||||
return internalStorage->getConstData()[
|
||||
internalStorage->voxelCoordToArrayIndex(globalCoord)];
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::hasWaterAtSkipAllEmptyCheck(
|
||||
const Cell& cell,
|
||||
const Vector3int16& globalCoord) const {
|
||||
RBXASSERT_SLOW(!isGuaranteedAllEmpty());
|
||||
return Water::cellHasWater(internalStorage, cell, globalCoord);
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,194 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Implementation file for Region.iterator
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
namespace VoxelIteratorConstants {
|
||||
const Vector3int16 kFaceDirectionToLocationOffset[6] =
|
||||
{
|
||||
Vector3int16( 1, 0, 0),
|
||||
Vector3int16( 0, 0, 1),
|
||||
Vector3int16(-1, 0, 0),
|
||||
Vector3int16( 0, 0,-1),
|
||||
Vector3int16( 0, 1, 0),
|
||||
Vector3int16( 0,-1, 0),
|
||||
};
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
Region<InternalStorageType>::iterator::iterator(
|
||||
const Region<InternalStorageType>& owningRegion) :
|
||||
owningRegion(owningRegion),
|
||||
rangeSize((owningRegion.maxCoords - owningRegion.minCoords) + Vector3int16::one()),
|
||||
xCounter(0), zCounter(0), reachedEnd(false) {
|
||||
|
||||
// read min and max coord from owning region to simplify constructor logic
|
||||
Vector3int16 minCoord = owningRegion.minCoords;
|
||||
Vector3int16 maxCoord = owningRegion.maxCoords;
|
||||
|
||||
if (!owningRegion.isGuaranteedAllEmpty()) {
|
||||
// For speed, this implementation keeps a pointer to the current voxel.
|
||||
// In order to implement operator++, we want to keep a "carriage return"
|
||||
// pointer offset, for when the pointer needs to go from the end of
|
||||
// an x line to the beginning of the x line in the next z line, and
|
||||
// another offset for when the pointer needs to go from the end of
|
||||
// an x-z plane to the beginning of the plane in the next y level.
|
||||
|
||||
// The "carriage return" offset for the end of an x line should be
|
||||
// zero in the degenerate case where the z dimension is 1.
|
||||
|
||||
// skip at end of x line: (minX,minY,minZ+1) - (maxX,minY,minZ)
|
||||
pointerSkipAtEndOfXLine = 0;
|
||||
if (rangeSize.z > 1) {
|
||||
pointerSkipAtEndOfXLine =
|
||||
owningRegion.internalStorage->voxelCoordToArrayIndex(
|
||||
Vector3int16(minCoord.x, minCoord.y, minCoord.z + 1)) -
|
||||
owningRegion.internalStorage->voxelCoordToArrayIndex(
|
||||
Vector3int16(maxCoord.x, minCoord.y, minCoord.z));
|
||||
}
|
||||
|
||||
// skip at end of x-z plane: (minX,minY+1,minZ) - (maxX,minY,maxZ)
|
||||
pointerSkipAtEndOfZLine = 0;
|
||||
if (rangeSize.y > 1) {
|
||||
pointerSkipAtEndOfZLine =
|
||||
owningRegion.internalStorage->voxelCoordToArrayIndex(
|
||||
Vector3int16(minCoord.x, minCoord.y + 1, minCoord.z)) -
|
||||
owningRegion.internalStorage->voxelCoordToArrayIndex(
|
||||
Vector3int16(maxCoord.x, minCoord.y, maxCoord.z));
|
||||
}
|
||||
|
||||
currentLocation = minCoord;
|
||||
currentIndex = owningRegion.internalStorage->voxelCoordToArrayIndex(currentLocation);
|
||||
currentCell = &owningRegion.internalStorage->getConstData()[currentIndex];
|
||||
reachedEnd = currentLocation.y > maxCoord.y;
|
||||
} else {
|
||||
reachedEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Vector3int16& Region<InternalStorageType>::iterator::getCurrentLocation() const {
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::iterator::getCellAtCurrentLocation() const {
|
||||
return *currentCell;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::iterator::hasWaterAtCurrentLocation() const {
|
||||
return owningRegion.hasWaterAtSkipAllEmptyCheck(*currentCell, currentLocation);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
CellMaterial Region<InternalStorageType>::iterator::getMaterialAtCurrentLocation() const {
|
||||
return (CellMaterial)readMaterial(
|
||||
&owningRegion.internalStorage->getConstMaterial()[0], currentIndex, *currentCell);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::iterator::getNeighborCell(
|
||||
FaceDirection direction) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation +
|
||||
kFaceDirectionToLocationOffset[direction]));
|
||||
return currentCell[
|
||||
InternalStorageType::kFaceDirectionToPointerOffset[direction]];
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::iterator::getNeighborCell(
|
||||
FaceDirection direction1, FaceDirection direction2) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation +
|
||||
kFaceDirectionToLocationOffset[direction1] + kFaceDirectionToLocationOffset[direction2]));
|
||||
return currentCell[
|
||||
InternalStorageType::kFaceDirectionToPointerOffset[direction1] + InternalStorageType::kFaceDirectionToPointerOffset[direction2]];
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
CellMaterial Region<InternalStorageType>::iterator::getNeighborMaterial(
|
||||
FaceDirection direction) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation +
|
||||
kFaceDirectionToLocationOffset[direction]));
|
||||
const int offset(InternalStorageType::kFaceDirectionToPointerOffset[direction]);
|
||||
return (CellMaterial)readMaterial(&owningRegion.internalStorage->getConstMaterial()[0],
|
||||
currentIndex + offset, currentCell[offset]);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
CellMaterial Region<InternalStorageType>::iterator::getNeighborMaterial(
|
||||
FaceDirection direction1, FaceDirection direction2) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation +
|
||||
kFaceDirectionToLocationOffset[direction1] + kFaceDirectionToLocationOffset[direction2));
|
||||
const int offset(InternalStorageType::kFaceDirectionToPointerOffset[direction1] + InternalStorageType::kFaceDirectionToPointerOffset[direction2]);
|
||||
return (CellMaterial)readMaterial(&owningRegion.internalStorage->getConstMaterial()[0],
|
||||
currentIndex + offset, currentCell[offset]);
|
||||
}
|
||||
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell& Region<InternalStorageType>::iterator::getArbitraryNeighborCell(
|
||||
const Vector3int16& neighborOffsets) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation + neighborOffsets));
|
||||
return currentCell[
|
||||
InternalStorageType::voxelCoordOffsetToIndexOffset(neighborOffsets)];
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::iterator::hasWaterAtNeighbor(
|
||||
const FaceDirection& direction) const {
|
||||
RBXASSERT_SLOW(owningRegion.contains(currentLocation +
|
||||
kFaceDirectionToLocationOffset[direction]));
|
||||
return owningRegion.hasWaterAtSkipAllEmptyCheck(
|
||||
currentCell[InternalStorageType::kFaceDirectionToPointerOffset[direction]],
|
||||
currentLocation +
|
||||
VoxelIteratorConstants::kFaceDirectionToLocationOffset[direction]);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
typename Region<InternalStorageType>::iterator&
|
||||
Region<InternalStorageType>::iterator::operator++() {
|
||||
++xCounter;
|
||||
if (xCounter == rangeSize.x) {
|
||||
xCounter = 0;
|
||||
++zCounter;
|
||||
if (zCounter == rangeSize.z) {
|
||||
zCounter = 0;
|
||||
currentLocation.x = owningRegion.minCoords.x;
|
||||
++currentLocation.y;
|
||||
currentLocation.z = owningRegion.minCoords.z;
|
||||
currentIndex += pointerSkipAtEndOfZLine;
|
||||
currentCell += pointerSkipAtEndOfZLine;
|
||||
} else {
|
||||
currentLocation.x = owningRegion.minCoords.x;
|
||||
++currentLocation.z;
|
||||
currentIndex += pointerSkipAtEndOfXLine;
|
||||
currentCell += pointerSkipAtEndOfXLine;
|
||||
}
|
||||
} else {
|
||||
++currentIndex;
|
||||
++currentCell;
|
||||
++currentLocation.x;
|
||||
}
|
||||
|
||||
reachedEnd = currentLocation.y > owningRegion.maxCoords.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::iterator::operator==(const iterator& other) {
|
||||
if (reachedEnd || other.reachedEnd) {
|
||||
return reachedEnd == other.reachedEnd;
|
||||
}
|
||||
return owningRegion == other.owningRegion &&
|
||||
currentLocation == other.currentLocation;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::iterator::operator!=(const iterator& other) {
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
template<class InternalStorageType>
|
||||
Region<InternalStorageType>::xline_iterator::xline_iterator(
|
||||
const Region<InternalStorageType>& owningRegion) :
|
||||
owningRegion(owningRegion),
|
||||
lineSize(owningRegion.maxCoords.x - owningRegion.minCoords.x + 1),
|
||||
minZ(owningRegion.minCoords.z),
|
||||
zDimSize(owningRegion.maxCoords.z - owningRegion.minCoords.z + 1),
|
||||
maxY(owningRegion.maxCoords.y) {
|
||||
|
||||
zDimCounter = 0;
|
||||
|
||||
if (!owningRegion.isGuaranteedAllEmpty()) {
|
||||
currentLocation = owningRegion.minCoords;
|
||||
|
||||
pointerSkipAtEndOfXLine = InternalStorageType::voxelCoordOffsetToIndexOffset(
|
||||
Vector3int16(0, 0, 1));
|
||||
pointerSkipAtEndOfZLine = InternalStorageType::voxelCoordOffsetToIndexOffset(
|
||||
Vector3int16(0, 1, owningRegion.minCoords.z - owningRegion.maxCoords.z));
|
||||
|
||||
currentIndex = owningRegion.internalStorage->voxelCoordToArrayIndex(currentLocation);
|
||||
currentCell = &owningRegion.internalStorage->getConstData()[currentIndex];
|
||||
reachedEnd = currentLocation.y > owningRegion.maxCoords.y;
|
||||
|
||||
// index needs to be even for half byte material alignment reasons
|
||||
RBXASSERT((currentIndex & 0x1) == 0);
|
||||
} else {
|
||||
reachedEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Vector3int16& Region<InternalStorageType>::xline_iterator::getCurrentLocation() const {
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
unsigned int Region<InternalStorageType>::xline_iterator::getLineSize() const {
|
||||
return lineSize;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const Cell* Region<InternalStorageType>::xline_iterator::getLineCells() const {
|
||||
return currentCell;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
const unsigned char* Region<InternalStorageType>::xline_iterator::getLineMaterials() const {
|
||||
return &owningRegion.internalStorage->getConstMaterial()[currentIndex / 2];
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::xline_iterator::operator==(
|
||||
const xline_iterator& other) const {
|
||||
if (reachedEnd || other.reachedEnd) {
|
||||
return reachedEnd == other.reachedEnd;
|
||||
}
|
||||
return owningRegion == other.owningRegion &&
|
||||
currentLocation == other.currentLocation;
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
bool Region<InternalStorageType>::xline_iterator::operator!=(
|
||||
const xline_iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
template<class InternalStorageType>
|
||||
typename Region<InternalStorageType>::xline_iterator&
|
||||
Region<InternalStorageType>::xline_iterator::operator++() {
|
||||
|
||||
++currentLocation.z;
|
||||
++zDimCounter;
|
||||
if (zDimCounter >= zDimSize) {
|
||||
currentLocation.z = minZ;
|
||||
zDimCounter = 0;
|
||||
++currentLocation.y;
|
||||
currentIndex += pointerSkipAtEndOfZLine;
|
||||
currentCell += pointerSkipAtEndOfZLine;
|
||||
} else {
|
||||
currentIndex += pointerSkipAtEndOfXLine;
|
||||
currentCell += pointerSkipAtEndOfXLine;
|
||||
}
|
||||
|
||||
// index needs to be even for half byte material alignment reasons
|
||||
RBXASSERT((currentIndex & 0x1) == 0);
|
||||
|
||||
reachedEnd = currentLocation.y > maxY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,241 @@
|
||||
#pragma once
|
||||
|
||||
#include "RBX/Debug.h"
|
||||
#include "Util/ClusterCellIterator.h"
|
||||
#include "Util/FixedSizeCircularBuffer.h"
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Util/SpatialRegion.h"
|
||||
#include "Util/VarInt.h"
|
||||
#include "Voxel/Cell.h"
|
||||
#include "Voxel/Grid.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
class SerializerConstants {
|
||||
public:
|
||||
// these values are used for serializing cluster
|
||||
// they are visible for testing
|
||||
static const unsigned char kNewCellMarker;
|
||||
static const unsigned char kRepeatCellMarker;
|
||||
static const unsigned char kEndSequenceMarker;
|
||||
static const unsigned int kRecentlyEncodedReferenceBits;
|
||||
};
|
||||
|
||||
class Serializer {
|
||||
typedef FixedSizeCircularBuffer<unsigned int, 8> RecentlyEncodedBuffer;
|
||||
|
||||
template<class CellBuffer, class OutputStream>
|
||||
void encodeFromPosition(const Grid* voxelStore, Vector3int16& cellpos,
|
||||
const SpatialRegion::Id& lastChunkPos, const Grid::Region& region,
|
||||
RecentlyEncodedBuffer& lastSeenNewCells,
|
||||
CellBuffer& cellBuffer, OutputStream* outputStream) const {
|
||||
|
||||
unsigned char cellValue = Cell::serializeAsUnsignedChar(region.voxelAt(cellpos));
|
||||
unsigned char materialValue = region.materialAt(cellpos);
|
||||
|
||||
unsigned int content = (materialValue << 8) | cellValue;
|
||||
Vector3int16 unread;
|
||||
|
||||
unsigned int findIndex;
|
||||
bool isOldContent = lastSeenNewCells.find(content, &findIndex);
|
||||
|
||||
if (!isOldContent) {
|
||||
outputStream->WriteBits(&SerializerConstants::kNewCellMarker, 2);
|
||||
outputStream->WriteBits(&materialValue, 8);
|
||||
outputStream->WriteBits(&cellValue, 8);
|
||||
lastSeenNewCells.push(content);
|
||||
CellBuffer::nextCellInIterationOrder(cellpos, &cellpos);
|
||||
} else {
|
||||
// TODO: The cell reads in this section aren't safe! They will read
|
||||
// past the end of the cluster's data array.
|
||||
unsigned int copyCount = 1; // this cell is a copy
|
||||
Vector3int16 nextPos;
|
||||
CellBuffer::nextCellInIterationOrder(cellpos, &nextPos);
|
||||
|
||||
SpatialRegion::Id nextChunk = SpatialRegion::regionContainingVoxel(nextPos);
|
||||
|
||||
unsigned char nextCellValue = Cell::serializeAsUnsignedChar(region.voxelAt(nextPos));
|
||||
unsigned char nextMaterialValue = region.materialAt(nextPos);
|
||||
unsigned int nextContent = (nextMaterialValue << 8) | nextCellValue;
|
||||
|
||||
while (nextChunk == lastChunkPos && cellBuffer.chk(nextPos) &&
|
||||
nextContent == content) {
|
||||
copyCount++;
|
||||
cellBuffer.pop(&unread);
|
||||
RBXASSERT(nextPos == unread);
|
||||
CellBuffer::nextCellInIterationOrder(nextPos, &nextPos);
|
||||
nextChunk = SpatialRegion::regionContainingVoxel(nextPos);
|
||||
nextCellValue = Cell::serializeAsUnsignedChar(region.voxelAt(nextPos));
|
||||
nextMaterialValue = region.materialAt(nextPos);
|
||||
nextContent = (nextMaterialValue << 8) | nextCellValue;
|
||||
}
|
||||
|
||||
cellpos = nextPos;
|
||||
outputStream->WriteBits(&SerializerConstants::kRepeatCellMarker, 2);
|
||||
unsigned char charFindIndex = findIndex;
|
||||
outputStream->WriteBits(&charFindIndex, SerializerConstants::kRecentlyEncodedReferenceBits);
|
||||
VarInt<>::encode(*outputStream, copyCount);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
template<class CellBuffer, class OutputStream>
|
||||
void encodeCells(const Grid* voxelStore, CellBuffer& cellBuffer,
|
||||
OutputStream* outputStream, int sizeLimitInBytes) const {
|
||||
|
||||
const Vector3int16 kCellInChunkBits(
|
||||
SpatialRegion::getRegionDimensionInVoxelsAsBitShifts());
|
||||
|
||||
RecentlyEncodedBuffer lastSeenNewCells;
|
||||
Grid::Region region;
|
||||
SpatialRegion::Id lastChunkPos(SHRT_MIN, SHRT_MIN, SHRT_MIN);
|
||||
while(cellBuffer.size() > 0 && (sizeLimitInBytes == -1 || ((int)outputStream->GetNumberOfBytesUsed()) < sizeLimitInBytes))
|
||||
{
|
||||
Vector3int16 cellpos;
|
||||
cellBuffer.pop(&cellpos);
|
||||
|
||||
SpatialRegion::Id chunk = SpatialRegion::regionContainingVoxel(cellpos);
|
||||
Vector3int16 cellModChunk = SpatialRegion::voxelCoordinateRelativeToEnclosingRegion(cellpos);
|
||||
|
||||
unsigned char chunkChanged = 0;
|
||||
if (chunk != lastChunkPos) {
|
||||
lastChunkPos = chunk;
|
||||
chunkChanged = 1;
|
||||
outputStream->WriteBits(&chunkChanged, 1);
|
||||
|
||||
// write a "0" to indicate that we aren't finished
|
||||
chunkChanged = 0;
|
||||
outputStream->WriteBits(&chunkChanged, 1);
|
||||
|
||||
boost::int16_t data = chunk.value().x;
|
||||
outputStream->WriteBits(reinterpret_cast<unsigned char*>(&data), 16);
|
||||
data = chunk.value().y;
|
||||
outputStream->WriteBits(reinterpret_cast<unsigned char*>(&data), 16);
|
||||
data = chunk.value().z;
|
||||
outputStream->WriteBits(reinterpret_cast<unsigned char*>(&data), 16);
|
||||
|
||||
Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(chunk);
|
||||
region = voxelStore->getRegion(extents.getMinPos(), extents.getMaxPos());
|
||||
} else {
|
||||
outputStream->WriteBits(&chunkChanged, 1);
|
||||
}
|
||||
|
||||
unsigned char data = cellModChunk.x;
|
||||
outputStream->WriteBits(&data, kCellInChunkBits.x);
|
||||
data = cellModChunk.y;
|
||||
outputStream->WriteBits(&data, kCellInChunkBits.y);
|
||||
data = cellModChunk.z;
|
||||
outputStream->WriteBits(&data, kCellInChunkBits.z);
|
||||
|
||||
Vector3int16& nextPos = cellpos;
|
||||
bool continuing = true;
|
||||
do {
|
||||
encodeFromPosition(voxelStore, nextPos, lastChunkPos, region, lastSeenNewCells,
|
||||
cellBuffer, outputStream);
|
||||
continuing = cellBuffer.chk(nextPos) &&
|
||||
SpatialRegion::regionContainingVoxel(nextPos) == lastChunkPos &&
|
||||
(sizeLimitInBytes == -1 || ((int)outputStream->GetNumberOfBytesUsed()) < sizeLimitInBytes);
|
||||
if (continuing) {
|
||||
Vector3int16 unused;
|
||||
cellBuffer.pop(&unused);
|
||||
RBXASSERT(unused == nextPos);
|
||||
}
|
||||
} while (continuing);
|
||||
|
||||
unsigned char endSequenceMarker = SerializerConstants::kEndSequenceMarker;
|
||||
outputStream->WriteBits(&endSequenceMarker, 2);
|
||||
}
|
||||
|
||||
// write finalizer
|
||||
unsigned char finalValue = 0xff;
|
||||
// write 1 bit for chunk changed, and one bit to indicate EOM
|
||||
outputStream->WriteBits(&finalValue, 2);
|
||||
}
|
||||
|
||||
template<class CellBuffer, class InputStream, class CellUpdateFilter>
|
||||
void decodeCells(Grid* voxelStore, InputStream& inputStream,
|
||||
CellUpdateFilter& filter) {
|
||||
const Vector3int16 kCellInChunkBits(
|
||||
SpatialRegion::getRegionDimensionInVoxelsAsBitShifts());
|
||||
|
||||
RecentlyEncodedBuffer lastSeenNewCells;
|
||||
SpatialRegion::Id chunkPos(SHRT_MIN, SHRT_MIN, SHRT_MIN);
|
||||
|
||||
while(1)
|
||||
{
|
||||
unsigned char changedChunk;
|
||||
inputStream.ReadBits(&changedChunk, 1);
|
||||
if (changedChunk) {
|
||||
unsigned char eomTokenReceived = 0;
|
||||
inputStream.ReadBits(&eomTokenReceived, 1);
|
||||
if (eomTokenReceived) {
|
||||
break;
|
||||
}
|
||||
|
||||
boost::int16_t x, y, z;
|
||||
|
||||
inputStream.ReadBits(reinterpret_cast<unsigned char*>(&x), 16);
|
||||
inputStream.ReadBits(reinterpret_cast<unsigned char*>(&y), 16);
|
||||
inputStream.ReadBits(reinterpret_cast<unsigned char*>(&z), 16);
|
||||
|
||||
chunkPos = SpatialRegion::Id(x, y, z);
|
||||
}
|
||||
Vector3int16 cellPos(0,0,0);
|
||||
|
||||
unsigned char data;
|
||||
inputStream.ReadBits(&data, kCellInChunkBits.x);
|
||||
cellPos.x = data;
|
||||
inputStream.ReadBits(&data, kCellInChunkBits.y);
|
||||
cellPos.y = data;
|
||||
inputStream.ReadBits(&data, kCellInChunkBits.z);
|
||||
cellPos.z = data;
|
||||
cellPos = SpatialRegion::globalVoxelCoordinateFromRegionAndRelativeCoordinate(
|
||||
chunkPos, cellPos);
|
||||
unsigned char controlBits;
|
||||
do {
|
||||
inputStream.ReadBits(&controlBits, 2);
|
||||
if (controlBits == SerializerConstants::kNewCellMarker) {
|
||||
unsigned char material, cell;
|
||||
inputStream.ReadBits(&material, 8);
|
||||
inputStream.ReadBits(&cell, 8);
|
||||
|
||||
unsigned int content = (material << 8) | cell;
|
||||
|
||||
lastSeenNewCells.push(content);
|
||||
|
||||
if (filter.canSet(cellPos)) {
|
||||
voxelStore->setCell(cellPos, Cell::deserializeFromUnsignedChar(cell), (CellMaterial)material);
|
||||
}
|
||||
// advance cellPos
|
||||
CellBuffer::nextCellInIterationOrder(cellPos, &cellPos);
|
||||
} else if (controlBits == SerializerConstants::kRepeatCellMarker) {
|
||||
unsigned char backIndex;
|
||||
inputStream.ReadBits(&backIndex, SerializerConstants::kRecentlyEncodedReferenceBits);
|
||||
unsigned int count = 0;
|
||||
VarInt<>::decode(inputStream, &count);
|
||||
RBXASSERT(count > 0);
|
||||
|
||||
unsigned int content = lastSeenNewCells[backIndex];
|
||||
unsigned char material = content >> 8;
|
||||
unsigned char cell = content & 0xFF;
|
||||
|
||||
do {
|
||||
if (filter.canSet(cellPos)) {
|
||||
voxelStore->setCell(cellPos,
|
||||
Cell::deserializeFromUnsignedChar(cell),
|
||||
(CellMaterial)material);
|
||||
}
|
||||
CellBuffer::nextCellInIterationOrder(cellPos, &cellPos);
|
||||
count--;
|
||||
} while (count);
|
||||
// at this point cellPos points to the next cell after the sequence
|
||||
}
|
||||
} while(controlBits != SerializerConstants::kEndSequenceMarker);
|
||||
// do not read cellPos after this line, the do {} while() loop ends with
|
||||
// cellPos at an invalid cell.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} } // namespace RBX
|
||||
@@ -0,0 +1,167 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Voxel/Cell.h"
|
||||
#include "rbx/Debug.h"
|
||||
#include "Util/Extents.h"
|
||||
#include "Util/Region3int16.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This file has methods for reading and writing individual voxel cells
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
inline CellMaterial getCellMaterial_Deprecated( unsigned char cell ) { return (CellMaterial)(cell & 0x07); }
|
||||
inline void setCellMaterial_Deprecated( unsigned char& cell, CellMaterial material ) { cell = (cell & 0xf8) | ((int)material & 0x07); }
|
||||
|
||||
inline CellMaterial readMaterial(const unsigned char* materials, const unsigned int cellIndex, const Cell cell) {
|
||||
return (CellMaterial)(
|
||||
cell.solid.getBlock() == CELL_BLOCK_Empty ?
|
||||
CELL_MATERIAL_Water :
|
||||
((materials[cellIndex >> 1] >> (4 * (cellIndex & 0x1))) & 0x0f) + 1);
|
||||
}
|
||||
inline void writeMaterial(unsigned char* materials, unsigned int cellIndex, const CellMaterial newMaterial) {
|
||||
RBXASSERT(newMaterial > 0);
|
||||
unsigned char& wholeByte = materials[cellIndex >> 1];
|
||||
unsigned int shift = (4 * (cellIndex & 0x1));
|
||||
unsigned char mask = 0x0f << shift;
|
||||
wholeByte &= (~mask);
|
||||
wholeByte |= (((newMaterial-1) << shift) & mask);
|
||||
}
|
||||
|
||||
enum FaceDirection
|
||||
{
|
||||
PlusX = 0,
|
||||
PlusZ = 1,
|
||||
MinusX = 2,
|
||||
MinusZ = 3,
|
||||
PlusY = 4,
|
||||
MinusY = 5,
|
||||
Invalid = 6
|
||||
};
|
||||
|
||||
struct BlockAxisFace {
|
||||
enum SkippedCorner {
|
||||
TopRight = 0,
|
||||
TopLeft = 1,
|
||||
BottomLeft = 2,
|
||||
BottomRight = 3,
|
||||
EmptyAllSkipped = 4,
|
||||
FullNoneSkipped = 5
|
||||
};
|
||||
|
||||
SkippedCorner skippedCorner;
|
||||
|
||||
static inline SkippedCorner rotate(SkippedCorner corner, const CellOrientation orient) {
|
||||
return (SkippedCorner) (corner < 4 ? (corner + orient) % 4 : corner);
|
||||
}
|
||||
|
||||
static inline bool divideTopLeftToBottomRight(SkippedCorner corner) {
|
||||
return corner == TopRight || corner == BottomLeft || corner == FullNoneSkipped;
|
||||
}
|
||||
|
||||
static inline SkippedCorner XZAxisMirror(SkippedCorner corner) {
|
||||
static SkippedCorner MIRROR[6] =
|
||||
{ TopLeft, TopRight, BottomRight, BottomLeft, EmptyAllSkipped, FullNoneSkipped };
|
||||
return MIRROR[corner];
|
||||
}
|
||||
|
||||
static inline SkippedCorner YAxisMirror(SkippedCorner corner) {
|
||||
static SkippedCorner MIRROR[6] =
|
||||
{ BottomRight, BottomLeft, TopLeft, TopRight, EmptyAllSkipped, FullNoneSkipped };
|
||||
return MIRROR[corner];
|
||||
}
|
||||
|
||||
static BlockAxisFace inverse(const BlockAxisFace other) {
|
||||
static const SkippedCorner OPPOSITE_CORNER[6] = {
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
TopRight,
|
||||
TopLeft,
|
||||
FullNoneSkipped,
|
||||
EmptyAllSkipped
|
||||
};
|
||||
|
||||
BlockAxisFace out;
|
||||
out.skippedCorner = OPPOSITE_CORNER[other.skippedCorner];
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
struct BlockFaceInfo {
|
||||
// indexed by FaceDirection
|
||||
BlockAxisFace faces[6];
|
||||
};
|
||||
|
||||
extern const BlockFaceInfo UnOrientedBlockFaceInfos[6];
|
||||
extern BlockAxisFace OrientedFaceMap[ 1536 ]; // 2^8 * 6
|
||||
|
||||
// ComputeOrientedFace is not declared because it is an implementation detail
|
||||
void initBlockOrientationFaceMap();
|
||||
|
||||
inline const BlockAxisFace& GetOrientedFace(Cell cell, FaceDirection f)
|
||||
{
|
||||
return OrientedFaceMap[ Cell::asUnsignedCharForDeprecatedUses(cell)*6 + f ];
|
||||
}
|
||||
|
||||
inline bool isWedgeSideNotFull(Cell voxel, FaceDirection f) {
|
||||
return GetOrientedFace(voxel, f).skippedCorner != BlockAxisFace::FullNoneSkipped;
|
||||
}
|
||||
|
||||
inline Vector3int16 worldToCell_floor(const Vector3& worldPos) {
|
||||
const int kXZOffset = 0;
|
||||
return Vector3int16(
|
||||
(int)(floorf(worldPos.x / kCELL_SIZE)) + kXZOffset,
|
||||
(int)(floorf(worldPos.y / kCELL_SIZE)),
|
||||
(int)(floorf(worldPos.z / kCELL_SIZE)) + kXZOffset);
|
||||
}
|
||||
|
||||
inline Vector3 worldSpaceToCellSpace(const Vector3& worldPos) {
|
||||
return Vector3(
|
||||
(worldPos.x * (1.0f / kCELL_SIZE)),
|
||||
(worldPos.y * (1.0f / kCELL_SIZE)),
|
||||
(worldPos.z * (1.0f / kCELL_SIZE)));
|
||||
}
|
||||
|
||||
inline Vector3 cellSpaceToWorldSpace(const Vector3& cellPos)
|
||||
{
|
||||
return Vector3(
|
||||
(cellPos.x * kCELL_SIZE),
|
||||
(cellPos.y * kCELL_SIZE),
|
||||
(cellPos.z * kCELL_SIZE));
|
||||
}
|
||||
|
||||
|
||||
inline Vector3 cellToWorld_smallestCorner(const Vector3int16& cellPos) {
|
||||
const int kXZOffset = 0;
|
||||
return Vector3(
|
||||
(cellPos.x - kXZOffset) * kCELL_SIZE,
|
||||
cellPos.y * kCELL_SIZE,
|
||||
(cellPos.z - kXZOffset) * kCELL_SIZE);
|
||||
|
||||
}
|
||||
|
||||
inline Vector3 cellToWorld_center(const Vector3int16& cellPos) {
|
||||
Vector3 pos = cellToWorld_smallestCorner(cellPos);
|
||||
return pos + Vector3(kHALF_CELL, kHALF_CELL, kHALF_CELL);
|
||||
}
|
||||
|
||||
inline Vector3 cellToWorld_largestCorner(const Vector3int16& cellPos) {
|
||||
return cellToWorld_smallestCorner(cellPos + Vector3int16(1, 1, 1));
|
||||
}
|
||||
|
||||
inline Region3int16 getTerrainExtentsInCells()
|
||||
{
|
||||
const int kRadius = 32000;
|
||||
|
||||
return Region3int16(Vector3int16(-kRadius, -kRadius, -kRadius), Vector3int16(kRadius, kRadius, kRadius));
|
||||
}
|
||||
|
||||
inline Extents getTerrainExtents()
|
||||
{
|
||||
Region3int16 extents = getTerrainExtentsInCells();
|
||||
|
||||
return Extents(cellToWorld_smallestCorner(extents.getMinPos()), cellToWorld_largestCorner(extents.getMaxPos()));
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
// suffix header file for Grid.h
|
||||
|
||||
#include "Util/SpatialRegion.h"
|
||||
#include "Util/Extents.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace RBX {
|
||||
class MegaClusterInstance;
|
||||
class ContactManager;
|
||||
class PartInstance;
|
||||
|
||||
namespace Voxel { class Grid; }
|
||||
namespace Voxel2 { class Grid; }
|
||||
|
||||
const int kVoxelChunkSizeXZ = 32;
|
||||
const int kVoxelChunkSizeY = 16;
|
||||
|
||||
const Vector3int32 kVoxelChunkSize = Vector3int32(kVoxelChunkSizeXZ, kVoxelChunkSizeY, kVoxelChunkSizeXZ);
|
||||
|
||||
namespace Voxel {
|
||||
|
||||
struct OccupancyChunk
|
||||
{
|
||||
unsigned int dirty;
|
||||
unsigned int age;
|
||||
Vector3int32 index;
|
||||
unsigned char occupancy[kVoxelChunkSizeY][kVoxelChunkSizeXZ][kVoxelChunkSizeXZ];
|
||||
Extents getChunkExtents() const;
|
||||
};
|
||||
|
||||
struct DataModelPartCache;
|
||||
|
||||
class Voxelizer
|
||||
{
|
||||
public:
|
||||
Voxelizer(bool collisionTransparency = false);
|
||||
|
||||
void occupancyUpdateChunk(OccupancyChunk& chunk, MegaClusterInstance* terrain, ContactManager* contactManager);
|
||||
|
||||
void occupancyUpdateChunkPrepare(OccupancyChunk& chunk, MegaClusterInstance* terrain, ContactManager* contactManager, std::vector<DataModelPartCache>& partCache);
|
||||
void occupancyUpdateChunkPerform(const std::vector<DataModelPartCache>& partCache);
|
||||
|
||||
void setNonFixedPartsEnabled(bool value) { nonFixedPartsEnabled = value; }
|
||||
bool getNonFixedPartsEnabled() const { return nonFixedPartsEnabled; }
|
||||
|
||||
private:
|
||||
void occupancyFillTerrainMega(OccupancyChunk& chunk, Voxel::Grid& terrain, const Vector3int32& chunkOffset, const Extents& chunkExtents);
|
||||
void occupancyFillTerrainMegaSIMD(OccupancyChunk& chunk, Voxel::Grid& terrain, const Vector3int32& chunkOffset, const Extents& chunkExtents);
|
||||
|
||||
void occupancyFillTerrainSmooth(OccupancyChunk& chunk, Voxel2::Grid& terrain, const Extents& chunkExtents);
|
||||
void occupancyFillTerrainSmoothSIMD(OccupancyChunk& chunk, Voxel2::Grid& terrain, const Extents& chunkExtents);
|
||||
|
||||
void occupancyFillBlock(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillBlockDF(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency);
|
||||
void occupancyFillBlockDFAA(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency);
|
||||
void occupancyFillBlockDFSIMD(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency);
|
||||
|
||||
void occupancyFillSphere(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillEllipsoid(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillCylinderX(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillCylinderY(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillWedge(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillCornerWedge(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillTorso(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
void occupancyFillMesh(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
|
||||
void addMeshToPartCache(std::vector<DataModelPartCache>& partCache, PartInstance* part, OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents_, const CoordinateFrame& cframe, float transparency);
|
||||
|
||||
template <typename DistanceFunction> void occupancyFillDF(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, DistanceFunction& df);
|
||||
|
||||
float getEffectiveTransparency(PartInstance* part);
|
||||
|
||||
bool useSIMD;
|
||||
bool nonFixedPartsEnabled;
|
||||
bool collisionTransparency;
|
||||
};
|
||||
|
||||
struct DataModelPartCache
|
||||
{
|
||||
typedef void (Voxelizer::*pfn)(OccupancyChunk& chunk, const Extents& chunkExtents, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius);
|
||||
|
||||
pfn fillFunc;
|
||||
OccupancyChunk* chunk;
|
||||
Vector3 extents;
|
||||
CoordinateFrame cframe;
|
||||
float transparency;
|
||||
float meshRadius;
|
||||
|
||||
DataModelPartCache(pfn fillFunc, OccupancyChunk& chunk, const Vector3& extents, const CoordinateFrame& cframe, float transparency, float meshRadius = 0)
|
||||
: fillFunc(fillFunc)
|
||||
, chunk(&chunk)
|
||||
, extents(extents)
|
||||
, cframe(cframe)
|
||||
, transparency(transparency)
|
||||
, meshRadius(meshRadius)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} }
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Voxel/Util.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
namespace Water {
|
||||
// Generate relative cell coords relevant to the water on wedge state of a
|
||||
// cell. Some locations will be initialized to the center location if they
|
||||
// are irelevant to the water on wedge state.
|
||||
struct RelevantNeighbors {
|
||||
const Vector3int16 aboveNeighbor;
|
||||
const Vector3int16 primaryNeighbor;
|
||||
const Vector3int16 secondaryNeighbor;
|
||||
const Vector3int16 diagonalNeighbor;
|
||||
const Vector3int16 diagonalUpNeighbor;
|
||||
|
||||
RelevantNeighbors(CellOrientation orientation);
|
||||
};
|
||||
|
||||
struct LocalAreaInfo {
|
||||
Cell aboveNeighbor;
|
||||
Cell primaryNeighbor;
|
||||
Cell secondaryNeighbor;
|
||||
Cell diagonalNeighbor;
|
||||
Cell diagonalUpNeighbor;
|
||||
};
|
||||
|
||||
template<class BoxType>
|
||||
inline bool cellHasWater(const BoxType* reader, const Cell& cell,
|
||||
const Vector3int16& globalCoord);
|
||||
template<class BoxType>
|
||||
Cell interpretAsWaterCell(const BoxType* reader, const Cell& cell,
|
||||
const Vector3int16& globalCoord);
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
#include "Voxel/Water.inl"
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include "Voxel/Util.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
namespace Water {
|
||||
|
||||
extern const RelevantNeighbors kRelevantNeighbors[MAX_CELL_ORIENTATIONS];
|
||||
|
||||
namespace {
|
||||
|
||||
const FaceDirection kOppositeFaceDirection[Invalid] = {
|
||||
MinusX,
|
||||
MinusZ,
|
||||
PlusX,
|
||||
PlusZ,
|
||||
MinusY,
|
||||
PlusY,
|
||||
};
|
||||
|
||||
const FaceDirection kPrimaryNeighborByOrientation[MAX_CELL_ORIENTATIONS] = {
|
||||
PlusZ,
|
||||
PlusX,
|
||||
MinusZ,
|
||||
MinusX
|
||||
};
|
||||
|
||||
const FaceDirection kSecondaryNeighborByOrientation[MAX_CELL_ORIENTATIONS] = {
|
||||
MinusX,
|
||||
PlusZ,
|
||||
PlusX,
|
||||
MinusZ
|
||||
};
|
||||
|
||||
const Vector3int16 kAboveNeighborCellOffset(0,1,0);
|
||||
const Vector3int16 kPrimaryNeighborCellOffset[MAX_CELL_ORIENTATIONS] = {
|
||||
Vector3int16(0,0,1),
|
||||
Vector3int16(1,0,0),
|
||||
Vector3int16(0,0,-1),
|
||||
Vector3int16(-1,0,0),
|
||||
};
|
||||
const Vector3int16 kSecondaryNeighborCellOffset[MAX_CELL_ORIENTATIONS] = {
|
||||
Vector3int16(-1,0,0),
|
||||
Vector3int16(0,0,1),
|
||||
Vector3int16(1,0,0),
|
||||
Vector3int16(0,0,-1),
|
||||
};
|
||||
|
||||
bool isWaterOnWedge(const Cell& center, const LocalAreaInfo& info) {
|
||||
if (center.solid.getBlock() != CELL_BLOCK_Empty && center.solid.getBlock() != CELL_BLOCK_Solid) {
|
||||
|
||||
if (info.aboveNeighbor.isExplicitWaterCell()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CellOrientation cellOrientation = center.solid.getOrientation();
|
||||
FaceDirection primaryDirection = kPrimaryNeighborByOrientation[cellOrientation];
|
||||
const Cell& primaryNeighbor = info.primaryNeighbor;
|
||||
|
||||
if (center.solid.getBlock() == CELL_BLOCK_VerticalWedge) {
|
||||
return primaryNeighbor.isExplicitWaterCell();
|
||||
} else {
|
||||
bool isPrimaryNeighborWater = primaryNeighbor.isExplicitWaterCell();
|
||||
bool isPrimarysSharedFaceNotSolidAndNotEmpty = !primaryNeighbor.isEmpty() &&
|
||||
isWedgeSideNotFull(primaryNeighbor, kOppositeFaceDirection[primaryDirection]);
|
||||
|
||||
FaceDirection secondaryDirection = kSecondaryNeighborByOrientation[cellOrientation];
|
||||
const Cell& secondaryNeighbor = info.secondaryNeighbor;
|
||||
bool isSecondaryNeighborWater = secondaryNeighbor.isExplicitWaterCell();
|
||||
bool isSecondarysSharedFaceNotSolidAndNotEmpty = !secondaryNeighbor.isEmpty() &&
|
||||
isWedgeSideNotFull(secondaryNeighbor, kOppositeFaceDirection[secondaryDirection]);
|
||||
|
||||
const Cell& diagonalNeighbor = info.diagonalNeighbor;
|
||||
bool isDiagonalWater = diagonalNeighbor.isExplicitWaterCell();
|
||||
|
||||
// add a special case for inv corner water wedges:
|
||||
// * can check the x, z, and +y offsets
|
||||
// * the block is an InverseCornerWedge
|
||||
// * x, z, and x+z offsets are all seperately not empty
|
||||
// * x + z + y offsets taken together contains explicit water
|
||||
bool inverseCornerWedgeVerticalDiagonalWaterCase =
|
||||
center.solid.getBlock() == CELL_BLOCK_InverseCornerWedge &&
|
||||
!primaryNeighbor.isEmpty() &&
|
||||
!secondaryNeighbor.isEmpty() &&
|
||||
!diagonalNeighbor.isEmpty() &&
|
||||
info.diagonalUpNeighbor.isExplicitWaterCell();
|
||||
|
||||
bool bothOrthoNeighborsAreExplicitWater = isPrimaryNeighborWater && isSecondaryNeighborWater;
|
||||
|
||||
bool bothOrthoNeighborsSupportDiagonalWater =
|
||||
(isPrimaryNeighborWater || isPrimarysSharedFaceNotSolidAndNotEmpty) &&
|
||||
(isSecondaryNeighborWater || isSecondarysSharedFaceNotSolidAndNotEmpty);
|
||||
|
||||
return
|
||||
inverseCornerWedgeVerticalDiagonalWaterCase ||
|
||||
(isDiagonalWater && bothOrthoNeighborsSupportDiagonalWater) ||
|
||||
bothOrthoNeighborsAreExplicitWater;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class BoxType>
|
||||
bool isWaterOnWedge(const BoxType* reader, const Cell& cell, const Vector3int16& globalCoord) {
|
||||
LocalAreaInfo info;
|
||||
reader->fillLocalAreaInfo(globalCoord, kRelevantNeighbors[cell.solid.getOrientation()], &info);
|
||||
return isWaterOnWedge(cell, info);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<class BoxType>
|
||||
bool cellHasWater(const BoxType* reader, const Cell& center,
|
||||
const Vector3int16& globalCoord) {
|
||||
return !center.isEmpty() &&
|
||||
center.solid.getBlock() != CELL_BLOCK_Solid &&
|
||||
(center.solid.getBlock() == CELL_BLOCK_Empty || isWaterOnWedge(reader, center, globalCoord));
|
||||
}
|
||||
|
||||
template<class BoxType>
|
||||
Cell interpretAsWaterCell(const BoxType* reader, const Cell& cell,
|
||||
const Vector3int16& globalCoord) {
|
||||
if (cellHasWater(reader, cell, globalCoord)) {
|
||||
if (cell.solid.getBlock() == CELL_BLOCK_Empty) {
|
||||
return cell;
|
||||
} else {
|
||||
return Constants::kWaterOnWedgeCell;
|
||||
}
|
||||
} else {
|
||||
return Constants::kUniqueEmptyCellRepresentation;
|
||||
}
|
||||
}
|
||||
|
||||
} // Water
|
||||
} }
|
||||
|
||||
Reference in New Issue
Block a user