From eae02757ad9bba9f6b0f8b00566f73787907e43f Mon Sep 17 00:00:00 2001 From: Teun Elisabeth Karel Groneschild <47506922+Groneschild@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:44:21 +0200 Subject: [PATCH] Trains (#110) * 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. * Substitute functions with excisting functions Substitute functions Vec2::{line_line_intersection, line_closed_point} with Line::{intersection_point, project} * Cool new features for trains. Make costum trains of any length for any persose. Make commuter, freight or high speed trains. * Changes to the trains * Changes * Adding comments and information about trains * More height offset options. reletive to ground and reletive to start * Forgot to commit icons * Fixing typos and substitute functions --- assets/models/passenger-emu-front.glb | 3 + assets/models/passenger-emu-middle.glb | 3 + assets/models/passenger-emu-rear.glb | 3 + 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 + assets/ui/icons/snap_angle.png | 3 + assets/ui/icons/snap_notting.png | 3 + base_mod/data.lua | 2 + base_mod/roadvehicles.lua | 27 ++ base_mod/rollingstock.lua | 83 +++++ native_app/src/init.rs | 2 + .../src/newgui/hud/toolbox/roadbuild.rs | 89 +++-- native_app/src/newgui/hud/toolbox/train.rs | 57 ++- native_app/src/newgui/tools/addtrain.rs | 68 +++- native_app/src/newgui/tools/roadbuild.rs | 340 +++++++++++++----- 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/map/objects/intersection.rs | 43 ++- simulation/src/map/objects/road.rs | 15 + simulation/src/transportation/train.rs | 102 +++--- simulation/src/world_command.rs | 18 +- 26 files changed, 888 insertions(+), 239 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 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 create mode 100644 assets/ui/icons/snap_angle.png create mode 100644 assets/ui/icons/snap_notting.png 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..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)