Terrafier/crates/palette-compress/src/bits.rs
loki5512344 91835402b3 Initial commit: Terrafier v0.1.0
- Core world model (World, Dimension, Tile, Terrain, Platform)
- NBT reader/writer with full tag support
- Anvil region (.mca) format reader with chunk parsing
- Block palette compression and export
- Biome database with Minecraft 1.21 biomes
- CLI commands: new, import, export, info, render
- GUI application with egui (viewport, tools, undo/redo)
- Noise-based heightmap generation and editing operations

License: GPL-3.0-or-later
2026-06-15 19:14:11 +02:00

112 lines
3.4 KiB
Rust
Executable file

/// Compact storage of fixed-size non-negative integers in a `Vec<i64>`.
///
/// Minecraft Anvil stores block palette indices as packed integers.
/// N bits per entry, entries packed consecutively into 64-bit longs.
/// Entries never cross long boundaries.
pub struct BitArray {
pub data: Vec<i64>,
pub bits_per_entry: u8,
pub size: usize,
}
impl BitArray {
/// Create a new `BitArray` with all entries initialized to 0.
pub fn new(size: usize, bits_per_entry: u8) -> Self {
assert!(
bits_per_entry > 0 && bits_per_entry <= 64,
"bits_per_entry must be 1..=64"
);
let entries_per_long = 64 / bits_per_entry as usize;
let data_len = size.div_ceil(entries_per_long);
BitArray {
data: vec![0i64; data_len],
bits_per_entry,
size,
}
}
/// Wrap existing raw data as a `BitArray`.
pub fn from_raw(data: Vec<i64>, bits_per_entry: u8, size: usize) -> Self {
BitArray {
data,
bits_per_entry,
size,
}
}
/// Read the value at `index`.
pub fn get(&self, index: usize) -> i64 {
assert!(index < self.size, "index out of bounds");
let epb = 64 / self.bits_per_entry as usize;
let long_idx = index / epb;
let offset = (index % epb) * self.bits_per_entry as usize;
let mask = (1i64 << self.bits_per_entry) - 1;
(self.data[long_idx] >> offset) & mask
}
/// Write `value` at `index`.
pub fn set(&mut self, index: usize, value: i64) {
assert!(index < self.size, "index out of bounds");
let epb = 64 / self.bits_per_entry as usize;
let long_idx = index / epb;
let offset = (index % epb) * self.bits_per_entry as usize;
let mask = (1i64 << self.bits_per_entry) - 1;
self.data[long_idx] =
(self.data[long_idx] & !(mask << offset)) | ((value & mask) << offset);
}
/// Borrow the raw underlying long array.
pub fn to_raw(&self) -> &[i64] {
&self.data
}
/// Minimum bits needed to represent `count` distinct values.
///
/// Minecraft requires at least 1 bit per entry, even for a single block.
pub fn bits_needed(count: usize) -> u8 {
if count <= 2 {
return 1; // 0 and 1 both fit in 1 bit; 2 values → 1 bit
}
let count = count as u64;
(64 - count.saturating_sub(1).leading_zeros()) as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pack_unpack() {
let mut ba = BitArray::new(10, 4);
for i in 0..10 {
ba.set(i, (i % 15) as i64);
}
for i in 0..10 {
assert_eq!(ba.get(i), (i % 15) as i64);
}
}
#[test]
fn test_bits_needed() {
assert_eq!(BitArray::bits_needed(0), 1);
assert_eq!(BitArray::bits_needed(1), 1);
assert_eq!(BitArray::bits_needed(2), 1);
assert_eq!(BitArray::bits_needed(3), 2);
assert_eq!(BitArray::bits_needed(4), 2);
assert_eq!(BitArray::bits_needed(8), 3);
assert_eq!(BitArray::bits_needed(16), 4);
assert_eq!(BitArray::bits_needed(256), 8);
}
#[test]
fn test_large_array() {
let mut ba = BitArray::new(4096, 13);
for i in 0..4096 {
ba.set(i, (i % 8191) as i64);
}
for i in 0..4096 {
assert_eq!(ba.get(i), (i % 8191) as i64);
}
}
}