added initial mask export
This commit is contained in:
@@ -22,6 +22,8 @@ Cargo.lock
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
#TODO file for my own personal use
|
||||||
|
TODO
|
||||||
|
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
|
|||||||
+202
-52
@@ -7,7 +7,7 @@ use rfd::FileDialog;
|
|||||||
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
||||||
|
|
||||||
use crate::layout::{Door, DungeonLayout, Room};
|
use crate::layout::{Door, DungeonLayout, Room};
|
||||||
use crate::ui::{ExportFormat, UiSettings};
|
use crate::ui::{ExportFormat, MaskFormat, UiSettings};
|
||||||
|
|
||||||
const BG: (u8, u8, u8, u8) = (24, 24, 26, 255);
|
const BG: (u8, u8, u8, u8) = (24, 24, 26, 255);
|
||||||
const GRID: (u8, u8, u8, u8) = (130, 130, 130, 255);
|
const GRID: (u8, u8, u8, u8) = (130, 130, 130, 255);
|
||||||
@@ -17,6 +17,7 @@ const BLACK: (u8, u8, u8, u8) = (0, 0, 0, 255);
|
|||||||
const OPEN_DOOR: (u8, u8, u8, u8) = (220, 70, 70, 255);
|
const OPEN_DOOR: (u8, u8, u8, u8) = (220, 70, 70, 255);
|
||||||
const LOCKED_DOOR: (u8, u8, u8, u8) = (80, 200, 120, 255);
|
const LOCKED_DOOR: (u8, u8, u8, u8) = (80, 200, 120, 255);
|
||||||
const ARCHWAY: (u8, u8, u8, u8) = (70, 130, 220, 255);
|
const ARCHWAY: (u8, u8, u8, u8) = (70, 130, 220, 255);
|
||||||
|
const WHITE: (u8, u8, u8, u8) = (255, 255, 255, 255);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum DoorStyle {
|
enum DoorStyle {
|
||||||
@@ -77,10 +78,54 @@ impl ExportGeometry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SceneData {
|
||||||
|
corridor_cells: HashSet<(usize, usize)>,
|
||||||
|
corridor_edges: HashSet<((usize, usize), (usize, usize))>,
|
||||||
|
room_cells: HashSet<(usize, usize)>,
|
||||||
|
door_edges: HashSet<((usize, usize), (usize, usize))>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_scene_data(layout: &DungeonLayout) -> SceneData {
|
||||||
|
let mut scene = SceneData::default();
|
||||||
|
|
||||||
|
for door in &layout.doors {
|
||||||
|
scene.door_edges.insert(norm_edge(door.from, door.to));
|
||||||
|
}
|
||||||
|
|
||||||
|
for corridor in &layout.corridors {
|
||||||
|
for &cell in &corridor.path {
|
||||||
|
scene.corridor_cells.insert(cell);
|
||||||
|
}
|
||||||
|
for pair in corridor.path.windows(2) {
|
||||||
|
scene.corridor_edges.insert(norm_edge(pair[0], pair[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for room in &layout.rooms {
|
||||||
|
for x in room.x..(room.x + room.width) {
|
||||||
|
for y in room.y..(room.y + room.height) {
|
||||||
|
scene.room_cells.insert((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scene
|
||||||
|
}
|
||||||
|
|
||||||
pub fn export_with_dialog(
|
pub fn export_with_dialog(
|
||||||
layout: &DungeonLayout,
|
layout: &DungeonLayout,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
|
if settings.export_format == ExportFormat::Folder {
|
||||||
|
let folder = FileDialog::new()
|
||||||
|
.set_title("Export composite masks folder")
|
||||||
|
.pick_folder()
|
||||||
|
.ok_or_else(|| "Export canceled".to_string())?;
|
||||||
|
export_composite_masks(layout, settings, &folder)?;
|
||||||
|
return Ok(folder);
|
||||||
|
}
|
||||||
|
|
||||||
let ext = settings.export_format.extension();
|
let ext = settings.export_format.extension();
|
||||||
let default_name = format!("dungeon_export.{ext}");
|
let default_name = format!("dungeon_export.{ext}");
|
||||||
|
|
||||||
@@ -110,11 +155,12 @@ pub fn export_with_dialog(
|
|||||||
ExportFormat::Png => ImageFormat::Png,
|
ExportFormat::Png => ImageFormat::Png,
|
||||||
ExportFormat::Jpeg => ImageFormat::Jpeg,
|
ExportFormat::Jpeg => ImageFormat::Jpeg,
|
||||||
ExportFormat::Webp => ImageFormat::WebP,
|
ExportFormat::Webp => ImageFormat::WebP,
|
||||||
ExportFormat::Svg => unreachable!(),
|
ExportFormat::Svg | ExportFormat::Folder => unreachable!(),
|
||||||
};
|
};
|
||||||
img.save_with_format(&path, format)
|
img.save_with_format(&path, format)
|
||||||
.map_err(|e| format!("Failed writing image: {e}"))?;
|
.map_err(|e| format!("Failed writing image: {e}"))?;
|
||||||
}
|
}
|
||||||
|
ExportFormat::Folder => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(path)
|
Ok(path)
|
||||||
@@ -122,6 +168,7 @@ pub fn export_with_dialog(
|
|||||||
|
|
||||||
fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap, String> {
|
fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap, String> {
|
||||||
let g = ExportGeometry::new(settings.cols, settings.rows);
|
let g = ExportGeometry::new(settings.cols, settings.rows);
|
||||||
|
let scene = collect_scene_data(layout);
|
||||||
let mut pixmap =
|
let mut pixmap =
|
||||||
Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?;
|
Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?;
|
||||||
fill_bg(&mut pixmap, BG);
|
fill_bg(&mut pixmap, BG);
|
||||||
@@ -131,33 +178,7 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
|||||||
|
|
||||||
draw_grid(&mut pixmap, &g);
|
draw_grid(&mut pixmap, &g);
|
||||||
|
|
||||||
let mut corridor_cells = HashSet::new();
|
for &(x, y) in &scene.corridor_cells {
|
||||||
let mut corridor_edges = HashSet::new();
|
|
||||||
let mut room_cells = HashSet::new();
|
|
||||||
let mut door_edges = HashSet::new();
|
|
||||||
|
|
||||||
for door in &layout.doors {
|
|
||||||
door_edges.insert(norm_edge(door.from, door.to));
|
|
||||||
}
|
|
||||||
|
|
||||||
for corridor in &layout.corridors {
|
|
||||||
for &cell in &corridor.path {
|
|
||||||
corridor_cells.insert(cell);
|
|
||||||
}
|
|
||||||
for pair in corridor.path.windows(2) {
|
|
||||||
corridor_edges.insert(norm_edge(pair[0], pair[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for room in &layout.rooms {
|
|
||||||
for x in room.x..(room.x + room.width) {
|
|
||||||
for y in room.y..(room.y + room.height) {
|
|
||||||
room_cells.insert((x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for &(x, y) in &corridor_cells {
|
|
||||||
fill_rect_cell(&mut pixmap, &g, x, y, CORRIDOR);
|
fill_rect_cell(&mut pixmap, &g, x, y, CORRIDOR);
|
||||||
if settings.colorblind_mode {
|
if settings.colorblind_mode {
|
||||||
let (cx, cy) = g.cell_center(x, y);
|
let (cx, cy) = g.cell_center(x, y);
|
||||||
@@ -175,18 +196,18 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
|||||||
draw_cell_walls(
|
draw_cell_walls(
|
||||||
&mut pixmap,
|
&mut pixmap,
|
||||||
&g,
|
&g,
|
||||||
&corridor_cells,
|
&scene.corridor_cells,
|
||||||
Some(&corridor_edges),
|
Some(&scene.corridor_edges),
|
||||||
&door_edges,
|
&scene.door_edges,
|
||||||
wall_w,
|
wall_w,
|
||||||
BLACK,
|
BLACK,
|
||||||
);
|
);
|
||||||
draw_cell_walls(
|
draw_cell_walls(
|
||||||
&mut pixmap,
|
&mut pixmap,
|
||||||
&g,
|
&g,
|
||||||
&room_cells,
|
&scene.room_cells,
|
||||||
None,
|
None,
|
||||||
&door_edges,
|
&scene.door_edges,
|
||||||
wall_w,
|
wall_w,
|
||||||
BLACK,
|
BLACK,
|
||||||
);
|
);
|
||||||
@@ -214,6 +235,92 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
|||||||
Ok(pixmap)
|
Ok(pixmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn export_composite_masks(
|
||||||
|
layout: &DungeonLayout,
|
||||||
|
settings: &UiSettings,
|
||||||
|
folder: &std::path::Path,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
std::fs::create_dir_all(folder).map_err(|e| format!("Failed creating folder: {e}"))?;
|
||||||
|
|
||||||
|
let g = ExportGeometry::new(settings.cols, settings.rows);
|
||||||
|
let scene = collect_scene_data(layout);
|
||||||
|
let wall_w = (g.cell / 5.0).max(1.0);
|
||||||
|
let door_w = (g.cell / 10.0).max(1.0);
|
||||||
|
|
||||||
|
let mut floors = Pixmap::new(g.width, g.height)
|
||||||
|
.ok_or_else(|| "Failed to allocate floors mask canvas".to_string())?;
|
||||||
|
fill_bg(&mut floors, BG);
|
||||||
|
for &(x, y) in &scene.corridor_cells {
|
||||||
|
fill_rect_cell(&mut floors, &g, x, y, WHITE);
|
||||||
|
}
|
||||||
|
for room in &layout.rooms {
|
||||||
|
fill_rect_room(&mut floors, &g, room, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut walls = Pixmap::new(g.width, g.height)
|
||||||
|
.ok_or_else(|| "Failed to allocate walls mask canvas".to_string())?;
|
||||||
|
fill_bg(&mut walls, BG);
|
||||||
|
draw_cell_walls(
|
||||||
|
&mut walls,
|
||||||
|
&g,
|
||||||
|
&scene.corridor_cells,
|
||||||
|
Some(&scene.corridor_edges),
|
||||||
|
&scene.door_edges,
|
||||||
|
wall_w,
|
||||||
|
WHITE,
|
||||||
|
);
|
||||||
|
draw_cell_walls(
|
||||||
|
&mut walls,
|
||||||
|
&g,
|
||||||
|
&scene.room_cells,
|
||||||
|
None,
|
||||||
|
&scene.door_edges,
|
||||||
|
wall_w,
|
||||||
|
WHITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut doors = Pixmap::new(g.width, g.height)
|
||||||
|
.ok_or_else(|| "Failed to allocate doors mask canvas".to_string())?;
|
||||||
|
fill_bg(&mut doors, BG);
|
||||||
|
for door in &layout.doors {
|
||||||
|
draw_door(&mut doors, &g, door, door_w, WHITE, DoorStyle::Solid);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut grid = Pixmap::new(g.width, g.height)
|
||||||
|
.ok_or_else(|| "Failed to allocate grid mask canvas".to_string())?;
|
||||||
|
fill_bg(&mut grid, BG);
|
||||||
|
draw_grid_color(&mut grid, &g, WHITE);
|
||||||
|
|
||||||
|
save_mask_image(folder, "floors_mask", &floors, settings)?;
|
||||||
|
save_mask_image(folder, "walls_mask", &walls, settings)?;
|
||||||
|
save_mask_image(folder, "doors_mask", &doors, settings)?;
|
||||||
|
save_mask_image(folder, "grid_mask", &grid, settings)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_mask_image(
|
||||||
|
folder: &std::path::Path,
|
||||||
|
stem: &str,
|
||||||
|
pixmap: &Pixmap,
|
||||||
|
settings: &UiSettings,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let ext = settings.mask_format.extension();
|
||||||
|
let path = folder.join(format!("{stem}.{ext}"));
|
||||||
|
let img = raster_image_from_pixmap(pixmap, settings)?;
|
||||||
|
img.save_with_format(&path, mask_image_format(settings.mask_format))
|
||||||
|
.map_err(|e| format!("Failed writing {}: {e}", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mask_image_format(format: MaskFormat) -> ImageFormat {
|
||||||
|
match format {
|
||||||
|
MaskFormat::Png => ImageFormat::Png,
|
||||||
|
MaskFormat::Jpeg => ImageFormat::Jpeg,
|
||||||
|
MaskFormat::Webp => ImageFormat::WebP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn raster_image_from_pixmap(
|
fn raster_image_from_pixmap(
|
||||||
pixmap: &Pixmap,
|
pixmap: &Pixmap,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
@@ -428,6 +535,10 @@ fn fill_bg(pixmap: &mut Pixmap, color: (u8, u8, u8, u8)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
||||||
|
draw_grid_color(pixmap, g, GRID);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_grid_color(pixmap: &mut Pixmap, g: &ExportGeometry, color: (u8, u8, u8, u8)) {
|
||||||
for c in 0..=g.cols {
|
for c in 0..=g.cols {
|
||||||
let x = g.left() + c as f32 * g.cell;
|
let x = g.left() + c as f32 * g.cell;
|
||||||
draw_line(
|
draw_line(
|
||||||
@@ -437,7 +548,7 @@ fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
|||||||
x,
|
x,
|
||||||
g.top() + g.rows as f32 * g.cell,
|
g.top() + g.rows as f32 * g.cell,
|
||||||
1.0,
|
1.0,
|
||||||
GRID,
|
color,
|
||||||
DoorStyle::Solid,
|
DoorStyle::Solid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -450,7 +561,7 @@ fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
|||||||
g.left() + g.cols as f32 * g.cell,
|
g.left() + g.cols as f32 * g.cell,
|
||||||
y,
|
y,
|
||||||
1.0,
|
1.0,
|
||||||
GRID,
|
color,
|
||||||
DoorStyle::Solid,
|
DoorStyle::Solid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -559,7 +670,7 @@ fn draw_cell_walls(
|
|||||||
|
|
||||||
let right_connected = connected_edges
|
let right_connected = connected_edges
|
||||||
.map(|set| set.contains(&norm_edge((col, row), right)))
|
.map(|set| set.contains(&norm_edge((col, row), right)))
|
||||||
.unwrap_or(false);
|
.unwrap_or(true);
|
||||||
let should_right = (!cells.contains(&right) || !right_connected)
|
let should_right = (!cells.contains(&right) || !right_connected)
|
||||||
&& !door_edges.contains(&norm_edge((col, row), right));
|
&& !door_edges.contains(&norm_edge((col, row), right));
|
||||||
|
|
||||||
@@ -569,12 +680,12 @@ fn draw_cell_walls(
|
|||||||
|
|
||||||
let bottom_connected = connected_edges
|
let bottom_connected = connected_edges
|
||||||
.map(|set| set.contains(&norm_edge((col, row), bottom)))
|
.map(|set| set.contains(&norm_edge((col, row), bottom)))
|
||||||
.unwrap_or(false);
|
.unwrap_or(true);
|
||||||
let should_bottom = (!cells.contains(&bottom) || !bottom_connected)
|
let should_bottom = (!cells.contains(&bottom) || !bottom_connected)
|
||||||
&& !door_edges.contains(&norm_edge((col, row), bottom));
|
&& !door_edges.contains(&norm_edge((col, row), bottom));
|
||||||
|
|
||||||
if should_left {
|
if should_left {
|
||||||
draw_line(
|
draw_wall_segment(
|
||||||
pixmap,
|
pixmap,
|
||||||
rect.left(),
|
rect.left(),
|
||||||
rect.top(),
|
rect.top(),
|
||||||
@@ -582,11 +693,10 @@ fn draw_cell_walls(
|
|||||||
rect.bottom(),
|
rect.bottom(),
|
||||||
width,
|
width,
|
||||||
color,
|
color,
|
||||||
DoorStyle::Solid,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if should_right {
|
if should_right {
|
||||||
draw_line(
|
draw_wall_segment(
|
||||||
pixmap,
|
pixmap,
|
||||||
rect.right(),
|
rect.right(),
|
||||||
rect.top(),
|
rect.top(),
|
||||||
@@ -594,11 +704,10 @@ fn draw_cell_walls(
|
|||||||
rect.bottom(),
|
rect.bottom(),
|
||||||
width,
|
width,
|
||||||
color,
|
color,
|
||||||
DoorStyle::Solid,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if should_top {
|
if should_top {
|
||||||
draw_line(
|
draw_wall_segment(
|
||||||
pixmap,
|
pixmap,
|
||||||
rect.left(),
|
rect.left(),
|
||||||
rect.top(),
|
rect.top(),
|
||||||
@@ -606,11 +715,10 @@ fn draw_cell_walls(
|
|||||||
rect.top(),
|
rect.top(),
|
||||||
width,
|
width,
|
||||||
color,
|
color,
|
||||||
DoorStyle::Solid,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if should_bottom {
|
if should_bottom {
|
||||||
draw_line(
|
draw_wall_segment(
|
||||||
pixmap,
|
pixmap,
|
||||||
rect.left(),
|
rect.left(),
|
||||||
rect.bottom(),
|
rect.bottom(),
|
||||||
@@ -618,13 +726,43 @@ fn draw_cell_walls(
|
|||||||
rect.bottom(),
|
rect.bottom(),
|
||||||
width,
|
width,
|
||||||
color,
|
color,
|
||||||
DoorStyle::Solid,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_wall_segment(
|
||||||
|
pixmap: &mut Pixmap,
|
||||||
|
x1: f32,
|
||||||
|
y1: f32,
|
||||||
|
x2: f32,
|
||||||
|
y2: f32,
|
||||||
|
width: f32,
|
||||||
|
color: (u8, u8, u8, u8),
|
||||||
|
) {
|
||||||
|
let half = width * 0.5;
|
||||||
|
let rect = if (x1 - x2).abs() <= f32::EPSILON {
|
||||||
|
let x = x1.min(x2);
|
||||||
|
let y0 = y1.min(y2);
|
||||||
|
let y1 = y1.max(y2);
|
||||||
|
Rect::from_xywh(x - half, y0 - half, width, (y1 - y0) + width)
|
||||||
|
} else {
|
||||||
|
let y = y1.min(y2);
|
||||||
|
let x0 = x1.min(x2);
|
||||||
|
let x1 = x1.max(x2);
|
||||||
|
Rect::from_xywh(x0 - half, y - half, (x1 - x0) + width, width)
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(rect) = rect else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
paint.set_color_rgba8(color.0, color.1, color.2, color.3);
|
||||||
|
pixmap.fill_rect(rect, &paint, Transform::identity(), None);
|
||||||
|
}
|
||||||
|
|
||||||
fn append_svg_walls(
|
fn append_svg_walls(
|
||||||
out: &mut String,
|
out: &mut String,
|
||||||
g: &ExportGeometry,
|
g: &ExportGeometry,
|
||||||
@@ -648,7 +786,7 @@ fn append_svg_walls(
|
|||||||
|
|
||||||
let right_connected = connected_edges
|
let right_connected = connected_edges
|
||||||
.map(|set| set.contains(&norm_edge((col, row), right_cell)))
|
.map(|set| set.contains(&norm_edge((col, row), right_cell)))
|
||||||
.unwrap_or(false);
|
.unwrap_or(true);
|
||||||
let right_edge = (!cells.contains(&right_cell) || !right_connected)
|
let right_edge = (!cells.contains(&right_cell) || !right_connected)
|
||||||
&& !door_edges.contains(&norm_edge((col, row), right_cell));
|
&& !door_edges.contains(&norm_edge((col, row), right_cell));
|
||||||
|
|
||||||
@@ -658,32 +796,44 @@ fn append_svg_walls(
|
|||||||
|
|
||||||
let bottom_connected = connected_edges
|
let bottom_connected = connected_edges
|
||||||
.map(|set| set.contains(&norm_edge((col, row), bottom_cell)))
|
.map(|set| set.contains(&norm_edge((col, row), bottom_cell)))
|
||||||
.unwrap_or(false);
|
.unwrap_or(true);
|
||||||
let bottom_edge = (!cells.contains(&bottom_cell) || !bottom_connected)
|
let bottom_edge = (!cells.contains(&bottom_cell) || !bottom_connected)
|
||||||
&& !door_edges.contains(&norm_edge((col, row), bottom_cell));
|
&& !door_edges.contains(&norm_edge((col, row), bottom_cell));
|
||||||
|
|
||||||
if left_edge {
|
if left_edge {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
"<line x1='{left}' y1='{top}' x2='{left}' y2='{bottom}' stroke='black' stroke-width='{width}'/>"
|
"<rect x='{}' y='{}' width='{width}' height='{}' fill='black'/>",
|
||||||
|
left - (width * 0.5),
|
||||||
|
top - (width * 0.5),
|
||||||
|
(bottom - top) + width
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if right_edge {
|
if right_edge {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
"<line x1='{right}' y1='{top}' x2='{right}' y2='{bottom}' stroke='black' stroke-width='{width}'/>"
|
"<rect x='{}' y='{}' width='{width}' height='{}' fill='black'/>",
|
||||||
|
right - (width * 0.5),
|
||||||
|
top - (width * 0.5),
|
||||||
|
(bottom - top) + width
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if top_edge {
|
if top_edge {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
"<line x1='{left}' y1='{top}' x2='{right}' y2='{top}' stroke='black' stroke-width='{width}'/>"
|
"<rect x='{}' y='{}' width='{}' height='{width}' fill='black'/>",
|
||||||
|
left - (width * 0.5),
|
||||||
|
top - (width * 0.5),
|
||||||
|
(right - left) + width
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if bottom_edge {
|
if bottom_edge {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
"<line x1='{left}' y1='{bottom}' x2='{right}' y2='{bottom}' stroke='black' stroke-width='{width}'/>"
|
"<rect x='{}' y='{}' width='{}' height='{width}' fill='black'/>",
|
||||||
|
left - (width * 0.5),
|
||||||
|
bottom - (width * 0.5),
|
||||||
|
(right - left) + width
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-33
@@ -393,7 +393,7 @@ fn draw_layout(
|
|||||||
|
|
||||||
let room_fill = Color32::from_rgb(70, 120, 160);
|
let room_fill = Color32::from_rgb(70, 120, 160);
|
||||||
let corridor_fill = Color32::from_rgb(210, 190, 120);
|
let corridor_fill = Color32::from_rgb(210, 190, 120);
|
||||||
let outline_stroke = Stroke::new(wall_width_px, Color32::BLACK);
|
let wall_color = Color32::BLACK;
|
||||||
let mut corridor_cells_set = HashSet::new();
|
let mut corridor_cells_set = HashSet::new();
|
||||||
let mut corridor_edges_set = HashSet::new();
|
let mut corridor_edges_set = HashSet::new();
|
||||||
let mut room_cells_set = HashSet::new();
|
let mut room_cells_set = HashSet::new();
|
||||||
@@ -436,24 +436,24 @@ fn draw_layout(
|
|||||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.top()),
|
egui::pos2(rect.left(), rect.top()),
|
||||||
egui::pos2(rect.left(), rect.bottom()),
|
egui::pos2(rect.left(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!corridor_cells_set.contains(&right)
|
if (!corridor_cells_set.contains(&right)
|
||||||
|| !corridor_edges_set.contains(&normalized_edge((col, row), right)))
|
|| !corridor_edges_set.contains(&normalized_edge((col, row), right)))
|
||||||
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.right(), rect.top()),
|
egui::pos2(rect.right(), rect.top()),
|
||||||
egui::pos2(rect.right(), rect.bottom()),
|
egui::pos2(rect.right(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if row == 0
|
if row == 0
|
||||||
@@ -462,24 +462,24 @@ fn draw_layout(
|
|||||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.top()),
|
egui::pos2(rect.left(), rect.top()),
|
||||||
egui::pos2(rect.right(), rect.top()),
|
egui::pos2(rect.right(), rect.top()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!corridor_cells_set.contains(&bottom)
|
if (!corridor_cells_set.contains(&bottom)
|
||||||
|| !corridor_edges_set.contains(&normalized_edge((col, row), bottom)))
|
|| !corridor_edges_set.contains(&normalized_edge((col, row), bottom)))
|
||||||
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.bottom()),
|
egui::pos2(rect.left(), rect.bottom()),
|
||||||
egui::pos2(rect.right(), rect.bottom()),
|
egui::pos2(rect.right(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,23 +516,23 @@ fn draw_layout(
|
|||||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.top()),
|
egui::pos2(rect.left(), rect.top()),
|
||||||
egui::pos2(rect.left(), rect.bottom()),
|
egui::pos2(rect.left(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if !room_cells_set.contains(&right)
|
if !room_cells_set.contains(&right)
|
||||||
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.right(), rect.top()),
|
egui::pos2(rect.right(), rect.top()),
|
||||||
egui::pos2(rect.right(), rect.bottom()),
|
egui::pos2(rect.right(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if row == 0
|
if row == 0
|
||||||
@@ -541,23 +541,23 @@ fn draw_layout(
|
|||||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.top()),
|
egui::pos2(rect.left(), rect.top()),
|
||||||
egui::pos2(rect.right(), rect.top()),
|
egui::pos2(rect.right(), rect.top()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if !room_cells_set.contains(&bottom)
|
if !room_cells_set.contains(&bottom)
|
||||||
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
||||||
{
|
{
|
||||||
painter.line_segment(
|
draw_wall_segment(
|
||||||
[
|
painter,
|
||||||
egui::pos2(rect.left(), rect.bottom()),
|
egui::pos2(rect.left(), rect.bottom()),
|
||||||
egui::pos2(rect.right(), rect.bottom()),
|
egui::pos2(rect.right(), rect.bottom()),
|
||||||
],
|
wall_width_px,
|
||||||
outline_stroke,
|
wall_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,6 +603,35 @@ fn cell_rect(geometry: &GridGeometry, col: usize, row: usize) -> egui::Rect {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_wall_segment(
|
||||||
|
painter: &egui::Painter,
|
||||||
|
from: egui::Pos2,
|
||||||
|
to: egui::Pos2,
|
||||||
|
width: f32,
|
||||||
|
color: Color32,
|
||||||
|
) {
|
||||||
|
let half = width * 0.5;
|
||||||
|
if (from.x - to.x).abs() <= f32::EPSILON {
|
||||||
|
let x = from.x.min(to.x);
|
||||||
|
let y0 = from.y.min(to.y);
|
||||||
|
let y1 = from.y.max(to.y);
|
||||||
|
let rect = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(x - half, y0 - half),
|
||||||
|
egui::pos2(x + half, y1 + half),
|
||||||
|
);
|
||||||
|
painter.rect_filled(rect, 0.0, color);
|
||||||
|
} else {
|
||||||
|
let y = from.y.min(to.y);
|
||||||
|
let x0 = from.x.min(to.x);
|
||||||
|
let x1 = from.x.max(to.x);
|
||||||
|
let rect = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(x0 - half, y - half),
|
||||||
|
egui::pos2(x1 + half, y + half),
|
||||||
|
);
|
||||||
|
painter.rect_filled(rect, 0.0, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn normalized_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) {
|
fn normalized_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) {
|
||||||
if a <= b { (a, b) } else { (b, a) }
|
if a <= b { (a, b) } else { (b, a) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub enum ExportFormat {
|
|||||||
Jpeg,
|
Jpeg,
|
||||||
Webp,
|
Webp,
|
||||||
Svg,
|
Svg,
|
||||||
|
Folder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExportFormat {
|
impl Default for ExportFormat {
|
||||||
@@ -36,6 +37,7 @@ impl ExportFormat {
|
|||||||
ExportFormat::Jpeg => "jpeg",
|
ExportFormat::Jpeg => "jpeg",
|
||||||
ExportFormat::Webp => "webp",
|
ExportFormat::Webp => "webp",
|
||||||
ExportFormat::Svg => "svg",
|
ExportFormat::Svg => "svg",
|
||||||
|
ExportFormat::Folder => "folder",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +47,46 @@ impl ExportFormat {
|
|||||||
ExportFormat::Jpeg => ".jpeg",
|
ExportFormat::Jpeg => ".jpeg",
|
||||||
ExportFormat::Webp => ".webp",
|
ExportFormat::Webp => ".webp",
|
||||||
ExportFormat::Svg => ".svg",
|
ExportFormat::Svg => ".svg",
|
||||||
|
ExportFormat::Folder => "Folder (Composite)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_raster(self) -> bool {
|
pub fn supports_resolution(self) -> bool {
|
||||||
!matches!(self, ExportFormat::Svg)
|
!matches!(self, ExportFormat::Svg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum MaskFormat {
|
||||||
|
Png,
|
||||||
|
Jpeg,
|
||||||
|
Webp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MaskFormat {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Png
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaskFormat {
|
||||||
|
pub fn extension(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
MaskFormat::Png => "png",
|
||||||
|
MaskFormat::Jpeg => "jpeg",
|
||||||
|
MaskFormat::Webp => "webp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
MaskFormat::Png => ".png",
|
||||||
|
MaskFormat::Jpeg => ".jpeg",
|
||||||
|
MaskFormat::Webp => ".webp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct UiSettings {
|
pub struct UiSettings {
|
||||||
@@ -71,6 +105,7 @@ pub struct UiSettings {
|
|||||||
pub allow_middle_corridor_doors: bool,
|
pub allow_middle_corridor_doors: bool,
|
||||||
pub colorblind_mode: bool,
|
pub colorblind_mode: bool,
|
||||||
pub export_format: ExportFormat,
|
pub export_format: ExportFormat,
|
||||||
|
pub mask_format: MaskFormat,
|
||||||
pub export_width: u32,
|
pub export_width: u32,
|
||||||
pub export_height: u32,
|
pub export_height: u32,
|
||||||
pub allow_export_aspect_change: bool,
|
pub allow_export_aspect_change: bool,
|
||||||
@@ -95,6 +130,7 @@ impl Default for UiSettings {
|
|||||||
allow_middle_corridor_doors: false,
|
allow_middle_corridor_doors: false,
|
||||||
colorblind_mode: false,
|
colorblind_mode: false,
|
||||||
export_format: ExportFormat::Png,
|
export_format: ExportFormat::Png,
|
||||||
|
mask_format: MaskFormat::Png,
|
||||||
export_width: 1920,
|
export_width: 1920,
|
||||||
export_height: 1080,
|
export_height: 1080,
|
||||||
allow_export_aspect_change: false,
|
allow_export_aspect_change: false,
|
||||||
@@ -211,10 +247,29 @@ fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut
|
|||||||
ui.selectable_value(&mut settings.export_format, ExportFormat::Jpeg, ".jpeg");
|
ui.selectable_value(&mut settings.export_format, ExportFormat::Jpeg, ".jpeg");
|
||||||
ui.selectable_value(&mut settings.export_format, ExportFormat::Webp, ".webp");
|
ui.selectable_value(&mut settings.export_format, ExportFormat::Webp, ".webp");
|
||||||
ui.selectable_value(&mut settings.export_format, ExportFormat::Svg, ".svg");
|
ui.selectable_value(&mut settings.export_format, ExportFormat::Svg, ".svg");
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut settings.export_format,
|
||||||
|
ExportFormat::Folder,
|
||||||
|
"Folder (Composite)",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if settings.export_format.is_raster() {
|
if settings.export_format == ExportFormat::Folder {
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Mask Format");
|
||||||
|
egui::ComboBox::from_id_salt("mask_format")
|
||||||
|
.selected_text(settings.mask_format.label())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(&mut settings.mask_format, MaskFormat::Png, ".png");
|
||||||
|
ui.selectable_value(&mut settings.mask_format, MaskFormat::Jpeg, ".jpeg");
|
||||||
|
ui.selectable_value(&mut settings.mask_format, MaskFormat::Webp, ".webp");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.export_format.supports_resolution() {
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.checkbox(
|
ui.checkbox(
|
||||||
&mut settings.allow_export_aspect_change,
|
&mut settings.allow_export_aspect_change,
|
||||||
@@ -243,9 +298,7 @@ fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut
|
|||||||
ui.label("Resolution Height");
|
ui.label("Resolution Height");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
height_changed |= ui
|
height_changed |= ui
|
||||||
.add(
|
.add(egui::Slider::new(&mut settings.export_height, 1..=10_000).show_value(false))
|
||||||
egui::Slider::new(&mut settings.export_height, 1..=10_000).show_value(false),
|
|
||||||
)
|
|
||||||
.changed();
|
.changed();
|
||||||
height_changed |= ui
|
height_changed |= ui
|
||||||
.add(
|
.add(
|
||||||
|
|||||||
Reference in New Issue
Block a user