Skip to content

Commit

Permalink
added nine slice drawing op for tile map edit mode
Browse files Browse the repository at this point in the history
- added line drawing method
  • Loading branch information
mrDIMAS committed Jul 29, 2024
1 parent 4690ff7 commit 8e8059b
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 2 deletions.
Binary file added editor/resources/nine_slice.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 31 additions & 1 deletion editor/src/plugins/tilemap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub enum DrawingMode {
RectFill {
click_grid_position: Option<Vector2<i32>>,
},
NineSlice {
click_grid_position: Option<Vector2<i32>>,
},
}

struct InteractionContext {
Expand Down Expand Up @@ -170,6 +173,9 @@ impl InteractionMode for TileMapInteractionMode {
}
| DrawingMode::Pick {
ref mut click_grid_position,
}
| DrawingMode::NineSlice {
ref mut click_grid_position,
} => {
*click_grid_position = Some(grid_coord);
}
Expand Down Expand Up @@ -237,7 +243,16 @@ impl InteractionMode for TileMapInteractionMode {
);
}
}

DrawingMode::NineSlice {
click_grid_position,
} => {
if let Some(click_grid_position) = click_grid_position {
tile_map.tiles.nine_slice(
Rect::from_points(grid_coord, click_grid_position),
&brush,
)
}
}
_ => (),
}
}
Expand Down Expand Up @@ -363,6 +378,9 @@ impl InteractionMode for TileMapInteractionMode {
}
| DrawingMode::RectFill {
click_grid_position,
}
| DrawingMode::NineSlice {
click_grid_position,
} => {
if self.interaction_context.is_some() {
if let Some(click_grid_position) = click_grid_position {
Expand Down Expand Up @@ -418,6 +436,18 @@ impl InteractionMode for TileMapInteractionMode {
}
}
}
DrawingMode::NineSlice {
click_grid_position,
} => {
if self.interaction_context.is_some() {
if let Some(click_grid_position) = click_grid_position {
tile_map.overlay_tiles.nine_slice(
Rect::from_points(self.brush_position, click_grid_position),
&brush,
);
}
}
}
}
}

Expand Down
20 changes: 19 additions & 1 deletion editor/src/plugins/tilemap/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub struct TileMapPanel {
flood_fill_button: Handle<UiNode>,
pick_button: Handle<UiNode>,
rect_fill_button: Handle<UiNode>,
nine_slice_button: Handle<UiNode>,
}

fn generate_tiles(
Expand Down Expand Up @@ -208,6 +209,14 @@ impl TileMapPanel {
"Fill the rectangle using the current brush.",
Some(0),
);
let nine_slice_button = make_drawing_mode_button(
ctx,
width,
height,
load_image(include_bytes!("../../../resources/nine_slice.png")),
"Draw rectangles with fixed corners, but stretchable sides.",
Some(0),
);

let drawing_modes_panel = WrapPanelBuilder::new(
WidgetBuilder::new()
Expand All @@ -216,7 +225,8 @@ impl TileMapPanel {
.with_child(erase_button)
.with_child(flood_fill_button)
.with_child(pick_button)
.with_child(rect_fill_button),
.with_child(rect_fill_button)
.with_child(nine_slice_button),
)
.with_orientation(Orientation::Horizontal)
.build(ctx);
Expand Down Expand Up @@ -269,6 +279,7 @@ impl TileMapPanel {
flood_fill_button,
pick_button,
rect_fill_button,
nine_slice_button,
}
}

Expand Down Expand Up @@ -432,6 +443,10 @@ impl TileMapPanel {
interaction_mode.drawing_mode = DrawingMode::Pick {
click_grid_position: Default::default(),
};
} else if message.destination() == self.nine_slice_button {
interaction_mode.drawing_mode = DrawingMode::NineSlice {
click_grid_position: Default::default(),
};
} else if message.destination() == self.edit {
sender.send(Message::SetInteractionMode(
TileMapInteractionMode::type_uuid(),
Expand Down Expand Up @@ -495,6 +510,9 @@ impl TileMapPanel {
DrawingMode::RectFill { .. } => {
highlight_all_except(self.rect_fill_button, &buttons, true, ui);
}
DrawingMode::NineSlice { .. } => {
highlight_all_except(self.nine_slice_button, &buttons, true, ui);
}
}
}
}
Expand Down
170 changes: 170 additions & 0 deletions fyrox-impl/src/scene/tilemap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,73 @@ use crate::{
use fxhash::{FxHashMap, FxHashSet};
use std::ops::{Deref, DerefMut};

struct BresenhamLineIter {
dx: i32,
dy: i32,
x: i32,
y: i32,
error: i32,
end_x: i32,
is_steep: bool,
y_step: i32,
}

impl BresenhamLineIter {
fn new(start: Vector2<i32>, end: Vector2<i32>) -> BresenhamLineIter {
let (mut x0, mut y0) = (start.x, start.y);
let (mut x1, mut y1) = (end.x, end.y);

let is_steep = (y1 - y0).abs() > (x1 - x0).abs();
if is_steep {
std::mem::swap(&mut x0, &mut y0);
std::mem::swap(&mut x1, &mut y1);
}

if x0 > x1 {
std::mem::swap(&mut x0, &mut x1);
std::mem::swap(&mut y0, &mut y1);
}

let dx = x1 - x0;

BresenhamLineIter {
dx,
dy: (y1 - y0).abs(),
x: x0,
y: y0,
error: dx / 2,
end_x: x1,
is_steep,
y_step: if y0 < y1 { 1 } else { -1 },
}
}
}

impl Iterator for BresenhamLineIter {
type Item = Vector2<i32>;

fn next(&mut self) -> Option<Vector2<i32>> {
if self.x > self.end_x {
None
} else {
let ret = if self.is_steep {
Vector2::new(self.y, self.x)
} else {
Vector2::new(self.x, self.y)
};

self.x += 1;
self.error -= self.dy;
if self.error < 0 {
self.y += self.y_step;
self.error += self.dx;
}

Some(ret)
}
}
}

/// Tile is a base block of a tile map. It has a position and a handle of tile definition, stored
/// in the respective tile set.
#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
Expand Down Expand Up @@ -216,6 +283,109 @@ impl Tiles {
}
}
}

/// Draw a line from a point to point.
#[inline]
pub fn line(
&mut self,
from: Vector2<i32>,
to: Vector2<i32>,
definition_handle: TileDefinitionHandle,
) {
for position in BresenhamLineIter::new(from, to) {
self.insert(Tile {
position,
definition_handle,
});
}
}

/// Fills in a rectangle using special brush with 3x3 tiles. It puts
/// corner tiles in the respective corners of the target rectangle and draws lines between each
/// corner using middle tiles.
#[inline]
pub fn nine_slice(&mut self, rect: Rect<i32>, brush: &TileMapBrush) {
let brush_rect = brush.bounding_rect();

// Place corners first.
for (corner_position, actual_corner_position) in [
(Vector2::new(0, 0), rect.left_top_corner()),
(Vector2::new(2, 0), rect.right_top_corner()),
(Vector2::new(2, 2), rect.right_bottom_corner()),
(Vector2::new(0, 2), rect.left_bottom_corner()),
] {
if let Some(tile) = brush
.tiles
.iter()
.find(|tile| tile.local_position - brush_rect.position == corner_position)
{
self.insert(Tile {
position: actual_corner_position,
definition_handle: tile.definition_handle,
});
}
}

// Fill gaps.
for (brush_tile_position, (begin, end)) in [
(
Vector2::new(0, 1),
(
Vector2::new(rect.position.x, rect.position.y + 1),
Vector2::new(rect.position.x, rect.position.y + rect.size.y - 1),
),
),
(
Vector2::new(1, 0),
(
Vector2::new(rect.position.x + 1, rect.position.y),
Vector2::new(rect.position.x + rect.size.x - 1, rect.position.y),
),
),
(
Vector2::new(2, 1),
(
Vector2::new(rect.position.x + rect.size.x, rect.position.y + 1),
Vector2::new(
rect.position.x + rect.size.x,
rect.position.y + rect.size.y - 1,
),
),
),
(
Vector2::new(1, 2),
(
Vector2::new(rect.position.x + 1, rect.position.y + rect.size.y),
Vector2::new(
rect.position.x + rect.size.x - 1,
rect.position.y + rect.size.y,
),
),
),
] {
if let Some(tile) = brush
.tiles
.iter()
.find(|tile| tile.local_position - brush_rect.position == brush_tile_position)
{
self.line(begin, end, tile.definition_handle);
}
}

if let Some(center_tile) = brush
.tiles
.iter()
.find(|tile| tile.local_position - brush_rect.position == Vector2::new(1, 1))
{
self.flood_fill(
rect.center(),
&TileMapBrush {
// TODO: Remove alloc.
tiles: vec![center_tile.clone()],
},
);
}
}
}

/// Tile map is a 2D "image", made out of a small blocks called tiles. Tile maps used in 2D games to
Expand Down

0 comments on commit 8e8059b

Please sign in to comment.