Skip to content

Commit

Permalink
Tile drawing implementation and bug fixes (#38)
Browse files Browse the repository at this point in the history
* 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 <lily@nowaffles.com>

* 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 <lily@nowaffles.com>
  • Loading branch information
white-axe and melody-rs authored Sep 14, 2023
1 parent bb49cab commit 4c25ee0
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 7 deletions.
4 changes: 2 additions & 2 deletions rmxp-types/src/rgss_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
}
}
5 changes: 3 additions & 2 deletions src/components/tilepicker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ use std::time::{Duration, Instant};

#[derive(Debug)]
pub struct Tilepicker {
pub selected_tile: SelectedTile,

resources: Arc<Resources>,
ani_instant: Instant,
selected_tile: SelectedTile,
}

#[derive(Debug)]
Expand All @@ -50,7 +51,7 @@ impl Tilepicker {
pub fn new(tileset: &rpg::Tileset) -> Result<Tilepicker, String> {
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();
Expand Down
4 changes: 4 additions & 0 deletions src/graphics/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/graphics/primitives/tiles/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
174 changes: 172 additions & 2 deletions src/tabs/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
});
});

Expand All @@ -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)
}) {
Expand Down

0 comments on commit 4c25ee0

Please sign in to comment.