use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub struct ResourcePackHandle {
inner: crate::ResourcePack,
}
#[wasm_bindgen]
impl ResourcePackHandle {
#[wasm_bindgen(constructor)]
pub fn new(data: &[u8]) -> Result<ResourcePackHandle, JsError> {
let pack = crate::load_resource_pack_from_bytes(data)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(ResourcePackHandle { inner: pack })
}
#[wasm_bindgen(getter)]
pub fn blockstate_count(&self) -> usize {
self.inner.blockstate_count()
}
#[wasm_bindgen(getter)]
pub fn model_count(&self) -> usize {
self.inner.model_count()
}
#[wasm_bindgen(getter)]
pub fn texture_count(&self) -> usize {
self.inner.texture_count()
}
}
#[wasm_bindgen]
#[derive(Default)]
pub struct MesherOptions {
cull_hidden_faces: bool,
ambient_occlusion: bool,
ao_intensity: f32,
atlas_max_size: u32,
biome: Option<String>,
}
#[wasm_bindgen]
impl MesherOptions {
#[wasm_bindgen(constructor)]
pub fn new() -> MesherOptions {
MesherOptions {
cull_hidden_faces: true,
ambient_occlusion: true,
ao_intensity: 0.4,
atlas_max_size: 4096,
biome: None,
}
}
#[wasm_bindgen(setter)]
pub fn set_cull_hidden_faces(&mut self, value: bool) {
self.cull_hidden_faces = value;
}
#[wasm_bindgen(setter)]
pub fn set_ambient_occlusion(&mut self, value: bool) {
self.ambient_occlusion = value;
}
#[wasm_bindgen(setter)]
pub fn set_ao_intensity(&mut self, value: f32) {
self.ao_intensity = value;
}
#[wasm_bindgen(setter)]
pub fn set_atlas_max_size(&mut self, value: u32) {
self.atlas_max_size = value;
}
#[wasm_bindgen(setter)]
pub fn set_biome(&mut self, value: String) {
self.biome = Some(value);
}
}
#[wasm_bindgen]
pub struct BlockInput {
x: i32,
y: i32,
z: i32,
name: String,
properties: std::collections::HashMap<String, String>,
}
#[wasm_bindgen]
impl BlockInput {
#[wasm_bindgen(constructor)]
pub fn new(x: i32, y: i32, z: i32, name: &str) -> BlockInput {
BlockInput {
x,
y,
z,
name: name.to_string(),
properties: std::collections::HashMap::new(),
}
}
pub fn set_property(&mut self, key: &str, value: &str) {
self.properties.insert(key.to_string(), value.to_string());
}
}
#[wasm_bindgen]
pub struct MeshResult {
glb_data: Vec<u8>,
vertex_count: usize,
triangle_count: usize,
has_transparency: bool,
}
#[wasm_bindgen]
impl MeshResult {
#[wasm_bindgen(getter)]
pub fn glb_data(&self) -> Vec<u8> {
self.glb_data.clone()
}
#[wasm_bindgen(getter)]
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
#[wasm_bindgen(getter)]
pub fn triangle_count(&self) -> usize {
self.triangle_count
}
#[wasm_bindgen(getter)]
pub fn has_transparency(&self) -> bool {
self.has_transparency
}
}
#[wasm_bindgen]
pub fn mesh_block(
pack: &ResourcePackHandle,
block_name: &str,
options: Option<MesherOptions>,
) -> Result<MeshResult, JsError> {
let options = options.unwrap_or_default();
let mut config = crate::MesherConfig::default();
config.cull_hidden_faces = options.cull_hidden_faces;
config.ambient_occlusion = options.ambient_occlusion;
config.ao_intensity = options.ao_intensity;
config.atlas_max_size = options.atlas_max_size;
if let Some(biome) = &options.biome {
config = config.with_biome(biome);
}
let block_name = if block_name.contains(':') {
block_name.to_string()
} else {
format!("minecraft:{}", block_name)
};
let block = crate::InputBlock::new(&block_name);
let pos = crate::BlockPosition::new(0, 0, 0);
let blocks = vec![(pos, block)];
let bounds = crate::BoundingBox::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let mesher = crate::Mesher::with_config(pack.inner.clone(), config);
let output = mesher.mesh_blocks(
blocks.iter().map(|(p, b)| (*p, b)),
bounds,
).map_err(|e| JsError::new(&e.to_string()))?;
let glb_data = crate::export_glb(&output)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(MeshResult {
glb_data,
vertex_count: output.total_vertices(),
triangle_count: output.total_triangles(),
has_transparency: output.has_transparency(),
})
}
#[wasm_bindgen]
pub fn mesh_blocks_json(
pack: &ResourcePackHandle,
json: &str,
options: Option<MesherOptions>,
) -> Result<MeshResult, JsError> {
#[derive(serde::Deserialize)]
struct BlockData {
blocks: Vec<BlockEntry>,
}
#[derive(serde::Deserialize)]
struct BlockEntry {
x: i32,
y: i32,
z: i32,
#[serde(default = "default_name")]
name: String,
#[serde(default)]
properties: std::collections::HashMap<String, String>,
}
fn default_name() -> String {
"minecraft:stone".to_string()
}
let data: BlockData = serde_json::from_str(json)
.map_err(|e| JsError::new(&format!("Invalid JSON: {}", e)))?;
let options = options.unwrap_or_default();
let mut config = crate::MesherConfig::default();
config.cull_hidden_faces = options.cull_hidden_faces;
config.ambient_occlusion = options.ambient_occlusion;
config.ao_intensity = options.ao_intensity;
config.atlas_max_size = options.atlas_max_size;
if let Some(biome) = &options.biome {
config = config.with_biome(biome);
}
let mut blocks = Vec::new();
let mut min = [i32::MAX; 3];
let mut max = [i32::MIN; 3];
for entry in &data.blocks {
let pos = crate::BlockPosition::new(entry.x, entry.y, entry.z);
let name = if entry.name.contains(':') {
entry.name.clone()
} else {
format!("minecraft:{}", entry.name)
};
let mut block = crate::InputBlock::new(name);
for (k, v) in &entry.properties {
block.properties.insert(k.clone(), v.clone());
}
blocks.push((pos, block));
min[0] = min[0].min(entry.x);
min[1] = min[1].min(entry.y);
min[2] = min[2].min(entry.z);
max[0] = max[0].max(entry.x);
max[1] = max[1].max(entry.y);
max[2] = max[2].max(entry.z);
}
let bounds = if blocks.is_empty() {
crate::BoundingBox::new([0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
} else {
crate::BoundingBox::new(
[min[0] as f32, min[1] as f32, min[2] as f32],
[(max[0] + 1) as f32, (max[1] + 1) as f32, (max[2] + 1) as f32],
)
};
let mesher = crate::Mesher::with_config(pack.inner.clone(), config);
let output = mesher.mesh_blocks(
blocks.iter().map(|(p, b)| (*p, b)),
bounds,
).map_err(|e| JsError::new(&e.to_string()))?;
let glb_data = crate::export_glb(&output)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(MeshResult {
glb_data,
vertex_count: output.total_vertices(),
triangle_count: output.total_triangles(),
has_transparency: output.has_transparency(),
})
}