1
2
Fork 0
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:
mat 2022-10-02 12:29:47 -05:00 committed by GitHub
parent aa78491ee0
commit c9b4dccd7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 39221 additions and 2711 deletions

1
Cargo.lock generated
View file

@ -236,6 +236,7 @@ dependencies = [
"azalea-block",
"azalea-core",
"azalea-world",
"lazy_static",
"uuid",
]

View file

@ -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;

View file

@ -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

View file

@ -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() {

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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);

View file

@ -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.;
}

View file

@ -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);
}
}

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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"

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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),
}
}
}

View 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);
}
}

View file

@ -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;

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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))
}
}

View file

@ -46,9 +46,9 @@ impl McBufReadable for ClientboundRecipePacket {
};
Ok(ClientboundRecipePacket {
action: action,
settings: settings,
recipes: recipes,
action,
settings,
recipes,
})
}
}

View file

@ -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>,

View file

@ -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

View file

@ -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,
};

View file

@ -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) => {}
_ => {}
}

View file

@ -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:\

View file

@ -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!')

View file

@ -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
View 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

View file

@ -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

View file

@ -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)