You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

188 lines
4.7 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using gaemstone.ECS;
namespace gaemstone.Bloxel;
// Based on "Palette-based compression for chunked discrete voxel data" by /u/Longor1996
// https://www.reddit.com/r/VoxelGameDev/comments/9yu8qy/palettebased_compression_for_chunked_discrete/
[Component]
public class ChunkPaletteStorage<T>
{
const int Size = 16 * 16 * 16;
static readonly EqualityComparer<T> COMPARER
= EqualityComparer<T>.Default;
BitArray? _data;
PaletteEntry[]? _palette;
int _usedPalettes;
int _indicesLength;
public T Default { get; }
public T this[int x, int y, int z]
{
get => Get(x, y, z);
set => Set(x, y, z, value);
}
public IEnumerable<T> Blocks
=> _palette?.Where(entry => !COMPARER.Equals(entry.Value, default!))
.Select(entry => entry.Value!)
?? Enumerable.Empty<T>();
public ChunkPaletteStorage(T @default)
=> Default = @default;
T Get(int x, int y, int z)
{
if (_palette == null) return Default;
var entry = _palette[GetPaletteIndex(x, y, z)];
return !COMPARER.Equals(entry.Value, default!) ? entry.Value : Default;
}
void Set(int x, int y, int z, T value)
{
if (_palette == null)
{
if (COMPARER.Equals(value, Default)) return;
}
else
{
var index = GetIndex(x, y, z);
ref var current = ref _palette[GetPaletteIndex(index)];
if (COMPARER.Equals(value, current.Value)) return;
if (--current.RefCount == 0)
_usedPalettes--;
var replace = Array.FindIndex(_palette, entry => COMPARER.Equals(value, entry.Value));
if (replace != -1)
{
SetPaletteIndex(index, replace);
_palette[replace].RefCount += 1;
return;
}
if (current.RefCount == 0)
{
current.Value = value;
current.RefCount = 1;
_usedPalettes++;
return;
}
}
var newPaletteIndex = NewPaletteEntry();
_palette![newPaletteIndex] = new PaletteEntry { Value = value, RefCount = 1 };
SetPaletteIndex(x, y, z, newPaletteIndex);
_usedPalettes++;
}
int NewPaletteEntry()
{
if (_palette != null)
{
int firstFree = Array.FindIndex(_palette, entry =>
entry.Value == null || entry.RefCount == 0);
if (firstFree != -1) return firstFree;
}
GrowPalette();
return NewPaletteEntry();
}
void GrowPalette()
{
if (_palette == null)
{
_data = new(Size);
_palette = new PaletteEntry[2];
_usedPalettes = 1;
_indicesLength = 1;
_palette[0] = new PaletteEntry { Value = Default, RefCount = Size };
return;
}
_indicesLength <<= 1;
var oldIndicesLength = _indicesLength >> 1;
var newData = new BitArray(Size * _indicesLength);
for (var i = 0; i < Size; i++)
for (var j = 0; j < oldIndicesLength; j++)
newData.Set(i * _indicesLength + j, _data!.Get(i * oldIndicesLength + j));
_data = newData;
Array.Resize(ref _palette, 1 << _indicesLength);
}
// public void FitPalette() {
// if (_usedPalettes > Mathf.NearestPo2(_usedPalettes) / 2) return;
// // decode all indices
// int[] indices = new int[size];
// for(int i = 0; i < indices.length; i++) {
// indices[i] = data.get(i * indicesLength, indicesLength);
// }
// // Create new palette, halfing it in size
// indicesLength = indicesLength >> 1;
// PaletteEntry[] newPalette = new PaletteEntry[2 pow indicesLength];
// // We gotta compress the palette entries!
// int paletteCounter = 0;
// for(int pi = 0; pi < palette.length; pi++, paletteCounter++) {
// PaletteEntry entry = newPalette[paletteCounter] = palette[pi];
// // Re-encode the indices (find and replace; with limit)
// for(int di = 0, fc = 0; di < indices.length && fc < entry.refcount; di++) {
// if(pi == indices[di]) {
// indices[di] = paletteCounter;
// fc += 1;
// }
// }
// }
// // Allocate new BitBuffer
// data = new BitBuffer(size * indicesLength); // the length is in bits, not bytes!
// // Encode the indices
// for(int i = 0; i < indices.length; i++) {
// data.set(i * indicesLength, indicesLength, indices[i]);
// }
// }
int GetPaletteIndex(int x, int y, int z)
=> GetPaletteIndex(GetIndex(x, y, z));
int GetPaletteIndex(int index)
{
var paletteIndex = 0;
for (var i = 0; i < _indicesLength; i++)
paletteIndex |= (_data!.Get(index + i) ? 1 : 0) << i;
return paletteIndex;
}
void SetPaletteIndex(int x, int y, int z, int paletteIndex)
=> SetPaletteIndex(GetIndex(x, y, z), paletteIndex);
void SetPaletteIndex(int index, int paletteIndex)
{
for (var i = 0; i < _indicesLength; i++)
_data!.Set(index + i, (paletteIndex >> i & 0b1) == 0b1);
}
int GetIndex(int x, int y, int z)
=> (x | y << 4 | z << 8) * _indicesLength;
struct PaletteEntry
{
public T Value { get; set; }
public int RefCount { get; set; }
}
}