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