Files
schemsearch/schemsearch-lib/src/lib.rs
2023-12-27 20:44:37 +01:00

296 lines
9.4 KiB
Rust

/*
* Copyright (C) 2023 Chaoscaot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod pattern_mapper;
use serde::{Serialize, Deserialize};
use pattern_mapper::match_palette;
use schemsearch_files::SpongeSchematic;
use crate::pattern_mapper::match_palette_adapt;
use math::round::ceil;
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct SearchBehavior {
pub ignore_block_data: bool,
pub ignore_block_entities: bool,
pub ignore_air: bool,
pub air_as_any: bool,
pub ignore_entities: bool,
pub threshold: f32,
}
extern "C" {
pub fn isMatching(
schem_data: *const i32,
pattern_data: *const i32,
pattern_data_length: usize,
x: usize,
y: usize,
z: usize,
schem_width: usize,
schem_length: usize,
pattern_width: usize,
pattern_height: usize,
pattern_length: usize,
w_ptr: *mut i32,
) -> i32;
pub fn is_matching_all(
schem_data: *const i32,
pattern_data: *const i32,
schem_width: i32,
schem_height: i32,
schem_length: i32,
pattern_width: i32,
pattern_height: i32,
pattern_length: i32,
result: *mut i32
);
}
pub fn search(
schem: SpongeSchematic,
pattern_schem: &SpongeSchematic,
search_behavior: SearchBehavior,
) -> Vec<Match> {
if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length {
return Vec::new();
}
if pattern_schem.palette.len() > schem.palette.len() {
return Vec::new();
}
let pattern_schem = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data);
let mut matches: Vec<Match> = Vec::with_capacity(4);
let pattern_data = pattern_schem.block_data.as_ptr();
let schem_data = if search_behavior.ignore_block_data {
match_palette_adapt(&schem, &pattern_schem.palette, search_behavior.ignore_block_data)
} else {
schem.block_data
};
let schem_data = schem_data.as_ptr();
let air_id = if search_behavior.ignore_air || search_behavior.air_as_any { pattern_schem.palette.get("minecraft:air").unwrap_or(&-1) } else { &-1};
let pattern_blocks_usize = pattern_schem.block_data.len();
let pattern_blocks = pattern_blocks_usize as f32;
let i_pattern_blocks = pattern_blocks as i32;
let pattern_width = pattern_schem.width as usize;
let pattern_height = pattern_schem.height as usize;
let pattern_length = pattern_schem.length as usize;
let schem_width = schem.width as usize;
let schem_height = schem.height as usize;
let schem_length = schem.length as usize;
let skip_amount = ceil((pattern_blocks * (1.0 - search_behavior.threshold)) as f64, 0) as i32;
/*for y in 0..=schem_height - pattern_height {
for z in 0..=schem_length - pattern_length {
for x in 0..=schem_width - pattern_width {
let matching_count;
unsafe {
matching_count = isMatching(
schem_data,
pattern_data,
pattern_blocks_usize,
x,
y,
z,
schem_width,
schem_length,
pattern_width,
pattern_height,
pattern_length,
w_ptr,
);
};
if matching_count >= i_pattern_blocks - skip_amount {
let percent = matching_count as f32 / pattern_blocks;
if percent >= search_behavior.threshold {
matches.push(Match {
x: x as u16,
y: y as u16,
z: z as u16,
percent,
});
}
}
}
}
}*/
let mut result = Vec::<i32>::with_capacity(schem_width * schem_height * schem_length);
result.resize(schem_width * schem_height * schem_length, 0);
unsafe {
is_matching_all(
schem_data,
pattern_data,
schem_width as i32,
schem_height as i32,
schem_length as i32,
pattern_width as i32,
pattern_height as i32,
pattern_length as i32,
result.as_mut_ptr()
);
}
result.into_iter().enumerate().filter(|(_, matching_count)| *matching_count >= i_pattern_blocks - skip_amount).for_each(|(i, matching_count)| {
let percent = matching_count as f32 / pattern_blocks;
let x = i % schem_width;
let y = (i / schem_width) % schem_height;
let z = i / (schem_width * schem_height);
matches.push(Match {
x: x as u16,
y: y as u16,
z: z as u16,
percent,
});
});
return matches;
}
#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)]
pub struct Match {
pub x: u16,
pub y: u16,
pub z: u16,
pub percent: f32,
}
#[inline]
pub fn normalize_data(data: &str, ignore_data: bool) -> &str {
if ignore_data {
data.split('[').next().unwrap()
} else {
data
}
}
#[allow(unused_imports)]
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use crate::pattern_mapper::strip_data;
use super::*;
#[test]
fn read_schematic() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len());
assert_eq!(schematic.palette_max, schematic.palette.len() as i32);
}
#[test]
fn test_parse_function() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len());
assert_eq!(schematic.palette_max, schematic.palette.len() as i32);
}
#[test]
fn test_strip_schem() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
let stripped = strip_data(&schematic);
assert_eq!(stripped.palette.keys().any(|k| k.contains('[')), false);
}
#[test]
fn test_match_palette() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
let endstone = SpongeSchematic::load(&PathBuf::from("../tests/endstone.schem")).unwrap();
let _ = match_palette(&schematic, &endstone, true);
}
#[test]
fn test_match_palette_ignore_data() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
let endstone = SpongeSchematic::load(&PathBuf::from("../tests/endstone.schem")).unwrap();
let _ = match_palette(&schematic, &endstone, false);
}
#[test]
pub fn test_big_search() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
let endstone = SpongeSchematic::load(&PathBuf::from("../tests/endstone.schem")).unwrap();
let _ = search(schematic, &endstone, SearchBehavior {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
}
#[test]
pub fn test_search() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/Random.schem")).unwrap();
let pattern = SpongeSchematic::load(&PathBuf::from("../tests/Pattern.schem")).unwrap();
let matches = search(schematic, &pattern, SearchBehavior {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].x, 1);
assert_eq!(matches[0].y, 0);
assert_eq!(matches[0].z, 3);
assert_eq!(matches[0].percent, 1.0);
}
#[test]
pub fn test_search_ws() {
let schematic = SpongeSchematic::load(&PathBuf::from("../tests/warships/GreyFly-by-Bosslar.schem")).unwrap();
let pattern = SpongeSchematic::load(&PathBuf::from("../tests/gray_castle_complex.schem")).unwrap();
let matches = search(schematic, &pattern, SearchBehavior {
ignore_block_data: false,
ignore_block_entities: false,
ignore_entities: false,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
assert_eq!(matches.len(), 1);
}
}