From 4c25ee0ca926025ae54e14a31f058673714e1c6b Mon Sep 17 00:00:00 2001 From: white-axe Date: Wed, 13 Sep 2023 22:25:39 -0400 Subject: [PATCH] Tile drawing implementation and bug fixes (#38) * Fix off-by-one when the layer number is displayed * Basic tile drawing for non-autotiles * Fix tilepicker default width being too small * Fix `Table3` indexing implementation Co-authored-by: Speak2Erase * Fix tilepicker showing incorrect autotile graphics RPG Maker XP displays autotile index 47 for autotiles in the tilepicker, not 0. * Tile drawing now works with autotiles as well * Fix bottom-right corner of map not rendering * Blank tiles should always have ID 0 --------- Co-authored-by: Speak2Erase --- rmxp-types/src/rgss_structs.rs | 4 +- src/components/tilepicker.rs | 5 +- src/graphics/map.rs | 4 + src/graphics/primitives/tiles/instance.rs | 2 +- src/tabs/map.rs | 174 +++++++++++++++++++++- 5 files changed, 182 insertions(+), 7 deletions(-) diff --git a/rmxp-types/src/rgss_structs.rs b/rmxp-types/src/rgss_structs.rs index 1499374b..e9e187d4 100644 --- a/rmxp-types/src/rgss_structs.rs +++ b/rmxp-types/src/rgss_structs.rs @@ -434,12 +434,12 @@ impl Index<(usize, usize, usize)> for Table3 { type Output = i16; fn index(&self, index: (usize, usize, usize)) -> &Self::Output { - &self.data[index.0 + (index.1 * self.xsize) + (index.2 * self.ysize)] + &self.data[index.0 + self.xsize * (index.1 + self.ysize * index.2)] } } impl IndexMut<(usize, usize, usize)> for Table3 { fn index_mut(&mut self, index: (usize, usize, usize)) -> &mut Self::Output { - &mut self.data[index.0 + (index.1 * self.xsize) + (index.2 * self.ysize)] + &mut self.data[index.0 + self.xsize * (index.1 + self.ysize * index.2)] } } diff --git a/src/components/tilepicker.rs b/src/components/tilepicker.rs index 2878ed84..f57e506f 100644 --- a/src/components/tilepicker.rs +++ b/src/components/tilepicker.rs @@ -22,9 +22,10 @@ use std::time::{Duration, Instant}; #[derive(Debug)] pub struct Tilepicker { + pub selected_tile: SelectedTile, + resources: Arc, ani_instant: Instant, - selected_tile: SelectedTile, } #[derive(Debug)] @@ -50,7 +51,7 @@ impl Tilepicker { pub fn new(tileset: &rpg::Tileset) -> Result { let atlas = state!().atlas_cache.load_atlas(tileset)?; - let tilepicker_data = (0..384) + let tilepicker_data = (47..(384 + 47)) .step_by(48) .chain(384..(atlas.tileset_height as i16 / 32 * 8 + 384)) .collect_vec(); diff --git a/src/graphics/map.rs b/src/graphics/map.rs index 530cc4ea..8f34e12a 100644 --- a/src/graphics/map.rs +++ b/src/graphics/map.rs @@ -100,6 +100,10 @@ impl Map { }) } + pub fn set_tile(&self, tile_id: i16, position: (usize, usize, usize)) { + self.resources.tiles.set_tile(tile_id, position); + } + pub fn paint(&mut self, painter: &egui::Painter, rect: egui::Rect) { if self.ani_instant.elapsed() >= Duration::from_secs_f32((1. / 60.) * 16.) { self.ani_instant = Instant::now(); diff --git a/src/graphics/primitives/tiles/instance.rs b/src/graphics/primitives/tiles/instance.rs index a07a5d67..cfe72400 100644 --- a/src/graphics/primitives/tiles/instance.rs +++ b/src/graphics/primitives/tiles/instance.rs @@ -114,7 +114,7 @@ impl Instances { // Calculate the start and end index of the buffer, as well as the amount of instances. let start_index = layer * self.map_width * self.map_height; - let end_index = (layer + 1) * self.map_width * self.map_height - 1; + let end_index = (layer + 1) * self.map_width * self.map_height; let count = (end_index - start_index) as u32; // Convert the indexes into actual offsets. diff --git a/src/tabs/map.rs b/src/tabs/map.rs index e9e61285..0bb16d5c 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -57,6 +57,156 @@ impl Tab { force_close: false, }) } + + fn recompute_autotile(&self, map: &rpg::Map, position: (usize, usize, usize)) -> i16 { + if map.data[position] >= 384 { + return map.data[position]; + } + + let autotile = map.data[position] / 48; + if autotile == 0 { + return 0; + } + + let x_array: [i8; 8] = [-1, 0, 1, 1, 1, 0, -1, -1]; + let y_array: [i8; 8] = [-1, -1, -1, 0, 1, 1, 1, 0]; + + /* + * 765 + * 0 4 + * 123 + */ + let mut bitfield = 0u8; + + // Loop through the 8 neighbors of this position + for (x, y) in x_array.into_iter().zip(y_array.into_iter()) { + bitfield <<= 1; + // Out-of-bounds tiles always count as valid neighbors + if ((x == -1 && position.0 == 0) || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) || (y == 1 && position.1 + 1 == map.data.ysize())) + { + bitfield |= 1; + } + // Otherwise, we only consider neighbors that are autotiles of the same type + else if map.data[( + if x == -1 { + position.0 - 1 + } else { + position.0 + x as usize + }, + if y == -1 { + position.1 - 1 + } else { + position.1 + y as usize + }, + position.2, + )] / 48 + == autotile + { + bitfield |= 1; + } + } + + // Check how many edges have valid neighbors + autotile * 48 + + match (bitfield & 0b01010101).count_ones() { + 4 => { + // If the autotile is surrounded on all 4 edges, + // then the autotile variant is one of the first 16, + // depending on which corners are surrounded + let tl = (bitfield & 0b10000000 == 0) as u8; + let tr = (bitfield & 0b00100000 == 0) as u8; + let br = (bitfield & 0b00001000 == 0) as u8; + let bl = (bitfield & 0b00000010 == 0) as u8; + tl | (tr << 1) | (br << 2) | (bl << 3) + } + + 3 => { + // Rotate the bitfield 90 degrees counterclockwise until + // the one edge that is not surrounded is at the left + let mut bitfield = bitfield; + let mut i = 16u8; + while bitfield & 0b00000001 != 0 { + bitfield = bitfield.rotate_left(2); + i += 4; + } + // Now, the variant is one of the next 16 + let tr = (bitfield & 0b00100000 == 0) as u8; + let br = (bitfield & 0b00001000 == 0) as u8; + i + (tr | (br << 1)) + } + + // Top and bottom edges + 2 if bitfield & 0b01000100 == 0b01000100 => 32, + + // Left and right edges + 2 if bitfield & 0b00010001 == 0b00010001 => 33, + + 2 => { + // Rotate the bitfield 90 degrees counterclockwise until + // the two edges that are surrounded are at the right and bottom + let mut bitfield = bitfield; + let mut i = 34u8; + while bitfield & 0b00010100 != 0b00010100 { + bitfield = bitfield.rotate_left(2); + i += 2; + } + let br = (bitfield & 0b00001000 == 0) as u8; + i + br + } + + 1 => { + // Rotate the bitfield 90 degrees clockwise until + // the edge is at the bottom + let mut bitfield = bitfield; + let mut i = 42u8; + while bitfield & 0b00000100 == 0 { + bitfield = bitfield.rotate_right(2); + i += 1; + } + i + } + + 0 => 46, + + _ => unreachable!(), + } as i16 + } + + fn set_tile(&self, map: &mut rpg::Map, tile: SelectedTile, position: (usize, usize, usize)) { + map.data[position] = match tile { + SelectedTile::Autotile(i) => i * 48, + SelectedTile::Tile(i) => i, + }; + + for y in -1i8..=1i8 { + for x in -1i8..=1i8 { + // Don't check tiles that are out of bounds + if ((x == -1 && position.0 == 0) || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) + || (y == 1 && position.1 + 1 == map.data.ysize())) + { + continue; + } + let position = ( + if x == -1 { + position.0 - 1 + } else { + position.0 + x as usize + }, + if y == -1 { + position.1 - 1 + } else { + position.1 + y as usize + }, + position.2, + ); + let tile_id = self.recompute_autotile(map, position); + map.data[position] = tile_id; + self.view.map.set_tile(tile_id, position); + } + } + } } impl tab::Tab for Tab { @@ -94,7 +244,7 @@ impl tab::Tab for Tab { // Format the text based on what layer is selected. match self.view.selected_layer { SelectedLayer::Events => "Events ⏷".to_string(), - SelectedLayer::Tiles(layer) => format!("Layer {layer} ⏷"), + SelectedLayer::Tiles(layer) => format!("Layer {} ⏷", layer + 1), }, |ui| { // TODO: Add layer enable button @@ -153,11 +303,19 @@ impl tab::Tab for Tab { }); // Display the tilepicker. + let spacing = ui.spacing(); + let tilepicker_default_width = 256. + + 3. * spacing.window_margin.left + + spacing.scroll_bar_inner_margin + + spacing.scroll_bar_width + + spacing.scroll_bar_outer_margin; egui::SidePanel::left(format!("map_{}_tilepicker", self.id)) - .default_width(256.) + .default_width(tilepicker_default_width) + .max_width(tilepicker_default_width) .show_inside(ui, |ui| { egui::ScrollArea::both().show(ui, |ui| { self.tilepicker.ui(ui); + ui.separator(); }); }); @@ -169,6 +327,18 @@ impl tab::Tab for Tab { let map_x = self.view.cursor_pos.x as i32; let map_y = self.view.cursor_pos.y as i32; + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { + if response.dragged_by(egui::PointerButton::Primary) + && !ui.input(|i| i.modifiers.command) + { + self.set_tile( + &mut map, + self.tilepicker.selected_tile, + (map_x as usize, map_y as usize, tile_layer), + ); + } + } + if ui.input(|i| { i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace) }) {