-
-
Notifications
You must be signed in to change notification settings - Fork 357
Make the route sketcher work nicely at all zoom levels #784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
To start, just make the bike network use this.
… to see the route on large maps. Note the draggable waypoint circles are still a fixed size; you can't easily manipulate the route when unzoomed far.
…ute sketcher nicer to use at all zoom levels!
} | ||
} | ||
|
||
fn mouseover_i(&self, ctx: &EventCtx) -> Option<IntersectionID> { | ||
let pt = ctx.canvas.get_cursor_in_map_space()?; | ||
// When zoomed really far out, it's harder to click small intersections, so snap more | ||
// aggressively. | ||
// aggressively. Note this should always be a larger hitbox than how the waypoint circles |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still want to convert this code to use World
instead of handling dragging directly, but not urgent.
|
||
// Arbitrarily pick a color when two different types of roads meet | ||
intersections.insert(r.src_i, color); | ||
intersections.insert(r.dst_i, color); | ||
} | ||
|
||
let mut batch = GeomBatch::new(); | ||
for (i, color) in intersections { | ||
// No clear way to thicken the intersection at different zoom levels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯 agree!
/// specify the behavior when barely unzoomed or zoomed in -- the shape starts being drawn in | ||
/// map-space "normally" without a constant screen-space size. | ||
pub struct DrawUnzoomedShapes { | ||
lines: Vec<UnzoomedLine>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, this could be just a lightweight Drawable
variation that just points to stuff uploaded to the GPU
}); | ||
} | ||
|
||
// TODO We might take EventCtx here to upload something to the GPU. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's where we would upload vertex data with the points, widths, and colors to the GPU, in some TBD format.
widgetry/src/mapspace/unzoomed.rs
Outdated
if value.is_none() { | ||
// Thicker shapes as we zoom out. Scale up to 5x. Never shrink past the original size. | ||
let mut thickness = (0.5 / zoom).max(1.0); | ||
// And on gigantic maps, zoom may approach 0, so avoid NaNs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused trying to reconcile these comments with the code.
Do you mean that the min should be 1.0 and the max should be 5.0?
(see also clamp
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also confused. :P Here's the table of values:
0.13822713522160424: rounded 0.1, idx 1. thickness 5
0.15030187423656013: rounded 0.2, idx 2. thickness 2.5
0.25051174719224095: rounded 0.3, idx 3. thickness 1.6666666666666667
0.35019705644415605: rounded 0.4, idx 4. thickness 1.25
0.4540073629671436: rounded 0.5, idx 5. thickness 1
0.5504481570314438: rounded 0.6, idx 6. thickness 1
0.6526359457120697: rounded 0.7, idx 7. thickness 1
0.7956998190130887: rounded 0.8, idx 8. thickness 1
0.9865105800157065: rounded 1, idx 10. thickness 1
The first is a raw canvas zoom. We round to 1 decimal place. But I think 10 buckets and a max thickness of 5 causes half the buckets to look the same. I'll play around with this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why the first formula was so complicated. I rewrote it to just take linear step sizes as you zoom in/out. Now the thickness is actually different for each zoom level, and the effect looks good.
0.04989743996161477: rounded 0, idx 0. thickness 5
0.10543709075265414: rounded 0.1, idx 1. thickness 4.6
0.15114338288839774: rounded 0.2, idx 2. thickness 4.2
0.2526185331120044: rounded 0.3, idx 3. thickness 3.8
0.356112087542117: rounded 0.4, idx 4. thickness 3.4
0.45654925750906206: rounded 0.5, idx 5. thickness 3
0.600205398261662: rounded 0.6, idx 6. thickness 2.6
0.6843547482453398: rounded 0.7, idx 7. thickness 2.2
0.846099434807287: rounded 0.8, idx 8. thickness 1.7999999999999998
0.8508365622424429: rounded 0.9, idx 9. thickness 1.4
1.005977672452568: rounded 1, idx 10. thickness 1
Why 10 buckets and a max scale of 5? Not actually sure, I think I previously just tuned this to look decent. It still looks OK for all the use cases so far. Can adjust and maybe make configurable per use, though I don't know if that's necessary.
/// ... But not yet. As an approximation of that, just discretize zoom into 10 buckets. Also, | ||
/// specify the behavior when barely unzoomed or zoomed in -- the shape starts being drawn in | ||
/// map-space "normally" without a constant screen-space size. | ||
pub struct DrawUnzoomedShapes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "unzoomed" shapes, I am assuming the coordinates of the geometries stored in DrawUnzoomedShapes
are with respect to zoom == 1.0
, and then we scale them up/down based on how far the current zoom is from 1.0.
Is that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, in reading the implementation, I guess it's just the thickness that gets scaled - not the coords, which are WRT the map space axes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rewrote to hopefully be more clear. We're not just storing the full geometry and scaling it; we could do that already with the current shader. We're just scaling circle radius or line thickness.
Thanks for the reviews! |
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much simpler!
Sorry for all the churn with drafts. This mostly supercedes #782. I think the code is reasonably future-proof and it fixes the usability problems with the route sketcher at least, so I think it's worth merging.
The problem is sketching a route to modify is hard to do when zoomed very far out: https://user-images.githubusercontent.com/1664407/137545910-a1ec508a-5341-4628-8151-8051d0dfe5b4.gif
The solution scales the lines and circles with the zoom. Ideally they'd always be exactly the same size on the screen, but for the moment, we discretize:

How the code works
The new
DrawUnzoomedShapes
has a builder API for drawing colored polylines and circles with a base width or radius. The implementation right now just holds onto the base shape and lazily scales it for different zoom levels, invisible to the caller. In the future, we can make this more efficient by writing a dedicated shader to draw thick lines or circles in the GPU.That should be purely an internal widgetry change when it happens. I've at least sketched out the steps to do it:
glow
programs -- not hardDrawable
struct and variation of actually_upload to plumb the proper data to the GPUMaking the shapes interactive
The tough next step is to make this work for the "your trip" page. Currently that uses a
World
to handle dragging waypoints and hovering on/clicking alternate routes. I thinkWorld
will need to understand these unzoomed shapes that change size.At least calculating mouseover collisions seems straightforward. We can use FindClosest for the thick lines. This finds the closest line segment, projects the cursor onto that line, and can tell us the distance to the line. We can look at the current zoom level and calculate if the line is thick enough to count as a hit. Similar for circles, figuring out if a circle with some changing radius contains a point is simple math.
Some of the things I've yet to think through are:
World
dynamically generate hovered polygons (which we should do anyway even for pure map-space stuff to save on memory and upfront calculation)