use serde::Deserialize;
use std::collections::HashMap;
#[cfg(test)]
mod test;
#[derive(Deserialize, Debug, Clone)]
pub struct Variant {
pub model: String,
pub x: Option<usize>,
pub y: Option<usize>,
pub uvlock: Option<bool>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Variants {
Single(Variant),
Many(Vec<Variant>),
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Blockstate {
Variants(HashMap<String, Variants>),
Multipart(Vec<Part>),
}
#[derive(Deserialize, Debug, Clone)]
pub struct Part {
pub apply: Variants,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Model {
pub parent: Option<String>,
pub textures: Option<HashMap<String, String>>,
pub elements: Option<Vec<Element>>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Element {
pub from: [f32; 3],
pub to: [f32; 3],
pub faces: HashMap<String, Face>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Face {
texture: String,
uv: Option<[f32; 4]>,
}
pub type Texture = Vec<u8>;
#[derive(Debug)]
pub enum Error {
Unsupported,
MissingBlockstate(String),
MissingVariant(String, String),
MissingModel(String),
MissingModelTextures,
MissingTexture(String, String, String),
MissingElements(String, String, String),
MissingTextureVariable(String, String, String, String),
}
fn merge_models(child: &Model, mut parent: Model) -> Result<Model> {
if parent.textures.is_none() {
parent.textures = Some(HashMap::new());
}
match parent.textures {
Some(ref mut parent_textures) => {
let child_textures = child.textures.as_ref().ok_or(Error::MissingModelTextures)?;
for (k, v) in child_textures.iter() {
parent_textures.insert(k.clone(), v.clone());
}
for (_, tvalue) in parent_textures.iter_mut() {
match tvalue.strip_prefix("#") {
Some(rest) => {
*tvalue = child_textures
.get(rest)
.ok_or(Error::MissingTextureVariable(
"?".to_owned(),
"?".to_owned(),
"?".to_owned(),
(*tvalue).clone(),
))?
.clone()
}
None => {}
}
}
}
None => {}
}
match parent.elements {
None => parent.elements = child.elements.clone(),
Some(ref mut pels) => {
for el in child.elements.iter().flatten() {
pels.push(el.clone());
}
}
}
Ok(parent)
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Render {
fn get_top(&mut self, id: &str, encoded_props: &str) -> Result<Texture>;
}
pub struct Renderer {
blockstates: HashMap<String, Blockstate>,
models: HashMap<String, Model>,
textures: HashMap<String, Texture>,
}
impl Renderer {
pub fn new(
blockstates: HashMap<String, Blockstate>,
models: HashMap<String, Model>,
textures: HashMap<String, Texture>,
) -> Self {
Self {
blockstates,
models,
textures,
}
}
fn model_get_top(&self, id: &str, encoded_props: &str, model_name: &str) -> Result<Texture> {
let model = self.flatten_model(model_name)?;
let els = &model.elements.ok_or(Error::MissingElements(
id.to_owned(),
encoded_props.to_owned(),
model_name.to_owned(),
))?;
let el = els.get(0).ok_or(Error::MissingElements(
id.to_owned(),
encoded_props.to_owned(),
model_name.to_owned(),
))?;
let face = el.faces.get("up").ok_or(Error::MissingElements(
id.to_owned(),
encoded_props.to_owned(),
model_name.to_owned(),
))?;
let tex = &face.texture;
let tex = match tex.strip_prefix("#") {
Some(rest) => {
model
.textures
.ok_or(Error::MissingModelTextures)?
.get(rest)
.ok_or(Error::MissingTextureVariable(
id.to_owned(),
encoded_props.to_owned(),
model_name.to_owned(),
(*tex).clone(),
))?
.clone()
}
None => (*tex).clone(),
};
self.extract_texture(&tex)
}
fn get_model(&self, model: &str) -> Result<&Model> {
self.models
.get(model)
.or_else(|| self.models.get(&("minecraft:".to_string() + model)))
.ok_or(Error::MissingModel(model.to_string()))
}
pub fn flatten_model(&self, model: &str) -> Result<Model> {
let mut model = self.get_model(model)?.clone();
while let Some(parent) = model.parent.as_ref() {
let parent = self.get_model(parent)?;
model = merge_models(&model, parent.clone())?;
}
Ok(model)
}
fn extract_texture(&self, tex_name: &str) -> Result<Texture> {
match self.textures.get(tex_name) {
Some(tex) => Ok(tex.clone()),
None => match self.textures.get(&("minecraft:".to_string() + tex_name)) {
Some(tex) => Ok(tex.clone()),
None => Err(Error::MissingTexture(
"?".to_owned(),
"?".to_owned(),
tex_name.to_string(),
)),
},
}
}
}
impl Render for Renderer {
fn get_top(&mut self, id: &str, encoded_props: &str) -> Result<Texture> {
let bs = self
.blockstates
.get(id)
.ok_or(Error::MissingBlockstate(id.to_string()))?;
match bs {
Blockstate::Variants(variants) => {
let v = variants.get(encoded_props).ok_or(Error::MissingVariant(
id.to_string(),
encoded_props.to_string(),
))?;
match v {
Variants::Single(variant) => {
let model_name = &variant.model;
self.model_get_top(id, encoded_props, model_name)
}
Variants::Many(variants) => {
let model_name = &variants[0].model;
self.model_get_top(id, encoded_props, model_name)
}
}
}
Blockstate::Multipart(_) => Err(Error::Unsupported),
}
}
}