mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
242 lines
8.5 KiB
C++
242 lines
8.5 KiB
C++
#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
|