1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::cell::RefCell;

use geom::{Circle, Distance, PolyLine, Pt2D};

use crate::{Color, Drawable, GeomBatch, GfxCtx};

/// Draw `Circles` and `PolyLines` in map-space that scale their size as the canvas is zoomed. The
/// goal is to appear with roughly constant screen-space size, but for the moment, this is
/// approximated by discretizing into 10 buckets. The scaling only happens when the canvas is
/// zoomed out less than a value of 1.0.
pub struct DrawUnzoomedShapes {
    lines: Vec<UnzoomedLine>,
    circles: Vec<UnzoomedCircle>,
    per_zoom: RefCell<[Option<Drawable>; 11]>,
}

struct UnzoomedLine {
    polyline: PolyLine,
    width: Distance,
    color: Color,
}

struct UnzoomedCircle {
    center: Pt2D,
    radius: Distance,
    color: Color,
}

pub struct DrawUnzoomedShapesBuilder {
    lines: Vec<UnzoomedLine>,
    circles: Vec<UnzoomedCircle>,
}

impl DrawUnzoomedShapes {
    pub fn empty() -> Self {
        Self {
            lines: Vec::new(),
            circles: Vec::new(),
            per_zoom: Default::default(),
        }
    }

    pub fn builder() -> DrawUnzoomedShapesBuilder {
        DrawUnzoomedShapesBuilder {
            lines: Vec::new(),
            circles: Vec::new(),
        }
    }

    pub fn draw(&self, g: &mut GfxCtx) {
        let (zoom, idx) = discretize_zoom(g.canvas.cam_zoom);
        let value = &mut self.per_zoom.borrow_mut()[idx];
        if value.is_none() {
            // Never shrink past the original size -- always at least 1.0.
            // zoom ranges between [0.0, 1.0], and we want thicker shapes as zoom approaches 0.
            let max = 5.0;
            // So thickness ranges between [1.0, 5.0]
            let thickness = 1.0 + (max - 1.0) * (1.0 - zoom);

            let mut batch = GeomBatch::new();
            render_lines(&mut batch, &self.lines, thickness);
            render_circles(&mut batch, &self.circles, thickness);
            *value = Some(g.upload(batch));
        }
        g.redraw(value.as_ref().unwrap());
    }
}

impl DrawUnzoomedShapesBuilder {
    pub fn add_line(&mut self, polyline: PolyLine, width: Distance, color: Color) {
        self.lines.push(UnzoomedLine {
            polyline,
            width,
            color,
        });
    }

    pub fn add_circle(&mut self, center: Pt2D, radius: Distance, color: Color) {
        self.circles.push(UnzoomedCircle {
            center,
            radius,
            color,
        });
    }

    // TODO We might take EventCtx here to upload something to the GPU.
    pub fn build(self) -> DrawUnzoomedShapes {
        DrawUnzoomedShapes {
            lines: self.lines,
            circles: self.circles,
            per_zoom: Default::default(),
        }
    }
}

// Continuously changing road width as we zoom looks great, but it's terribly slow. We'd have to
// move line thickening into the shader to do it better. So recalculate with less granularity.
//
// Returns ([0.0, 1.0], [0, 10])
fn discretize_zoom(zoom: f64) -> (f64, usize) {
    if zoom >= 1.0 {
        return (1.0, 10);
    }
    let rounded = (zoom * 10.0).round();
    let idx = rounded as usize;
    (rounded / 10.0, idx)
}

fn render_lines(batch: &mut GeomBatch, lines: &[UnzoomedLine], thickness: f64) {
    for line in lines {
        batch.push(
            line.color,
            line.polyline.make_polygons(thickness * line.width),
        );
    }
}

fn render_circles(batch: &mut GeomBatch, circles: &[UnzoomedCircle], thickness: f64) {
    // TODO Here especially if we're drawing lots of circles with the same radius, generating the
    // shape once and translating it is much more efficient. UnzoomedAgents does this.
    for circle in circles {
        batch.push(
            circle.color,
            Circle::new(circle.center, thickness * circle.radius).to_polygon(),
        );
    }
}