From 6ee3e6cfb58bb84d4014ab4997a07689036d546e Mon Sep 17 00:00:00 2001 From: Teun Date: Thu, 11 Jan 2024 16:45:32 +0100 Subject: [PATCH 1/9] More functionality in the roadbuild tools Straight for road and rail: Based on the start and end MapProject, a dot will appear that indicates a straight connection from the selected MapProject. Curved connection ProjectState: When clicking on any MapProject and Ground or Intersection, a straight road would be created. Now an interpolation point needs to be set. Function for line line intersection With four points, representing two lines, the intersection of these two lines is calculated Function for line closed point Given two points representing one line and one point, the closed point on the line from the third point is calculated. --- geom/src/v2.rs | 15 ++ native_app/src/gui/roadbuild.rs | 268 +++++++++++++++------ native_app/src/gui/topgui.rs | 1 + simulation/src/map/objects/intersection.rs | 45 +++- simulation/src/map/objects/road.rs | 15 ++ 5 files changed, 264 insertions(+), 80 deletions(-) diff --git a/geom/src/v2.rs b/geom/src/v2.rs index 7c7740be..0cf327f5 100644 --- a/geom/src/v2.rs +++ b/geom/src/v2.rs @@ -403,6 +403,21 @@ impl Vec2 { pub fn rotated_by_angle(self, angle: Radians) -> Self { self.rotated_by(vec2(angle.cos(), angle.sin())) } + + pub fn line_line_intersection(a: Self, b: Self, c: Self, d: Self) -> Self { + let denominator = (a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x); + let (axbyaybx, cxdycydx) = (a.x*b.y-a.y*b.x, c.x*d.y-c.y*d.x); + + Self::new( + ((c.x-d.x)*axbyaybx - (a.x-b.x)*cxdycydx) / denominator, + ((c.y-d.y)*axbyaybx - (a.y-b.y)*cxdycydx) / denominator, + ) + } + + pub fn line_closed_point(p: Self, a: Self, b: Self) -> Self { + let ab_min = b - a; + a + ab_min * (ab_min.dot(p-a) / ab_min.mag2()) + } } impl Vec2d { diff --git a/native_app/src/gui/roadbuild.rs b/native_app/src/gui/roadbuild.rs index b089f998..8cbce26a 100644 --- a/native_app/src/gui/roadbuild.rs +++ b/native_app/src/gui/roadbuild.rs @@ -6,7 +6,7 @@ use simulation::map::{ }; use simulation::world_command::{WorldCommand, WorldCommands}; use simulation::Simulation; -use BuildState::{Hover, Interpolation, Start, StartInterp}; +use BuildState::{Hover, Interpolation, Start, StartInterp, Connection}; use ProjectKind::{Building, Ground, Inter, Road}; use crate::gui::{PotentialCommands, Tool}; @@ -20,6 +20,7 @@ pub enum BuildState { Hover, Start(MapProject), StartInterp(MapProject), + Connection(MapProject, MapProject), Interpolation(Vec2, MapProject), } @@ -51,6 +52,15 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { let mousepos = if state.snap_to_grid { let v = unproj.xy().snap(grid_size, grid_size); v.z(unwrap_ret!(map.environment.height(v)) + state.height_offset) + } else if state.snap_to_angle { + state.streight_points = state._update_points(map, unproj.up(state.height_offset)); + state.streight_points.iter() + .filter_map(|&point| { + let distance = point.distance(unproj); + if distance < grid_size {Some((point, distance))} else { None } + }) + .reduce(|acc, e| { if acc.1 < e.1 {acc} else { e } }) + .unwrap_or((unproj.up(state.height_offset), 0.0)).0 } else { unproj.up(state.height_offset) }; @@ -116,11 +126,14 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { state.height_offset = state.height_offset.max(0.0); } - let mut cur_proj = map.project( - mousepos, - (log_camheight * 5.0).clamp(1.0, 10.0), - ProjectFilter::INTER | ProjectFilter::ROAD, - ); + let mut cur_proj = if !matches!(state.build_state, Connection(..)) { + map.project(mousepos, + (log_camheight * 5.0).clamp(1.0, 10.0), + ProjectFilter::INTER | ProjectFilter::ROAD + ) + } else { + MapProject::ground(mousepos) + }; let patwidth = state.pattern_builder.width(); @@ -157,55 +170,80 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { let mut is_valid = match (state.build_state, cur_proj.kind) { (Hover, Building(_)) => false, - (Start(selected_proj) | StartInterp(selected_proj), _) => { + (StartInterp(sel_proj), Ground) => { + compatible(map, cur_proj, sel_proj) + && check_angle(map, sel_proj, cur_proj.pos.xy(), is_rail) + } + (StartInterp(sel_proj), Inter(_)|Road(_)) => { + compatible(map, sel_proj, cur_proj) + } + (Start(selected_proj), _) => { let sp = BoldLine::new( PolyLine::new(vec![selected_proj.pos.xy(), cur_proj.pos.xy()]), patwidth * 0.5, ); compatible(map, cur_proj, selected_proj) - && check_angle(map, selected_proj, cur_proj.pos.xy(), is_rail) - && check_angle(map, cur_proj, selected_proj.pos.xy(), is_rail) - && !check_intersect( - map, - &ShapeEnum::BoldLine(sp), - (selected_proj.pos.z + cur_proj.pos.z) / 2.0, - cur_proj.kind, - selected_proj.kind, - ) + && check_angle(map, selected_proj, cur_proj.pos.xy(), is_rail) + && check_angle(map, cur_proj, selected_proj.pos.xy(), is_rail) + && !check_intersect( + map, &ShapeEnum::BoldLine(sp), + (selected_proj.pos.z + cur_proj.pos.z) / 2.0, + cur_proj.kind, selected_proj.kind, + ) + } + (Connection(src, dst), _) => { + let sp = Spline { + from: src.pos.xy(), to: dst.pos.xy(), + from_derivative: (cur_proj.pos.xy() - src.pos.xy()) * std::f32::consts::FRAC_1_SQRT_2, + to_derivative: (dst.pos.xy() - cur_proj.pos.xy()) * std::f32::consts::FRAC_1_SQRT_2, + }; + + compatible(map, dst, src) + && check_angle(map, src, cur_proj.pos.xy(), is_rail) + && check_angle(map, dst, cur_proj.pos.xy(), is_rail) + && !sp.is_steep(state.pattern_builder.width()) + && !check_intersect( + map, &ShapeEnum::BoldSpline(BoldSpline::new(sp, patwidth * 0.5)), + (src.pos.z + dst.pos.z) / 2.0, + src.kind, dst.kind, + ) } (Interpolation(interpoint, selected_proj), _) => { let sp = Spline { from: selected_proj.pos.xy(), to: cur_proj.pos.xy(), - from_derivative: (interpoint - selected_proj.pos.xy()) - * std::f32::consts::FRAC_1_SQRT_2, + from_derivative: (interpoint - selected_proj.pos.xy()) * std::f32::consts::FRAC_1_SQRT_2, to_derivative: (cur_proj.pos.xy() - interpoint) * std::f32::consts::FRAC_1_SQRT_2, }; compatible(map, cur_proj, selected_proj) - && check_angle(map, selected_proj, interpoint, is_rail) - && check_angle(map, cur_proj, interpoint, is_rail) - && !sp.is_steep(state.pattern_builder.width()) - && !check_intersect( - map, - &ShapeEnum::BoldSpline(BoldSpline::new(sp, patwidth * 0.5)), - (selected_proj.pos.z + cur_proj.pos.z) / 2.0, - selected_proj.kind, - cur_proj.kind, - ) + && check_angle(map, selected_proj, interpoint, is_rail) + && check_angle(map, cur_proj, interpoint, is_rail) + && !sp.is_steep(state.pattern_builder.width()) + && !check_intersect( + map, &ShapeEnum::BoldSpline(BoldSpline::new(sp, patwidth * 0.5)), + (selected_proj.pos.z + cur_proj.pos.z) / 2.0, + selected_proj.kind, cur_proj.kind, + ) } _ => true, }; let build_args = match state.build_state { StartInterp(selected_proj) if !cur_proj.is_ground() => { - Some((selected_proj, None, state.pattern_builder.build())) + Some((selected_proj, cur_proj, None, state.pattern_builder.build())) + } + Start(selected_proj) => { + Some((selected_proj, cur_proj, None, state.pattern_builder.build())) + }, + Connection(src, dst) => { + Some((src, dst, Some(cur_proj.pos.xy()), state.pattern_builder.build())) } - Start(selected_proj) => Some((selected_proj, None, state.pattern_builder.build())), + Interpolation(interpoint, selected_proj) => { let inter = Some(interpoint); - Some((selected_proj, inter, state.pattern_builder.build())) + Some((selected_proj, cur_proj, inter, state.pattern_builder.build())) } _ => None, }; @@ -213,22 +251,19 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { let mut points = None; - if let Some((selected_proj, inter, pat)) = build_args { + if let Some((src, dst, inter, pat)) = build_args { potential_command.set(WorldCommand::MapMakeConnection { - from: selected_proj, - to: cur_proj, - inter, - pat, + from: src, to: dst, inter, pat, }); let connection_segment = match inter { - Some(x) => RoadSegmentKind::from_elbow(selected_proj.pos.xy(), cur_proj.pos.xy(), x), + Some(x) => RoadSegmentKind::from_elbow(src.pos.xy(), dst.pos.xy(), x), None => RoadSegmentKind::Straight, }; let (p, err) = simulation::map::Road::generate_points( - selected_proj.pos, - cur_proj.pos, + src.pos, + dst.pos, connection_segment, is_rail, &map.environment, @@ -242,14 +277,10 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { state.update_drawing(map, immdraw, cur_proj, patwidth, is_valid, points); if is_valid && inp.just_act.contains(&InputAction::Select) { - log::info!( - "left clicked with state {:?} and {:?}", - state.build_state, - cur_proj.kind - ); + log::info!("left clicked with state {:?} and {:?}", state.build_state, cur_proj.kind); match (state.build_state, cur_proj.kind) { - (Hover, Ground) | (Hover, Road(_)) | (Hover, Inter(_)) => { + (Hover, Ground|Road(_)|Inter(_)) => { // Hover selection if tool == Tool::RoadbuildCurved { state.build_state = StartInterp(cur_proj); @@ -261,13 +292,24 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { // Set interpolation point state.build_state = Interpolation(mousepos.xy(), v); } - (Start(_) | StartInterp(_), _) => { + (StartInterp(p), Road(_)|Inter(_)) => { + // Set interpolation point + state.build_state = Connection(p, cur_proj); + } + + (Start(_), _) => { // Straight connection to something immsound.play("road_lay", AudioKind::Ui); if let Some(wc) = potential_command.0.drain(..).next() { commands.push(wc); } - + state.build_state = Hover; + } + (Connection(_, _), _) => { + immsound.play("road_lay", AudioKind::Ui); + if let Some(wc) = potential_command.0.drain(..).next() { + commands.push(wc); + } state.build_state = Hover; } (Interpolation(_, _), _) => { @@ -276,7 +318,6 @@ pub fn roadbuild(sim: &Simulation, uiworld: &mut UiWorld) { if let Some(wc) = potential_command.0.drain(..).next() { commands.push(wc); } - state.build_state = Hover; } _ => {} @@ -289,42 +330,35 @@ pub struct RoadBuildResource { pub build_state: BuildState, pub pattern_builder: LanePatternBuilder, pub snap_to_grid: bool, + pub snap_to_angle: bool, pub height_offset: f32, + pub streight_points: Vec, } fn check_angle(map: &Map, from: MapProject, to: Vec2, is_rail: bool) -> bool { let max_turn_angle = if is_rail { - 1.0 * std::f32::consts::PI / 180.0 + 0.0 } else { 30.0 * std::f32::consts::PI / 180.0 }; match from.kind { Inter(i) => { - let inter = &map.intersections()[i]; + let Some(inter) = map.intersections().get(i) else {return false;}; let dir = (to - inter.pos.xy()).normalize(); - for &road in &inter.roads { - let road = &map.roads()[road]; - let v = road.dir_from(i); - if v.angle(dir).abs() < max_turn_angle { - return false; - } - } - true + + inter.roads.iter() + .map(|road_id| map.roads()[*road_id].dir_from(i)) + .any(|v| {v.angle(dir).abs() >= max_turn_angle}) } Road(r) => { - let Some(r) = map.roads().get(r) else { - return false; - }; + let Some(r) = map.roads().get(r) else { return false; }; let (proj, _, rdir1) = r.points().project_segment_dir(from.pos); let rdir2 = -rdir1; let dir = (to - proj.xy()).normalize(); - if rdir1.xy().angle(dir).abs() < max_turn_angle - || rdir2.xy().angle(dir).abs() < max_turn_angle - { - return false; - } - true + + rdir1.xy().angle(dir).abs() >= max_turn_angle + && rdir2.xy().angle(dir).abs() >= max_turn_angle } _ => true, } @@ -363,7 +397,7 @@ fn check_intersect( .any(move |x| { if let Road(rid) = x { let r = &map.roads()[rid]; - if (r.points.first().z - z).abs() > 0.1 || (r.points.last().z - z).abs() > 0.1 { + if (r.points.first().z - z).abs() > 1.0 || (r.points.last().z - z).abs() > 1.0 { return false; } if let Inter(id) = start { @@ -399,6 +433,10 @@ impl RoadBuildResource { simulation::config().gui_danger }; + self.streight_points.iter().for_each(|p|{ + immdraw.circle(*p, 2.0); + }); + let p = match self.build_state { Hover => { immdraw.circle(proj_pos, patwidth * 0.5).color(col); @@ -443,4 +481,98 @@ impl RoadBuildResource { immdraw.circle(p.last(), patwidth * 0.5).color(col); immdraw.polyline(p.into_vec(), patwidth, false).color(col); } + + pub fn _update_points( + &self, + map: &Map, + mousepos: Vec3, + ) -> Vec { + let (start, end) = match self.build_state { + Hover | Interpolation(_, _) => { return vec![]; }, + Connection(src, dst) => (src, dst), + Start(sel_proj)|StartInterp(sel_proj) => (sel_proj, MapProject::ground(mousepos)), + }; + + match (start.kind, end.kind) { + (Inter(id0), Inter(id1)) => { + let Some(inter0) = map.intersections().get(id0) else {return vec![]}; + let Some(inter1) = map.intersections().get(id1) else {return vec![]}; + + inter0.roads.iter().flat_map(|i| inter1.roads.iter().map(move |j| (i, j))) + .map( |road_ids| (&map.roads()[*road_ids.0], &map.roads()[*road_ids.1])) + .filter_map(|roads| { + let p = Vec2::line_line_intersection( + inter0.pos.xy(), roads.0.get_straight_connection_point(id0), + inter1.pos.xy(), roads.1.get_straight_connection_point(id1)); + + if let Some(h) = map.environment.height(p) + { Some(p.z(h)) } else { None } + }).collect() + }, + + (Inter(id), Ground) | + (Ground, Inter(id)) => { + let Some(inter) = map.intersections().get(id) else {return vec![]}; + + inter.roads.iter() + .map(|&road_id| &map.roads()[road_id]) + .filter_map(|road| { + let p = Vec2::line_closed_point(mousepos.xy(), + road.get_straight_connection_point(id), inter.pos.xy()); + + if let Some(h) = map.environment.height(p) + { Some(p.z(h)) } else { None } + }).collect() + } + + (Inter(inter_id), Road(road_id)) | + (Road(road_id), Inter(inter_id)) + if self.pattern_builder.rail => { + let Some(inter) = map.intersections().get(inter_id) else {return vec![]}; + let Some(road) = map.roads().get(road_id) else {return vec![]}; + + let pos = if start.kind == Road(road_id) {start.pos} else {end.pos}; + let (pos, _, dir) = road.points().project_segment_dir(pos); + + inter.roads.iter() + .map(|&road_id| &map.roads()[road_id]) + .filter_map(|road| { + let p = Vec2::line_line_intersection( + road.get_straight_connection_point(inter_id), inter.pos.xy(), + pos.xy(), pos.xy()+dir.xy(), + ); + if let Some(h) = map.environment.height(p) + { Some(p.z(h)) } else { None } + }).collect() + } + + (Road(id), Ground) | + (Ground, Road(id)) + if self.pattern_builder.rail => { + let Some(road) = map.roads().get(id) else {return vec![]}; + + let pos = if start.kind == Road(id) {start.pos} else {end.pos}; + let (pos, _, dir) = road.points().project_segment_dir(pos); + + let p = Vec2::line_closed_point(mousepos.xy(), pos.xy()+dir.xy(), pos.xy()); + if let Some(h) = map.environment.height(p) + { vec![p.z(h)] } else { vec![] } + } + (Road(id0), Road(id1)) if self.pattern_builder.rail => { + let Some(road0) = map.roads().get(id0) else {return vec![]}; + let Some(road1) = map.roads().get(id1) else {return vec![]}; + + let (pos0, _, dir0) = road0.points().project_segment_dir(start.pos); + let (pos1, _, dir1) = road1.points().project_segment_dir(end.pos); + + let p = Vec2::line_line_intersection( + pos0.xy(), pos0.xy()+dir0.xy(), + pos1.xy(), pos1.xy()+dir1.xy()); + + if let Some(h) = map.environment.height(p) + { vec![p.z(h)] } else { vec![] } + }, + _ => { vec![] } + } + } } diff --git a/native_app/src/gui/topgui.rs b/native_app/src/gui/topgui.rs index 0d0bddca..dfd74d36 100644 --- a/native_app/src/gui/topgui.rs +++ b/native_app/src/gui/topgui.rs @@ -364,6 +364,7 @@ impl Gui { .show(ui, |ui| { let mut roadbuild = uiworld.write::(); ui.checkbox(&mut roadbuild.snap_to_grid, "snap to grid"); + ui.checkbox(&mut roadbuild.snap_to_angle, "snap to angle"); ui.horizontal(|ui| { if ui.button("zero").clicked() { roadbuild.height_offset = 0.0; diff --git a/simulation/src/map/objects/intersection.rs b/simulation/src/map/objects/intersection.rs index 2ef91253..55989345 100644 --- a/simulation/src/map/objects/intersection.rs +++ b/simulation/src/map/objects/intersection.rs @@ -65,10 +65,10 @@ impl Intersection { .roads .iter() .flat_map(|x| roads.get(*x)) - .map(|x| OrderedFloat(x.interface_from(self.id))) + .map(|x| OrderedFloat(x.width)) .max() .map(|x| x.0) - .unwrap_or(10.0), + .unwrap_or(10.0)-0.1, } } @@ -133,25 +133,25 @@ impl Intersection { } } - if self.roads.len() <= 1 { - return; - } + if self.roads.len() <= 1 { return; } for i in 0..self.roads.len() { let r1_id = self.roads[i]; let r2_id = self.roads[(i + 1) % self.roads.len()]; - let r1 = &roads[r1_id]; - let r2 = &roads[r2_id]; + let (r1, r2) = (&roads[r1_id], &roads[r2_id]); + let (dir1, dir2) = (r1.dir_from(id), r2.dir_from(id)); + + let min_dist = if dir1.angle(dir2).abs() < 0.174532925 + { self.interface_calc_numerically(r1.width, r2.width, r1, r2) } + else { Self::interface_calc_formula(r1.width, r2.width, dir1, dir2) }; - let min_dist = - Self::interface_calc(r1.width, r2.width, r1.dir_from(id), r2.dir_from(id)); roads[r1_id].max_interface(id, min_dist); roads[r2_id].max_interface(id, min_dist); } } - fn interface_calc(w1: f32, w2: f32, dir1: Vec2, dir2: Vec2) -> f32 { + fn interface_calc_formula(w1: f32, w2: f32, dir1: Vec2, dir2: Vec2) -> f32 { let hwidth1 = w1 * 0.5; let hwidth2 = w2 * 0.5; @@ -160,7 +160,28 @@ impl Intersection { let d = dir1.dot(dir2).clamp(0.0, 1.0); let sin = (1.0 - d * d).sqrt(); - (w * 1.1 / sin).min(30.0) + (w * 1.1 / sin).min(50.0) + } + + fn interface_calc_numerically(&self, w1: f32, w2: f32, r1: &Road, r2: &Road) -> f32 { + let w = (w1+w2)* 0.75; + + let mut points1: Vec<(Vec3, Vec3)> = r1.points().equipoints_dir(1.0, true).collect(); + let mut points2: Vec<(Vec3, Vec3)> = r2.points().equipoints_dir(1.0, true).collect(); + + if r1.dst == self.id { points1.reverse(); } + if r2.dst == self.id { points2.reverse(); } + + let points = points1.into_iter().zip(points2) + .map(|((p1,_),(p2,_))| (p1.xy(), p2.xy()) ) + .find(|p| p.0.distance(p.1) > w ); + + if let Some(p) = points { + (self.pos.xy().distance(p.0) + self.pos.xy().distance(p.0)) * 0.5 + } else { + 50.0 + } + } pub fn empty_interface(width: f32) -> f32 { @@ -172,7 +193,7 @@ impl Intersection { let id = self.id; for &r1_id in &self.roads { let r1 = unwrap_cont!(roads.get(r1_id)); - max_inter = max_inter.max(Self::interface_calc(r1.width, width, r1.dir_from(id), dir)); + max_inter = max_inter.max(Self::interface_calc_formula(r1.width, width, r1.dir_from(id), dir)); } max_inter } diff --git a/simulation/src/map/objects/road.rs b/simulation/src/map/objects/road.rs index 50c24fb2..8bd60bec 100644 --- a/simulation/src/map/objects/road.rs +++ b/simulation/src/map/objects/road.rs @@ -599,4 +599,19 @@ impl Road { } None } + + + pub fn get_straight_connection_point(&self, my_end: IntersectionID) -> Vec2 { + match self.segment { + RoadSegmentKind::Straight if self.src == my_end => self.points.last().xy(), + RoadSegmentKind::Straight if self.dst == my_end => self.points.first().xy(), + + RoadSegmentKind::Curved((fr, _)) if self.src == my_end + => self.points.first().xy() + fr, + RoadSegmentKind::Curved((_, to)) if self.dst == my_end + => self.points.last().xy() + to, + + _ => panic!("Intersection {:?} not connected to the road", my_end) + } + } } From 0ce918db68697afb05c15e35633659c73795de8d Mon Sep 17 00:00:00 2001 From: Teun Date: Sun, 18 Feb 2024 19:13:31 +0100 Subject: [PATCH 2/9] Substitute functions with excisting functions Substitute functions Vec2::{line_line_intersection, line_closed_point} with Line::{intersection_point, project} --- geom/src/v2.rs | 15 ------- native_app/src/gui/roadbuild.rs | 72 ++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 47 deletions(-) diff --git a/geom/src/v2.rs b/geom/src/v2.rs index 0cf327f5..7c7740be 100644 --- a/geom/src/v2.rs +++ b/geom/src/v2.rs @@ -403,21 +403,6 @@ impl Vec2 { pub fn rotated_by_angle(self, angle: Radians) -> Self { self.rotated_by(vec2(angle.cos(), angle.sin())) } - - pub fn line_line_intersection(a: Self, b: Self, c: Self, d: Self) -> Self { - let denominator = (a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x); - let (axbyaybx, cxdycydx) = (a.x*b.y-a.y*b.x, c.x*d.y-c.y*d.x); - - Self::new( - ((c.x-d.x)*axbyaybx - (a.x-b.x)*cxdycydx) / denominator, - ((c.y-d.y)*axbyaybx - (a.y-b.y)*cxdycydx) / denominator, - ) - } - - pub fn line_closed_point(p: Self, a: Self, b: Self) -> Self { - let ab_min = b - a; - a + ab_min * (ab_min.dot(p-a) / ab_min.mag2()) - } } impl Vec2d { diff --git a/native_app/src/gui/roadbuild.rs b/native_app/src/gui/roadbuild.rs index 8cbce26a..a4f41b46 100644 --- a/native_app/src/gui/roadbuild.rs +++ b/native_app/src/gui/roadbuild.rs @@ -1,5 +1,5 @@ use common::AudioKind; -use geom::{BoldLine, BoldSpline, Camera, PolyLine, ShapeEnum, Spline}; +use geom::{BoldLine, BoldSpline, Camera, Line, PolyLine, ShapeEnum, Spline}; use geom::{PolyLine3, Vec2, Vec3}; use simulation::map::{ LanePatternBuilder, Map, MapProject, ProjectFilter, ProjectKind, PylonPosition, RoadSegmentKind, @@ -498,15 +498,16 @@ impl RoadBuildResource { let Some(inter0) = map.intersections().get(id0) else {return vec![]}; let Some(inter1) = map.intersections().get(id1) else {return vec![]}; - inter0.roads.iter().flat_map(|i| inter1.roads.iter().map(move |j| (i, j))) - .map( |road_ids| (&map.roads()[*road_ids.0], &map.roads()[*road_ids.1])) - .filter_map(|roads| { - let p = Vec2::line_line_intersection( - inter0.pos.xy(), roads.0.get_straight_connection_point(id0), - inter1.pos.xy(), roads.1.get_straight_connection_point(id1)); - - if let Some(h) = map.environment.height(p) - { Some(p.z(h)) } else { None } + inter0.roads.iter() + .flat_map(|i| inter1.roads.iter().map(move |j| (i, j))) + .map( |(r0, r1)| (&map.roads()[*r0], &map.roads()[*r1])) + .filter_map(|(road0, road1)| { + let line0 = Line::new(inter0.pos.xy(), road0.get_straight_connection_point(id0)); + let line1 = Line::new(inter1.pos.xy(), road1.get_straight_connection_point(id1)); + + let p = line0.intersection_point(&line1)?; + let h = map.environment.height(p)?; + Some(p.z(h)) }).collect() }, @@ -517,12 +518,14 @@ impl RoadBuildResource { inter.roads.iter() .map(|&road_id| &map.roads()[road_id]) .filter_map(|road| { - let p = Vec2::line_closed_point(mousepos.xy(), - road.get_straight_connection_point(id), inter.pos.xy()); - - if let Some(h) = map.environment.height(p) - { Some(p.z(h)) } else { None } - }).collect() + let p = + Line::new(road.get_straight_connection_point(id), inter.pos.xy()) + .project(mousepos.xy()); + + let h = map.environment.height(p)?; + + Some(p.z(h)) + }).collect::>() } (Inter(inter_id), Road(road_id)) | @@ -537,13 +540,14 @@ impl RoadBuildResource { inter.roads.iter() .map(|&road_id| &map.roads()[road_id]) .filter_map(|road| { - let p = Vec2::line_line_intersection( - road.get_straight_connection_point(inter_id), inter.pos.xy(), - pos.xy(), pos.xy()+dir.xy(), - ); - if let Some(h) = map.environment.height(p) - { Some(p.z(h)) } else { None } - }).collect() + let line0 = Line::new(road.get_straight_connection_point(inter_id), inter.pos.xy()); + let line1 = Line::new(pos.xy(), pos.xy()+dir.xy()); + + let p = line0.intersection_point(&line1)?; + let h = map.environment.height(p)?; + + Some(p.z(h)) + }).collect::>() } (Road(id), Ground) | @@ -554,9 +558,11 @@ impl RoadBuildResource { let pos = if start.kind == Road(id) {start.pos} else {end.pos}; let (pos, _, dir) = road.points().project_segment_dir(pos); - let p = Vec2::line_closed_point(mousepos.xy(), pos.xy()+dir.xy(), pos.xy()); - if let Some(h) = map.environment.height(p) - { vec![p.z(h)] } else { vec![] } + let line = Line::new(pos.xy()+dir.xy(), pos.xy()); + let p = line.project(mousepos.xy()); + + let Some(h) = map.environment.height(p) else { return vec![] }; + vec![p.z(h)] } (Road(id0), Road(id1)) if self.pattern_builder.rail => { let Some(road0) = map.roads().get(id0) else {return vec![]}; @@ -565,13 +571,15 @@ impl RoadBuildResource { let (pos0, _, dir0) = road0.points().project_segment_dir(start.pos); let (pos1, _, dir1) = road1.points().project_segment_dir(end.pos); - let p = Vec2::line_line_intersection( - pos0.xy(), pos0.xy()+dir0.xy(), - pos1.xy(), pos1.xy()+dir1.xy()); + let line0 = Line::new(pos0.xy(), pos0.xy()+dir0.xy()); + let line1 = Line::new(pos1.xy(), pos1.xy()+dir1.xy()); - if let Some(h) = map.environment.height(p) - { vec![p.z(h)] } else { vec![] } - }, + let Some(p) = line0.intersection_point(&line1) else { return vec![] }; + let Some(h) = map.environment.height(p) else { return vec![] }; + + vec![p.z(h)] + } + _ => { vec![] } } } From 2705c45c31bb439908d9d1a2aa888aea12c622f2 Mon Sep 17 00:00:00 2001 From: Teun Date: Fri, 3 May 2024 13:04:33 +0200 Subject: [PATCH 3/9] Cool new features for trains. Make costum trains of any length for any persose. Make commuter, freight or high speed trains. --- assets/models/passenger-emu-front.glb | 3 + assets/models/passenger-emu-middle.glb | 3 + assets/models/passenger-emu-rear.glb | 3 + base_mod/data.lua | 2 + base_mod/roadvehicles.lua | 27 ++++++ base_mod/rollingstock.lua | 83 +++++++++++++++++ native_app/src/init.rs | 2 + native_app/src/newgui/hud/toolbox/train.rs | 57 +++++++++++- native_app/src/newgui/tools/addtrain.rs | 51 ++++++++--- native_app/src/rendering/entity_render.rs | 81 ++++++++-------- prototypes/src/prototypes/mod.rs | 29 +++--- prototypes/src/prototypes/road_vehicles.rs | 62 +++++++++++++ prototypes/src/prototypes/rolling_stock.rs | 46 ++++++++++ prototypes/src/prototypes/vehicles.rs | 36 ++++++++ simulation/src/transportation/train.rs | 102 ++++++++++++--------- simulation/src/world_command.rs | 18 ++-- 16 files changed, 488 insertions(+), 117 deletions(-) create mode 100644 assets/models/passenger-emu-front.glb create mode 100644 assets/models/passenger-emu-middle.glb create mode 100644 assets/models/passenger-emu-rear.glb create mode 100644 base_mod/roadvehicles.lua create mode 100644 base_mod/rollingstock.lua create mode 100644 prototypes/src/prototypes/road_vehicles.rs create mode 100644 prototypes/src/prototypes/rolling_stock.rs create mode 100644 prototypes/src/prototypes/vehicles.rs diff --git a/assets/models/passenger-emu-front.glb b/assets/models/passenger-emu-front.glb new file mode 100644 index 00000000..9b8b6633 --- /dev/null +++ b/assets/models/passenger-emu-front.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd606ad59ba95e075b6bafcb14a8265f9c37ed65608b17e191b9a8ff2e8993d5 +size 67620 diff --git a/assets/models/passenger-emu-middle.glb b/assets/models/passenger-emu-middle.glb new file mode 100644 index 00000000..b32a0a3a --- /dev/null +++ b/assets/models/passenger-emu-middle.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:508f514d5bbdb5fe883d3fa083b99f4786af9359a5453cc6777e3c32bd70ca48 +size 62868 diff --git a/assets/models/passenger-emu-rear.glb b/assets/models/passenger-emu-rear.glb new file mode 100644 index 00000000..1ddfd500 --- /dev/null +++ b/assets/models/passenger-emu-rear.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9df1cb0ac7a09deb0cf08745804426f016000fba3ba4224d9c9f0918328c5ba7 +size 67640 diff --git a/base_mod/data.lua b/base_mod/data.lua index 435d08b7..bdc9f45b 100644 --- a/base_mod/data.lua +++ b/base_mod/data.lua @@ -2,6 +2,8 @@ require("items") require("companies") require("leisure") require("colors") +require("roadvehicles") +require("rollingstock") data:extend { { diff --git a/base_mod/roadvehicles.lua b/base_mod/roadvehicles.lua new file mode 100644 index 00000000..7b5056c7 --- /dev/null +++ b/base_mod/roadvehicles.lua @@ -0,0 +1,27 @@ +data:extend { + { + type = "road-vehicle", + order = "a-1", + name = "simple_car", + label = "Simple Car", + max_speed = 50.0, + acceleration = 8.0, + deceleration = 12.0, + asset = "simple_car.glb", + price = 100.0, + }, + { + type = "road-vehicle", + order = "b-1", + name = "simple_truck", + label = "simple truck", + max_speed = 22.0, + acceleration = 6.0, + deceleration = 10.0, + asset = "truck.glb", + price = 100.0, + } +} + + + diff --git a/base_mod/rollingstock.lua b/base_mod/rollingstock.lua new file mode 100644 index 00000000..3cc94e36 --- /dev/null +++ b/base_mod/rollingstock.lua @@ -0,0 +1,83 @@ +data:extend { + { + type = "rolling-stock", + order = "l-1", + name = "locomotive", + label = "Locomotive", + length = 16.75, + mass = 60, + max_speed = 200.0, + acc_force = 1000.0, + dec_force = 360.0, + asset = "train.glb", + price = 100, + }, + { + type = "rolling-stock", + order = "p-1", + name = "passenger-wagon", + label = "Passenger Wagon", + length = 16.75, + mass = 40, + max_speed = 200.0, + acc_force = 0.0, + dec_force = 240.0, + asset = "wagon.glb", + price = 100, + }, + { + type = "rolling-stock", + order = "f-1", + name = "freight-wagon", + label = "Freight Wagon", + length = 16.75, + mass = 80, + max_speed = 160.0, + acc_force = 0.0, + dec_force = 480.0, + asset = "wagon_freight.glb", + price = 100, + }, + { + type = "rolling-stock", + order = "e-1", + name = "passenger-emu-front", + label = "Passenger EMU Front", + length = 28.1, + mass = 60, + max_speed = 360.0, + acc_force = 240.0, + dec_force = 360.0, + asset = "passenger-emu-front.glb", + price = 500, + }, + { + type = "rolling-stock", + order = "e-2", + name = "passenger-emu-middle", + label = "Passenger EMU Middle", + length = 28.1, + mass = 60, + max_speed = 360.0, + acc_force = 240.0, + dec_force = 360.0, + asset = "passenger-emu-middle.glb", + price = 200, + }, + { + type = "rolling-stock", + order = "e-3", + name = "passenger-emu-rear", + label = "Passenger EMU Rear", + length = 28.1, + mass = 60, + max_speed = 360.0, + acc_force = 240.0, + dec_force = 360.0, + asset = "passenger-emu-rear.glb", + price = 500, + }, +} + + + diff --git a/native_app/src/init.rs b/native_app/src/init.rs index 9061a246..0c5efac7 100644 --- a/native_app/src/init.rs +++ b/native_app/src/init.rs @@ -10,6 +10,7 @@ use crate::newgui::lotbrush::LotBrushResource; use crate::newgui::roadbuild::RoadBuildResource; use crate::newgui::roadeditor::RoadEditorResource; use crate::newgui::specialbuilding::SpecialBuildingResource; +use crate::newgui::addtrain::TrainSpawnResource; use crate::newgui::terraforming::TerraformingResource; use crate::newgui::toolbox::building::BuildingIcons; use crate::newgui::windows::economy::EconomyState; @@ -60,6 +61,7 @@ pub fn init() { register_resource_noserialize::(); register_resource_noserialize::(); register_resource_noserialize::(); + register_resource_noserialize::(); register_resource_noserialize::(); register_resource_noserialize::(); register_resource_noserialize::(); diff --git a/native_app/src/newgui/hud/toolbox/train.rs b/native_app/src/newgui/hud/toolbox/train.rs index 621f3c2a..950521ab 100644 --- a/native_app/src/newgui/hud/toolbox/train.rs +++ b/native_app/src/newgui/hud/toolbox/train.rs @@ -1,6 +1,59 @@ +use goryak::{mincolumn, minrow, padxy, outline}; +use prototypes::{prototypes_iter, RollingStockID, RollingStockPrototype}; +use yakui::widgets::List; +use yakui::{button, divider, label, CrossAxisAlignment, MainAxisAlignment}; + +use crate::newgui::addtrain::TrainSpawnResource; use crate::uiworld::UiWorld; -pub fn train_properties(_uiw: &UiWorld) {} +pub fn train_properties(uiw: &UiWorld) { + let mut state = uiw.write::(); + + padxy(0.0, 0.0, || { + let mut l = List::row(); + l.main_axis_alignment = MainAxisAlignment::Start; + l.cross_axis_alignment = CrossAxisAlignment::Center; + l.item_spacing = 10.0; + l.show(|| { + mincolumn(0.1, || { + if button("remove trian").clicked { + state.wagons.clear(); + state.calculate(); + } + label(format!("Acceleration: {}", state.acceleration)); + label(format!("Deceleration: {}", state.deceleration)); + label(format!("Total Lenght: {}", state.total_lenght.ceil())); + }); + + mincolumn(0.5, || { + minrow(0.0, || { + let mut remove: Option= None; + for (i, rs) in state.wagons.iter() + .map(|id| RollingStockID::prototype(*id)) + .enumerate() { + if button(rs.label.clone()).clicked { remove = Some(i); } + } + if let Some(i) = remove { + state.wagons.remove(i); + state.calculate(); + } + }); + + divider(outline(), 10.0, 1.0); + + minrow(0.0, || { + for rolling_stock in prototypes_iter::() { + let resp = button(rolling_stock.label.clone()); + if resp.clicked { + state.wagons.push(rolling_stock.id); + state.calculate(); + } + } + }); + }); + }); + }); +} /* if ui.button(freightstation).clicked() { @@ -41,4 +94,4 @@ if ui.button(freightstation).clicked() { road_snap: false, }); } -*/ +*/ \ No newline at end of file diff --git a/native_app/src/newgui/tools/addtrain.rs b/native_app/src/newgui/tools/addtrain.rs index a95ccd3f..4dfa8d76 100644 --- a/native_app/src/newgui/tools/addtrain.rs +++ b/native_app/src/newgui/tools/addtrain.rs @@ -3,21 +3,32 @@ use crate::newgui::{PotentialCommands, Tool}; use crate::rendering::immediate::ImmediateDraw; use crate::uiworld::UiWorld; use geom::{Color, OBB}; +use prototypes::RollingStockID; use simulation::map::LaneKind; -use simulation::transportation::train::{train_length, wagons_positions_for_render}; +use simulation::transportation::train::{calculate_locomotive, wagons_positions_for_render}; use simulation::world_command::WorldCommand; use simulation::Simulation; use std::option::Option::None; + +#[derive(Clone, Debug, Default)] +pub struct TrainSpawnResource { + pub wagons: Vec, + pub max_speed: f32, + pub acceleration: f32, + pub deceleration: f32, + pub total_lenght: f32, + +} + /// Addtrain handles the "Adding a train" tool /// It allows to add a train to any rail lane pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { profiling::scope!("gui::addtrain"); + let state = uiworld.write::(); let tool = *uiworld.read::(); - if !matches!(tool, Tool::Train) { - return; - } - + if !matches!(tool, Tool::Train) { return; } + let inp = uiworld.read::(); let mut potential = uiworld.write::(); @@ -41,14 +52,14 @@ pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { let proj = nearbylane.points.project(mpos); let dist = nearbylane.points.length_at_proj(proj); - let n_wagons = 7; - let trainlength = train_length(n_wagons); + let trainlength = state.total_lenght + 1.0; let mut drawtrain = |col: Color| { - for (p, dir) in wagons_positions_for_render(&nearbylane.points, dist, n_wagons) { - draw.obb(OBB::new(p.xy(), dir.xy(), 16.5, 3.0), p.z + 0.5) - .color(col); - } + wagons_positions_for_render(&state.wagons, &nearbylane.points, dist) + .for_each(|(pos, dir, length)| { + draw.obb(OBB::new(pos.xy(), dir.xy(), length, 4.0), pos.z + 0.5) + .color(col); + }); }; if dist <= trainlength { @@ -58,14 +69,26 @@ pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { drawtrain(simulation::colors().gui_primary); - let cmd = WorldCommand::AddTrain { - dist, - n_wagons, + let cmd = WorldCommand::SpawnTrain { + wagons: state.wagons.clone(), lane: nearbylane.id, + dist: dist }; + if inp.just_act.contains(&InputAction::Select) { commands.push(cmd); } else { potential.set(cmd); } } + + +impl TrainSpawnResource { + pub fn calculate(&mut self) { + let locomotive = calculate_locomotive(&self.wagons); + self.max_speed = locomotive.max_speed; + self.acceleration = locomotive.acc_force; + self.deceleration = locomotive.dec_force; + self.total_lenght = locomotive.length; + } +} \ No newline at end of file diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index 7f299b43..7b8e621c 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -1,16 +1,18 @@ +use common::FastMap; use engine::{FrameContext, GfxContext, InstancedMeshBuilder, MeshInstance, SpriteBatchBuilder}; use geom::{LinearColor, Vec3, V3}; -use simulation::transportation::train::RailWagonKind; +use prototypes::{RollingStockID, RollingStockPrototype, RenderAsset}; use simulation::transportation::{Location, VehicleKind}; use simulation::Simulation; /// Render all entities using instanced rendering for performance pub struct InstancedRender { pub path_not_found: SpriteBatchBuilder, + pub rolling_stock: FastMap>, pub cars: InstancedMeshBuilder, - pub locomotives: InstancedMeshBuilder, - pub wagons_passenger: InstancedMeshBuilder, - pub wagons_freight: InstancedMeshBuilder, + // pub locomotives: InstancedMeshBuilder, + // pub wagons_passenger: InstancedMeshBuilder, + // pub wagons_freight: InstancedMeshBuilder, pub trucks: InstancedMeshBuilder, pub pedestrians: InstancedMeshBuilder, } @@ -19,24 +21,36 @@ impl InstancedRender { pub fn new(gfx: &mut GfxContext) -> Self { defer!(log::info!("finished init of instanced render")); + let mut rolling_stock = FastMap::default(); + RollingStockPrototype::iter() + .map(|rail_wagon_proto| (&rail_wagon_proto.asset, rail_wagon_proto.id)) + .filter_map(|(asset, id)| { + let RenderAsset::Mesh { path } = asset else { None? }; + match gfx.mesh(path) { + Err(e) => { log::error!("Failed to load mesh {}: {:?}", asset, e); None } + Ok(m) => Some((id, m)), + } + }) + .for_each(|(id, mesh)|{ + rolling_stock.insert(id, InstancedMeshBuilder::new_ref(&mesh)); + }); + + let car = gfx.mesh("simple_car.glb".as_ref()).unwrap(); InstancedRender { path_not_found: SpriteBatchBuilder::new( &gfx.texture("assets/sprites/path_not_found.png", "path_not_found"), - gfx, + gfx ), + + rolling_stock, + cars: InstancedMeshBuilder::new_ref(&car), - locomotives: InstancedMeshBuilder::new_ref(&gfx.mesh("train.glb".as_ref()).unwrap()), - wagons_freight: InstancedMeshBuilder::new_ref( - &gfx.mesh("wagon_freight.glb".as_ref()).unwrap(), - ), - wagons_passenger: InstancedMeshBuilder::new_ref( - &gfx.mesh("wagon.glb".as_ref()).unwrap(), - ), + // locomotives: InstancedMeshBuilder::new_ref(&gfx.mesh("train.glb".as_ref()).unwrap()), + // wagons_freight: InstancedMeshBuilder::new_ref(&gfx.mesh("wagon_freight.glb".as_ref()).unwrap()), + // wagons_passenger: InstancedMeshBuilder::new_ref(&gfx.mesh("wagon.glb".as_ref()).unwrap()), trucks: InstancedMeshBuilder::new_ref(&gfx.mesh("truck.glb".as_ref()).unwrap()), - pedestrians: InstancedMeshBuilder::new_ref( - &gfx.mesh("pedestrian.glb".as_ref()).unwrap(), - ), + pedestrians: InstancedMeshBuilder::new_ref(&gfx.mesh("pedestrian.glb".as_ref()).unwrap()), } } @@ -60,9 +74,8 @@ impl InstancedRender { } } - self.locomotives.instances.clear(); - self.wagons_passenger.instances.clear(); - self.wagons_freight.instances.clear(); + self.rolling_stock.iter_mut() + .for_each(|(_, m)| { m.instances.clear(); }); for wagon in sim.world().wagons.values() { let trans = &wagon.trans; let instance = MeshInstance { @@ -70,17 +83,11 @@ impl InstancedRender { dir: trans.dir, tint: LinearColor::WHITE, }; - - match wagon.wagon.kind { - RailWagonKind::Passenger => { - self.wagons_passenger.instances.push(instance); - } - RailWagonKind::Freight => { - self.wagons_freight.instances.push(instance); - } - RailWagonKind::Locomotive => { - self.locomotives.instances.push(instance); - } + + if let Some(mesh) + = self.rolling_stock.get_mut(&wagon.wagon.rolling_stock) + { + mesh.instances.push(instance); } } @@ -127,14 +134,12 @@ impl InstancedRender { if let Some(x) = self.pedestrians.build(fctx.gfx) { fctx.objs.push(Box::new(x)); } - if let Some(x) = self.locomotives.build(fctx.gfx) { - fctx.objs.push(Box::new(x)); - } - if let Some(x) = self.wagons_passenger.build(fctx.gfx) { - fctx.objs.push(Box::new(x)); - } - if let Some(x) = self.wagons_freight.build(fctx.gfx) { - fctx.objs.push(Box::new(x)); - } + + self.rolling_stock.iter_mut() + .for_each(|(_, imb)| { + if let Some(x) = imb.build(fctx.gfx) { + fctx.objs.push(Box::new(x)); + } + }); } } diff --git a/prototypes/src/prototypes/mod.rs b/prototypes/src/prototypes/mod.rs index 32947758..c5f5d405 100644 --- a/prototypes/src/prototypes/mod.rs +++ b/prototypes/src/prototypes/mod.rs @@ -1,18 +1,13 @@ -mod building; -mod colors; -mod freightstation; -mod goods_company; -mod item; -mod leisure; -mod solar; - -pub use building::*; -pub use colors::*; -pub use freightstation::*; -pub use goods_company::*; -pub use item::*; -pub use leisure::*; -pub use solar::*; +mod building; pub use building::*; +mod colors; pub use colors::*; +mod freightstation; pub use freightstation::*; +mod goods_company; pub use goods_company::*; +mod item; pub use item::*; +mod leisure; pub use leisure::*; +mod road_vehicles; pub use road_vehicles::*; +mod rolling_stock; pub use rolling_stock::*; +mod solar; pub use solar::*; +mod vehicles; pub use vehicles::*; crate::gen_prototypes!( items: ItemID = ItemPrototype, @@ -21,6 +16,10 @@ crate::gen_prototypes!( leisure: LeisurePrototypeID = LeisurePrototype => BuildingPrototypeID, solar: SolarPanelID = SolarPanelPrototype => GoodsCompanyID, + vehicles: VehiclePrototypeID = VehiclePrototype, + road_vehicles: RoadVehicleID = RoadVehiclePrototype => VehiclePrototypeID, + rolling_stock: RollingStockID = RollingStockPrototype => VehiclePrototypeID, + colors: ColorsPrototypeID = ColorsPrototype, stations: FreightStationPrototypeID = FreightStationPrototype, ); diff --git a/prototypes/src/prototypes/road_vehicles.rs b/prototypes/src/prototypes/road_vehicles.rs new file mode 100644 index 00000000..a53842ca --- /dev/null +++ b/prototypes/src/prototypes/road_vehicles.rs @@ -0,0 +1,62 @@ +use crate::{ + get_lua, Prototype}; + +use mlua::Table; +use std::ops::Deref; + +use super::*; + +#[derive(Clone, Debug)] +pub struct RoadVehiclePrototype { + pub base: VehiclePrototype, + pub id: RoadVehicleID, + /// m/s + pub max_speed: f32, + /// m.s^2 + pub acceleration: f32, + /// m.s^2 + pub deceleration: f32, +} + +impl Prototype for RoadVehiclePrototype { + type Parent = VehiclePrototype; + type ID = RoadVehicleID; + const NAME: &'static str = "road-vehicle"; + + fn from_lua(table: &Table) -> mlua::Result { + let base = VehiclePrototype::from_lua(table)?; + Ok(Self { id: Self::ID::new(&base.name), base, + max_speed: get_lua::(table, "max_speed")?, + acceleration: get_lua::(table, "acceleration")?, + deceleration: get_lua::(table, "deceleration")?, + }) + } + fn id(&self) -> Self::ID { self.id } + fn parent(&self) -> &Self::Parent { &self.base } +} + +impl Deref for RoadVehiclePrototype { + type Target = VehiclePrototype; + fn deref(&self) -> &Self::Target {&self.base} +} + + + + + + + + + + +/* + +mass total + +acc_force: + + + + + +*/ \ No newline at end of file diff --git a/prototypes/src/prototypes/rolling_stock.rs b/prototypes/src/prototypes/rolling_stock.rs new file mode 100644 index 00000000..086ae01c --- /dev/null +++ b/prototypes/src/prototypes/rolling_stock.rs @@ -0,0 +1,46 @@ +use crate::{get_lua, Prototype}; +use mlua::Table; +use std::ops::Deref; + +use super::*; + +#[derive(Clone, Debug)] +pub struct RollingStockPrototype { + pub base: VehiclePrototype, + pub id: RollingStockID, + /// meter + pub length: f32, + /// metric ton + pub mass: u32, + /// m/s + pub max_speed: f32, + /// kN + pub acc_force: f32, + /// kN + pub dec_force: f32, + +} + +impl Prototype for RollingStockPrototype { + type Parent = VehiclePrototype; + type ID = RollingStockID; + const NAME: &'static str = "rolling-stock"; + + fn from_lua(table: &Table) -> mlua::Result { + let base = VehiclePrototype::from_lua(table)?; + Ok(Self { id: Self::ID::new(&base.name), base, + length: get_lua::(table, "length")?, + mass: get_lua(table, "mass")?, + max_speed: get_lua::(table, "max_speed")?, + acc_force: get_lua::(table, "acc_force")?, + dec_force: get_lua::(table, "dec_force")?, + }) + } + fn id(&self) -> Self::ID { self.id } + fn parent(&self) -> &Self::Parent { &self.base } +} + +impl Deref for RollingStockPrototype { + type Target = VehiclePrototype; + fn deref(&self) -> &Self::Target {&self.base} +} \ No newline at end of file diff --git a/prototypes/src/prototypes/vehicles.rs b/prototypes/src/prototypes/vehicles.rs new file mode 100644 index 00000000..94d7e236 --- /dev/null +++ b/prototypes/src/prototypes/vehicles.rs @@ -0,0 +1,36 @@ +use crate::{ + get_lua, Money, NoParent, Prototype, PrototypeBase, RenderAsset}; +use mlua::Table; +use std::ops::Deref; + +use super::*; + + +#[derive(Clone, Debug)] +pub struct VehiclePrototype { + pub base: PrototypeBase, + pub id: VehiclePrototypeID, + pub asset: RenderAsset, + pub price: Money, +} + +impl Prototype for VehiclePrototype { + type Parent = NoParent; + type ID = VehiclePrototypeID; + const NAME: &'static str = "vehicle"; + + fn from_lua(table: &Table) -> mlua::Result { + let base = PrototypeBase::from_lua(table)?; + Ok(Self { id: Self::ID::new(&base.name), base, + asset: get_lua(table, "asset")?, + price: get_lua(table, "price")?, + }) + } + fn id(&self) -> Self::ID { self.id } + fn parent(&self) -> &Self::Parent { &NoParent } +} + +impl Deref for VehiclePrototype { + type Target = PrototypeBase; + fn deref(&self) -> &Self::Target {&self.base} +} \ No newline at end of file diff --git a/simulation/src/transportation/train.rs b/simulation/src/transportation/train.rs index 5ff34d7c..e665c903 100644 --- a/simulation/src/transportation/train.rs +++ b/simulation/src/transportation/train.rs @@ -7,7 +7,7 @@ use slotmapd::HopSlotMap; use egui_inspect::Inspect; use geom::{PolyLine3, Polyline3Queue, Transform, Vec3}; -use prototypes::DELTA; +use prototypes::{RollingStockID, DELTA}; use crate::map::{IntersectionID, LaneID, Map, TraverseKind}; use crate::map_dynamic::ItineraryFollower; @@ -52,44 +52,69 @@ pub enum RailWagonKind { #[derive(Inspect, Serialize, Deserialize)] pub struct RailWagon { pub kind: RailWagonKind, + pub rolling_stock: RollingStockID, } -const WAGON_INTERLENGTH: f32 = 16.75; +pub fn calculate_locomotive(wagons: &Vec) -> Locomotive { + let info = wagons.iter() + .fold((720.0, 0.0, 0.0, 0.0, 0), + |(speed, acc, dec, length, mass): (f32, f32, f32, f32, u32), &id| { + let rs = RollingStockID::prototype(id); + ( + speed.min(rs.max_speed), + acc + rs.acc_force, dec + rs.dec_force, + length + rs.length, mass + rs.mass, + ) + }); + Locomotive { + max_speed: info.0, + acc_force: info.1 / info.4 as f32, + dec_force: info.2 / info.4 as f32, + length: info.3 + 10.0, + } +} -pub fn wagons_dists_to_loco(n_wagons: u32) -> impl DoubleEndedIterator { - (0..n_wagons + 1).map(|x| x as f32 * 16.75) +pub fn wagons_loco_dists_lengths(wagons: &Vec) -> impl DoubleEndedIterator + '_ { + let mut loco_dist = 0.0; + wagons.iter() + .map(move |&id| { + let length = RollingStockID::prototype(id).length; + loco_dist += length; + (loco_dist - length, length) + }) } -pub fn wagons_positions_for_render( - points: &PolyLine3, +pub fn wagons_positions_for_render<'a>( + wagons: &'a Vec, + points: &'a PolyLine3, dist: f32, - n_wagons: u32, -) -> impl Iterator + '_ { - let positions = std::iter::once(0.0) - .chain(wagons_dists_to_loco(n_wagons).map(|x| x + WAGON_INTERLENGTH * 0.5)) - .rev() - .filter_map(move |wdist| { - let pos = dist - wdist; - if pos >= 0.0 { - Some(pos) - } else { - None - } - }); - - points.points_dirs_along(positions) +) -> impl Iterator + 'a { + wagons_loco_dists_lengths(wagons) + .map(|(wagon_dist, length)| (wagon_dist + length * 0.5, length)) + .rev() + .filter_map(move |(wagin_dist, length)| { + let pos = dist - wagin_dist; + if pos >= 0.0 { Some((pos, length)) } + else { None } + }) + .map(move |(d, length)|{ + let (pos, dir) = points.point_dir_along(d); + (pos, dir, length) + }) } -pub fn train_length(n_wagons: u32) -> f32 { - 1.0 + (n_wagons + 1) as f32 * WAGON_INTERLENGTH +pub fn train_length(wagons: &Vec) -> f32 { + wagons.iter() + .map(|id| RollingStockID::prototype(*id).length) + .sum::() } pub fn spawn_train( sim: &mut Simulation, - dist: f32, - n_wagons: u32, - lane: LaneID, + wagons: &Vec, kind: RailWagonKind, + lane: LaneID, + dist: f32, ) -> Option { let (world, res) = sim.world_res(); @@ -108,18 +133,14 @@ pub fn spawn_train( .collect::>(); points.reverse(); - let trainlength = train_length(n_wagons); + let locomotive = calculate_locomotive(wagons); + let train_length = locomotive.length; let loco = world.insert(TrainEnt { trans: Transform::new_dir(locopos, locodir), speed: Default::default(), it: Itinerary::NONE, - locomotive: Locomotive { - max_speed: 50.0, - acc_force: 1.0, - dec_force: 2.5, - length: trainlength, - }, + locomotive: locomotive, res: LocomotiveReservation { cur_travers_dist: dist, waited_for: 0.0, @@ -130,7 +151,7 @@ pub fn spawn_train( upcoming_inters: Default::default(), }, leader: ItineraryLeader { - past: Polyline3Queue::new(points.into_iter(), locopos, trainlength + 20.0), + past: Polyline3Queue::new(points.into_iter(), locopos, train_length + 20.0), }, }); @@ -139,9 +160,8 @@ pub fn spawn_train( let mut followers: Vec<_> = leader .past .mk_followers( - wagons_dists_to_loco(n_wagons) - .flat_map(|x| [x + WAGON_INTERLENGTH * 0.1, x + WAGON_INTERLENGTH * 0.9]), - ) + wagons_loco_dists_lengths(&wagons) + .flat_map(|(dist, length)| [dist + length * 0.1, dist + length * 0.9])) .collect(); for (i, follower) in followers.chunks_exact_mut(2).enumerate() { let (pos, dir) = follower[0].update(&leader.past); @@ -150,11 +170,10 @@ pub fn spawn_train( trans: Transform::new_dir(pos * 0.5 + pos2 * 0.5, (0.5 * (dir + dir2)).normalize()), speed: Speed::default(), wagon: RailWagon { + rolling_stock: wagons[i], kind: if i == 0 { RailWagonKind::Locomotive - } else { - kind - }, + } else { kind }, }, itfollower: ItineraryFollower { leader: loco, @@ -163,7 +182,8 @@ pub fn spawn_train( }, }); } - + + log::info!("Spawned Train with {} wagons", wagons.len()); Some(loco) } diff --git a/simulation/src/world_command.rs b/simulation/src/world_command.rs index 7fd98c6f..377815f7 100644 --- a/simulation/src/world_command.rs +++ b/simulation/src/world_command.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::time::Instant; +use prototypes::RollingStockID; use serde::{Deserialize, Serialize}; use geom::{vec3, Vec2, Vec3, OBB}; @@ -56,6 +57,11 @@ pub enum WorldCommand { n_wagons: u32, lane: LaneID, }, + SpawnTrain { + wagons: Vec, + lane: LaneID, + dist: f32, + }, MapMakeConnection { from: MapProject, to: MapProject, @@ -293,13 +299,11 @@ impl WorldCommand { } } SetGameTime(gt) => *sim.write::() = gt, - AddTrain { - dist, - n_wagons, - lane, - } => { - spawn_train(sim, dist, n_wagons, lane, RailWagonKind::Freight); - } + AddTrain {dist:_, n_wagons:_,lane:_,} => {} + SpawnTrain {ref wagons, lane, dist} => { + spawn_train(sim, wagons, RailWagonKind::Freight, lane, dist); + }, + MapLoadParis => load_parismap(&mut sim.map_mut()), MapLoadTestField { pos, size, spacing } => { load_testfield(&mut sim.map_mut(), pos, size, spacing) From 23269b6df0a489be8f4a24ccbd25408544e2618f Mon Sep 17 00:00:00 2001 From: Teun Date: Fri, 3 May 2024 14:47:27 +0200 Subject: [PATCH 4/9] Changes to the trains --- assets/models/passenger-emu-front.glb | 4 ++-- assets/models/passenger-emu-middle.glb | 4 ++-- assets/models/passenger-emu-rear.glb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/models/passenger-emu-front.glb b/assets/models/passenger-emu-front.glb index 9b8b6633..e9db4ad8 100644 --- a/assets/models/passenger-emu-front.glb +++ b/assets/models/passenger-emu-front.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd606ad59ba95e075b6bafcb14a8265f9c37ed65608b17e191b9a8ff2e8993d5 -size 67620 +oid sha256:ab034896acd415bfcbe7d4cecb24eab35eda59489126be77421f8c726f935926 +size 71680 diff --git a/assets/models/passenger-emu-middle.glb b/assets/models/passenger-emu-middle.glb index b32a0a3a..eb154b68 100644 --- a/assets/models/passenger-emu-middle.glb +++ b/assets/models/passenger-emu-middle.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:508f514d5bbdb5fe883d3fa083b99f4786af9359a5453cc6777e3c32bd70ca48 -size 62868 +oid sha256:4c83c7c8684cda1b2ea42228a73d6f740951b9c1abd37b5f7a571c2541cb889b +size 72444 diff --git a/assets/models/passenger-emu-rear.glb b/assets/models/passenger-emu-rear.glb index 1ddfd500..48ad51e1 100644 --- a/assets/models/passenger-emu-rear.glb +++ b/assets/models/passenger-emu-rear.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9df1cb0ac7a09deb0cf08745804426f016000fba3ba4224d9c9f0918328c5ba7 -size 67640 +oid sha256:d76dd167e8054c595daabfe9ea8a8ae89b7f26fad5354f71db44d6fca45475d4 +size 71700 From e93565edb619051d3b023cef63334914a60e1eab Mon Sep 17 00:00:00 2001 From: Teun Date: Sat, 4 May 2024 16:07:47 +0200 Subject: [PATCH 5/9] Changes --- assets/ui/icons/snap_angle.png | 3 + assets/ui/icons/snap_notting.png | 3 + base_mod/rollingstock.lua | 8 +-- .../src/newgui/hud/toolbox/roadbuild.rs | 66 ++++++++++++------- native_app/src/newgui/hud/toolbox/train.rs | 2 +- native_app/src/newgui/tools/addtrain.rs | 24 ++++++- native_app/src/newgui/tools/roadbuild.rs | 49 +++++++++----- simulation/src/map/objects/intersection.rs | 5 +- 8 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 assets/ui/icons/snap_angle.png create mode 100644 assets/ui/icons/snap_notting.png diff --git a/assets/ui/icons/snap_angle.png b/assets/ui/icons/snap_angle.png new file mode 100644 index 00000000..6542c8c2 --- /dev/null +++ b/assets/ui/icons/snap_angle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:debfdd82e6b15f9e06d41399bee86f5d739e5573c9a03bb4d4195d700afdbba3 +size 6922 diff --git a/assets/ui/icons/snap_notting.png b/assets/ui/icons/snap_notting.png new file mode 100644 index 00000000..eef295b1 --- /dev/null +++ b/assets/ui/icons/snap_notting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6270f4d919dc0b962d9b8b11b3f4912f0feb07a2e8a478fef950afc318cd9db +size 15393 diff --git a/base_mod/rollingstock.lua b/base_mod/rollingstock.lua index 3cc94e36..5e278cdd 100644 --- a/base_mod/rollingstock.lua +++ b/base_mod/rollingstock.lua @@ -7,7 +7,7 @@ data:extend { length = 16.75, mass = 60, max_speed = 200.0, - acc_force = 1000.0, + acc_force = 2000.0, dec_force = 360.0, asset = "train.glb", price = 100, @@ -43,7 +43,7 @@ data:extend { order = "e-1", name = "passenger-emu-front", label = "Passenger EMU Front", - length = 28.1, + length = 28.0, mass = 60, max_speed = 360.0, acc_force = 240.0, @@ -56,7 +56,7 @@ data:extend { order = "e-2", name = "passenger-emu-middle", label = "Passenger EMU Middle", - length = 28.1, + length = 28.0, mass = 60, max_speed = 360.0, acc_force = 240.0, @@ -69,7 +69,7 @@ data:extend { order = "e-3", name = "passenger-emu-rear", label = "Passenger EMU Rear", - length = 28.1, + length = 28.0, mass = 60, max_speed = 360.0, acc_force = 240.0, diff --git a/native_app/src/newgui/hud/toolbox/roadbuild.rs b/native_app/src/newgui/hud/toolbox/roadbuild.rs index 41c258eb..14f32474 100644 --- a/native_app/src/newgui/hud/toolbox/roadbuild.rs +++ b/native_app/src/newgui/hud/toolbox/roadbuild.rs @@ -1,14 +1,13 @@ use yakui::widgets::List; use yakui::{ - image, reflow, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, MainAxisSize, - Vec2, + image, reflow, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, MainAxisSize, Vec2 }; -use goryak::{image_button, padxy, primary}; +use goryak::{image_button, mincolumn, padxy, primary}; use simulation::map::LanePatternBuilder; use crate::newgui::hud::toolbox::updown_value; -use crate::newgui::roadbuild::RoadBuildResource; +use crate::newgui::roadbuild::{RoadBuildResource, Snapping}; use crate::newgui::textures::UiTextures; use crate::uiworld::UiWorld; @@ -21,25 +20,46 @@ pub fn roadbuild_properties(uiw: &UiWorld) { l.cross_axis_alignment = CrossAxisAlignment::Center; l.item_spacing = 10.0; l.show(|| { - // Snap to grid - let (default_col, hover_col) = if state.snap_to_grid { - let c = primary().lerp(&Color::WHITE, 0.3); - (c, c.with_alpha(0.7)) - } else { - (Color::WHITE.with_alpha(0.3), Color::WHITE.with_alpha(0.5)) - }; - if image_button( - uiw.read::().get("snap_grid"), - Vec2::new(32.0, 32.0), - default_col, - hover_col, - primary(), - "snap to grid", - ) - .clicked - { - state.snap_to_grid = !state.snap_to_grid; - } + let c = primary().lerp(&Color::WHITE, 0.3); + let active = (c, c.with_alpha(0.7)); + let default = (Color::WHITE.with_alpha(0.3), Color::WHITE.with_alpha(0.5)); + + mincolumn(0.0, || { + let (snapping_none, snapping_grid, snapping_angel) = match state.snapping { + Snapping::None => {(active, default, default)}, + Snapping::SnapToGrid => {(default, active, default)}, + Snapping::SnapToAngle => {(default, default, active)}, + }; + + if image_button( + uiw.read::().get("snap_notting"), + Vec2::new(25.0, 25.0), + snapping_none.0, + snapping_none.1, + primary(), + "no snapping", + ).clicked { state.snapping = Snapping::None; } + + if image_button( + uiw.read::().get("snap_grid"), + Vec2::new(25.0, 25.0), + snapping_grid.0, + snapping_grid.1, + primary(), + "snap to grid", + ) + .clicked { state.snapping = Snapping::SnapToGrid; } + + if image_button( + uiw.read::().get("snap_angle"), + Vec2::new(25.0, 25.0), + snapping_angel.0, + snapping_angel.1, + primary(), + "snap to angle", + ) + .clicked { state.snapping = Snapping::SnapToAngle; } + }); // Road elevation updown_value(&mut state.height_offset, 2.0, "m"); diff --git a/native_app/src/newgui/hud/toolbox/train.rs b/native_app/src/newgui/hud/toolbox/train.rs index 950521ab..f43ed191 100644 --- a/native_app/src/newgui/hud/toolbox/train.rs +++ b/native_app/src/newgui/hud/toolbox/train.rs @@ -18,7 +18,7 @@ pub fn train_properties(uiw: &UiWorld) { mincolumn(0.1, || { if button("remove trian").clicked { state.wagons.clear(); - state.calculate(); + state.set_zero(); } label(format!("Acceleration: {}", state.acceleration)); label(format!("Deceleration: {}", state.deceleration)); diff --git a/native_app/src/newgui/tools/addtrain.rs b/native_app/src/newgui/tools/addtrain.rs index 4dfa8d76..e4494e82 100644 --- a/native_app/src/newgui/tools/addtrain.rs +++ b/native_app/src/newgui/tools/addtrain.rs @@ -25,9 +25,14 @@ pub struct TrainSpawnResource { /// It allows to add a train to any rail lane pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { profiling::scope!("gui::addtrain"); - let state = uiworld.write::(); + let state = &mut *uiworld.write::(); let tool = *uiworld.read::(); - if !matches!(tool, Tool::Train) { return; } + + if !matches!(tool, Tool::Train) { + state.wagons.clear(); + state.set_zero(); + return; + } let inp = uiworld.read::(); let mut potential = uiworld.write::(); @@ -49,6 +54,10 @@ pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { } }; + if state.wagons.is_empty() { + return; + } + let proj = nearbylane.points.project(mpos); let dist = nearbylane.points.length_at_proj(proj); @@ -86,9 +95,20 @@ pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { impl TrainSpawnResource { pub fn calculate(&mut self) { let locomotive = calculate_locomotive(&self.wagons); + if locomotive.acc_force.is_nan() || locomotive.dec_force.is_nan() { + self.set_zero(); return; + } + self.max_speed = locomotive.max_speed; self.acceleration = locomotive.acc_force; self.deceleration = locomotive.dec_force; self.total_lenght = locomotive.length; } + + pub fn set_zero(&mut self) { + self.max_speed = 0.0; + self.acceleration = 0.0; + self.deceleration = 0.0; + self.total_lenght = 0.0; + } } \ No newline at end of file diff --git a/native_app/src/newgui/tools/roadbuild.rs b/native_app/src/newgui/tools/roadbuild.rs index 80831e3f..65477d7a 100644 --- a/native_app/src/newgui/tools/roadbuild.rs +++ b/native_app/src/newgui/tools/roadbuild.rs @@ -49,20 +49,24 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { // Prepare mousepos depending on snap to grid let unproj = unwrap_ret!(inp.unprojected); let grid_size = 20.0; - let mousepos = if state.snap_to_grid { - let v = unproj.xy().snap(grid_size, grid_size); - v.z(unwrap_ret!(map.environment.height(v)) + state.height_offset) - } else if state.snap_to_angle { - state.streight_points = state._update_points(map, unproj.up(state.height_offset)); - state.streight_points.iter() - .filter_map(|&point| { - let distance = point.distance(unproj); - if distance < grid_size {Some((point, distance))} else { None } - }) - .reduce(|acc, e| { if acc.1 < e.1 {acc} else { e } }) - .unwrap_or((unproj.up(state.height_offset), 0.0)).0 - } else { - unproj.up(state.height_offset) + let mousepos = match state.snapping { + Snapping::None => { + unproj.up(state.height_offset) + }, + Snapping::SnapToGrid => { + let v = unproj.xy().snap(grid_size, grid_size); + v.z(unwrap_ret!(map.environment.height(v)) + state.height_offset) + }, + Snapping::SnapToAngle => { + state.streight_points = state._update_points(map, unproj.up(state.height_offset)); + state.streight_points.iter() + .filter_map(|&point| { + let distance = point.distance(unproj); + if distance < grid_size {Some((point, distance))} else { None } + }) + .reduce(|acc, e| { if acc.1 < e.1 {acc} else { e } }) + .unwrap_or((unproj.up(state.height_offset), 0.0)).0 + } }; let log_camheight = cam.eye().z.log10(); @@ -329,12 +333,25 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { pub struct RoadBuildResource { pub build_state: BuildState, pub pattern_builder: LanePatternBuilder, - pub snap_to_grid: bool, - pub snap_to_angle: bool, + pub snapping: Snapping, pub height_offset: f32, +// pub height_reference: HeightReference, pub streight_points: Vec, } +#[derive(Default, Clone, Copy)] +pub enum Snapping { + #[default] None, + SnapToGrid, SnapToAngle +} + +// #[derive(Default, Clone, Copy)] +// pub enum HeightReference { +// #[default] Ground, +// Start, Absolute, +// MaxIncline, MaxDecline +// } + fn check_angle(map: &Map, from: MapProject, to: Vec2, is_rail: bool) -> bool { let max_turn_angle = if is_rail { 0.0 diff --git a/simulation/src/map/objects/intersection.rs b/simulation/src/map/objects/intersection.rs index 816fe0f7..a9f0d646 100644 --- a/simulation/src/map/objects/intersection.rs +++ b/simulation/src/map/objects/intersection.rs @@ -72,7 +72,10 @@ impl Intersection { .roads .iter() .flat_map(|x| roads.get(*x)) - .map(|x| OrderedFloat(x.interface_from(self.id))) + .map(|x| { + if self.is_roundabout() { OrderedFloat(x.interface_from(self.id)) } + else { OrderedFloat(x.width) } + }) .max() .map(|x| x.0) .unwrap_or(10.0); From 5e1eb4ebf4512840ab0275cd73b3d82b28125657 Mon Sep 17 00:00:00 2001 From: TEKGroneschild Date: Mon, 10 Jun 2024 19:26:18 +0200 Subject: [PATCH 6/9] Adding comments and information about trains --- native_app/src/newgui/hud/toolbox/train.rs | 6 +++--- native_app/src/newgui/tools/addtrain.rs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/native_app/src/newgui/hud/toolbox/train.rs b/native_app/src/newgui/hud/toolbox/train.rs index f43ed191..de985829 100644 --- a/native_app/src/newgui/hud/toolbox/train.rs +++ b/native_app/src/newgui/hud/toolbox/train.rs @@ -20,9 +20,9 @@ pub fn train_properties(uiw: &UiWorld) { state.wagons.clear(); state.set_zero(); } - label(format!("Acceleration: {}", state.acceleration)); - label(format!("Deceleration: {}", state.deceleration)); - label(format!("Total Lenght: {}", state.total_lenght.ceil())); + label(format!("Acceleration: {:.1} m/s^2", state.acceleration)); + label(format!("Deceleration: {:.1} m/s^2", state.deceleration)); + label(format!("Total Lenght: {} m", state.total_lenght.ceil())); }); mincolumn(0.5, || { diff --git a/native_app/src/newgui/tools/addtrain.rs b/native_app/src/newgui/tools/addtrain.rs index e4494e82..4eb35630 100644 --- a/native_app/src/newgui/tools/addtrain.rs +++ b/native_app/src/newgui/tools/addtrain.rs @@ -14,11 +14,14 @@ use std::option::Option::None; #[derive(Clone, Debug, Default)] pub struct TrainSpawnResource { pub wagons: Vec, + /// m/s pub max_speed: f32, + /// m/s^2 pub acceleration: f32, + /// m/s^2 pub deceleration: f32, + /// meter pub total_lenght: f32, - } /// Addtrain handles the "Adding a train" tool From 9e9d65b4568f99ac0e5ed226a0e3f247416a5b73 Mon Sep 17 00:00:00 2001 From: TEKGroneschild Date: Tue, 11 Jun 2024 17:56:44 +0200 Subject: [PATCH 7/9] More height offset options. reletive to ground and reletive to start --- .../src/newgui/hud/toolbox/roadbuild.rs | 97 ++++++++++++------- native_app/src/newgui/tools/roadbuild.rs | 54 ++++++----- 2 files changed, 92 insertions(+), 59 deletions(-) diff --git a/native_app/src/newgui/hud/toolbox/roadbuild.rs b/native_app/src/newgui/hud/toolbox/roadbuild.rs index 14f32474..c6b7fc09 100644 --- a/native_app/src/newgui/hud/toolbox/roadbuild.rs +++ b/native_app/src/newgui/hud/toolbox/roadbuild.rs @@ -3,11 +3,11 @@ use yakui::{ image, reflow, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, MainAxisSize, Vec2 }; -use goryak::{image_button, mincolumn, padxy, primary}; +use goryak::{image_button, mincolumn, minrow, padxy, primary}; use simulation::map::LanePatternBuilder; use crate::newgui::hud::toolbox::updown_value; -use crate::newgui::roadbuild::{RoadBuildResource, Snapping}; +use crate::newgui::roadbuild::{HeightReference, RoadBuildResource, Snapping}; use crate::newgui::textures::UiTextures; use crate::uiworld::UiWorld; @@ -24,43 +24,66 @@ pub fn roadbuild_properties(uiw: &UiWorld) { let active = (c, c.with_alpha(0.7)); let default = (Color::WHITE.with_alpha(0.3), Color::WHITE.with_alpha(0.5)); - mincolumn(0.0, || { - let (snapping_none, snapping_grid, snapping_angel) = match state.snapping { - Snapping::None => {(active, default, default)}, - Snapping::SnapToGrid => {(default, active, default)}, - Snapping::SnapToAngle => {(default, default, active)}, - }; - - if image_button( - uiw.read::().get("snap_notting"), - Vec2::new(25.0, 25.0), - snapping_none.0, - snapping_none.1, - primary(), - "no snapping", - ).clicked { state.snapping = Snapping::None; } + mincolumn(4.0, || { + minrow(2.0, || { + let (snapping_none, snapping_grid, snapping_angel) = match state.snapping { + Snapping::None => {(active, default, default)}, + Snapping::SnapToGrid => {(default, active, default)}, + Snapping::SnapToAngle => {(default, default, active)}, + }; + if image_button( + uiw.read::().get("snap_notting"), + Vec2::new(30.0, 30.0), + snapping_none.0, snapping_none.1, + primary(), "no snapping", + ).clicked { state.snapping = Snapping::None; } + if image_button( + uiw.read::().get("snap_grid"), + Vec2::new(30.0, 30.0), + snapping_grid.0, snapping_grid.1, + primary(), "snap to grid", + ).clicked { state.snapping = Snapping::SnapToGrid; } + if image_button( + uiw.read::().get("snap_angle"), + Vec2::new(30.0, 30.0), + snapping_angel.0, snapping_angel.1, + primary(), "snap to angle", + ).clicked { state.snapping = Snapping::SnapToAngle; } + }); - if image_button( - uiw.read::().get("snap_grid"), - Vec2::new(25.0, 25.0), - snapping_grid.0, - snapping_grid.1, - primary(), - "snap to grid", - ) - .clicked { state.snapping = Snapping::SnapToGrid; } - - if image_button( - uiw.read::().get("snap_angle"), - Vec2::new(25.0, 25.0), - snapping_angel.0, - snapping_angel.1, - primary(), - "snap to angle", - ) - .clicked { state.snapping = Snapping::SnapToAngle; } + minrow(2.0, || { + let (hos_ground, hos_start, hos_incline, hos_decline) = match state.height_reference { + HeightReference::Ground => {(active, default, default, default)}, + HeightReference::Start => {(default, active, default, default)}, + HeightReference::MaxIncline => {(default, default, active, default)}, + HeightReference::MaxDecline => {(default, default, default, active)}, + }; + if image_button( + uiw.read::().get("height_reference_ground"), + Vec2::new(30.0, 30.0), + hos_ground.0, hos_ground.1, + primary(), "Relative to ground", + ).clicked { state.height_reference = HeightReference::Ground; } + if image_button( + uiw.read::().get("height_reference_start"), + Vec2::new(30.0, 30.0), + hos_start.0, hos_start.1, + primary(), "Relative to start", + ).clicked { state.height_reference = HeightReference::Start; } + if image_button( + uiw.read::().get("height_reference_incline"), + Vec2::new(30.0, 30.0), + hos_incline.0, hos_incline.1, + primary(), "Maximum incline", + ).clicked { state.height_reference = HeightReference::MaxIncline; } + if image_button( + uiw.read::().get("height_reference_decline"), + Vec2::new(30.0, 30.0), + hos_decline.0, hos_decline.1, + primary(), "Maximum decline", + ).clicked { state.height_reference = HeightReference::MaxDecline; } + }); }); - // Road elevation updown_value(&mut state.height_offset, 2.0, "m"); diff --git a/native_app/src/newgui/tools/roadbuild.rs b/native_app/src/newgui/tools/roadbuild.rs index 65477d7a..3c6738b0 100644 --- a/native_app/src/newgui/tools/roadbuild.rs +++ b/native_app/src/newgui/tools/roadbuild.rs @@ -44,28 +44,37 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { return; } - let nosnapping = inp.act.contains(&InputAction::NoSnapping); - // Prepare mousepos depending on snap to grid - let unproj = unwrap_ret!(inp.unprojected); let grid_size = 20.0; + let unproj = unwrap_ret!(inp.unprojected); + let mut interpoliation_points: Vec = Vec::new(); + let nosnapping = inp.act.contains(&InputAction::NoSnapping); + + let mouse_height = match (state.height_reference, state.build_state) { + (HeightReference::Start, Start(id)|StartInterp(id)|Connection(id, _)) + => id.pos.z + state.height_offset, + (HeightReference::Ground|HeightReference::Start, _) => unproj.z + state.height_offset, + (HeightReference::MaxIncline|HeightReference::MaxDecline, _) => unproj.z, // work in progress + }; + + // Prepare mousepos depending on snap to grid or snap to angle let mousepos = match state.snapping { Snapping::None => { - unproj.up(state.height_offset) + unproj.z0().up(mouse_height) }, Snapping::SnapToGrid => { - let v = unproj.xy().snap(grid_size, grid_size); - v.z(unwrap_ret!(map.environment.height(v)) + state.height_offset) + unproj.xy().snap(grid_size, grid_size).z(mouse_height) }, Snapping::SnapToAngle => { - state.streight_points = state._update_points(map, unproj.up(state.height_offset)); - state.streight_points.iter() - .filter_map(|&point| { - let distance = point.distance(unproj); + interpoliation_points = state.update_points(map, unproj); + interpoliation_points.iter() + .map(|point| {point.xy()}) + .filter_map(|point| { + let distance = point.distance(unproj.xy()); if distance < grid_size {Some((point, distance))} else { None } }) .reduce(|acc, e| { if acc.1 < e.1 {acc} else { e } }) - .unwrap_or((unproj.up(state.height_offset), 0.0)).0 + .unwrap_or((unproj.xy(), 0.0)).0.z0().up(mouse_height) } }; @@ -278,7 +287,7 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { } } - state.update_drawing(map, immdraw, cur_proj, patwidth, is_valid, points); + state.update_drawing(map, immdraw, cur_proj, patwidth, is_valid, points, interpoliation_points); if is_valid && inp.just_act.contains(&InputAction::Select) { log::info!("left clicked with state {:?} and {:?}", state.build_state, cur_proj.kind); @@ -335,8 +344,7 @@ pub struct RoadBuildResource { pub pattern_builder: LanePatternBuilder, pub snapping: Snapping, pub height_offset: f32, -// pub height_reference: HeightReference, - pub streight_points: Vec, + pub height_reference: HeightReference, } #[derive(Default, Clone, Copy)] @@ -345,12 +353,13 @@ pub enum Snapping { SnapToGrid, SnapToAngle } -// #[derive(Default, Clone, Copy)] -// pub enum HeightReference { -// #[default] Ground, -// Start, Absolute, -// MaxIncline, MaxDecline -// } +#[derive(Default, Clone, Copy)] +pub enum HeightReference { + #[default] Ground, + Start, + MaxIncline, + MaxDecline +} fn check_angle(map: &Map, from: MapProject, to: Vec2, is_rail: bool) -> bool { let max_turn_angle = if is_rail { @@ -441,6 +450,7 @@ impl RoadBuildResource { patwidth: f32, is_valid: bool, points: Option, + interpoliation_points: Vec, ) { let mut proj_pos = proj.pos; proj_pos.z += 0.4; @@ -450,7 +460,7 @@ impl RoadBuildResource { simulation::colors().gui_danger }; - self.streight_points.iter().for_each(|p|{ + interpoliation_points.iter().for_each(|p|{ immdraw.circle(*p, 2.0); }); @@ -503,7 +513,7 @@ impl RoadBuildResource { immdraw.polyline(p.into_vec(), patwidth, false).color(col); } - pub fn _update_points( + pub fn update_points( &self, map: &Map, mousepos: Vec3, From dd884d12c7a5275936498f36ab8500a512476271 Mon Sep 17 00:00:00 2001 From: TEKGroneschild Date: Tue, 11 Jun 2024 18:01:34 +0200 Subject: [PATCH 8/9] Forgot to commit icons --- assets/ui/icons/height_reference_decline.png | 3 +++ assets/ui/icons/height_reference_ground.png | 3 +++ assets/ui/icons/height_reference_incline.png | 3 +++ assets/ui/icons/height_reference_start.png | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 assets/ui/icons/height_reference_decline.png create mode 100644 assets/ui/icons/height_reference_ground.png create mode 100644 assets/ui/icons/height_reference_incline.png create mode 100644 assets/ui/icons/height_reference_start.png diff --git a/assets/ui/icons/height_reference_decline.png b/assets/ui/icons/height_reference_decline.png new file mode 100644 index 00000000..5d6d2760 --- /dev/null +++ b/assets/ui/icons/height_reference_decline.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21d02dbab234e872936d7b9942e78437f974d50265f96669d8989a91ac09a2fb +size 2533 diff --git a/assets/ui/icons/height_reference_ground.png b/assets/ui/icons/height_reference_ground.png new file mode 100644 index 00000000..5bbb687a --- /dev/null +++ b/assets/ui/icons/height_reference_ground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24172890746fa539b7c78bcdbf06596b25d675b1ba4b0dd0d07000fa36a7d8b7 +size 1654 diff --git a/assets/ui/icons/height_reference_incline.png b/assets/ui/icons/height_reference_incline.png new file mode 100644 index 00000000..1a91eb18 --- /dev/null +++ b/assets/ui/icons/height_reference_incline.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:360f8a38677988faec1c9e25cea46a6bbbe26642957b1494aff4318f88f59147 +size 2381 diff --git a/assets/ui/icons/height_reference_start.png b/assets/ui/icons/height_reference_start.png new file mode 100644 index 00000000..2dc7ef26 --- /dev/null +++ b/assets/ui/icons/height_reference_start.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5eadb9fc96a41a557c8893c8f37c6a8b046831ca00a17cc44fe5c3048ebfef7e +size 9264 From 59c71162f86f55c53c2a1a66caff2cf869502031 Mon Sep 17 00:00:00 2001 From: TEKGroneschild Date: Wed, 19 Jun 2024 17:21:22 +0200 Subject: [PATCH 9/9] Fixing typos and substitute functions --- native_app/src/newgui/tools/roadbuild.rs | 14 ++++++------ simulation/src/map/objects/intersection.rs | 25 ++++++++++------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/native_app/src/newgui/tools/roadbuild.rs b/native_app/src/newgui/tools/roadbuild.rs index 3c6738b0..b027bb1d 100644 --- a/native_app/src/newgui/tools/roadbuild.rs +++ b/native_app/src/newgui/tools/roadbuild.rs @@ -47,7 +47,7 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { let grid_size = 20.0; let unproj = unwrap_ret!(inp.unprojected); - let mut interpoliation_points: Vec = Vec::new(); + let mut interpolation_points: Vec = Vec::new(); let nosnapping = inp.act.contains(&InputAction::NoSnapping); let mouse_height = match (state.height_reference, state.build_state) { @@ -66,8 +66,8 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { unproj.xy().snap(grid_size, grid_size).z(mouse_height) }, Snapping::SnapToAngle => { - interpoliation_points = state.update_points(map, unproj); - interpoliation_points.iter() + interpolation_points = state.posible_interpolations(map, unproj); + interpolation_points.iter() .map(|point| {point.xy()}) .filter_map(|point| { let distance = point.distance(unproj.xy()); @@ -287,7 +287,7 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { } } - state.update_drawing(map, immdraw, cur_proj, patwidth, is_valid, points, interpoliation_points); + state.update_drawing(map, immdraw, cur_proj, patwidth, is_valid, points, interpolation_points); if is_valid && inp.just_act.contains(&InputAction::Select) { log::info!("left clicked with state {:?} and {:?}", state.build_state, cur_proj.kind); @@ -450,7 +450,7 @@ impl RoadBuildResource { patwidth: f32, is_valid: bool, points: Option, - interpoliation_points: Vec, + interpolation_points: Vec, ) { let mut proj_pos = proj.pos; proj_pos.z += 0.4; @@ -460,7 +460,7 @@ impl RoadBuildResource { simulation::colors().gui_danger }; - interpoliation_points.iter().for_each(|p|{ + interpolation_points.iter().for_each(|p|{ immdraw.circle(*p, 2.0); }); @@ -513,7 +513,7 @@ impl RoadBuildResource { immdraw.polyline(p.into_vec(), patwidth, false).color(col); } - pub fn update_points( + pub fn posible_interpolations( &self, map: &Map, mousepos: Vec3, diff --git a/simulation/src/map/objects/intersection.rs b/simulation/src/map/objects/intersection.rs index a9f0d646..af7bad6c 100644 --- a/simulation/src/map/objects/intersection.rs +++ b/simulation/src/map/objects/intersection.rs @@ -175,24 +175,21 @@ impl Intersection { } fn interface_calc_numerically(&self, w1: f32, w2: f32, r1: &Road, r2: &Road) -> f32 { - let w = (w1+w2)* 0.75; + let w: f32 = (w1+w2)* 0.80; - let mut points1: Vec<(Vec3, Vec3)> = r1.points().equipoints_dir(1.0, true).collect(); - let mut points2: Vec<(Vec3, Vec3)> = r2.points().equipoints_dir(1.0, true).collect(); + let mut points1: Vec<(Vec3, Vec3)> = r1.points() + .points_dirs_along((1..r1.points().length() as i32).map(|d| d as f32)).collect(); + let mut points2: Vec<(Vec3, Vec3)> = r2.points() + .points_dirs_along((1..r2.points().length() as i32).map(|d| d as f32)).collect(); - if r1.dst == self.id { points1.reverse(); } - if r2.dst == self.id { points2.reverse(); } + if !(r1.src == self.id) { points1.reverse(); } + if !(r2.src == self.id) { points2.reverse(); } - let points = points1.into_iter().zip(points2) + points1.into_iter().zip(points2) .map(|((p1,_),(p2,_))| (p1.xy(), p2.xy()) ) - .find(|p| p.0.distance(p.1) > w ); - - if let Some(p) = points { - (self.pos.xy().distance(p.0) + self.pos.xy().distance(p.0)) * 0.5 - } else { - 50.0 - } - + .find(|p| p.0.distance(p.1) > w ) + .and_then(|p| Some((self.pos.xy().distance(p.0)+self.pos.xy().distance(p.0))*0.5)) + .unwrap_or(50.0) } pub fn empty_interface(width: f32) -> f32 {