1use crate::{
2 Index,
3 sim::WorldSim,
4 site::{self, Site, plot::PlotKindMeta},
5 util::{DHashMap, DHashSet, seed_expan},
6};
7use common::{
8 store::{Id, Store},
9 terrain::{MapSizeLg, TERRAIN_CHUNK_BLOCKS_LG},
10 util::Dir,
11};
12use delaunator::{Point, Triangulation, triangulate};
13use itertools::Itertools;
14use rand::{SeedableRng, prelude::*};
15use rand_chacha::ChaChaRng;
16use tracing::{error, warn};
17use vek::*;
18
19#[cfg(debug_assertions)] use tracing::debug;
20
21#[cfg(feature = "airship_maps")]
22use crate::civ::airship_route_map::*;
23
24#[cfg(debug_assertions)]
25macro_rules! debug_airships {
26 ($($arg:tt)*) => {
27 debug!($($arg)*);
28 }
29}
30
31#[cfg(not(debug_assertions))]
32macro_rules! debug_airships {
33 ($($arg:tt)*) => {};
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq)]
39pub struct AirshipDockingPosition(pub u32, pub Vec3<f32>);
40
41#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
45pub enum AirshipDockPlatform {
46 #[default]
47 NorthPlatform,
48 EastPlatform,
49 SouthPlatform,
50 WestPlatform,
51}
52
53#[derive(Debug, Copy, Clone, PartialEq, Default)]
55pub enum AirshipDockingSide {
56 #[default]
57 Port,
58 Starboard,
59}
60
61impl AirshipDockingSide {
62 const EAST_REF_VEC: Vec2<f32> = Vec2 { x: 1.0, y: 0.0 };
63 const NORTH_REF_VEC: Vec2<f32> = Vec2 { x: 0.0, y: 1.0 };
64 const SOUTH_REF_VEC: Vec2<f32> = Vec2 { x: 0.0, y: -1.0 };
65 const WEST_REF_VEC: Vec2<f32> = Vec2 { x: -1.0, y: 0.0 };
66
67 pub fn from_dir_to_platform(dir: &Vec2<f32>, platform: &AirshipDockPlatform) -> Self {
83 let (ref_vec, negate_angle) = match platform {
86 AirshipDockPlatform::NorthPlatform => (&AirshipDockingSide::NORTH_REF_VEC, dir.x < 0.0),
87 AirshipDockPlatform::EastPlatform => (&AirshipDockingSide::EAST_REF_VEC, dir.y > 0.0),
88 AirshipDockPlatform::SouthPlatform => (&AirshipDockingSide::SOUTH_REF_VEC, dir.x > 0.0),
89 AirshipDockPlatform::WestPlatform => (&AirshipDockingSide::WEST_REF_VEC, dir.y < 0.0),
90 };
91 let mut angle = dir.angle_between(*ref_vec).to_degrees();
92 if negate_angle {
93 angle = -angle;
94 }
95 match angle as i32 {
96 -360..=0 => AirshipDockingSide::Port,
97 _ => AirshipDockingSide::Starboard,
98 }
99 }
100}
101
102#[derive(Clone, Copy, Debug, PartialEq)]
105pub struct AirshipDockingApproach {
106 pub airship_pos: Vec3<f32>,
110 pub airship_direction: Dir,
112 pub dock_center: Vec2<f32>,
114 pub height: f32,
116 pub approach_transition_pos: Vec3<f32>,
118 pub side: AirshipDockingSide,
121 pub site_id: Id<Site>,
124}
125
126#[derive(Clone, Debug)]
128pub struct AirshipDockPositions {
129 pub center: Vec2<f32>,
131 pub docking_positions: Vec<AirshipDockingPosition>,
134 pub site_id: Id<site::Site>,
136}
137
138#[derive(Clone, Default, Debug)]
140pub struct AirshipRouteLeg {
141 pub dest_index: usize,
143 pub platform: AirshipDockPlatform,
145}
146
147#[derive(Debug, Clone)]
150pub struct AirshipSpawningLocation {
151 pub pos: Vec2<f32>,
152 pub dir: Vec2<f32>,
153 pub height: f32,
154 pub route_index: usize,
155 pub leg_index: usize,
156}
157
158#[derive(Clone, Default)]
160pub struct Airships {
161 pub airship_docks: Vec<AirshipDockPositions>,
162 pub routes: Vec<Vec<AirshipRouteLeg>>,
163 pub spawning_locations: Vec<AirshipSpawningLocation>,
164}
165
166impl AirshipDockPositions {
169 fn from_plot_meta(
170 first_id: u32,
171 center: Vec2<i32>,
172 docking_positions: &[Vec3<i32>],
173 site_id: Id<site::Site>,
174 ) -> Self {
175 let mut dock_pos_id = first_id;
176 Self {
177 center: center.map(|i| i as f32),
178 docking_positions: docking_positions
179 .iter()
180 .map(|pos: &Vec3<i32>| {
181 let docking_position =
182 AirshipDockingPosition(dock_pos_id, pos.map(|i| i as f32));
183 dock_pos_id += 1;
184 docking_position
185 })
186 .collect(),
187 site_id,
188 }
189 }
190
191 fn docking_position(&self, platform: AirshipDockPlatform) -> Vec3<f32> {
193 self.docking_positions
194 .iter()
195 .find_map(|&docking_position| {
196 let docking_position_platform =
200 AirshipDockPlatform::from_dir(docking_position.1.xy() - self.center);
201 if docking_position_platform == platform {
202 Some(docking_position.1)
203 } else {
204 None
205 }
206 })
207 .unwrap_or_else(|| {
208 self.center.with_z(1000.0)
210 })
211 }
212}
213
214static SWEN_PLATFORMS: [AirshipDockPlatform; 4] = [
217 AirshipDockPlatform::SouthPlatform,
218 AirshipDockPlatform::WestPlatform,
219 AirshipDockPlatform::EastPlatform,
220 AirshipDockPlatform::NorthPlatform,
221];
222
223static SEWN_PLATFORMS: [AirshipDockPlatform; 4] = [
224 AirshipDockPlatform::SouthPlatform,
225 AirshipDockPlatform::EastPlatform,
226 AirshipDockPlatform::WestPlatform,
227 AirshipDockPlatform::NorthPlatform,
228];
229
230static WNSE_PLATFORMS: [AirshipDockPlatform; 4] = [
231 AirshipDockPlatform::WestPlatform,
232 AirshipDockPlatform::NorthPlatform,
233 AirshipDockPlatform::SouthPlatform,
234 AirshipDockPlatform::EastPlatform,
235];
236
237static WSNE_PLATFORMS: [AirshipDockPlatform; 4] = [
238 AirshipDockPlatform::WestPlatform,
239 AirshipDockPlatform::SouthPlatform,
240 AirshipDockPlatform::NorthPlatform,
241 AirshipDockPlatform::EastPlatform,
242];
243
244static NEWS_PLATFORMS: [AirshipDockPlatform; 4] = [
245 AirshipDockPlatform::NorthPlatform,
246 AirshipDockPlatform::EastPlatform,
247 AirshipDockPlatform::WestPlatform,
248 AirshipDockPlatform::SouthPlatform,
249];
250
251static NWES_PLATFORMS: [AirshipDockPlatform; 4] = [
252 AirshipDockPlatform::NorthPlatform,
253 AirshipDockPlatform::WestPlatform,
254 AirshipDockPlatform::EastPlatform,
255 AirshipDockPlatform::SouthPlatform,
256];
257
258static ESNW_PLATFORMS: [AirshipDockPlatform; 4] = [
259 AirshipDockPlatform::EastPlatform,
260 AirshipDockPlatform::SouthPlatform,
261 AirshipDockPlatform::NorthPlatform,
262 AirshipDockPlatform::WestPlatform,
263];
264
265static ENSW_PLATFORMS: [AirshipDockPlatform; 4] = [
266 AirshipDockPlatform::EastPlatform,
267 AirshipDockPlatform::NorthPlatform,
268 AirshipDockPlatform::SouthPlatform,
269 AirshipDockPlatform::WestPlatform,
270];
271
272impl AirshipDockPlatform {
282 pub fn from_dir(dir: Vec2<f32>) -> Self {
284 if let Some(dir) = dir.try_normalized() {
285 let mut angle = dir.angle_between(Vec2::unit_y()).to_degrees();
286 if dir.x < 0.0 {
287 angle = -angle;
288 }
289 match angle as i32 {
290 -360..=-135 => AirshipDockPlatform::SouthPlatform,
291 -134..=-45 => AirshipDockPlatform::WestPlatform,
292 -44..=45 => AirshipDockPlatform::NorthPlatform,
293 46..=135 => AirshipDockPlatform::EastPlatform,
294 136..=360 => AirshipDockPlatform::SouthPlatform,
295 _ => AirshipDockPlatform::NorthPlatform, }
297 } else {
298 AirshipDockPlatform::NorthPlatform }
300 }
301
302 pub fn choices_from_dir(dir: Vec2<f32>) -> &'static [AirshipDockPlatform] {
309 if let Some(dir) = dir.try_normalized() {
310 let mut angle = dir.angle_between(Vec2::unit_y()).to_degrees();
311 if dir.x < 0.0 {
312 angle = -angle;
313 }
314 match angle as i32 {
321 -360..=-135 => {
322 if angle as i32 > -180 {
329 &SWEN_PLATFORMS
330 } else {
331 &SEWN_PLATFORMS
332 }
333 },
334 -134..=-45 => {
335 if angle as i32 > -90 {
337 &WNSE_PLATFORMS
338 } else {
339 &WSNE_PLATFORMS
340 }
341 },
342 -44..=45 => {
343 if angle as i32 > 0 {
345 &NEWS_PLATFORMS
346 } else {
347 &NWES_PLATFORMS
348 }
349 },
350 46..=135 => {
351 if angle as i32 > 90 {
353 &ESNW_PLATFORMS
354 } else {
355 &ENSW_PLATFORMS
356 }
357 },
358 136..=360 => {
359 if angle as i32 > 180 {
361 &SWEN_PLATFORMS
362 } else {
363 &SEWN_PLATFORMS
364 }
365 },
366 _ => &SEWN_PLATFORMS,
367 }
368 } else {
369 &SEWN_PLATFORMS
370 }
371 }
372
373 fn airship_dir_for_side(&self, side: AirshipDockingSide) -> Dir {
375 match self {
376 AirshipDockPlatform::NorthPlatform => match side {
377 AirshipDockingSide::Starboard => Dir::new(Vec2::unit_x().with_z(0.0)),
378 AirshipDockingSide::Port => Dir::new(-Vec2::unit_x().with_z(0.0)),
379 },
380 AirshipDockPlatform::EastPlatform => match side {
381 AirshipDockingSide::Starboard => Dir::new(-Vec2::unit_y().with_z(0.0)),
382 AirshipDockingSide::Port => Dir::new(Vec2::unit_y().with_z(0.0)),
383 },
384 AirshipDockPlatform::SouthPlatform => match side {
385 AirshipDockingSide::Starboard => Dir::new(-Vec2::unit_x().with_z(0.0)),
386 AirshipDockingSide::Port => Dir::new(Vec2::unit_x().with_z(0.0)),
387 },
388 AirshipDockPlatform::WestPlatform => match side {
389 AirshipDockingSide::Starboard => Dir::new(Vec2::unit_y().with_z(0.0)),
390 AirshipDockingSide::Port => Dir::new(-Vec2::unit_y().with_z(0.0)),
391 },
392 }
393 }
394}
395
396#[derive(Clone, Debug, Default, PartialEq)]
399pub struct DockNode {
400 pub node_id: usize,
402 pub on_hull: bool,
405 pub connected: Vec<usize>,
407}
408
409impl Airships {
410 pub const AIRSHIP_SPACING: f32 = 5000.0;
413 const AIRSHIP_TO_DOCK_Z_OFFSET: f32 = -3.0;
416 pub const CRUISE_HEIGHTS: [f32; 4] = [400.0, 475.0, 550.0, 625.0];
419 const DEFAULT_DOCK_DURATION: f32 = 60.0;
421 const DOCKING_TRANSITION_OFFSET: f32 = 175.0;
422 const DOCK_ALIGN_POS_PORT: Vec2<f32> =
425 Vec2::new(Airships::DOCK_ALIGN_X, -Airships::DOCK_ALIGN_Y);
426 const DOCK_ALIGN_POS_STARBOARD: Vec2<f32> =
429 Vec2::new(-Airships::DOCK_ALIGN_X, -Airships::DOCK_ALIGN_Y);
430 const DOCK_ALIGN_X: f32 = 18.0;
436 const DOCK_ALIGN_Y: f32 = 1.0;
441 const MIN_SPAWN_POINT_DIST_FROM_DOCK: f32 = 300.0;
444 const SPAWN_TARGET_DIST_INCREMENT: f32 = 47.0;
453
454 #[inline(always)]
455 pub fn docking_duration() -> f32 { Airships::DEFAULT_DOCK_DURATION }
456
457 fn all_airshipdock_positions(sites: &Store<Site>) -> Vec<AirshipDockPositions> {
459 let mut dock_pos_id = 0;
460 sites
461 .iter()
462 .flat_map(|(site_id, site)| {
463 site.plots().flat_map(move |plot| {
464 if let Some(PlotKindMeta::AirshipDock {
465 center,
466 docking_positions,
467 ..
468 }) = plot.kind().meta()
469 {
470 Some((center, docking_positions, site_id))
471 } else {
472 None
473 }
474 })
475 })
476 .map(|(center, docking_positions, site_id)| {
477 let positions = AirshipDockPositions::from_plot_meta(
478 dock_pos_id,
479 center,
480 docking_positions,
481 site_id,
482 );
483
484 dock_pos_id += positions.docking_positions.len() as u32;
485 positions
486 })
487 .collect::<Vec<_>>()
488 }
489
490 pub fn increment_route_leg(&self, route_index: usize, leg_index: usize) -> usize {
493 if route_index >= self.routes.len() {
494 error!("Invalid route index: {}", route_index);
495 return 0;
496 }
497 (leg_index + 1) % self.routes[route_index].len()
498 }
499
500 pub fn decrement_route_leg(&self, route_index: usize, leg_index: usize) -> usize {
503 if route_index >= self.routes.len() {
504 error!("Invalid route index: {}", route_index);
505 return 0;
506 }
507 if leg_index > 0 {
508 leg_index - 1
509 } else {
510 self.routes[route_index].len() - 1
511 }
512 }
513
514 pub fn route_count(&self) -> usize { self.routes.len() }
516
517 pub fn docking_site_count_for_route(&self, route_index: usize) -> usize {
519 if route_index >= self.routes.len() {
520 error!("Invalid route index: {}", route_index);
521 return 0;
522 }
523 self.routes[route_index].len()
524 }
525
526 fn assign_docking_platforms(
533 route_segments: &[Vec<usize>],
534 dock_locations: &[Vec2<f32>],
535 ) -> Vec<Vec<AirshipRouteLeg>> {
536 let mut incoming_edges = DHashMap::default();
537 for segment in route_segments.iter() {
538 if segment.len() < 3 {
539 continue;
540 }
541 let mut prev_node_id = segment[0];
542 segment.iter().skip(1).for_each(|&node_id| {
543 incoming_edges
544 .entry(node_id)
545 .or_insert_with(Vec::new)
546 .push(prev_node_id);
547 prev_node_id = node_id;
548 });
549 }
550
551 let mut leg_platforms = DHashMap::default();
552
553 incoming_edges.iter().for_each(|(node_id, edges)| {
554 let dock_location = dock_locations[*node_id];
555 let mut used_platforms = DHashSet::default();
556 for origin in edges {
557 let origin_location = dock_locations[*origin];
558 let rev_approach_dir = origin_location - dock_location;
561 let docking_platforms = AirshipDockPlatform::choices_from_dir(rev_approach_dir);
562 let docking_platform = docking_platforms
563 .iter()
564 .find(|&platform| !used_platforms.contains(platform))
565 .copied()
566 .unwrap_or(AirshipDockPlatform::NorthPlatform);
567 leg_platforms.insert((*origin, *node_id), docking_platform);
568 used_platforms.insert(docking_platform);
569 }
570 });
571
572 #[cfg(debug_assertions)]
573 {
574 debug!("Route segments: {:?}", route_segments);
575 debug!("Leg platforms: {:?}", leg_platforms);
576 }
577
578 let mut routes = Vec::new();
583 route_segments.iter().for_each(|segment| {
584 assert!(
585 segment.len() > 2,
586 "Segments must have at least two nodes and they must wrap around."
587 );
588 let mut route_legs = Vec::new();
589 let leg_start = &segment[segment.len() - 2..];
590 for leg_index in 0..segment.len() - 1 {
591 let from_node = segment[leg_index];
592 let to_node = segment[leg_index + 1];
593 if leg_index == 0 {
594 assert!(
595 from_node == leg_start[1],
596 "The 'previous' leg's 'to' node must match the current leg's 'from' node."
597 );
598 }
599 let to_platform = leg_platforms.get(&(from_node, to_node)).copied().unwrap_or(
600 AirshipDockPlatform::from_dir(
601 dock_locations[from_node] - dock_locations[to_node],
602 ),
603 );
604 route_legs.push(AirshipRouteLeg {
605 dest_index: to_node,
606 platform: to_platform,
607 });
608 }
609 routes.push(route_legs);
610 });
611 routes
612 }
613
614 pub fn calculate_spawning_locations(&mut self, all_dock_points: &[Point]) {
622 let mut spawning_locations = Vec::new();
623 let mut expected_airships_count = 0;
624 self.routes
625 .iter()
626 .enumerate()
627 .for_each(|(route_index, route)| {
628 let route_len_blocks = route.iter().enumerate().fold(0.0f64, |acc, (j, leg)| {
630 let to_dock_point = &all_dock_points[leg.dest_index];
631 let from_dock_point = if j > 0 {
632 &all_dock_points[route[j - 1].dest_index]
633 } else {
634 &all_dock_points[route[route.len() - 1].dest_index]
635 };
636 let from_loc = Vec2::new(from_dock_point.x as f32, from_dock_point.y as f32);
637 let to_loc = Vec2::new(to_dock_point.x as f32, to_dock_point.y as f32);
638 acc + from_loc.distance(to_loc) as f64
639 });
640 let airship_count = route
644 .len()
645 .max((route_len_blocks / Airships::AIRSHIP_SPACING as f64) as usize);
646
647 expected_airships_count += airship_count;
649
650 let airship_spacing = (route_len_blocks / airship_count as f64) as f32;
652 debug_airships!(
653 "Route {} length: {} blocks, avg: {}, expecting {} airships for {} docking \
654 sites",
655 route_index,
656 route_len_blocks,
657 route_len_blocks / route.len() as f64,
658 airship_count,
659 route.len()
660 );
661
662 let route_points = route
664 .iter()
665 .map(|leg| all_dock_points[leg.dest_index].clone())
666 .collect::<Vec<_>>();
667
668 let mut best_route_spawning_locations = Vec::new();
677 let mut best_route_deviations = f32::MAX;
678 #[cfg(debug_assertions)]
679 let mut best_route_iteration = 0;
680 for i in 0..50 {
682 let mut route_spawning_locations = Vec::new();
683 let mut prev_point = &route_points[route_points.len() - 1];
684 let mut target_dist = -1.0;
685 let mut airships_spawned = 0;
686 let mut deviation = 0.0;
687 route_points
688 .iter()
689 .enumerate()
690 .for_each(|(leg_index, dock_point)| {
691 let to_loc = Vec2::new(dock_point.x as f32, dock_point.y as f32);
692 let from_loc = Vec2::new(prev_point.x as f32, prev_point.y as f32);
693 let leg_dir = (to_loc - from_loc).normalized();
694 let leg_len = from_loc.distance(to_loc);
695 let max_target_dist =
700 leg_len - Airships::MIN_SPAWN_POINT_DIST_FROM_DOCK;
701 if target_dist < 0.0 {
707 target_dist = Airships::MIN_SPAWN_POINT_DIST_FROM_DOCK
708 + i as f32 * Airships::SPAWN_TARGET_DIST_INCREMENT;
709 }
710 while target_dist <= leg_len {
713 let spawn_point_dist = if target_dist > max_target_dist {
715 deviation += target_dist - max_target_dist;
716 max_target_dist
717 } else if target_dist < Airships::MIN_SPAWN_POINT_DIST_FROM_DOCK {
718 deviation +=
719 Airships::MIN_SPAWN_POINT_DIST_FROM_DOCK - target_dist;
720 Airships::MIN_SPAWN_POINT_DIST_FROM_DOCK
721 } else {
722 target_dist
723 };
724
725 let spawn_loc = from_loc + leg_dir * spawn_point_dist;
726 route_spawning_locations.push(AirshipSpawningLocation {
727 pos: Vec2::new(spawn_loc.x, spawn_loc.y),
728 dir: leg_dir,
729 height: Airships::CRUISE_HEIGHTS
730 [route_index % Airships::CRUISE_HEIGHTS.len()],
731 route_index,
732 leg_index,
733 });
734 airships_spawned += 1;
735 target_dist += airship_spacing;
736 }
737 target_dist -= leg_len;
738 assert!(
739 target_dist > 0.0,
740 "Target distance should not be zero or negative: {}",
741 target_dist
742 );
743 prev_point = dock_point;
744 });
745 if deviation < best_route_deviations {
746 best_route_deviations = deviation;
747 best_route_spawning_locations = route_spawning_locations.clone();
748 #[cfg(debug_assertions)]
749 {
750 best_route_iteration = i;
751 }
752 }
753 }
754 debug_airships!(
755 "Route {}: {} airships, {} spawning locations, best deviation: {}, iteration: \
756 {}",
757 route_index,
758 airship_count,
759 best_route_spawning_locations.len(),
760 best_route_deviations,
761 best_route_iteration
762 );
763 spawning_locations.extend(best_route_spawning_locations);
764 });
765 debug_airships!(
766 "Set spawning locations for {} airships of {} expected",
767 spawning_locations.len(),
768 expected_airships_count
769 );
770 if spawning_locations.len() == expected_airships_count {
771 self.spawning_locations = spawning_locations;
772 debug_airships!("Spawning locations: {:?}", self.spawning_locations);
773 } else {
774 error!(
775 "Expected {} airships, but produced only {} spawning locations.",
776 expected_airships_count,
777 spawning_locations.len()
778 );
779 }
780 }
781
782 pub fn generate_airship_routes_inner(
784 &mut self,
785 map_size_lg: &MapSizeLg,
786 seed: u32,
787 _index: Option<&Index>,
788 _sampler: Option<&WorldSim>,
789 _map_image_path: Option<&str>,
790 ) {
791 let all_dock_points = self
792 .airship_docks
793 .iter()
794 .map(|dock| Point {
795 x: dock.center.x as f64,
796 y: dock.center.y as f64,
797 })
798 .collect::<Vec<_>>();
799 debug_airships!("all_dock_points: {:?}", all_dock_points);
800
801 let triangulation = triangulate(&all_dock_points);
803
804 #[cfg(feature = "airship_maps")]
805 save_airship_routes_triangulation(
806 &triangulation,
807 &all_dock_points,
808 map_size_lg,
809 seed,
810 _index,
811 _sampler,
812 _map_image_path,
813 );
814
815 let blocks_per_chunk = 1 << TERRAIN_CHUNK_BLOCKS_LG;
818 let world_blocks = map_size_lg.chunks().map(|u| u as f32) * blocks_per_chunk as f32;
819 let max_route_leg_length = 1000.0 * world_blocks.x;
820
821 let pow2 = map_size_lg.vec().x;
833 let max_iterations = (3742931.0 * std::f32::consts::E.powf(-1.113823 * pow2 as f32))
834 .clamp(1.0, 100.0)
835 .round() as usize;
836
837 if let Some((best_segments, _, _max_seg_len, _min_spread, _iteration)) = triangulation
838 .eulerized_route_segments(
839 &all_dock_points,
840 max_iterations,
841 max_route_leg_length as f64,
842 seed,
843 )
844 {
845 #[cfg(debug_assertions)]
846 {
847 debug!("Max segment length: {}", _max_seg_len);
848 debug!("Min spread: {}", _min_spread);
849 debug!("Iteration: {}", _iteration);
850 debug!("Segments count:");
851 best_segments.iter().enumerate().for_each(|segment| {
852 debug!(" {} : {}", segment.0, segment.1.len());
853 });
854 debug!("Best segments: {:?}", best_segments);
855 #[cfg(feature = "airship_maps")]
856 if let Some(index) = _index
857 && let Some(world_sim) = _sampler
858 && let Err(e) = export_world_map(index, world_sim)
859 {
860 eprintln!("Failed to export world map: {:?}", e);
861 }
862 }
863
864 self.routes = Airships::assign_docking_platforms(
865 &best_segments,
866 all_dock_points
867 .iter()
868 .map(|p| Vec2::new(p.x as f32, p.y as f32))
869 .collect::<Vec<_>>()
870 .as_slice(),
871 );
872
873 self.calculate_spawning_locations(&all_dock_points);
875
876 #[cfg(feature = "airship_maps")]
877 save_airship_route_segments(
878 &self.routes,
879 &all_dock_points,
880 &self.spawning_locations,
881 map_size_lg,
882 seed,
883 _index,
884 _sampler,
885 _map_image_path,
886 );
887 } else {
888 eprintln!("Error - cannot eulerize the dock points.");
889 }
890 }
891
892 pub fn generate_airship_routes(&mut self, world_sim: &mut WorldSim, index: &Index) {
893 self.airship_docks = Airships::all_airshipdock_positions(&index.sites);
894
895 self.generate_airship_routes_inner(
896 &world_sim.map_size_lg(),
897 index.seed,
898 Some(index),
899 Some(world_sim),
900 None,
901 );
902 }
903
904 pub fn approach_transition_point(
950 &self,
951 dock_index: usize,
952 route_index: usize,
953 platform: AirshipDockPlatform,
954 from: Vec2<f32>,
955 ) -> Option<Vec3<f32>> {
956 if let Some(dock_pos) = self.airship_docks.get(dock_index) {
957 let docking_position = dock_pos.docking_position(platform);
958 let dir = (docking_position.xy() - from).normalized();
959 return Some(
960 (docking_position.xy() - dir * Airships::DOCKING_TRANSITION_OFFSET)
961 .with_z(docking_position.z + Airships::CRUISE_HEIGHTS[route_index]),
962 );
963 }
964 warn!(
965 "Approach point invalid, no airship dock found for docking position index {}",
966 dock_index
967 );
968 None
969 }
970
971 fn vec3_relative_eq(a: &vek::Vec3<f32>, b: &vek::Vec3<f32>, epsilon: f32) -> bool {
972 (a.x - b.x).abs() < epsilon && (a.y - b.y).abs() < epsilon && (a.z - b.z).abs() < epsilon
973 }
974
975 pub fn approach_for_route_and_leg(
976 &self,
977 route_index: usize,
978 leg_index: usize,
979 ) -> AirshipDockingApproach {
980 let to_route_leg = &self.routes[route_index][leg_index];
982 let from_route_leg = if leg_index == 0 {
983 &self.routes[route_index][self.routes[route_index].len() - 1]
984 } else {
985 &self.routes[route_index][leg_index - 1]
986 };
987 let dest_dock_positions = &self.airship_docks[to_route_leg.dest_index];
988 let from_dock_positions = &self.airship_docks[from_route_leg.dest_index];
989
990 let docking_side = AirshipDockingSide::from_dir_to_platform(
991 &(dest_dock_positions.center - from_dock_positions.center),
992 &to_route_leg.platform,
993 );
994
995 let (airship_pos, airship_direction) = Airships::airship_vec_for_docking_pos(
996 dest_dock_positions.docking_position(to_route_leg.platform),
997 dest_dock_positions.center,
998 Some(docking_side),
999 );
1000
1001 AirshipDockingApproach {
1002 airship_pos,
1003 airship_direction,
1004 dock_center: dest_dock_positions.center,
1005 height: Airships::CRUISE_HEIGHTS[route_index],
1006 approach_transition_pos: self
1007 .approach_transition_point(
1008 to_route_leg.dest_index,
1009 route_index,
1010 to_route_leg.platform,
1011 from_dock_positions.center,
1012 )
1013 .unwrap_or_else(|| {
1014 warn!(
1015 "Failed to calculate approach transition point for route {} leg {}",
1016 route_index, leg_index
1017 );
1018 dest_dock_positions.docking_position(to_route_leg.platform)
1019 }),
1020 side: docking_side,
1021 site_id: dest_dock_positions.site_id,
1022 }
1023 }
1024
1025 pub fn airship_spawning_locations(&self) -> Vec<AirshipSpawningLocation> {
1026 self.spawning_locations.clone()
1027 }
1028
1029 pub fn route_leg_departure_location(&self, route_index: usize, leg_index: usize) -> Vec2<f32> {
1031 if route_index >= self.routes.len() || leg_index >= self.routes[route_index].len() {
1032 error!("Invalid index: rt {}, leg {}", route_index, leg_index);
1033 return Vec2::zero();
1034 }
1035
1036 let prev_leg = if leg_index == 0 {
1037 &self.routes[route_index][self.routes[route_index].len() - 1]
1038 } else {
1039 &self.routes[route_index][leg_index - 1]
1040 };
1041
1042 self.airship_docks[prev_leg.dest_index]
1043 .docking_position(prev_leg.platform)
1044 .xy()
1045 }
1046
1047 pub fn airship_vec_for_docking_pos(
1057 docking_pos: Vec3<f32>,
1058 airship_dock_center: Vec2<f32>,
1059 docking_side: Option<AirshipDockingSide>,
1060 ) -> (Vec3<f32>, Dir) {
1061 let dock_side = docking_side.unwrap_or_else(|| {
1063 if rand::rng().random::<bool>() {
1064 AirshipDockingSide::Starboard
1065 } else {
1066 AirshipDockingSide::Port
1067 }
1068 });
1069 let (dock_align_to_captain, side_rotation) = if dock_side == AirshipDockingSide::Starboard {
1079 (
1080 Airships::DOCK_ALIGN_POS_STARBOARD,
1081 3.0 * std::f32::consts::FRAC_PI_2,
1082 )
1083 } else {
1084 (Airships::DOCK_ALIGN_POS_PORT, std::f32::consts::FRAC_PI_2)
1085 };
1086 let dock_pos_offset = (docking_pos - airship_dock_center).xy();
1089 let airship_dir =
1092 Dir::from_unnormalized(dock_pos_offset.rotated_z(side_rotation).with_z(0.0))
1093 .unwrap_or_default();
1094 let ship_dock_rotation =
1097 Airships::angle_between_vectors_ccw(Vec2::unit_y(), airship_dir.vec().xy());
1098 let captain_offset = dock_align_to_captain
1099 .rotated_z(ship_dock_rotation)
1100 .with_z(Airships::AIRSHIP_TO_DOCK_Z_OFFSET);
1101
1102 (docking_pos + captain_offset, airship_dir)
1105 }
1106
1107 pub fn angle_between_vectors_ccw(v1: Vec2<f32>, v2: Vec2<f32>) -> f32 {
1109 let dot_product = v1.dot(v2);
1110 let det = v1.x * v2.y - v1.y * v2.x; let angle = det.atan2(dot_product); if angle < 0.0 {
1113 angle + std::f32::consts::TAU
1114 } else {
1115 angle
1116 }
1117 }
1118
1119 pub fn angle_between_vectors_cw(v1: Vec2<f32>, v2: Vec2<f32>) -> f32 {
1121 let ccw_angle = Airships::angle_between_vectors_ccw(v1, v2);
1122 std::f32::consts::TAU - ccw_angle
1123 }
1124}
1125
1126#[cfg(debug_assertions)]
1127macro_rules! debug_airship_eulerization {
1128 ($($arg:tt)*) => {
1129 debug!($($arg)*);
1130 }
1131}
1132
1133#[cfg(not(debug_assertions))]
1134macro_rules! debug_airship_eulerization {
1135 ($($arg:tt)*) => {};
1136}
1137
1138type DockNodeGraph = DHashMap<usize, DockNode>;
1141
1142trait TriangulationExt {
1144 fn all_edges(&self) -> DHashSet<(usize, usize)>;
1145 fn is_hull_node(&self, index: usize) -> bool;
1146 fn node_connections(&self) -> DockNodeGraph;
1147 fn eulerized_route_segments(
1148 &self,
1149 all_dock_points: &[Point],
1150 iterations: usize,
1151 max_route_leg_length: f64,
1152 seed: u32,
1153 ) -> Option<(Vec<Vec<usize>>, Vec<usize>, usize, f32, usize)>;
1154}
1155
1156fn first_odd_node(
1159 search_order: &[usize],
1160 start: usize,
1161 nodes: &DockNodeGraph,
1162) -> Option<(usize, usize)> {
1163 search_order
1164 .iter()
1165 .enumerate()
1166 .skip(start)
1167 .find_map(|(index, &node_index)| {
1168 if let Some(dock_node) = nodes.get(&node_index) {
1169 if dock_node.connected.len() % 2 == 1 {
1170 Some((index, node_index))
1171 } else {
1172 None
1173 }
1174 } else {
1175 None
1176 }
1177 })
1178}
1179
1180fn remove_edge(edge: (usize, usize), nodes: &mut DockNodeGraph) {
1182 if let Some(dock_node) = nodes.get_mut(&edge.0) {
1183 if let Some(index) = dock_node
1186 .connected
1187 .iter()
1188 .position(|&node_id| node_id == edge.1)
1189 {
1190 dock_node.connected.remove(index);
1191 }
1192 }
1193 if let Some(dock_node) = nodes.get_mut(&edge.1) {
1194 if let Some(index) = dock_node
1197 .connected
1198 .iter()
1199 .position(|&node_id| node_id == edge.0)
1200 {
1201 dock_node.connected.remove(index);
1202 }
1203 }
1204}
1205
1206fn add_edge(edge: (usize, usize), nodes: &mut DockNodeGraph) {
1208 if let Some(dock_node) = nodes.get_mut(&edge.0) {
1209 dock_node.connected.push(edge.1);
1210 }
1211 if let Some(dock_node) = nodes.get_mut(&edge.1) {
1212 dock_node.connected.push(edge.0);
1213 }
1214}
1215
1216impl TriangulationExt for Triangulation {
1218 fn all_edges(&self) -> DHashSet<(usize, usize)> {
1220 let mut edges = DHashSet::default();
1221 for t in self.triangles.chunks(3) {
1222 let a = t[0];
1223 let b = t[1];
1224 let c = t[2];
1225 edges.insert(if a < b { (a, b) } else { (b, a) });
1228 edges.insert(if b < c { (b, c) } else { (c, b) });
1229 edges.insert(if a < c { (a, c) } else { (c, a) });
1230 }
1231 edges
1232 }
1233
1234 fn node_connections(&self) -> DockNodeGraph {
1237 let mut connections = DHashMap::default();
1238
1239 self.triangles.chunks(3).for_each(|t| {
1240 for &node in t {
1241 let dock_node = connections.entry(node).or_insert_with(|| DockNode {
1242 node_id: node,
1243 on_hull: self.is_hull_node(node),
1244 connected: Vec::default(),
1245 });
1246 for &connected_node in t {
1247 if connected_node != node && !dock_node.connected.contains(&connected_node) {
1248 dock_node.connected.push(connected_node);
1249 }
1250 }
1251 }
1252 });
1253 for (_, dock_node) in connections.iter_mut() {
1254 dock_node.connected = dock_node.connected.to_vec();
1255 }
1256 connections
1257 }
1258
1259 fn is_hull_node(&self, index: usize) -> bool { self.hull.contains(&index) }
1261
1262 fn eulerized_route_segments(
1291 &self,
1292 all_dock_points: &[Point],
1293 iterations: usize,
1294 max_route_leg_length: f64,
1295 seed: u32,
1296 ) -> Option<(Vec<Vec<usize>>, Vec<usize>, usize, f32, usize)> {
1297 let mut edges_to_remove = DHashSet::default();
1298
1299 let max_distance_squared = max_route_leg_length.powi(2);
1323
1324 let all_edges = self.all_edges();
1325 for edge in all_edges.iter() {
1326 let pt1 = &all_dock_points[edge.0];
1327 let pt2 = &all_dock_points[edge.1];
1328 let v1 = Vec2 { x: pt1.x, y: pt1.y };
1329 let v2 = Vec2 { x: pt2.x, y: pt2.y };
1330 if v1.distance_squared(v2) > max_distance_squared {
1333 edges_to_remove.insert(*edge);
1334 }
1335 }
1336
1337 #[cfg(debug_assertions)]
1338 let long_edges = edges_to_remove.len();
1339
1340 debug_airship_eulerization!(
1341 "Found {} long edges to remove out of {} total edges",
1342 edges_to_remove.len(),
1343 all_edges.len()
1344 );
1345
1346 let node_connections = self.node_connections();
1347 node_connections.iter().for_each(|(&node_id, node)| {
1348 if node.connected.len() > 8 {
1349 let excess_edges_count = node.connected.len() - 8;
1350 let mut connected_node_info = node
1352 .connected
1353 .iter()
1354 .map(|&connected_node_id| {
1355 let pt1 = &all_dock_points[node_id];
1356 let pt2 = &all_dock_points[connected_node_id];
1357 let v1 = Vec2 { x: pt1.x, y: pt1.y };
1358 let v2 = Vec2 { x: pt2.x, y: pt2.y };
1359 (connected_node_id, v1.distance_squared(v2) as i64)
1360 })
1361 .collect::<Vec<_>>();
1362 connected_node_info.sort_by(|a, b| a.1.cmp(&b.1));
1363 let mut excess_edges_remaining = excess_edges_count;
1364 let mut remove_index = 0;
1365 while excess_edges_remaining > 0 && remove_index < connected_node_info.len() {
1366 let (connected_node_id, _) = connected_node_info[remove_index];
1367 let edge = if node_id < connected_node_id {
1368 (node_id, connected_node_id)
1369 } else {
1370 (connected_node_id, node_id)
1371 };
1372 if !edges_to_remove.contains(&edge) {
1373 edges_to_remove.insert(edge);
1374 excess_edges_remaining -= 1;
1375 }
1376 remove_index += 1;
1377 }
1378 }
1379 });
1380
1381 let mut mutable_node_connections = node_connections.clone();
1382
1383 debug_airship_eulerization!(
1384 "Removing {} long edges and {} excess edges for a total of {} removed edges out of a \
1385 total of {} edges",
1386 long_edges,
1387 edges_to_remove.len() - long_edges,
1388 edges_to_remove.len(),
1389 all_edges.len(),
1390 );
1391
1392 for edge in edges_to_remove {
1393 remove_edge(edge, &mut mutable_node_connections);
1394 }
1395
1396 #[cfg(debug_assertions)]
1397 {
1398 let odd_connected_count0 = mutable_node_connections
1400 .iter()
1401 .filter(|(_, node)| node.connected.len() % 2 == 1)
1402 .count();
1403 let total_connections1 = mutable_node_connections
1404 .iter()
1405 .map(|(_, node)| node.connected.len())
1406 .sum::<usize>();
1407 debug_airship_eulerization!(
1408 "After Removing, odd connected count: {} in {} nodes, total connections: {}",
1409 odd_connected_count0,
1410 mutable_node_connections.len(),
1411 total_connections1
1412 );
1413 }
1414
1415 let mut odd_keys: Vec<usize> = mutable_node_connections
1423 .iter()
1424 .filter(|(_, node)| node.connected.len() % 2 == 1)
1425 .map(|(node_id, _)| *node_id)
1426 .collect();
1427
1428 let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
1429
1430 let mut best_circuit = Vec::new();
1447 let mut best_route_segments = Vec::new();
1448 let mut best_max_seg_len = 0;
1449 let mut best_min_spread = f32::MAX;
1450 let mut best_iteration = 0;
1451
1452 for i in 0..iterations {
1453 let mut eulerized_node_connections = mutable_node_connections.clone();
1456
1457 let mut odd_connected_count = odd_keys.len();
1458 assert!(
1459 odd_connected_count.is_multiple_of(2),
1460 "Odd connected count should be even, got {}",
1461 odd_connected_count
1462 );
1463 assert!(
1464 odd_keys.len()
1465 == eulerized_node_connections
1466 .iter()
1467 .filter(|(_, node)| node.connected.len() % 2 == 1)
1468 .count()
1469 );
1470
1471 if odd_connected_count > 0 {
1474 odd_keys.shuffle(&mut rng);
1475
1476 let mut edges_to_add = DHashSet::default();
1478 let mut paired_odd_nodes = DHashSet::default();
1481
1482 for node_key in odd_keys.iter() {
1483 if paired_odd_nodes.contains(node_key) {
1485 continue;
1486 }
1487 if let Some(node) = mutable_node_connections.get(node_key) {
1488 let mut closest_node_id = None;
1491 let mut closest_distance = f64::MAX;
1492 for candidate_key in odd_keys.iter() {
1493 if paired_odd_nodes.contains(candidate_key) {
1495 continue;
1496 }
1497 if let Some(candidate_node) =
1498 mutable_node_connections.get(candidate_key)
1499 {
1500 if *candidate_key != *node_key
1503 && !node.connected.contains(candidate_key)
1504 && !candidate_node.connected.contains(node_key)
1505 {
1506 let edge_to_add = if *node_key < *candidate_key {
1510 (*node_key, *candidate_key)
1511 } else {
1512 (*candidate_key, *node_key)
1513 };
1514 if !edges_to_add.contains(&edge_to_add) {
1516 let pt1 = &all_dock_points[*node_key];
1517 let pt2 = &all_dock_points[*candidate_key];
1518 let v1 = Vec2 { x: pt1.x, y: pt1.y };
1519 let v2 = Vec2 { x: pt2.x, y: pt2.y };
1520 let distance = v1.distance_squared(v2);
1521 if distance < closest_distance {
1522 closest_distance = distance;
1523 closest_node_id = Some(*candidate_key);
1524 }
1525 }
1526 }
1527 }
1528 }
1529 if closest_node_id.is_none() {
1535 for candidate_key in odd_keys.iter() {
1538 if paired_odd_nodes.contains(candidate_key) {
1540 continue;
1541 }
1542 if *candidate_key != *node_key {
1544 let edge_to_add = if *node_key < *candidate_key {
1548 (*node_key, *candidate_key)
1549 } else {
1550 (*candidate_key, *node_key)
1551 };
1552 if !edges_to_add.contains(&edge_to_add) {
1554 let pt1 = &all_dock_points[*node_key];
1555 let pt2 = &all_dock_points[*candidate_key];
1556 let v1 = Vec2 { x: pt1.x, y: pt1.y };
1557 let v2 = Vec2 { x: pt2.x, y: pt2.y };
1558 let distance = v1.distance_squared(v2);
1559 if distance < closest_distance {
1560 closest_distance = distance;
1561 closest_node_id = Some(*candidate_key);
1562 }
1563 }
1564 }
1565 }
1566 }
1567 if let Some(close_node_id) = closest_node_id {
1571 let edge_to_add = if *node_key < close_node_id {
1573 (*node_key, close_node_id)
1574 } else {
1575 (close_node_id, *node_key)
1576 };
1577 edges_to_add.insert(edge_to_add);
1578 paired_odd_nodes.insert(*node_key);
1579 paired_odd_nodes.insert(close_node_id);
1580 } else {
1581 error!("Cannot pair all odd nodes, this should not happen.");
1582 }
1583 }
1584 if edges_to_add.len() == odd_connected_count / 2 {
1585 break;
1589 }
1590 }
1591 for edge in edges_to_add {
1592 add_edge(edge, &mut eulerized_node_connections);
1593 }
1594 odd_connected_count = eulerized_node_connections
1596 .iter()
1597 .filter(|(_, node)| node.connected.len() % 2 == 1)
1598 .count();
1599
1600 #[cfg(debug_assertions)]
1601 {
1602 let total_connections = eulerized_node_connections
1603 .iter()
1604 .map(|(_, node)| node.connected.len())
1605 .sum::<usize>();
1606 debug_airship_eulerization!(
1607 "Outer Iteration: {}, After Adding, odd connected count: {} in {} nodes, \
1608 total connections: {}",
1609 i,
1610 odd_connected_count,
1611 eulerized_node_connections.len(),
1612 total_connections
1613 );
1614 }
1615 }
1616
1617 if odd_connected_count == 0 {
1620 if let Some((route_segments, circuit, max_seg_len, min_spread, _)) =
1622 find_best_eulerian_circuit(&eulerized_node_connections)
1623 {
1624 #[cfg(debug_assertions)]
1625 {
1626 debug_airship_eulerization!("Outer Iteration: {}", i);
1627 debug_airship_eulerization!("Max segment length: {}", max_seg_len);
1628 debug_airship_eulerization!("Min spread: {}", min_spread);
1629 debug_airship_eulerization!("Segments count:");
1630 route_segments.iter().enumerate().for_each(|segment| {
1631 debug_airship_eulerization!(" {} : {}", segment.0, segment.1.len());
1632 });
1633 }
1634 if max_seg_len > best_max_seg_len
1637 || (max_seg_len == best_max_seg_len && min_spread < best_min_spread)
1638 {
1639 best_circuit = circuit;
1640 best_route_segments = route_segments;
1641 best_max_seg_len = max_seg_len;
1642 best_min_spread = min_spread;
1643 best_iteration = i;
1644 }
1645 }
1646 } else {
1647 debug_airship_eulerization!(
1648 "Error, this should not happen: iteration {}, odd connected count: {} of {} \
1649 nodes, total connections: {}, SKIPPING iteration",
1650 i,
1651 odd_connected_count,
1652 eulerized_node_connections.len(),
1653 eulerized_node_connections
1654 .iter()
1655 .map(|(_, node)| node.connected.len())
1656 .sum::<usize>()
1657 );
1658 error!(
1659 "Eulerian circuit not found on iteration {}. Odd connected count is not zero, \
1660 this should not happen",
1661 i
1662 );
1663 }
1664 }
1665 #[cfg(debug_assertions)]
1666 {
1667 debug_airship_eulerization!("Max segment length: {}", best_max_seg_len);
1668 debug_airship_eulerization!("Min spread: {}", best_min_spread);
1669 debug_airship_eulerization!("Iteration: {}", best_iteration);
1670 debug_airship_eulerization!("Segments count:");
1671 best_route_segments.iter().enumerate().for_each(|segment| {
1672 debug_airship_eulerization!(" {} : {}", segment.0, segment.1.len());
1673 });
1674 }
1675
1676 if best_route_segments.is_empty() {
1677 return None;
1678 }
1679 Some((
1680 best_route_segments,
1681 best_circuit,
1682 best_max_seg_len,
1683 best_min_spread,
1684 best_iteration,
1685 ))
1686 }
1687}
1688
1689fn find_best_eulerian_circuit(
1694 graph: &DockNodeGraph,
1695) -> Option<(Vec<Vec<usize>>, Vec<usize>, usize, f32, usize)> {
1696 let mut best_circuit = Vec::new();
1697 let mut best_route_segments = Vec::new();
1698 let mut best_max_seg_len = 0;
1699 let mut best_min_spread = f32::MAX;
1700 let mut best_iteration = 0;
1701
1702 let graph_keys = graph.keys().copied().collect::<Vec<_>>();
1703
1704 for (i, &start_vertex) in graph_keys.iter().enumerate() {
1706 let mut graph = graph.clone();
1707 let mut circuit = Vec::new();
1708 let mut stack = Vec::new();
1709 let mut circuit_nodes = DHashSet::default();
1710
1711 let mut current_vertex = start_vertex;
1712
1713 while !stack.is_empty() || !graph[¤t_vertex].connected.is_empty() {
1715 if graph[¤t_vertex].connected.is_empty() {
1716 circuit.push(current_vertex);
1717 circuit_nodes.insert(current_vertex);
1718 current_vertex = stack.pop()?;
1719 } else {
1720 stack.push(current_vertex);
1721 if let Some(&next_vertex) = graph
1722 .get(¤t_vertex)?
1723 .connected
1724 .iter()
1725 .find(|&vertex| !circuit_nodes.contains(vertex))
1726 .or(graph.get(¤t_vertex)?.connected.first())
1727 {
1728 remove_edge((current_vertex, next_vertex), &mut graph);
1729 current_vertex = next_vertex;
1730 } else {
1731 return None;
1732 }
1733 }
1734 }
1735 circuit.push(current_vertex);
1736 circuit.reverse();
1737
1738 if let Some((route_segments, max_seg_len, min_spread)) =
1739 best_eulerian_circuit_segments(&graph, &circuit)
1740 && (max_seg_len > best_max_seg_len
1741 || (max_seg_len == best_max_seg_len && min_spread < best_min_spread))
1742 {
1743 best_circuit = circuit.clone();
1744 best_route_segments = route_segments;
1745 best_max_seg_len = max_seg_len;
1746 best_min_spread = min_spread;
1747 best_iteration = i;
1748 }
1749 }
1750 if best_route_segments.is_empty() {
1751 return None;
1752 }
1753 Some((
1754 best_route_segments,
1755 best_circuit,
1756 best_max_seg_len,
1757 best_min_spread,
1758 best_iteration,
1759 ))
1760}
1761
1762fn best_eulerian_circuit_segments(
1775 graph: &DockNodeGraph,
1776 circuit: &[usize],
1777) -> Option<(Vec<Vec<usize>>, usize, f32)> {
1778 let sorted_node_ids: Vec<usize> = graph
1781 .keys()
1782 .copied()
1783 .sorted_by_key(|&node_id| graph[&node_id].connected.len())
1784 .rev()
1785 .collect();
1786
1787 let mut max_segments_count = 0;
1788 let mut min_segments_len_spread = f32::MAX;
1789 let mut best_segments = Vec::new();
1790
1791 sorted_node_ids.iter().for_each(|&node_id| {
1796 let mut segments = Vec::new();
1797 let mut current_segment = Vec::new();
1798 let circuit_len = circuit.len();
1799 let mut starting_index = usize::MAX;
1800 let mut end_index = usize::MAX;
1801 let mut prev_value = usize::MAX;
1802
1803 for (index, &value) in circuit.iter().cycle().enumerate() {
1804 if value == node_id {
1805 if starting_index == usize::MAX {
1806 starting_index = index;
1807 if starting_index > 0 {
1808 end_index = index + circuit_len - 1;
1809 } else {
1810 end_index = index + circuit_len - 2;
1811 }
1812 }
1813 if !current_segment.is_empty() {
1814 current_segment.push(value);
1815 segments.push(current_segment);
1816 current_segment = Vec::new();
1817 }
1818 }
1819 if starting_index < usize::MAX {
1820 if value != prev_value {
1821 current_segment.push(value);
1822 }
1823 prev_value = value;
1824 }
1825
1826 if index == end_index {
1828 break;
1829 }
1830 }
1831
1832 if !current_segment.is_empty() {
1834 current_segment.push(node_id);
1835 segments.push(current_segment);
1836 }
1837
1838 let avg_segment_length = segments.iter().map(|segment| segment.len()).sum::<usize>() as f32
1839 / segments.len() as f32;
1840
1841 let seg_lengths_spread = segments
1844 .iter()
1845 .map(|segment| (segment.len() as f32 - avg_segment_length).powi(2))
1846 .sum::<f32>()
1847 .sqrt()
1848 / segments.len() as f32;
1849
1850 if segments.len() > max_segments_count {
1853 max_segments_count = segments.len();
1854 min_segments_len_spread = seg_lengths_spread;
1855 best_segments = segments;
1856 } else if segments.len() == max_segments_count
1857 && seg_lengths_spread < min_segments_len_spread
1858 {
1859 min_segments_len_spread = seg_lengths_spread;
1860 best_segments = segments;
1861 }
1862 });
1863 if best_segments.is_empty() {
1864 return None;
1865 }
1866 Some((best_segments, max_segments_count, min_segments_len_spread))
1867}
1868
1869#[cfg(test)]
1875pub fn airships_from_test_data() -> Airships {
1876 let mut store = Store::<Site>::default();
1877 let dummy_site = Site::default();
1878 let dummy_site_id = store.insert(dummy_site);
1879
1880 let docks = vec![
1881 AirshipDockPositions {
1882 center: Vec2 {
1883 x: 26688.0,
1884 y: 4758.0,
1885 },
1886 docking_positions: vec![
1887 AirshipDockingPosition(0, Vec3 {
1888 x: 26707.0,
1889 y: 4758.0,
1890 z: 213.0,
1891 }),
1892 AirshipDockingPosition(1, Vec3 {
1893 x: 26688.0,
1894 y: 4777.0,
1895 z: 213.0,
1896 }),
1897 AirshipDockingPosition(2, Vec3 {
1898 x: 26669.0,
1899 y: 4758.0,
1900 z: 213.0,
1901 }),
1902 AirshipDockingPosition(3, Vec3 {
1903 x: 26688.0,
1904 y: 4739.0,
1905 z: 213.0,
1906 }),
1907 ],
1908 site_id: dummy_site_id,
1909 },
1910 AirshipDockPositions {
1911 center: Vec2 {
1912 x: 24574.0,
1913 y: 26108.0,
1914 },
1915 docking_positions: vec![
1916 AirshipDockingPosition(4, Vec3 {
1917 x: 24593.0,
1918 y: 26108.0,
1919 z: 214.0,
1920 }),
1921 AirshipDockingPosition(5, Vec3 {
1922 x: 24574.0,
1923 y: 26127.0,
1924 z: 214.0,
1925 }),
1926 AirshipDockingPosition(6, Vec3 {
1927 x: 24555.0,
1928 y: 26108.0,
1929 z: 214.0,
1930 }),
1931 AirshipDockingPosition(7, Vec3 {
1932 x: 24574.0,
1933 y: 26089.0,
1934 z: 214.0,
1935 }),
1936 ],
1937 site_id: dummy_site_id,
1938 },
1939 AirshipDockPositions {
1940 center: Vec2 {
1941 x: 24253.0,
1942 y: 20715.0,
1943 },
1944 docking_positions: vec![
1945 AirshipDockingPosition(8, Vec3 {
1946 x: 24272.0,
1947 y: 20715.0,
1948 z: 515.0,
1949 }),
1950 AirshipDockingPosition(9, Vec3 {
1951 x: 24253.0,
1952 y: 20734.0,
1953 z: 515.0,
1954 }),
1955 AirshipDockingPosition(10, Vec3 {
1956 x: 24234.0,
1957 y: 20715.0,
1958 z: 515.0,
1959 }),
1960 AirshipDockingPosition(11, Vec3 {
1961 x: 24253.0,
1962 y: 20696.0,
1963 z: 515.0,
1964 }),
1965 ],
1966 site_id: dummy_site_id,
1967 },
1968 AirshipDockPositions {
1969 center: Vec2 {
1970 x: 20809.0,
1971 y: 6555.0,
1972 },
1973 docking_positions: vec![
1974 AirshipDockingPosition(12, Vec3 {
1975 x: 20828.0,
1976 y: 6555.0,
1977 z: 216.0,
1978 }),
1979 AirshipDockingPosition(13, Vec3 {
1980 x: 20809.0,
1981 y: 6574.0,
1982 z: 216.0,
1983 }),
1984 AirshipDockingPosition(14, Vec3 {
1985 x: 20790.0,
1986 y: 6555.0,
1987 z: 216.0,
1988 }),
1989 AirshipDockingPosition(15, Vec3 {
1990 x: 20809.0,
1991 y: 6536.0,
1992 z: 216.0,
1993 }),
1994 ],
1995 site_id: dummy_site_id,
1996 },
1997 AirshipDockPositions {
1998 center: Vec2 {
1999 x: 16492.0,
2000 y: 1061.0,
2001 },
2002 docking_positions: vec![
2003 AirshipDockingPosition(16, Vec3 {
2004 x: 16511.0,
2005 y: 1061.0,
2006 z: 211.0,
2007 }),
2008 AirshipDockingPosition(17, Vec3 {
2009 x: 16492.0,
2010 y: 1080.0,
2011 z: 211.0,
2012 }),
2013 AirshipDockingPosition(18, Vec3 {
2014 x: 16473.0,
2015 y: 1061.0,
2016 z: 211.0,
2017 }),
2018 AirshipDockingPosition(19, Vec3 {
2019 x: 16492.0,
2020 y: 1042.0,
2021 z: 211.0,
2022 }),
2023 ],
2024 site_id: dummy_site_id,
2025 },
2026 AirshipDockPositions {
2027 center: Vec2 {
2028 x: 18452.0,
2029 y: 11236.0,
2030 },
2031 docking_positions: vec![
2032 AirshipDockingPosition(20, Vec3 {
2033 x: 18471.0,
2034 y: 11236.0,
2035 z: 421.0,
2036 }),
2037 AirshipDockingPosition(21, Vec3 {
2038 x: 18452.0,
2039 y: 11255.0,
2040 z: 421.0,
2041 }),
2042 AirshipDockingPosition(22, Vec3 {
2043 x: 18433.0,
2044 y: 11236.0,
2045 z: 421.0,
2046 }),
2047 AirshipDockingPosition(23, Vec3 {
2048 x: 18452.0,
2049 y: 11217.0,
2050 z: 421.0,
2051 }),
2052 ],
2053 site_id: dummy_site_id,
2054 },
2055 AirshipDockPositions {
2056 center: Vec2 {
2057 x: 21870.0,
2058 y: 8530.0,
2059 },
2060 docking_positions: vec![
2061 AirshipDockingPosition(24, Vec3 {
2062 x: 21889.0,
2063 y: 8530.0,
2064 z: 216.0,
2065 }),
2066 AirshipDockingPosition(25, Vec3 {
2067 x: 21870.0,
2068 y: 8549.0,
2069 z: 216.0,
2070 }),
2071 AirshipDockingPosition(26, Vec3 {
2072 x: 21851.0,
2073 y: 8530.0,
2074 z: 216.0,
2075 }),
2076 AirshipDockingPosition(27, Vec3 {
2077 x: 21870.0,
2078 y: 8511.0,
2079 z: 216.0,
2080 }),
2081 ],
2082 site_id: dummy_site_id,
2083 },
2084 AirshipDockPositions {
2085 center: Vec2 {
2086 x: 22577.0,
2087 y: 15197.0,
2088 },
2089 docking_positions: vec![
2090 AirshipDockingPosition(28, Vec3 {
2091 x: 22605.0,
2092 y: 15197.0,
2093 z: 277.0,
2094 }),
2095 AirshipDockingPosition(29, Vec3 {
2096 x: 22577.0,
2097 y: 15225.0,
2098 z: 277.0,
2099 }),
2100 AirshipDockingPosition(30, Vec3 {
2101 x: 22549.0,
2102 y: 15197.0,
2103 z: 277.0,
2104 }),
2105 AirshipDockingPosition(31, Vec3 {
2106 x: 22577.0,
2107 y: 15169.0,
2108 z: 277.0,
2109 }),
2110 ],
2111 site_id: dummy_site_id,
2112 },
2113 AirshipDockPositions {
2114 center: Vec2 {
2115 x: 5477.0,
2116 y: 15207.0,
2117 },
2118 docking_positions: vec![
2119 AirshipDockingPosition(32, Vec3 {
2120 x: 5514.0,
2121 y: 15207.0,
2122 z: 1675.0,
2123 }),
2124 AirshipDockingPosition(33, Vec3 {
2125 x: 5477.0,
2126 y: 15244.0,
2127 z: 1675.0,
2128 }),
2129 AirshipDockingPosition(34, Vec3 {
2130 x: 5440.0,
2131 y: 15207.0,
2132 z: 1675.0,
2133 }),
2134 AirshipDockingPosition(35, Vec3 {
2135 x: 5477.0,
2136 y: 15170.0,
2137 z: 1675.0,
2138 }),
2139 ],
2140 site_id: dummy_site_id,
2141 },
2142 AirshipDockPositions {
2143 center: Vec2 {
2144 x: 23884.0,
2145 y: 24302.0,
2146 },
2147 docking_positions: vec![
2148 AirshipDockingPosition(36, Vec3 {
2149 x: 23903.0,
2150 y: 24302.0,
2151 z: 214.0,
2152 }),
2153 AirshipDockingPosition(37, Vec3 {
2154 x: 23884.0,
2155 y: 24321.0,
2156 z: 214.0,
2157 }),
2158 AirshipDockingPosition(38, Vec3 {
2159 x: 23865.0,
2160 y: 24302.0,
2161 z: 214.0,
2162 }),
2163 AirshipDockingPosition(39, Vec3 {
2164 x: 23884.0,
2165 y: 24283.0,
2166 z: 214.0,
2167 }),
2168 ],
2169 site_id: dummy_site_id,
2170 },
2171 AirshipDockPositions {
2172 center: Vec2 {
2173 x: 13373.0,
2174 y: 2313.0,
2175 },
2176 docking_positions: vec![
2177 AirshipDockingPosition(40, Vec3 {
2178 x: 13392.0,
2179 y: 2313.0,
2180 z: 259.0,
2181 }),
2182 AirshipDockingPosition(41, Vec3 {
2183 x: 13373.0,
2184 y: 2332.0,
2185 z: 259.0,
2186 }),
2187 AirshipDockingPosition(42, Vec3 {
2188 x: 13354.0,
2189 y: 2313.0,
2190 z: 259.0,
2191 }),
2192 AirshipDockingPosition(43, Vec3 {
2193 x: 13373.0,
2194 y: 2294.0,
2195 z: 259.0,
2196 }),
2197 ],
2198 site_id: dummy_site_id,
2199 },
2200 AirshipDockPositions {
2201 center: Vec2 {
2202 x: 20141.0,
2203 y: 31861.0,
2204 },
2205 docking_positions: vec![
2206 AirshipDockingPosition(44, Vec3 {
2207 x: 20160.0,
2208 y: 31861.0,
2209 z: 215.0,
2210 }),
2211 AirshipDockingPosition(45, Vec3 {
2212 x: 20141.0,
2213 y: 31880.0,
2214 z: 215.0,
2215 }),
2216 AirshipDockingPosition(46, Vec3 {
2217 x: 20122.0,
2218 y: 31861.0,
2219 z: 215.0,
2220 }),
2221 AirshipDockingPosition(47, Vec3 {
2222 x: 20141.0,
2223 y: 31842.0,
2224 z: 215.0,
2225 }),
2226 ],
2227 site_id: dummy_site_id,
2228 },
2229 AirshipDockPositions {
2230 center: Vec2 {
2231 x: 29713.0,
2232 y: 24533.0,
2233 },
2234 docking_positions: vec![
2235 AirshipDockingPosition(48, Vec3 {
2236 x: 29732.0,
2237 y: 24533.0,
2238 z: 214.0,
2239 }),
2240 AirshipDockingPosition(49, Vec3 {
2241 x: 29713.0,
2242 y: 24552.0,
2243 z: 214.0,
2244 }),
2245 AirshipDockingPosition(50, Vec3 {
2246 x: 29694.0,
2247 y: 24533.0,
2248 z: 214.0,
2249 }),
2250 AirshipDockingPosition(51, Vec3 {
2251 x: 29713.0,
2252 y: 24514.0,
2253 z: 214.0,
2254 }),
2255 ],
2256 site_id: dummy_site_id,
2257 },
2258 AirshipDockPositions {
2259 center: Vec2 {
2260 x: 18992.0,
2261 y: 17120.0,
2262 },
2263 docking_positions: vec![
2264 AirshipDockingPosition(52, Vec3 {
2265 x: 19011.0,
2266 y: 17120.0,
2267 z: 435.0,
2268 }),
2269 AirshipDockingPosition(53, Vec3 {
2270 x: 18992.0,
2271 y: 17139.0,
2272 z: 435.0,
2273 }),
2274 AirshipDockingPosition(54, Vec3 {
2275 x: 18973.0,
2276 y: 17120.0,
2277 z: 435.0,
2278 }),
2279 AirshipDockingPosition(55, Vec3 {
2280 x: 18992.0,
2281 y: 17101.0,
2282 z: 435.0,
2283 }),
2284 ],
2285 site_id: dummy_site_id,
2286 },
2287 AirshipDockPositions {
2288 center: Vec2 {
2289 x: 7705.0,
2290 y: 12533.0,
2291 },
2292 docking_positions: vec![
2293 AirshipDockingPosition(56, Vec3 {
2294 x: 7742.0,
2295 y: 12533.0,
2296 z: 1911.0,
2297 }),
2298 AirshipDockingPosition(57, Vec3 {
2299 x: 7705.0,
2300 y: 12570.0,
2301 z: 1911.0,
2302 }),
2303 AirshipDockingPosition(58, Vec3 {
2304 x: 7668.0,
2305 y: 12533.0,
2306 z: 1911.0,
2307 }),
2308 AirshipDockingPosition(59, Vec3 {
2309 x: 7705.0,
2310 y: 12496.0,
2311 z: 1911.0,
2312 }),
2313 ],
2314 site_id: dummy_site_id,
2315 },
2316 AirshipDockPositions {
2317 center: Vec2 {
2318 x: 30365.0,
2319 y: 12987.0,
2320 },
2321 docking_positions: vec![
2322 AirshipDockingPosition(60, Vec3 {
2323 x: 30393.0,
2324 y: 12987.0,
2325 z: 244.0,
2326 }),
2327 AirshipDockingPosition(61, Vec3 {
2328 x: 30365.0,
2329 y: 13015.0,
2330 z: 244.0,
2331 }),
2332 AirshipDockingPosition(62, Vec3 {
2333 x: 30337.0,
2334 y: 12987.0,
2335 z: 244.0,
2336 }),
2337 AirshipDockingPosition(63, Vec3 {
2338 x: 30365.0,
2339 y: 12959.0,
2340 z: 244.0,
2341 }),
2342 ],
2343 site_id: dummy_site_id,
2344 },
2345 AirshipDockPositions {
2346 center: Vec2 {
2347 x: 10142.0,
2348 y: 19190.0,
2349 },
2350 docking_positions: vec![
2351 AirshipDockingPosition(64, Vec3 {
2352 x: 10170.0,
2353 y: 19190.0,
2354 z: 1141.0,
2355 }),
2356 AirshipDockingPosition(65, Vec3 {
2357 x: 10142.0,
2358 y: 19218.0,
2359 z: 1141.0,
2360 }),
2361 AirshipDockingPosition(66, Vec3 {
2362 x: 10114.0,
2363 y: 19190.0,
2364 z: 1141.0,
2365 }),
2366 AirshipDockingPosition(67, Vec3 {
2367 x: 10142.0,
2368 y: 19162.0,
2369 z: 1141.0,
2370 }),
2371 ],
2372 site_id: dummy_site_id,
2373 },
2374 AirshipDockPositions {
2375 center: Vec2 {
2376 x: 13716.0,
2377 y: 17505.0,
2378 },
2379 docking_positions: vec![
2380 AirshipDockingPosition(68, Vec3 {
2381 x: 13753.0,
2382 y: 17505.0,
2383 z: 1420.0,
2384 }),
2385 AirshipDockingPosition(69, Vec3 {
2386 x: 13716.0,
2387 y: 17542.0,
2388 z: 1420.0,
2389 }),
2390 AirshipDockingPosition(70, Vec3 {
2391 x: 13679.0,
2392 y: 17505.0,
2393 z: 1420.0,
2394 }),
2395 AirshipDockingPosition(71, Vec3 {
2396 x: 13716.0,
2397 y: 17468.0,
2398 z: 1420.0,
2399 }),
2400 ],
2401 site_id: dummy_site_id,
2402 },
2403 AirshipDockPositions {
2404 center: Vec2 {
2405 x: 9383.0,
2406 y: 17145.0,
2407 },
2408 docking_positions: vec![
2409 AirshipDockingPosition(72, Vec3 {
2410 x: 9411.0,
2411 y: 17145.0,
2412 z: 909.0,
2413 }),
2414 AirshipDockingPosition(73, Vec3 {
2415 x: 9383.0,
2416 y: 17173.0,
2417 z: 909.0,
2418 }),
2419 AirshipDockingPosition(74, Vec3 {
2420 x: 9355.0,
2421 y: 17145.0,
2422 z: 909.0,
2423 }),
2424 AirshipDockingPosition(75, Vec3 {
2425 x: 9383.0,
2426 y: 17117.0,
2427 z: 909.0,
2428 }),
2429 ],
2430 site_id: dummy_site_id,
2431 },
2432 AirshipDockPositions {
2433 center: Vec2 {
2434 x: 24424.0,
2435 y: 7800.0,
2436 },
2437 docking_positions: vec![
2438 AirshipDockingPosition(76, Vec3 {
2439 x: 24443.0,
2440 y: 7800.0,
2441 z: 329.0,
2442 }),
2443 AirshipDockingPosition(77, Vec3 {
2444 x: 24424.0,
2445 y: 7819.0,
2446 z: 329.0,
2447 }),
2448 AirshipDockingPosition(78, Vec3 {
2449 x: 24405.0,
2450 y: 7800.0,
2451 z: 329.0,
2452 }),
2453 AirshipDockingPosition(79, Vec3 {
2454 x: 24424.0,
2455 y: 7781.0,
2456 z: 329.0,
2457 }),
2458 ],
2459 site_id: dummy_site_id,
2460 },
2461 AirshipDockPositions {
2462 center: Vec2 {
2463 x: 7528.0,
2464 y: 28426.0,
2465 },
2466 docking_positions: vec![
2467 AirshipDockingPosition(80, Vec3 {
2468 x: 7547.0,
2469 y: 28426.0,
2470 z: 218.0,
2471 }),
2472 AirshipDockingPosition(81, Vec3 {
2473 x: 7528.0,
2474 y: 28445.0,
2475 z: 218.0,
2476 }),
2477 AirshipDockingPosition(82, Vec3 {
2478 x: 7509.0,
2479 y: 28426.0,
2480 z: 218.0,
2481 }),
2482 AirshipDockingPosition(83, Vec3 {
2483 x: 7528.0,
2484 y: 28407.0,
2485 z: 218.0,
2486 }),
2487 ],
2488 site_id: dummy_site_id,
2489 },
2490 AirshipDockPositions {
2491 center: Vec2 {
2492 x: 9942.0,
2493 y: 30936.0,
2494 },
2495 docking_positions: vec![
2496 AirshipDockingPosition(84, Vec3 {
2497 x: 9961.0,
2498 y: 30936.0,
2499 z: 185.0,
2500 }),
2501 AirshipDockingPosition(85, Vec3 {
2502 x: 9942.0,
2503 y: 30955.0,
2504 z: 185.0,
2505 }),
2506 AirshipDockingPosition(86, Vec3 {
2507 x: 9923.0,
2508 y: 30936.0,
2509 z: 185.0,
2510 }),
2511 AirshipDockingPosition(87, Vec3 {
2512 x: 9942.0,
2513 y: 30917.0,
2514 z: 185.0,
2515 }),
2516 ],
2517 site_id: dummy_site_id,
2518 },
2519 AirshipDockPositions {
2520 center: Vec2 {
2521 x: 27915.0,
2522 y: 18559.0,
2523 },
2524 docking_positions: vec![
2525 AirshipDockingPosition(88, Vec3 {
2526 x: 27934.0,
2527 y: 18559.0,
2528 z: 498.0,
2529 }),
2530 AirshipDockingPosition(89, Vec3 {
2531 x: 27915.0,
2532 y: 18578.0,
2533 z: 498.0,
2534 }),
2535 AirshipDockingPosition(90, Vec3 {
2536 x: 27896.0,
2537 y: 18559.0,
2538 z: 498.0,
2539 }),
2540 AirshipDockingPosition(91, Vec3 {
2541 x: 27915.0,
2542 y: 18540.0,
2543 z: 498.0,
2544 }),
2545 ],
2546 site_id: dummy_site_id,
2547 },
2548 AirshipDockPositions {
2549 center: Vec2 {
2550 x: 3688.0,
2551 y: 29168.0,
2552 },
2553 docking_positions: vec![
2554 AirshipDockingPosition(92, Vec3 {
2555 x: 3711.0,
2556 y: 29168.0,
2557 z: 198.0,
2558 }),
2559 AirshipDockingPosition(93, Vec3 {
2560 x: 3688.0,
2561 y: 29191.0,
2562 z: 198.0,
2563 }),
2564 AirshipDockingPosition(94, Vec3 {
2565 x: 3665.0,
2566 y: 29168.0,
2567 z: 198.0,
2568 }),
2569 AirshipDockingPosition(95, Vec3 {
2570 x: 3688.0,
2571 y: 29145.0,
2572 z: 198.0,
2573 }),
2574 ],
2575 site_id: dummy_site_id,
2576 },
2577 AirshipDockPositions {
2578 center: Vec2 {
2579 x: 15864.0,
2580 y: 15584.0,
2581 },
2582 docking_positions: vec![
2583 AirshipDockingPosition(96, Vec3 {
2584 x: 15892.0,
2585 y: 15584.0,
2586 z: 419.0,
2587 }),
2588 AirshipDockingPosition(97, Vec3 {
2589 x: 15864.0,
2590 y: 15612.0,
2591 z: 419.0,
2592 }),
2593 AirshipDockingPosition(98, Vec3 {
2594 x: 15836.0,
2595 y: 15584.0,
2596 z: 419.0,
2597 }),
2598 AirshipDockingPosition(99, Vec3 {
2599 x: 15864.0,
2600 y: 15556.0,
2601 z: 419.0,
2602 }),
2603 ],
2604 site_id: dummy_site_id,
2605 },
2606 AirshipDockPositions {
2607 center: Vec2 {
2608 x: 9975.0,
2609 y: 24289.0,
2610 },
2611 docking_positions: vec![
2612 AirshipDockingPosition(100, Vec3 {
2613 x: 10012.0,
2614 y: 24289.0,
2615 z: 755.0,
2616 }),
2617 AirshipDockingPosition(101, Vec3 {
2618 x: 9975.0,
2619 y: 24326.0,
2620 z: 755.0,
2621 }),
2622 AirshipDockingPosition(102, Vec3 {
2623 x: 9938.0,
2624 y: 24289.0,
2625 z: 755.0,
2626 }),
2627 AirshipDockingPosition(103, Vec3 {
2628 x: 9975.0,
2629 y: 24252.0,
2630 z: 755.0,
2631 }),
2632 ],
2633 site_id: dummy_site_id,
2634 },
2635 AirshipDockPositions {
2636 center: Vec2 {
2637 x: 479.0,
2638 y: 18279.0,
2639 },
2640 docking_positions: vec![
2641 AirshipDockingPosition(104, Vec3 {
2642 x: 516.0,
2643 y: 18279.0,
2644 z: 449.0,
2645 }),
2646 AirshipDockingPosition(105, Vec3 {
2647 x: 479.0,
2648 y: 18316.0,
2649 z: 449.0,
2650 }),
2651 AirshipDockingPosition(106, Vec3 {
2652 x: 442.0,
2653 y: 18279.0,
2654 z: 449.0,
2655 }),
2656 AirshipDockingPosition(107, Vec3 {
2657 x: 479.0,
2658 y: 18242.0,
2659 z: 449.0,
2660 }),
2661 ],
2662 site_id: dummy_site_id,
2663 },
2664 AirshipDockPositions {
2665 center: Vec2 {
2666 x: 26543.0,
2667 y: 17175.0,
2668 },
2669 docking_positions: vec![
2670 AirshipDockingPosition(108, Vec3 {
2671 x: 26566.0,
2672 y: 17175.0,
2673 z: 362.0,
2674 }),
2675 AirshipDockingPosition(109, Vec3 {
2676 x: 26543.0,
2677 y: 17198.0,
2678 z: 362.0,
2679 }),
2680 AirshipDockingPosition(110, Vec3 {
2681 x: 26520.0,
2682 y: 17175.0,
2683 z: 362.0,
2684 }),
2685 AirshipDockingPosition(111, Vec3 {
2686 x: 26543.0,
2687 y: 17152.0,
2688 z: 362.0,
2689 }),
2690 ],
2691 site_id: dummy_site_id,
2692 },
2693 ];
2694
2695 let routes = vec![
2696 vec![
2697 AirshipRouteLeg {
2698 dest_index: 13,
2699 platform: AirshipDockPlatform::SouthPlatform,
2700 },
2701 AirshipRouteLeg {
2702 dest_index: 24,
2703 platform: AirshipDockPlatform::EastPlatform,
2704 },
2705 AirshipRouteLeg {
2706 dest_index: 17,
2707 platform: AirshipDockPlatform::EastPlatform,
2708 },
2709 AirshipRouteLeg {
2710 dest_index: 13,
2711 platform: AirshipDockPlatform::WestPlatform,
2712 },
2713 AirshipRouteLeg {
2714 dest_index: 9,
2715 platform: AirshipDockPlatform::SouthPlatform,
2716 },
2717 AirshipRouteLeg {
2718 dest_index: 2,
2719 platform: AirshipDockPlatform::NorthPlatform,
2720 },
2721 AirshipRouteLeg {
2722 dest_index: 13,
2723 platform: AirshipDockPlatform::EastPlatform,
2724 },
2725 AirshipRouteLeg {
2726 dest_index: 7,
2727 platform: AirshipDockPlatform::WestPlatform,
2728 },
2729 AirshipRouteLeg {
2730 dest_index: 2,
2731 platform: AirshipDockPlatform::SouthPlatform,
2732 },
2733 AirshipRouteLeg {
2734 dest_index: 22,
2735 platform: AirshipDockPlatform::WestPlatform,
2736 },
2737 AirshipRouteLeg {
2738 dest_index: 27,
2739 platform: AirshipDockPlatform::NorthPlatform,
2740 },
2741 AirshipRouteLeg {
2742 dest_index: 2,
2743 platform: AirshipDockPlatform::EastPlatform,
2744 },
2745 AirshipRouteLeg {
2746 dest_index: 12,
2747 platform: AirshipDockPlatform::WestPlatform,
2748 },
2749 AirshipRouteLeg {
2750 dest_index: 22,
2751 platform: AirshipDockPlatform::NorthPlatform,
2752 },
2753 AirshipRouteLeg {
2754 dest_index: 15,
2755 platform: AirshipDockPlatform::NorthPlatform,
2756 },
2757 AirshipRouteLeg {
2758 dest_index: 19,
2759 platform: AirshipDockPlatform::EastPlatform,
2760 },
2761 AirshipRouteLeg {
2762 dest_index: 6,
2763 platform: AirshipDockPlatform::EastPlatform,
2764 },
2765 AirshipRouteLeg {
2766 dest_index: 5,
2767 platform: AirshipDockPlatform::EastPlatform,
2768 },
2769 ],
2770 vec![
2771 AirshipRouteLeg {
2772 dest_index: 24,
2773 platform: AirshipDockPlatform::SouthPlatform,
2774 },
2775 AirshipRouteLeg {
2776 dest_index: 14,
2777 platform: AirshipDockPlatform::EastPlatform,
2778 },
2779 AirshipRouteLeg {
2780 dest_index: 18,
2781 platform: AirshipDockPlatform::SouthPlatform,
2782 },
2783 AirshipRouteLeg {
2784 dest_index: 16,
2785 platform: AirshipDockPlatform::SouthPlatform,
2786 },
2787 AirshipRouteLeg {
2788 dest_index: 17,
2789 platform: AirshipDockPlatform::WestPlatform,
2790 },
2791 AirshipRouteLeg {
2792 dest_index: 18,
2793 platform: AirshipDockPlatform::EastPlatform,
2794 },
2795 AirshipRouteLeg {
2796 dest_index: 8,
2797 platform: AirshipDockPlatform::EastPlatform,
2798 },
2799 AirshipRouteLeg {
2800 dest_index: 16,
2801 platform: AirshipDockPlatform::WestPlatform,
2802 },
2803 AirshipRouteLeg {
2804 dest_index: 26,
2805 platform: AirshipDockPlatform::EastPlatform,
2806 },
2807 AirshipRouteLeg {
2808 dest_index: 25,
2809 platform: AirshipDockPlatform::WestPlatform,
2810 },
2811 AirshipRouteLeg {
2812 dest_index: 13,
2813 platform: AirshipDockPlatform::NorthPlatform,
2814 },
2815 AirshipRouteLeg {
2816 dest_index: 11,
2817 platform: AirshipDockPlatform::SouthPlatform,
2818 },
2819 AirshipRouteLeg {
2820 dest_index: 1,
2821 platform: AirshipDockPlatform::NorthPlatform,
2822 },
2823 AirshipRouteLeg {
2824 dest_index: 9,
2825 platform: AirshipDockPlatform::NorthPlatform,
2826 },
2827 AirshipRouteLeg {
2828 dest_index: 25,
2829 platform: AirshipDockPlatform::EastPlatform,
2830 },
2831 AirshipRouteLeg {
2832 dest_index: 17,
2833 platform: AirshipDockPlatform::NorthPlatform,
2834 },
2835 AirshipRouteLeg {
2836 dest_index: 14,
2837 platform: AirshipDockPlatform::NorthPlatform,
2838 },
2839 AirshipRouteLeg {
2840 dest_index: 5,
2841 platform: AirshipDockPlatform::WestPlatform,
2842 },
2843 ],
2844 vec![
2845 AirshipRouteLeg {
2846 dest_index: 10,
2847 platform: AirshipDockPlatform::NorthPlatform,
2848 },
2849 AirshipRouteLeg {
2850 dest_index: 14,
2851 platform: AirshipDockPlatform::SouthPlatform,
2852 },
2853 AirshipRouteLeg {
2854 dest_index: 8,
2855 platform: AirshipDockPlatform::SouthPlatform,
2856 },
2857 AirshipRouteLeg {
2858 dest_index: 26,
2859 platform: AirshipDockPlatform::SouthPlatform,
2860 },
2861 AirshipRouteLeg {
2862 dest_index: 14,
2863 platform: AirshipDockPlatform::WestPlatform,
2864 },
2865 AirshipRouteLeg {
2866 dest_index: 16,
2867 platform: AirshipDockPlatform::EastPlatform,
2868 },
2869 AirshipRouteLeg {
2870 dest_index: 25,
2871 platform: AirshipDockPlatform::SouthPlatform,
2872 },
2873 AirshipRouteLeg {
2874 dest_index: 23,
2875 platform: AirshipDockPlatform::EastPlatform,
2876 },
2877 AirshipRouteLeg {
2878 dest_index: 20,
2879 platform: AirshipDockPlatform::WestPlatform,
2880 },
2881 AirshipRouteLeg {
2882 dest_index: 21,
2883 platform: AirshipDockPlatform::SouthPlatform,
2884 },
2885 AirshipRouteLeg {
2886 dest_index: 11,
2887 platform: AirshipDockPlatform::WestPlatform,
2888 },
2889 AirshipRouteLeg {
2890 dest_index: 9,
2891 platform: AirshipDockPlatform::WestPlatform,
2892 },
2893 AirshipRouteLeg {
2894 dest_index: 12,
2895 platform: AirshipDockPlatform::SouthPlatform,
2896 },
2897 AirshipRouteLeg {
2898 dest_index: 1,
2899 platform: AirshipDockPlatform::EastPlatform,
2900 },
2901 AirshipRouteLeg {
2902 dest_index: 7,
2903 platform: AirshipDockPlatform::NorthPlatform,
2904 },
2905 AirshipRouteLeg {
2906 dest_index: 27,
2907 platform: AirshipDockPlatform::WestPlatform,
2908 },
2909 AirshipRouteLeg {
2910 dest_index: 15,
2911 platform: AirshipDockPlatform::WestPlatform,
2912 },
2913 AirshipRouteLeg {
2914 dest_index: 7,
2915 platform: AirshipDockPlatform::EastPlatform,
2916 },
2917 AirshipRouteLeg {
2918 dest_index: 19,
2919 platform: AirshipDockPlatform::NorthPlatform,
2920 },
2921 AirshipRouteLeg {
2922 dest_index: 3,
2923 platform: AirshipDockPlatform::EastPlatform,
2924 },
2925 AirshipRouteLeg {
2926 dest_index: 5,
2927 platform: AirshipDockPlatform::SouthPlatform,
2928 },
2929 ],
2930 vec![
2931 AirshipRouteLeg {
2932 dest_index: 4,
2933 platform: AirshipDockPlatform::NorthPlatform,
2934 },
2935 AirshipRouteLeg {
2936 dest_index: 10,
2937 platform: AirshipDockPlatform::EastPlatform,
2938 },
2939 AirshipRouteLeg {
2940 dest_index: 3,
2941 platform: AirshipDockPlatform::WestPlatform,
2942 },
2943 AirshipRouteLeg {
2944 dest_index: 4,
2945 platform: AirshipDockPlatform::EastPlatform,
2946 },
2947 AirshipRouteLeg {
2948 dest_index: 0,
2949 platform: AirshipDockPlatform::WestPlatform,
2950 },
2951 AirshipRouteLeg {
2952 dest_index: 15,
2953 platform: AirshipDockPlatform::SouthPlatform,
2954 },
2955 AirshipRouteLeg {
2956 dest_index: 12,
2957 platform: AirshipDockPlatform::EastPlatform,
2958 },
2959 AirshipRouteLeg {
2960 dest_index: 11,
2961 platform: AirshipDockPlatform::EastPlatform,
2962 },
2963 AirshipRouteLeg {
2964 dest_index: 25,
2965 platform: AirshipDockPlatform::NorthPlatform,
2966 },
2967 AirshipRouteLeg {
2968 dest_index: 21,
2969 platform: AirshipDockPlatform::EastPlatform,
2970 },
2971 AirshipRouteLeg {
2972 dest_index: 23,
2973 platform: AirshipDockPlatform::NorthPlatform,
2974 },
2975 AirshipRouteLeg {
2976 dest_index: 26,
2977 platform: AirshipDockPlatform::NorthPlatform,
2978 },
2979 AirshipRouteLeg {
2980 dest_index: 10,
2981 platform: AirshipDockPlatform::WestPlatform,
2982 },
2983 AirshipRouteLeg {
2984 dest_index: 19,
2985 platform: AirshipDockPlatform::WestPlatform,
2986 },
2987 AirshipRouteLeg {
2988 dest_index: 0,
2989 platform: AirshipDockPlatform::NorthPlatform,
2990 },
2991 AirshipRouteLeg {
2992 dest_index: 3,
2993 platform: AirshipDockPlatform::SouthPlatform,
2994 },
2995 AirshipRouteLeg {
2996 dest_index: 6,
2997 platform: AirshipDockPlatform::SouthPlatform,
2998 },
2999 AirshipRouteLeg {
3000 dest_index: 7,
3001 platform: AirshipDockPlatform::SouthPlatform,
3002 },
3003 AirshipRouteLeg {
3004 dest_index: 5,
3005 platform: AirshipDockPlatform::NorthPlatform,
3006 },
3007 ],
3008 ];
3009
3010 Airships {
3011 airship_docks: docks,
3012 routes,
3013 ..Default::default()
3014 }
3015}
3016
3017#[cfg(test)]
3018mod tests {
3019 use super::{
3020 AirshipDockPlatform, AirshipDockingSide, Airships, DockNode, TriangulationExt,
3021 airships_from_test_data, approx::assert_relative_eq, find_best_eulerian_circuit,
3022 remove_edge,
3023 };
3024 use crate::util::{DHashMap, DHashSet};
3025 use delaunator::{Point, triangulate};
3026 use vek::{Quaternion, Vec2, Vec3};
3027
3028 #[test]
3029 fn basic_vec_test() {
3030 let vec1 = Vec3::new(0.0f32, 10.0, 0.0);
3031 let vec2 = Vec3::new(10.0, 0.0, 0.0);
3032 let a12 = vec2.angle_between(vec1);
3033 assert_relative_eq!(a12, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3034
3035 let rotc2 = Quaternion::rotation_z(a12);
3036 let vec3 = rotc2 * vec2;
3037 assert!(vec3 == vec1);
3038 }
3039
3040 #[test]
3041 fn std_vec_angles_test() {
3042 let refvec = Vec2::new(0.0f32, 10.0);
3043
3044 let vec1 = Vec2::new(0.0f32, 10.0);
3045 let vec2 = Vec2::new(10.0f32, 0.0);
3046 let vec3 = Vec2::new(0.0f32, -10.0);
3047 let vec4 = Vec2::new(-10.0f32, 0.0);
3048
3049 let a1r = vec1.angle_between(refvec);
3050 assert!(a1r == 0.0f32);
3051
3052 let a2r = vec2.angle_between(refvec);
3053 assert_relative_eq!(a2r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3054
3055 let a3r: f32 = vec3.angle_between(refvec);
3056 assert_relative_eq!(a3r, std::f32::consts::PI, epsilon = 0.00001);
3057
3058 let a4r = vec4.angle_between(refvec);
3059 assert_relative_eq!(a4r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3060 }
3061
3062 #[test]
3063 fn vec_angles_test() {
3064 let refvec = Vec3::new(0.0f32, 10.0, 0.0);
3065
3066 let vec1 = Vec3::new(0.0f32, 10.0, 0.0);
3067 let vec2 = Vec3::new(10.0f32, 0.0, 0.0);
3068 let vec3 = Vec3::new(0.0f32, -10.0, 0.0);
3069 let vec4 = Vec3::new(-10.0f32, 0.0, 0.0);
3070
3071 let a1r = vec1.angle_between(refvec);
3072 let a1r3 = Airships::angle_between_vectors_ccw(vec1.xy(), refvec.xy());
3073 assert!(a1r == 0.0f32);
3074 assert!(a1r3 == 0.0f32);
3075
3076 let a2r = vec2.angle_between(refvec);
3077 let a2r3 = Airships::angle_between_vectors_ccw(vec2.xy(), refvec.xy());
3078 assert_relative_eq!(a2r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3079 assert_relative_eq!(a2r3, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3080
3081 let a3r: f32 = vec3.angle_between(refvec);
3082 let a3r3 = Airships::angle_between_vectors_ccw(vec3.xy(), refvec.xy());
3083 assert_relative_eq!(a3r, std::f32::consts::PI, epsilon = 0.00001);
3084 assert_relative_eq!(a3r3, std::f32::consts::PI, epsilon = 0.00001);
3085
3086 let a4r = vec4.angle_between(refvec);
3087 let a4r3 = Airships::angle_between_vectors_ccw(vec4.xy(), refvec.xy());
3088 assert_relative_eq!(a4r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
3089 assert_relative_eq!(a4r3, std::f32::consts::FRAC_PI_2 * 3.0, epsilon = 0.00001);
3090 }
3091
3092 #[test]
3093 fn airship_angles_test() {
3094 let refvec = Vec2::new(0.0f32, 37.0);
3095 let ovec = Vec2::new(-4.0f32, -14.0);
3096 let oveccw0 = Vec2::new(-4, -14);
3097 let oveccw90 = Vec2::new(-14, 4);
3098 let oveccw180 = Vec2::new(4, 14);
3099 let oveccw270 = Vec2::new(14, -4);
3100 let ovecccw0 = Vec2::new(-4, -14);
3101 let ovecccw90 = Vec2::new(14, -4);
3102 let ovecccw180 = Vec2::new(4, 14);
3103 let ovecccw270 = Vec2::new(-14, 4);
3104
3105 let vec1 = Vec2::new(0.0f32, 37.0);
3106 let vec2 = Vec2::new(37.0f32, 0.0);
3107 let vec3 = Vec2::new(0.0f32, -37.0);
3108 let vec4 = Vec2::new(-37.0f32, 0.0);
3109
3110 assert!(
3111 ovec.rotated_z(Airships::angle_between_vectors_cw(vec1, refvec))
3112 .map(|x| x.round() as i32)
3113 == oveccw0
3114 );
3115 assert!(
3116 ovec.rotated_z(Airships::angle_between_vectors_cw(vec2, refvec))
3117 .map(|x| x.round() as i32)
3118 == oveccw90
3119 );
3120 assert!(
3121 ovec.rotated_z(Airships::angle_between_vectors_cw(vec3, refvec))
3122 .map(|x| x.round() as i32)
3123 == oveccw180
3124 );
3125 assert!(
3126 ovec.rotated_z(Airships::angle_between_vectors_cw(vec4, refvec))
3127 .map(|x| x.round() as i32)
3128 == oveccw270
3129 );
3130
3131 assert!(
3132 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec1, refvec))
3133 .map(|x| x.round() as i32)
3134 == ovecccw0
3135 );
3136 assert!(
3137 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec2, refvec))
3138 .map(|x| x.round() as i32)
3139 == ovecccw90
3140 );
3141 assert!(
3142 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec3, refvec))
3143 .map(|x| x.round() as i32)
3144 == ovecccw180
3145 );
3146 assert!(
3147 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec4, refvec))
3148 .map(|x| x.round() as i32)
3149 == ovecccw270
3150 );
3151 }
3152
3153 #[test]
3154 fn airship_vec_test() {
3155 {
3156 let dock_pos = Vec3::new(10.0f32, 10.0, 0.0);
3157 let airship_dock_center = Vec2::new(0.0, 0.0);
3158 let mut left_tested = false;
3159 let mut right_tested = false;
3160 {
3161 for _ in 0..1000 {
3162 let (airship_pos, airship_dir) =
3163 Airships::airship_vec_for_docking_pos(dock_pos, airship_dock_center, None);
3164 if airship_pos.x > 23.0 {
3165 assert_relative_eq!(
3166 airship_pos,
3167 Vec3 {
3168 x: 23.435028,
3169 y: 22.020815,
3170 z: -3.0
3171 },
3172 epsilon = 0.00001
3173 );
3174 assert_relative_eq!(
3175 airship_dir.to_vec(),
3176 Vec3 {
3177 x: -0.70710677,
3178 y: 0.70710677,
3179 z: 0.0
3180 },
3181 epsilon = 0.00001
3182 );
3183 left_tested = true;
3184 } else {
3185 assert_relative_eq!(
3186 airship_pos,
3187 Vec3 {
3188 x: 22.020815,
3189 y: 23.435028,
3190 z: -3.0
3191 },
3192 epsilon = 0.00001
3193 );
3194 assert_relative_eq!(
3195 airship_dir.to_vec(),
3196 Vec3 {
3197 x: 0.70710677,
3198 y: -0.70710677,
3199 z: 0.0
3200 },
3201 epsilon = 0.00001
3202 );
3203 right_tested = true;
3204 }
3205 if left_tested && right_tested {
3206 break;
3207 }
3208 }
3209 }
3210 {
3211 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
3212 dock_pos,
3213 airship_dock_center,
3214 Some(AirshipDockingSide::Port),
3215 );
3216 assert_relative_eq!(
3217 airship_pos,
3218 Vec3 {
3219 x: 23.435028,
3220 y: 22.020815,
3221 z: -3.0
3222 },
3223 epsilon = 0.00001
3224 );
3225 assert_relative_eq!(
3226 airship_dir.to_vec(),
3227 Vec3 {
3228 x: -0.70710677,
3229 y: 0.70710677,
3230 z: 0.0
3231 },
3232 epsilon = 0.00001
3233 );
3234 }
3235 {
3236 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
3237 dock_pos,
3238 airship_dock_center,
3239 Some(AirshipDockingSide::Starboard),
3240 );
3241 assert_relative_eq!(
3242 airship_pos,
3243 Vec3 {
3244 x: 22.020815,
3245 y: 23.435028,
3246 z: -3.0
3247 },
3248 epsilon = 0.00001
3249 );
3250 assert_relative_eq!(
3251 airship_dir.to_vec(),
3252 Vec3 {
3253 x: 0.70710677,
3254 y: -0.70710677,
3255 z: 0.0
3256 },
3257 epsilon = 0.00001
3258 );
3259 }
3260 }
3261 {
3262 let dock_pos = Vec3::new(28874.0, 18561.0, 0.0);
3263 let airship_dock_center = Vec2::new(28911.0, 18561.0);
3264 {
3265 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
3266 dock_pos,
3267 airship_dock_center,
3268 Some(AirshipDockingSide::Port),
3269 );
3270 assert_relative_eq!(
3271 airship_pos,
3272 Vec3 {
3273 x: 28856.0,
3274 y: 18562.0,
3275 z: -3.0
3276 },
3277 epsilon = 0.00001
3278 );
3279 assert_relative_eq!(
3280 airship_dir.to_vec(),
3281 Vec3 {
3282 x: 4.371139e-8,
3283 y: -1.0,
3284 z: 0.0
3285 },
3286 epsilon = 0.00001
3287 );
3288 }
3289 {
3290 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
3291 dock_pos,
3292 airship_dock_center,
3293 Some(AirshipDockingSide::Starboard),
3294 );
3295 assert_relative_eq!(
3296 airship_pos,
3297 Vec3 {
3298 x: 28856.0,
3299 y: 18560.0,
3300 z: -3.0
3301 },
3302 epsilon = 0.00001
3303 );
3304 assert_relative_eq!(
3305 airship_dir.to_vec(),
3306 Vec3 {
3307 x: -1.1924881e-8,
3308 y: 1.0,
3309 z: 0.0
3310 },
3311 epsilon = 0.00001
3312 );
3313 }
3314 }
3315 }
3316
3317 #[test]
3318 fn angle_score_test() {
3319 let rt_angles = [
3320 0.0,
3321 std::f32::consts::FRAC_PI_2,
3322 std::f32::consts::PI,
3323 std::f32::consts::FRAC_PI_2 * 3.0,
3324 ];
3325 let con_angles = [
3326 0.0,
3327 std::f32::consts::FRAC_PI_2,
3328 std::f32::consts::PI,
3329 std::f32::consts::FRAC_PI_2 * 3.0,
3330 ];
3331 let scores = [
3332 [0.0, 2.5, 5.0, 2.5],
3333 [2.5, 0.0, 2.5, 5.0],
3334 [5.0, 2.5, 0.0, 2.5],
3335 [2.5, 5.0, 2.5, 0.0],
3336 ];
3337 let score_fn2 = |a1: f32, a2: f32| {
3338 let optimal_angle = (a1 + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU);
3339 let angle_diff = (optimal_angle - a2)
3340 .abs()
3341 .min(std::f32::consts::TAU - (optimal_angle - a2).abs());
3342 (1.0 - (angle_diff / std::f32::consts::PI)) * 5.0
3343 };
3344 let mut i = 0;
3345 let mut j = 0;
3346 rt_angles.iter().for_each(|rt_angle| {
3347 j = 0;
3348 con_angles.iter().for_each(|con_angle| {
3349 let score = score_fn2(*con_angle, *rt_angle);
3350 assert_relative_eq!(score, scores[i][j], epsilon = 0.00001);
3351 j += 1;
3352 });
3353 i += 1;
3354 });
3355 }
3356
3357 #[test]
3358 fn best_eulerian_circuit_test() {
3359 let node_connections: DHashMap<usize, DockNode> = DHashMap::from_iter([
3360 (0, DockNode {
3361 node_id: 0,
3362 on_hull: false,
3363 connected: Vec::from_iter([23, 29, 26, 14, 19, 4]),
3364 }),
3365 (28, DockNode {
3366 node_id: 28,
3367 on_hull: false,
3368 connected: Vec::from_iter([23, 15, 25, 20, 21, 22]),
3369 }),
3370 (25, DockNode {
3371 node_id: 25,
3372 on_hull: false,
3373 connected: Vec::from_iter([23, 11, 28, 21]),
3374 }),
3375 (22, DockNode {
3376 node_id: 22,
3377 on_hull: false,
3378 connected: Vec::from_iter([23, 28, 27, 9, 3, 15]),
3379 }),
3380 (19, DockNode {
3381 node_id: 19,
3382 on_hull: false,
3383 connected: Vec::from_iter([0, 6, 29, 18, 2, 4]),
3384 }),
3385 (16, DockNode {
3386 node_id: 16,
3387 on_hull: false,
3388 connected: Vec::from_iter([10, 12, 20, 21]),
3389 }),
3390 (13, DockNode {
3391 node_id: 13,
3392 on_hull: true,
3393 connected: Vec::from_iter([7, 26, 9, 27, 3, 18]),
3394 }),
3395 (10, DockNode {
3396 node_id: 10,
3397 on_hull: false,
3398 connected: Vec::from_iter([24, 29, 11, 2, 16, 21]),
3399 }),
3400 (7, DockNode {
3401 node_id: 7,
3402 on_hull: true,
3403 connected: Vec::from_iter([26, 1, 13, 11]),
3404 }),
3405 (4, DockNode {
3406 node_id: 4,
3407 on_hull: false,
3408 connected: Vec::from_iter([0, 6, 14, 19]),
3409 }),
3410 (1, DockNode {
3411 node_id: 1,
3412 on_hull: true,
3413 connected: Vec::from_iter([7, 26, 8, 17]),
3414 }),
3415 (29, DockNode {
3416 node_id: 29,
3417 on_hull: false,
3418 connected: Vec::from_iter([0, 10, 24, 23, 19, 2]),
3419 }),
3420 (26, DockNode {
3421 node_id: 26,
3422 on_hull: false,
3423 connected: Vec::from_iter([0, 23, 14, 1, 27, 5, 7, 13]),
3424 }),
3425 (23, DockNode {
3426 node_id: 23,
3427 on_hull: false,
3428 connected: Vec::from_iter([0, 29, 25, 22, 28, 24, 11, 26]),
3429 }),
3430 (20, DockNode {
3431 node_id: 20,
3432 on_hull: true,
3433 connected: Vec::from_iter([18, 28, 12, 15, 16, 21]),
3434 }),
3435 (17, DockNode {
3436 node_id: 17,
3437 on_hull: false,
3438 connected: Vec::from_iter([5, 6, 8, 1]),
3439 }),
3440 (14, DockNode {
3441 node_id: 14,
3442 on_hull: false,
3443 connected: Vec::from_iter([0, 5, 26, 4]),
3444 }),
3445 (11, DockNode {
3446 node_id: 11,
3447 on_hull: false,
3448 connected: Vec::from_iter([10, 24, 23, 25, 21, 7]),
3449 }),
3450 (8, DockNode {
3451 node_id: 8,
3452 on_hull: true,
3453 connected: Vec::from_iter([18, 6, 1, 17]),
3454 }),
3455 (5, DockNode {
3456 node_id: 5,
3457 on_hull: false,
3458 connected: Vec::from_iter([6, 26, 14, 17]),
3459 }),
3460 (2, DockNode {
3461 node_id: 2,
3462 on_hull: false,
3463 connected: Vec::from_iter([10, 29, 12, 19]),
3464 }),
3465 (27, DockNode {
3466 node_id: 27,
3467 on_hull: false,
3468 connected: Vec::from_iter([26, 9, 13, 22]),
3469 }),
3470 (24, DockNode {
3471 node_id: 24,
3472 on_hull: false,
3473 connected: Vec::from_iter([10, 29, 11, 23]),
3474 }),
3475 (21, DockNode {
3476 node_id: 21,
3477 on_hull: false,
3478 connected: Vec::from_iter([10, 11, 25, 28, 20, 16]),
3479 }),
3480 (18, DockNode {
3481 node_id: 18,
3482 on_hull: true,
3483 connected: Vec::from_iter([6, 12, 8, 19, 20, 13]),
3484 }),
3485 (15, DockNode {
3486 node_id: 15,
3487 on_hull: true,
3488 connected: Vec::from_iter([28, 20, 3, 22]),
3489 }),
3490 (12, DockNode {
3491 node_id: 12,
3492 on_hull: false,
3493 connected: Vec::from_iter([18, 2, 16, 20]),
3494 }),
3495 (9, DockNode {
3496 node_id: 9,
3497 on_hull: false,
3498 connected: Vec::from_iter([13, 27, 3, 22]),
3499 }),
3500 (6, DockNode {
3501 node_id: 6,
3502 on_hull: false,
3503 connected: Vec::from_iter([4, 8, 5, 18, 19, 17]),
3504 }),
3505 (3, DockNode {
3506 node_id: 3,
3507 on_hull: true,
3508 connected: Vec::from_iter([13, 9, 15, 22]),
3509 }),
3510 ]);
3511
3512 let (best_segments, circuit, max_seg_len, min_spread, iteration) =
3513 find_best_eulerian_circuit(&node_connections)
3514 .expect("a circuit should have been found");
3515 assert_eq!(max_seg_len, 4);
3516 assert_relative_eq!(min_spread, 1.0606601, epsilon = 0.0000001);
3517 assert_eq!(iteration, 6);
3518 let expected_segments = vec![
3519 vec![26, 0, 23, 29, 0, 14, 5, 6, 4, 0, 19, 6, 8, 18, 6, 17, 5, 26],
3520 vec![
3521 26, 23, 25, 11, 10, 24, 29, 10, 2, 29, 19, 18, 12, 2, 19, 4, 14, 26,
3522 ],
3523 vec![
3524 26, 1, 8, 17, 1, 7, 11, 24, 23, 22, 28, 23, 11, 21, 10, 16, 12, 20, 18, 13, 26,
3525 ],
3526 vec![
3527 26, 27, 9, 13, 27, 22, 9, 3, 15, 28, 25, 21, 28, 20, 16, 21, 20, 15, 22, 3, 13, 7,
3528 26,
3529 ],
3530 ];
3531 assert_eq!(best_segments, expected_segments);
3532 let expected_circuit = vec![
3533 13, 7, 26, 0, 23, 29, 0, 14, 5, 6, 4, 0, 19, 6, 8, 18, 6, 17, 5, 26, 23, 25, 11, 10,
3534 24, 29, 10, 2, 29, 19, 18, 12, 2, 19, 4, 14, 26, 1, 8, 17, 1, 7, 11, 24, 23, 22, 28,
3535 23, 11, 21, 10, 16, 12, 20, 18, 13, 26, 27, 9, 13, 27, 22, 9, 3, 15, 28, 25, 21, 28,
3536 20, 16, 21, 20, 15, 22, 3, 13,
3537 ];
3538 assert_eq!(circuit, expected_circuit);
3539 }
3540
3541 fn large_map_docking_locations() -> Vec<Vec2<f32>> {
3542 [
3543 [384, 113],
3544 [713, 67],
3545 [1351, 17],
3546 [3146, 64],
3547 [720, 248],
3548 [775, 204],
3549 [829, 166],
3550 [1391, 161],
3551 [1812, 156],
3552 [3022, 204],
3553 [3094, 193],
3554 [781, 529],
3555 [860, 289],
3556 [889, 371],
3557 [892, 488],
3558 [975, 408],
3559 [1039, 509],
3560 [1050, 449],
3561 [1167, 379],
3562 [1359, 457],
3563 [1425, 382],
3564 [1468, 424],
3565 [1493, 363],
3566 [1752, 322],
3567 [1814, 452],
3568 [2139, 469],
3569 [2179, 343],
3570 [2283, 333],
3571 [2428, 299],
3572 [2499, 504],
3573 [2567, 498],
3574 [3110, 363],
3575 [3126, 503],
3576 [3248, 330],
3577 [3343, 491],
3578 [96, 837],
3579 [98, 752],
3580 [149, 884],
3581 [258, 679],
3582 [349, 873],
3583 [350, 676],
3584 [431, 983],
3585 [541, 842],
3586 [686, 640],
3587 [923, 728],
3588 [941, 537],
3589 [951, 654],
3590 [991, 575],
3591 [999, 955],
3592 [1164, 767],
3593 [1238, 669],
3594 [1250, 923],
3595 [1266, 808],
3596 [1343, 878],
3597 [1535, 711],
3598 [1633, 773],
3599 [1684, 705],
3600 [1690, 833],
3601 [1694, 982],
3602 [1742, 774],
3603 [1781, 821],
3604 [1833, 558],
3605 [1854, 623],
3606 [2169, 815],
3607 [2189, 966],
3608 [2232, 691],
3609 [2243, 778],
3610 [2266, 934],
3611 [2354, 742],
3612 [2423, 753],
3613 [2423, 999],
3614 [2438, 637],
3615 [2491, 758],
3616 [2497, 636],
3617 [2507, 855],
3618 [3066, 909],
3619 [3088, 568],
3620 [3124, 687],
3621 [3198, 681],
3622 [3241, 901],
3623 [3260, 603],
3624 [3276, 704],
3625 [3314, 652],
3626 [3329, 744],
3627 [3374, 888],
3628 [3513, 999],
3629 [3609, 708],
3630 [3864, 934],
3631 [3959, 933],
3632 [3959, 1000],
3633 [167, 1135],
3634 [229, 1072],
3635 [333, 1198],
3636 [349, 1481],
3637 [399, 1165],
3638 [473, 1350],
3639 [510, 1032],
3640 [523, 1481],
3641 [535, 1294],
3642 [552, 1080],
3643 [587, 1388],
3644 [789, 1103],
3645 [816, 1284],
3646 [886, 1183],
3647 [905, 1338],
3648 [1022, 1158],
3649 [1161, 1359],
3650 [1187, 1457],
3651 [1197, 1289],
3652 [1231, 1067],
3653 [1311, 1352],
3654 [1331, 1076],
3655 [1340, 1504],
3656 [1367, 1415],
3657 [1414, 1384],
3658 [1424, 1091],
3659 [1447, 1018],
3660 [1642, 1383],
3661 [1733, 1237],
3662 [1740, 1066],
3663 [1751, 1128],
3664 [1797, 1171],
3665 [1802, 1060],
3666 [1960, 1495],
3667 [1977, 1081],
3668 [2305, 1064],
3669 [2372, 1117],
3670 [2411, 1480],
3671 [2688, 1320],
3672 [2745, 1359],
3673 [2819, 1162],
3674 [2860, 1268],
3675 [2868, 1088],
3676 [2934, 1481],
3677 [2991, 1388],
3678 [3078, 1447],
3679 [3166, 1267],
3680 [3222, 1374],
3681 [3234, 1234],
3682 [3244, 1057],
3683 [3256, 1437],
3684 [3302, 1274],
3685 [3354, 1165],
3686 [3389, 1340],
3687 [3416, 1406],
3688 [3451, 1122],
3689 [3594, 1205],
3690 [3681, 1435],
3691 [3838, 1265],
3692 [3892, 1181],
3693 [3911, 1243],
3694 [200, 1663],
3695 [328, 1843],
3696 [363, 1630],
3697 [445, 1640],
3698 [505, 1756],
3699 [537, 1594],
3700 [560, 1779],
3701 [654, 1594],
3702 [713, 1559],
3703 [769, 1912],
3704 [970, 1782],
3705 [988, 1705],
3706 [1361, 1595],
3707 [1370, 1949],
3708 [1480, 1695],
3709 [1695, 1531],
3710 [1881, 1703],
3711 [2315, 1979],
3712 [2411, 1536],
3713 [2508, 1990],
3714 [2679, 1737],
3715 [2731, 1704],
3716 [2734, 1956],
3717 [2739, 1606],
3718 [2770, 1781],
3719 [2778, 1879],
3720 [2781, 1664],
3721 [2841, 1716],
3722 [2858, 1647],
3723 [2858, 1826],
3724 [2898, 1715],
3725 [2935, 1554],
3726 [3051, 1837],
3727 [3060, 1965],
3728 [3185, 1918],
3729 [3251, 1869],
3730 [3442, 1856],
3731 [3447, 1543],
3732 [3534, 1951],
3733 [3590, 1878],
3734 [3611, 1960],
3735 [3635, 1584],
3736 [3649, 1781],
3737 [3656, 1850],
3738 [3668, 1912],
3739 [3750, 1906],
3740 [3762, 1826],
3741 [3831, 1971],
3742 [3841, 1876],
3743 [3888, 1806],
3744 [3960, 1818],
3745 [177, 2260],
3746 [239, 2026],
3747 [358, 2364],
3748 [471, 2327],
3749 [528, 2100],
3750 [536, 2198],
3751 [588, 2244],
3752 [648, 2180],
3753 [665, 2038],
3754 [693, 2366],
3755 [852, 2410],
3756 [898, 2293],
3757 [969, 2205],
3758 [1095, 2322],
3759 [1198, 2217],
3760 [1267, 2284],
3761 [1278, 2220],
3762 [1339, 2114],
3763 [1419, 2203],
3764 [1470, 2049],
3765 [1487, 2108],
3766 [1959, 2257],
3767 [2087, 2061],
3768 [2226, 2048],
3769 [2231, 2319],
3770 [2385, 2251],
3771 [2417, 2039],
3772 [2598, 2035],
3773 [2686, 2071],
3774 [2715, 2204],
3775 [2778, 2188],
3776 [2900, 2128],
3777 [2910, 2007],
3778 [2988, 2087],
3779 [3002, 2435],
3780 [3082, 2433],
3781 [3115, 2006],
3782 [3167, 2143],
3783 [3170, 2361],
3784 [3360, 2433],
3785 [3472, 2370],
3786 [3514, 2022],
3787 [3599, 2045],
3788 [3662, 2365],
3789 [3676, 2172],
3790 [3838, 2208],
3791 [3921, 2060],
3792 [87, 2628],
3793 [239, 2604],
3794 [270, 2668],
3795 [327, 2726],
3796 [371, 2781],
3797 [419, 2583],
3798 [546, 2574],
3799 [620, 2776],
3800 [979, 2850],
3801 [1052, 2762],
3802 [1095, 2825],
3803 [1486, 2601],
3804 [1587, 2701],
3805 [1620, 2599],
3806 [1633, 2492],
3807 [1948, 2809],
3808 [2156, 2852],
3809 [2464, 2605],
3810 [2544, 2777],
3811 [2645, 2605],
3812 [2743, 2466],
3813 [2836, 2785],
3814 [2981, 2635],
3815 [3029, 2699],
3816 [3162, 2733],
3817 [3389, 2769],
3818 [3484, 2776],
3819 [3561, 2795],
3820 [3631, 2549],
3821 [3669, 2474],
3822 [3732, 2625],
3823 [33, 3129],
3824 [97, 3152],
3825 [191, 3289],
3826 [449, 2938],
3827 [450, 3000],
3828 [590, 3142],
3829 [654, 3065],
3830 [744, 3093],
3831 [870, 3042],
3832 [875, 2904],
3833 [921, 3103],
3834 [1018, 3034],
3835 [1040, 3135],
3836 [1079, 3238],
3837 [1122, 3316],
3838 [1136, 2996],
3839 [1237, 3366],
3840 [1294, 3127],
3841 [1360, 3297],
3842 [1366, 3043],
3843 [1368, 2985],
3844 [1381, 3128],
3845 [1464, 3089],
3846 [1514, 2965],
3847 [1529, 3046],
3848 [1901, 3052],
3849 [1954, 3272],
3850 [2117, 3121],
3851 [2182, 3381],
3852 [2225, 3212],
3853 [2241, 3142],
3854 [2250, 2949],
3855 [2340, 3333],
3856 [2395, 3195],
3857 [2496, 3383],
3858 [2521, 3162],
3859 [2604, 2959],
3860 [2635, 3287],
3861 [2644, 3021],
3862 [2657, 3140],
3863 [2716, 3367],
3864 [2726, 3184],
3865 [2734, 3264],
3866 [2799, 3300],
3867 [2866, 3361],
3868 [2907, 2893],
3869 [2938, 3362],
3870 [3058, 2982],
3871 [3187, 3076],
3872 [3357, 3200],
3873 [3467, 3300],
3874 [3511, 3359],
3875 [3522, 3105],
3876 [3538, 2997],
3877 [3791, 3348],
3878 [3866, 3261],
3879 [3947, 3223],
3880 [33, 3807],
3881 [109, 3828],
3882 [390, 3472],
3883 [468, 3510],
3884 [534, 3508],
3885 [563, 3659],
3886 [665, 3830],
3887 [668, 3732],
3888 [742, 3770],
3889 [896, 3818],
3890 [934, 3475],
3891 [1255, 3871],
3892 [1309, 3477],
3893 [1318, 3812],
3894 [1425, 3417],
3895 [1443, 3950],
3896 [1479, 3638],
3897 [1492, 3546],
3898 [1498, 3940],
3899 [1533, 3593],
3900 [1584, 3448],
3901 [1605, 3691],
3902 [1632, 3831],
3903 [1798, 3826],
3904 [1992, 3612],
3905 [2101, 3713],
3906 [2157, 3496],
3907 [2204, 3796],
3908 [2314, 3835],
3909 [2350, 3650],
3910 [2446, 3697],
3911 [2474, 3624],
3912 [2516, 3528],
3913 [2607, 3551],
3914 [2644, 3929],
3915 [2714, 3603],
3916 [2760, 3707],
3917 [2797, 3658],
3918 [2940, 3520],
3919 [2955, 3687],
3920 [2971, 3446],
3921 [3081, 3427],
3922 [3082, 3828],
3923 [3124, 3475],
3924 [3149, 3624],
3925 [3174, 3539],
3926 [3341, 3897],
3927 [3371, 3841],
3928 [3663, 3786],
3929 [3740, 3468],
3930 [3783, 3575],
3931 [3886, 3584],
3932 [3948, 3547],
3933 ]
3934 .iter()
3935 .map(|&[x, y]| Vec2::new(x as f32, y as f32))
3936 .collect()
3937 }
3938
3939 fn large_map_docking_points() -> Vec<Point> {
3940 large_map_docking_locations()
3941 .iter()
3942 .map(|&loc| Point {
3943 x: loc.x as f64,
3944 y: loc.y as f64,
3945 })
3946 .collect()
3947 }
3948
3949 #[test]
3950 fn large_map_graph_remove_edges_compare_test() {
3951 let all_dock_points1 = large_map_docking_points();
3952 let triangulation1 = triangulate(&all_dock_points1);
3953 let node_connections1 = triangulation1.node_connections();
3954
3955 let all_dock_points2 = large_map_docking_points();
3956 let triangulation2 = triangulate(&all_dock_points2);
3957 let node_connections2 = triangulation2.node_connections();
3958
3959 assert_eq!(
3960 all_dock_points1, all_dock_points2,
3961 "Dock points should be the same."
3962 );
3963 assert_eq!(
3964 node_connections1, node_connections2,
3965 "Node connections should be equal before removing edges."
3966 );
3967
3968 let max_distance_squared = 1000.0f64.powi(2);
3969
3970 let mut edges_to_remove1 = Vec::new();
3971 node_connections1.iter().for_each(|(node_id, node)| {
3972 for &connected_node_id in &node.connected {
3973 let pt1 = &all_dock_points1[*node_id];
3974 let pt2 = &all_dock_points1[connected_node_id];
3975 let v1 = Vec2 { x: pt1.x, y: pt1.y };
3976 let v2 = Vec2 { x: pt2.x, y: pt2.y };
3977 if v1.distance_squared(v2) > max_distance_squared {
3979 edges_to_remove1.push((*node_id, connected_node_id));
3980 }
3981 }
3982 });
3983 let edges_to_remove1b = edges_to_remove1.clone();
3984
3985 let mut edges_to_remove1c = DHashSet::default();
3986 edges_to_remove1b
3987 .iter()
3988 .for_each(|&(node_id, connected_node_id)| {
3989 edges_to_remove1c.insert(if node_id < connected_node_id {
3990 (node_id, connected_node_id)
3991 } else {
3992 (connected_node_id, node_id)
3993 });
3994 });
3995
3996 let mut edges_to_remove2 = DHashSet::default();
3997 let all_edges2 = triangulation2.all_edges();
3998 for edge in all_edges2.iter() {
3999 let pt1 = &all_dock_points2[edge.0];
4000 let pt2 = &all_dock_points2[edge.1];
4001 let v1 = Vec2 { x: pt1.x, y: pt1.y };
4002 let v2 = Vec2 { x: pt2.x, y: pt2.y };
4003 if v1.distance_squared(v2) > max_distance_squared {
4006 edges_to_remove2.insert(*edge);
4007 }
4008 }
4009
4010 assert_eq!(
4011 edges_to_remove1c, edges_to_remove2,
4012 "Edges to remove should be the same in hashset form."
4013 );
4014
4015 let mut mutable_node_connections1 = node_connections1.clone();
4016 for (node_id, connected_node_id) in edges_to_remove1 {
4017 remove_edge((node_id, connected_node_id), &mut mutable_node_connections1);
4018 }
4019
4020 let mut mutable_node_connections1b = node_connections1.clone();
4021 for edge in edges_to_remove1c {
4022 remove_edge(edge, &mut mutable_node_connections1b);
4023 }
4024
4025 assert_eq!(
4026 mutable_node_connections1, mutable_node_connections1b,
4027 "Node connections1 should be the same for either Vec or HashSet remove edges."
4028 );
4029
4030 let mut mutable_node_connections2 = node_connections2.clone();
4031 for edge in edges_to_remove2 {
4032 remove_edge(edge, &mut mutable_node_connections2);
4033 }
4034
4035 assert_eq!(
4036 mutable_node_connections1, mutable_node_connections2,
4037 "Node connections should be equal after removing edges."
4038 );
4039
4040 assert_eq!(
4041 mutable_node_connections1, mutable_node_connections2,
4042 "Node connections should be equal after removing edges."
4043 );
4044 }
4045
4046 #[test]
4047 fn docking_position_from_platform_test() {
4048 let airships = airships_from_test_data();
4049 let platforms = [
4050 AirshipDockPlatform::NorthPlatform,
4051 AirshipDockPlatform::EastPlatform,
4052 AirshipDockPlatform::SouthPlatform,
4053 AirshipDockPlatform::WestPlatform,
4054 ];
4055 let expected = [
4056 Vec3 {
4057 x: 26688.0,
4058 y: 4777.0,
4059 z: 213.0,
4060 },
4061 Vec3 {
4062 x: 26707.0,
4063 y: 4758.0,
4064 z: 213.0,
4065 },
4066 Vec3 {
4067 x: 26688.0,
4068 y: 4739.0,
4069 z: 213.0,
4070 },
4071 Vec3 {
4072 x: 26669.0,
4073 y: 4758.0,
4074 z: 213.0,
4075 },
4076 Vec3 {
4077 x: 24574.0,
4078 y: 26127.0,
4079 z: 214.0,
4080 },
4081 Vec3 {
4082 x: 24593.0,
4083 y: 26108.0,
4084 z: 214.0,
4085 },
4086 Vec3 {
4087 x: 24574.0,
4088 y: 26089.0,
4089 z: 214.0,
4090 },
4091 Vec3 {
4092 x: 24555.0,
4093 y: 26108.0,
4094 z: 214.0,
4095 },
4096 Vec3 {
4097 x: 24253.0,
4098 y: 20734.0,
4099 z: 515.0,
4100 },
4101 Vec3 {
4102 x: 24272.0,
4103 y: 20715.0,
4104 z: 515.0,
4105 },
4106 Vec3 {
4107 x: 24253.0,
4108 y: 20696.0,
4109 z: 515.0,
4110 },
4111 Vec3 {
4112 x: 24234.0,
4113 y: 20715.0,
4114 z: 515.0,
4115 },
4116 Vec3 {
4117 x: 20809.0,
4118 y: 6574.0,
4119 z: 216.0,
4120 },
4121 Vec3 {
4122 x: 20828.0,
4123 y: 6555.0,
4124 z: 216.0,
4125 },
4126 Vec3 {
4127 x: 20809.0,
4128 y: 6536.0,
4129 z: 216.0,
4130 },
4131 Vec3 {
4132 x: 20790.0,
4133 y: 6555.0,
4134 z: 216.0,
4135 },
4136 Vec3 {
4137 x: 16492.0,
4138 y: 1080.0,
4139 z: 211.0,
4140 },
4141 Vec3 {
4142 x: 16511.0,
4143 y: 1061.0,
4144 z: 211.0,
4145 },
4146 Vec3 {
4147 x: 16492.0,
4148 y: 1042.0,
4149 z: 211.0,
4150 },
4151 Vec3 {
4152 x: 16473.0,
4153 y: 1061.0,
4154 z: 211.0,
4155 },
4156 Vec3 {
4157 x: 18452.0,
4158 y: 11255.0,
4159 z: 421.0,
4160 },
4161 Vec3 {
4162 x: 18471.0,
4163 y: 11236.0,
4164 z: 421.0,
4165 },
4166 Vec3 {
4167 x: 18452.0,
4168 y: 11217.0,
4169 z: 421.0,
4170 },
4171 Vec3 {
4172 x: 18433.0,
4173 y: 11236.0,
4174 z: 421.0,
4175 },
4176 Vec3 {
4177 x: 21870.0,
4178 y: 8549.0,
4179 z: 216.0,
4180 },
4181 Vec3 {
4182 x: 21889.0,
4183 y: 8530.0,
4184 z: 216.0,
4185 },
4186 Vec3 {
4187 x: 21870.0,
4188 y: 8511.0,
4189 z: 216.0,
4190 },
4191 Vec3 {
4192 x: 21851.0,
4193 y: 8530.0,
4194 z: 216.0,
4195 },
4196 Vec3 {
4197 x: 22577.0,
4198 y: 15225.0,
4199 z: 277.0,
4200 },
4201 Vec3 {
4202 x: 22605.0,
4203 y: 15197.0,
4204 z: 277.0,
4205 },
4206 Vec3 {
4207 x: 22577.0,
4208 y: 15169.0,
4209 z: 277.0,
4210 },
4211 Vec3 {
4212 x: 22549.0,
4213 y: 15197.0,
4214 z: 277.0,
4215 },
4216 Vec3 {
4217 x: 5477.0,
4218 y: 15244.0,
4219 z: 1675.0,
4220 },
4221 Vec3 {
4222 x: 5514.0,
4223 y: 15207.0,
4224 z: 1675.0,
4225 },
4226 Vec3 {
4227 x: 5477.0,
4228 y: 15170.0,
4229 z: 1675.0,
4230 },
4231 Vec3 {
4232 x: 5440.0,
4233 y: 15207.0,
4234 z: 1675.0,
4235 },
4236 Vec3 {
4237 x: 23884.0,
4238 y: 24321.0,
4239 z: 214.0,
4240 },
4241 Vec3 {
4242 x: 23903.0,
4243 y: 24302.0,
4244 z: 214.0,
4245 },
4246 Vec3 {
4247 x: 23884.0,
4248 y: 24283.0,
4249 z: 214.0,
4250 },
4251 Vec3 {
4252 x: 23865.0,
4253 y: 24302.0,
4254 z: 214.0,
4255 },
4256 Vec3 {
4257 x: 13373.0,
4258 y: 2332.0,
4259 z: 259.0,
4260 },
4261 Vec3 {
4262 x: 13392.0,
4263 y: 2313.0,
4264 z: 259.0,
4265 },
4266 Vec3 {
4267 x: 13373.0,
4268 y: 2294.0,
4269 z: 259.0,
4270 },
4271 Vec3 {
4272 x: 13354.0,
4273 y: 2313.0,
4274 z: 259.0,
4275 },
4276 Vec3 {
4277 x: 20141.0,
4278 y: 31880.0,
4279 z: 215.0,
4280 },
4281 Vec3 {
4282 x: 20160.0,
4283 y: 31861.0,
4284 z: 215.0,
4285 },
4286 Vec3 {
4287 x: 20141.0,
4288 y: 31842.0,
4289 z: 215.0,
4290 },
4291 Vec3 {
4292 x: 20122.0,
4293 y: 31861.0,
4294 z: 215.0,
4295 },
4296 Vec3 {
4297 x: 29713.0,
4298 y: 24552.0,
4299 z: 214.0,
4300 },
4301 Vec3 {
4302 x: 29732.0,
4303 y: 24533.0,
4304 z: 214.0,
4305 },
4306 Vec3 {
4307 x: 29713.0,
4308 y: 24514.0,
4309 z: 214.0,
4310 },
4311 Vec3 {
4312 x: 29694.0,
4313 y: 24533.0,
4314 z: 214.0,
4315 },
4316 Vec3 {
4317 x: 18992.0,
4318 y: 17139.0,
4319 z: 435.0,
4320 },
4321 Vec3 {
4322 x: 19011.0,
4323 y: 17120.0,
4324 z: 435.0,
4325 },
4326 Vec3 {
4327 x: 18992.0,
4328 y: 17101.0,
4329 z: 435.0,
4330 },
4331 Vec3 {
4332 x: 18973.0,
4333 y: 17120.0,
4334 z: 435.0,
4335 },
4336 Vec3 {
4337 x: 7705.0,
4338 y: 12570.0,
4339 z: 1911.0,
4340 },
4341 Vec3 {
4342 x: 7742.0,
4343 y: 12533.0,
4344 z: 1911.0,
4345 },
4346 Vec3 {
4347 x: 7705.0,
4348 y: 12496.0,
4349 z: 1911.0,
4350 },
4351 Vec3 {
4352 x: 7668.0,
4353 y: 12533.0,
4354 z: 1911.0,
4355 },
4356 Vec3 {
4357 x: 30365.0,
4358 y: 13015.0,
4359 z: 244.0,
4360 },
4361 Vec3 {
4362 x: 30393.0,
4363 y: 12987.0,
4364 z: 244.0,
4365 },
4366 Vec3 {
4367 x: 30365.0,
4368 y: 12959.0,
4369 z: 244.0,
4370 },
4371 Vec3 {
4372 x: 30337.0,
4373 y: 12987.0,
4374 z: 244.0,
4375 },
4376 Vec3 {
4377 x: 10142.0,
4378 y: 19218.0,
4379 z: 1141.0,
4380 },
4381 Vec3 {
4382 x: 10170.0,
4383 y: 19190.0,
4384 z: 1141.0,
4385 },
4386 Vec3 {
4387 x: 10142.0,
4388 y: 19162.0,
4389 z: 1141.0,
4390 },
4391 Vec3 {
4392 x: 10114.0,
4393 y: 19190.0,
4394 z: 1141.0,
4395 },
4396 Vec3 {
4397 x: 13716.0,
4398 y: 17542.0,
4399 z: 1420.0,
4400 },
4401 Vec3 {
4402 x: 13753.0,
4403 y: 17505.0,
4404 z: 1420.0,
4405 },
4406 Vec3 {
4407 x: 13716.0,
4408 y: 17468.0,
4409 z: 1420.0,
4410 },
4411 Vec3 {
4412 x: 13679.0,
4413 y: 17505.0,
4414 z: 1420.0,
4415 },
4416 Vec3 {
4417 x: 9383.0,
4418 y: 17173.0,
4419 z: 909.0,
4420 },
4421 Vec3 {
4422 x: 9411.0,
4423 y: 17145.0,
4424 z: 909.0,
4425 },
4426 Vec3 {
4427 x: 9383.0,
4428 y: 17117.0,
4429 z: 909.0,
4430 },
4431 Vec3 {
4432 x: 9355.0,
4433 y: 17145.0,
4434 z: 909.0,
4435 },
4436 Vec3 {
4437 x: 24424.0,
4438 y: 7819.0,
4439 z: 329.0,
4440 },
4441 Vec3 {
4442 x: 24443.0,
4443 y: 7800.0,
4444 z: 329.0,
4445 },
4446 Vec3 {
4447 x: 24424.0,
4448 y: 7781.0,
4449 z: 329.0,
4450 },
4451 Vec3 {
4452 x: 24405.0,
4453 y: 7800.0,
4454 z: 329.0,
4455 },
4456 Vec3 {
4457 x: 7528.0,
4458 y: 28445.0,
4459 z: 218.0,
4460 },
4461 Vec3 {
4462 x: 7547.0,
4463 y: 28426.0,
4464 z: 218.0,
4465 },
4466 Vec3 {
4467 x: 7528.0,
4468 y: 28407.0,
4469 z: 218.0,
4470 },
4471 Vec3 {
4472 x: 7509.0,
4473 y: 28426.0,
4474 z: 218.0,
4475 },
4476 Vec3 {
4477 x: 9942.0,
4478 y: 30955.0,
4479 z: 185.0,
4480 },
4481 Vec3 {
4482 x: 9961.0,
4483 y: 30936.0,
4484 z: 185.0,
4485 },
4486 Vec3 {
4487 x: 9942.0,
4488 y: 30917.0,
4489 z: 185.0,
4490 },
4491 Vec3 {
4492 x: 9923.0,
4493 y: 30936.0,
4494 z: 185.0,
4495 },
4496 Vec3 {
4497 x: 27915.0,
4498 y: 18578.0,
4499 z: 498.0,
4500 },
4501 Vec3 {
4502 x: 27934.0,
4503 y: 18559.0,
4504 z: 498.0,
4505 },
4506 Vec3 {
4507 x: 27915.0,
4508 y: 18540.0,
4509 z: 498.0,
4510 },
4511 Vec3 {
4512 x: 27896.0,
4513 y: 18559.0,
4514 z: 498.0,
4515 },
4516 Vec3 {
4517 x: 3688.0,
4518 y: 29191.0,
4519 z: 198.0,
4520 },
4521 Vec3 {
4522 x: 3711.0,
4523 y: 29168.0,
4524 z: 198.0,
4525 },
4526 Vec3 {
4527 x: 3688.0,
4528 y: 29145.0,
4529 z: 198.0,
4530 },
4531 Vec3 {
4532 x: 3665.0,
4533 y: 29168.0,
4534 z: 198.0,
4535 },
4536 Vec3 {
4537 x: 15864.0,
4538 y: 15612.0,
4539 z: 419.0,
4540 },
4541 Vec3 {
4542 x: 15892.0,
4543 y: 15584.0,
4544 z: 419.0,
4545 },
4546 Vec3 {
4547 x: 15864.0,
4548 y: 15556.0,
4549 z: 419.0,
4550 },
4551 Vec3 {
4552 x: 15836.0,
4553 y: 15584.0,
4554 z: 419.0,
4555 },
4556 Vec3 {
4557 x: 9975.0,
4558 y: 24326.0,
4559 z: 755.0,
4560 },
4561 Vec3 {
4562 x: 10012.0,
4563 y: 24289.0,
4564 z: 755.0,
4565 },
4566 Vec3 {
4567 x: 9975.0,
4568 y: 24252.0,
4569 z: 755.0,
4570 },
4571 Vec3 {
4572 x: 9938.0,
4573 y: 24289.0,
4574 z: 755.0,
4575 },
4576 Vec3 {
4577 x: 479.0,
4578 y: 18316.0,
4579 z: 449.0,
4580 },
4581 Vec3 {
4582 x: 516.0,
4583 y: 18279.0,
4584 z: 449.0,
4585 },
4586 Vec3 {
4587 x: 479.0,
4588 y: 18242.0,
4589 z: 449.0,
4590 },
4591 Vec3 {
4592 x: 442.0,
4593 y: 18279.0,
4594 z: 449.0,
4595 },
4596 Vec3 {
4597 x: 26543.0,
4598 y: 17198.0,
4599 z: 362.0,
4600 },
4601 Vec3 {
4602 x: 26566.0,
4603 y: 17175.0,
4604 z: 362.0,
4605 },
4606 Vec3 {
4607 x: 26543.0,
4608 y: 17152.0,
4609 z: 362.0,
4610 },
4611 Vec3 {
4612 x: 26520.0,
4613 y: 17175.0,
4614 z: 362.0,
4615 },
4616 ];
4617
4618 for (i, dock_pos) in airships.airship_docks.iter().enumerate() {
4619 for platform in platforms {
4620 let docking_position = dock_pos.docking_position(platform);
4621 assert_eq!(docking_position, expected[i * 4 + platform as usize]);
4622 }
4623 }
4624 }
4625
4626 #[test]
4627 fn docking_transition_point_test() {
4628 let expected = [
4629 Vec3 {
4630 x: 26567.24,
4631 y: 4903.6567,
4632 z: 613.0,
4633 },
4634 Vec3 {
4635 x: 26725.146,
4636 y: 4948.012,
4637 z: 613.0,
4638 },
4639 Vec3 {
4640 x: 26825.607,
4641 y: 4668.8833,
4642 z: 613.0,
4643 },
4644 Vec3 {
4645 x: 26515.738,
4646 y: 4746.166,
4647 z: 613.0,
4648 },
4649 Vec3 {
4650 x: 26586.238,
4651 y: 4884.6543,
4652 z: 613.0,
4653 },
4654 Vec3 {
4655 x: 26744.012,
4656 y: 4929.0415,
4657 z: 613.0,
4658 },
4659 Vec3 {
4660 x: 26844.652,
4661 y: 4649.9404,
4662 z: 613.0,
4663 },
4664 Vec3 {
4665 x: 26534.713,
4666 y: 4727.306,
4667 z: 613.0,
4668 },
4669 Vec3 {
4670 x: 26567.326,
4671 y: 4865.7383,
4672 z: 613.0,
4673 },
4674 Vec3 {
4675 x: 26725.098,
4676 y: 4910.0225,
4677 z: 613.0,
4678 },
4679 Vec3 {
4680 x: 26826.025,
4681 y: 4631.4175,
4682 z: 613.0,
4683 },
4684 Vec3 {
4685 x: 26515.695,
4686 y: 4708.404,
4687 z: 613.0,
4688 },
4689 Vec3 {
4690 x: 26548.328,
4691 y: 4884.74,
4692 z: 613.0,
4693 },
4694 Vec3 {
4695 x: 26706.232,
4696 y: 4928.993,
4697 z: 613.0,
4698 },
4699 Vec3 {
4700 x: 26806.979,
4701 y: 4650.3584,
4702 z: 613.0,
4703 },
4704 Vec3 {
4705 x: 26496.72,
4706 y: 4727.2637,
4707 z: 613.0,
4708 },
4709 Vec3 {
4710 x: 21752.715,
4711 y: 8678.882,
4712 z: 766.0,
4713 },
4714 Vec3 {
4715 x: 21941.81,
4716 y: 8708.588,
4717 z: 766.0,
4718 },
4719 Vec3 {
4720 x: 22007.69,
4721 y: 8440.988,
4722 z: 766.0,
4723 },
4724 Vec3 {
4725 x: 21707.01,
4726 y: 8485.287,
4727 z: 766.0,
4728 },
4729 Vec3 {
4730 x: 21771.709,
4731 y: 8659.877,
4732 z: 766.0,
4733 },
4734 Vec3 {
4735 x: 21960.66,
4736 y: 8689.655,
4737 z: 766.0,
4738 },
4739 Vec3 {
4740 x: 22026.715,
4741 y: 8422.0205,
4742 z: 766.0,
4743 },
4744 Vec3 {
4745 x: 21725.943,
4746 y: 8466.458,
4747 z: 766.0,
4748 },
4749 Vec3 {
4750 x: 21752.816,
4751 y: 8640.974,
4752 z: 766.0,
4753 },
4754 Vec3 {
4755 x: 21941.717,
4756 y: 8670.63,
4757 z: 766.0,
4758 },
4759 Vec3 {
4760 x: 22007.924,
4761 y: 8403.286,
4762 z: 766.0,
4763 },
4764 Vec3 {
4765 x: 21706.914,
4766 y: 8447.533,
4767 z: 766.0,
4768 },
4769 Vec3 {
4770 x: 21733.822,
4771 y: 8659.979,
4772 z: 766.0,
4773 },
4774 Vec3 {
4775 x: 21922.867,
4776 y: 8689.562,
4777 z: 766.0,
4778 },
4779 Vec3 {
4780 x: 21988.898,
4781 y: 8422.254,
4782 z: 766.0,
4783 },
4784 Vec3 {
4785 x: 21687.98,
4786 y: 8466.362,
4787 z: 766.0,
4788 },
4789 Vec3 {
4790 x: 29544.33,
4791 y: 24598.639,
4792 z: 614.0,
4793 },
4794 Vec3 {
4795 x: 29773.992,
4796 y: 24716.027,
4797 z: 614.0,
4798 },
4799 Vec3 {
4800 x: 29734.61,
4801 y: 24378.34,
4802 z: 614.0,
4803 },
4804 Vec3 {
4805 x: 29578.096,
4806 y: 24440.527,
4807 z: 614.0,
4808 },
4809 Vec3 {
4810 x: 29563.35,
4811 y: 24579.71,
4812 z: 614.0,
4813 },
4814 Vec3 {
4815 x: 29792.535,
4816 y: 24697.197,
4817 z: 614.0,
4818 },
4819 Vec3 {
4820 x: 29753.492,
4821 y: 24359.324,
4822 z: 614.0,
4823 },
4824 Vec3 {
4825 x: 29597.02,
4826 y: 24421.621,
4827 z: 614.0,
4828 },
4829 Vec3 {
4830 x: 29544.385,
4831 y: 24560.84,
4832 z: 614.0,
4833 },
4834 Vec3 {
4835 x: 29773.744,
4836 y: 24678.12,
4837 z: 614.0,
4838 },
4839 Vec3 {
4840 x: 29734.64,
4841 y: 24340.344,
4842 z: 614.0,
4843 },
4844 Vec3 {
4845 x: 29578.012,
4846 y: 24402.63,
4847 z: 614.0,
4848 },
4849 Vec3 {
4850 x: 29525.365,
4851 y: 24579.768,
4852 z: 614.0,
4853 },
4854 Vec3 {
4855 x: 29755.2,
4856 y: 24696.95,
4857 z: 614.0,
4858 },
4859 Vec3 {
4860 x: 29715.758,
4861 y: 24359.357,
4862 z: 614.0,
4863 },
4864 Vec3 {
4865 x: 29559.088,
4866 y: 24421.537,
4867 z: 614.0,
4868 },
4869 Vec3 {
4870 x: 9292.779,
4871 y: 17322.951,
4872 z: 1459.0,
4873 },
4874 Vec3 {
4875 x: 9528.595,
4876 y: 17270.094,
4877 z: 1459.0,
4878 },
4879 Vec3 {
4880 x: 9524.052,
4881 y: 17069.418,
4882 z: 1459.0,
4883 },
4884 Vec3 {
4885 x: 9299.091,
4886 y: 17019.428,
4887 z: 1459.0,
4888 },
4889 Vec3 {
4890 x: 9320.701,
4891 y: 17294.904,
4892 z: 1459.0,
4893 },
4894 Vec3 {
4895 x: 9556.46,
4896 y: 17242.295,
4897 z: 1459.0,
4898 },
4899 Vec3 {
4900 x: 9552.073,
4901 y: 17041.447,
4902 z: 1459.0,
4903 },
4904 Vec3 {
4905 x: 9326.793,
4906 y: 16991.592,
4907 z: 1459.0,
4908 },
4909 Vec3 {
4910 x: 9293.017,
4911 y: 17267.094,
4912 z: 1459.0,
4913 },
4914 Vec3 {
4915 x: 9528.434,
4916 y: 17214.336,
4917 z: 1459.0,
4918 },
4919 Vec3 {
4920 x: 9524.213,
4921 y: 17013.637,
4922 z: 1459.0,
4923 },
4924 Vec3 {
4925 x: 9298.88,
4926 y: 16963.543,
4927 z: 1459.0,
4928 },
4929 Vec3 {
4930 x: 9265.096,
4931 y: 17295.14,
4932 z: 1459.0,
4933 },
4934 Vec3 {
4935 x: 9500.567,
4936 y: 17242.135,
4937 z: 1459.0,
4938 },
4939 Vec3 {
4940 x: 9496.191,
4941 y: 17041.607,
4942 z: 1459.0,
4943 },
4944 Vec3 {
4945 x: 9271.179,
4946 y: 16991.38,
4947 z: 1459.0,
4948 },
4949 Vec3 {
4950 x: 15745.189,
4951 y: 15740.487,
4952 z: 819.0,
4953 },
4954 Vec3 {
4955 x: 15986.825,
4956 y: 15736.656,
4957 z: 819.0,
4958 },
4959 Vec3 {
4960 x: 15992.56,
4961 y: 15493.267,
4962 z: 819.0,
4963 },
4964 Vec3 {
4965 x: 15739.27,
4966 y: 15489.251,
4967 z: 819.0,
4968 },
4969 Vec3 {
4970 x: 15773.181,
4971 y: 15712.4795,
4972 z: 819.0,
4973 },
4974 Vec3 {
4975 x: 16014.62,
4976 y: 15708.857,
4977 z: 819.0,
4978 },
4979 Vec3 {
4980 x: 16020.567,
4981 y: 15465.275,
4982 z: 819.0,
4983 },
4984 Vec3 {
4985 x: 15767.052,
4986 y: 15461.473,
4987 z: 819.0,
4988 },
4989 Vec3 {
4990 x: 15745.397,
4991 y: 15684.68,
4992 z: 819.0,
4993 },
4994 Vec3 {
4995 x: 15986.621,
4996 y: 15680.855,
4997 z: 819.0,
4998 },
4999 Vec3 {
5000 x: 15992.771,
5001 y: 15437.497,
5002 z: 819.0,
5003 },
5004 Vec3 {
5005 x: 15739.049,
5006 y: 15433.476,
5007 z: 819.0,
5008 },
5009 Vec3 {
5010 x: 15717.407,
5011 y: 15712.688,
5012 z: 819.0,
5013 },
5014 Vec3 {
5015 x: 15958.826,
5016 y: 15708.654,
5017 z: 819.0,
5018 },
5019 Vec3 {
5020 x: 15964.763,
5021 y: 15465.488,
5022 z: 819.0,
5023 },
5024 Vec3 {
5025 x: 15711.268,
5026 y: 15461.253,
5027 z: 819.0,
5028 },
5029 ];
5030
5031 let airships = airships_from_test_data();
5032 let platforms = [
5033 AirshipDockPlatform::NorthPlatform,
5034 AirshipDockPlatform::EastPlatform,
5035 AirshipDockPlatform::SouthPlatform,
5036 AirshipDockPlatform::WestPlatform,
5037 ];
5038 let from_positions = [
5039 Vec2::new(0.0, 32768.0),
5040 Vec2::new(32768.0, 32768.0),
5041 Vec2::new(32768.0, 0.0),
5042 Vec2::new(0.0, 0.0),
5043 ];
5044 for dock_index in (0..airships.airship_docks.len()).step_by(6) {
5045 for platform in platforms.iter() {
5046 for (i, from_pos) in from_positions.iter().enumerate() {
5047 let transition_point = airships
5048 .approach_transition_point(dock_index, dock_index % 4, *platform, *from_pos)
5049 .unwrap();
5050 assert_eq!(
5051 transition_point,
5052 expected[dock_index / 6 * 16 + *platform as usize * 4 + i]
5053 );
5054 }
5055 }
5056 }
5057 }
5058
5059 #[test]
5060 fn docking_side_for_platform_test() {
5061 let dirs = [
5064 Vec2::new(0.0, 100.0) - Vec2::zero(),
5065 Vec2::new(100.0, 100.0) - Vec2::zero(),
5066 Vec2::new(100.0, 0.0) - Vec2::zero(),
5067 Vec2::new(100.0, -100.0) - Vec2::zero(),
5068 Vec2::new(0.0, -100.0) - Vec2::zero(),
5069 Vec2::new(-100.0, -100.0) - Vec2::zero(),
5070 Vec2::new(-100.0, 0.0) - Vec2::zero(),
5071 Vec2::new(-100.0, 100.0) - Vec2::zero(),
5072 ];
5073 let expected = [
5074 AirshipDockingSide::Port,
5075 AirshipDockingSide::Starboard,
5076 AirshipDockingSide::Starboard,
5077 AirshipDockingSide::Starboard,
5078 AirshipDockingSide::Starboard,
5079 AirshipDockingSide::Port,
5080 AirshipDockingSide::Port,
5081 AirshipDockingSide::Port,
5082 AirshipDockingSide::Port,
5083 AirshipDockingSide::Port,
5084 AirshipDockingSide::Port,
5085 AirshipDockingSide::Starboard,
5086 AirshipDockingSide::Starboard,
5087 AirshipDockingSide::Starboard,
5088 AirshipDockingSide::Starboard,
5089 AirshipDockingSide::Port,
5090 AirshipDockingSide::Starboard,
5091 AirshipDockingSide::Port,
5092 AirshipDockingSide::Port,
5093 AirshipDockingSide::Port,
5094 AirshipDockingSide::Port,
5095 AirshipDockingSide::Starboard,
5096 AirshipDockingSide::Starboard,
5097 AirshipDockingSide::Starboard,
5098 AirshipDockingSide::Starboard,
5099 AirshipDockingSide::Starboard,
5100 AirshipDockingSide::Starboard,
5101 AirshipDockingSide::Port,
5102 AirshipDockingSide::Port,
5103 AirshipDockingSide::Port,
5104 AirshipDockingSide::Port,
5105 AirshipDockingSide::Starboard,
5106 ];
5107 for platform in [
5108 AirshipDockPlatform::NorthPlatform,
5109 AirshipDockPlatform::EastPlatform,
5110 AirshipDockPlatform::SouthPlatform,
5111 AirshipDockPlatform::WestPlatform,
5112 ]
5113 .iter()
5114 {
5115 for (i, dir) in dirs.iter().enumerate() {
5116 let side = AirshipDockingSide::from_dir_to_platform(dir, platform);
5117 assert_eq!(side, expected[*platform as usize * 8 + i]);
5118 }
5119 }
5120 }
5121
5122 #[test]
5123 fn airship_spawning_locations_test() {
5124 let mut airships = airships_from_test_data();
5125 let all_dock_points = airships
5126 .airship_docks
5127 .iter()
5128 .map(|dock| Point {
5129 x: dock.center.x as f64,
5130 y: dock.center.y as f64,
5131 })
5132 .collect::<Vec<_>>();
5133
5134 airships.calculate_spawning_locations(&all_dock_points);
5135 }
5136}