switch to new units in sim... and editor, darnit, accidentally ammended

commit
This commit is contained in:
Dustin Carlino 2019-01-30 13:11:12 -08:00
parent 133ec037c9
commit ac2b8f5a9a
32 changed files with 350 additions and 277 deletions

View File

@ -4,6 +4,9 @@
- try fixed pt again, for determinism purposes mostly
- get rid of dimensioned. just wrap f64's at first and get to the new API.
- go through and use less f64's... like LANE_THICKNESS, make_polygons, Circle::new, project_away
- audit inner_foo()'s
- rerun import
- change internal pt2d representation to int. JUST get that working first.
- clamp distances first, not points?

View File

@ -9,7 +9,6 @@ aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
counter = "0.4.3"
cpuprofiler = "0.0.3"
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
downcast = "0.9.2"
ezgui = { path = "../ezgui" }
generator = "0.6"

View File

@ -1,12 +1,11 @@
use crate::objects::{Ctx, ID};
use crate::plugins::{Plugin, PluginCtx};
use dimensioned::si;
use ezgui::{Color, GfxCtx, Key};
use geom::Distance;
use map_model::{
BuildingID, IntersectionID, IntersectionType, LaneID, LaneType, PathRequest, Pathfinder,
Position, Trace, LANE_THICKNESS,
};
use std::f64;
#[derive(Clone)]
enum Source {
@ -86,7 +85,7 @@ impl Plugin for SpawnAgent {
if recalculate {
let start = match self.from {
Source::Walking(from) => map.get_b(from).front_path.sidewalk,
Source::Driving(from) => Position::new(from, 0.0 * si::M),
Source::Driving(from) => Position::new(from, Distance::ZERO),
};
let end = match new_goal {
Goal::Building(to) => match self.from {
@ -121,10 +120,8 @@ impl Plugin for SpawnAgent {
can_use_bus_lanes: false,
},
) {
self.maybe_goal = Some((
new_goal,
path.trace(map, start.dist_along(), f64::MAX * si::M),
));
self.maybe_goal =
Some((new_goal, path.trace(map, start.dist_along(), Distance::MAX)));
} else {
self.maybe_goal = None;
}

View File

@ -1,8 +1,8 @@
use crate::objects::{Ctx, ID};
use crate::plugins::{Plugin, PluginCtx};
use crate::render::{draw_signal_cycle, draw_signal_diagram, DrawTurn};
use dimensioned::si;
use ezgui::{Color, GfxCtx, Key, ScreenPt, Wizard, WrappedWizard};
use geom::Duration;
use map_model::{ControlTrafficSignal, Cycle, IntersectionID, Map, TurnID, TurnPriority, TurnType};
// TODO Warn if there are empty cycles or if some turn is completely absent from the signal.
@ -78,12 +78,13 @@ impl Plugin for TrafficSignalEditor {
"{}",
ctx.primary.map.get_traffic_signal(self.i).cycles[self.current_cycle]
.duration
.value_unsafe as usize
.inner_seconds() as usize
),
)
{
let mut signal = ctx.primary.map.get_traffic_signal(self.i).clone();
signal.cycles[self.current_cycle].edit_duration((new_duration as f64) * si::S);
signal.cycles[self.current_cycle]
.edit_duration(Duration::seconds(new_duration as f64));
ctx.primary.map.edit_traffic_signal(signal);
self.cycle_duration_wizard = None;
} else if self.cycle_duration_wizard.as_ref().unwrap().aborted() {

View File

@ -169,7 +169,7 @@ impl Plugin for SimControls {
if ctx.input.is_update_event() {
// TODO https://gafferongames.com/post/fix_your_timestep/
let dt_s = elapsed_seconds(*last_step);
if dt_s >= TIMESTEP.value_unsafe / self.desired_speed {
if dt_s >= TIMESTEP.inner_seconds() / self.desired_speed {
let tick = ctx.primary.sim.time;
let events = ctx.primary.sim.step(&ctx.primary.map);
self.primary_events = Some((tick, events));

View File

@ -1,11 +1,9 @@
use crate::objects::Ctx;
use crate::plugins::{Plugin, PluginCtx};
use dimensioned::si;
use ezgui::{Color, GfxCtx, Key};
use geom::Line;
use geom::{Distance, Line};
use map_model::{Trace, LANE_THICKNESS};
use sim::{Tick, TripID};
use std::f64;
pub struct DiffTripState {
time: Tick,
@ -90,10 +88,10 @@ fn diff_trip(trip: TripID, ctx: &mut PluginCtx) -> DiffTripState {
};
let primary_route = primary_sim
.trip_to_agent(trip)
.and_then(|agent| primary_sim.trace_route(agent, primary_map, f64::MAX * si::M));
.and_then(|agent| primary_sim.trace_route(agent, primary_map, Distance::MAX));
let secondary_route = secondary_sim
.trip_to_agent(trip)
.and_then(|agent| secondary_sim.trace_route(agent, secondary_map, f64::MAX * si::M));
.and_then(|agent| secondary_sim.trace_route(agent, secondary_map, Distance::MAX));
if line.is_none() || primary_route.is_none() || secondary_route.is_none() {
warn!("{} isn't present in both sims", trip);

View File

@ -1,10 +1,9 @@
use crate::objects::Ctx;
use crate::plugins::{Plugin, PluginCtx};
use dimensioned::si;
use ezgui::{Color, GfxCtx, Key};
use geom::Distance;
use map_model::{Trace, LANE_THICKNESS};
use sim::{Tick, TripID};
use std::f64;
pub enum ShowRouteState {
Inactive,
@ -87,7 +86,7 @@ fn show_route(trip: TripID, ctx: &mut PluginCtx) -> ShowRouteState {
if let Some(trace) = ctx
.primary
.sim
.trace_route(agent, &ctx.primary.map, f64::MAX * si::M)
.trace_route(agent, &ctx.primary.map, Distance::MAX)
{
ShowRouteState::Active(time, trip, Some(trace))
} else {
@ -108,7 +107,7 @@ fn debug_all_routes(ctx: &mut PluginCtx) -> ShowRouteState {
let mut traces: Vec<Trace> = Vec::new();
for trip in sim.get_stats().canonical_pt_per_trip.keys() {
if let Some(agent) = sim.trip_to_agent(*trip) {
if let Some(trace) = sim.trace_route(agent, &ctx.primary.map, f64::MAX * si::M) {
if let Some(trace) = sim.trace_route(agent, &ctx.primary.map, Distance::MAX) {
traces.push(trace);
}
}

View File

@ -1,8 +1,8 @@
use crate::objects::{Ctx, ID};
use crate::plugins::{Plugin, PluginCtx};
use crate::render::{draw_signal_diagram, DrawTurn};
use dimensioned::si;
use ezgui::{Color, GfxCtx, Key};
use geom::Duration;
use map_model::{IntersectionID, LaneID, TurnType};
pub struct TurnCyclerState {
@ -95,7 +95,7 @@ impl Plugin for TurnCyclerState {
signal.current_cycle_and_remaining_time(ctx.sim.time.as_time());
if ctx.sim.is_in_overtime(i) {
// TODO Hacky way of indicating overtime. Should make a 3-case enum.
time_left = -1.0 * si::S;
time_left = Duration::seconds(-1.0);
}
draw_signal_diagram(
i,

View File

@ -63,7 +63,7 @@ impl Plugin for WarpState {
return false;
} else {
ctx.canvas
.center_on_map_pt(line.dist_along(percent * line.length()));
.center_on_map_pt(line.dist_along(line.length() * percent));
}
}
};

View File

@ -1,9 +1,8 @@
use crate::colors::ColorScheme;
use crate::objects::{Ctx, ID};
use crate::render::{RenderOptions, Renderable};
use dimensioned::si;
use ezgui::{Color, Drawable, GfxCtx, Prerender};
use geom::{Bounds, Line, Polygon, Pt2D};
use geom::{Bounds, Distance, Line, Polygon, Pt2D};
use map_model::{Building, BuildingID, LANE_THICKNESS};
pub struct DrawBuilding {
@ -20,7 +19,7 @@ impl DrawBuilding {
// overlap. For now, this cleanup is visual; it doesn't belong in the map_model layer.
let mut front_path_line = bldg.front_path.line.clone();
let len = front_path_line.length();
let trim_back = LANE_THICKNESS / 2.0 * si::M;
let trim_back = Distance::meters(LANE_THICKNESS / 2.0);
if len > trim_back && len - trim_back > geom::EPSILON_DIST {
front_path_line = Line::new(
front_path_line.pt1(),

View File

@ -1,8 +1,7 @@
use crate::objects::{Ctx, ID};
use crate::render::{RenderOptions, Renderable};
use dimensioned::si;
use ezgui::{Color, GfxCtx};
use geom::{Bounds, PolyLine, Polygon, Pt2D};
use geom::{Bounds, Distance, PolyLine, Polygon, Pt2D};
use map_model::{BusStop, BusStopID, Map, LANE_THICKNESS};
pub struct DrawBusStop {
@ -13,7 +12,7 @@ pub struct DrawBusStop {
impl DrawBusStop {
pub fn new(stop: &BusStop, map: &Map) -> DrawBusStop {
let radius = 2.0 * si::M;
let radius = Distance::meters(2.0);
// TODO if this happens to cross a bend in the lane, it'll look weird. similar to the
// lookahead arrows and center points / dashed white, we really want to render an Interval
// or something.

View File

@ -1,8 +1,7 @@
use crate::objects::{Ctx, ID};
use crate::render::{RenderOptions, Renderable};
use dimensioned::si;
use ezgui::{Color, GfxCtx};
use geom::{Angle, Bounds, Circle, PolyLine, Polygon, Pt2D};
use geom::{Angle, Bounds, Circle, Distance, PolyLine, Polygon, Pt2D};
use map_model::{Map, TurnType};
use sim::{CarID, CarState, DrawCarInput, MIN_CAR_LENGTH};
use std;
@ -54,15 +53,18 @@ impl DrawCar {
};
}
let (front_blinker_pos, front_blinker_angle) =
input.body.dist_along(input.body.length() - 0.5 * si::M);
let (back_blinker_pos, back_blinker_angle) = input.body.dist_along(0.5 * si::M);
let (front_blinker_pos, front_blinker_angle) = input
.body
.dist_along(input.body.length() - Distance::meters(0.5));
let (back_blinker_pos, back_blinker_angle) = input.body.dist_along(Distance::meters(0.5));
let blinker_radius = 0.3;
let window_length_gap = 0.2;
let window_thickness = 0.3;
let front_window = {
let (pos, angle) = input.body.dist_along(input.body.length() - 1.0 * si::M);
let (pos, angle) = input
.body
.dist_along(input.body.length() - Distance::meters(1.0));
thick_line_from_angle(
window_thickness,
CAR_WIDTH - 2.0 * window_length_gap,
@ -74,7 +76,7 @@ impl DrawCar {
)
};
let back_window = {
let (pos, angle) = input.body.dist_along(1.0 * si::M);
let (pos, angle) = input.body.dist_along(Distance::meters(1.0));
thick_line_from_angle(
window_thickness * 0.8,
CAR_WIDTH - 2.0 * window_length_gap,

View File

@ -1,8 +1,7 @@
use crate::objects::{Ctx, ID};
use crate::render::{RenderOptions, Renderable, EXTRA_SHAPE_POINT_RADIUS, EXTRA_SHAPE_THICKNESS};
use dimensioned::si;
use ezgui::{Color, GfxCtx};
use geom::{Bounds, Circle, GPSBounds, PolyLine, Polygon, Pt2D};
use geom::{Bounds, Circle, Distance, GPSBounds, PolyLine, Polygon, Pt2D};
use kml::ExtraShape;
use map_model::{FindClosest, RoadID, LANE_THICKNESS};
use std::collections::BTreeMap;
@ -52,19 +51,18 @@ impl DrawExtraShape {
})
} else {
let width = get_sidewalk_width(&s.attributes)
.unwrap_or(EXTRA_SHAPE_THICKNESS * si::M)
.value_unsafe;
.unwrap_or(Distance::meters(EXTRA_SHAPE_THICKNESS));
let pl = PolyLine::new(pts);
// The blockface line endpoints will be close to other roads, so match based on the
// middle of the blockface.
// TODO Long blockfaces sometimes cover two roads. Should maybe find ALL matches within
// the threshold distance?
let road = closest
.closest_pt(pl.middle(), 5.0 * LANE_THICKNESS * si::M)
.closest_pt(pl.middle(), Distance::meters(5.0 * LANE_THICKNESS))
.map(|(r, _)| r);
Some(DrawExtraShape {
id,
shape: Shape::Polygon(pl.make_polygons(width)),
shape: Shape::Polygon(pl.make_polygons(width.inner_meters())),
attributes: s.attributes,
road,
})
@ -110,15 +108,14 @@ impl Renderable for DrawExtraShape {
}
// See https://www.seattle.gov/Documents/Departments/SDOT/GIS/Sidewalks_OD.pdf
fn get_sidewalk_width(attribs: &BTreeMap<String, String>) -> Option<si::Meter<f64>> {
let meters_per_inch = 0.0254;
fn get_sidewalk_width(attribs: &BTreeMap<String, String>) -> Option<Distance> {
let base_width = attribs
.get("SW_WIDTH")
.and_then(|s| s.parse::<f64>().ok())
.map(|inches| inches * meters_per_inch * si::M)?;
.map(|i| Distance::inches(i))?;
let filler_width = attribs
.get("FILLERWID")
.and_then(|s| s.parse::<f64>().ok())
.map(|inches| inches * meters_per_inch * si::M)?;
.map(|i| Distance::inches(i))?;
Some(base_width + filler_width)
}

View File

@ -1,9 +1,8 @@
use crate::colors::ColorScheme;
use crate::objects::{Ctx, ID};
use crate::render::{DrawCrosswalk, DrawTurn, RenderOptions, Renderable, MIN_ZOOM_FOR_MARKINGS};
use dimensioned::si;
use ezgui::{Color, Drawable, GfxCtx, Prerender, ScreenPt, Text};
use geom::{Bounds, Circle, Line, Polygon, Pt2D};
use geom::{Bounds, Circle, Distance, Duration, Line, Polygon, Pt2D};
use map_model::{
Cycle, Intersection, IntersectionID, IntersectionType, Map, TurnPriority, TurnType,
LANE_THICKNESS,
@ -225,7 +224,7 @@ fn draw_signal_cycle_with_icons(cycle: &Cycle, g: &mut GfxCtx, ctx: &Ctx) {
};
}
let radius = LANE_THICKNESS / 2.0 * si::M;
let radius = Distance::meters(LANE_THICKNESS / 2.0);
// TODO Ignore right_ok...
{
@ -235,7 +234,7 @@ fn draw_signal_cycle_with_icons(cycle: &Cycle, g: &mut GfxCtx, ctx: &Ctx) {
} else {
ctx.cs.get_def("traffic light stop", Color::RED)
};
g.draw_circle(color, &Circle::new(center1, radius.value_unsafe));
g.draw_circle(color, &Circle::new(center1, radius.inner_meters()));
}
if let Some(pri) = left_priority {
@ -249,14 +248,16 @@ fn draw_signal_cycle_with_icons(cycle: &Cycle, g: &mut GfxCtx, ctx: &Ctx) {
};
g.draw_circle(
ctx.cs.get_def("traffic light box", Color::BLACK),
&Circle::new(center2, radius.value_unsafe),
&Circle::new(center2, radius.inner_meters()),
);
g.draw_arrow(
color,
0.1,
&Line::new(
center2.project_away(radius.value_unsafe, lane_line.angle().rotate_degs(90.0)),
center2.project_away(radius.value_unsafe, lane_line.angle().rotate_degs(-90.0)),
center2
.project_away(radius.inner_meters(), lane_line.angle().rotate_degs(90.0)),
center2
.project_away(radius.inner_meters(), lane_line.angle().rotate_degs(-90.0)),
),
);
}
@ -266,7 +267,7 @@ fn draw_signal_cycle_with_icons(cycle: &Cycle, g: &mut GfxCtx, ctx: &Ctx) {
pub fn draw_signal_diagram(
i: IntersectionID,
current_cycle: usize,
time_left: Option<si::Second<f64>>,
time_left: Option<Duration>,
y1_screen: f64,
g: &mut GfxCtx,
ctx: &Ctx,
@ -289,7 +290,7 @@ pub fn draw_signal_diagram(
for (idx, cycle) in cycles.iter().enumerate() {
if idx == current_cycle && time_left.is_some() {
// TODO Hacky way of indicating overtime
if time_left.unwrap() < 0.0 * si::S {
if time_left.unwrap() < Duration::ZERO {
let mut txt = Text::from_line(format!("Cycle {}: ", idx + 1));
txt.append(
"OVERTIME".to_string(),
@ -300,7 +301,7 @@ pub fn draw_signal_diagram(
labels.push(Text::from_line(format!(
"Cycle {}: {:.01}s / {}",
idx + 1,
(cycle.duration - time_left.unwrap()).value_unsafe,
(cycle.duration - time_left.unwrap()).inner_seconds(),
cycle.duration
)));
}
@ -364,13 +365,13 @@ pub fn draw_signal_diagram(
fn find_pts_between(pts: &Vec<Pt2D>, start: Pt2D, end: Pt2D) -> Option<Vec<Pt2D>> {
let mut result = Vec::new();
for pt in pts {
if result.is_empty() && pt.approx_eq(start, 1.0 * si::M) {
if result.is_empty() && pt.approx_eq(start, Distance::meters(1.0)) {
result.push(*pt);
} else if !result.is_empty() {
result.push(*pt);
}
// start and end might be the same.
if !result.is_empty() && pt.approx_eq(end, 1.0 * si::M) {
if !result.is_empty() && pt.approx_eq(end, Distance::meters(1.0)) {
return Some(result);
}
}
@ -383,7 +384,7 @@ fn find_pts_between(pts: &Vec<Pt2D>, start: Pt2D, end: Pt2D) -> Option<Vec<Pt2D>
// Go through again, looking for end
for pt in pts {
result.push(*pt);
if pt.approx_eq(end, 1.0 * si::M) {
if pt.approx_eq(end, Distance::meters(1.0)) {
return Some(result);
}
}

View File

@ -4,9 +4,8 @@ use crate::render::{
RenderOptions, Renderable, BIG_ARROW_THICKNESS, MIN_ZOOM_FOR_MARKINGS,
PARCEL_BOUNDARY_THICKNESS,
};
use dimensioned::si;
use ezgui::{Color, Drawable, GfxCtx, Prerender};
use geom::{Bounds, Circle, Line, Polygon, Pt2D};
use geom::{Bounds, Circle, Distance, Line, Polygon, Pt2D};
use map_model::{
IntersectionType, Lane, LaneID, LaneType, Map, Road, Turn, LANE_THICKNESS, PARKING_SPOT_LENGTH,
};
@ -130,7 +129,7 @@ fn perp_line(l: Line, length: f64) -> Line {
}
fn calculate_sidewalk_lines(lane: &Lane, cs: &ColorScheme) -> Vec<(Color, Polygon)> {
let tile_every = LANE_THICKNESS * si::M;
let tile_every = Distance::meters(LANE_THICKNESS);
let color = cs.get_def("sidewalk lines", Color::grey(0.7));
let length = lane.length();
@ -188,11 +187,11 @@ fn calculate_driving_lines(lane: &Lane, parent: &Road, cs: &ColorScheme) -> Vec<
return Vec::new();
}
let dash_separation = 1.5 * si::M;
let dash_len = 1.0 * si::M;
let dash_separation = Distance::meters(1.5);
let dash_len = Distance::meters(1.0);
let lane_edge_pts = lane.lane_center_pts.shift_left(LANE_THICKNESS / 2.0);
if lane_edge_pts.length() < 2.0 * dash_separation {
if lane_edge_pts.length() < dash_separation * 2.0 {
return Vec::new();
}
// Don't draw the dashes too close to the ends.
@ -219,7 +218,7 @@ fn calculate_stop_sign_line(
// TODO maybe draw the stop sign octagon on each lane?
let (pt1, angle) = lane.safe_dist_along(lane.length() - (1.0 * si::M))?;
let (pt1, angle) = lane.safe_dist_along(lane.length() - Distance::meters(1.0))?;
// Reuse perp_line. Project away an arbitrary amount
let pt2 = pt1.project_away(1.0, angle);
// Don't clobber the yellow line.
@ -258,13 +257,13 @@ fn calculate_turn_markings(map: &Map, lane: &Lane, cs: &ColorScheme) -> Vec<(Col
fn turn_markings(turn: &Turn, map: &Map, cs: &ColorScheme) -> Vec<(Color, Polygon)> {
let lane = map.get_l(turn.id.src);
let len = lane.length();
if len < 7.0 * si::M {
if len < Distance::meters(7.0) {
return Vec::new();
}
let common_base = lane
.lane_center_pts
.slice(len - 7.0 * si::M, len - 5.0 * si::M)
.slice(len - Distance::meters(7.0), len - Distance::meters(5.0))
.unwrap()
.0;
let base_polygon = common_base.make_polygons(0.1);

View File

@ -3,9 +3,8 @@ use crate::render::{
RenderOptions, Renderable, BIG_ARROW_THICKNESS, CROSSWALK_LINE_THICKNESS,
TURN_ICON_ARROW_LENGTH, TURN_ICON_ARROW_THICKNESS,
};
use dimensioned::si;
use ezgui::{Color, GfxCtx};
use geom::{Bounds, Circle, Line, Polygon, Pt2D};
use geom::{Bounds, Circle, Distance, Line, Polygon, Pt2D};
use map_model::{Map, Turn, TurnID, LANE_THICKNESS};
use std::f64;
@ -23,9 +22,9 @@ impl DrawTurn {
let end_line = map.get_l(turn.id.src).end_line(turn.id.parent);
// Start the distance from the intersection
let icon_center = end_line
.reverse()
.unbounded_dist_along((offset_along_lane + 0.5) * TURN_ICON_ARROW_LENGTH * si::M);
let icon_center = end_line.reverse().unbounded_dist_along(Distance::meters(
(offset_along_lane + 0.5) * TURN_ICON_ARROW_LENGTH,
));
let icon_circle = Circle::new(icon_center, TURN_ICON_ARROW_LENGTH / 2.0);
@ -51,10 +50,10 @@ impl DrawTurn {
}
pub fn draw_dashed(turn: &Turn, g: &mut GfxCtx, color: Color) {
let dash_len = 1.0 * si::M;
let dashed = turn
.geom
.dashed_polygons(BIG_ARROW_THICKNESS, dash_len, 0.5 * si::M);
let dash_len = Distance::meters(1.0);
let dashed =
turn.geom
.dashed_polygons(BIG_ARROW_THICKNESS, dash_len, Distance::meters(0.5));
g.draw_polygon_batch(dashed.iter().map(|poly| (color, poly)).collect());
// And a cap on the arrow. In case the last line is long, trim it to be the dash
// length.
@ -119,8 +118,8 @@ impl DrawCrosswalk {
// Start at least LANE_THICKNESS out to not hit sidewalk corners. Also account for
// the thickness of the crosswalk line itself. Center the lines inside these two
// boundaries.
let boundary = (LANE_THICKNESS + CROSSWALK_LINE_THICKNESS) * si::M;
let tile_every = 0.6 * LANE_THICKNESS * si::M;
let boundary = Distance::meters(LANE_THICKNESS + CROSSWALK_LINE_THICKNESS);
let tile_every = Distance::meters(0.6 * LANE_THICKNESS);
let line = {
// The middle line in the crosswalk geometry is the main crossing line.
let pts = turn.geom.points();
@ -128,8 +127,8 @@ impl DrawCrosswalk {
};
let mut draw = Vec::new();
let available_length = line.length() - (2.0 * boundary);
if available_length > 0.0 * si::M {
let available_length = line.length() - (boundary * 2.0);
if available_length > Distance::ZERO {
let num_markings = (available_length / tile_every).floor() as usize;
let mut dist_along =
boundary + (available_length - tile_every * (num_markings as f64)) / 2.0;

View File

@ -1,6 +1,6 @@
use ordered_float::NotNan;
use serde_derive::{Deserialize, Serialize};
use std::{fmt, ops};
use std::{f64, fmt, ops};
// In meters. Can be negative.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
@ -8,6 +8,7 @@ pub struct Distance(f64);
impl Distance {
pub const ZERO: Distance = Distance::const_meters(0.0);
pub const MAX: Distance = Distance::const_meters(f64::MAX);
pub fn meters(value: f64) -> Distance {
if !value.is_finite() {
@ -22,6 +23,10 @@ impl Distance {
Distance(value)
}
pub fn inches(value: f64) -> Distance {
Distance::meters(0.0254 * value)
}
pub fn abs(self) -> Distance {
if self.0 > 0.0 {
self
@ -107,10 +112,13 @@ impl ops::Div<f64> for Distance {
}
// In seconds. Can be negative.
// TODO Naming is awkward. Can represent a moment in time or a duration.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Duration(f64);
impl Duration {
pub const ZERO: Duration = Duration::const_seconds(0.0);
pub fn seconds(value: f64) -> Duration {
if !value.is_finite() {
panic!("Bad Duration {}", value);
@ -122,6 +130,19 @@ impl Duration {
pub const fn const_seconds(value: f64) -> Duration {
Duration(value)
}
pub fn min(self, other: Duration) -> Duration {
if self <= other {
self
} else {
other
}
}
// TODO Remove if possible.
pub fn inner_seconds(self) -> f64 {
self.0
}
}
impl fmt::Display for Duration {
@ -146,6 +167,14 @@ impl ops::Mul<f64> for Duration {
}
}
impl ops::Mul<Speed> for Duration {
type Output = Distance;
fn mul(self, other: Speed) -> Distance {
Distance::meters(self.0 * other.0)
}
}
impl ops::Div<Duration> for Duration {
type Output = f64;
@ -162,6 +191,8 @@ impl ops::Div<Duration> for Duration {
pub struct Speed(f64);
impl Speed {
pub const ZERO: Speed = Speed::const_meters_per_second(0.0);
pub fn meters_per_second(value: f64) -> Speed {
if !value.is_finite() {
panic!("Bad Speed {}", value);
@ -170,9 +201,72 @@ impl Speed {
Speed(value)
}
pub const fn const_meters_per_second(value: f64) -> Speed {
Speed(value)
}
pub fn miles_per_hour(value: f64) -> Speed {
Speed::meters_per_second(0.44704 * value)
}
// TODO Remove if possible.
pub fn inner_meters_per_second(self) -> f64 {
self.0
}
}
impl ops::Add for Speed {
type Output = Speed;
fn add(self, other: Speed) -> Speed {
Speed::meters_per_second(self.0 + other.0)
}
}
impl ops::Sub for Speed {
type Output = Speed;
fn sub(self, other: Speed) -> Speed {
Speed::meters_per_second(self.0 - other.0)
}
}
impl ops::Mul<f64> for Speed {
type Output = Speed;
fn mul(self, scalar: f64) -> Speed {
Speed::meters_per_second(self.0 * scalar)
}
}
impl ops::Mul<Duration> for Speed {
type Output = Distance;
fn mul(self, other: Duration) -> Distance {
Distance::meters(self.0 * other.0)
}
}
impl ops::Div<Duration> for Speed {
type Output = Acceleration;
fn div(self, other: Duration) -> Acceleration {
if other == Duration::ZERO {
panic!("Can't divide {} / {}", self, other);
}
Acceleration::meters_per_second_squared(self.0 / other.0)
}
}
impl ops::Div<Acceleration> for Speed {
type Output = Duration;
fn div(self, other: Acceleration) -> Duration {
if other == Acceleration::ZERO {
panic!("Can't divide {} / {}", self, other);
}
Duration::seconds(self.0 / other.0)
}
}
impl fmt::Display for Speed {
@ -186,6 +280,8 @@ impl fmt::Display for Speed {
pub struct Acceleration(f64);
impl Acceleration {
pub const ZERO: Acceleration = Acceleration::const_meters_per_second_squared(0.0);
pub fn meters_per_second_squared(value: f64) -> Acceleration {
if !value.is_finite() {
panic!("Bad Acceleration {}", value);
@ -193,6 +289,28 @@ impl Acceleration {
Acceleration(value)
}
pub const fn const_meters_per_second_squared(value: f64) -> Acceleration {
Acceleration(value)
}
// TODO Remove by making Acceleration itself Ord.
pub fn as_ordered(self) -> NotNan<f64> {
NotNan::new(self.0).unwrap()
}
pub fn min(self, other: Acceleration) -> Acceleration {
if self <= other {
self
} else {
other
}
}
// TODO Remove if possible.
pub fn inner_meters_per_second_squared(self) -> f64 {
self.0
}
}
impl fmt::Display for Acceleration {
@ -200,3 +318,11 @@ impl fmt::Display for Acceleration {
write!(f, "{}m/s^2", self.0)
}
}
impl ops::Mul<Duration> for Acceleration {
type Output = Speed;
fn mul(self, other: Duration) -> Speed {
Speed::meters_per_second(self.0 * other.0)
}
}

View File

@ -8,14 +8,12 @@ edition = "2018"
abstutil = { path = "../abstutil" }
backtrace = "0.3.9"
derivative = "1.0.0"
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
geom = { path = "../geom" }
lazy_static = "1.1.0"
log = "0.4.5"
map_model = { path = "../map_model" }
more-asserts = "0.2.1"
multimap = "0.4.0"
ordered-float = "1.0.1"
pretty_assertions = "0.5.1"
rand = { version = "0.6.4", features = ["serde1"] }
rand_xorshift = "0.1.1"

View File

@ -6,28 +6,22 @@ use crate::router::Router;
use crate::transit::TransitSimState;
use crate::view::{AgentView, WorldView};
use crate::{
Acceleration, AgentID, CarID, CarState, Distance, DrawCarInput, Event, ParkedCar, ParkingSpot,
Speed, Tick, Time, TripID, VehicleType,
AgentID, CarID, CarState, DrawCarInput, Event, ParkedCar, ParkingSpot, Tick, TripID,
VehicleType,
};
use abstutil;
use abstutil::{deserialize_btreemap, serialize_btreemap, Error};
use dimensioned::si;
use geom::EPSILON_DIST;
use geom::{Acceleration, Distance, Duration, Speed, EPSILON_DIST};
use map_model::{
BuildingID, IntersectionID, LaneID, Map, Path, PathStep, Position, Trace, Traversable, TurnID,
LANE_THICKNESS,
};
use multimap::MultiMap;
use ordered_float::NotNan;
use rand_xorshift::XorShiftRng;
use serde_derive::{Deserialize, Serialize};
use std;
use std::collections::{BTreeMap, HashSet};
const TIME_TO_PARK_OR_DEPART: Time = si::Second {
value_unsafe: 10.0,
_marker: std::marker::PhantomData,
};
const TIME_TO_PARK_OR_DEPART: Duration = Duration::const_seconds(10.0);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DrivingGoal {
@ -121,7 +115,7 @@ impl Car {
let mut current_on = self.on;
let mut current_dist_along = self.dist_along;
let mut current_router = orig_router.clone();
let mut dist_scanned_ahead = 0.0 * si::M;
let mut dist_scanned_ahead = Distance::ZERO;
if self.debug {
debug!(
@ -198,7 +192,7 @@ impl Car {
let dist_to_maybe_stop_at =
maybe_stop_early.unwrap_or_else(|| current_on.length(map));
let dist_from_stop = dist_to_maybe_stop_at - current_dist_along;
if dist_from_stop < 0.0 * si::M {
if dist_from_stop < Distance::ZERO {
return Err(Error::new(format!("Router for {} looking ahead to {:?} said to stop at {:?}, but lookahead already at {}", self.id, current_on, maybe_stop_early, current_dist_along)));
}
@ -245,20 +239,19 @@ impl Car {
// Advance to the next step.
let dist_this_step = current_on.length(map) - current_dist_along;
dist_to_lookahead -= dist_this_step;
if dist_to_lookahead <= 0.0 * si::M {
if dist_to_lookahead <= Distance::ZERO {
break;
}
current_on = current_router.finished_step(current_on).as_traversable();
current_dist_along = 0.0 * si::M;
current_dist_along = Distance::ZERO;
dist_scanned_ahead += dist_this_step;
}
// Clamp based on what we can actually do
// TODO this type mangling is awful
let safe_accel = vehicle.clamp_accel(
constraints
.into_iter()
.min_by_key(|a| NotNan::new(a.value_unsafe).unwrap())
.min_by_key(|a| a.as_ordered())
.unwrap(),
);
if self.debug {
@ -306,7 +299,7 @@ impl Car {
}
let leftover_dist = self.dist_along - self.on.length(map);
if leftover_dist < 0.0 * si::M {
if leftover_dist < Distance::ZERO {
break;
}
@ -384,7 +377,7 @@ impl SimQueue {
let mut cars_queue: Vec<(Distance, CarID)> =
ids.iter().map(|id| (cars[id].dist_along, *id)).collect();
// Sort descending.
cars_queue.sort_by_key(|(dist, _)| -NotNan::new(dist.value_unsafe).unwrap());
cars_queue.sort_by_key(|(dist, _)| -dist.as_ordered());
let capacity =
((id.length(map) / Vehicle::best_case_following_dist()).ceil() as usize).max(1);
@ -425,7 +418,7 @@ impl SimQueue {
adjusted_cars += 1;
let fixed_dist = dist2 - (kinematics::FOLLOWING_DISTANCE - dist_apart);
if fixed_dist < 0.0 * si::M {
if fixed_dist < Distance::ZERO {
return Err(Error::new(format!("can't hack around bug where cars are too close on {:?}, because last car doesn't have room to be shifted back", id)));
}
cars.get_mut(&c2).unwrap().dist_along = fixed_dist;
@ -691,7 +684,7 @@ impl DrivingSimState {
let mut cars_per_traversable = MultiMap::new();
for c in self.cars.values() {
// Also do some sanity checks.
if c.dist_along < 0.0 * si::M {
if c.dist_along < Distance::ZERO {
return Err(Error::new(format!(
"{} is {} along {:?}",
c.id, c.dist_along, c.on
@ -757,7 +750,7 @@ impl DrivingSimState {
other_vehicle.clamp_speed(map.get_parent(start_lane).get_speed_limit()),
&params.vehicle,
start_dist - other_dist - params.vehicle.length,
0.0 * si::MPS,
Speed::ZERO,
)
.unwrap();
if accel_for_other_to_stop <= other_vehicle.max_deaccel {
@ -778,7 +771,7 @@ impl DrivingSimState {
owner: params.owner,
on: start_on,
dist_along: start_dist,
speed: 0.0 * si::MPS,
speed: Speed::ZERO,
vehicle: params.vehicle,
debug: false,
parking: params.maybe_parked_car.and_then(|parked_car| {
@ -846,7 +839,7 @@ impl DrivingSimState {
prev_len - prev_dist
} else {
// TODO we need two steps back, urgh
0.0 * si::M
Distance::ZERO
},
c.vehicle.length,
)
@ -854,12 +847,11 @@ impl DrivingSimState {
} else {
// TODO Kinda weird to consider the car not present, but eventually cars spawning
// at borders should appear fully anyway.
c.on.slice(0.0 * si::M, c.dist_along, map)?.0
c.on.slice(Distance::ZERO, c.dist_along, map)?.0
};
let body = if let Some(ref parking) = c.parking {
let progress: f64 =
((time - parking.started_at).as_time() / TIME_TO_PARK_OR_DEPART).value_unsafe;
let progress: f64 = (time - parking.started_at).as_time() / TIME_TO_PARK_OR_DEPART;
assert!(progress >= 0.0 && progress <= 1.0);
let project_away_ratio = if parking.is_parking {
progress

View File

@ -1,7 +1,6 @@
use dimensioned::si;
use geom::Pt2D;
use geom::{Duration, Speed, Distance, Pt2D};
use map_model::{BuildingID, BusRouteID, BusStopID, LaneID, Map, TurnID};
use crate::{CarID, Distance, ParkingSpot, Time};
use crate::{CarID, ParkingSpot};
// This is experimental for now, but it might subsume the entire design of the sim crate.
@ -39,10 +38,10 @@ enum Action {
impl Action {
// These are always lower bounds, aka, the best case.
fn cost(&self, map: &Map) -> Time {
fn cost(&self, map: &Map) -> Duration {
// TODO driving speed limits and these could depend on preferences of the individual
// ped/vehicle
let ped_speed = 3.9 * si::MPS;
let ped_speed = Speed::meters_per_second(3.9);
match *self {
// TODO wait, we need to know if a ped or car is crossing something
@ -50,17 +49,17 @@ impl Action {
Action::CrossTurn(id) => {
map.get_t(id).length() / map.get_parent(id.dst).get_speed_limit()
}
Action::ParkingCar(_, _) => 20.0 * si::S,
Action::UnparkingCar(_, _) => 10.0 * si::S,
Action::ParkingCar(_, _) => Duration::seconds(20.0),
Action::UnparkingCar(_, _) => Duration::seconds(10.0),
Action::CrossLaneContraflow(id) => map.get_l(id).length() / ped_speed,
Action::CrossPathFromBuildingToSidewalk(id)
| Action::CrossPathFromSidewalkToBuilding(id) => {
map.get_b(id).front_path.line.length() / ped_speed
}
// TODO Could try lots of things here...
Action::WaitForBus(_, _) => 60.0 * si::S,
Action::WaitForBus(_, _) => Duration::seconds(60.0),
// TODO Cache the expected time to travel between stops
Action::RideBus(_stop1, _stop2) => 300.0 * si::S,
Action::RideBus(_stop1, _stop2) => Duration::seconds(300.0),
_ => panic!("TODO"),
}
@ -71,7 +70,7 @@ impl Action {
// TODO hard to convert distance and time
fn heuristic(&self, goal: Pt2D) -> Distance {
// TODO
0.0 * si::M
Distance::ZERO
}
fn next_steps(&self) -> Vec<Action> {

View File

@ -1,18 +1,14 @@
use crate::kinematics;
use crate::view::WorldView;
use crate::{AgentID, CarID, Event, PedestrianID, Tick, Time};
use crate::{AgentID, CarID, Event, PedestrianID, Tick};
use abstutil;
use abstutil::{deserialize_btreemap, serialize_btreemap, Error};
use dimensioned::si;
use geom::Duration;
use map_model::{ControlStopSign, IntersectionID, IntersectionType, Map, TurnID, TurnPriority};
use serde_derive::{Deserialize, Serialize};
use std;
use std::collections::{BTreeMap, BTreeSet, HashSet};
const WAIT_AT_STOP_SIGN: Time = si::Second {
value_unsafe: 1.5,
_marker: std::marker::PhantomData,
};
const WAIT_AT_STOP_SIGN: Duration = Duration::const_seconds(1.5);
// One agent may make several requests at one intersection at a time. This is normal for
// pedestrians and crosswalks. IntersectionPolicies should expect this.

View File

@ -1,48 +1,25 @@
use crate::{Acceleration, CarID, Distance, Speed, Time, TIMESTEP};
use crate::{CarID, TIMESTEP};
use abstutil::Error;
use dimensioned::si;
use geom::EPSILON_DIST;
use geom::{Acceleration, Distance, Duration, Speed, EPSILON_DIST};
use more_asserts::assert_ge;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use serde_derive::{Deserialize, Serialize};
use std;
pub const EPSILON_SPEED: Speed = si::MeterPerSecond {
value_unsafe: 0.000_000_01,
_marker: std::marker::PhantomData,
};
pub const EPSILON_SPEED: Speed = Speed::const_meters_per_second(0.000_000_01);
// http://pccsc.net/bicycle-parking-info/ says 68 inches, which is 1.73m
const MIN_BIKE_LENGTH: Distance = si::Meter {
value_unsafe: 1.7,
_marker: std::marker::PhantomData,
};
const MAX_BIKE_LENGTH: Distance = si::Meter {
value_unsafe: 2.0,
_marker: std::marker::PhantomData,
};
const MIN_BIKE_LENGTH: Distance = Distance::const_meters(1.7);
const MAX_BIKE_LENGTH: Distance = Distance::const_meters(2.0);
// These two must be < PARKING_SPOT_LENGTH
pub const MIN_CAR_LENGTH: Distance = si::Meter {
value_unsafe: 4.5,
_marker: std::marker::PhantomData,
};
const MAX_CAR_LENGTH: Distance = si::Meter {
value_unsafe: 6.5,
_marker: std::marker::PhantomData,
};
pub const MIN_CAR_LENGTH: Distance = Distance::const_meters(4.5);
const MAX_CAR_LENGTH: Distance = Distance::const_meters(6.5);
// Note this is more than MAX_CAR_LENGTH
const BUS_LENGTH: Distance = si::Meter {
value_unsafe: 12.5,
_marker: std::marker::PhantomData,
};
const BUS_LENGTH: Distance = Distance::const_meters(12.5);
// At all speeds (including at rest), cars must be at least this far apart, measured from front of
// one car to the back of the other.
pub const FOLLOWING_DISTANCE: Distance = si::Meter {
value_unsafe: 1.0,
_marker: std::marker::PhantomData,
};
pub const FOLLOWING_DISTANCE: Distance = Distance::const_meters(1.0);
// TODO unit test all of this
// TODO handle floating point issues uniformly here
@ -75,10 +52,12 @@ impl Vehicle {
id,
vehicle_type: VehicleType::Car,
debug: false,
max_accel: rng.gen_range(2.4, 2.8) * si::MPS2,
max_deaccel: rng.gen_range(-2.8, -2.4) * si::MPS2,
max_accel: Acceleration::meters_per_second_squared(rng.gen_range(2.4, 2.8)),
max_deaccel: Acceleration::meters_per_second_squared(rng.gen_range(-2.8, -2.4)),
// TODO more realistic to have a few preset lengths and choose between them
length: rng.gen_range(MIN_CAR_LENGTH.value_unsafe, MAX_CAR_LENGTH.value_unsafe) * si::M,
length: Distance::meters(
rng.gen_range(MIN_CAR_LENGTH.inner_meters(), MAX_CAR_LENGTH.inner_meters()),
),
max_speed: None,
}
}
@ -88,8 +67,8 @@ impl Vehicle {
id,
vehicle_type: VehicleType::Bus,
debug: false,
max_accel: rng.gen_range(2.4, 2.8) * si::MPS2,
max_deaccel: rng.gen_range(-2.8, -2.4) * si::MPS2,
max_accel: Acceleration::meters_per_second_squared(rng.gen_range(2.4, 2.8)),
max_deaccel: Acceleration::meters_per_second_squared(rng.gen_range(-2.8, -2.4)),
length: BUS_LENGTH,
max_speed: None,
}
@ -101,14 +80,16 @@ impl Vehicle {
vehicle_type: VehicleType::Bike,
debug: false,
// http://eprints.uwe.ac.uk/20767/ says mean 0.231
max_accel: rng.gen_range(0.2, 0.3) * si::MPS2,
max_accel: Acceleration::meters_per_second_squared(rng.gen_range(0.2, 0.3)),
// Much easier deaccel. Partly to avoid accel_to_stop_in_dist bugs with bikes running
// stop signs.
max_deaccel: rng.gen_range(-1.3, -1.2) * si::MPS2,
length: rng.gen_range(MIN_BIKE_LENGTH.value_unsafe, MAX_BIKE_LENGTH.value_unsafe)
* si::M,
max_deaccel: Acceleration::meters_per_second_squared(rng.gen_range(-1.3, -1.2)),
length: Distance::meters(rng.gen_range(
MIN_BIKE_LENGTH.inner_meters(),
MAX_BIKE_LENGTH.inner_meters(),
)),
// 7 to 10 mph
max_speed: Some(rng.gen_range(3.13, 4.47) * si::MPS),
max_speed: Some(Speed::meters_per_second(rng.gen_range(3.13, 4.47))),
}
}
@ -142,7 +123,7 @@ impl Vehicle {
pub fn stopping_distance(&self, speed: Speed) -> Result<Distance, Error> {
// 0 = v_0 + a*t
let stopping_time = -1.0 * speed / self.max_deaccel;
let stopping_time = speed / self.max_deaccel * -1.0;
dist_at_constant_accel(self.max_deaccel, stopping_time, speed)
}
@ -161,7 +142,7 @@ impl Vehicle {
speed: Speed,
dist: Distance,
) -> Result<Acceleration, Error> {
if dist < -EPSILON_DIST {
if dist < EPSILON_DIST * -1.0 {
return Err(Error::new(format!(
"{} called accel_to_stop_in_dist({}, {}) with negative distance",
self.id, speed, dist
@ -170,9 +151,9 @@ impl Vehicle {
// Don't NaN out. Don't check for <= EPSILON_DIST here -- it makes cars slightly overshoot
// sometimes.
if dist <= 0.0 * si::M {
if dist <= Distance::ZERO {
// TODO assert speed is 0ish?
return Ok(0.0 * si::MPS2);
return Ok(Acceleration::ZERO);
}
// d = (v_1)(t) + (1/2)(a)(t^2)
@ -180,12 +161,16 @@ impl Vehicle {
// Eliminating time yields the formula for accel below. This same accel should be applied
// for t = -v_1 / a, which is possible even if that's not a multiple of TIMESTEP since
// we're decelerating to rest.
let normal_case: Acceleration = (-1.0 * speed * speed) / (2.0 * dist);
let required_time: Time = -1.0 * speed / normal_case;
let normal_case = Acceleration::meters_per_second_squared(
(-1.0 * speed.inner_meters_per_second() * speed.inner_meters_per_second())
/ (2.0 * dist.inner_meters()),
);
// TODO might validlyish be NaN, so just f64 here
let required_time: f64 = (speed / normal_case * -1.0).inner_seconds();
if self.debug {
debug!(
" accel_to_stop_in_dist({}, {}) would normally recommend {} and take {} to finish",
" accel_to_stop_in_dist({}, {}) would normally recommend {} and take {}s to finish",
speed, dist, normal_case, required_time
);
}
@ -194,7 +179,7 @@ impl Vehicle {
// absurd amount of time to finish, with tiny little steps. But need to tune and understand
// this value better. Higher initial speeds or slower max_deaccel's mean this is naturally
// going to take longer. We don't want to start stopping now if we can't undo it next tick.
if !required_time.value_unsafe.is_nan() && required_time < 15.0 * si::S {
if !required_time.is_nan() && Duration::seconds(required_time) < Duration::seconds(15.0) {
return Ok(normal_case);
}
@ -203,7 +188,9 @@ impl Vehicle {
// acceleration is then too high, we'll cap off and trigger a normal case next tick.
// Want (1/2)(a)(dt^2) + (a dt)dt - (1/2)(a)(dt^2) = dist
// TODO I don't understand the above.
Ok(dist / (TIMESTEP * TIMESTEP))
Ok(Acceleration::meters_per_second_squared(
dist.inner_meters() / (TIMESTEP.inner_seconds() * TIMESTEP.inner_seconds()),
))
}
// Assume we accelerate as much as possible this tick (restricted only by the speed limit),
@ -231,10 +218,9 @@ impl Vehicle {
)));
}
Ok(min_accel(
self.max_accel,
self.accel_to_achieve_speed_in_one_tick(current_speed, speed_limit),
))
Ok(self
.max_accel
.min(self.accel_to_achieve_speed_in_one_tick(current_speed, speed_limit)))
}
fn max_next_dist(&self, current_speed: Speed, speed_limit: Speed) -> Result<Distance, Error> {
@ -274,10 +260,10 @@ impl Vehicle {
fn dist_at_constant_accel(
accel: Acceleration,
time: Time,
time: Duration,
initial_speed: Speed,
) -> Result<Distance, Error> {
if time < 0.0 * si::S {
if time < Duration::ZERO {
return Err(Error::new(format!(
"dist_at_constant_accel called with time = {}",
time
@ -285,14 +271,18 @@ fn dist_at_constant_accel(
}
// Don't deaccelerate into going backwards, just cap things off.
let actual_time = if accel >= 0.0 * si::MPS2 {
let actual_time = if accel >= Acceleration::ZERO {
time
} else {
// 0 = v_0 + a*t
min_time(time, -1.0 * initial_speed / accel)
time.min(initial_speed / accel * -1.0)
};
let dist = (initial_speed * actual_time) + (0.5 * accel * (actual_time * actual_time));
if dist < 0.0 * si::M {
let dist = (initial_speed * actual_time)
+ Distance::meters(
0.5 * accel.inner_meters_per_second_squared()
* (actual_time.inner_seconds() * actual_time.inner_seconds()),
);
if dist < Distance::ZERO {
return Err(Error::new(format!(
"dist_at_constant_accel yielded result = {}",
dist
@ -301,42 +291,35 @@ fn dist_at_constant_accel(
Ok(dist)
}
fn min_time(t1: Time, t2: Time) -> Time {
if t1 < t2 {
return t1;
}
t2
}
fn min_accel(a1: Acceleration, a2: Acceleration) -> Acceleration {
if a1 < a2 {
return a1;
}
a2
}
pub fn results_of_accel_for_one_tick(
initial_speed: Speed,
accel: Acceleration,
) -> (Distance, Speed) {
// Don't deaccelerate into going backwards, just cap things off.
let actual_time = if accel >= 0.0 * si::MPS2 {
let actual_time = if accel >= Acceleration::ZERO {
TIMESTEP
} else {
min_time(TIMESTEP, -1.0 * initial_speed / accel)
TIMESTEP.min(initial_speed / accel * -1.0)
};
let dist = (initial_speed * actual_time) + (0.5 * accel * (actual_time * actual_time));
assert_ge!(dist, 0.0 * si::M);
let dist = (initial_speed * actual_time)
+ Distance::meters(
0.5 * accel.inner_meters_per_second_squared()
* (actual_time.inner_seconds() * actual_time.inner_seconds()),
);
assert_ge!(dist, Distance::ZERO);
let mut new_speed = initial_speed + (accel * actual_time);
// Handle some floating point imprecision
if new_speed < 0.0 * si::MPS && new_speed >= -1.0 * EPSILON_SPEED {
new_speed = 0.0 * si::MPS;
if new_speed < Speed::ZERO && new_speed >= EPSILON_SPEED * -1.0 {
new_speed = Speed::ZERO;
}
assert_ge!(new_speed, 0.0 * si::MPS);
assert_ge!(new_speed, Speed::ZERO);
(dist, new_speed)
}
fn accel_to_cover_dist_in_one_tick(dist: Distance, speed: Speed) -> Acceleration {
// d = (v_i)(t) + (1/2)(a)(t^2), solved for a
2.0 * (dist - (speed * TIMESTEP)) / (TIMESTEP * TIMESTEP)
Acceleration::meters_per_second_squared(
2.0 * (dist - (speed * TIMESTEP)).inner_meters()
/ (TIMESTEP.inner_seconds() * TIMESTEP.inner_seconds()),
)
}

View File

@ -30,7 +30,7 @@ pub use crate::make::{
load, ABTest, ABTestResults, BorderSpawnOverTime, OriginDestination, Scenario, SeedParkedCars,
SimFlags, SpawnOverTime,
};
pub use crate::physics::{Acceleration, Distance, Speed, Tick, Time, TIMESTEP};
pub use crate::physics::{Tick, TIMESTEP};
pub use crate::query::{Benchmark, ScoreSummary, SimStats, Summary};
pub use crate::render::{CarState, DrawCarInput, DrawPedestrianInput, GetDrawAgents};
pub use crate::sim::Sim;

View File

@ -1,6 +1,6 @@
use crate::kinematics::Vehicle;
use crate::{CarID, CarState, Distance, DrawCarInput, ParkedCar, ParkingSpot, VehicleType};
use geom::{Angle, Pt2D};
use crate::{CarID, CarState, DrawCarInput, ParkedCar, ParkingSpot, VehicleType};
use geom::{Angle, Distance, Pt2D};
use map_model;
use map_model::{BuildingID, Lane, LaneID, LaneType, Map, Position, Traversable};
use serde_derive::{Deserialize, Serialize};

View File

@ -1,20 +1,11 @@
use dimensioned::si;
use geom::Duration;
use lazy_static::lazy_static;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use regex::Regex;
use serde_derive::{Deserialize, Serialize};
pub const TIMESTEP: Time = si::Second {
value_unsafe: 0.1,
_marker: std::marker::PhantomData,
};
// TODO Don't just alias types; assert that time, dist, and speed are always positive
pub type Time = si::Second<f64>;
pub type Distance = si::Meter<f64>;
pub type Speed = si::MeterPerSecond<f64>;
pub type Acceleration = si::MeterPerSecond2<f64>;
pub const TIMESTEP: Duration = Duration::const_seconds(0.1);
// Represents a moment in time, not a duration/delta
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
@ -29,8 +20,8 @@ impl Tick {
Tick(0)
}
pub fn from_minutes(secs: u32) -> Tick {
Tick(60 * 10 * secs)
pub fn from_minutes(mins: u32) -> Tick {
Tick(60 * 10 * mins)
}
pub fn from_seconds(secs: u32) -> Tick {
@ -88,8 +79,9 @@ impl Tick {
Some(Tick(hours + minutes + seconds + ms))
}
pub fn as_time(self) -> Time {
f64::from(self.0) * TIMESTEP
// TODO as_duration?
pub fn as_time(self) -> Duration {
TIMESTEP * f64::from(self.0)
}
pub fn next(self) -> Tick {
@ -135,11 +127,11 @@ impl Tick {
}
}
impl std::ops::Add<Time> for Tick {
impl std::ops::Add<Duration> for Tick {
type Output = Tick;
fn add(self, other: Time) -> Tick {
let ticks = other.value_unsafe / TIMESTEP.value_unsafe;
fn add(self, other: Duration) -> Tick {
let ticks = other / TIMESTEP;
// TODO check that there's no remainder!
Tick(self.0 + (ticks as u32))
}

View File

@ -1,7 +1,6 @@
// Code to inspect the simulation state.
use crate::{Sim, Tick, TripID};
use dimensioned::si;
use geom::Pt2D;
use map_model::LaneID;
use serde_derive::{Deserialize, Serialize};
@ -44,11 +43,11 @@ impl Sim {
}
pub fn measure_speed(&self, b: &mut Benchmark) -> f64 {
let dt = abstutil::elapsed_seconds(b.last_real_time) * si::S;
let dt = geom::Duration::seconds(abstutil::elapsed_seconds(b.last_real_time));
let speed = (self.time - b.last_sim_time).as_time() / dt;
b.last_real_time = Instant::now();
b.last_sim_time = self.time;
speed.value_unsafe
speed
}
}

View File

@ -4,9 +4,8 @@ use crate::kinematics::Vehicle;
use crate::parking::ParkingSimState;
use crate::transit::TransitSimState;
use crate::view::AgentView;
use crate::{Distance, Event, ParkingSpot, Tick};
use dimensioned::si;
use geom::EPSILON_DIST;
use crate::{Event, ParkingSpot, Tick};
use geom::{Acceleration, Distance, EPSILON_DIST};
use map_model::{
BuildingID, LaneID, LaneType, Map, Path, PathStep, Position, Trace, Traversable, TurnID,
};
@ -115,7 +114,7 @@ impl Router {
self.path = p;
}
if should_idle {
return Some(Action::Continue(0.0 * si::MPS2, Vec::new()));
return Some(Action::Continue(Acceleration::ZERO, Vec::new()));
}
}
// Don't stop at the border node; plow through

View File

@ -8,12 +8,11 @@ use crate::transit::TransitSimState;
use crate::trips::TripManager;
use crate::view::WorldView;
use crate::walking::WalkingSimState;
use crate::{
AgentID, CarID, Distance, Event, ParkedCar, PedestrianID, SimStats, Tick, TripID, TIMESTEP,
};
use crate::{AgentID, CarID, Event, ParkedCar, PedestrianID, SimStats, Tick, TripID, TIMESTEP};
use abstutil;
use abstutil::Error;
use derivative::Derivative;
use geom::Distance;
use map_model::{BuildingID, IntersectionID, LaneID, LaneType, Map, Path, Trace, Turn};
use rand::{FromEntropy, SeedableRng};
use rand_xorshift::XorShiftRng;

View File

@ -10,7 +10,7 @@ use crate::{
AgentID, CarID, Event, ParkedCar, ParkingSpot, PedestrianID, Tick, TripID, VehicleType,
};
use abstutil::{fork_rng, WeightedUsizeChoice};
use dimensioned::si;
use geom::Distance;
use map_model::{
BuildingID, BusRoute, BusRouteID, BusStopID, LaneID, LaneType, Map, Path, PathRequest,
Pathfinder, Position, RoadID,
@ -114,7 +114,7 @@ impl Command {
DrivingGoal::Border(_, l) => *l,
};
PathRequest {
start: Position::new(*start, 0.0 * si::M),
start: Position::new(*start, Distance::ZERO),
end: Position::new(goal_lane, map.get_l(goal_lane).length()),
can_use_bus_lanes: vehicle.vehicle_type == VehicleType::Bus,
can_use_bike_lanes: vehicle.vehicle_type == VehicleType::Bike,
@ -222,7 +222,7 @@ impl Spawner {
owner: None,
maybe_parked_car: None,
vehicle: vehicle.clone(),
start: Position::new(start, 0.0 * si::M),
start: Position::new(start, Distance::ZERO),
router: match goal {
DrivingGoal::ParkNear(b) => {
if vehicle.vehicle_type == VehicleType::Bike {

View File

@ -4,9 +4,9 @@ use crate::spawn::Spawner;
use crate::trips::TripManager;
use crate::view::AgentView;
use crate::walking::WalkingSimState;
use crate::{CarID, Distance, PedestrianID, Tick};
use crate::{CarID, PedestrianID, Tick};
use abstutil::{deserialize_btreemap, serialize_btreemap};
use dimensioned::si;
use geom::{Distance, Duration};
use map_model::{BusRoute, BusRouteID, BusStop, LaneID, Map, Path, PathRequest, Pathfinder};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
@ -143,7 +143,7 @@ impl TransitSimState {
if stop.driving_pos.dist_along() == view.dist_along {
// TODO constant for stop time
self.buses.get_mut(&car).unwrap().state =
BusState::AtStop(stop_idx, time + 10.0 * si::S);
BusState::AtStop(stop_idx, time + Duration::seconds(10.0));
events.push(Event::BusArrivedAtStop(car, stop.id));
capture_backtrace("BusArrivedAtStop");
if view.debug {

View File

@ -1,6 +1,7 @@
use crate::driving::SimQueue;
use crate::kinematics::Vehicle;
use crate::{AgentID, CarID, Distance, Speed};
use crate::{AgentID, CarID};
use geom::{Distance, Speed};
use map_model::Traversable;
use std::collections::{BTreeMap, HashMap};

View File

@ -4,14 +4,12 @@ use crate::parking::ParkingSimState;
use crate::trips::TripManager;
use crate::view::{AgentView, WorldView};
use crate::{
AgentID, Distance, DrawPedestrianInput, Event, ParkingSpot, PedestrianID, Speed, Tick, Time,
TripID, TIMESTEP,
AgentID, DrawPedestrianInput, Event, ParkingSpot, PedestrianID, Tick, TripID, TIMESTEP,
};
use abstutil;
use abstutil::{deserialize_multimap, serialize_multimap, Error};
use derivative::Derivative;
use dimensioned::si;
use geom::{Line, Pt2D};
use geom::{Distance, Duration, Line, Pt2D, Speed};
use map_model::{
BuildingID, BusStopID, IntersectionID, LaneID, LaneType, Map, Path, PathStep, Position, Trace,
Traversable, TurnID,
@ -24,15 +22,9 @@ use std::collections::{BTreeMap, HashSet};
// TODO tune these!
// TODO make it vary, after we can easily serialize these
// TODO temporarily very high to debug peds faster
const SPEED: Speed = si::MeterPerSecond {
value_unsafe: 3.9,
_marker: std::marker::PhantomData,
};
const SPEED: Speed = Speed::const_meters_per_second(3.9);
const TIME_TO_PREPARE_BIKE: Time = si::Second {
value_unsafe: 15.0,
_marker: std::marker::PhantomData,
};
const TIME_TO_PREPARE_BIKE: Duration = Duration::const_seconds(15.0);
// A pedestrian can start from a parking spot (after driving and parking) or at a building.
// A pedestrian can end at a parking spot (to start driving) or at a building.
@ -86,7 +78,7 @@ impl SidewalkSpot {
None
} else {
Some(SidewalkSpot {
sidewalk_pos: Position::new(lanes[0], 0.0 * si::M),
sidewalk_pos: Position::new(lanes[0], Distance::ZERO),
connection: SidewalkPOI::Border(i),
})
}
@ -186,7 +178,7 @@ impl Pedestrian {
} else {
goal_dist - self.dist_along
};
if dist_away <= 2.0 * SPEED * TIMESTEP {
if dist_away <= SPEED * TIMESTEP * 2.0 {
return match self.goal.connection {
SidewalkPOI::ParkingSpot(spot) => Action::StartParkedCar(spot),
SidewalkPOI::Building(id) => Action::StartCrossingPath(id),
@ -201,7 +193,7 @@ impl Pedestrian {
{
let contraflow = self.path.current_step().is_contraflow();
if (!contraflow && self.dist_along < self.on.length(map))
|| (contraflow && self.dist_along > 0.0 * si::M)
|| (contraflow && self.dist_along > Distance::ZERO)
{
return Action::Continue;
}
@ -217,7 +209,12 @@ impl Pedestrian {
}
// If true, then we're completely done!
fn step_cross_path(&mut self, events: &mut Vec<Event>, delta_time: Time, map: &Map) -> bool {
fn step_cross_path(
&mut self,
events: &mut Vec<Event>,
delta_time: Duration,
map: &Map,
) -> bool {
let new_dist = delta_time * SPEED;
// TODO arguably a different direction would make this easier
@ -227,7 +224,7 @@ impl Pedestrian {
fp.dist_along >= map.get_b(fp.bldg).front_path.line.length()
} else {
fp.dist_along -= new_dist;
if fp.dist_along < 0.0 * si::M {
if fp.dist_along < Distance::ZERO {
events.push(Event::PedReachedBuilding(self.id, fp.bldg));
capture_backtrace("PedReachedBuilding");
return true;
@ -243,13 +240,13 @@ impl Pedestrian {
false
}
fn step_continue(&mut self, delta_time: Time, map: &Map) {
fn step_continue(&mut self, delta_time: Duration, map: &Map) {
let new_dist = delta_time * SPEED;
if self.path.current_step().is_contraflow() {
self.dist_along -= new_dist;
if self.dist_along < 0.0 * si::M {
self.dist_along = 0.0 * si::M;
if self.dist_along < Distance::ZERO {
self.dist_along = Distance::ZERO;
}
} else {
self.dist_along += new_dist;
@ -280,7 +277,7 @@ impl Pedestrian {
match self.path.current_step() {
PathStep::Lane(id) => {
self.on = Traversable::Lane(id);
self.dist_along = 0.0 * si::M;
self.dist_along = Distance::ZERO;
}
PathStep::ContraflowLane(id) => {
self.on = Traversable::Lane(id);
@ -288,7 +285,7 @@ impl Pedestrian {
}
PathStep::Turn(t) => {
self.on = Traversable::Turn(t);
self.dist_along = 0.0 * si::M;
self.dist_along = Distance::ZERO;
intersections.on_enter(Request::for_ped(self.id, t))?;
}
}
@ -315,8 +312,7 @@ impl Pedestrian {
);
let line = Line::new(sidewalk_pos.pt(map), street_pos.pt(map));
let progress: f64 =
((now - bp.started_at).as_time() / TIME_TO_PREPARE_BIKE).value_unsafe;
let progress: f64 = (now - bp.started_at).as_time() / TIME_TO_PREPARE_BIKE;
assert!(progress >= 0.0 && progress <= 1.0);
let ratio = if bp.is_parking {
1.0 - progress
@ -324,7 +320,7 @@ impl Pedestrian {
progress
};
line.dist_along(ratio * line.length())
line.dist_along(line.length() * ratio)
} else {
self.on.dist_along(self.dist_along, map).0
}
@ -393,7 +389,7 @@ impl WalkingSimState {
pub fn step(
&mut self,
events: &mut Vec<Event>,
delta_time: Time,
delta_time: Duration,
now: Tick,
map: &Map,
intersections: &mut IntersectionSimState,
@ -581,7 +577,7 @@ impl WalkingSimState {
let front_path = match params.start.connection {
SidewalkPOI::Building(id) => Some(CrossingFrontPath {
bldg: id,
dist_along: 0.0 * si::M,
dist_along: Distance::ZERO,
going_to_sidewalk: true,
}),
_ => None,
@ -627,7 +623,7 @@ impl WalkingSimState {
debug: false,
on: p.on,
dist_along: p.dist_along,
speed: if p.moving { SPEED } else { 0.0 * si::MPS },
speed: if p.moving { SPEED } else { Speed::ZERO },
vehicle: None,
},
);