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.
|
||||
#.idea/
|
||||
|
||||
#TODO file for my own personal use
|
||||
TODO
|
||||
|
||||
# Added by cargo
|
||||
|
||||
|
||||
+202
-52
@@ -7,7 +7,7 @@ use rfd::FileDialog;
|
||||
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
||||
|
||||
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 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 LOCKED_DOOR: (u8, u8, u8, u8) = (80, 200, 120, 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)]
|
||||
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(
|
||||
layout: &DungeonLayout,
|
||||
settings: &UiSettings,
|
||||
) -> 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 default_name = format!("dungeon_export.{ext}");
|
||||
|
||||
@@ -110,11 +155,12 @@ pub fn export_with_dialog(
|
||||
ExportFormat::Png => ImageFormat::Png,
|
||||
ExportFormat::Jpeg => ImageFormat::Jpeg,
|
||||
ExportFormat::Webp => ImageFormat::WebP,
|
||||
ExportFormat::Svg => unreachable!(),
|
||||
ExportFormat::Svg | ExportFormat::Folder => unreachable!(),
|
||||
};
|
||||
img.save_with_format(&path, format)
|
||||
.map_err(|e| format!("Failed writing image: {e}"))?;
|
||||
}
|
||||
ExportFormat::Folder => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
@@ -122,6 +168,7 @@ pub fn export_with_dialog(
|
||||
|
||||
fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap, String> {
|
||||
let g = ExportGeometry::new(settings.cols, settings.rows);
|
||||
let scene = collect_scene_data(layout);
|
||||
let mut pixmap =
|
||||
Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?;
|
||||
fill_bg(&mut pixmap, BG);
|
||||
@@ -131,33 +178,7 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
||||
|
||||
draw_grid(&mut pixmap, &g);
|
||||
|
||||
let mut corridor_cells = HashSet::new();
|
||||
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 {
|
||||
for &(x, y) in &scene.corridor_cells {
|
||||
fill_rect_cell(&mut pixmap, &g, x, y, CORRIDOR);
|
||||
if settings.colorblind_mode {
|
||||
let (cx, cy) = g.cell_center(x, y);
|
||||
@@ -175,18 +196,18 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
||||
draw_cell_walls(
|
||||
&mut pixmap,
|
||||
&g,
|
||||
&corridor_cells,
|
||||
Some(&corridor_edges),
|
||||
&door_edges,
|
||||
&scene.corridor_cells,
|
||||
Some(&scene.corridor_edges),
|
||||
&scene.door_edges,
|
||||
wall_w,
|
||||
BLACK,
|
||||
);
|
||||
draw_cell_walls(
|
||||
&mut pixmap,
|
||||
&g,
|
||||
&room_cells,
|
||||
&scene.room_cells,
|
||||
None,
|
||||
&door_edges,
|
||||
&scene.door_edges,
|
||||
wall_w,
|
||||
BLACK,
|
||||
);
|
||||
@@ -214,6 +235,92 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<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(
|
||||
pixmap: &Pixmap,
|
||||
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) {
|
||||
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 {
|
||||
let x = g.left() + c as f32 * g.cell;
|
||||
draw_line(
|
||||
@@ -437,7 +548,7 @@ fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
||||
x,
|
||||
g.top() + g.rows as f32 * g.cell,
|
||||
1.0,
|
||||
GRID,
|
||||
color,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
@@ -450,7 +561,7 @@ fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
|
||||
g.left() + g.cols as f32 * g.cell,
|
||||
y,
|
||||
1.0,
|
||||
GRID,
|
||||
color,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
@@ -559,7 +670,7 @@ fn draw_cell_walls(
|
||||
|
||||
let right_connected = connected_edges
|
||||
.map(|set| set.contains(&norm_edge((col, row), right)))
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(true);
|
||||
let should_right = (!cells.contains(&right) || !right_connected)
|
||||
&& !door_edges.contains(&norm_edge((col, row), right));
|
||||
|
||||
@@ -569,12 +680,12 @@ fn draw_cell_walls(
|
||||
|
||||
let bottom_connected = connected_edges
|
||||
.map(|set| set.contains(&norm_edge((col, row), bottom)))
|
||||
.unwrap_or(false);
|
||||
.unwrap_or(true);
|
||||
let should_bottom = (!cells.contains(&bottom) || !bottom_connected)
|
||||
&& !door_edges.contains(&norm_edge((col, row), bottom));
|
||||
|
||||
if should_left {
|
||||
draw_line(
|
||||
draw_wall_segment(
|
||||
pixmap,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
@@ -582,11 +693,10 @@ fn draw_cell_walls(
|
||||
rect.bottom(),
|
||||
width,
|
||||
color,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
if should_right {
|
||||
draw_line(
|
||||
draw_wall_segment(
|
||||
pixmap,
|
||||
rect.right(),
|
||||
rect.top(),
|
||||
@@ -594,11 +704,10 @@ fn draw_cell_walls(
|
||||
rect.bottom(),
|
||||
width,
|
||||
color,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
if should_top {
|
||||
draw_line(
|
||||
draw_wall_segment(
|
||||
pixmap,
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
@@ -606,11 +715,10 @@ fn draw_cell_walls(
|
||||
rect.top(),
|
||||
width,
|
||||
color,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
if should_bottom {
|
||||
draw_line(
|
||||
draw_wall_segment(
|
||||
pixmap,
|
||||
rect.left(),
|
||||
rect.bottom(),
|
||||
@@ -618,13 +726,43 @@ fn draw_cell_walls(
|
||||
rect.bottom(),
|
||||
width,
|
||||
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(
|
||||
out: &mut String,
|
||||
g: &ExportGeometry,
|
||||
@@ -648,7 +786,7 @@ fn append_svg_walls(
|
||||
|
||||
let right_connected = connected_edges
|
||||
.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)
|
||||
&& !door_edges.contains(&norm_edge((col, row), right_cell));
|
||||
|
||||
@@ -658,32 +796,44 @@ fn append_svg_walls(
|
||||
|
||||
let bottom_connected = connected_edges
|
||||
.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)
|
||||
&& !door_edges.contains(&norm_edge((col, row), bottom_cell));
|
||||
|
||||
if left_edge {
|
||||
let _ = writeln!(
|
||||
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 {
|
||||
let _ = writeln!(
|
||||
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 {
|
||||
let _ = writeln!(
|
||||
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 {
|
||||
let _ = writeln!(
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+78
-49
@@ -393,7 +393,7 @@ fn draw_layout(
|
||||
|
||||
let room_fill = Color32::from_rgb(70, 120, 160);
|
||||
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_edges_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)))
|
||||
.unwrap_or(true))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if (!corridor_cells_set.contains(&right)
|
||||
|| !corridor_edges_set.contains(&normalized_edge((col, row), right)))
|
||||
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if row == 0
|
||||
@@ -462,24 +462,24 @@ fn draw_layout(
|
||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||
.unwrap_or(true))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if (!corridor_cells_set.contains(&bottom)
|
||||
|| !corridor_edges_set.contains(&normalized_edge((col, row), bottom)))
|
||||
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -516,23 +516,23 @@ fn draw_layout(
|
||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||
.unwrap_or(true))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if !room_cells_set.contains(&right)
|
||||
&& !door_edges_set.contains(&normalized_edge((col, row), right))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if row == 0
|
||||
@@ -541,23 +541,23 @@ fn draw_layout(
|
||||
.map(|n| !door_edges_set.contains(&normalized_edge((col, row), n)))
|
||||
.unwrap_or(true))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.top()),
|
||||
egui::pos2(rect.right(), rect.top()),
|
||||
wall_width_px,
|
||||
wall_color,
|
||||
);
|
||||
}
|
||||
if !room_cells_set.contains(&bottom)
|
||||
&& !door_edges_set.contains(&normalized_edge((col, row), bottom))
|
||||
{
|
||||
painter.line_segment(
|
||||
[
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
],
|
||||
outline_stroke,
|
||||
draw_wall_segment(
|
||||
painter,
|
||||
egui::pos2(rect.left(), rect.bottom()),
|
||||
egui::pos2(rect.right(), rect.bottom()),
|
||||
wall_width_px,
|
||||
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)) {
|
||||
if a <= b { (a, b) } else { (b, a) }
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub enum ExportFormat {
|
||||
Jpeg,
|
||||
Webp,
|
||||
Svg,
|
||||
Folder,
|
||||
}
|
||||
|
||||
impl Default for ExportFormat {
|
||||
@@ -36,6 +37,7 @@ impl ExportFormat {
|
||||
ExportFormat::Jpeg => "jpeg",
|
||||
ExportFormat::Webp => "webp",
|
||||
ExportFormat::Svg => "svg",
|
||||
ExportFormat::Folder => "folder",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,14 +47,46 @@ impl ExportFormat {
|
||||
ExportFormat::Jpeg => ".jpeg",
|
||||
ExportFormat::Webp => ".webp",
|
||||
ExportFormat::Svg => ".svg",
|
||||
ExportFormat::Folder => "Folder (Composite)",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_raster(self) -> bool {
|
||||
pub fn supports_resolution(self) -> bool {
|
||||
!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)]
|
||||
#[serde(default)]
|
||||
pub struct UiSettings {
|
||||
@@ -71,6 +105,7 @@ pub struct UiSettings {
|
||||
pub allow_middle_corridor_doors: bool,
|
||||
pub colorblind_mode: bool,
|
||||
pub export_format: ExportFormat,
|
||||
pub mask_format: MaskFormat,
|
||||
pub export_width: u32,
|
||||
pub export_height: u32,
|
||||
pub allow_export_aspect_change: bool,
|
||||
@@ -95,6 +130,7 @@ impl Default for UiSettings {
|
||||
allow_middle_corridor_doors: false,
|
||||
colorblind_mode: false,
|
||||
export_format: ExportFormat::Png,
|
||||
mask_format: MaskFormat::Png,
|
||||
export_width: 1920,
|
||||
export_height: 1080,
|
||||
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::Webp, ".webp");
|
||||
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.checkbox(
|
||||
&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.horizontal(|ui| {
|
||||
height_changed |= ui
|
||||
.add(
|
||||
egui::Slider::new(&mut settings.export_height, 1..=10_000).show_value(false),
|
||||
)
|
||||
.add(egui::Slider::new(&mut settings.export_height, 1..=10_000).show_value(false))
|
||||
.changed();
|
||||
height_changed |= ui
|
||||
.add(
|
||||
|
||||
Reference in New Issue
Block a user