Add Litematica exporter and integrate serde for serialization
- Introduced Litematica exporter with necessary data structures for Minecraft text generation. - Added serde dependency for serialization support in multiple crates. - Updated Cargo.toml files to include new dependencies and features. - Created palette module for block palette management.
This commit is contained in:
109
Cargo.lock
generated
109
Cargo.lock
generated
@@ -18,6 +18,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
@@ -83,6 +89,18 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -135,16 +153,50 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exporters"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fastnbt",
|
||||
"flate2",
|
||||
"lib",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastnbt"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2c4573fc9ea51f5ad19c47a16b78427b127f1c00b62808c6ab392beeb5f72c9"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cesu8",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -169,6 +221,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"anyhow",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
@@ -207,6 +260,16 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
@@ -278,6 +341,46 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -287,6 +390,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
|
||||
@@ -7,3 +7,4 @@ anyhow = "1.0.102"
|
||||
thiserror = "2.0.18"
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
@@ -5,6 +5,9 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
lib = { path = "../lib" }
|
||||
fastnbt = "2.6.1"
|
||||
flate2 = "1.1.9"
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod litematica;
|
||||
mod mcfunction;
|
||||
|
||||
pub use mcfunction::McFunctionExporter;
|
||||
|
||||
181
crates/exporters/src/litematica.rs
Normal file
181
crates/exporters/src/litematica.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::Write,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use flate2::Compression;
|
||||
use flate2::write::GzEncoder;
|
||||
use lib::{StructureExporter, VoxelType};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Litematic {
|
||||
pub metadata: Metadata,
|
||||
pub minecraft_data_version: i32,
|
||||
pub version: i32,
|
||||
pub regions: std::collections::HashMap<String, Region>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Metadata {
|
||||
pub author: String,
|
||||
pub description: String,
|
||||
pub enclosing_size: Vector3,
|
||||
pub name: String,
|
||||
pub region_count: i32,
|
||||
pub time_created: i64,
|
||||
pub time_modified: i64,
|
||||
pub total_blocks: i32,
|
||||
pub total_volume: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Region {
|
||||
pub block_state_palette: Vec<BlockState>,
|
||||
pub block_states: Vec<i64>,
|
||||
pub position: Vector3,
|
||||
pub size: Vector3,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct BlockState {
|
||||
pub name: String,
|
||||
// Optional properties like facing=north would go here,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy)]
|
||||
pub struct Vector3 {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
pub struct LitematicaExporter {
|
||||
palette_map: HashMap<VoxelType, String>,
|
||||
}
|
||||
|
||||
impl LitematicaExporter {
|
||||
pub fn new(body_block: &str, outline_block: &str) -> Self {
|
||||
let mut palette_map = HashMap::new();
|
||||
palette_map.insert(VoxelType::Body, body_block.to_string());
|
||||
palette_map.insert(VoxelType::Outline, outline_block.to_string());
|
||||
Self { palette_map }
|
||||
}
|
||||
|
||||
fn pack_bits(indicies: &[usize], bits_per_entry: usize) -> Vec<i64> {
|
||||
let entries_per_long = 64 / bits_per_entry;
|
||||
let mut packed = Vec::new();
|
||||
let mut current_long: i64 = 0;
|
||||
let mut current_idx = 0;
|
||||
|
||||
for &index in indicies {
|
||||
current_long |= (index as i64) << (current_idx * bits_per_entry);
|
||||
current_idx += 1;
|
||||
|
||||
if current_idx >= entries_per_long {
|
||||
packed.push(current_long as i64);
|
||||
current_long = 0;
|
||||
current_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if current_idx > 0 {
|
||||
packed.push(current_long as i64);
|
||||
}
|
||||
|
||||
packed
|
||||
}
|
||||
}
|
||||
|
||||
impl StructureExporter for LitematicaExporter {
|
||||
fn export(&self, grid: &lib::VoxelGrid) -> anyhow::Result<Vec<u8>> {
|
||||
let mut palette_list = vec![BlockState {
|
||||
name: "minecraft:air".to_string(),
|
||||
}];
|
||||
let mut type_to_index = HashMap::new();
|
||||
|
||||
for (voxel_type, block_id) in &self.palette_map {
|
||||
type_to_index.insert(*voxel_type, palette_list.len());
|
||||
palette_list.push(BlockState {
|
||||
name: block_id.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let bits_per_entry =
|
||||
std::cmp::max(2, (palette_list.len() as f64 - 1.0).log2().ceil() as usize);
|
||||
|
||||
let mut indices = Vec::with_capacity((grid.width * grid.height * grid.depth) as usize);
|
||||
let mut total_blocks = 0;
|
||||
|
||||
for y in 0..grid.height {
|
||||
for z in 0..grid.depth {
|
||||
for x in 0..grid.width {
|
||||
if let Some(voxel) = grid.get(x, y, z) {
|
||||
indices.push(*type_to_index.get(&voxel).unwrap());
|
||||
total_blocks += 1;
|
||||
} else {
|
||||
indices.push(0); // Index 0 is Air
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let packed_states = Self::pack_bits(&indices, bits_per_entry);
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
let dimensions = Vector3 {
|
||||
x: grid.width as i32,
|
||||
y: grid.height as i32,
|
||||
z: grid.depth as i32,
|
||||
};
|
||||
|
||||
let mut regions = HashMap::new();
|
||||
regions.insert(
|
||||
"TextRegion".to_string(),
|
||||
Region {
|
||||
block_state_palette: palette_list,
|
||||
block_states: packed_states,
|
||||
position: Vector3 { x: 0, y: 0, z: 0 },
|
||||
size: dimensions,
|
||||
},
|
||||
);
|
||||
|
||||
let litematic = Litematic {
|
||||
minecraft_data_version: 3465, // 1.20+ Data Version
|
||||
version: 6, // Litematica format version
|
||||
metadata: Metadata {
|
||||
author: "Minecraft Text Builder".to_string(),
|
||||
description: "Generated 3D Text".to_string(),
|
||||
name: "Text_Structure".to_string(),
|
||||
enclosing_size: dimensions,
|
||||
region_count: 1,
|
||||
total_blocks,
|
||||
total_volume: (grid.width * grid.height * grid.depth) as i32,
|
||||
time_created: timestamp,
|
||||
time_modified: timestamp,
|
||||
},
|
||||
regions,
|
||||
};
|
||||
|
||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||
|
||||
let nbt_bytes = fastnbt::to_bytes(&litematic).context("NBT serialization failed")?;
|
||||
|
||||
encoder.write_all(&nbt_bytes)?;
|
||||
|
||||
Ok(encoder.finish()?)
|
||||
}
|
||||
|
||||
fn file_extension(&self) -> &'static str {
|
||||
"litematic"
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@ edition = "2024"
|
||||
[dependencies]
|
||||
ab_glyph = "0.2.32"
|
||||
anyhow = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -4,6 +4,7 @@ mod font;
|
||||
mod fonts;
|
||||
mod grid;
|
||||
mod models;
|
||||
mod palette;
|
||||
|
||||
pub use engine::{GenerationOptions, TextBuilder};
|
||||
pub use error::{FontError, VoxelError};
|
||||
|
||||
34
crates/lib/src/palette.rs
Normal file
34
crates/lib/src/palette.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::VoxelType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BlockPalette {
|
||||
pub name: String,
|
||||
pub author: Option<String>,
|
||||
pub blocks: PaletteMappings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PaletteMappings {
|
||||
pub body: String,
|
||||
pub outline: Option<String>,
|
||||
pub shadow: Option<String>,
|
||||
}
|
||||
|
||||
impl BlockPalette {
|
||||
pub fn resolve(&self, voxel: &VoxelType) -> String {
|
||||
match voxel {
|
||||
VoxelType::Body => self.blocks.body.clone(),
|
||||
VoxelType::Outline => self
|
||||
.blocks
|
||||
.outline
|
||||
.clone()
|
||||
.unwrap_or_else(|| "minecraft:air".to_string()),
|
||||
VoxelType::Shadow => self
|
||||
.blocks
|
||||
.shadow
|
||||
.clone()
|
||||
.unwrap_or_else(|| "minecraft:air".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user