Multipart model support

This commit is contained in:
Thinkofname 2016-03-31 14:59:40 +01:00
parent 35d9a2aefa
commit 480b7afd3b
3 changed files with 336 additions and 39 deletions

View File

@ -159,17 +159,13 @@ fn build_func(id: usize, models: Arc<RwLock<model::Factory>>, work_recv: mpsc::R
_ => {},
}
let model_name = block.get_model();
let variant = block.get_model_variant();
if !mat.transparent {
solid_count += model::Factory::get_state_model(
&models, &model_name.0, &model_name.1, &variant, &mut rng,
&snapshot, x, y, z, &mut solid_buffer
&models, block, &mut rng, &snapshot, x, y, z, &mut solid_buffer
);
} else {
trans_count += model::Factory::get_state_model(
&models, &model_name.0, &model_name.1, &variant, &mut rng,
&snapshot, x, y, z, &mut trans_buffer
&models, block, &mut rng, &snapshot, x, y, z, &mut trans_buffer
);
}
}

View File

@ -3,12 +3,13 @@ pub mod liquid;
use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use std::cell::RefCell;
use std::io::Write;
use byteorder::{WriteBytesExt, NativeEndian};
use resources;
use render;
use world;
use world::block::TintType;
use world::block::{Block, TintType};
use types::Direction;
use serde_json;
@ -52,6 +53,10 @@ macro_rules! try_log {
);
}
thread_local!(
static MULTIPART_CACHE: RefCell<HashMap<(Key, Block), Model, BuildHasherDefault<FNVHash>>> = RefCell::new(HashMap::with_hasher(BuildHasherDefault::default()))
);
impl Factory {
pub fn new(resources: Arc<RwLock<resources::Manager>>, textures: Arc<RwLock<render::TextureManager>>) -> Factory {
Factory {
@ -80,35 +85,97 @@ impl Factory {
self.foliage_colors = Factory::load_biome_colors(self.resources.clone(), "foliage");
}
pub fn get_state_model<R: Rng, W: Write>(models: &Arc<RwLock<Factory>>, plugin: &str, name: &str, variant: &str, rng: &mut R,
fn get_model<R: Rng, W: Write>(&self, key: Key, block: Block, rng: &mut R, snapshot: &world::Snapshot, x: i32, y: i32, z: i32, buf: &mut W) -> Result<usize, bool> {
use std::collections::hash_map::Entry;
if let Some(model) = self.models.get(&key) {
if model.multipart.is_empty() {
let variant = block.get_model_variant();
if let Some(var) = model.get_variants(&variant) {
let model = var.choose_model(rng);
return Ok(model.render(self, snapshot, x, y, z, buf));
}
} else {
return MULTIPART_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let entry = cache.entry((key.clone(), block));
match entry {
Entry::Occupied(e) => {
return Ok(e.get().render(self, snapshot, x, y, z, buf));
},
Entry::Vacant(e) => {
let mut res: Option<Model> = None;
for rule in &model.multipart {
let ok = Self::eval_rules(block, &rule.rules);
if ok {
if res.is_some() {
res.as_mut().unwrap().join(&rule.apply.choose_model(rng));
} else {
res = Some(rule.apply.choose_model(rng).clone());
}
}
}
if let Some(mdl) = res {
return Ok(e.insert(mdl).render(self, snapshot, x, y, z, buf));
}
},
};
return Err(true);
});
}
return Err(true);
}
Err(false)
}
fn eval_rules(block: Block, rules: &[Rule]) -> bool {
for mrule in rules {
match *mrule {
Rule::Or(ref sub_rules) => {
let mut ok = false;
for srule in sub_rules {
if Self::eval_rules(block, srule) {
ok = true;
break;
}
}
if !ok {
return false;
}
},
Rule::Match(ref key, ref val) => {
if !block.match_multipart(&key, &val) {
return false;
}
}
}
}
true
}
pub fn get_state_model<R: Rng, W: Write>(models: &Arc<RwLock<Factory>>, block: Block, rng: &mut R,
snapshot: &world::Snapshot, x: i32, y: i32, z: i32, buf: &mut W) -> usize {
let (plugin, name) = block.get_model();
let key = Key(plugin.to_owned(), name.to_owned());
let mut missing_variant = false;
let mut missing_variant;
{
let m = models.read().unwrap();
if let Some(model) = m.models.get(&key) {
if let Some(var) = model.get_variants(variant) {
let model = var.choose_model(rng);
return model.render(&*m, snapshot, x, y, z, buf);
}
missing_variant = true;
}
match m.get_model(key.clone(), block, rng, snapshot, x, y, z, buf) {
Ok(val) => return val,
Err(val) => missing_variant = val,
};
}
if !missing_variant {
// Whole model not loaded, try and load
let mut m = models.write().unwrap();
if !m.models.contains_key(&key) && !m.load_model(plugin, name) {
if !m.models.contains_key(&key) && !m.load_model(&plugin, &name) {
error!("Error loading model {}:{}", plugin, name);
}
if let Some(model) = m.models.get(&key) {
if let Some(var) = model.get_variants(variant) {
let model = var.choose_model(rng);
return model.render(&*m, snapshot, x, y, z, buf);
}
missing_variant = true;
}
match m.get_model(key.clone(), block, rng, snapshot, x, y, z, buf) {
Ok(val) => return val,
Err(val) => missing_variant = val,
};
}
let ret = Factory::get_state_model(models, "steven", "missing_block", "normal", rng, snapshot, x, y, z, buf);
let ret = Factory::get_state_model(models, Block::Missing{}, rng, snapshot, x, y, z, buf);
if !missing_variant {
// Still no model, replace with placeholder
let mut m = models.write().unwrap();
@ -130,6 +197,7 @@ impl Factory {
let mut model = StateModel {
variants: HashMap::with_hasher(BuildHasherDefault::default()),
multipart: vec![],
};
if let Some(variants) = mdl.find("variants").and_then(|v| v.as_object()) {
@ -141,14 +209,48 @@ impl Factory {
model.variants.insert(k.clone(), vars);
}
}
if let Some(_multipart) = mdl.find("multipart").and_then(|v| v.as_array()) {
warn!("Found unhandled multipart for {}:{}", plugin, name);
if let Some(multipart) = mdl.find("multipart").and_then(|v| v.as_array()) {
for rule in multipart {
let apply = self.parse_model_list(plugin, rule.find("apply").unwrap());
let mut rules = vec![];
if let Some(when) = rule.find("when").and_then(|v| v.as_object()) {
Self::parse_rules(when, &mut rules);
}
model.multipart.push(MultipartRule {
apply: apply,
rules: rules,
})
}
}
self.models.insert(Key(plugin.to_owned(), name.to_owned()), model);
true
}
fn parse_rules(when: &::std::collections::BTreeMap<String, serde_json::Value>, rules: &mut Vec<Rule>) {
for (name, val) in when {
if name == "OR" {
let mut or_rules = vec![];
for sub in val.as_array().unwrap() {
let mut sub_rules = vec![];
Self::parse_rules(sub.as_object().unwrap(), &mut sub_rules);
or_rules.push(sub_rules);
}
rules.push(Rule::Or(or_rules));
} else {
let v = match *val {
serde_json::Value::Bool(v) => v.to_string(),
serde_json::Value::I64(v) => v.to_string(),
serde_json::Value::U64(v) => v.to_string(),
serde_json::Value::F64(v) => v.to_string(),
serde_json::Value::String(ref v) => v.to_owned(),
_ => unreachable!(),
};
rules.push(Rule::Match(name.to_owned(), v));
}
}
}
fn parse_model_list(&self, plugin: &str, v: &serde_json::Value) -> Variants {
let mut variants = Variants {
models: vec![]
@ -638,6 +740,7 @@ fn rotate_direction(val: Direction, offset: i32, rots: &[Direction], invalid: &[
#[derive(Clone)]
pub struct StateModel {
variants: HashMap<String, Variants, BuildHasherDefault<FNVHash>>,
multipart: Vec<MultipartRule>,
}
impl StateModel {
@ -646,6 +749,18 @@ impl StateModel {
}
}
#[derive(Clone)]
struct MultipartRule {
apply: Variants,
rules: Vec<Rule>,
}
#[derive(Clone)]
enum Rule {
Match(String, String),
Or(Vec<Vec<Rule>>),
}
#[derive(Clone)]
pub struct Variants {
models: Vec<Model>,
@ -745,6 +860,10 @@ struct Face {
}
impl Model {
fn join(&mut self, other: &Model) {
self.faces.extend_from_slice(&other.faces);
}
fn render<W: Write>(&self, factory: &Factory, snapshot: &world::Snapshot, x: i32, y: i32, z: i32, buf: &mut W) -> usize {
let this = snapshot.get_block(x, y, z);
let this_mat = this.get_material();

View File

@ -51,6 +51,7 @@ macro_rules! define_blocks {
$(tint $tint:expr,)*
$(collision $collision:expr,)*
$(update_state ($world:ident, $x:ident, $y:ident, $z:ident) => $update_state:expr,)*
$(multipart ($mkey:ident, $mval:ident) => $multipart:expr,)*
}
)+
) => (
@ -242,6 +243,24 @@ macro_rules! define_blocks {
)+
}
}
#[allow(unused_variables, unreachable_code)]
pub fn match_multipart(&self, key: &str, val: &str) -> bool{
match *self {
$(
Block::$name {
$($fname,)*
} => {
$(
let $mkey = key;
let $mval = val;
return $multipart;
)*
false
}
)+
}
}
}
lazy_static! {
@ -343,7 +362,7 @@ define_blocks! {
snowy: match world.get_block(x, y + 1, z) {
Block::Snow { .. } | Block::SnowLayer { .. } => true,
_ => false,
}
}
}
},
}
@ -366,7 +385,7 @@ define_blocks! {
},
model { ("minecraft", variant.as_string()) },
variant {
if variant == DirtVariant::Podzol {
if variant == DirtVariant::Podzol {
format!("snowy={}", snowy)
} else {
"normal".to_owned()
@ -377,7 +396,7 @@ define_blocks! {
snowy: match world.get_block(x, y + 1, z) {
Block::Snow{ .. } | Block::SnowLayer { .. } => true,
_ => false,
},
},
variant: variant
}
} else {
@ -939,7 +958,7 @@ define_blocks! {
Direction::East => 0x5,
_ => unreachable!(),
} | if variant == PistonType::Sticky { 0x8 } else { 0x0 }
)} else {
)} else {
None
},
material Material {
@ -1928,6 +1947,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "fence") },
update_state (world, x, y, z) => Block::Fence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
Pumpkin {
props {
@ -2008,7 +2040,7 @@ define_blocks! {
model { ("minecraft", "portal") },
variant format!("axis={}", axis.as_string()),
update_state (world, x, y, z) => {
let axis = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
let axis = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
world.get_block(x, y, z - 1), world.get_block(x, y, z + 1)) {
(Block::Portal{ .. }, _, _, _) | (_, Block::Portal{ .. }, _, _) => Axis::X,
(_, _, Block::Portal{ .. }, _) | (_, _, _, Block::Portal{ .. }) => Axis::Z,
@ -2311,9 +2343,28 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "iron_bars") },
update_state (world, x, y, z) => Block::IronBars {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
GlassPane {
props {},
props {
north: bool = [false, true],
south: bool = [false, true],
east: bool = [false, true],
west: bool = [false, true],
},
data if !north && !south && !east && !west { Some(0) } else { None },
material Material {
renderable: true,
never_cull: false,
@ -2322,6 +2373,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "glass_pane") },
update_state (world, x, y, z) => Block::GlassPane {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
MelonBlock {
props {},
@ -2363,8 +2427,8 @@ define_blocks! {
},
tint TintType::Foliage,
update_state (world, x, y, z) => {
let facing = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
world.get_block(x, y, z - 1), world.get_block(x, y, z + 1)) {
let facing = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
world.get_block(x, y, z - 1), world.get_block(x, y, z + 1)) {
(Block::Pumpkin{ .. }, _, _, _) => Direction::East,
(_, Block::Pumpkin{ .. }, _, _) => Direction::West,
(_, _, Block::Pumpkin{ .. }, _) => Direction::North,
@ -2404,8 +2468,8 @@ define_blocks! {
},
tint TintType::Foliage,
update_state (world, x, y, z) => {
let facing = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
world.get_block(x, y, z - 1), world.get_block(x, y, z + 1)) {
let facing = match (world.get_block(x - 1, y, z), world.get_block(x + 1, y, z),
world.get_block(x, y, z - 1), world.get_block(x, y, z + 1)) {
(Block::MelonBlock{ .. }, _, _, _) => Direction::East,
(_, Block::MelonBlock{ .. }, _, _) => Direction::West,
(_, _, Block::MelonBlock{ .. }, _) => Direction::North,
@ -2574,6 +2638,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "nether_brick_fence") },
update_state (world, x, y, z) => Block::NetherBrickFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
NetherBrickStairs {
props {
@ -3636,8 +3713,12 @@ define_blocks! {
ColoredVariant::Red,
ColoredVariant::Black
],
north: bool = [false, true],
south: bool = [false, true],
east: bool = [false, true],
west: bool = [false, true],
},
data Some(color.data()),
data if !north && !south && !east && !west { Some(color.data()) } else { None },
material Material {
renderable: true,
never_cull: false,
@ -3646,6 +3727,20 @@ define_blocks! {
transparent: true,
},
model { ("minecraft", format!("{}_stained_glass_pane", color.as_string()) ) },
update_state (world, x, y, z) => Block::StainedGlassPane {
color: color,
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
Leaves2 {
props {
@ -4221,6 +4316,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "spruce_fence") },
update_state (world, x, y, z) => Block::SpruceFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
BirchFence {
props {
@ -4238,6 +4346,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "birch_fence") },
update_state (world, x, y, z) => Block::BirchFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
JungleFence {
props {
@ -4255,6 +4376,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "jungle_fence") },
update_state (world, x, y, z) => Block::JungleFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
DarkOakFence {
props {
@ -4272,6 +4406,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "dark_oak_fence") },
update_state (world, x, y, z) => Block::DarkOakFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
AcaciaFence {
props {
@ -4289,6 +4436,19 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "acacia_fence") },
update_state (world, x, y, z) => Block::AcaciaFence {
north: can_connect(world, x, y, z, Direction::North),
south: can_connect(world, x, y, z, Direction::South),
east: can_connect(world, x, y, z, Direction::East),
west: can_connect(world, x, y, z, Direction::West),
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
_ => false,
},
}
SpruceDoor {
props {
@ -4480,6 +4640,23 @@ define_blocks! {
transparent: false,
},
model { ("minecraft", "chorus_plant") },
update_state (world, x, y, z) => Block::ChorusPlant {
north: match world.get_block(x, y, z - 1) { Block::ChorusPlant{..} | Block::ChorusFlower{..} => true, _ => false,},
south: match world.get_block(x, y, z + 1) { Block::ChorusPlant{..} | Block::ChorusFlower{..} => true, _ => false,},
west: match world.get_block(x - 1, y, z) { Block::ChorusPlant{..} | Block::ChorusFlower{..} => true, _ => false,},
east: match world.get_block(x + 1, y, z) { Block::ChorusPlant{..} | Block::ChorusFlower{..} => true, _ => false,},
up: match world.get_block(x, y + 1, z) { Block::ChorusPlant{..} | Block::ChorusFlower{..} => true, _ => false,},
down: match world.get_block(x, y - 1, z) { Block::ChorusPlant{..} | Block::ChorusFlower{..} | Block::EndStone{..} => true, _ => false,},
},
multipart (key, val) => match key {
"north" => north == (val == "true"),
"south" => south == (val == "true"),
"east" => east == (val == "true"),
"west" => west == (val == "true"),
"up" => up == (val == "true"),
"down" => down == (val == "true"),
_ => false,
},
}
ChorusFlower {
props {
@ -4703,6 +4880,11 @@ define_blocks! {
}
}
fn can_connect(world: &super::World, x: i32, y: i32, z: i32, dir: Direction) -> bool {
let (ox, oy, oz) = dir.get_offset();
world.get_block(x + ox, y + oy, z + oz).get_material().renderable
}
fn fence_gate_data(facing: Direction, in_wall: bool, open: bool, powered: bool) -> Option<usize> {
if in_wall || powered {
return None;
@ -4783,7 +4965,7 @@ fn update_double_plant_state(world: &super::World, x: i32, y: i32, z: i32, ohalf
}
match world.get_block(x, y - 1, z) {
Block::DoublePlant{half, variant} => (ohalf, variant),
Block::DoublePlant{variant, ..} => (ohalf, variant),
_ => (ohalf, ovariant),
}
}