cleaned up the code and added comments and changed readme
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "desktop_dungeon_generator"
|
name = "desktop_dungeon_generator"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -1,73 +1,128 @@
|
|||||||
# Desktop Dungeon Generator
|
# Desktop Dungeon Generator
|
||||||
|
|
||||||
**Pre-release Notice:** This program is incomplete and functionality is subject to change and it might break, you've been warned. *though releases should at least be stable enough to run*
|
Desktop Dungeon Generator is a Rust desktop app for building and exporting simple tabletop RPG dungeon layouts.
|
||||||
|
|
||||||
A Rust desktop app for generating simple tabletop dungeon layouts.
|
It combines deterministic generation with manual editing, so you can generate a baseline layout from a seed and then adjust rooms, corridors, doors, text labels, and start/end markers directly on the canvas.
|
||||||
|
|
||||||
## Current Features
|
## Current Features
|
||||||
|
|
||||||
- Resizable split layout:
|
- Deterministic dungeon generation from a master seed
|
||||||
- Left panel: generation settings
|
- Room generation with configurable:
|
||||||
- Right panel: vector-rendered grid + dungeon preview
|
- room count
|
||||||
- Grid controls:
|
- minimum and maximum room size
|
||||||
- Columns
|
- square-only rooms
|
||||||
- Rows
|
- packed-room mode without corridors
|
||||||
- Room controls:
|
- Corridor generation with configurable:
|
||||||
- Room count
|
- minimum and maximum corridor width
|
||||||
- Minimum room size (in grid cells)
|
- corridor randomness
|
||||||
- Maximum room size (in grid cells)
|
- dead-end room percentage
|
||||||
- `Square Rooms Only` toggle
|
- Automatic door generation with configurable:
|
||||||
- Corridor controls:
|
- overall frequency
|
||||||
- `Corridor Randomness (%)` from `0` to `100`
|
- room/hallway chance
|
||||||
- `Dead-End Rooms (%)` from `0` to `50`
|
- locked door chance
|
||||||
- Door controls:
|
- secret door chance
|
||||||
- `Door Frequency (%)`
|
- middle-corridor doors toggle
|
||||||
- `Room/Hallway Door Chance (%)`
|
- Automatic window generation with configurable:
|
||||||
- `Locked Door Chance (%)`
|
- enable/disable
|
||||||
- `Allow Middle Corridor Doors` toggle
|
- minimum and maximum width
|
||||||
- Accessibility:
|
- overall frequency
|
||||||
- `Colorblind Mode` toggle (right panel)
|
- room/hallway chance
|
||||||
- Pattern/style encoding:
|
- internal windows toggle
|
||||||
- Rooms: crosshatch
|
- Generated start/end markers with configurable:
|
||||||
- Corridors: dotted fill
|
- minimum and maximum size
|
||||||
- Walls: solid lines
|
- minimum and maximum count
|
||||||
- Doors: long dashes
|
- placement inside rooms
|
||||||
- Locked doors: short dashes
|
- farthest-pair matching between start and end markers
|
||||||
- Archways: dotted lines
|
- Manual editing tools for:
|
||||||
- Generate control:
|
- rooms
|
||||||
- Master seed input (`u64`)
|
- corridors
|
||||||
- `Random` seed button
|
- archways
|
||||||
- `Reset` button
|
- doors
|
||||||
- `Export Image` button
|
- locked doors
|
||||||
- Export format selector: `.png`, `.jpeg`, `.webp`, `.svg`
|
- secret doors
|
||||||
- Export uses an OS folder picker to choose save location
|
- text labels
|
||||||
|
- start markers
|
||||||
|
- end markers
|
||||||
|
- Export support for:
|
||||||
|
- `.png`
|
||||||
|
- `.jpeg`
|
||||||
|
- `.webp`
|
||||||
|
- `.svg`
|
||||||
|
- composite folder export with masks
|
||||||
|
- Accessibility mode with alternate visual encodings for rooms, corridors, walls, doors, archways, and windows
|
||||||
|
|
||||||
## Generation Behavior
|
## Tabs
|
||||||
|
|
||||||
- Rooms are stored as rectangles (`x`, `y`, `width`, `height`) and placed without overlap.
|
### Generate
|
||||||
- Corridors are stored separately as vector line segments between grid cells.
|
|
||||||
- Every generated room is connected into one navigable network when possible.
|
|
||||||
- A master seed drives deterministic generation, and internal subsystems derive their own sub-seeds from it.
|
|
||||||
- Doors are generated from corridor adjacency:
|
|
||||||
- Open doors are rendered in red
|
|
||||||
- Locked doors are rendered in green
|
|
||||||
- Doors are rendered as line segments on grid boundaries
|
|
||||||
|
|
||||||
### Corridor Randomness
|
Controls global generation and export:
|
||||||
|
|
||||||
- `0%` randomness:
|
- grid columns and rows
|
||||||
- Uses shortest-path routing for room-to-room corridor paths.
|
- master seed and random-seed button
|
||||||
- Produces cleaner, direct corridor routes.
|
- reset and clear actions
|
||||||
- `100%` randomness:
|
- export format, dimensions, and grid visibility
|
||||||
- Uses noisy/randomized routing while still ensuring room connectivity.
|
|
||||||
- Produces more wandering, less direct routes.
|
|
||||||
- Intermediate values blend between the two behaviors.
|
|
||||||
|
|
||||||
### Dead-End Rooms
|
### Layout
|
||||||
|
|
||||||
- Controls how many rooms should end up as dead ends (rooms with only one room-to-room connection).
|
Controls room, corridor, door, and window generation behavior.
|
||||||
- `0%` targets no dead-end rooms (except tiny edge cases where topology makes this impossible, such as only 2 rooms).
|
|
||||||
- `50%` is the upper cap to avoid over-constraining connectivity.
|
Changing values in this tab regenerates the dungeon immediately.
|
||||||
|
|
||||||
|
### Start & End
|
||||||
|
|
||||||
|
Controls generated start/end marker behavior:
|
||||||
|
|
||||||
|
- minimum and maximum start marker size
|
||||||
|
- minimum and maximum end marker size
|
||||||
|
- minimum and maximum start marker count
|
||||||
|
- minimum and maximum end marker count
|
||||||
|
|
||||||
|
Changing values in this tab regenerates the dungeon immediately.
|
||||||
|
|
||||||
|
Generated markers are placed inside rooms. Matching start/end indices are paired to rooms that are as far apart as possible.
|
||||||
|
|
||||||
|
### Add
|
||||||
|
|
||||||
|
Contains manual placement tools for editing the current layout without regenerating it.
|
||||||
|
|
||||||
|
## Canvas Interaction
|
||||||
|
|
||||||
|
- Left click and drag a room to move it
|
||||||
|
- Left click and drag a start/end marker to move it
|
||||||
|
- Right click and drag a room to resize it
|
||||||
|
- Right click without dragging to cancel an active add tool
|
||||||
|
- `Delete` or `Backspace` removes the currently hovered room, corridor, door, text label, or marker
|
||||||
|
- `Ctrl+Z` undoes
|
||||||
|
- `Ctrl+Y` redoes
|
||||||
|
|
||||||
|
## Start / End Markers
|
||||||
|
|
||||||
|
The app currently supports two marker workflows:
|
||||||
|
|
||||||
|
- Generated markers:
|
||||||
|
- created automatically during dungeon generation
|
||||||
|
- always placed inside rooms
|
||||||
|
- sized from the configured min/max ranges
|
||||||
|
- paired by distance for each matching start/end index
|
||||||
|
- Manual markers:
|
||||||
|
- added from the `Add` tab
|
||||||
|
- use the configured minimum size for their marker type
|
||||||
|
- can be repositioned by dragging on the canvas
|
||||||
|
|
||||||
|
## Folder Export Masks
|
||||||
|
|
||||||
|
Folder export writes a composite image plus separate masks for:
|
||||||
|
|
||||||
|
- floors
|
||||||
|
- walls
|
||||||
|
- doors
|
||||||
|
- archways
|
||||||
|
- locked doors
|
||||||
|
- secret doors
|
||||||
|
- windows
|
||||||
|
- start markers
|
||||||
|
- end markers
|
||||||
|
- grid, when enabled
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
@@ -77,8 +132,5 @@ cargo run
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Rendering is vector-based in-app (`egui` painter), so zooming/scaling the preview remains crisp.
|
- Settings are persisted between runs using the local OS application data directory.
|
||||||
- Room and corridor vectors are stored separately to support future export workflows (for example, SVG export).
|
- The app is still pre-release and the file layout and feature set may continue to change.
|
||||||
- Settings are persisted on exit and restored on startup from:
|
|
||||||
- Windows: `%LOCALAPPDATA%/desktop_dungeon_generator/settings.json`
|
|
||||||
- Linux: `~/.local/share/desktop_dungeon_generator/settings.json` (or distro-equivalent local data dir)
|
|
||||||
|
|||||||
+14
-21
@@ -331,27 +331,13 @@ fn export_composite_masks(
|
|||||||
WHITE,
|
WHITE,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut doors = Pixmap::new(g.width, g.height)
|
let mut doors = blank_mask(&g, "doors")?;
|
||||||
.ok_or_else(|| "Failed to allocate doors mask canvas".to_string())?;
|
let mut archways = blank_mask(&g, "archways")?;
|
||||||
let mut archways = Pixmap::new(g.width, g.height)
|
let mut locked_doors = blank_mask(&g, "locked doors")?;
|
||||||
.ok_or_else(|| "Failed to allocate archways mask canvas".to_string())?;
|
let mut secret_doors = blank_mask(&g, "secret doors")?;
|
||||||
let mut locked_doors = Pixmap::new(g.width, g.height)
|
let mut windows = blank_mask(&g, "windows")?;
|
||||||
.ok_or_else(|| "Failed to allocate locked doors mask canvas".to_string())?;
|
let mut start_markers = blank_mask(&g, "start markers")?;
|
||||||
let mut secret_doors = Pixmap::new(g.width, g.height)
|
let mut end_markers = blank_mask(&g, "end markers")?;
|
||||||
.ok_or_else(|| "Failed to allocate secret doors mask canvas".to_string())?;
|
|
||||||
let mut windows = Pixmap::new(g.width, g.height)
|
|
||||||
.ok_or_else(|| "Failed to allocate windows mask canvas".to_string())?;
|
|
||||||
let mut start_markers = Pixmap::new(g.width, g.height)
|
|
||||||
.ok_or_else(|| "Failed to allocate start markers mask canvas".to_string())?;
|
|
||||||
let mut end_markers = Pixmap::new(g.width, g.height)
|
|
||||||
.ok_or_else(|| "Failed to allocate end markers mask canvas".to_string())?;
|
|
||||||
fill_bg(&mut doors, BLACK);
|
|
||||||
fill_bg(&mut archways, BLACK);
|
|
||||||
fill_bg(&mut locked_doors, BLACK);
|
|
||||||
fill_bg(&mut secret_doors, BLACK);
|
|
||||||
fill_bg(&mut windows, BLACK);
|
|
||||||
fill_bg(&mut start_markers, BLACK);
|
|
||||||
fill_bg(&mut end_markers, BLACK);
|
|
||||||
for door in &layout.doors {
|
for door in &layout.doors {
|
||||||
if door.secret {
|
if door.secret {
|
||||||
draw_door(&mut secret_doors, &g, door, door_w, WHITE, DoorStyle::Solid);
|
draw_door(&mut secret_doors, &g, door, door_w, WHITE, DoorStyle::Solid);
|
||||||
@@ -401,6 +387,13 @@ fn export_composite_masks(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn blank_mask(g: &ExportGeometry, name: &str) -> Result<Pixmap, String> {
|
||||||
|
let mut pixmap = Pixmap::new(g.width, g.height)
|
||||||
|
.ok_or_else(|| format!("Failed to allocate {name} mask canvas"))?;
|
||||||
|
fill_bg(&mut pixmap, BLACK);
|
||||||
|
Ok(pixmap)
|
||||||
|
}
|
||||||
|
|
||||||
// Write a single mask image to disk.
|
// Write a single mask image to disk.
|
||||||
fn save_mask_image(
|
fn save_mask_image(
|
||||||
folder: &std::path::Path,
|
folder: &std::path::Path,
|
||||||
|
|||||||
+28
-24
@@ -122,30 +122,7 @@ impl eframe::App for DungeonApp {
|
|||||||
|| (i.modifiers.command && i.modifiers.shift && i.key_pressed(egui::Key::Z))
|
|| (i.modifiers.command && i.modifiers.shift && i.key_pressed(egui::Key::Z))
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.settings.min_room_size > self.settings.max_room_size {
|
clamp_dependent_settings(&mut self.settings);
|
||||||
self.settings.max_room_size = self.settings.min_room_size;
|
|
||||||
}
|
|
||||||
if self.settings.min_corridor_width == 0 {
|
|
||||||
self.settings.min_corridor_width = 1;
|
|
||||||
}
|
|
||||||
if self.settings.min_corridor_width > self.settings.max_corridor_width {
|
|
||||||
self.settings.max_corridor_width = self.settings.min_corridor_width;
|
|
||||||
}
|
|
||||||
if self.settings.min_window_width > self.settings.max_window_width {
|
|
||||||
self.settings.max_window_width = self.settings.min_window_width;
|
|
||||||
}
|
|
||||||
if self.settings.min_start_marker_size > self.settings.max_start_marker_size {
|
|
||||||
self.settings.max_start_marker_size = self.settings.min_start_marker_size;
|
|
||||||
}
|
|
||||||
if self.settings.min_end_marker_size > self.settings.max_end_marker_size {
|
|
||||||
self.settings.max_end_marker_size = self.settings.min_end_marker_size;
|
|
||||||
}
|
|
||||||
if self.settings.min_start_marker_count > self.settings.max_start_marker_count {
|
|
||||||
self.settings.max_start_marker_count = self.settings.min_start_marker_count;
|
|
||||||
}
|
|
||||||
if self.settings.min_end_marker_count > self.settings.max_end_marker_count {
|
|
||||||
self.settings.max_end_marker_count = self.settings.min_end_marker_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if undo_requested {
|
if undo_requested {
|
||||||
self.undo();
|
self.undo();
|
||||||
@@ -280,6 +257,33 @@ impl eframe::App for DungeonApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clamp_dependent_settings(settings: &mut UiSettings) {
|
||||||
|
if settings.min_room_size > settings.max_room_size {
|
||||||
|
settings.max_room_size = settings.min_room_size;
|
||||||
|
}
|
||||||
|
if settings.min_corridor_width == 0 {
|
||||||
|
settings.min_corridor_width = 1;
|
||||||
|
}
|
||||||
|
if settings.min_corridor_width > settings.max_corridor_width {
|
||||||
|
settings.max_corridor_width = settings.min_corridor_width;
|
||||||
|
}
|
||||||
|
if settings.min_window_width > settings.max_window_width {
|
||||||
|
settings.max_window_width = settings.min_window_width;
|
||||||
|
}
|
||||||
|
if settings.min_start_marker_size > settings.max_start_marker_size {
|
||||||
|
settings.max_start_marker_size = settings.min_start_marker_size;
|
||||||
|
}
|
||||||
|
if settings.min_end_marker_size > settings.max_end_marker_size {
|
||||||
|
settings.max_end_marker_size = settings.min_end_marker_size;
|
||||||
|
}
|
||||||
|
if settings.min_start_marker_count > settings.max_start_marker_count {
|
||||||
|
settings.max_start_marker_count = settings.min_start_marker_count;
|
||||||
|
}
|
||||||
|
if settings.min_end_marker_count > settings.max_end_marker_count {
|
||||||
|
settings.max_end_marker_count = settings.min_end_marker_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DungeonApp {
|
impl DungeonApp {
|
||||||
fn snapshot(&self) -> AppSnapshot {
|
fn snapshot(&self) -> AppSnapshot {
|
||||||
AppSnapshot {
|
AppSnapshot {
|
||||||
|
|||||||
+24
-8
@@ -2,6 +2,16 @@ use crate::layout::{self, DungeonLayout};
|
|||||||
use crate::seed;
|
use crate::seed;
|
||||||
use crate::ui::UiSettings;
|
use crate::ui::UiSettings;
|
||||||
|
|
||||||
|
const START_COUNT_STREAM: u64 = 10_001;
|
||||||
|
const END_COUNT_STREAM: u64 = 10_002;
|
||||||
|
const START_ROOM_STREAM_BASE: u64 = 11_000;
|
||||||
|
const END_ROOM_STREAM_BASE: u64 = 12_000;
|
||||||
|
const EXTRA_START_ROOM_STREAM_BASE: u64 = 13_000;
|
||||||
|
const EXTRA_START_MARKER_STREAM_BASE: u64 = 14_000;
|
||||||
|
const EXTRA_END_ROOM_STREAM_BASE: u64 = 15_000;
|
||||||
|
const EXTRA_END_MARKER_STREAM_BASE: u64 = 16_000;
|
||||||
|
|
||||||
|
// Populate generated start/end markers after the core dungeon layout exists.
|
||||||
pub fn populate_random_markers(
|
pub fn populate_random_markers(
|
||||||
mut layout: DungeonLayout,
|
mut layout: DungeonLayout,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
@@ -16,12 +26,12 @@ pub fn populate_random_markers(
|
|||||||
let start_count = random_range_inclusive(
|
let start_count = random_range_inclusive(
|
||||||
settings.min_start_marker_count,
|
settings.min_start_marker_count,
|
||||||
settings.max_start_marker_count,
|
settings.max_start_marker_count,
|
||||||
seed::derive_seed(settings.seed, 10_001),
|
seed::derive_seed(settings.seed, START_COUNT_STREAM),
|
||||||
);
|
);
|
||||||
let end_count = random_range_inclusive(
|
let end_count = random_range_inclusive(
|
||||||
settings.min_end_marker_count,
|
settings.min_end_marker_count,
|
||||||
settings.max_end_marker_count,
|
settings.max_end_marker_count,
|
||||||
seed::derive_seed(settings.seed, 10_002),
|
seed::derive_seed(settings.seed, END_COUNT_STREAM),
|
||||||
);
|
);
|
||||||
let pair_count = start_count.min(end_count);
|
let pair_count = start_count.min(end_count);
|
||||||
|
|
||||||
@@ -37,13 +47,13 @@ pub fn populate_random_markers(
|
|||||||
start_room,
|
start_room,
|
||||||
settings.min_start_marker_size,
|
settings.min_start_marker_size,
|
||||||
settings.max_start_marker_size,
|
settings.max_start_marker_size,
|
||||||
seed::derive_seed(settings.seed, 11_000 + pair_idx as u64),
|
seed::derive_seed(settings.seed, START_ROOM_STREAM_BASE + pair_idx as u64),
|
||||||
));
|
));
|
||||||
layout.end_markers.push(marker_in_room(
|
layout.end_markers.push(marker_in_room(
|
||||||
end_room,
|
end_room,
|
||||||
settings.min_end_marker_size,
|
settings.min_end_marker_size,
|
||||||
settings.max_end_marker_size,
|
settings.max_end_marker_size,
|
||||||
seed::derive_seed(settings.seed, 12_000 + pair_idx as u64),
|
seed::derive_seed(settings.seed, END_ROOM_STREAM_BASE + pair_idx as u64),
|
||||||
));
|
));
|
||||||
consume_room(&mut available_start_rooms, start_room_idx);
|
consume_room(&mut available_start_rooms, start_room_idx);
|
||||||
consume_room(&mut available_end_rooms, end_room_idx);
|
consume_room(&mut available_end_rooms, end_room_idx);
|
||||||
@@ -53,14 +63,14 @@ pub fn populate_random_markers(
|
|||||||
let room_idx = pick_room_index(
|
let room_idx = pick_room_index(
|
||||||
&available_start_rooms,
|
&available_start_rooms,
|
||||||
layout.rooms.len(),
|
layout.rooms.len(),
|
||||||
seed::derive_seed(settings.seed, 13_000 + idx as u64),
|
seed::derive_seed(settings.seed, EXTRA_START_ROOM_STREAM_BASE + idx as u64),
|
||||||
);
|
);
|
||||||
let room = &layout.rooms[room_idx];
|
let room = &layout.rooms[room_idx];
|
||||||
layout.start_markers.push(marker_in_room(
|
layout.start_markers.push(marker_in_room(
|
||||||
room,
|
room,
|
||||||
settings.min_start_marker_size,
|
settings.min_start_marker_size,
|
||||||
settings.max_start_marker_size,
|
settings.max_start_marker_size,
|
||||||
seed::derive_seed(settings.seed, 14_000 + idx as u64),
|
seed::derive_seed(settings.seed, EXTRA_START_MARKER_STREAM_BASE + idx as u64),
|
||||||
));
|
));
|
||||||
consume_room(&mut available_start_rooms, room_idx);
|
consume_room(&mut available_start_rooms, room_idx);
|
||||||
}
|
}
|
||||||
@@ -69,14 +79,14 @@ pub fn populate_random_markers(
|
|||||||
let room_idx = pick_room_index(
|
let room_idx = pick_room_index(
|
||||||
&available_end_rooms,
|
&available_end_rooms,
|
||||||
layout.rooms.len(),
|
layout.rooms.len(),
|
||||||
seed::derive_seed(settings.seed, 15_000 + idx as u64),
|
seed::derive_seed(settings.seed, EXTRA_END_ROOM_STREAM_BASE + idx as u64),
|
||||||
);
|
);
|
||||||
let room = &layout.rooms[room_idx];
|
let room = &layout.rooms[room_idx];
|
||||||
layout.end_markers.push(marker_in_room(
|
layout.end_markers.push(marker_in_room(
|
||||||
room,
|
room,
|
||||||
settings.min_end_marker_size,
|
settings.min_end_marker_size,
|
||||||
settings.max_end_marker_size,
|
settings.max_end_marker_size,
|
||||||
seed::derive_seed(settings.seed, 16_000 + idx as u64),
|
seed::derive_seed(settings.seed, EXTRA_END_MARKER_STREAM_BASE + idx as u64),
|
||||||
));
|
));
|
||||||
consume_room(&mut available_end_rooms, room_idx);
|
consume_room(&mut available_end_rooms, room_idx);
|
||||||
}
|
}
|
||||||
@@ -84,6 +94,7 @@ pub fn populate_random_markers(
|
|||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual marker placement uses the configured minimum size for the chosen marker type.
|
||||||
pub fn manual_marker_size(settings: &UiSettings, is_start: bool) -> usize {
|
pub fn manual_marker_size(settings: &UiSettings, is_start: bool) -> usize {
|
||||||
if is_start {
|
if is_start {
|
||||||
settings
|
settings
|
||||||
@@ -95,6 +106,7 @@ pub fn manual_marker_size(settings: &UiSettings, is_start: bool) -> usize {
|
|||||||
.clamp(1, 10)
|
.clamp(1, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pick a deterministic inclusive random value from a seed-derived stream.
|
||||||
fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize {
|
fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize {
|
||||||
let min = min.max(1);
|
let min = min.max(1);
|
||||||
let max = max.max(min);
|
let max = max.max(min);
|
||||||
@@ -102,6 +114,7 @@ fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize {
|
|||||||
min + (seed_value as usize % span)
|
min + (seed_value as usize % span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fit a marker inside a room and choose a deterministic offset within that room.
|
||||||
fn marker_in_room(
|
fn marker_in_room(
|
||||||
room: &layout::Room,
|
room: &layout::Room,
|
||||||
min_size: usize,
|
min_size: usize,
|
||||||
@@ -129,6 +142,7 @@ fn marker_in_room(
|
|||||||
layout::AreaMarker { cell: (x, y), size }
|
layout::AreaMarker { cell: (x, y), size }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match each start/end pair to the farthest available room combination.
|
||||||
fn farthest_room_pair(
|
fn farthest_room_pair(
|
||||||
rooms: &[layout::Room],
|
rooms: &[layout::Room],
|
||||||
available_start_rooms: &[usize],
|
available_start_rooms: &[usize],
|
||||||
@@ -170,12 +184,14 @@ fn room_distance_sq(a: &layout::Room, b: &layout::Room) -> usize {
|
|||||||
dx * dx + dy * dy
|
dx * dx + dy * dy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove a room from the available set once it has been consumed by a marker assignment.
|
||||||
fn consume_room(available_rooms: &mut Vec<usize>, room_idx: usize) {
|
fn consume_room(available_rooms: &mut Vec<usize>, room_idx: usize) {
|
||||||
if let Some(pos) = available_rooms.iter().position(|&idx| idx == room_idx) {
|
if let Some(pos) = available_rooms.iter().position(|&idx| idx == room_idx) {
|
||||||
available_rooms.remove(pos);
|
available_rooms.remove(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall back to any room when all candidates for a side have been exhausted.
|
||||||
fn pick_room_index(available_rooms: &[usize], room_count: usize, seed_value: u64) -> usize {
|
fn pick_room_index(available_rooms: &[usize], room_count: usize, seed_value: u64) -> usize {
|
||||||
if !available_rooms.is_empty() {
|
if !available_rooms.is_empty() {
|
||||||
available_rooms[seed_value as usize % available_rooms.len()]
|
available_rooms[seed_value as usize % available_rooms.len()]
|
||||||
|
|||||||
@@ -783,123 +783,97 @@ fn draw_start_and_end_tab(
|
|||||||
) {
|
) {
|
||||||
ui.label(RichText::new("Start Markers").strong());
|
ui.label(RichText::new("Start Markers").strong());
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.label("Min Start Marker Size");
|
draw_usize_slider_row(
|
||||||
ui.horizontal(|ui| {
|
ui,
|
||||||
result.settings_changed |= ui
|
result,
|
||||||
.add(egui::Slider::new(&mut settings.min_start_marker_size, 1..=10).show_value(false))
|
"Min Start Marker Size",
|
||||||
.changed();
|
&mut settings.min_start_marker_size,
|
||||||
result.settings_changed |= ui
|
1..=10,
|
||||||
.add(
|
1..=10,
|
||||||
egui::DragValue::new(&mut settings.min_start_marker_size)
|
);
|
||||||
.speed(1.0)
|
draw_usize_slider_row(
|
||||||
.range(1..=10),
|
ui,
|
||||||
)
|
result,
|
||||||
.changed();
|
"Max Start Marker Size",
|
||||||
});
|
&mut settings.max_start_marker_size,
|
||||||
ui.add_space(6.0);
|
1..=10,
|
||||||
ui.label("Max Start Marker Size");
|
1..=10,
|
||||||
ui.horizontal(|ui| {
|
);
|
||||||
result.settings_changed |= ui
|
draw_usize_slider_row(
|
||||||
.add(egui::Slider::new(&mut settings.max_start_marker_size, 1..=10).show_value(false))
|
ui,
|
||||||
.changed();
|
result,
|
||||||
result.settings_changed |= ui
|
"Min Start Marker Count",
|
||||||
.add(
|
&mut settings.min_start_marker_count,
|
||||||
egui::DragValue::new(&mut settings.max_start_marker_size)
|
1..=20,
|
||||||
.speed(1.0)
|
1..=100,
|
||||||
.range(1..=10),
|
);
|
||||||
)
|
draw_usize_slider_row(
|
||||||
.changed();
|
ui,
|
||||||
});
|
result,
|
||||||
ui.add_space(6.0);
|
"Max Start Marker Count",
|
||||||
ui.label("Min Start Marker Count");
|
&mut settings.max_start_marker_count,
|
||||||
ui.horizontal(|ui| {
|
1..=20,
|
||||||
result.settings_changed |= ui
|
1..=100,
|
||||||
.add(egui::Slider::new(&mut settings.min_start_marker_count, 1..=20).show_value(false))
|
);
|
||||||
.changed();
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(
|
|
||||||
egui::DragValue::new(&mut settings.min_start_marker_count)
|
|
||||||
.speed(1.0)
|
|
||||||
.range(1..=100),
|
|
||||||
)
|
|
||||||
.changed();
|
|
||||||
});
|
|
||||||
ui.add_space(6.0);
|
|
||||||
ui.label("Max Start Marker Count");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(egui::Slider::new(&mut settings.max_start_marker_count, 1..=20).show_value(false))
|
|
||||||
.changed();
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(
|
|
||||||
egui::DragValue::new(&mut settings.max_start_marker_count)
|
|
||||||
.speed(1.0)
|
|
||||||
.range(1..=100),
|
|
||||||
)
|
|
||||||
.changed();
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.label(RichText::new("End Markers").strong());
|
ui.label(RichText::new("End Markers").strong());
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.label("Min End Marker Size");
|
draw_usize_slider_row(
|
||||||
ui.horizontal(|ui| {
|
ui,
|
||||||
result.settings_changed |= ui
|
result,
|
||||||
.add(egui::Slider::new(&mut settings.min_end_marker_size, 1..=10).show_value(false))
|
"Min End Marker Size",
|
||||||
.changed();
|
&mut settings.min_end_marker_size,
|
||||||
result.settings_changed |= ui
|
1..=10,
|
||||||
.add(
|
1..=10,
|
||||||
egui::DragValue::new(&mut settings.min_end_marker_size)
|
);
|
||||||
.speed(1.0)
|
draw_usize_slider_row(
|
||||||
.range(1..=10),
|
ui,
|
||||||
)
|
result,
|
||||||
.changed();
|
"Max End Marker Size",
|
||||||
});
|
&mut settings.max_end_marker_size,
|
||||||
ui.add_space(6.0);
|
1..=10,
|
||||||
ui.label("Max End Marker Size");
|
1..=10,
|
||||||
ui.horizontal(|ui| {
|
);
|
||||||
result.settings_changed |= ui
|
draw_usize_slider_row(
|
||||||
.add(egui::Slider::new(&mut settings.max_end_marker_size, 1..=10).show_value(false))
|
ui,
|
||||||
.changed();
|
result,
|
||||||
result.settings_changed |= ui
|
"Min End Marker Count",
|
||||||
.add(
|
&mut settings.min_end_marker_count,
|
||||||
egui::DragValue::new(&mut settings.max_end_marker_size)
|
1..=20,
|
||||||
.speed(1.0)
|
1..=100,
|
||||||
.range(1..=10),
|
);
|
||||||
)
|
draw_usize_slider_row(
|
||||||
.changed();
|
ui,
|
||||||
});
|
result,
|
||||||
ui.add_space(6.0);
|
"Max End Marker Count",
|
||||||
ui.label("Min End Marker Count");
|
&mut settings.max_end_marker_count,
|
||||||
ui.horizontal(|ui| {
|
1..=20,
|
||||||
result.settings_changed |= ui
|
1..=100,
|
||||||
.add(egui::Slider::new(&mut settings.min_end_marker_count, 1..=20).show_value(false))
|
);
|
||||||
.changed();
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(
|
|
||||||
egui::DragValue::new(&mut settings.min_end_marker_count)
|
|
||||||
.speed(1.0)
|
|
||||||
.range(1..=100),
|
|
||||||
)
|
|
||||||
.changed();
|
|
||||||
});
|
|
||||||
ui.add_space(6.0);
|
|
||||||
ui.label("Max End Marker Count");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(egui::Slider::new(&mut settings.max_end_marker_count, 1..=20).show_value(false))
|
|
||||||
.changed();
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.add(
|
|
||||||
egui::DragValue::new(&mut settings.max_end_marker_count)
|
|
||||||
.speed(1.0)
|
|
||||||
.range(1..=100),
|
|
||||||
)
|
|
||||||
.changed();
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.label("Markers are generated inside rooms. Matching start/end indices are paired as far apart as possible.");
|
ui.label("Markers are generated inside rooms. Matching start/end indices are paired as far apart as possible.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_usize_slider_row(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
result: &mut SidePanelResult,
|
||||||
|
label: &str,
|
||||||
|
value: &mut usize,
|
||||||
|
slider_range: std::ops::RangeInclusive<usize>,
|
||||||
|
drag_range: std::ops::RangeInclusive<usize>,
|
||||||
|
) {
|
||||||
|
ui.label(label);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
result.settings_changed |= ui
|
||||||
|
.add(egui::Slider::new(value, slider_range).show_value(false))
|
||||||
|
.changed();
|
||||||
|
result.settings_changed |= ui
|
||||||
|
.add(egui::DragValue::new(value).speed(1.0).range(drag_range))
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
ui.add_space(6.0);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user