use crate::app::App;
use crate::colors::ColorScheme;
use crate::helpers::ID;
use crate::options::TrafficSignalStyle;
use crate::render::{
draw_signal_stage, DrawOptions, Renderable, CROSSWALK_LINE_THICKNESS, OUTLINE_THICKNESS,
};
use geom::{Angle, ArrowCap, Distance, Line, PolyLine, Polygon, Pt2D, Ring, Time, EPSILON_DIST};
use map_model::{
Direction, DrivingSide, Intersection, IntersectionID, IntersectionType, Map, Road,
RoadWithStopSign, Turn, TurnType, SIDEWALK_THICKNESS,
};
use std::cell::RefCell;
use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, RewriteColor, Text};
pub struct DrawIntersection {
pub id: IntersectionID,
zorder: isize,
draw_default: RefCell<Option<Drawable>>,
pub draw_traffic_signal: RefCell<Option<(Time, Drawable)>>,
}
impl DrawIntersection {
pub fn new(i: &Intersection, map: &Map) -> DrawIntersection {
DrawIntersection {
id: i.id,
zorder: i.get_zorder(map),
draw_default: RefCell::new(None),
draw_traffic_signal: RefCell::new(None),
}
}
pub fn clear_rendering(&mut self) {
*self.draw_default.borrow_mut() = None;
*self.draw_traffic_signal.borrow_mut() = None;
}
fn render(&self, g: &mut GfxCtx, app: &App) -> Drawable {
let map = &app.primary.map;
let i = map.get_i(self.id);
let mut default_geom = GeomBatch::new();
default_geom.push(app.cs.normal_intersection, i.polygon.clone());
default_geom.extend(app.cs.sidewalk, calculate_corners(i, map));
for turn in map.get_turns_in_intersection(i.id) {
if turn.turn_type == TurnType::Crosswalk
&& !turn.other_crosswalk_ids.iter().any(|id| *id < turn.id)
{
make_crosswalk(&mut default_geom, turn, map, &app.cs);
}
}
if i.is_private(map) {
default_geom.push(app.cs.private_road.alpha(0.5), i.polygon.clone());
}
match i.intersection_type {
IntersectionType::Border => {
let r = map.get_r(*i.roads.iter().next().unwrap());
default_geom.extend(app.cs.road_center_line, calculate_border_arrows(i, r, map));
}
IntersectionType::StopSign => {
for ss in map.get_stop_sign(i.id).roads.values() {
if ss.must_stop {
if let Some((octagon, pole)) = DrawIntersection::stop_sign_geom(ss, map) {
default_geom.push(app.cs.stop_sign, octagon);
default_geom.push(app.cs.stop_sign_pole, pole);
}
}
}
}
IntersectionType::Construction => {
default_geom.append(
GeomBatch::load_svg(g.prerender, "system/assets/map/under_construction.svg")
.scale(0.08)
.centered_on(i.polygon.center()),
);
}
IntersectionType::TrafficSignal => {}
}
let zorder = i.get_zorder(map);
if zorder < 0 {
default_geom = default_geom.color(RewriteColor::ChangeAlpha(0.5));
}
g.upload(default_geom)
}
pub fn stop_sign_geom(ss: &RoadWithStopSign, map: &Map) -> Option<(Polygon, Polygon)> {
let trim_back = Distance::meters(0.1);
let edge_lane = map.get_l(ss.lane_closest_to_edge);
if edge_lane.length() - trim_back <= EPSILON_DIST {
return None;
}
let last_line = edge_lane
.lane_center_pts
.exact_slice(Distance::ZERO, edge_lane.length() - trim_back)
.last_line();
let last_line = if map.get_config().driving_side == DrivingSide::Right {
last_line.shift_right(edge_lane.width)
} else {
last_line.shift_left(edge_lane.width)
};
let octagon = make_octagon(last_line.pt2(), Distance::meters(1.0), last_line.angle());
let pole = Line::must_new(
last_line
.pt2()
.project_away(Distance::meters(1.5), last_line.angle().opposite()),
last_line
.pt2()
.project_away(Distance::meters(0.9), last_line.angle().opposite()),
)
.make_polygons(Distance::meters(0.3));
Some((octagon, pole))
}
}
impl Renderable for DrawIntersection {
fn get_id(&self) -> ID {
ID::Intersection(self.id)
}
fn draw(&self, g: &mut GfxCtx, app: &App, opts: &DrawOptions) {
let mut draw = self.draw_default.borrow_mut();
if draw.is_none() {
*draw = Some(self.render(g, app));
}
g.redraw(draw.as_ref().unwrap());
if let Some(signal) = app.primary.map.maybe_get_traffic_signal(self.id) {
if !opts.suppress_traffic_signal_details.contains(&self.id) {
let mut maybe_redraw = self.draw_traffic_signal.borrow_mut();
let recalc = maybe_redraw
.as_ref()
.map(|(t, _)| *t != app.primary.sim.time())
.unwrap_or(true);
if recalc {
let (idx, remaining) =
app.primary.sim.current_stage_and_remaining_time(self.id);
let mut batch = GeomBatch::new();
draw_signal_stage(
g.prerender,
&signal.stages[idx],
self.id,
Some(remaining),
&mut batch,
app,
app.opts.traffic_signal_style.clone(),
);
if app.opts.traffic_signal_style != TrafficSignalStyle::BAP {
batch.append(
Text::from(Line(format!("{}", idx + 1)))
.render_to_batch(g.prerender)
.scale(0.1)
.centered_on(app.primary.map.get_i(self.id).polygon.center()),
);
}
*maybe_redraw = Some((app.primary.sim.time(), g.prerender.upload(batch)));
}
let (_, batch) = maybe_redraw.as_ref().unwrap();
g.redraw(batch);
}
}
}
fn get_outline(&self, map: &Map) -> Polygon {
let poly = &map.get_i(self.id).polygon;
poly.to_outline(OUTLINE_THICKNESS)
.unwrap_or_else(|_| poly.clone())
}
fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool {
map.get_i(self.id).polygon.contains_pt(pt)
}
fn get_zorder(&self) -> isize {
self.zorder
}
}
pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
let mut corners = Vec::new();
for turn in map.get_turns_in_intersection(i.id) {
if turn.turn_type == TurnType::SharedSidewalkCorner {
if map.get_l(turn.id.src).dst_i != i.id {
continue;
}
let width = map
.get_l(turn.id.src)
.width
.min(map.get_l(turn.id.dst).width);
if i.roads.len() == 1 {
corners.push(turn.geom.make_polygons(width));
continue;
}
let l1 = map.get_l(turn.id.src);
let l2 = map.get_l(turn.id.dst);
if let Some(poly) = (|| {
let mut pts = turn.geom.shift_left(width / 2.0).ok()?.into_points();
pts.push(l2.first_line().shift_left(width / 2.0).pt1());
pts.push(l2.first_line().shift_right(width / 2.0).pt1());
pts.extend(
turn.geom
.shift_right(width / 2.0)
.ok()?
.reversed()
.into_points(),
);
pts.push(l1.last_line().shift_right(width / 2.0).pt2());
pts.push(l1.last_line().shift_left(width / 2.0).pt2());
pts.push(pts[0]);
Some(Polygon::buggy_new(pts))
})() {
corners.push(poly);
}
}
}
corners
}
fn calculate_border_arrows(i: &Intersection, r: &Road, map: &Map) -> Vec<Polygon> {
let mut result = Vec::new();
let mut width_fwd = Distance::ZERO;
let mut width_back = Distance::ZERO;
for (l, dir, _) in r.lanes_ltr() {
if dir == Direction::Fwd {
width_fwd += map.get_l(l).width;
} else {
width_back += map.get_l(l).width;
}
}
let center = r.get_dir_change_pl(map);
if !i.outgoing_lanes.is_empty() {
let (line, width) = if r.dst_i == i.id {
(
center.last_line().shift_left(width_back / 2.0).reverse(),
width_back,
)
} else {
(center.first_line().shift_right(width_fwd / 2.0), width_fwd)
};
result.push(
PolyLine::must_new(vec![
line.unbounded_dist_along(Distance::meters(-9.5)),
line.unbounded_dist_along(Distance::meters(-0.5)),
])
.make_arrow(width / 3.0, ArrowCap::Triangle),
);
}
if !i.incoming_lanes.is_empty() {
let (line, width) = if r.dst_i == i.id {
(
center.last_line().shift_right(width_fwd / 2.0).reverse(),
width_fwd,
)
} else {
(center.first_line().shift_left(width_back / 2.0), width_back)
};
result.push(
PolyLine::must_new(vec![
line.unbounded_dist_along(Distance::meters(-0.5)),
line.unbounded_dist_along(Distance::meters(-9.5)),
])
.make_arrow(width / 3.0, ArrowCap::Triangle),
);
}
result
}
fn make_octagon(center: Pt2D, radius: Distance, facing: Angle) -> Polygon {
Ring::must_new(
(0..=8)
.map(|i| center.project_away(radius, facing.rotate_degs(22.5 + f64::from(i * 360 / 8))))
.collect(),
)
.to_polygon()
}
pub fn make_crosswalk(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &ColorScheme) {
if make_rainbow_crosswalk(batch, turn, map) {
return;
}
let width = SIDEWALK_THICKNESS;
let boundary = width;
let tile_every = width * 0.6;
let line = {
let pts = turn.geom.points();
if pts.len() < 3 {
println!(
"Not rendering crosswalk for {}; its geometry was squished earlier",
turn.id
);
return;
}
match Line::new(pts[1], pts[2]) {
Some(l) => l,
None => {
return;
}
}
};
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;
let err = format!("make_crosswalk for {} broke", turn.id);
for _ in 0..=num_markings {
let pt1 = line.dist_along(dist_along).expect(&err);
let pt2 = pt1.project_away(Distance::meters(1.0), turn.angle());
batch.push(
cs.general_road_marking,
perp_line(Line::must_new(pt1, pt2), width).make_polygons(CROSSWALK_LINE_THICKNESS),
);
let pt3 = line
.dist_along(dist_along + 2.0 * CROSSWALK_LINE_THICKNESS)
.expect(&err);
let pt4 = pt3.project_away(Distance::meters(1.0), turn.angle());
batch.push(
cs.general_road_marking,
perp_line(Line::must_new(pt3, pt4), width).make_polygons(CROSSWALK_LINE_THICKNESS),
);
dist_along += tile_every;
}
}
}
fn make_rainbow_crosswalk(batch: &mut GeomBatch, turn: &Turn, map: &Map) -> bool {
let node = map.get_i(turn.id.parent).orig_id.0;
let way = map.get_parent(turn.id.src).orig_id.osm_way_id.0;
match (node, way) {
(53073255, 428246441) |
(53073255, 332601014) |
(53073254, 6447455) |
(53073254, 607690679) |
(53168934, 6456052) |
(53200834, 6456052) |
(53068795, 607691081) |
(53068795, 65588105) |
(53068794, 65588105) => {}
_ => { return false; }
}
let total_width = map.get_l(turn.id.src).width;
let colors = vec![
Color::WHITE,
Color::RED,
Color::ORANGE,
Color::YELLOW,
Color::GREEN,
Color::BLUE,
Color::hex("#8B00FF"),
Color::WHITE,
];
let band_width = total_width / (colors.len() as f64);
let slice = turn
.geom
.exact_slice(total_width, turn.geom.length() - total_width)
.must_shift_left(total_width / 2.0 - band_width / 2.0);
for (idx, color) in colors.into_iter().enumerate() {
batch.push(
color,
slice
.must_shift_right(band_width * (idx as f64))
.make_polygons(band_width),
);
}
true
}
fn perp_line(l: Line, length: Distance) -> Line {
let pt1 = l.shift_right(length / 2.0).pt1();
let pt2 = l.shift_left(length / 2.0).pt1();
Line::must_new(pt1, pt2)
}