/* * 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 . */ 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 { 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 = 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::::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); } }