diff --git a/src/exporter.rs b/src/exporter.rs index 8c191ed..616694c 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -341,11 +341,17 @@ fn export_composite_masks( .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 { if door.secret { draw_door(&mut secret_doors, &g, door, door_w, WHITE, DoorStyle::Solid); @@ -360,6 +366,12 @@ fn export_composite_masks( for window in &layout.windows { draw_window(&mut windows, &g, window, door_w, WHITE, DoorStyle::Solid); } + for marker in &layout.start_markers { + fill_rect_marker(&mut start_markers, &g, marker, WHITE); + } + for marker in &layout.end_markers { + fill_rect_marker(&mut end_markers, &g, marker, WHITE); + } let composite = render_pixmap(layout, settings)?; let composite_img = raster_image_from_pixmap(&composite, settings)?; @@ -376,6 +388,8 @@ fn export_composite_masks( save_mask_image(folder, "locked_doors_mask", &locked_doors, settings)?; save_mask_image(folder, "secret_doors_mask", &secret_doors, settings)?; save_mask_image(folder, "windows_mask", &windows, settings)?; + save_mask_image(folder, "start_markers_mask", &start_markers, settings)?; + save_mask_image(folder, "end_markers_mask", &end_markers, settings)?; if settings.export_show_grid { let mut grid = Pixmap::new(g.width, g.height) .ok_or_else(|| "Failed to allocate grid mask canvas".to_string())?; diff --git a/src/main.rs b/src/main.rs index d2b1599..8f2ba8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod exporter; mod layout; mod seed; mod settings; +mod startend; mod ui; use std::collections::HashSet; @@ -14,6 +15,7 @@ use layout::{ DoorSettings, DungeonLayout, WindowSettings, blocked_room_cells, corridor_cells, shortest_path_cells, }; +use startend::{manual_marker_size, populate_random_markers}; use ui::{AddTool, UiSettings, draw_side_panel}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -848,7 +850,7 @@ impl DungeonApp { return; } - let size = manual_marker_size(&self.settings, kind); + let size = manual_marker_size(&self.settings, matches!(kind, MarkerKind::Start)); let origin = centered_marker_origin(cell, size, self.settings.cols, self.settings.rows); let marker = layout::AreaMarker { cell: origin, size }; @@ -2004,180 +2006,6 @@ fn centered_marker_origin( (cell.0.saturating_sub(half).min(max_x), cell.1.saturating_sub(half).min(max_y)) } -fn populate_random_markers( - mut layout: DungeonLayout, - settings: &UiSettings, -) -> DungeonLayout { - layout.start_markers.clear(); - layout.end_markers.clear(); - - if layout.rooms.is_empty() { - return layout; - } - - let start_count = random_range_inclusive( - settings.min_start_marker_count, - settings.max_start_marker_count, - seed::derive_seed(settings.seed, 10_001), - ); - let end_count = random_range_inclusive( - settings.min_end_marker_count, - settings.max_end_marker_count, - seed::derive_seed(settings.seed, 10_002), - ); - let pair_count = start_count.min(end_count); - - let mut available_start_rooms: Vec = (0..layout.rooms.len()).collect(); - let mut available_end_rooms: Vec = (0..layout.rooms.len()).collect(); - - for pair_idx in 0..pair_count { - let (start_room_idx, end_room_idx) = - farthest_room_pair(&layout.rooms, &available_start_rooms, &available_end_rooms); - let start_room = &layout.rooms[start_room_idx]; - let end_room = &layout.rooms[end_room_idx]; - layout.start_markers.push(marker_in_room( - start_room, - settings.min_start_marker_size, - settings.max_start_marker_size, - seed::derive_seed(settings.seed, 11_000 + pair_idx as u64), - )); - layout.end_markers.push(marker_in_room( - end_room, - settings.min_end_marker_size, - settings.max_end_marker_size, - seed::derive_seed(settings.seed, 12_000 + pair_idx as u64), - )); - consume_room(&mut available_start_rooms, start_room_idx); - consume_room(&mut available_end_rooms, end_room_idx); - } - - for idx in pair_count..start_count { - let room_idx = pick_room_index( - &available_start_rooms, - layout.rooms.len(), - seed::derive_seed(settings.seed, 13_000 + idx as u64), - ); - let room = &layout.rooms[room_idx]; - layout.start_markers.push(marker_in_room( - room, - settings.min_start_marker_size, - settings.max_start_marker_size, - seed::derive_seed(settings.seed, 14_000 + idx as u64), - )); - consume_room(&mut available_start_rooms, room_idx); - } - - for idx in pair_count..end_count { - let room_idx = pick_room_index( - &available_end_rooms, - layout.rooms.len(), - seed::derive_seed(settings.seed, 15_000 + idx as u64), - ); - let room = &layout.rooms[room_idx]; - layout.end_markers.push(marker_in_room( - room, - settings.min_end_marker_size, - settings.max_end_marker_size, - seed::derive_seed(settings.seed, 16_000 + idx as u64), - )); - consume_room(&mut available_end_rooms, room_idx); - } - - layout -} - -fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize { - let min = min.max(1); - let max = max.max(min); - let span = max - min + 1; - min + (seed_value as usize % span) -} - -fn manual_marker_size(settings: &UiSettings, kind: MarkerKind) -> usize { - match kind { - MarkerKind::Start => settings.min_start_marker_size.min(settings.max_start_marker_size), - MarkerKind::End => settings.min_end_marker_size.min(settings.max_end_marker_size), - } - .clamp(1, 10) -} - -fn marker_in_room(room: &layout::Room, min_size: usize, max_size: usize, seed_value: u64) -> layout::AreaMarker { - let room_limit = room.width.min(room.height).max(1); - let min_size = min_size.clamp(1, 10).min(room_limit); - let max_size = max_size.clamp(min_size, 10).min(room_limit); - let size = random_range_inclusive(min_size, max_size, seed_value.rotate_left(7)); - let x_span = room.width.saturating_sub(size); - let y_span = room.height.saturating_sub(size); - let x = room.x - + if x_span == 0 { - 0 - } else { - (seed_value as usize) % (x_span + 1) - }; - let y = room.y - + if y_span == 0 { - 0 - } else { - (seed_value.rotate_left(19) as usize) % (y_span + 1) - }; - layout::AreaMarker { cell: (x, y), size } -} - -fn farthest_room_pair( - rooms: &[layout::Room], - available_start_rooms: &[usize], - available_end_rooms: &[usize], -) -> (usize, usize) { - let start_rooms = if available_start_rooms.is_empty() { - (0..rooms.len()).collect::>() - } else { - available_start_rooms.to_vec() - }; - let end_rooms = if available_end_rooms.is_empty() { - (0..rooms.len()).collect::>() - } else { - available_end_rooms.to_vec() - }; - - let mut best = (start_rooms[0], end_rooms[0]); - let mut best_dist = 0usize; - for &start_idx in &start_rooms { - for &end_idx in &end_rooms { - if rooms.len() > 1 && start_idx == end_idx { - continue; - } - let dist = room_distance_sq(&rooms[start_idx], &rooms[end_idx]); - if dist > best_dist { - best = (start_idx, end_idx); - best_dist = dist; - } - } - } - best -} - -fn room_distance_sq(a: &layout::Room, b: &layout::Room) -> usize { - let ac = a.center_cell(); - let bc = b.center_cell(); - let dx = ac.0.abs_diff(bc.0); - let dy = ac.1.abs_diff(bc.1); - dx * dx + dy * dy -} - -fn consume_room(available_rooms: &mut Vec, room_idx: usize) { - if let Some(pos) = available_rooms.iter().position(|&idx| idx == room_idx) { - available_rooms.remove(pos); - } -} - -fn pick_room_index(available_rooms: &[usize], room_count: usize, seed_value: u64) -> usize { - if !available_rooms.is_empty() { - available_rooms[seed_value as usize % available_rooms.len()] - } else { - seed_value as usize % room_count.max(1) - } -} - fn draw_area_marker( painter: &egui::Painter, geometry: &GridGeometry, diff --git a/src/startend.rs b/src/startend.rs new file mode 100644 index 0000000..923edde --- /dev/null +++ b/src/startend.rs @@ -0,0 +1,185 @@ +use crate::layout::{self, DungeonLayout}; +use crate::seed; +use crate::ui::UiSettings; + +pub fn populate_random_markers( + mut layout: DungeonLayout, + settings: &UiSettings, +) -> DungeonLayout { + layout.start_markers.clear(); + layout.end_markers.clear(); + + if layout.rooms.is_empty() { + return layout; + } + + let start_count = random_range_inclusive( + settings.min_start_marker_count, + settings.max_start_marker_count, + seed::derive_seed(settings.seed, 10_001), + ); + let end_count = random_range_inclusive( + settings.min_end_marker_count, + settings.max_end_marker_count, + seed::derive_seed(settings.seed, 10_002), + ); + let pair_count = start_count.min(end_count); + + let mut available_start_rooms: Vec = (0..layout.rooms.len()).collect(); + let mut available_end_rooms: Vec = (0..layout.rooms.len()).collect(); + + for pair_idx in 0..pair_count { + let (start_room_idx, end_room_idx) = + farthest_room_pair(&layout.rooms, &available_start_rooms, &available_end_rooms); + let start_room = &layout.rooms[start_room_idx]; + let end_room = &layout.rooms[end_room_idx]; + layout.start_markers.push(marker_in_room( + start_room, + settings.min_start_marker_size, + settings.max_start_marker_size, + seed::derive_seed(settings.seed, 11_000 + pair_idx as u64), + )); + layout.end_markers.push(marker_in_room( + end_room, + settings.min_end_marker_size, + settings.max_end_marker_size, + seed::derive_seed(settings.seed, 12_000 + pair_idx as u64), + )); + consume_room(&mut available_start_rooms, start_room_idx); + consume_room(&mut available_end_rooms, end_room_idx); + } + + for idx in pair_count..start_count { + let room_idx = pick_room_index( + &available_start_rooms, + layout.rooms.len(), + seed::derive_seed(settings.seed, 13_000 + idx as u64), + ); + let room = &layout.rooms[room_idx]; + layout.start_markers.push(marker_in_room( + room, + settings.min_start_marker_size, + settings.max_start_marker_size, + seed::derive_seed(settings.seed, 14_000 + idx as u64), + )); + consume_room(&mut available_start_rooms, room_idx); + } + + for idx in pair_count..end_count { + let room_idx = pick_room_index( + &available_end_rooms, + layout.rooms.len(), + seed::derive_seed(settings.seed, 15_000 + idx as u64), + ); + let room = &layout.rooms[room_idx]; + layout.end_markers.push(marker_in_room( + room, + settings.min_end_marker_size, + settings.max_end_marker_size, + seed::derive_seed(settings.seed, 16_000 + idx as u64), + )); + consume_room(&mut available_end_rooms, room_idx); + } + + layout +} + +pub fn manual_marker_size(settings: &UiSettings, is_start: bool) -> usize { + if is_start { + settings + .min_start_marker_size + .min(settings.max_start_marker_size) + } else { + settings.min_end_marker_size.min(settings.max_end_marker_size) + } + .clamp(1, 10) +} + +fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize { + let min = min.max(1); + let max = max.max(min); + let span = max - min + 1; + min + (seed_value as usize % span) +} + +fn marker_in_room( + room: &layout::Room, + min_size: usize, + max_size: usize, + seed_value: u64, +) -> layout::AreaMarker { + let room_limit = room.width.min(room.height).max(1); + let min_size = min_size.clamp(1, 10).min(room_limit); + let max_size = max_size.clamp(min_size, 10).min(room_limit); + let size = random_range_inclusive(min_size, max_size, seed_value.rotate_left(7)); + let x_span = room.width.saturating_sub(size); + let y_span = room.height.saturating_sub(size); + let x = room.x + + if x_span == 0 { + 0 + } else { + (seed_value as usize) % (x_span + 1) + }; + let y = room.y + + if y_span == 0 { + 0 + } else { + (seed_value.rotate_left(19) as usize) % (y_span + 1) + }; + layout::AreaMarker { cell: (x, y), size } +} + +fn farthest_room_pair( + rooms: &[layout::Room], + available_start_rooms: &[usize], + available_end_rooms: &[usize], +) -> (usize, usize) { + let start_rooms = if available_start_rooms.is_empty() { + (0..rooms.len()).collect::>() + } else { + available_start_rooms.to_vec() + }; + let end_rooms = if available_end_rooms.is_empty() { + (0..rooms.len()).collect::>() + } else { + available_end_rooms.to_vec() + }; + + let mut best = (start_rooms[0], end_rooms[0]); + let mut best_dist = 0usize; + for &start_idx in &start_rooms { + for &end_idx in &end_rooms { + if rooms.len() > 1 && start_idx == end_idx { + continue; + } + let dist = room_distance_sq(&rooms[start_idx], &rooms[end_idx]); + if dist > best_dist { + best = (start_idx, end_idx); + best_dist = dist; + } + } + } + best +} + +fn room_distance_sq(a: &layout::Room, b: &layout::Room) -> usize { + let ac = a.center_cell(); + let bc = b.center_cell(); + let dx = ac.0.abs_diff(bc.0); + let dy = ac.1.abs_diff(bc.1); + dx * dx + dy * dy +} + +fn consume_room(available_rooms: &mut Vec, room_idx: usize) { + if let Some(pos) = available_rooms.iter().position(|&idx| idx == room_idx) { + available_rooms.remove(pos); + } +} + +fn pick_room_index(available_rooms: &[usize], room_count: usize, seed_value: u64) -> usize { + if !available_rooms.is_empty() { + available_rooms[seed_value as usize % available_rooms.len()] + } else { + seed_value as usize % room_count.max(1) + } +}