Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tile drawing implementation and bug fixes #38

Merged
merged 12 commits into from
Sep 14, 2023
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah, how did you figure this out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at how RPG Maker XP behaves when you place autotiles and used how the autotile variants are ordered numerically to deduce this algorithm

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