1use crate::{
2 sim::WorldSim,
3 site::{self, Site},
4 site2::plot::PlotKindMeta,
5 util::{DHashMap, DHashSet, seed_expan},
6};
7use common::{
8 store::{Id, Store},
9 terrain::CoordinateConversions,
10 util::Dir,
11};
12use rand::prelude::*;
13use rand_chacha::ChaChaRng;
14use std::{fs::OpenOptions, io::Write};
15use tracing::{debug, warn};
16use vek::*;
17
18const AIRSHIP_TRAVEL_DEBUG: bool = false;
19
20macro_rules! debug_airships {
21 ($($arg:tt)*) => {
22 if AIRSHIP_TRAVEL_DEBUG {
23 debug!($($arg)*);
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq)]
31pub struct AirshipDockingPosition(pub u32, pub Vec3<f32>);
32
33#[derive(Debug, Copy, Clone, PartialEq, Default)]
35pub enum AirshipDockingSide {
36 #[default]
37 Port,
38 Starboard,
39}
40
41#[derive(Clone, Debug, PartialEq)]
51pub struct AirshipDockingApproach {
52 pub dock_pos: AirshipDockingPosition,
53 pub airship_pos: Vec3<f32>,
57 pub airship_direction: Dir,
59 pub dock_center: Vec2<f32>,
61 pub height: f32,
63 pub approach_initial_pos: Vec2<f32>,
67 pub approach_final_pos: Vec2<f32>,
70 pub side: AirshipDockingSide,
73 pub site_id: Id<Site>,
76}
77
78#[derive(Clone, Debug)]
80pub struct AirshipRoute {
81 pub sites: [Id<site::Site>; 2],
83 pub approaches: [AirshipDockingApproach; 2],
86 pub distance: u32,
88}
89
90impl AirshipRoute {
91 fn new(
92 site1: Id<site::Site>,
93 site2: Id<site::Site>,
94 approaches: [AirshipDockingApproach; 2],
95 distance: u32,
96 ) -> Self {
97 Self {
98 sites: [site1, site2],
99 approaches,
100 distance,
101 }
102 }
103}
104
105type AirshipRouteId = u32;
107
108#[derive(Clone, Default)]
110pub struct Airships {
111 pub routes: DHashMap<AirshipRouteId, AirshipRoute>,
113}
114
115#[derive(Clone, Debug)]
121struct AirshipDockPositions {
122 pub center: Vec2<f32>,
123 pub docking_positions: Vec<AirshipDockingPosition>,
124 pub site_id: Id<site::Site>,
125}
126
127impl AirshipDockPositions {
128 fn from_plot_meta(
129 first_id: u32,
130 center: Vec2<i32>,
131 docking_positions: &[Vec3<i32>],
132 site_id: Id<site::Site>,
133 ) -> Self {
134 let mut dock_pos_id = first_id;
135 Self {
136 center: center.map(|i| i as f32),
137 docking_positions: docking_positions
138 .iter()
139 .map(|pos: &Vec3<i32>| {
140 let docking_position =
141 AirshipDockingPosition(dock_pos_id, pos.map(|i| i as f32));
142 dock_pos_id += 1;
143 docking_position
144 })
145 .collect(),
146 site_id,
147 }
148 }
149}
150
151#[derive(Clone, Debug)]
155struct AirRouteConnection<'a> {
156 pub dock1: &'a AirshipDockPositions,
157 pub dock2: &'a AirshipDockPositions,
158 pub angle: f32, pub distance: i64, }
161
162impl<'a> AirRouteConnection<'a> {
163 fn new(dock1: &'a AirshipDockPositions, dock2: &'a AirshipDockPositions) -> Self {
164 let angle = Airships::angle_between_vectors_ccw(
165 Airships::ROUTES_NORTH,
166 dock2.center - dock1.center,
167 );
168 let distance = dock1.center.distance_squared(dock2.center) as i64;
169 Self {
170 dock1,
171 dock2,
172 angle,
173 distance,
174 }
175 }
176}
177
178#[derive(Eq, PartialEq, Hash, Debug)]
182struct DockConnectionHashKey(Id<site::Site>, Id<site::Site>);
183
184#[derive(Clone, Debug)]
187struct DockConnection<'a> {
188 pub dock: &'a AirshipDockPositions,
189 pub available_connections: usize,
190 pub connections: Vec<&'a AirRouteConnection<'a>>,
191}
192
193impl<'a> DockConnection<'a> {
194 fn new(dock: &'a AirshipDockPositions) -> Self {
195 Self {
196 dock,
197 available_connections: dock.docking_positions.len(),
198 connections: Vec::new(),
199 }
200 }
201
202 fn add_connection(&mut self, connection: &'a AirRouteConnection<'a>) {
203 self.connections.push(connection);
204 self.available_connections -= 1;
205 }
206}
207
208impl Airships {
209 const AIRSHIP_PORT_OFFSET: Vec2<f32> = Vec2::new(
210 -Airships::AIRSHIP_TO_DOCK_CENTERLINE_OFFSET,
211 -Airships::AIRSHIP_TO_DOCK_FORE_AFT_OFFSET,
212 );
213 const AIRSHIP_STARBOARD_OFFSET: Vec2<f32> = Vec2::new(
214 -Airships::AIRSHIP_TO_DOCK_CENTERLINE_OFFSET,
215 Airships::AIRSHIP_TO_DOCK_FORE_AFT_OFFSET,
216 );
217 const AIRSHIP_TO_DOCK_CENTERLINE_OFFSET: f32 = 18.0;
218 const AIRSHIP_TO_DOCK_FORE_AFT_OFFSET: f32 = 3.0;
219 const DEFAULT_DOCK_DURATION: f32 = 90.0;
220 const ROUTES_NORTH: Vec2<f32> = Vec2::new(0.0, 15000.0);
221 const STD_CRUISE_HAT: f32 = 300.0;
222 const TAKEOFF_ASCENT_ALT: f32 = 150.0;
223
224 #[inline(always)]
225 pub fn docking_duration() -> f32 { Airships::DEFAULT_DOCK_DURATION }
226
227 #[inline(always)]
228 pub fn takeoff_ascent_hat() -> f32 { Airships::TAKEOFF_ASCENT_ALT }
229
230 #[inline(always)]
231 pub fn std_cruise_hat() -> f32 { Airships::STD_CRUISE_HAT }
232
233 fn all_airshipdock_positions(sites: &mut Store<Site>) -> Vec<AirshipDockPositions> {
235 let mut dock_pos_id = 0;
236 sites
237 .iter()
238 .flat_map(|(site_id, site)| site.site2().map(|site2| (site_id, site2)))
239 .flat_map(|(site_id, site2)| {
240 site2.plots().flat_map(move |plot| {
241 if let Some(PlotKindMeta::AirshipDock {
242 center,
243 docking_positions,
244 ..
245 }) = plot.kind().meta()
246 {
247 Some((center, docking_positions, site_id))
248 } else {
249 None
250 }
251 })
252 })
253 .map(|(center, docking_positions, site_id)| {
254 let positions = AirshipDockPositions::from_plot_meta(
255 dock_pos_id,
256 center,
257 docking_positions,
258 site_id,
259 );
260
261 dock_pos_id += positions.docking_positions.len() as u32;
262 positions
263 })
264 .collect::<Vec<_>>()
265 }
266
267 pub fn generate_airship_routes(
289 &mut self,
290 sites: &mut Store<Site>,
291 world_sim: &mut WorldSim,
292 seed: u32,
293 ) {
294 let all_docking_positions = Airships::all_airshipdock_positions(sites);
295 let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
297 let mut routes = DHashMap::<DockConnectionHashKey, AirRouteConnection>::default();
298 all_docking_positions.iter().for_each(|from_dock| {
299 all_docking_positions
300 .iter()
301 .filter(|to_dock| to_dock.site_id != from_dock.site_id)
302 .for_each(|to_dock| {
303 routes.insert(
304 DockConnectionHashKey(from_dock.site_id, to_dock.site_id),
305 AirRouteConnection::new(from_dock, to_dock),
306 );
307 });
308 });
309
310 let mut dock_connections = all_docking_positions
334 .iter()
335 .map(DockConnection::new)
336 .collect::<Vec<_>>();
337
338 let angle_score_fn = |a1: f32, a2: f32| {
341 let optimal_angle = (a1 + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU);
342 let angle_diff = (optimal_angle - a2)
343 .abs()
344 .min(std::f32::consts::TAU - (optimal_angle - a2).abs());
345 1.0 - (angle_diff / std::f32::consts::PI)
346 };
347
348 let centroid_angle_score_fn =
352 |centroid: Vec2<f32>, dock_center: Vec2<f32>, rt: &AirRouteConnection| {
353 let centroid_dir = centroid - dock_center;
354 if centroid_dir.is_approx_zero() {
355 return 0.0;
356 }
357 let centroid_angle =
358 Airships::angle_between_vectors_ccw(Airships::ROUTES_NORTH, centroid_dir);
359 angle_score_fn(centroid_angle, rt.angle)
360 };
361
362 let distance_score_fn = |distance: i64| {
368 if distance > 25000000 {
370 (((distance - 24999000) / 1000) as f32).ln() / 8.0 - 0.5
371 } else {
372 0.0
373 }
374 };
375
376 let score_fn = |con: &DockConnection, rt: &AirRouteConnection| {
378 let mut angle_score = match con.connections.len() {
379 0 => 1.0,
381 1 => angle_score_fn(con.connections[0].angle, rt.angle),
383 2 => {
386 let centroid = (con.connections[0].dock2.center
387 + con.connections[1].dock2.center
388 + con.dock.center)
389 / 3.0;
390 centroid_angle_score_fn(centroid, con.dock.center, rt)
391 },
392 3 => {
394 let centroid = (con.connections[0].dock2.center
395 + con.connections[1].dock2.center
396 + con.connections[2].dock2.center)
397 / 3.0;
398 centroid_angle_score_fn(centroid, con.dock.center, rt)
399 },
400 _ => 0.0,
401 };
402 let distance_score = distance_score_fn(rt.distance);
403 angle_score *= 5.0;
408 (angle_score, distance_score, angle_score + distance_score)
409 };
410
411 for _ in 0..4 {
412 let mut best_trial: Option<(Vec<(Id<site::Site>, Id<site::Site>)>, f32)> = None;
413 for _ in 0..100 {
416 dock_connections.shuffle(&mut rng);
417 let candidates = dock_connections
418 .iter()
419 .filter(|con| con.available_connections > 0)
420 .collect::<Vec<_>>();
421 let mut trial = Vec::new();
422 let mut trial_score = 0f32;
423 for chunk in candidates.chunks(2) {
424 if let [con1, con2] = chunk {
425 let dock1_id = con1.dock.site_id;
426 let dock2_id = con2.dock.site_id;
427 let dock1_route = routes
428 .get(&DockConnectionHashKey(dock1_id, dock2_id))
429 .unwrap();
430 let dock2_route = routes
431 .get(&DockConnectionHashKey(dock2_id, dock1_id))
432 .unwrap();
433 let score1 = score_fn(con1, dock1_route);
434 let score2 = score_fn(con2, dock2_route);
435 trial_score += score1.2 + score2.2;
436 trial.push((dock1_id, dock2_id));
437 }
438 }
439 if let Some(current_best_trial) = best_trial.as_mut() {
440 if trial_score > current_best_trial.1 {
441 *current_best_trial = (trial, trial_score);
442 }
443 } else {
444 best_trial = Some((trial, trial_score));
445 }
446 }
447 if let Some(ref final_best_trial) = best_trial {
448 for (site1, site2) in final_best_trial.0.iter() {
449 let dock1_route = routes.get(&DockConnectionHashKey(*site1, *site2)).unwrap();
450 let dock2_route = routes.get(&DockConnectionHashKey(*site2, *site1)).unwrap();
451 let con1 = dock_connections
452 .iter_mut()
453 .find(|con| con.dock.site_id == *site1)
454 .unwrap();
455 if con1.available_connections > 0 {
456 con1.add_connection(dock1_route);
457 }
458 let con2 = dock_connections
459 .iter_mut()
460 .find(|con| con.dock.site_id == *site2)
461 .unwrap();
462 if con2.available_connections > 0 {
463 con2.add_connection(dock2_route);
464 }
465 }
466 }
467 }
468
469 let mut routes_added = DHashSet::<DockConnectionHashKey>::default();
486 let mut used_docking_positions = DHashSet::<u32>::default();
489
490 let mut random_dock_pos_fn =
491 |dock: &AirshipDockPositions, used_positions: &DHashSet<u32>| {
492 let mut dock_pos_index = rng.gen_range(0..dock.docking_positions.len());
493 let begin = dock_pos_index;
494 while used_positions.contains(&dock.docking_positions[dock_pos_index].0) {
495 dock_pos_index = (dock_pos_index + 1) % dock.docking_positions.len();
496 if dock_pos_index == begin {
497 return None;
498 }
499 }
500 Some(dock_pos_index)
501 };
502
503 let mut airship_route_id: u32 = 0;
504 dock_connections.iter().for_each(|con| {
505 con.connections.iter().for_each(|rt| {
506 if !routes_added
507 .contains(&DockConnectionHashKey(rt.dock1.site_id, rt.dock2.site_id))
508 {
509 if let Some(from_dock_pos_index) =
510 random_dock_pos_fn(rt.dock1, &used_docking_positions)
511 {
512 if let Some(to_dock_pos_index) =
513 random_dock_pos_fn(rt.dock2, &used_docking_positions)
514 {
515 let from_dock_pos_id =
516 rt.dock1.docking_positions[from_dock_pos_index].0;
517 let to_dock_pos_id = rt.dock2.docking_positions[to_dock_pos_index].0;
518 let approaches = Airships::airship_approaches_for_route(
519 world_sim,
520 rt,
521 from_dock_pos_id,
522 to_dock_pos_id,
523 );
524 let distance = rt.dock1.docking_positions[from_dock_pos_index]
525 .1
526 .xy()
527 .distance(rt.dock2.docking_positions[to_dock_pos_index].1.xy())
528 as u32;
529
530 self.routes.insert(
531 airship_route_id,
532 AirshipRoute::new(
533 rt.dock1.site_id,
534 rt.dock2.site_id,
535 approaches,
536 distance,
537 ),
538 );
539 airship_route_id += 1;
540
541 used_docking_positions.insert(from_dock_pos_id);
542 used_docking_positions.insert(to_dock_pos_id);
543 routes_added
544 .insert(DockConnectionHashKey(rt.dock1.site_id, rt.dock2.site_id));
545 routes_added
546 .insert(DockConnectionHashKey(rt.dock2.site_id, rt.dock1.site_id));
547 }
548 }
549 }
550 });
551 });
552 }
553
554 pub fn airship_route_for_docking_pos(
558 &self,
559 docking_pos: Vec3<f32>,
560 ) -> Option<(AirshipRouteId, usize)> {
561 if let Some((route_id, min_index, _)) = self
564 .routes
565 .iter()
566 .flat_map(|(rt_id, rt)| {
567 rt.approaches
568 .iter()
569 .enumerate()
570 .map(move |(index, approach)| {
571 let distance =
572 approach.dock_pos.1.xy().distance_squared(docking_pos.xy()) as i64;
573 (rt_id, index, distance)
574 })
575 })
576 .min_by_key(|(_, _, distance)| *distance)
577 {
578 Some((*route_id, min_index))
579 } else {
580 warn!(
583 "No airship route has a docking postion near {:?}",
584 docking_pos
585 );
586 None
587 }
588 }
589
590 pub fn should_spawn_airship_at_docking_position(
597 &self,
598 docking_pos: &Vec3<i32>,
599 site_name: &str,
600 ) -> bool {
601 let use_docking_pos = self.routes.iter().any(|(_, rt)| {
602 rt.approaches.iter().any(|approach| {
603 approach
604 .dock_pos
605 .1
606 .xy()
607 .distance_squared(docking_pos.map(|i| i as f32).xy())
608 < 10.0
609 })
610 });
611 if !use_docking_pos {
612 debug_airships!(
613 "Skipping docking position {:?} for site {}",
614 docking_pos,
615 site_name
616 );
617 }
618 use_docking_pos
619 }
620
621 pub fn airship_vec_for_docking_pos(
629 docking_pos: Vec3<f32>,
630 airship_dock_center: Vec2<f32>,
631 docking_side: Option<AirshipDockingSide>,
632 ) -> (Vec3<f32>, Dir) {
633 let dock_pos_offset = (docking_pos - airship_dock_center).xy();
635 let (ship_center_offset, reverse_ship) = match docking_side {
638 Some(AirshipDockingSide::Starboard) => (Airships::AIRSHIP_STARBOARD_OFFSET, false), Some(AirshipDockingSide::Port) => (Airships::AIRSHIP_PORT_OFFSET, true), None => {
642 if thread_rng().gen::<bool>() {
643 (Airships::AIRSHIP_STARBOARD_OFFSET, false) } else {
645 (Airships::AIRSHIP_PORT_OFFSET, true) }
647 },
648 };
649 let refvec = Vec2::unit_y();
651 let dock_pos_anglecw = Airships::angle_between_vectors_cw(refvec, dock_pos_offset);
653 let airship_offset = ship_center_offset
658 .rotated_z(std::f32::consts::TAU - (dock_pos_anglecw + std::f32::consts::FRAC_PI_2))
659 .with_z(3.0);
660 let rotation_angle = if reverse_ship {
665 std::f32::consts::FRAC_PI_2
666 } else {
667 std::f32::consts::FRAC_PI_2 * 3.0
668 };
669 let airship_dir =
670 Dir::from_unnormalized(dock_pos_offset.rotated_z(rotation_angle).with_z(0.0))
671 .unwrap_or_default();
672
673 (docking_pos + airship_offset, airship_dir)
675 }
676
677 fn docking_approach_for(
679 depart_center: Vec2<f32>,
680 dest_center: Vec2<f32>,
681 docking_pos: &AirshipDockingPosition,
682 depart_to_dest_angle: f32,
683 map_center: Vec2<f32>,
684 max_dims: Vec2<f32>,
685 site_id: Id<Site>,
686 ) -> AirshipDockingApproach {
687 let (airship_pos, airship_direction) = Airships::airship_vec_for_docking_pos(
688 docking_pos.1,
689 dest_center,
690 Some(AirshipDockingSide::Starboard),
691 );
692 let port_final_pos = docking_pos.1.xy() + airship_direction.to_vec().xy() * 500.0;
695 let starboard_final_pos = docking_pos.1.xy() - airship_direction.to_vec().xy() * 500.0;
696 let port_final_angle =
701 (airship_pos.xy() - port_final_pos).angle_between(-(depart_center - port_final_pos));
702 let starboard_final_angle = (airship_pos.xy() - starboard_final_pos)
704 .angle_between(-(depart_center - starboard_final_pos));
705
706 let side = if (port_final_angle - starboard_final_angle).abs() < 0.1 {
713 if port_final_angle < std::f32::consts::FRAC_PI_4 {
715 if port_final_pos.distance_squared(depart_center)
717 < starboard_final_pos.distance_squared(depart_center)
718 {
719 AirshipDockingSide::Port
721 } else {
722 AirshipDockingSide::Starboard
724 }
725 } else {
726 if port_final_pos.distance_squared(map_center)
729 < starboard_final_pos.distance_squared(map_center)
730 {
731 AirshipDockingSide::Port
733 } else {
734 AirshipDockingSide::Starboard
736 }
737 }
738 } else {
739 if port_final_angle < starboard_final_angle {
741 AirshipDockingSide::Port
743 } else {
744 AirshipDockingSide::Starboard
746 }
747 };
748
749 let height = if depart_to_dest_angle < std::f32::consts::PI {
750 Airships::STD_CRUISE_HAT
751 } else {
752 Airships::STD_CRUISE_HAT + 100.0
753 };
754
755 let check_pos_fn = |pos: Vec2<f32>, what: &str| {
756 if pos.x < 0.0 || pos.y < 0.0 || pos.x > max_dims.x || pos.y > max_dims.y {
757 warn!("{} pos out of bounds: {:?}", what, pos);
758 }
759 };
760
761 let initial_pos_fn = |final_pos: Vec2<f32>| {
762 let line1 = (depart_center - final_pos).normalized();
768 let angle = line1.angle_between((airship_pos.xy() - final_pos).normalized());
769 let initial_pos_line = line1.rotated_z(angle / 2.0 + 3.0 * std::f32::consts::FRAC_PI_2);
770 let initial_pos = final_pos + initial_pos_line * 500.0;
771 check_pos_fn(final_pos, "final_pos");
772 check_pos_fn(initial_pos, "initial_pos");
773 initial_pos
774 };
775
776 if side == AirshipDockingSide::Starboard {
777 AirshipDockingApproach {
778 dock_pos: *docking_pos,
779 airship_pos,
780 airship_direction,
781 dock_center: dest_center,
782 height,
783 approach_initial_pos: initial_pos_fn(starboard_final_pos),
784 approach_final_pos: starboard_final_pos,
785 side,
786 site_id,
787 }
788 } else {
789 AirshipDockingApproach {
790 dock_pos: *docking_pos,
791 airship_pos,
792 airship_direction: -airship_direction,
793 dock_center: dest_center,
794 height,
795 approach_initial_pos: initial_pos_fn(port_final_pos),
796 approach_final_pos: port_final_pos,
797 side,
798 site_id,
799 }
800 }
801 }
802
803 fn airship_approaches_for_route(
817 world_sim: &mut WorldSim,
818 route: &AirRouteConnection,
819 dock1_position_id: u32,
820 dock2_position_id: u32,
821 ) -> [AirshipDockingApproach; 2] {
822 let map_size_chunks = world_sim.get_size().map(|u| u as i32);
833 let max_dims = map_size_chunks.cpos_to_wpos().map(|u| u as f32);
834 let map_center = Vec2::new(max_dims.x / 2.0, max_dims.y / 2.0);
835
836 let dock1_positions = &route.dock1;
837 let dock2_positions = &route.dock2;
838 let dock1_center = dock1_positions.center;
839 let dock2_center = dock2_positions.center;
840 let docking_pos1 = dock1_positions
841 .docking_positions
842 .iter()
843 .find(|dp| dp.0 == dock1_position_id)
844 .unwrap();
845 let docking_pos2 = dock2_positions
846 .docking_positions
847 .iter()
848 .find(|dp| dp.0 == dock2_position_id)
849 .unwrap();
850 let dock1_to_dock2_angle = Airships::angle_between_vectors_ccw(
851 Airships::ROUTES_NORTH,
852 docking_pos2.1.xy() - docking_pos1.1.xy(),
853 );
854 let dock2_to_dock1_angle = std::f32::consts::TAU - dock1_to_dock2_angle;
855 debug_airships!(
856 "airship_approaches_for_route - dock1_pos:{:?}, dock2_pos:{:?}, \
857 dock1_to_dock2_angle:{}, dock2_to_dock1_angle:{}",
858 docking_pos1,
859 docking_pos2,
860 dock1_to_dock2_angle,
861 dock2_to_dock1_angle
862 );
863
864 [
865 Airships::docking_approach_for(
866 dock1_center,
867 dock2_center,
868 docking_pos2,
869 dock1_to_dock2_angle,
870 map_center,
871 max_dims,
872 dock2_positions.site_id,
873 ),
874 Airships::docking_approach_for(
875 dock2_center,
876 dock1_center,
877 docking_pos1,
878 dock2_to_dock1_angle,
879 map_center,
880 max_dims,
881 dock1_positions.site_id,
882 ),
883 ]
884 }
885
886 fn angle_between_vectors_ccw(v1: Vec2<f32>, v2: Vec2<f32>) -> f32 {
888 let dot_product = v1.dot(v2);
889 let det = v1.x * v2.y - v1.y * v2.x; let angle = det.atan2(dot_product); if angle < 0.0 {
892 angle + std::f32::consts::TAU
893 } else {
894 angle
895 }
896 }
897
898 fn angle_between_vectors_cw(v1: Vec2<f32>, v2: Vec2<f32>) -> f32 {
900 let ccw_angle = Airships::angle_between_vectors_ccw(v1, v2);
901 std::f32::consts::TAU - ccw_angle
902 }
903}
904
905fn write_airship_routes_log(file_path: &str, jsonstr: &str) -> std::io::Result<()> {
907 let mut file = OpenOptions::new()
908 .write(true)
909 .create(true)
910 .truncate(true)
911 .open(file_path)?;
912 file.write_all(jsonstr.as_bytes())?;
913 Ok(())
914}
915
916#[cfg(test)]
917mod tests {
918 use super::{AirshipDockingSide, Airships, approx::assert_relative_eq};
919 use vek::{Quaternion, Vec2, Vec3};
920
921 #[test]
922 fn basic_vec_test() {
923 let vec1 = Vec3::new(0.0f32, 10.0, 0.0);
924 let vec2 = Vec3::new(10.0, 0.0, 0.0);
925 let a12 = vec2.angle_between(vec1);
926 assert_relative_eq!(a12, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
927
928 let rotc2 = Quaternion::rotation_z(a12);
929 let vec3 = rotc2 * vec2;
930 assert!(vec3 == vec1);
931 }
932
933 #[test]
934 fn std_vec_angles_test() {
935 let refvec = Vec2::new(0.0f32, 10.0);
936
937 let vec1 = Vec2::new(0.0f32, 10.0);
938 let vec2 = Vec2::new(10.0f32, 0.0);
939 let vec3 = Vec2::new(0.0f32, -10.0);
940 let vec4 = Vec2::new(-10.0f32, 0.0);
941
942 let a1r = vec1.angle_between(refvec);
943 assert!(a1r == 0.0f32);
944
945 let a2r = vec2.angle_between(refvec);
946 assert_relative_eq!(a2r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
947
948 let a3r: f32 = vec3.angle_between(refvec);
949 assert_relative_eq!(a3r, std::f32::consts::PI, epsilon = 0.00001);
950
951 let a4r = vec4.angle_between(refvec);
952 assert_relative_eq!(a4r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
953 }
954
955 #[test]
956 fn vec_angles_test() {
957 let refvec = Vec3::new(0.0f32, 10.0, 0.0);
958
959 let vec1 = Vec3::new(0.0f32, 10.0, 0.0);
960 let vec2 = Vec3::new(10.0f32, 0.0, 0.0);
961 let vec3 = Vec3::new(0.0f32, -10.0, 0.0);
962 let vec4 = Vec3::new(-10.0f32, 0.0, 0.0);
963
964 let a1r = vec1.angle_between(refvec);
965 let a1r3 = Airships::angle_between_vectors_ccw(vec1.xy(), refvec.xy());
966 assert!(a1r == 0.0f32);
967 assert!(a1r3 == 0.0f32);
968
969 let a2r = vec2.angle_between(refvec);
970 let a2r3 = Airships::angle_between_vectors_ccw(vec2.xy(), refvec.xy());
971 assert_relative_eq!(a2r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
972 assert_relative_eq!(a2r3, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
973
974 let a3r: f32 = vec3.angle_between(refvec);
975 let a3r3 = Airships::angle_between_vectors_ccw(vec3.xy(), refvec.xy());
976 assert_relative_eq!(a3r, std::f32::consts::PI, epsilon = 0.00001);
977 assert_relative_eq!(a3r3, std::f32::consts::PI, epsilon = 0.00001);
978
979 let a4r = vec4.angle_between(refvec);
980 let a4r3 = Airships::angle_between_vectors_ccw(vec4.xy(), refvec.xy());
981 assert_relative_eq!(a4r, std::f32::consts::FRAC_PI_2, epsilon = 0.00001);
982 assert_relative_eq!(a4r3, std::f32::consts::FRAC_PI_2 * 3.0, epsilon = 0.00001);
983 }
984
985 #[test]
986 fn airship_angles_test() {
987 let refvec = Vec2::new(0.0f32, 37.0);
988 let ovec = Vec2::new(-4.0f32, -14.0);
989 let oveccw0 = Vec2::new(-4, -14);
990 let oveccw90 = Vec2::new(-14, 4);
991 let oveccw180 = Vec2::new(4, 14);
992 let oveccw270 = Vec2::new(14, -4);
993 let ovecccw0 = Vec2::new(-4, -14);
994 let ovecccw90 = Vec2::new(14, -4);
995 let ovecccw180 = Vec2::new(4, 14);
996 let ovecccw270 = Vec2::new(-14, 4);
997
998 let vec1 = Vec2::new(0.0f32, 37.0);
999 let vec2 = Vec2::new(37.0f32, 0.0);
1000 let vec3 = Vec2::new(0.0f32, -37.0);
1001 let vec4 = Vec2::new(-37.0f32, 0.0);
1002
1003 assert!(
1004 ovec.rotated_z(Airships::angle_between_vectors_cw(vec1, refvec))
1005 .map(|x| x.round() as i32)
1006 == oveccw0
1007 );
1008 assert!(
1009 ovec.rotated_z(Airships::angle_between_vectors_cw(vec2, refvec))
1010 .map(|x| x.round() as i32)
1011 == oveccw90
1012 );
1013 assert!(
1014 ovec.rotated_z(Airships::angle_between_vectors_cw(vec3, refvec))
1015 .map(|x| x.round() as i32)
1016 == oveccw180
1017 );
1018 assert!(
1019 ovec.rotated_z(Airships::angle_between_vectors_cw(vec4, refvec))
1020 .map(|x| x.round() as i32)
1021 == oveccw270
1022 );
1023
1024 assert!(
1025 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec1, refvec))
1026 .map(|x| x.round() as i32)
1027 == ovecccw0
1028 );
1029 assert!(
1030 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec2, refvec))
1031 .map(|x| x.round() as i32)
1032 == ovecccw90
1033 );
1034 assert!(
1035 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec3, refvec))
1036 .map(|x| x.round() as i32)
1037 == ovecccw180
1038 );
1039 assert!(
1040 ovec.rotated_z(Airships::angle_between_vectors_ccw(vec4, refvec))
1041 .map(|x| x.round() as i32)
1042 == ovecccw270
1043 );
1044 }
1045
1046 #[test]
1047 fn airship_vec_test() {
1048 {
1049 let dock_pos = Vec3::new(10.0f32, 10.0, 0.0);
1050 let airship_dock_center = Vec2::new(0.0, 0.0);
1051 {
1052 let (airship_pos, airship_dir) =
1053 Airships::airship_vec_for_docking_pos(dock_pos, airship_dock_center, None);
1054 if airship_pos.x > 21.0 {
1055 assert_relative_eq!(
1056 airship_pos,
1057 Vec3 {
1058 x: 24.84924,
1059 y: 20.606606,
1060 z: 3.0
1061 },
1062 epsilon = 0.00001
1063 );
1064 assert_relative_eq!(
1065 airship_dir.to_vec(),
1066 Vec3 {
1067 x: 0.70710677,
1068 y: -0.70710677,
1069 z: 0.0
1070 },
1071 epsilon = 0.00001
1072 );
1073 } else {
1074 assert_relative_eq!(
1075 airship_pos,
1076 Vec3 {
1077 x: 20.606598,
1078 y: 24.849243,
1079 z: 3.0
1080 },
1081 epsilon = 0.00001
1082 );
1083 assert_relative_eq!(
1084 airship_dir.to_vec(),
1085 Vec3 {
1086 x: -0.70710677,
1087 y: 0.70710677,
1088 z: 0.0
1089 },
1090 epsilon = 0.00001
1091 );
1092 }
1093 }
1094 {
1095 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
1096 dock_pos,
1097 airship_dock_center,
1098 Some(AirshipDockingSide::Port),
1099 );
1100 assert_relative_eq!(
1101 airship_pos,
1102 Vec3 {
1103 x: 20.606598,
1104 y: 24.849243,
1105 z: 3.0
1106 },
1107 epsilon = 0.00001
1108 );
1109 assert_relative_eq!(
1110 airship_dir.to_vec(),
1111 Vec3 {
1112 x: -0.70710677,
1113 y: 0.70710677,
1114 z: 0.0
1115 },
1116 epsilon = 0.00001
1117 );
1118 }
1119 {
1120 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
1121 dock_pos,
1122 airship_dock_center,
1123 Some(AirshipDockingSide::Starboard),
1124 );
1125 assert_relative_eq!(
1126 airship_pos,
1127 Vec3 {
1128 x: 24.84924,
1129 y: 20.606606,
1130 z: 3.0
1131 },
1132 epsilon = 0.00001
1133 );
1134 assert_relative_eq!(
1135 airship_dir.to_vec(),
1136 Vec3 {
1137 x: 0.70710677,
1138 y: -0.70710677,
1139 z: 0.0
1140 },
1141 epsilon = 0.00001
1142 );
1143 }
1144 }
1145 {
1146 let dock_pos = Vec3::new(28874.0, 18561.0, 0.0);
1147 let airship_dock_center = Vec2::new(28911.0, 18561.0);
1148 {
1149 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
1150 dock_pos,
1151 airship_dock_center,
1152 Some(AirshipDockingSide::Port),
1153 );
1154 assert_relative_eq!(
1155 airship_pos,
1156 Vec3 {
1157 x: 28856.0,
1158 y: 18558.0,
1159 z: 3.0
1160 },
1161 epsilon = 0.00001
1162 );
1163 assert_relative_eq!(
1164 airship_dir.to_vec(),
1165 Vec3 {
1166 x: 4.371139e-8,
1167 y: -1.0,
1168 z: 0.0
1169 },
1170 epsilon = 0.00001
1171 );
1172 }
1173 {
1174 let (airship_pos, airship_dir) = Airships::airship_vec_for_docking_pos(
1175 dock_pos,
1176 airship_dock_center,
1177 Some(AirshipDockingSide::Starboard),
1178 );
1179 assert_relative_eq!(
1180 airship_pos,
1181 Vec3 {
1182 x: 28856.0,
1183 y: 18564.0,
1184 z: 3.0
1185 },
1186 epsilon = 0.00001
1187 );
1188 assert_relative_eq!(
1189 airship_dir.to_vec(),
1190 Vec3 {
1191 x: -1.1924881e-8,
1192 y: 1.0,
1193 z: 0.0
1194 },
1195 epsilon = 0.00001
1196 );
1197 }
1198 }
1199 }
1200
1201 #[test]
1202 fn angle_score_test() {
1203 let rt_angles = [
1204 0.0,
1205 std::f32::consts::FRAC_PI_2,
1206 std::f32::consts::PI,
1207 std::f32::consts::FRAC_PI_2 * 3.0,
1208 ];
1209 let con_angles = [
1210 0.0,
1211 std::f32::consts::FRAC_PI_2,
1212 std::f32::consts::PI,
1213 std::f32::consts::FRAC_PI_2 * 3.0,
1214 ];
1215 let scores = [
1216 [0.0, 2.5, 5.0, 2.5],
1217 [2.5, 0.0, 2.5, 5.0],
1218 [5.0, 2.5, 0.0, 2.5],
1219 [2.5, 5.0, 2.5, 0.0],
1220 ];
1221 let score_fn2 = |a1: f32, a2: f32| {
1222 let optimal_angle = (a1 + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU);
1223 let angle_diff = (optimal_angle - a2)
1224 .abs()
1225 .min(std::f32::consts::TAU - (optimal_angle - a2).abs());
1226 (1.0 - (angle_diff / std::f32::consts::PI)) * 5.0
1227 };
1228 let mut i = 0;
1229 let mut j = 0;
1230 rt_angles.iter().for_each(|rt_angle| {
1231 j = 0;
1232 con_angles.iter().for_each(|con_angle| {
1233 let score = score_fn2(*con_angle, *rt_angle);
1234 assert_relative_eq!(score, scores[i][j], epsilon = 0.00001);
1235 j += 1;
1236 });
1237 i += 1;
1238 });
1239 }
1240
1241 #[test]
1242 fn distance_score_test() {
1243 let distances = [1, 1000, 5001, 6000, 15000, 30000, 48000];
1244 let scores = [
1245 0.0,
1246 0.0,
1247 -0.20026308,
1248 0.66321766,
1249 1.0257597,
1250 1.2102475,
1251 1.329906,
1252 ];
1253 let score_fn = |distance: i64| {
1254 if distance > 25000000 {
1255 (((distance - 24999000) / 1000) as f32).ln() / 8.0 - 0.5
1256 } else {
1257 0.0
1258 }
1259 };
1260 let mut i = 0;
1261 distances.iter().for_each(|distance| {
1262 let dist2 = *distance * *distance;
1263 let score = score_fn(dist2);
1264 assert_relative_eq!(score, scores[i], epsilon = 0.00001);
1265 i += 1;
1266 });
1267 }
1268}