diff --git a/assets/models/passenger-emu-front.glb b/assets/models/passenger-emu-front.glb new file mode 100644 index 00000000..e9db4ad8 --- /dev/null +++ b/assets/models/passenger-emu-front.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab034896acd415bfcbe7d4cecb24eab35eda59489126be77421f8c726f935926 +size 71680 diff --git a/assets/models/passenger-emu-middle.glb b/assets/models/passenger-emu-middle.glb new file mode 100644 index 00000000..eb154b68 --- /dev/null +++ b/assets/models/passenger-emu-middle.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c83c7c8684cda1b2ea42228a73d6f740951b9c1abd37b5f7a571c2541cb889b +size 72444 diff --git a/assets/models/passenger-emu-rear.glb b/assets/models/passenger-emu-rear.glb new file mode 100644 index 00000000..48ad51e1 --- /dev/null +++ b/assets/models/passenger-emu-rear.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d76dd167e8054c595daabfe9ea8a8ae89b7f26fad5354f71db44d6fca45475d4 +size 71700 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 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/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..5e278cdd --- /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 = 2000.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.0, + 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.0, + 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.0, + 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/roadbuild.rs b/native_app/src/newgui/hud/toolbox/roadbuild.rs index 41c258eb..c6b7fc09 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, minrow, padxy, primary}; use simulation::map::LanePatternBuilder; use crate::newgui::hud::toolbox::updown_value; -use crate::newgui::roadbuild::RoadBuildResource; +use crate::newgui::roadbuild::{HeightReference, RoadBuildResource, Snapping}; use crate::newgui::textures::UiTextures; use crate::uiworld::UiWorld; @@ -21,26 +20,70 @@ 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(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; } + }); + 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/hud/toolbox/train.rs b/native_app/src/newgui/hud/toolbox/train.rs index 621f3c2a..de985829 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.set_zero(); + } + 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, || { + 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..4eb35630 100644 --- a/native_app/src/newgui/tools/addtrain.rs +++ b/native_app/src/newgui/tools/addtrain.rs @@ -3,21 +3,40 @@ 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, + /// 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 /// It allows to add a train to any rail lane pub fn addtrain(sim: &Simulation, uiworld: &UiWorld) { profiling::scope!("gui::addtrain"); + let state = &mut *uiworld.write::(); let tool = *uiworld.read::(); + if !matches!(tool, Tool::Train) { + state.wagons.clear(); + state.set_zero(); return; } - + let inp = uiworld.read::(); let mut potential = uiworld.write::(); @@ -38,17 +57,21 @@ 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); - 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 +81,37 @@ 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); + 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 2d668765..b027bb1d 100644 --- a/native_app/src/newgui/tools/roadbuild.rs +++ b/native_app/src/newgui/tools/roadbuild.rs @@ -1,12 +1,12 @@ use engine::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, }; 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::inputmap::{InputAction, InputMap}; @@ -20,6 +20,7 @@ pub enum BuildState { Hover, Start(MapProject), StartInterp(MapProject), + Connection(MapProject, MapProject), Interpolation(Vec2, MapProject), } @@ -43,16 +44,38 @@ 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 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 { - unproj.up(state.height_offset) + let unproj = unwrap_ret!(inp.unprojected); + let mut interpolation_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.z0().up(mouse_height) + }, + Snapping::SnapToGrid => { + unproj.xy().snap(grid_size, grid_size).z(mouse_height) + }, + Snapping::SnapToAngle => { + interpolation_points = state.posible_interpolations(map, unproj); + interpolation_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.xy(), 0.0)).0.z0().up(mouse_height) + } }; let log_camheight = cam.eye().z.log10(); @@ -116,11 +139,14 @@ pub fn roadbuild(sim: &Simulation, uiworld: &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 +183,80 @@ pub fn roadbuild(sim: &Simulation, uiworld: &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 +264,19 @@ pub fn roadbuild(sim: &Simulation, uiworld: &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, @@ -239,17 +287,13 @@ 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, interpolation_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 +305,24 @@ pub fn roadbuild(sim: &Simulation, uiworld: &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 +331,6 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { if let Some(wc) = potential_command.0.drain(..).next() { commands.push(wc); } - state.build_state = Hover; } _ => {} @@ -288,43 +342,49 @@ pub fn roadbuild(sim: &Simulation, uiworld: &UiWorld) { pub struct RoadBuildResource { pub build_state: BuildState, pub pattern_builder: LanePatternBuilder, - pub snap_to_grid: bool, + pub snapping: Snapping, pub height_offset: f32, + pub height_reference: HeightReference, +} + +#[derive(Default, Clone, Copy)] +pub enum Snapping { + #[default] None, + SnapToGrid, SnapToAngle +} + +#[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 { - 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 +423,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 { @@ -390,6 +450,7 @@ impl RoadBuildResource { patwidth: f32, is_valid: bool, points: Option, + interpolation_points: Vec, ) { let mut proj_pos = proj.pos; proj_pos.z += 0.4; @@ -399,6 +460,10 @@ impl RoadBuildResource { simulation::colors().gui_danger }; + interpolation_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,17 +508,110 @@ impl RoadBuildResource { .color(col); } - immdraw.circle(p.first().up(0.4), patwidth * 0.5).color(col); - immdraw.circle(p.last().up(0.4), patwidth * 0.5).color(col); - immdraw - .polyline( - p.into_vec() - .into_iter() - .map(|v| v.up(0.4)) - .collect::>(), - patwidth, - false, - ) - .color(col); + immdraw.circle(p.first(), patwidth * 0.5).color(col); + immdraw.circle(p.last(), patwidth * 0.5).color(col); + immdraw.polyline(p.into_vec(), patwidth, false).color(col); + } + + pub fn posible_interpolations( + &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( |(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() + }, + + (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 = + 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)) | + (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 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) | + (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 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![]}; + 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 line0 = Line::new(pos0.xy(), pos0.xy()+dir0.xy()); + let line1 = Line::new(pos1.xy(), pos1.xy()+dir1.xy()); + + 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![] } + } } } 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/map/objects/intersection.rs b/simulation/src/map/objects/intersection.rs index f0a40421..af7bad6c 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); @@ -139,19 +142,19 @@ 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); } @@ -159,7 +162,7 @@ impl Intersection { self.update_radius(roads); } - 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; @@ -168,7 +171,25 @@ 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: f32 = (w1+w2)* 0.80; + + 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.src == self.id) { points1.reverse(); } + if !(r2.src == self.id) { points2.reverse(); } + + points1.into_iter().zip(points2) + .map(|((p1,_),(p2,_))| (p1.xy(), p2.xy()) ) + .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 { @@ -180,7 +201,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 5e4c224e..84a948eb 100644 --- a/simulation/src/map/objects/road.rs +++ b/simulation/src/map/objects/road.rs @@ -602,4 +602,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) + } + } } 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)