mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
All block shapes & collisions (#22)
* start adding shapes * add more collision stuff * DiscreteCubeMerger * more mergers * start adding BitSetDiscreteVoxelShape::join * i love rust 😃 😃 😃 * r * IT COMPILES???? * fix warning * fix error * fix more clippy issues * add box_shape * more shape stuff * make DiscreteVoxelShape an enum * Update shape.rs * also make VoxelShape an enum * implement BitSet::clear * add more missing things * it compiles W * start block shape codegen * optimize shape codegen * make az-block/blocks.rs look better (broken) * almost new block macro * make the codegen not generate 'type' * try to fix * work more on the blocks macro * wait it compiles * fix clippy issues * shapes codegen works * well it's almost working * simplify some shape codegen * enum type names are correct * W it compiles * cargo check no longer warns * fix some clippy issues * start making it so the shape impl is on BlockStates * insane code * new impl compiles * fix wrong find_bits + TESTS PASS! * add a test for slab collision * fix clippy issues * ok rust * fix error that happens when on stairs * add test for top slabs * start adding join_is_not_empty * add more to join_is_not_empty * top slabs still don't work!! * x..=0 doesn't work in rust 😃 😃 😃 😃 😃 😃 😃 😃 😃 😃 😃 😃 😃 😃 * remove comment since i added more useful names * remove some printlns * fix walls in some configurations erroring * fix some warnings * change comment to \`\`\`ignore instead of \`\`\`no_run * players are .6 wide not .8 * fix clippy's complaints * i missed one clippy warning
This commit is contained in:
parent
aa78491ee0
commit
c9b4dccd7e
33 changed files with 39221 additions and 2711 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -236,6 +236,7 @@ dependencies = [
|
|||
"azalea-block",
|
||||
"azalea-core",
|
||||
"azalea-world",
|
||||
"lazy_static",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::collections::HashMap;
|
|||
use std::fmt::Write;
|
||||
use syn::{
|
||||
self, braced,
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
|
@ -13,38 +14,79 @@ use syn::{
|
|||
};
|
||||
use utils::{combinations_of, to_pascal_case};
|
||||
|
||||
enum PropertyType {
|
||||
/// `Axis { X, Y, Z }`
|
||||
Enum {
|
||||
type_name: Ident,
|
||||
variants: Punctuated<Ident, Token![,]>,
|
||||
},
|
||||
/// `bool`
|
||||
Boolean,
|
||||
}
|
||||
|
||||
/// `"snowy" => bool`
|
||||
struct PropertyDefinition {
|
||||
name: LitStr,
|
||||
struct_name: Ident,
|
||||
variants: Punctuated<Ident, Token![,]>,
|
||||
property_type: PropertyType,
|
||||
}
|
||||
|
||||
/// Comma separated PropertyDefinitions (`"snowy" => bool,`)
|
||||
struct PropertyDefinitions {
|
||||
properties: Vec<PropertyDefinition>,
|
||||
}
|
||||
|
||||
struct PropertyAndDefault {
|
||||
struct_name: Ident,
|
||||
default: Ident,
|
||||
}
|
||||
/// `snowy: false` or `axis: Axis::Y`
|
||||
#[derive(Debug)]
|
||||
struct PropertyWithNameAndDefault {
|
||||
name: String,
|
||||
struct_name: Ident,
|
||||
default: Ident,
|
||||
name: Ident,
|
||||
property_type: Ident,
|
||||
is_enum: bool,
|
||||
default: proc_macro2::TokenStream,
|
||||
}
|
||||
|
||||
/// ```ignore
|
||||
/// grass_block => BlockBehavior::default(), {
|
||||
/// snowy: false,
|
||||
/// },
|
||||
/// ```
|
||||
struct BlockDefinition {
|
||||
name: Ident,
|
||||
behavior: Expr,
|
||||
properties_and_defaults: Vec<PropertyAndDefault>,
|
||||
properties_and_defaults: Vec<PropertyWithNameAndDefault>,
|
||||
}
|
||||
impl PropertyAndDefault {
|
||||
fn as_property_with_name_and_default(&self, name: String) -> PropertyWithNameAndDefault {
|
||||
PropertyWithNameAndDefault {
|
||||
name,
|
||||
struct_name: self.struct_name.clone(),
|
||||
default: self.default.clone(),
|
||||
}
|
||||
impl Parse for PropertyWithNameAndDefault {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// `snowy: false` or `axis: Axis::Y`
|
||||
let property_name = input.parse()?;
|
||||
input.parse::<Token![:]>()?;
|
||||
|
||||
let first_ident = input.call(Ident::parse_any)?;
|
||||
let first_ident_string = first_ident.to_string();
|
||||
let mut property_default = quote! { #first_ident };
|
||||
|
||||
let property_type: Ident;
|
||||
let mut is_enum = false;
|
||||
|
||||
if input.parse::<Token![::]>().is_ok() {
|
||||
is_enum = true;
|
||||
property_type = first_ident;
|
||||
let variant = input.parse::<Ident>()?;
|
||||
property_default.extend(quote! { ::#variant })
|
||||
} else if first_ident_string == "true" || first_ident_string == "false" {
|
||||
property_type = Ident::new("bool", first_ident.span());
|
||||
} else {
|
||||
return Err(input.error("Expected a boolean or an enum variant"));
|
||||
};
|
||||
|
||||
Ok(PropertyWithNameAndDefault {
|
||||
name: property_name,
|
||||
property_type,
|
||||
is_enum,
|
||||
default: property_default,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockDefinitions {
|
||||
blocks: Vec<BlockDefinition>,
|
||||
}
|
||||
|
@ -53,6 +95,26 @@ struct MakeBlockStates {
|
|||
block_definitions: BlockDefinitions,
|
||||
}
|
||||
|
||||
impl Parse for PropertyType {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// like `Axis { X, Y, Z }` or `bool`
|
||||
|
||||
let keyword = Ident::parse(input)?;
|
||||
let keyword_string = keyword.to_string();
|
||||
if keyword_string == "bool" {
|
||||
Ok(Self::Boolean)
|
||||
} else {
|
||||
let content;
|
||||
braced!(content in input);
|
||||
let variants = content.parse_terminated(Ident::parse)?;
|
||||
Ok(Self::Enum {
|
||||
type_name: keyword,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for PropertyDefinition {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// "face" => Face {
|
||||
|
@ -66,17 +128,12 @@ impl Parse for PropertyDefinition {
|
|||
// syntax error
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![=>]>()?;
|
||||
let struct_name = input.parse()?;
|
||||
|
||||
let content;
|
||||
braced!(content in input);
|
||||
let variants = content.parse_terminated(Ident::parse)?;
|
||||
let property_type = input.parse()?;
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
Ok(PropertyDefinition {
|
||||
name,
|
||||
struct_name,
|
||||
variants,
|
||||
property_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +157,7 @@ impl Parse for BlockDefinition {
|
|||
// Facing=North,
|
||||
// Powered=False,
|
||||
// Face=Wall,
|
||||
// },
|
||||
// }
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![=>]>()?;
|
||||
let behavior = input.parse()?;
|
||||
|
@ -111,18 +168,14 @@ impl Parse for BlockDefinition {
|
|||
|
||||
let mut properties_and_defaults = Vec::new();
|
||||
|
||||
while let Ok(property) = content.parse() {
|
||||
content.parse::<Token![=]>()?;
|
||||
let property_default = content.parse()?;
|
||||
properties_and_defaults.push(PropertyAndDefault {
|
||||
struct_name: property,
|
||||
default: property_default,
|
||||
});
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
break;
|
||||
}
|
||||
// read the things comma-separated
|
||||
let property_and_default_punctuated: Punctuated<PropertyWithNameAndDefault, Token![,]> =
|
||||
content.parse_terminated(PropertyWithNameAndDefault::parse)?;
|
||||
|
||||
for property_and_default in property_and_default_punctuated {
|
||||
properties_and_defaults.push(property_and_default);
|
||||
}
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
Ok(BlockDefinition {
|
||||
name,
|
||||
behavior,
|
||||
|
@ -134,8 +187,11 @@ impl Parse for BlockDefinition {
|
|||
impl Parse for BlockDefinitions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut blocks = Vec::new();
|
||||
while !input.is_empty() {
|
||||
blocks.push(input.parse()?);
|
||||
|
||||
let block_definitions_punctuated: Punctuated<BlockDefinition, Token![,]> =
|
||||
input.parse_terminated(BlockDefinition::parse)?;
|
||||
for block_definition in block_definitions_punctuated {
|
||||
blocks.push(block_definition);
|
||||
}
|
||||
|
||||
Ok(BlockDefinitions { blocks })
|
||||
|
@ -179,57 +235,70 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
let mut state_id: usize = 0;
|
||||
|
||||
for property in &input.property_definitions.properties {
|
||||
let mut property_enum_variants = quote! {};
|
||||
let mut property_from_number_variants = quote! {};
|
||||
let mut property_enum_variant_names = Vec::new();
|
||||
let property_type_name: Ident;
|
||||
let mut property_variant_types = Vec::new();
|
||||
|
||||
let property_struct_name = &property.struct_name;
|
||||
match &property.property_type {
|
||||
PropertyType::Enum {
|
||||
type_name,
|
||||
variants,
|
||||
} => {
|
||||
let mut property_enum_variants = quote! {};
|
||||
let mut property_from_number_variants = quote! {};
|
||||
|
||||
property_struct_names_to_names.insert(
|
||||
property_struct_name.to_string(),
|
||||
property.name.clone().value(),
|
||||
);
|
||||
property_type_name = type_name.clone();
|
||||
|
||||
for i in 0..property.variants.len() {
|
||||
let variant = &property.variants[i];
|
||||
property_struct_names_to_names.insert(
|
||||
property_type_name.to_string(),
|
||||
property.name.clone().value(),
|
||||
);
|
||||
|
||||
let i_lit = syn::Lit::Int(syn::LitInt::new(
|
||||
&i.to_string(),
|
||||
proc_macro2::Span::call_site(),
|
||||
));
|
||||
for i in 0..variants.len() {
|
||||
let variant = &variants[i];
|
||||
|
||||
property_enum_variants.extend(quote! {
|
||||
#variant = #i_lit,
|
||||
});
|
||||
let i_lit = syn::Lit::Int(syn::LitInt::new(
|
||||
&i.to_string(),
|
||||
proc_macro2::Span::call_site(),
|
||||
));
|
||||
|
||||
// i_lit is used here instead of i because otherwise it says 0size
|
||||
// in the expansion and that looks uglier
|
||||
property_from_number_variants.extend(quote! {
|
||||
#i_lit => #property_struct_name::#variant,
|
||||
});
|
||||
property_enum_variants.extend(quote! {
|
||||
#variant = #i_lit,
|
||||
});
|
||||
|
||||
property_enum_variant_names.push(variant.to_string());
|
||||
}
|
||||
// i_lit is used here instead of i because otherwise it says 0size
|
||||
// in the expansion and that looks uglier
|
||||
property_from_number_variants.extend(quote! {
|
||||
#i_lit => #property_type_name::#variant,
|
||||
});
|
||||
|
||||
property_enums.extend(quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum #property_struct_name {
|
||||
#property_enum_variants
|
||||
}
|
||||
|
||||
impl From<usize> for #property_struct_name {
|
||||
fn from(value: usize) -> Self {
|
||||
match value {
|
||||
#property_from_number_variants
|
||||
_ => panic!("Invalid property value: {}", value),
|
||||
}
|
||||
property_variant_types.push(variant.to_string());
|
||||
}
|
||||
|
||||
property_enums.extend(quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum #property_type_name {
|
||||
#property_enum_variants
|
||||
}
|
||||
|
||||
impl From<usize> for #property_type_name {
|
||||
fn from(value: usize) -> Self {
|
||||
match value {
|
||||
#property_from_number_variants
|
||||
_ => panic!("Invalid property value: {}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
properties_map.insert(
|
||||
property_struct_name.to_string(),
|
||||
property_enum_variant_names,
|
||||
);
|
||||
PropertyType::Boolean => {
|
||||
property_type_name = Ident::new("bool", proc_macro2::Span::call_site());
|
||||
// property_type_name =
|
||||
// Ident::new(&property.name.value(), proc_macro2::Span::call_site());
|
||||
property_variant_types = vec!["true".to_string(), "false".to_string()];
|
||||
}
|
||||
}
|
||||
properties_map.insert(property_type_name.to_string(), property_variant_types);
|
||||
// properties_map.insert(property.name.value(), property_variant_types);
|
||||
}
|
||||
|
||||
let mut block_state_enum_variants = quote! {};
|
||||
|
@ -239,10 +308,13 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
let block_property_names = &block
|
||||
.properties_and_defaults
|
||||
.iter()
|
||||
.map(|p| p.struct_name.to_string())
|
||||
.map(|p| p.property_type.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let mut block_properties_vec = Vec::new();
|
||||
for property_name in block_property_names {
|
||||
// if property_name == "stage" {
|
||||
// panic!("{:?}", block.properties_and_defaults);
|
||||
// }
|
||||
let property_variants = properties_map
|
||||
.get(property_name)
|
||||
.unwrap_or_else(|| panic!("Property '{}' not found", property_name))
|
||||
|
@ -252,34 +324,46 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
|
||||
let mut properties_with_name: Vec<PropertyWithNameAndDefault> =
|
||||
Vec::with_capacity(block.properties_and_defaults.len());
|
||||
// Used to determine the index of the property so we can optionally add a number to it
|
||||
let mut previous_names: Vec<String> = Vec::new();
|
||||
for property in &block.properties_and_defaults {
|
||||
let index: Option<usize> = if block
|
||||
.properties_and_defaults
|
||||
.iter()
|
||||
.filter(|p| p.struct_name == property.struct_name)
|
||||
.filter(|p| p.name == property.name)
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
Some(
|
||||
properties_with_name
|
||||
previous_names
|
||||
.iter()
|
||||
.filter(|p| p.struct_name == property.struct_name)
|
||||
.filter(|&p| p == &property.name.to_string())
|
||||
.count(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// let mut property_name = property_struct_names_to_names
|
||||
// .get(&property.property_type.to_string())
|
||||
// .unwrap_or_else(|| panic!("Property '{}' is bad", property.property_type))
|
||||
// .clone();
|
||||
let mut property_name = property_struct_names_to_names
|
||||
.get(&property.struct_name.to_string())
|
||||
.unwrap_or_else(|| panic!("Property '{}' is bad", property.struct_name))
|
||||
.clone();
|
||||
.get(&property.name.to_string())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| property.name.to_string());
|
||||
previous_names.push(property_name.clone());
|
||||
if let Some(index) = index {
|
||||
// property_name.push_str(&format!("_{}", &index.to_string()));
|
||||
write!(property_name, "_{}", index).unwrap();
|
||||
}
|
||||
properties_with_name
|
||||
.push(property.as_property_with_name_and_default(property_name.clone()));
|
||||
properties_with_name.push(PropertyWithNameAndDefault {
|
||||
name: Ident::new(&property_name, proc_macro2::Span::call_site()),
|
||||
property_type: property.property_type.clone(),
|
||||
is_enum: property.is_enum,
|
||||
default: property.default.clone(),
|
||||
});
|
||||
}
|
||||
drop(previous_names);
|
||||
|
||||
// pub face: properties::Face,
|
||||
// pub facing: properties::Facing,
|
||||
|
@ -290,14 +374,15 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
// pub has_bottle_2: HasBottle,
|
||||
let mut block_struct_fields = quote! {};
|
||||
for PropertyWithNameAndDefault {
|
||||
struct_name, name, ..
|
||||
property_type: struct_name,
|
||||
name,
|
||||
..
|
||||
} in &properties_with_name
|
||||
{
|
||||
// let property_name_snake =
|
||||
// Ident::new(&property.to_string(), proc_macro2::Span::call_site());
|
||||
let name_ident = Ident::new(name, proc_macro2::Span::call_site());
|
||||
block_struct_fields.extend(quote! {
|
||||
pub #name_ident: #struct_name,
|
||||
pub #name: #struct_name,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -329,7 +414,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
block_name_pascal_case,
|
||||
combination
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.map(|v| v[0..1].to_uppercase() + &v[1..])
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
),
|
||||
|
@ -346,13 +431,18 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
for i in 0..properties_with_name.len() {
|
||||
let property = &properties_with_name[i];
|
||||
let property_name = &property.name;
|
||||
let property_name_ident = Ident::new(property_name, proc_macro2::Span::call_site());
|
||||
let property_struct_name_ident = &property.struct_name;
|
||||
let property_struct_name_ident = &property.property_type;
|
||||
let variant =
|
||||
Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());
|
||||
|
||||
let property_type = if property.is_enum {
|
||||
quote! {#property_struct_name_ident::#variant}
|
||||
} else {
|
||||
quote! {#variant}
|
||||
};
|
||||
|
||||
from_block_to_state_combination_match_inner.extend(quote! {
|
||||
#property_name_ident: #property_struct_name_ident::#variant,
|
||||
#property_name: #property_type,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -375,16 +465,23 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
let mut division = 1usize;
|
||||
for i in (0..properties_with_name.len()).rev() {
|
||||
let PropertyWithNameAndDefault {
|
||||
struct_name: property_struct_name_ident,
|
||||
property_type: property_struct_name_ident,
|
||||
name: property_name,
|
||||
..
|
||||
} = &properties_with_name[i];
|
||||
|
||||
let property_variants = &block_properties_vec[i];
|
||||
let property_variants_count = property_variants.len();
|
||||
let property_name_ident = Ident::new(property_name, proc_macro2::Span::call_site());
|
||||
let conversion_code = {
|
||||
if &property_struct_name_ident.to_string() == "bool" {
|
||||
assert_eq!(property_variants_count, 2);
|
||||
quote! {(b / #division) % #property_variants_count != 0}
|
||||
} else {
|
||||
quote! {#property_struct_name_ident::from((b / #division) % #property_variants_count)}
|
||||
}
|
||||
};
|
||||
from_state_to_block_inner.extend(quote! {
|
||||
#property_name_ident: #property_struct_name_ident::from((b / #division) % #property_variants_count),
|
||||
#property_name: #conversion_code,
|
||||
});
|
||||
|
||||
division *= property_variants_count;
|
||||
|
@ -402,15 +499,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
|
||||
let mut block_default_fields = quote! {};
|
||||
for PropertyWithNameAndDefault {
|
||||
struct_name: struct_name_ident,
|
||||
name,
|
||||
default: property_default,
|
||||
..
|
||||
} in properties_with_name
|
||||
{
|
||||
let name_ident = Ident::new(&name, proc_macro2::Span::call_site());
|
||||
block_default_fields.extend(quote! {
|
||||
#name_ident: #struct_name_ident::#property_default,
|
||||
})
|
||||
block_default_fields.extend(quote! {#name: #property_default,})
|
||||
}
|
||||
|
||||
let block_behavior = &block.behavior;
|
||||
|
|
|
@ -23,8 +23,17 @@ pub fn combinations_of<T: Clone>(items: &[Vec<T>]) -> Vec<Vec<T>> {
|
|||
}
|
||||
|
||||
pub fn to_pascal_case(s: &str) -> String {
|
||||
// we get the first item later so this is to make it impossible for that
|
||||
// to error
|
||||
if s.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut result = String::new();
|
||||
let mut prev_was_underscore = true; // set to true by default so the first character is capitalized
|
||||
if s.chars().next().unwrap().is_numeric() {
|
||||
result.push('_');
|
||||
}
|
||||
for c in s.chars() {
|
||||
if c == '_' {
|
||||
prev_was_underscore = true;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -159,7 +159,6 @@ impl<S> CommandDispatcher<S> {
|
|||
}
|
||||
|
||||
pub fn add_paths(
|
||||
&self,
|
||||
node: Rc<RefCell<CommandNode<S>>>,
|
||||
result: &mut Vec<Vec<Rc<RefCell<CommandNode<S>>>>>,
|
||||
parents: Vec<Rc<RefCell<CommandNode<S>>>>,
|
||||
|
@ -169,14 +168,14 @@ impl<S> CommandDispatcher<S> {
|
|||
result.push(current.clone());
|
||||
|
||||
for child in node.borrow().children.values() {
|
||||
self.add_paths(child.clone(), result, current.clone());
|
||||
Self::add_paths(child.clone(), result, current.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> {
|
||||
let rc_target = Rc::new(RefCell::new(target));
|
||||
let mut nodes: Vec<Vec<Rc<RefCell<CommandNode<S>>>>> = Vec::new();
|
||||
self.add_paths(self.root.clone(), &mut nodes, vec![]);
|
||||
Self::add_paths(self.root.clone(), &mut nodes, vec![]);
|
||||
|
||||
for list in nodes {
|
||||
if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() {
|
||||
|
|
|
@ -7,5 +7,5 @@ pub mod message;
|
|||
pub mod modifier;
|
||||
pub mod parse_results;
|
||||
pub mod string_reader;
|
||||
pub mod tree;
|
||||
pub mod suggestion;
|
||||
pub mod tree;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use crate::{read::BufReadError, McBufReadable, McBufWritable};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
ops::Deref,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A Vec<u8> that isn't prefixed by a VarInt with the size.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -27,30 +23,3 @@ impl From<&str> for UnsizedByteArray {
|
|||
Self(s.as_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents Java's BitSet, a list of bits.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BitSet {
|
||||
data: Vec<u64>,
|
||||
}
|
||||
|
||||
// the Index trait requires us to return a reference, but we can't do that
|
||||
impl BitSet {
|
||||
pub fn index(&self, index: usize) -> bool {
|
||||
(self.data[index / 64] & (1u64 << (index % 64))) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for BitSet {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
|
||||
Ok(Self {
|
||||
data: Vec::<u64>::read_from(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for BitSet {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
self.data.write_into(buf)
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@ impl McBufWritable for u64 {
|
|||
|
||||
impl McBufWritable for bool {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
let byte: u8 = if *self { 1 } else { 0 };
|
||||
let byte = u8::from(*self);
|
||||
byte.write_into(buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{movement::MoveDirection, Account, Player};
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_chat::component::Component;
|
||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
||||
use azalea_protocol::{
|
||||
|
@ -618,17 +617,8 @@ impl Client {
|
|||
}
|
||||
ClientboundGamePacket::BlockUpdate(p) => {
|
||||
debug!("Got block update packet {:?}", p);
|
||||
// TODO: update world
|
||||
let mut dimension = client.dimension.lock()?;
|
||||
// dimension.get_block_state(pos)
|
||||
if let Ok(block_state) = BlockState::try_from(p.block_state) {
|
||||
dimension.set_block_state(&p.pos, block_state);
|
||||
} else {
|
||||
warn!(
|
||||
"Non-existent block state for block update packet {:?}",
|
||||
p.block_state
|
||||
);
|
||||
}
|
||||
dimension.set_block_state(&p.pos, p.block_state);
|
||||
}
|
||||
ClientboundGamePacket::Animate(p) => {
|
||||
debug!("Got animate packet {:?}", p);
|
||||
|
|
|
@ -180,7 +180,8 @@ impl Client {
|
|||
|
||||
let mut forward_impulse: f32 = 0.;
|
||||
let mut left_impulse: f32 = 0.;
|
||||
match physics_state.move_direction {
|
||||
let move_direction = physics_state.move_direction;
|
||||
match move_direction {
|
||||
MoveDirection::Forward | MoveDirection::ForwardRight | MoveDirection::ForwardLeft => {
|
||||
forward_impulse += 1.;
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ impl Client {
|
|||
}
|
||||
_ => {}
|
||||
};
|
||||
match physics_state.move_direction {
|
||||
match move_direction {
|
||||
MoveDirection::Right | MoveDirection::ForwardRight | MoveDirection::BackwardRight => {
|
||||
left_impulse += 1.;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
use std::io::{Read, Write};
|
||||
use azalea_buf::McBuf;
|
||||
|
||||
/// Represents Java's BitSet, a list of bits.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, McBuf)]
|
||||
pub struct BitSet {
|
||||
data: Vec<u64>,
|
||||
}
|
||||
|
@ -21,70 +20,84 @@ impl BitSet {
|
|||
(self.data[index / 64] & (1u64 << (index % 64))) != 0
|
||||
}
|
||||
|
||||
// private static int wordIndex(int bitIndex) {
|
||||
// return bitIndex >> ADDRESS_BITS_PER_WORD;
|
||||
// }
|
||||
pub fn word_index(bit_index: usize) -> usize {
|
||||
fn check_range(&self, from_index: usize, to_index: usize) {
|
||||
assert!(
|
||||
from_index <= to_index,
|
||||
"fromIndex: {} > toIndex: {}",
|
||||
from_index,
|
||||
to_index
|
||||
);
|
||||
}
|
||||
|
||||
fn word_index(&self, bit_index: usize) -> usize {
|
||||
bit_index >> ADDRESS_BITS_PER_WORD
|
||||
}
|
||||
|
||||
pub fn clear_from_to(&mut self, from: usize, to: usize) {
|
||||
assert!(from <= to);
|
||||
assert!(to <= self.data.len() * 64);
|
||||
assert!(to > 0);
|
||||
pub fn clear(&mut self, from_index: usize, mut to_index: usize) {
|
||||
self.check_range(from_index, to_index);
|
||||
|
||||
if from == to {
|
||||
if from_index == to_index {
|
||||
return;
|
||||
}
|
||||
|
||||
// int startWordIndex = wordIndex(fromIndex);
|
||||
// if (startWordIndex >= wordsInUse)
|
||||
// return;
|
||||
let start_word_index = self.word_index(from_index);
|
||||
if start_word_index >= self.data.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
// int endWordIndex = wordIndex(toIndex - 1);
|
||||
// if (endWordIndex >= wordsInUse) {
|
||||
// toIndex = length();
|
||||
// endWordIndex = wordsInUse - 1;
|
||||
// }
|
||||
let mut end_word_index = self.word_index(to_index - 1);
|
||||
if end_word_index >= self.data.len() {
|
||||
to_index = self.len();
|
||||
end_word_index = self.data.len() - 1;
|
||||
}
|
||||
|
||||
// long firstWordMask = WORD_MASK << fromIndex;
|
||||
// long lastWordMask = WORD_MASK >>> -toIndex;
|
||||
// if (startWordIndex == endWordIndex) {
|
||||
// // Case 1: One word
|
||||
// words[startWordIndex] &= ~(firstWordMask & lastWordMask);
|
||||
// } else {
|
||||
// // Case 2: Multiple words
|
||||
// // Handle first word
|
||||
// words[startWordIndex] &= ~firstWordMask;
|
||||
let first_word_mask = u64::MAX << from_index;
|
||||
let last_word_mask = u64::MAX >> (64 - (to_index % 64));
|
||||
if start_word_index == end_word_index {
|
||||
// Case 1: One word
|
||||
self.data[start_word_index] &= !(first_word_mask & last_word_mask);
|
||||
} else {
|
||||
// Case 2: Multiple words
|
||||
// Handle first word
|
||||
self.data[start_word_index] &= !first_word_mask;
|
||||
|
||||
// // Handle intermediate words, if any
|
||||
// for (int i = startWordIndex+1; i < endWordIndex; i++)
|
||||
// words[i] = 0;
|
||||
// Handle intermediate words, if any
|
||||
for i in start_word_index + 1..end_word_index {
|
||||
self.data[i] = 0;
|
||||
}
|
||||
|
||||
// // Handle last word
|
||||
// words[endWordIndex] &= ~lastWordMask;
|
||||
// }
|
||||
|
||||
// recalculateWordsInUse();
|
||||
// checkInvariants();
|
||||
// Handle last word
|
||||
self.data[end_word_index] &= !last_word_mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for BitSet {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> {
|
||||
Ok(Self {
|
||||
data: Vec::<u64>::read_from(buf)?,
|
||||
})
|
||||
/// Returns the maximum potential items in the BitSet. This will be divisible by 64.
|
||||
fn len(&self) -> usize {
|
||||
self.data.len() * 64
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for BitSet {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
self.data.write_into(buf)
|
||||
/// Returns the index of the first bit that is set to `false`
|
||||
/// that occurs on or after the specified starting index.
|
||||
pub fn next_clear_bit(&self, from_index: usize) -> usize {
|
||||
let mut u = self.word_index(from_index);
|
||||
if u >= self.data.len() {
|
||||
return from_index;
|
||||
}
|
||||
|
||||
let mut word = !self.data[u] & (u64::MAX << from_index);
|
||||
|
||||
loop {
|
||||
if word != 0 {
|
||||
return (u * 64) + word.trailing_zeros() as usize;
|
||||
}
|
||||
u += 1;
|
||||
if u == self.data.len() {
|
||||
return self.data.len() * 64;
|
||||
}
|
||||
word = !self.data[u];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitSet {
|
||||
pub fn set(&mut self, bit_index: usize) {
|
||||
self.data[bit_index / 64] |= 1u64 << (bit_index % 64);
|
||||
}
|
||||
|
@ -105,4 +118,22 @@ mod tests {
|
|||
assert_eq!(bitset.index(1), true);
|
||||
assert_eq!(bitset.index(2), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let mut bitset = BitSet::new(128);
|
||||
bitset.set(62);
|
||||
bitset.set(63);
|
||||
bitset.set(64);
|
||||
bitset.set(65);
|
||||
bitset.set(66);
|
||||
|
||||
bitset.clear(63, 65);
|
||||
|
||||
assert_eq!(bitset.index(62), true);
|
||||
assert_eq!(bitset.index(63), false);
|
||||
assert_eq!(bitset.index(64), false);
|
||||
assert_eq!(bitset.index(65), true);
|
||||
assert_eq!(bitset.index(66), true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ impl AxisCycle {
|
|||
Self::Backward => Axis::from_ordinal(floor_mod(axis as i32 - 1, 3)),
|
||||
}
|
||||
}
|
||||
pub fn cycle_xyz(self, x: u32, y: u32, z: u32, axis: Axis) -> u32 {
|
||||
pub fn cycle_xyz(self, x: i32, y: i32, z: i32, axis: Axis) -> i32 {
|
||||
match self {
|
||||
Self::None => axis.choose(x, y, z),
|
||||
Self::Forward => axis.choose(z, x, y),
|
||||
|
|
|
@ -64,3 +64,39 @@ pub fn binary_search(mut min: i32, max: i32, predicate: &dyn Fn(i32) -> bool) ->
|
|||
|
||||
min
|
||||
}
|
||||
|
||||
pub fn lcm(a: u32, b: u32) -> u64 {
|
||||
let gcd = gcd(a, b);
|
||||
(a as u64) * (b / gcd) as u64
|
||||
}
|
||||
pub fn gcd(mut a: u32, mut b: u32) -> u32 {
|
||||
while b != 0 {
|
||||
let t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
a
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gcd() {
|
||||
assert_eq!(gcd(0, 0), 0);
|
||||
assert_eq!(gcd(1, 1), 1);
|
||||
|
||||
assert_eq!(gcd(0, 1), 1);
|
||||
assert_eq!(gcd(1, 0), 1);
|
||||
|
||||
assert_eq!(gcd(12, 8), 4);
|
||||
assert_eq!(gcd(8, 12), 4);
|
||||
|
||||
assert_eq!(gcd(12, 9), 3);
|
||||
assert_eq!(gcd(9, 12), 3);
|
||||
|
||||
assert_eq!(gcd(12, 7), 1);
|
||||
assert_eq!(gcd(7, 12), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ version = "0.1.0"
|
|||
azalea-block = {path = "../azalea-block", version = "^0.1.0"}
|
||||
azalea-core = {path = "../azalea-core", version = "^0.1.0"}
|
||||
azalea-world = {path = "../azalea-world", version = "^0.1.0"}
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = "^1.1.2"
|
||||
|
|
35688
azalea-physics/src/collision/blocks.rs
Normal file
35688
azalea-physics/src/collision/blocks.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,12 @@
|
|||
use crate::collision::{VoxelShape, AABB};
|
||||
use crate::collision::{BlockWithShape, VoxelShape, AABB};
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
|
||||
use azalea_world::entity::EntityData;
|
||||
use azalea_world::{Chunk, Dimension};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::Shapes;
|
||||
|
||||
pub trait CollisionGetter {
|
||||
fn get_block_collisions<'a>(
|
||||
&'a self,
|
||||
|
@ -27,12 +29,13 @@ pub struct BlockCollisions<'a> {
|
|||
pub dimension: &'a Dimension,
|
||||
// context: CollisionContext,
|
||||
pub aabb: AABB,
|
||||
|
||||
pub entity_shape: VoxelShape,
|
||||
pub cursor: Cursor3d,
|
||||
pub only_suffocating_blocks: bool,
|
||||
}
|
||||
|
||||
impl<'a> BlockCollisions<'a> {
|
||||
// TODO: the entity is stored in the context
|
||||
pub fn new(dimension: &'a Dimension, _entity: Option<&EntityData>, aabb: AABB) -> Self {
|
||||
let origin_x = (aabb.min_x - EPSILON) as i32 - 1;
|
||||
let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
|
||||
|
@ -47,6 +50,7 @@ impl<'a> BlockCollisions<'a> {
|
|||
Self {
|
||||
dimension,
|
||||
aabb,
|
||||
entity_shape: VoxelShape::from(aabb),
|
||||
cursor,
|
||||
only_suffocating_blocks: false,
|
||||
}
|
||||
|
@ -75,7 +79,7 @@ impl<'a> BlockCollisions<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for BlockCollisions<'a> {
|
||||
type Item = Box<dyn VoxelShape>;
|
||||
type Item = VoxelShape;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(item) = self.cursor.next() {
|
||||
|
@ -92,19 +96,13 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
|||
|
||||
let pos = item.pos;
|
||||
let block_state: BlockState = chunk_lock.get(&(&pos).into(), self.dimension.min_y());
|
||||
// let block: Box<dyn Block> = block_state.into();
|
||||
|
||||
// TODO: continue if self.only_suffocating_blocks and the block is not suffocating
|
||||
|
||||
let block_shape = if block_state == BlockState::Air {
|
||||
crate::collision::empty_shape()
|
||||
} else {
|
||||
crate::collision::block_shape()
|
||||
};
|
||||
// let block_shape = block.get_collision_shape();
|
||||
// if block_shape == Shapes::block() {
|
||||
if true {
|
||||
// TODO: this can be optimized
|
||||
let block_shape = block_state.shape();
|
||||
|
||||
// if it's a full block do a faster collision check
|
||||
if block_shape == &crate::collision::block_shape() {
|
||||
if !self.aabb.intersects_aabb(&AABB {
|
||||
min_x: item.pos.x as f64,
|
||||
min_y: item.pos.y as f64,
|
||||
|
@ -123,12 +121,14 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
|||
));
|
||||
}
|
||||
|
||||
// let block_shape = block_shape.move_relative(item.pos.x, item.pos.y, item.pos.z);
|
||||
// if (!Shapes.joinIsNotEmpty(block_shape, this.entityShape, BooleanOp.AND)) {
|
||||
// continue;
|
||||
// }
|
||||
let block_shape =
|
||||
block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64);
|
||||
// if the entity shape and block shape don't collide, continue
|
||||
if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// return block_shape;
|
||||
return Some(block_shape);
|
||||
}
|
||||
|
||||
None
|
||||
|
|
|
@ -1,36 +1,55 @@
|
|||
use super::mergers::IndexMerger;
|
||||
use azalea_core::{Axis, AxisCycle, BitSet};
|
||||
|
||||
// TODO: every impl of DiscreteVoxelShape could be turned into a single enum as an optimization
|
||||
pub trait IntLineConsumer = FnMut(u32, u32, u32, u32, u32, u32);
|
||||
|
||||
pub trait DiscreteVoxelShape {
|
||||
fn size(&self, axis: Axis) -> u32;
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum DiscreteVoxelShape {
|
||||
BitSet(BitSetDiscreteVoxelShape),
|
||||
}
|
||||
|
||||
fn first_full_x(&self) -> u32;
|
||||
fn first_full_y(&self) -> u32;
|
||||
fn first_full_z(&self) -> u32;
|
||||
impl DiscreteVoxelShape {
|
||||
pub fn size(&self, axis: Axis) -> u32 {
|
||||
match self {
|
||||
DiscreteVoxelShape::BitSet(shape) => shape.size(axis),
|
||||
}
|
||||
}
|
||||
|
||||
fn last_full_x(&self) -> u32;
|
||||
fn last_full_y(&self) -> u32;
|
||||
fn last_full_z(&self) -> u32;
|
||||
pub fn first_full(&self, axis: Axis) -> i32 {
|
||||
match self {
|
||||
DiscreteVoxelShape::BitSet(shape) => shape.first_full(axis),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
if self.first_full_x() >= self.last_full_x() {
|
||||
pub fn last_full(&self, axis: Axis) -> i32 {
|
||||
match self {
|
||||
DiscreteVoxelShape::BitSet(shape) => shape.last_full(axis),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
if self.first_full(Axis::X) >= self.last_full(Axis::X) {
|
||||
return true;
|
||||
}
|
||||
if self.first_full_y() >= self.last_full_y() {
|
||||
if self.first_full(Axis::Y) >= self.last_full(Axis::Y) {
|
||||
return true;
|
||||
}
|
||||
if self.first_full_x() >= self.last_full_x() {
|
||||
if self.first_full(Axis::Z) >= self.last_full(Axis::Z) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_full_wide(&self, x: u32, y: u32, z: u32) -> bool {
|
||||
pub fn is_full_wide(&self, x: i32, y: i32, z: i32) -> bool {
|
||||
if x < 0 || y < 0 || z < 0 {
|
||||
return false;
|
||||
}
|
||||
let (x, y, z) = (x as u32, y as u32, z as u32);
|
||||
(x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z))
|
||||
&& (self.is_full(x, y, z))
|
||||
}
|
||||
fn is_full_wide_axis_cycle(&self, axis_cycle: AxisCycle, x: u32, y: u32, z: u32) -> bool {
|
||||
|
||||
pub fn is_full_wide_axis_cycle(&self, axis_cycle: AxisCycle, x: i32, y: i32, z: i32) -> bool {
|
||||
self.is_full_wide(
|
||||
axis_cycle.cycle_xyz(x, y, z, Axis::X),
|
||||
axis_cycle.cycle_xyz(x, y, z, Axis::Y),
|
||||
|
@ -38,35 +57,33 @@ pub trait DiscreteVoxelShape {
|
|||
)
|
||||
}
|
||||
|
||||
fn is_full(&self, x: u32, y: u32, z: u32) -> bool;
|
||||
pub fn is_full(&self, x: u32, y: u32, z: u32) -> bool {
|
||||
match self {
|
||||
DiscreteVoxelShape::BitSet(shape) => shape.is_full(x, y, z),
|
||||
}
|
||||
}
|
||||
|
||||
// i don't know how to do this properly
|
||||
fn clone(&self) -> Box<dyn DiscreteVoxelShape>;
|
||||
pub fn for_all_boxes(&self, consumer: impl IntLineConsumer, swap: bool) {
|
||||
BitSetDiscreteVoxelShape::for_all_boxes(self, consumer, swap)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Default, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BitSetDiscreteVoxelShape {
|
||||
x_size: u32,
|
||||
y_size: u32,
|
||||
z_size: u32,
|
||||
|
||||
storage: BitSet,
|
||||
x_min: u32,
|
||||
y_min: u32,
|
||||
z_min: u32,
|
||||
x_max: u32,
|
||||
y_max: u32,
|
||||
z_max: u32,
|
||||
x_min: i32,
|
||||
y_min: i32,
|
||||
z_min: i32,
|
||||
x_max: i32,
|
||||
y_max: i32,
|
||||
z_max: i32,
|
||||
}
|
||||
|
||||
impl BitSetDiscreteVoxelShape {
|
||||
// public BitSetDiscreteVoxelShape(int var1, int var2, int var3) {
|
||||
// super(var1, var2, var3);
|
||||
// this.storage = new BitSet(var1 * var2 * var3);
|
||||
// this.xMin = var1;
|
||||
// this.yMin = var2;
|
||||
// this.zMin = var3;
|
||||
// }
|
||||
pub fn new(x_min: u32, y_min: u32, z_min: u32) -> Self {
|
||||
BitSetDiscreteVoxelShape {
|
||||
x_size: x_min,
|
||||
|
@ -74,83 +91,272 @@ impl BitSetDiscreteVoxelShape {
|
|||
z_size: z_min,
|
||||
|
||||
storage: BitSet::new((x_min * y_min * z_min).try_into().unwrap()),
|
||||
x_min,
|
||||
y_min,
|
||||
z_min,
|
||||
x_min: z_min.try_into().unwrap(),
|
||||
y_min: z_min.try_into().unwrap(),
|
||||
z_min: z_min.try_into().unwrap(),
|
||||
x_max: 0,
|
||||
y_max: 0,
|
||||
z_max: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// private void fillUpdateBounds(int var1, int var2, int var3, boolean var4) {
|
||||
// this.storage.set(this.getIndex(var1, var2, var3));
|
||||
// if (var4) {
|
||||
// this.xMin = Math.min(this.xMin, var1);
|
||||
// this.yMin = Math.min(this.yMin, var2);
|
||||
// this.zMin = Math.min(this.zMin, var3);
|
||||
// this.xMax = Math.max(this.xMax, var1 + 1);
|
||||
// this.yMax = Math.max(this.yMax, var2 + 1);
|
||||
// this.zMax = Math.max(this.zMax, var3 + 1);
|
||||
// }
|
||||
// }
|
||||
// yeah don't really feel like fixing this one
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn with_filled_bounds(
|
||||
x_size: u32,
|
||||
y_size: u32,
|
||||
z_size: u32,
|
||||
x_min: i32,
|
||||
y_min: i32,
|
||||
z_min: i32,
|
||||
x_max: i32,
|
||||
y_max: i32,
|
||||
z_max: i32,
|
||||
) -> Self {
|
||||
let mut shape = BitSetDiscreteVoxelShape::new(x_size, y_size, z_size);
|
||||
shape.x_min = x_min;
|
||||
shape.y_min = y_min;
|
||||
shape.z_min = z_min;
|
||||
shape.x_max = x_max;
|
||||
shape.y_max = y_max;
|
||||
shape.z_max = z_max;
|
||||
|
||||
for x in x_min..x_max {
|
||||
for y in y_min..y_max {
|
||||
for z in z_min..z_max {
|
||||
shape.fill_update_bounds(
|
||||
x.try_into().unwrap(),
|
||||
y.try_into().unwrap(),
|
||||
z.try_into().unwrap(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shape
|
||||
}
|
||||
|
||||
fn fill_update_bounds(&mut self, x: u32, y: u32, z: u32, update: bool) {
|
||||
self.storage.set(self.get_index(x, y, z));
|
||||
if update {
|
||||
self.x_min = std::cmp::min(self.x_min, x);
|
||||
self.y_min = std::cmp::min(self.y_min, y);
|
||||
self.z_min = std::cmp::min(self.z_min, z);
|
||||
self.x_max = std::cmp::max(self.x_max, x + 1);
|
||||
self.y_max = std::cmp::max(self.y_max, y + 1);
|
||||
self.z_max = std::cmp::max(self.z_max, z + 1);
|
||||
self.x_min = std::cmp::min(self.x_min, x as i32);
|
||||
self.y_min = std::cmp::min(self.y_min, y as i32);
|
||||
self.z_min = std::cmp::min(self.z_min, z as i32);
|
||||
self.x_max = std::cmp::max(self.x_max, (x + 1) as i32);
|
||||
self.y_max = std::cmp::max(self.y_max, (y + 1) as i32);
|
||||
self.z_max = std::cmp::max(self.z_max, (z + 1) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
// public void fill(int var1, int var2, int var3) {
|
||||
// this.fillUpdateBounds(var1, var2, var3, true);
|
||||
// }
|
||||
pub fn fill(&mut self, x: u32, y: u32, z: u32) {
|
||||
self.fill_update_bounds(x, y, z, true);
|
||||
}
|
||||
|
||||
// protected int getIndex(int var1, int var2, int var3) {
|
||||
// return (var1 * this.ySize + var2) * this.zSize + var3;
|
||||
// }
|
||||
fn get_index_from_size(x: u32, y: u32, z: u32, y_size: u32, z_size: u32) -> usize {
|
||||
((x * y_size + y) * z_size + z) as usize
|
||||
}
|
||||
fn get_index(&self, x: u32, y: u32, z: u32) -> usize {
|
||||
((x * self.y_size + y) * self.z_size + z) as usize
|
||||
Self::get_index_from_size(x, y, z, self.y_size, self.z_size)
|
||||
}
|
||||
|
||||
pub fn join(
|
||||
var0: &DiscreteVoxelShape,
|
||||
var1: &DiscreteVoxelShape,
|
||||
var2: &IndexMerger,
|
||||
var3: &IndexMerger,
|
||||
var4: &IndexMerger,
|
||||
var5: impl Fn(bool, bool) -> bool,
|
||||
) -> Self {
|
||||
let mut var6 = BitSetDiscreteVoxelShape::new(
|
||||
(var2.size() - 1) as u32,
|
||||
(var3.size() - 1) as u32,
|
||||
(var4.size() - 1) as u32,
|
||||
);
|
||||
let mut var7: [i32; 6] = [
|
||||
2147483647,
|
||||
2147483647,
|
||||
2147483647,
|
||||
-2147483648,
|
||||
-2147483648,
|
||||
-2147483648,
|
||||
];
|
||||
var2.for_merged_indexes(|var7x: i32, var8: i32, var9: i32| {
|
||||
let mut var10 = [false];
|
||||
var3.for_merged_indexes(|var10x: i32, var11: i32, var12: i32| {
|
||||
let mut var13 = [false];
|
||||
var4.for_merged_indexes(|var12x: i32, var13x: i32, var14: i32| {
|
||||
if var5(
|
||||
var0.is_full_wide(var7x, var10x, var12x),
|
||||
var1.is_full_wide(var8, var11, var13x),
|
||||
) {
|
||||
var6.storage.set(var6.get_index(
|
||||
var9.try_into().unwrap(),
|
||||
var12.try_into().unwrap(),
|
||||
var14.try_into().unwrap(),
|
||||
));
|
||||
var7[2] = std::cmp::min(var7[2], var14);
|
||||
var7[5] = std::cmp::max(var7[5], var14);
|
||||
var13[0] = true;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
if var13[0] {
|
||||
var7[1] = std::cmp::min(var7[1], var12);
|
||||
var7[4] = std::cmp::max(var7[4], var12);
|
||||
var10[0] = true;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
if var10[0] {
|
||||
var7[0] = std::cmp::min(var7[0], var9);
|
||||
var7[3] = std::cmp::max(var7[3], var9);
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
var6.x_min = var7[0];
|
||||
var6.y_min = var7[1];
|
||||
var6.z_min = var7[2];
|
||||
var6.x_max = var7[3] + 1;
|
||||
var6.y_max = var7[4] + 1;
|
||||
var6.z_max = var7[5] + 1;
|
||||
var6
|
||||
}
|
||||
|
||||
pub fn for_all_boxes(
|
||||
var0: &DiscreteVoxelShape,
|
||||
mut consumer: impl IntLineConsumer,
|
||||
var2: bool,
|
||||
) {
|
||||
let mut var3 = BitSetDiscreteVoxelShape::from(var0);
|
||||
for var4 in 0..var3.y_size {
|
||||
for var5 in 0..var3.x_size {
|
||||
let mut var6 = None;
|
||||
for var7 in 0..=var3.z_size {
|
||||
if var3.is_full_wide(var5, var4, var7) {
|
||||
if var2 {
|
||||
if var6.is_none() {
|
||||
var6 = Some(var7);
|
||||
}
|
||||
} else {
|
||||
consumer(var5, var4, var7, var5 + 1, var4 + 1, var7 + 1);
|
||||
}
|
||||
} else if var6.is_some() {
|
||||
let mut var8 = var5;
|
||||
let mut var9 = var4;
|
||||
var3.clear_z_strip(var6.unwrap(), var7, var5, var4);
|
||||
while var3.is_z_strip_full(var6.unwrap(), var7, var8 + 1, var4) {
|
||||
var3.clear_z_strip(var6.unwrap(), var7, var8 + 1, var4);
|
||||
var8 += 1;
|
||||
}
|
||||
while var3.is_xz_rectangle_full(
|
||||
var5,
|
||||
var8 + 1,
|
||||
var6.unwrap(),
|
||||
var7,
|
||||
var9 + 1,
|
||||
) {
|
||||
for var10 in var5..=var8 {
|
||||
var3.clear_z_strip(var6.unwrap(), var7, var10, var9 + 1);
|
||||
}
|
||||
var9 += 1;
|
||||
}
|
||||
consumer(var5, var4, var6.unwrap(), var8 + 1, var9 + 1, var7);
|
||||
var6 = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_z_strip_full(&self, var1: u32, var2: u32, var3: u32, var4: u32) -> bool {
|
||||
if var3 < self.x_size && var4 < self.y_size {
|
||||
self.storage
|
||||
.next_clear_bit(self.get_index(var3, var4, var1))
|
||||
>= self.get_index(var3, var4, var2)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_xz_rectangle_full(&self, var1: u32, var2: u32, var3: u32, var4: u32, var5: u32) -> bool {
|
||||
for var6 in var1..var2 {
|
||||
if !self.is_z_strip_full(var3, var4, var6, var5) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn clear_z_strip(&mut self, var1: u32, var2: u32, var3: u32, var4: u32) {
|
||||
self.storage.clear(
|
||||
self.get_index(var3, var4, var1),
|
||||
self.get_index(var3, var4, var2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl DiscreteVoxelShape for BitSetDiscreteVoxelShape {
|
||||
impl BitSetDiscreteVoxelShape {
|
||||
fn size(&self, axis: Axis) -> u32 {
|
||||
axis.choose(self.x_size, self.y_size, self.z_size)
|
||||
}
|
||||
|
||||
fn first_full_x(&self) -> u32 {
|
||||
self.x_min
|
||||
}
|
||||
fn first_full_y(&self) -> u32 {
|
||||
self.y_min
|
||||
}
|
||||
fn first_full_z(&self) -> u32 {
|
||||
self.z_min
|
||||
fn first_full(&self, axis: Axis) -> i32 {
|
||||
axis.choose(self.x_min, self.y_min, self.z_min)
|
||||
}
|
||||
|
||||
fn last_full_x(&self) -> u32 {
|
||||
self.x_max
|
||||
}
|
||||
fn last_full_y(&self) -> u32 {
|
||||
self.y_max
|
||||
}
|
||||
fn last_full_z(&self) -> u32 {
|
||||
self.z_max
|
||||
}
|
||||
|
||||
fn clone(&self) -> Box<dyn DiscreteVoxelShape> {
|
||||
Box::new(Clone::clone(self))
|
||||
fn last_full(&self, axis: Axis) -> i32 {
|
||||
axis.choose(self.x_max, self.y_max, self.z_max)
|
||||
}
|
||||
|
||||
fn is_full(&self, x: u32, y: u32, z: u32) -> bool {
|
||||
self.storage.index(self.get_index(x, y, z))
|
||||
}
|
||||
|
||||
fn is_full_wide(&self, x: u32, y: u32, z: u32) -> bool {
|
||||
(x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z))
|
||||
&& (self.is_full(x, y, z))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DiscreteVoxelShape> for BitSetDiscreteVoxelShape {
|
||||
fn from(shape: &DiscreteVoxelShape) -> Self {
|
||||
let x_size = shape.size(Axis::X);
|
||||
let y_size = shape.size(Axis::Y);
|
||||
let z_size = shape.size(Axis::Z);
|
||||
let mut storage;
|
||||
// more things could be added to DiscreteVoxelShape in the future
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let DiscreteVoxelShape::BitSet(shape) = shape {
|
||||
storage = shape.storage.clone();
|
||||
} else {
|
||||
storage = BitSet::new((x_size * y_size * z_size) as usize);
|
||||
for x in 0..x_size {
|
||||
for y in 0..y_size {
|
||||
for z in 0..z_size {
|
||||
if shape.is_full(x, y, z) {
|
||||
storage
|
||||
.set(Self::get_index_from_size(x, y, z, y_size, z_size) as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
x_size,
|
||||
y_size,
|
||||
z_size,
|
||||
storage,
|
||||
x_min: shape.first_full(Axis::X),
|
||||
y_min: shape.first_full(Axis::Y),
|
||||
z_min: shape.first_full(Axis::Z),
|
||||
x_max: shape.last_full(Axis::X),
|
||||
y_max: shape.last_full(Axis::Y),
|
||||
z_max: shape.last_full(Axis::Z),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
239
azalea-physics/src/collision/mergers.rs
Normal file
239
azalea-physics/src/collision/mergers.rs
Normal file
|
@ -0,0 +1,239 @@
|
|||
use std::{cmp::Ordering, convert::TryInto};
|
||||
|
||||
use super::CubePointRange;
|
||||
use azalea_core::{gcd, lcm, EPSILON};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IndexMerger {
|
||||
Identical {
|
||||
coords: Vec<f64>,
|
||||
},
|
||||
DiscreteCube {
|
||||
result: CubePointRange,
|
||||
first_div: u32,
|
||||
second_div: u32,
|
||||
},
|
||||
NonOverlapping {
|
||||
lower: Vec<f64>,
|
||||
upper: Vec<f64>,
|
||||
swap: bool,
|
||||
},
|
||||
Indirect {
|
||||
result: Vec<f64>,
|
||||
first_indices: Vec<isize>,
|
||||
second_indices: Vec<isize>,
|
||||
result_length: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl IndexMerger {
|
||||
pub fn get_list(&self) -> Vec<f64> {
|
||||
match self {
|
||||
IndexMerger::Identical { coords } => coords.clone(),
|
||||
IndexMerger::DiscreteCube { result, .. } => result.iter(),
|
||||
IndexMerger::NonOverlapping { lower, upper, .. } => (0..self.size())
|
||||
.map(|i| {
|
||||
if i < lower.len() {
|
||||
lower[i]
|
||||
} else {
|
||||
upper[i - lower.len()]
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
IndexMerger::Indirect {
|
||||
result,
|
||||
result_length,
|
||||
..
|
||||
} => {
|
||||
if *result_length <= 1 {
|
||||
vec![]
|
||||
} else {
|
||||
result[..*result_length].to_vec()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn for_merged_indexes(&self, mut consumer: impl IndexConsumer) -> bool {
|
||||
match self {
|
||||
IndexMerger::Identical { coords } => {
|
||||
for coord in 0..(coords.len() - 1) {
|
||||
if !consumer(coord as i32, coord as i32, coord as i32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
IndexMerger::DiscreteCube {
|
||||
result,
|
||||
first_div,
|
||||
second_div,
|
||||
} => {
|
||||
for var3 in 0..(result.size() - 1) {
|
||||
if !consumer(
|
||||
(var3 / second_div).try_into().unwrap(),
|
||||
(var3 / first_div).try_into().unwrap(),
|
||||
var3.try_into().unwrap(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
IndexMerger::NonOverlapping { lower, upper, swap } => {
|
||||
if *swap {
|
||||
for_non_swapped_indexes(lower, upper, move |var1x, var2, var3| {
|
||||
consumer(var2, var1x, var3)
|
||||
})
|
||||
} else {
|
||||
for_non_swapped_indexes(lower, upper, consumer)
|
||||
}
|
||||
}
|
||||
IndexMerger::Indirect {
|
||||
first_indices,
|
||||
second_indices,
|
||||
result_length,
|
||||
..
|
||||
} => {
|
||||
let var2 = result_length - 1;
|
||||
|
||||
for var3 in 0..var2 {
|
||||
if !consumer(
|
||||
first_indices[var3].try_into().unwrap(),
|
||||
second_indices[var3].try_into().unwrap(),
|
||||
var3.try_into().unwrap(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
IndexMerger::Identical { coords } => coords.len(),
|
||||
IndexMerger::DiscreteCube { result, .. } => result.size().try_into().unwrap(),
|
||||
IndexMerger::NonOverlapping { lower, upper, .. } => lower.len() + upper.len(),
|
||||
IndexMerger::Indirect { result_length, .. } => *result_length,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_discrete_cube(a: u32, b: u32) -> Self {
|
||||
let result = CubePointRange {
|
||||
parts: (u32::try_from(lcm(a, b)).expect("lcm should be able to fit in a u32"))
|
||||
.try_into()
|
||||
.expect("lcm should not be 0"),
|
||||
};
|
||||
let gcd = gcd(a, b);
|
||||
let first_div = a / gcd;
|
||||
let second_div = b / gcd;
|
||||
Self::DiscreteCube {
|
||||
result,
|
||||
first_div,
|
||||
second_div,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_indirect(var1: &Vec<f64>, var2: &Vec<f64>, var3: bool, var4: bool) -> Self {
|
||||
let mut var5 = f64::NAN;
|
||||
let var7 = var1.len();
|
||||
let var8 = var2.len();
|
||||
let var9 = var7 + var8;
|
||||
let mut result = vec![0.0; var9];
|
||||
let mut first_indices: Vec<isize> = vec![0; var9];
|
||||
let mut second_indices: Vec<isize> = vec![0; var9];
|
||||
let var10 = !var3;
|
||||
let var11 = !var4;
|
||||
let mut var12 = 0;
|
||||
let mut var13 = 0;
|
||||
let mut var14 = 0;
|
||||
|
||||
loop {
|
||||
let mut var17: bool;
|
||||
loop {
|
||||
let var15 = var13 >= var7;
|
||||
let var16 = var14 >= var8;
|
||||
if var15 && var16 {
|
||||
let result_length = std::cmp::max(1, var12);
|
||||
return Self::Indirect {
|
||||
result,
|
||||
first_indices,
|
||||
second_indices,
|
||||
result_length,
|
||||
};
|
||||
}
|
||||
|
||||
var17 = !var15 && (var16 || var1[var13] < var2[var14] + EPSILON);
|
||||
if var17 {
|
||||
var13 += 1;
|
||||
if !var10 || var14 != 0 && !var16 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
var14 += 1;
|
||||
if !var11 || var13 != 0 && !var15 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let var18: isize = (var13 as isize) - 1;
|
||||
let var19: isize = (var14 as isize) - 1;
|
||||
let var20 = if var17 {
|
||||
var1[TryInto::<usize>::try_into(var18).unwrap()]
|
||||
} else {
|
||||
var2[TryInto::<usize>::try_into(var19).unwrap()]
|
||||
};
|
||||
match var5.partial_cmp(&(var20 - EPSILON)) {
|
||||
None | Some(Ordering::Less) => {
|
||||
result[var12] = var20;
|
||||
first_indices[var12] = var18;
|
||||
second_indices[var12] = var19;
|
||||
var12 += 1;
|
||||
var5 = var20;
|
||||
}
|
||||
_ => {
|
||||
first_indices[var12 - 1] = var18;
|
||||
second_indices[var12 - 1] = var19;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IndexConsumer = FnMut(i32, i32, i32) -> bool;
|
||||
|
||||
fn for_non_swapped_indexes(
|
||||
lower: &Vec<f64>,
|
||||
upper: &Vec<f64>,
|
||||
mut consumer: impl IndexConsumer,
|
||||
) -> bool {
|
||||
let var2 = lower.len();
|
||||
for var3 in 0..var2 {
|
||||
if !consumer(var3.try_into().unwrap(), -1, var3.try_into().unwrap()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let var3 = upper.len() - 1;
|
||||
for var4 in 0..var3 {
|
||||
if !consumer(
|
||||
(var2 - 1).try_into().unwrap(),
|
||||
var4.try_into().unwrap(),
|
||||
(var2 + var4).try_into().unwrap(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_indirect_index_merger() {
|
||||
IndexMerger::new_indirect(&vec![0.0, 1.0], &vec![0.0, 0.5, 1.0], true, true);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
mod blocks;
|
||||
mod dimension_collisions;
|
||||
mod discrete_voxel_shape;
|
||||
mod mergers;
|
||||
mod shape;
|
||||
|
||||
use azalea_core::{Axis, PositionXYZ, Vec3, AABB, EPSILON};
|
||||
use azalea_world::entity::{EntityData, EntityMut};
|
||||
use azalea_world::{Dimension, MoveEntityError};
|
||||
pub use blocks::BlockWithShape;
|
||||
use dimension_collisions::CollisionGetter;
|
||||
pub use discrete_voxel_shape::*;
|
||||
pub use shape::*;
|
||||
|
@ -192,10 +195,9 @@ fn collide_bounding_box(
|
|||
movement: &Vec3,
|
||||
entity_bounding_box: &AABB,
|
||||
dimension: &Dimension,
|
||||
entity_collisions: Vec<Box<dyn VoxelShape>>,
|
||||
entity_collisions: Vec<VoxelShape>,
|
||||
) -> Vec3 {
|
||||
let mut collision_boxes: Vec<Box<dyn VoxelShape>> =
|
||||
Vec::with_capacity(entity_collisions.len() + 1);
|
||||
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);
|
||||
|
||||
if !entity_collisions.is_empty() {
|
||||
collision_boxes.extend(entity_collisions);
|
||||
|
@ -205,6 +207,7 @@ fn collide_bounding_box(
|
|||
|
||||
let block_collisions =
|
||||
dimension.get_block_collisions(entity, entity_bounding_box.expand_towards(movement));
|
||||
let block_collisions = block_collisions.collect::<Vec<_>>();
|
||||
collision_boxes.extend(block_collisions);
|
||||
collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
|
||||
}
|
||||
|
@ -212,7 +215,7 @@ fn collide_bounding_box(
|
|||
fn collide_with_shapes(
|
||||
movement: &Vec3,
|
||||
mut entity_box: AABB,
|
||||
collision_boxes: &Vec<Box<dyn VoxelShape>>,
|
||||
collision_boxes: &Vec<VoxelShape>,
|
||||
) -> Vec3 {
|
||||
if collision_boxes.is_empty() {
|
||||
return *movement;
|
||||
|
|
|
@ -1,28 +1,114 @@
|
|||
use super::mergers::IndexMerger;
|
||||
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
||||
use azalea_core::{binary_search, Axis, AxisCycle, EPSILON};
|
||||
use std::cmp;
|
||||
use std::{cmp, num::NonZeroU32};
|
||||
|
||||
pub struct Shapes {}
|
||||
|
||||
pub fn block_shape() -> Box<dyn VoxelShape> {
|
||||
pub fn block_shape() -> VoxelShape {
|
||||
let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1);
|
||||
shape.fill(0, 0, 0);
|
||||
Box::new(CubeVoxelShape::new(Box::new(shape)))
|
||||
VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape)))
|
||||
}
|
||||
pub fn empty_shape() -> Box<dyn VoxelShape> {
|
||||
Box::new(ArrayVoxelShape::new(
|
||||
Box::new(BitSetDiscreteVoxelShape::new(0, 0, 0)),
|
||||
|
||||
pub fn box_shape(
|
||||
min_x: f64,
|
||||
min_y: f64,
|
||||
min_z: f64,
|
||||
max_x: f64,
|
||||
max_y: f64,
|
||||
max_z: f64,
|
||||
) -> VoxelShape {
|
||||
assert!(min_x >= 0., "min_x must be >= 0 but was {}", min_x);
|
||||
assert!(min_y >= 0.);
|
||||
assert!(min_z >= 0.);
|
||||
assert!(max_x >= 0.);
|
||||
assert!(max_y >= 0.);
|
||||
assert!(max_z >= 0.);
|
||||
|
||||
box_shape_unchecked(min_x, min_y, min_z, max_x, max_y, max_z)
|
||||
}
|
||||
|
||||
pub fn box_shape_unchecked(
|
||||
min_x: f64,
|
||||
min_y: f64,
|
||||
min_z: f64,
|
||||
max_x: f64,
|
||||
max_y: f64,
|
||||
max_z: f64,
|
||||
) -> VoxelShape {
|
||||
if max_x - min_x < EPSILON && max_y - min_y < EPSILON && max_z - min_z < EPSILON {
|
||||
return empty_shape();
|
||||
}
|
||||
|
||||
let x_bits = find_bits(min_x, max_x);
|
||||
let y_bits = find_bits(min_y, max_y);
|
||||
let z_bits = find_bits(min_z, max_z);
|
||||
|
||||
if x_bits < 0 || y_bits < 0 || z_bits < 0 {
|
||||
return VoxelShape::Array(ArrayVoxelShape::new(
|
||||
block_shape().shape(),
|
||||
vec![min_x, max_x],
|
||||
vec![min_y, max_y],
|
||||
vec![min_z, max_z],
|
||||
));
|
||||
}
|
||||
if x_bits == 0 && y_bits == 0 && z_bits == 0 {
|
||||
return block_shape();
|
||||
}
|
||||
|
||||
let x_bits = 1 << x_bits;
|
||||
let y_bits = 1 << y_bits;
|
||||
let z_bits = 1 << z_bits;
|
||||
let shape = BitSetDiscreteVoxelShape::with_filled_bounds(
|
||||
x_bits,
|
||||
y_bits,
|
||||
z_bits,
|
||||
(min_x * x_bits as f64).round() as i32,
|
||||
(min_y * y_bits as f64).round() as i32,
|
||||
(min_z * z_bits as f64).round() as i32,
|
||||
(max_x * x_bits as f64).round() as i32,
|
||||
(max_y * y_bits as f64).round() as i32,
|
||||
(max_z * z_bits as f64).round() as i32,
|
||||
);
|
||||
VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(shape)))
|
||||
}
|
||||
|
||||
pub fn empty_shape() -> VoxelShape {
|
||||
VoxelShape::Array(ArrayVoxelShape::new(
|
||||
DiscreteVoxelShape::BitSet(BitSetDiscreteVoxelShape::new(0, 0, 0)),
|
||||
vec![0.],
|
||||
vec![0.],
|
||||
vec![0.],
|
||||
))
|
||||
}
|
||||
|
||||
fn find_bits(min: f64, max: f64) -> i32 {
|
||||
if min < -EPSILON || max > 1. + EPSILON {
|
||||
return -1;
|
||||
}
|
||||
for bits in 0..=3 {
|
||||
let shifted_bits = 1 << bits;
|
||||
let min = min * shifted_bits as f64;
|
||||
let max = max * shifted_bits as f64;
|
||||
let min_ok = (min - min.round()).abs() < EPSILON * shifted_bits as f64;
|
||||
let max_ok = (max - max.round()).abs() < EPSILON * shifted_bits as f64;
|
||||
if min_ok && max_ok {
|
||||
return bits;
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
impl Shapes {
|
||||
pub fn or(a: VoxelShape, b: VoxelShape) -> VoxelShape {
|
||||
Self::join(a, b, |a, b| a || b)
|
||||
}
|
||||
|
||||
pub fn collide(
|
||||
axis: &Axis,
|
||||
entity_box: &AABB,
|
||||
collision_boxes: &Vec<Box<dyn VoxelShape>>,
|
||||
collision_boxes: &Vec<VoxelShape>,
|
||||
mut movement: f64,
|
||||
) -> f64 {
|
||||
for shape in collision_boxes {
|
||||
|
@ -33,21 +119,239 @@ impl Shapes {
|
|||
}
|
||||
movement
|
||||
}
|
||||
|
||||
pub fn join(a: VoxelShape, b: VoxelShape, op: fn(bool, bool) -> bool) -> VoxelShape {
|
||||
Self::join_unoptimized(a, b, op).optimize()
|
||||
}
|
||||
|
||||
pub fn join_unoptimized(
|
||||
a: VoxelShape,
|
||||
b: VoxelShape,
|
||||
op: fn(bool, bool) -> bool,
|
||||
) -> VoxelShape {
|
||||
if op(false, false) {
|
||||
panic!("Illegal operation");
|
||||
};
|
||||
// if (a == b) {
|
||||
// return if op(true, true) { a } else { empty_shape() };
|
||||
// }
|
||||
let op_true_false = op(true, false);
|
||||
let op_false_true = op(false, true);
|
||||
if a.is_empty() {
|
||||
return if op_false_true { b } else { empty_shape() };
|
||||
}
|
||||
if b.is_empty() {
|
||||
return if op_true_false { a } else { empty_shape() };
|
||||
}
|
||||
// IndexMerger var5 = createIndexMerger(1, a.getCoords(Direction.Axis.X), b.getCoords(Direction.Axis.X), var3, var4);
|
||||
// IndexMerger var6 = createIndexMerger(var5.size() - 1, a.getCoords(Direction.Axis.Y), b.getCoords(Direction.Axis.Y), var3, var4);
|
||||
// IndexMerger var7 = createIndexMerger((var5.size() - 1) * (var6.size() - 1), a.getCoords(Direction.Axis.Z), b.getCoords(Direction.Axis.Z), var3, var4);
|
||||
// BitSetDiscreteVoxelShape var8 = BitSetDiscreteVoxelShape.join(a.shape, b.shape, var5, var6, var7, op);
|
||||
// return (VoxelShape)(var5 instanceof DiscreteCubeMerger && var6 instanceof DiscreteCubeMerger && var7 instanceof DiscreteCubeMerger ? new CubeVoxelShape(var8) : new ArrayVoxelShape(var8, var5.getList(), var6.getList(), var7.getList()));
|
||||
let var5 = Self::create_index_merger(
|
||||
1,
|
||||
a.get_coords(Axis::X),
|
||||
b.get_coords(Axis::X),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
let var6 = Self::create_index_merger(
|
||||
(var5.size() - 1).try_into().unwrap(),
|
||||
a.get_coords(Axis::Y),
|
||||
b.get_coords(Axis::Y),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
let var7 = Self::create_index_merger(
|
||||
((var5.size() - 1) * (var6.size() - 1)).try_into().unwrap(),
|
||||
a.get_coords(Axis::Z),
|
||||
b.get_coords(Axis::Z),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
let var8 = BitSetDiscreteVoxelShape::join(&a.shape(), &b.shape(), &var5, &var6, &var7, op);
|
||||
// if var5.is_discrete_cube_merger()
|
||||
if let IndexMerger::DiscreteCube { .. } = var5
|
||||
&& let IndexMerger::DiscreteCube { .. } = var6
|
||||
&& let IndexMerger::DiscreteCube { .. } = var7
|
||||
{
|
||||
VoxelShape::Cube(CubeVoxelShape::new(DiscreteVoxelShape::BitSet(var8)))
|
||||
} else {
|
||||
VoxelShape::Array(ArrayVoxelShape::new(
|
||||
DiscreteVoxelShape::BitSet(var8),
|
||||
var5.get_list(),
|
||||
var6.get_list(),
|
||||
var7.get_list(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the op is true anywhere when joining the two shapes
|
||||
/// vanilla calls this joinIsNotEmpty
|
||||
pub fn matches_anywhere(a: &VoxelShape, b: &VoxelShape, op: fn(bool, bool) -> bool) -> bool {
|
||||
assert!(!op(false, false));
|
||||
let a_is_empty = a.is_empty();
|
||||
let b_is_empty = b.is_empty();
|
||||
if a_is_empty || b_is_empty {
|
||||
return op(!a_is_empty, !b_is_empty);
|
||||
}
|
||||
if a == b {
|
||||
return op(true, true);
|
||||
}
|
||||
|
||||
let op_true_false = op(true, false);
|
||||
let op_false_true = op(false, true);
|
||||
|
||||
for axis in [Axis::X, Axis::Y, Axis::Z] {
|
||||
if a.max(axis) < b.min(axis) - EPSILON {
|
||||
return op_true_false || op_false_true;
|
||||
}
|
||||
if b.max(axis) < a.min(axis) - EPSILON {
|
||||
return op_true_false || op_false_true;
|
||||
}
|
||||
}
|
||||
|
||||
let x_merger = Self::create_index_merger(
|
||||
1,
|
||||
a.get_coords(Axis::X),
|
||||
b.get_coords(Axis::X),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
let y_merger = Self::create_index_merger(
|
||||
(x_merger.size() - 1) as i32,
|
||||
a.get_coords(Axis::Y),
|
||||
b.get_coords(Axis::Y),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
let z_merger = Self::create_index_merger(
|
||||
((x_merger.size() - 1) * (y_merger.size() - 1)) as i32,
|
||||
a.get_coords(Axis::Z),
|
||||
b.get_coords(Axis::Z),
|
||||
op_true_false,
|
||||
op_false_true,
|
||||
);
|
||||
|
||||
Self::matches_anywhere_with_mergers(x_merger, y_merger, z_merger, a.shape(), b.shape(), op)
|
||||
}
|
||||
|
||||
pub fn matches_anywhere_with_mergers(
|
||||
merged_x: IndexMerger,
|
||||
merged_y: IndexMerger,
|
||||
merged_z: IndexMerger,
|
||||
shape1: DiscreteVoxelShape,
|
||||
shape2: DiscreteVoxelShape,
|
||||
op: fn(bool, bool) -> bool,
|
||||
) -> bool {
|
||||
!merged_x.for_merged_indexes(|var5x, var6, _var7| {
|
||||
merged_y.for_merged_indexes(|var6x, var7x, _var8| {
|
||||
merged_z.for_merged_indexes(|var7, var8x, _var9| {
|
||||
!op(
|
||||
shape1.is_full_wide(var5x, var6x, var7),
|
||||
shape2.is_full_wide(var6, var7x, var8x),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_index_merger(
|
||||
_var0: i32,
|
||||
var1: Vec<f64>,
|
||||
var2: Vec<f64>,
|
||||
var3: bool,
|
||||
var4: bool,
|
||||
) -> IndexMerger {
|
||||
let var5 = var1.len() - 1;
|
||||
let var6 = var2.len() - 1;
|
||||
// if (&var1 as &dyn Any).is::<CubePointRange>() && (&var2 as &dyn Any).is::<CubePointRange>()
|
||||
// {
|
||||
// return new DiscreteCubeMerger(var0, var5, var6, var3, var4);
|
||||
// let var7: i64 = lcm(var5 as u32, var6 as u32).try_into().unwrap();
|
||||
// // if ((long)var0 * var7 <= 256L) {
|
||||
// if var0 as i64 * var7 <= 256 {
|
||||
// return IndexMerger::new_discrete_cube(var5 as u32, var6 as u32);
|
||||
// }
|
||||
// }
|
||||
|
||||
if var1[var5] < var2[0] - EPSILON {
|
||||
IndexMerger::NonOverlapping {
|
||||
lower: var1,
|
||||
upper: var2,
|
||||
swap: false,
|
||||
}
|
||||
} else if var2[var6] < var1[0] - EPSILON {
|
||||
IndexMerger::NonOverlapping {
|
||||
lower: var2,
|
||||
upper: var1,
|
||||
swap: true,
|
||||
}
|
||||
} else if var5 == var6 && var1 == var2 {
|
||||
IndexMerger::Identical { coords: var1 }
|
||||
} else {
|
||||
IndexMerger::new_indirect(&var1, &var2, var3, var4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VoxelShape {
|
||||
fn shape(&self) -> Box<dyn DiscreteVoxelShape>;
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum VoxelShape {
|
||||
Array(ArrayVoxelShape),
|
||||
Cube(CubeVoxelShape),
|
||||
}
|
||||
|
||||
fn get_coords(&self, axis: Axis) -> Vec<f64>;
|
||||
impl VoxelShape {
|
||||
// public double min(Direction.Axis var1) {
|
||||
// int var2 = this.shape.firstFull(var1);
|
||||
// return var2 >= this.shape.getSize(var1) ? 1.0D / 0.0 : this.get(var1, var2);
|
||||
// }
|
||||
// public double max(Direction.Axis var1) {
|
||||
// int var2 = this.shape.lastFull(var1);
|
||||
// return var2 <= 0 ? -1.0D / 0.0 : this.get(var1, var2);
|
||||
// }
|
||||
fn min(&self, axis: Axis) -> f64 {
|
||||
let first_full = self.shape().first_full(axis);
|
||||
if first_full >= self.shape().size(axis) as i32 {
|
||||
f64::INFINITY
|
||||
} else {
|
||||
self.get(axis, first_full.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
fn max(&self, axis: Axis) -> f64 {
|
||||
let last_full = self.shape().last_full(axis);
|
||||
if last_full <= 0 {
|
||||
f64::NEG_INFINITY
|
||||
} else {
|
||||
self.get(axis, last_full.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: optimization: should this be changed to return ArrayVoxelShape?
|
||||
// i might change the implementation of empty_shape in the future so not 100% sure
|
||||
fn move_relative(&self, x: f64, y: f64, z: f64) -> Box<dyn VoxelShape> {
|
||||
pub fn shape(&self) -> DiscreteVoxelShape {
|
||||
match self {
|
||||
VoxelShape::Array(s) => s.shape(),
|
||||
VoxelShape::Cube(s) => s.shape(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_coords(&self, axis: Axis) -> Vec<f64> {
|
||||
match self {
|
||||
VoxelShape::Array(s) => s.get_coords(axis),
|
||||
VoxelShape::Cube(s) => s.get_coords(axis),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.shape().is_empty()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn move_relative(&self, x: f64, y: f64, z: f64) -> VoxelShape {
|
||||
if self.shape().is_empty() {
|
||||
return empty_shape();
|
||||
}
|
||||
|
||||
Box::new(ArrayVoxelShape::new(
|
||||
VoxelShape::Array(ArrayVoxelShape::new(
|
||||
self.shape(),
|
||||
self.get_coords(Axis::X).iter().map(|c| c + x).collect(),
|
||||
self.get_coords(Axis::Y).iter().map(|c| c + y).collect(),
|
||||
|
@ -55,21 +359,34 @@ pub trait VoxelShape {
|
|||
))
|
||||
}
|
||||
|
||||
fn get(&self, axis: Axis, index: usize) -> f64 {
|
||||
self.get_coords(axis)[index]
|
||||
pub fn get(&self, axis: Axis, index: usize) -> f64 {
|
||||
// self.get_coords(axis)[index]
|
||||
match self {
|
||||
VoxelShape::Array(s) => s.get_coords(axis)[index],
|
||||
VoxelShape::Cube(s) => s.get_coords(axis)[index],
|
||||
// _ => self.get_coords(axis)[index],
|
||||
}
|
||||
}
|
||||
|
||||
fn find_index(&self, axis: Axis, coord: f64) -> i32 {
|
||||
let r = binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| {
|
||||
coord < self.get(axis, t as usize)
|
||||
}) - 1;
|
||||
r
|
||||
pub fn find_index(&self, axis: Axis, coord: f64) -> i32 {
|
||||
// let r = binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| {
|
||||
// coord < self.get(axis, t as usize)
|
||||
// }) - 1;
|
||||
// r
|
||||
match self {
|
||||
VoxelShape::Cube(s) => s.find_index(axis, coord),
|
||||
_ => {
|
||||
binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| {
|
||||
coord < self.get(axis, t as usize)
|
||||
}) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 {
|
||||
pub fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 {
|
||||
self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement)
|
||||
}
|
||||
fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 {
|
||||
pub fn collide_x(&self, axis_cycle: AxisCycle, entity_box: &AABB, mut movement: f64) -> f64 {
|
||||
if self.shape().is_empty() {
|
||||
return movement;
|
||||
}
|
||||
|
@ -79,7 +396,6 @@ pub trait VoxelShape {
|
|||
|
||||
let inverse_axis_cycle = axis_cycle.inverse();
|
||||
|
||||
// probably not good names but idk what this does
|
||||
let x_axis = inverse_axis_cycle.cycle(Axis::X);
|
||||
let y_axis = inverse_axis_cycle.cycle(Axis::Y);
|
||||
let z_axis = inverse_axis_cycle.cycle(Axis::Z);
|
||||
|
@ -87,40 +403,36 @@ pub trait VoxelShape {
|
|||
let max_x = entity_box.max(&x_axis);
|
||||
let min_x = entity_box.min(&x_axis);
|
||||
|
||||
// i gave up on names at this point (these are the obfuscated names from fernflower)
|
||||
let var13 = self.find_index(x_axis, min_x + EPSILON);
|
||||
let var14 = self.find_index(x_axis, max_x - EPSILON);
|
||||
let x_min_index = self.find_index(x_axis, min_x + EPSILON);
|
||||
let x_max_index = self.find_index(x_axis, max_x - EPSILON);
|
||||
|
||||
let var15 = cmp::max(
|
||||
let y_min_index = cmp::max(
|
||||
0,
|
||||
self.find_index(y_axis, entity_box.min(&y_axis) + EPSILON),
|
||||
);
|
||||
let var16 = cmp::min(
|
||||
let y_max_index = cmp::min(
|
||||
self.shape().size(y_axis) as i32,
|
||||
self.find_index(y_axis, entity_box.max(&y_axis) - EPSILON) + 1,
|
||||
);
|
||||
|
||||
let var17 = cmp::max(
|
||||
let z_min_index = cmp::max(
|
||||
0,
|
||||
self.find_index(z_axis, entity_box.min(&z_axis) + EPSILON),
|
||||
);
|
||||
let var18 = cmp::min(
|
||||
let z_max_index = cmp::min(
|
||||
self.shape().size(z_axis) as i32,
|
||||
self.find_index(z_axis, entity_box.max(&z_axis) - EPSILON) + 1,
|
||||
);
|
||||
|
||||
let var19 = self.shape().size(x_axis);
|
||||
if movement > 0. {
|
||||
for var20 in var14 + 1..(var19 as i32) {
|
||||
for var21 in var15..var16 {
|
||||
for var22 in var17..var18 {
|
||||
if self.shape().is_full_wide_axis_cycle(
|
||||
inverse_axis_cycle,
|
||||
var20.try_into().unwrap(),
|
||||
var21.try_into().unwrap(),
|
||||
var22.try_into().unwrap(),
|
||||
) {
|
||||
let var23 = self.get(x_axis, var20 as usize) - max_x;
|
||||
for x in x_max_index + 1..(self.shape().size(x_axis) as i32) {
|
||||
for y in y_min_index..y_max_index {
|
||||
for z in z_min_index..z_max_index {
|
||||
if self
|
||||
.shape()
|
||||
.is_full_wide_axis_cycle(inverse_axis_cycle, x, y, z)
|
||||
{
|
||||
let var23 = self.get(x_axis, x as usize) - max_x;
|
||||
if var23 >= -EPSILON {
|
||||
movement = f64::min(movement, var23);
|
||||
}
|
||||
|
@ -129,23 +441,19 @@ pub trait VoxelShape {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if movement < 0. {
|
||||
if var13 > 0 {
|
||||
for var20 in (var13 - 1)..=0 {
|
||||
for var21 in var15..var16 {
|
||||
for var22 in var17..var18 {
|
||||
if self.shape().is_full_wide_axis_cycle(
|
||||
inverse_axis_cycle,
|
||||
var20.try_into().unwrap(),
|
||||
var21.try_into().unwrap(),
|
||||
var22.try_into().unwrap(),
|
||||
) {
|
||||
let var23 = self.get(x_axis, (var20 + 1) as usize) - min_x;
|
||||
if var23 <= EPSILON {
|
||||
movement = f64::max(movement, var23);
|
||||
}
|
||||
return movement;
|
||||
} else if movement < 0. && x_min_index > 0 {
|
||||
for x in (0..x_min_index).rev() {
|
||||
for y in y_min_index..y_max_index {
|
||||
for z in z_min_index..z_max_index {
|
||||
if self
|
||||
.shape()
|
||||
.is_full_wide_axis_cycle(inverse_axis_cycle, x, y, z)
|
||||
{
|
||||
let var23 = self.get(x_axis, (x + 1) as usize) - min_x;
|
||||
if var23 <= EPSILON {
|
||||
movement = f64::max(movement, var23);
|
||||
}
|
||||
return movement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,33 +462,112 @@ pub trait VoxelShape {
|
|||
|
||||
movement
|
||||
}
|
||||
|
||||
// public VoxelShape optimize() {
|
||||
// VoxelShape[] var1 = new VoxelShape[]{Shapes.empty()};
|
||||
// this.forAllBoxes((var1x, var3, var5, var7, var9, var11) -> {
|
||||
// var1[0] = Shapes.joinUnoptimized(var1[0], Shapes.box(var1x, var3, var5, var7, var9, var11), BooleanOp.OR);
|
||||
// });
|
||||
// return var1[0];
|
||||
// }
|
||||
fn optimize(&self) -> VoxelShape {
|
||||
// let mut var1 = empty_shape();
|
||||
// self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
|
||||
// var1 = Shapes::join_unoptimized(
|
||||
// var1,
|
||||
// box_shape(var1x, var3, var5, var7, var9, var11),
|
||||
// |a, b| a || b,
|
||||
// );
|
||||
// });
|
||||
// var1
|
||||
let mut var1 = empty_shape();
|
||||
self.for_all_boxes(|var1x, var3, var5, var7, var9, var11| {
|
||||
var1 = Shapes::join_unoptimized(
|
||||
var1.clone(),
|
||||
box_shape(var1x, var3, var5, var7, var9, var11),
|
||||
|a, b| a || b,
|
||||
);
|
||||
});
|
||||
var1
|
||||
}
|
||||
|
||||
// public void forAllBoxes(Shapes.DoubleLineConsumer var1) {
|
||||
// DoubleList var2 = this.getCoords(Direction.Axis.X);
|
||||
// DoubleList var3 = this.getCoords(Direction.Axis.Y);
|
||||
// DoubleList var4 = this.getCoords(Direction.Axis.Z);
|
||||
// this.shape.forAllBoxes((var4x, var5, var6, var7, var8, var9) -> {
|
||||
// var1.consume(var2.getDouble(var4x), var3.getDouble(var5), var4.getDouble(var6), var2.getDouble(var7), var3.getDouble(var8), var4.getDouble(var9));
|
||||
// }, true);
|
||||
// }
|
||||
pub fn for_all_boxes(&self, mut consumer: impl FnMut(f64, f64, f64, f64, f64, f64))
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// let x_coords = self.get_coords(Axis::X);
|
||||
// let y_coords = self.get_coords(Axis::Y);
|
||||
// let z_coords = self.get_coords(Axis::Z);
|
||||
// self.shape().for_all_boxes(
|
||||
// |var4x, var5, var6, var7, var8, var9| {
|
||||
// consumer(
|
||||
// x_coords[var4x as usize],
|
||||
// y_coords[var5 as usize],
|
||||
// z_coords[var6 as usize],
|
||||
// x_coords[var7 as usize],
|
||||
// y_coords[var8 as usize],
|
||||
// z_coords[var9 as usize],
|
||||
// )
|
||||
// },
|
||||
// true,
|
||||
// );
|
||||
let x_coords = self.get_coords(Axis::X);
|
||||
let y_coords = self.get_coords(Axis::Y);
|
||||
let z_coords = self.get_coords(Axis::Z);
|
||||
self.shape().for_all_boxes(
|
||||
|var4x, var5, var6, var7, var8, var9| {
|
||||
consumer(
|
||||
x_coords[var4x as usize],
|
||||
y_coords[var5 as usize],
|
||||
z_coords[var6 as usize],
|
||||
x_coords[var7 as usize],
|
||||
y_coords[var8 as usize],
|
||||
z_coords[var9 as usize],
|
||||
)
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AABB> for VoxelShape {
|
||||
fn from(aabb: AABB) -> Self {
|
||||
box_shape_unchecked(
|
||||
aabb.min_x, aabb.min_y, aabb.min_z, aabb.max_x, aabb.max_y, aabb.max_z,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct ArrayVoxelShape {
|
||||
shape: Box<dyn DiscreteVoxelShape>,
|
||||
shape: DiscreteVoxelShape,
|
||||
// TODO: check where faces is used in minecraft
|
||||
#[allow(dead_code)]
|
||||
faces: Option<Vec<Box<dyn VoxelShape>>>,
|
||||
faces: Option<Vec<VoxelShape>>,
|
||||
|
||||
pub xs: Vec<f64>,
|
||||
pub ys: Vec<f64>,
|
||||
pub zs: Vec<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct CubeVoxelShape {
|
||||
shape: Box<dyn DiscreteVoxelShape>,
|
||||
shape: DiscreteVoxelShape,
|
||||
// TODO: check where faces is used in minecraft
|
||||
#[allow(dead_code)]
|
||||
faces: Option<Vec<Box<dyn VoxelShape>>>,
|
||||
faces: Option<Vec<VoxelShape>>,
|
||||
}
|
||||
|
||||
impl ArrayVoxelShape {
|
||||
pub fn new(
|
||||
shape: Box<dyn DiscreteVoxelShape>,
|
||||
xs: Vec<f64>,
|
||||
ys: Vec<f64>,
|
||||
zs: Vec<f64>,
|
||||
) -> Self {
|
||||
pub fn new(shape: DiscreteVoxelShape, xs: Vec<f64>, ys: Vec<f64>, zs: Vec<f64>) -> Self {
|
||||
let x_size = shape.size(Axis::X) + 1;
|
||||
let y_size = shape.size(Axis::Y) + 1;
|
||||
let z_size = shape.size(Axis::Z) + 1;
|
||||
|
@ -201,13 +588,13 @@ impl ArrayVoxelShape {
|
|||
}
|
||||
|
||||
impl CubeVoxelShape {
|
||||
pub fn new(shape: Box<dyn DiscreteVoxelShape>) -> Self {
|
||||
pub fn new(shape: DiscreteVoxelShape) -> Self {
|
||||
Self { shape, faces: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl VoxelShape for ArrayVoxelShape {
|
||||
fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
|
||||
impl ArrayVoxelShape {
|
||||
fn shape(&self) -> DiscreteVoxelShape {
|
||||
self.shape.clone()
|
||||
}
|
||||
|
||||
|
@ -216,8 +603,8 @@ impl VoxelShape for ArrayVoxelShape {
|
|||
}
|
||||
}
|
||||
|
||||
impl VoxelShape for CubeVoxelShape {
|
||||
fn shape(&self) -> Box<dyn DiscreteVoxelShape> {
|
||||
impl CubeVoxelShape {
|
||||
fn shape(&self) -> DiscreteVoxelShape {
|
||||
self.shape.clone()
|
||||
}
|
||||
|
||||
|
@ -236,6 +623,25 @@ impl VoxelShape for CubeVoxelShape {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CubePointRange {
|
||||
/// Needs at least 1 part
|
||||
pub parts: NonZeroU32,
|
||||
}
|
||||
impl CubePointRange {
|
||||
pub fn get_double(&self, index: u32) -> f64 {
|
||||
index as f64 / self.parts.get() as f64
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u32 {
|
||||
self.parts.get() + 1
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Vec<f64> {
|
||||
(0..=self.parts.get()).map(|i| self.get_double(i)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -251,4 +657,37 @@ mod tests {
|
|||
assert_eq!(shape.get_coords(Axis::Y).len(), 2);
|
||||
assert_eq!(shape.get_coords(Axis::Z).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_shape() {
|
||||
let shape = box_shape(0., 0., 0., 1., 1., 1.);
|
||||
assert_eq!(shape.shape().size(Axis::X), 1);
|
||||
assert_eq!(shape.shape().size(Axis::Y), 1);
|
||||
assert_eq!(shape.shape().size(Axis::Z), 1);
|
||||
|
||||
assert_eq!(shape.get_coords(Axis::X).len(), 2);
|
||||
assert_eq!(shape.get_coords(Axis::Y).len(), 2);
|
||||
assert_eq!(shape.get_coords(Axis::Z).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_slab_shape() {
|
||||
let shape = box_shape(0., 0.5, 0., 1., 1., 1.);
|
||||
assert_eq!(shape.shape().size(Axis::X), 1);
|
||||
assert_eq!(shape.shape().size(Axis::Y), 2);
|
||||
assert_eq!(shape.shape().size(Axis::Z), 1);
|
||||
|
||||
assert_eq!(shape.get_coords(Axis::X).len(), 2);
|
||||
assert_eq!(shape.get_coords(Axis::Y).len(), 3);
|
||||
assert_eq!(shape.get_coords(Axis::Z).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_join_is_not_empty() {
|
||||
let shape = box_shape(0., 0., 0., 1., 1., 1.);
|
||||
let shape2 = box_shape(0., 0.5, 0., 1., 1., 1.);
|
||||
// detect if the shapes intersect at all
|
||||
let joined = Shapes::matches_anywhere(&shape, &shape2, |a, b| a && b);
|
||||
assert!(joined, "Shapes should intersect");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#![feature(trait_alias)]
|
||||
#![feature(let_chains)]
|
||||
|
||||
pub mod collision;
|
||||
|
||||
use azalea_block::{Block, BlockState};
|
||||
|
@ -191,11 +194,10 @@ mod tests {
|
|||
},
|
||||
),
|
||||
);
|
||||
let old_block_state =
|
||||
dim.set_block_state(&BlockPos { x: 0, y: 69, z: 0 }, BlockState::Stone);
|
||||
let block_state = dim.set_block_state(&BlockPos { x: 0, y: 69, z: 0 }, BlockState::Stone);
|
||||
assert!(
|
||||
old_block_state.is_some(),
|
||||
"Old block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
let mut entity = dim.entity_mut(0).unwrap();
|
||||
entity.ai_step();
|
||||
|
@ -206,4 +208,100 @@ mod tests {
|
|||
// the second tick applies the delta to the position, but it also does collision
|
||||
assert_eq!(entity.pos().y, 70.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slab_collision() {
|
||||
let mut dim = Dimension::default();
|
||||
dim.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
|
||||
.unwrap();
|
||||
dim.add_entity(
|
||||
0,
|
||||
EntityData::new(
|
||||
Uuid::from_u128(0),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
),
|
||||
);
|
||||
let block_state = dim.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
BlockState::StoneSlab_BottomFalse,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
let mut entity = dim.entity_mut(0).unwrap();
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
entity.ai_step();
|
||||
}
|
||||
assert_eq!(entity.pos().y, 69.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_slab_collision() {
|
||||
let mut dim = Dimension::default();
|
||||
dim.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
|
||||
.unwrap();
|
||||
dim.add_entity(
|
||||
0,
|
||||
EntityData::new(
|
||||
Uuid::from_u128(0),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 71.,
|
||||
z: 0.5,
|
||||
},
|
||||
),
|
||||
);
|
||||
let block_state = dim.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
BlockState::StoneSlab_TopFalse,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
let mut entity = dim.entity_mut(0).unwrap();
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
entity.ai_step();
|
||||
}
|
||||
assert_eq!(entity.pos().y, 70.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weird_wall_collision() {
|
||||
let mut dim = Dimension::default();
|
||||
dim.set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default()))
|
||||
.unwrap();
|
||||
dim.add_entity(
|
||||
0,
|
||||
EntityData::new(
|
||||
Uuid::from_u128(0),
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: 73.,
|
||||
z: 0.5,
|
||||
},
|
||||
),
|
||||
);
|
||||
let block_state = dim.set_block_state(
|
||||
&BlockPos { x: 0, y: 69, z: 0 },
|
||||
BlockState::CobblestoneWall_LowLowLowFalseFalseLow,
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
let mut entity = dim.entity_mut(0).unwrap();
|
||||
// do a few steps so we fall on the slab
|
||||
for _ in 0..20 {
|
||||
entity.ai_step();
|
||||
}
|
||||
assert_eq!(entity.pos().y, 70.5);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,9 +54,11 @@ impl PlayerChatMessage {
|
|||
.content
|
||||
.decorated
|
||||
.clone()
|
||||
.unwrap_or(Component::from(self.signed_body.content.plain.clone()));
|
||||
.unwrap_or_else(|| Component::from(self.signed_body.content.plain.clone()));
|
||||
}
|
||||
self.unsigned_content.clone().unwrap_or(self.message(true))
|
||||
self.unsigned_content
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.message(true))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@ impl McBufReadable for ClientboundRecipePacket {
|
|||
};
|
||||
|
||||
Ok(ClientboundRecipePacket {
|
||||
action: action,
|
||||
settings: settings,
|
||||
recipes: recipes,
|
||||
action,
|
||||
settings,
|
||||
recipes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@ use azalea_buf::McBuf;
|
|||
use azalea_protocol_macros::ServerboundLoginPacket;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, ServerboundLoginPacket, McBuf, PartialEq)]
|
||||
#[derive(Clone, Debug, ServerboundLoginPacket, McBuf, PartialEq, Eq)]
|
||||
pub struct ServerboundHelloPacket {
|
||||
pub username: String,
|
||||
pub public_key: Option<ProfilePublicKeyData>,
|
||||
pub profile_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, McBuf, PartialEq)]
|
||||
#[derive(Clone, Debug, McBuf, PartialEq, Eq)]
|
||||
pub struct ProfilePublicKeyData {
|
||||
pub expires_at: u64,
|
||||
pub key: Vec<u8>,
|
||||
|
|
|
@ -83,7 +83,7 @@ fn packet_decoder<P: ProtocolPacket>(stream: &mut impl Read) -> Result<P, ReadPa
|
|||
// Packet ID
|
||||
let packet_id =
|
||||
u32::var_read_from(stream).map_err(|e| ReadPacketError::ReadPacketId { source: e })?;
|
||||
P::read(packet_id.try_into().unwrap(), stream)
|
||||
P::read(packet_id, stream)
|
||||
}
|
||||
|
||||
// this is always true in multiplayer, false in singleplayer
|
||||
|
|
|
@ -265,7 +265,7 @@ pub struct EntityData {
|
|||
impl EntityData {
|
||||
pub fn new(uuid: Uuid, pos: Vec3) -> Self {
|
||||
let dimensions = EntityDimensions {
|
||||
width: 0.8,
|
||||
width: 0.6,
|
||||
height: 1.8,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use azalea_client::{Account, Client, Event, MoveDirection};
|
||||
use azalea_protocol::packets::game::ClientboundGamePacket;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -25,13 +24,7 @@ async fn handle_event(event: Event, mut bot: Client) -> anyhow::Result<()> {
|
|||
// }
|
||||
// bot.walk(MoveDirection::None);
|
||||
}
|
||||
Event::Packet(packet) => {
|
||||
if let ClientboundGamePacket::SetHealth(_) = *packet {
|
||||
println!("got set health");
|
||||
bot.shutdown().await?;
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
Event::Packet(_packet) => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,12 @@ Tools for automatically generating code to help with updating Minecraft versions
|
|||
|
||||
The directory name doesn't start with `azalea-` because it's not a Rust crate.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.8+
|
||||
- Java 17+
|
||||
- Gradle
|
||||
|
||||
## Usage
|
||||
|
||||
Generate packet:\
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import lib.code.version
|
||||
import lib.code.shapes
|
||||
import lib.code.packet
|
||||
import lib.code.blocks
|
||||
import lib.code.utils
|
||||
|
@ -8,7 +9,8 @@ import lib.utils
|
|||
|
||||
version_id = lib.code.version.get_version_id()
|
||||
|
||||
lib.extract.get_generator_mod_data(version_id, 'blockCollisionShapes')
|
||||
shape_datas = lib.extract.get_generator_mod_data(
|
||||
version_id, 'blockCollisionShapes')
|
||||
|
||||
mappings = lib.download.get_mappings_for_version(version_id)
|
||||
block_states_burger = lib.extract.get_block_states_burger(version_id)
|
||||
|
@ -18,6 +20,9 @@ block_states_report = lib.extract.get_block_states_report(version_id)
|
|||
lib.code.blocks.generate_blocks(
|
||||
block_states_burger, block_states_report, ordered_blocks, mappings)
|
||||
|
||||
lib.code.shapes.generate_block_shapes(
|
||||
shape_datas['blocks'], shape_datas['shapes'], block_states_report, block_states_burger, mappings)
|
||||
|
||||
lib.code.utils.fmt()
|
||||
|
||||
print('Done!')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Optional
|
||||
from lib.utils import to_snake_case, upper_first_letter, get_dir_location, to_camel_case
|
||||
from lib.utils import get_dir_location, to_camel_case
|
||||
from lib.code.utils import clean_property_name
|
||||
from ..mappings import Mappings
|
||||
from typing import Optional
|
||||
import re
|
||||
|
||||
BLOCKS_RS_DIR = get_dir_location('../azalea-block/src/blocks.rs')
|
||||
|
@ -19,49 +20,6 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
new_make_block_states_macro_code = []
|
||||
new_make_block_states_macro_code.append('make_block_states! {')
|
||||
|
||||
def get_property_struct_name(property: Optional[dict], block_data_burger: dict, property_variants: list[str]) -> str:
|
||||
# these are hardcoded because otherwise they cause conflicts
|
||||
# some names inspired by https://github.com/feather-rs/feather/blob/main/feather/blocks/src/generated/table.rs
|
||||
if property_variants == ['north', 'east', 'south', 'west', 'up', 'down']:
|
||||
return 'FacingCubic'
|
||||
if property_variants == ['north', 'south', 'west', 'east']:
|
||||
return 'FacingCardinal'
|
||||
if property_variants == ['top', 'bottom']:
|
||||
return 'TopBottom'
|
||||
if property_variants == ['north_south', 'east_west', 'ascending_east', 'ascending_west', 'ascending_north', 'ascending_south']:
|
||||
return 'RailShape'
|
||||
if property_variants == ['straight', 'inner_left', 'inner_right', 'outer_left', 'outer_right']:
|
||||
return 'StairShape'
|
||||
if property_variants == ['normal', 'sticky']:
|
||||
return 'PistonType'
|
||||
if property_variants == ['x', 'z']:
|
||||
return 'AxisXZ'
|
||||
if property_variants == ['single', 'left', 'right']:
|
||||
return 'ChestType'
|
||||
if property_variants == ['compare', 'subtract']:
|
||||
return 'ComparatorType'
|
||||
|
||||
if property is None:
|
||||
return ''.join(map(to_camel_case, property_variants))
|
||||
|
||||
property_name = None
|
||||
for class_name in [block_data_burger['class']] + block_data_burger['super']:
|
||||
property_name = mappings.get_field(
|
||||
class_name, property['field_name'])
|
||||
if property_name:
|
||||
break
|
||||
assert property_name
|
||||
property_name = to_camel_case(property_name.lower())
|
||||
if property['type'] == 'int':
|
||||
property_name = to_camel_case(
|
||||
block_data_burger['text_id']) + property_name
|
||||
|
||||
# if property_variants == ['none', 'low', 'tall']:
|
||||
|
||||
if property_variants == ['up', 'side', 'none']:
|
||||
property_name = 'Wire' + to_camel_case(property_name)
|
||||
|
||||
return property_name
|
||||
|
||||
# Find properties
|
||||
properties = {}
|
||||
|
@ -87,7 +45,7 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
'Warning: The reports have states for a block, but Burger doesn\'t!', block_data_burger)
|
||||
|
||||
property_struct_name = get_property_struct_name(
|
||||
property_burger, block_data_burger, property_variants)
|
||||
property_burger, block_data_burger, property_variants, mappings)
|
||||
|
||||
if property_struct_name in properties:
|
||||
if not properties[property_struct_name] == property_variants:
|
||||
|
@ -99,14 +57,7 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
|
||||
block_properties[property_struct_name] = property_variants
|
||||
|
||||
# if the name ends with _<number>, remove that part
|
||||
ending = property_name.split('_')[-1]
|
||||
if ending.isdigit():
|
||||
property_name = property_name[:-(len(ending) + 1)]
|
||||
|
||||
# `type` is a reserved keyword, so we use kind instead ¯\_(ツ)_/¯
|
||||
if property_name == 'type':
|
||||
property_name = 'kind'
|
||||
property_name = clean_property_name(property_name)
|
||||
property_struct_names_to_names[property_struct_name] = property_name
|
||||
|
||||
properties.update(block_properties)
|
||||
|
@ -120,15 +71,20 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
# Ceiling,
|
||||
# },
|
||||
property_name = property_struct_names_to_names[property_struct_name]
|
||||
new_make_block_states_macro_code.append(
|
||||
f' "{property_name}" => {property_struct_name} {{')
|
||||
|
||||
for variant in property_variants:
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {to_camel_case(variant)},')
|
||||
# if the only variants are true and false, we can just make it a normal boolean
|
||||
if property_variants == ['true', 'false']:
|
||||
property_shape_code = 'bool'
|
||||
else:
|
||||
property_shape_code = f'{property_struct_name} {{\n'
|
||||
for variant in property_variants:
|
||||
property_shape_code += f' {to_camel_case(variant)},\n'
|
||||
property_shape_code += ' }'
|
||||
|
||||
new_make_block_states_macro_code.append(
|
||||
f' }},')
|
||||
f' "{property_name}" => {property_shape_code},')
|
||||
|
||||
|
||||
new_make_block_states_macro_code.append(' },')
|
||||
|
||||
# Block codegen
|
||||
|
@ -145,9 +101,8 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
if state.get('default'):
|
||||
default_property_variants = state.get('properties', {})
|
||||
|
||||
# TODO: use burger to generate the blockbehavior
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {block_id} => BlockBehavior::default(), {{')
|
||||
|
||||
properties_code = '{'
|
||||
for property_name in list(block_data_report.get('properties', {}).keys()):
|
||||
property_burger = None
|
||||
for property in block_data_burger.get('states', []):
|
||||
|
@ -159,11 +114,33 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
property_variants = block_data_report['properties'][property_name]
|
||||
|
||||
property_struct_name = get_property_struct_name(
|
||||
property_burger, block_data_burger, property_variants)
|
||||
property_burger, block_data_burger, property_variants, mappings)
|
||||
|
||||
is_boolean_property = property_variants == ['true', 'false']
|
||||
|
||||
if is_boolean_property:
|
||||
# if it's a boolean, keep the type lowercase
|
||||
# (so it's either `true` or `false`)
|
||||
property_default_type = property_default
|
||||
else:
|
||||
property_default_type = f'{property_struct_name}::{to_camel_case(property_default)}'
|
||||
|
||||
assert property_default is not None
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {property_struct_name}={to_camel_case(property_default)},')
|
||||
new_make_block_states_macro_code.append(' },')
|
||||
|
||||
property_name = clean_property_name(property_name)
|
||||
this_property_code = f'{property_name}: {property_default_type}'
|
||||
|
||||
properties_code += f'\n {this_property_code},'
|
||||
# if there's nothing inside the properties, keep it in one line
|
||||
if properties_code == '{':
|
||||
properties_code += '}'
|
||||
else:
|
||||
properties_code += '\n }'
|
||||
|
||||
# TODO: use burger to generate the blockbehavior
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {block_id} => BlockBehavior::default(), {properties_code},')
|
||||
|
||||
new_make_block_states_macro_code.append(' }')
|
||||
new_make_block_states_macro_code.append('}')
|
||||
|
||||
|
@ -185,3 +162,47 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
|
||||
with open(BLOCKS_RS_DIR, 'w') as f:
|
||||
f.write('\n'.join(new_code))
|
||||
|
||||
def get_property_struct_name(property: Optional[dict], block_data_burger: dict, property_variants: list[str], mappings: Mappings) -> str:
|
||||
# these are hardcoded because otherwise they cause conflicts
|
||||
# some names inspired by https://github.com/feather-rs/feather/blob/main/feather/blocks/src/generated/table.rs
|
||||
if property_variants == ['north', 'east', 'south', 'west', 'up', 'down']:
|
||||
return 'FacingCubic'
|
||||
if property_variants == ['north', 'south', 'west', 'east']:
|
||||
return 'FacingCardinal'
|
||||
if property_variants == ['top', 'bottom']:
|
||||
return 'TopBottom'
|
||||
if property_variants == ['north_south', 'east_west', 'ascending_east', 'ascending_west', 'ascending_north', 'ascending_south']:
|
||||
return 'RailShape'
|
||||
if property_variants == ['straight', 'inner_left', 'inner_right', 'outer_left', 'outer_right']:
|
||||
return 'StairShape'
|
||||
if property_variants == ['normal', 'sticky']:
|
||||
return 'PistonType'
|
||||
if property_variants == ['x', 'z']:
|
||||
return 'AxisXZ'
|
||||
if property_variants == ['single', 'left', 'right']:
|
||||
return 'ChestType'
|
||||
if property_variants == ['compare', 'subtract']:
|
||||
return 'ComparatorType'
|
||||
|
||||
if property is None:
|
||||
return ''.join(map(to_camel_case, property_variants))
|
||||
|
||||
property_name = None
|
||||
for class_name in [block_data_burger['class']] + block_data_burger['super']:
|
||||
property_name = mappings.get_field(
|
||||
class_name, property['field_name'])
|
||||
if property_name:
|
||||
break
|
||||
assert property_name
|
||||
property_name = to_camel_case(property_name.lower())
|
||||
if property['type'] == 'int':
|
||||
property_name = to_camel_case(
|
||||
block_data_burger['text_id']) + property_name
|
||||
|
||||
# if property_variants == ['none', 'low', 'tall']:
|
||||
|
||||
if property_variants == ['up', 'side', 'none']:
|
||||
property_name = 'Wire' + to_camel_case(property_name)
|
||||
|
||||
return property_name
|
||||
|
|
110
codegen/lib/code/shapes.py
Normal file
110
codegen/lib/code/shapes.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
from lib.utils import get_dir_location, to_camel_case
|
||||
from lib.code.utils import clean_property_name
|
||||
from .blocks import get_property_struct_name
|
||||
from ..mappings import Mappings
|
||||
|
||||
COLLISION_BLOCKS_RS_DIR = get_dir_location(
|
||||
'../azalea-physics/src/collision/blocks.rs')
|
||||
|
||||
|
||||
def generate_block_shapes(blocks: dict, shapes: dict, block_states_report, block_datas_burger, mappings: Mappings):
|
||||
code = generate_block_shapes_code(blocks, shapes, block_states_report, block_datas_burger, mappings)
|
||||
with open(COLLISION_BLOCKS_RS_DIR, 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
|
||||
def generate_block_shapes_code(blocks: dict, shapes: dict, block_states_report, block_datas_burger, mappings: Mappings):
|
||||
# look at downloads/generator-mod-*/blockCollisionShapes.json for format of blocks and shapes
|
||||
|
||||
generated_shape_code = ''
|
||||
# we make several lazy_static! blocks so it doesn't complain about
|
||||
# recursion and hopefully the compiler can paralleize it?
|
||||
generated_shape_code += 'lazy_static! {'
|
||||
for i, (shape_id, shape) in enumerate(sorted(shapes.items(), key=lambda shape: int(shape[0]))):
|
||||
if i > 0 and i % 10 == 0:
|
||||
generated_shape_code += '}\nlazy_static! {'
|
||||
generated_shape_code += generate_code_for_shape(shape_id, shape)
|
||||
generated_shape_code += '}'
|
||||
|
||||
# BlockState::PurpurStairs_NorthTopStraightTrue => &SHAPE24,
|
||||
generated_match_inner_code = ''
|
||||
shape_ids_to_variants = {}
|
||||
for block_id, shape_ids in blocks.items():
|
||||
if isinstance(shape_ids, int):
|
||||
shape_ids = [shape_ids]
|
||||
block_report_data = block_states_report['minecraft:' + block_id]
|
||||
block_data_burger = block_datas_burger[block_id]
|
||||
|
||||
for possible_state, shape_id in zip(block_report_data['states'], shape_ids):
|
||||
variant_values = []
|
||||
for value in tuple(possible_state.get('properties', {}).values()):
|
||||
variant_values.append(to_camel_case(value))
|
||||
|
||||
if variant_values == []:
|
||||
variant_name = to_camel_case(block_id)
|
||||
else:
|
||||
variant_name = f'{to_camel_case(block_id)}_{"".join(variant_values)}'
|
||||
|
||||
if shape_id not in shape_ids_to_variants:
|
||||
shape_ids_to_variants[shape_id] = []
|
||||
shape_ids_to_variants[shape_id].append(f'BlockState::{variant_name}')
|
||||
# shape 1 is the most common so we have a _ => &SHAPE1 at the end
|
||||
del shape_ids_to_variants[1]
|
||||
for shape_id, variants in shape_ids_to_variants.items():
|
||||
generated_match_inner_code += f'{"|".join(variants)} => &SHAPE{shape_id},\n'
|
||||
|
||||
|
||||
return f'''
|
||||
//! Autogenerated block collisions for every block
|
||||
|
||||
// This file is generated from codegen/lib/code/block_shapes.py. If you want to
|
||||
// modify it, change that file.
|
||||
|
||||
#![allow(clippy::explicit_auto_deref)]
|
||||
|
||||
use super::VoxelShape;
|
||||
use crate::collision::{{self, Shapes}};
|
||||
use azalea_block::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
trait BlockWithShape {{
|
||||
fn shape(&self) -> &'static VoxelShape;
|
||||
}}
|
||||
|
||||
{generated_shape_code}
|
||||
|
||||
impl BlockWithShape for BlockState {{
|
||||
fn shape(&self) -> &'static VoxelShape {{
|
||||
match self {{
|
||||
{generated_match_inner_code}_ => &SHAPE1
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
'''
|
||||
|
||||
|
||||
def generate_code_for_shape(shape_id: str, parts: list[list[float]]):
|
||||
def make_arguments(part: list[float]):
|
||||
return ', '.join(map(lambda n: str(n).rstrip('0'), part))
|
||||
code = ''
|
||||
code += f'static ref SHAPE{shape_id}: VoxelShape = '
|
||||
steps = []
|
||||
if parts == []:
|
||||
steps.append('collision::empty_shape()')
|
||||
else:
|
||||
steps.append(f'collision::box_shape({make_arguments(parts[0])})')
|
||||
for part in parts[1:]:
|
||||
steps.append(
|
||||
f'Shapes::or(s, collision::box_shape({make_arguments(part)}))')
|
||||
|
||||
if len(steps) == 1:
|
||||
code += steps[0]
|
||||
else:
|
||||
code += '{\n'
|
||||
for step in steps[:-1]:
|
||||
code += f' let s = {step};\n'
|
||||
code += f' {steps[-1]}\n'
|
||||
code += '};\n'
|
||||
return code
|
||||
|
||||
|
|
@ -147,3 +147,17 @@ def write_packet_file(state, packet_name_snake_case, code):
|
|||
|
||||
def fmt():
|
||||
os.system(f'cd {get_dir_location("..")} && cargo fmt')
|
||||
|
||||
|
||||
def clean_property_name(property_name):
|
||||
# if the name ends with _<number>, remove that part
|
||||
ending = property_name.split('_')[-1]
|
||||
if ending.isdigit():
|
||||
property_name = property_name[:-(len(ending) + 1)]
|
||||
|
||||
# `type` is a reserved keyword, so we use kind instead ¯\_(ツ)_/¯
|
||||
if property_name == 'type':
|
||||
property_name = 'kind'
|
||||
|
||||
return property_name
|
||||
|
||||
|
|
|
@ -123,9 +123,27 @@ def get_generator_mod_data(version_id: str, category: str):
|
|||
with open(get_dir_location(f'{generator_mod_dir}/src/main/resources/fabric.mod.json'), 'w') as f:
|
||||
json.dump(fabric_mod_json, f, indent=2)
|
||||
|
||||
os.system(
|
||||
f'cd {generator_mod_dir} && gradlew runServer'
|
||||
)
|
||||
try: os.system(f'cd {generator_mod_dir} && chmod u+x ./gradlew')
|
||||
except: pass
|
||||
|
||||
# set the server port to something other than 25565 so it doesn't
|
||||
# conflict with anything else that's running
|
||||
try: os.makedirs(get_dir_location(f'{generator_mod_dir}/run'))
|
||||
except: pass
|
||||
with open(get_dir_location(f'{generator_mod_dir}/run/server.properties'), 'w') as f:
|
||||
f.write('server-port=56553')
|
||||
|
||||
# make sure we have perms to run this file
|
||||
# (on windows it fails but keeps running)
|
||||
os.system(f'cd {generator_mod_dir} && chmod u+x ./gradlew')
|
||||
try:
|
||||
subprocess.run(
|
||||
[f'cd {generator_mod_dir} && ./gradlew runServer'],
|
||||
check=True,
|
||||
shell=True
|
||||
)
|
||||
except Exception as e:
|
||||
os.system(f'cd {generator_mod_dir} && gradlew runServer')
|
||||
|
||||
if os.path.exists(target_dir):
|
||||
os.unlink(target_dir)
|
||||
|
|
Loading…
Add table
Reference in a new issue