rpfm_extensions/startpos/
trade.rs1use getset::Getters;
18use serde_derive::{Deserialize, Serialize};
19
20use std::collections::HashSet;
21
22use rpfm_lib::files::trade_routes::{TradeRoute, TradeRouteEdge, TradeRoutes, TradeSplineSegment};
23
24use super::coords::MapScale;
25
26#[cfg(test)] #[path = "trade_test.rs"] mod trade_test;
27
28const GAUSS_NODES: [f64; 5] = [-0.906_179_845_938_664, -0.538_469_310_105_683, 0.0, 0.538_469_310_105_683, 0.906_179_845_938_664];
30
31const GAUSS_WEIGHTS: [f64; 5] = [0.236_926_885_056_189, 0.478_628_670_499_366, 0.568_888_888_888_889, 0.478_628_670_499_366, 0.236_926_885_056_189];
33
34const LENGTH_SUBINTERVALS: usize = 64;
36
37const SPEED_SAMPLES: usize = 100;
39
40#[derive(Debug, Clone, PartialEq, Default, Getters, Serialize, Deserialize)]
46#[getset(get = "pub")]
47pub struct TradeNetwork {
48
49 regions: Vec<String>,
51
52 splines: Vec<NetworkSpline>,
54
55 region_routes: Vec<NetworkRoute>,
57
58 port_routes: Vec<NetworkRoute>,
60}
61
62#[derive(Debug, Clone, PartialEq, Default, Getters, Serialize, Deserialize)]
64#[getset(get = "pub")]
65pub struct NetworkSpline {
66
67 flags: [bool; 4],
69
70 segments: Vec<[(f32, f32); 4]>,
72
73 speed_tables: Vec<Vec<f32>>,
75
76 lengths: Vec<f32>,
78
79 total_length: f32,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Default, Getters, Serialize, Deserialize)]
85#[getset(get = "pub")]
86pub struct NetworkRoute {
87
88 from: u32,
90
91 to: u32,
93
94 edges: Vec<TradeRouteEdge>,
96}
97
98impl TradeNetwork {
103
104 pub fn from_trade_routes(trade_routes: &TradeRoutes, scale: &MapScale) -> Self {
115 let splines = trade_routes.splines().iter().map(|spline| {
116 let segments = spline.segments().iter().map(|segment| control_points_world(segment, scale)).collect::<Vec<_>>();
117 let lengths = segments.iter().map(bezier_length).collect::<Vec<_>>();
118 let speed_tables = segments.iter().map(speed_table).collect();
119 let total_length = lengths.iter().sum();
120 NetworkSpline {
121 flags: *spline.flags(),
122 segments,
123 speed_tables,
124 lengths,
125 total_length,
126 }
127 }).collect();
128
129 Self {
130 regions: trade_routes.regions().clone(),
131 splines,
132 region_routes: first_route_per_pair(trade_routes.region_routes()),
133 port_routes: first_route_per_pair(trade_routes.port_routes()),
134 }
135 }
136}
137
138fn first_route_per_pair(routes: &[TradeRoute]) -> Vec<NetworkRoute> {
140 let mut seen = HashSet::new();
141 routes.iter()
142 .filter(|route| seen.insert((*route.from(), *route.to())))
143 .map(|route| NetworkRoute {
144 from: *route.from(),
145 to: *route.to(),
146 edges: route.edges().clone(),
147 })
148 .collect()
149}
150
151fn control_points_world(segment: &TradeSplineSegment, scale: &MapScale) -> [(f32, f32); 4] {
154 let start = scale.raw_to_world(f32::from(segment.start().0), f32::from(segment.start().1));
155 let end = scale.raw_to_world(f32::from(segment.end().0), f32::from(segment.end().1));
156 let start_offset = scale.raw_offset_to_world(segment.start_control().0, segment.start_control().1);
157 let end_offset = scale.raw_offset_to_world(segment.end_control().0, segment.end_control().1);
158 [
159 start,
160 (start.0 + start_offset.0, start.1 + start_offset.1),
161 (end.0 + end_offset.0, end.1 + end_offset.1),
162 end,
163 ]
164}
165
166fn bezier_speed(points: &[(f32, f32); 4], t: f64) -> f64 {
168 let [p0, p1, p2, p3] = points.map(|(x, y)| (f64::from(x), f64::from(y)));
169 let u = 1.0 - t;
170 let dx = 3.0 * u * u * (p1.0 - p0.0) + 6.0 * u * t * (p2.0 - p1.0) + 3.0 * t * t * (p3.0 - p2.0);
171 let dy = 3.0 * u * u * (p1.1 - p0.1) + 6.0 * u * t * (p2.1 - p1.1) + 3.0 * t * t * (p3.1 - p2.1);
172 (dx * dx + dy * dy).sqrt()
173}
174
175fn speed_table(points: &[(f32, f32); 4]) -> Vec<f32> {
178 let mut sum = 0.0;
179 (0..SPEED_SAMPLES).map(|sample| {
180 sum += bezier_speed(points, sample as f64 / SPEED_SAMPLES as f64);
181 sum as f32
182 }).collect()
183}
184
185fn bezier_length(points: &[(f32, f32); 4]) -> f32 {
187 let speed = |t: f64| bezier_speed(points, t);
188
189 let step = 1.0 / LENGTH_SUBINTERVALS as f64;
190 let length: f64 = (0..LENGTH_SUBINTERVALS).map(|interval| {
191 let start = interval as f64 * step;
192 GAUSS_NODES.iter().zip(GAUSS_WEIGHTS.iter())
193 .map(|(node, weight)| weight * speed(start + (node + 1.0) * step / 2.0))
194 .sum::<f64>() * step / 2.0
195 }).sum();
196
197 length as f32
198}