2019-11-29 09:41:08 +03:00
|
|
|
use crate::assets::Assets;
|
2019-12-08 23:46:56 +03:00
|
|
|
use crate::svg;
|
2019-10-09 21:21:04 +03:00
|
|
|
use crate::{
|
2020-02-07 04:29:26 +03:00
|
|
|
Canvas, Color, EventCtx, HorizontalAlignment, ScreenDims, ScreenPt, ScreenRectangle, Text,
|
|
|
|
VerticalAlignment,
|
2019-10-09 21:21:04 +03:00
|
|
|
};
|
2020-01-02 19:24:12 +03:00
|
|
|
use geom::{Angle, Bounds, Circle, Distance, Line, Polygon, Pt2D};
|
2019-09-11 20:46:03 +03:00
|
|
|
use glium::uniforms::{SamplerBehavior, SamplerWrapFunction, UniformValue};
|
2019-09-11 00:08:05 +03:00
|
|
|
use glium::Surface;
|
2019-09-11 19:03:59 +03:00
|
|
|
use std::cell::Cell;
|
|
|
|
|
2020-02-07 22:20:11 +03:00
|
|
|
// Lower is more on top
|
|
|
|
const MAPSPACE_Z: f32 = 1.0;
|
|
|
|
const SCREENSPACE_Z: f32 = 0.5;
|
|
|
|
const TOOLTIP_Z: f32 = 0.0;
|
2019-01-25 03:09:29 +03:00
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
struct Uniforms<'a> {
|
2019-09-11 00:08:05 +03:00
|
|
|
// (cam_x, cam_y, cam_zoom)
|
|
|
|
transform: [f32; 3],
|
2019-09-11 21:06:57 +03:00
|
|
|
// (window_width, window_height, 0.0 for mapspace or 1.0 for screenspace)
|
2019-09-11 00:08:05 +03:00
|
|
|
window: [f32; 3],
|
2019-09-11 01:21:58 +03:00
|
|
|
canvas: &'a Canvas,
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
impl<'a> Uniforms<'a> {
|
|
|
|
fn new(canvas: &'a Canvas) -> Uniforms<'a> {
|
2019-09-11 00:08:05 +03:00
|
|
|
Uniforms {
|
|
|
|
transform: [
|
|
|
|
canvas.cam_x as f32,
|
|
|
|
canvas.cam_y as f32,
|
|
|
|
canvas.cam_zoom as f32,
|
|
|
|
],
|
|
|
|
window: [
|
|
|
|
canvas.window_width as f32,
|
|
|
|
canvas.window_height as f32,
|
2020-02-07 22:20:11 +03:00
|
|
|
MAPSPACE_Z,
|
2019-09-11 00:08:05 +03:00
|
|
|
],
|
2019-09-11 01:21:58 +03:00
|
|
|
canvas,
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
impl<'b> glium::uniforms::Uniforms for Uniforms<'b> {
|
2019-09-11 00:08:05 +03:00
|
|
|
fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut output: F) {
|
|
|
|
output("transform", UniformValue::Vec3(self.transform));
|
|
|
|
output("window", UniformValue::Vec3(self.window));
|
2019-09-11 20:46:03 +03:00
|
|
|
|
2019-10-24 01:46:00 +03:00
|
|
|
// This is fine to use for all of the texture styles; all but non-tiling textures clamp to
|
|
|
|
// [0, 1] anyway.
|
2019-09-11 20:46:03 +03:00
|
|
|
let tile = SamplerBehavior {
|
|
|
|
wrap_function: (
|
|
|
|
SamplerWrapFunction::Repeat,
|
|
|
|
SamplerWrapFunction::Repeat,
|
|
|
|
SamplerWrapFunction::Repeat,
|
|
|
|
),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2019-11-24 18:21:30 +03:00
|
|
|
for (idx, tex) in self.canvas.texture_arrays.iter().enumerate() {
|
2019-09-11 20:46:03 +03:00
|
|
|
output(
|
|
|
|
&format!("tex{}", idx),
|
2019-11-24 18:21:30 +03:00
|
|
|
UniformValue::Texture2dArray(tex, Some(tile)),
|
2019-09-11 20:46:03 +03:00
|
|
|
);
|
2019-09-11 01:44:07 +03:00
|
|
|
}
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:09:29 +03:00
|
|
|
pub struct GfxCtx<'a> {
|
2019-05-05 00:40:03 +03:00
|
|
|
pub(crate) target: &'a mut glium::Frame,
|
2019-01-25 03:09:29 +03:00
|
|
|
program: &'a glium::Program,
|
2019-09-11 01:21:58 +03:00
|
|
|
uniforms: Uniforms<'a>,
|
2020-01-14 05:26:24 +03:00
|
|
|
pub(crate) params: glium::DrawParameters<'a>,
|
2019-01-25 03:19:02 +03:00
|
|
|
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode: bool,
|
|
|
|
pub(crate) naming_hint: Option<String>,
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
// TODO Don't be pub. Delegate everything.
|
|
|
|
pub canvas: &'a Canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
pub prerender: &'a Prerender<'a>,
|
2019-02-01 03:43:09 +03:00
|
|
|
|
2019-01-25 03:19:02 +03:00
|
|
|
pub num_draw_calls: usize,
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> GfxCtx<'a> {
|
2019-02-06 01:43:46 +03:00
|
|
|
pub(crate) fn new(
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas: &'a Canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
prerender: &'a Prerender<'a>,
|
2019-01-25 03:09:29 +03:00
|
|
|
target: &'a mut glium::Frame,
|
|
|
|
program: &'a glium::Program,
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode: bool,
|
2019-01-25 03:09:29 +03:00
|
|
|
) -> GfxCtx<'a> {
|
|
|
|
let params = glium::DrawParameters {
|
|
|
|
blend: glium::Blend::alpha_blending(),
|
2019-08-15 02:19:54 +03:00
|
|
|
depth: glium::Depth {
|
|
|
|
test: glium::DepthTest::IfLessOrEqual,
|
|
|
|
write: true,
|
|
|
|
..Default::default()
|
|
|
|
},
|
2019-01-25 03:09:29 +03:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2019-09-11 00:08:05 +03:00
|
|
|
let uniforms = Uniforms::new(&canvas);
|
2019-01-25 03:09:29 +03:00
|
|
|
|
|
|
|
GfxCtx {
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
prerender,
|
2019-01-25 03:09:29 +03:00
|
|
|
target,
|
|
|
|
program,
|
|
|
|
uniforms,
|
|
|
|
params,
|
2019-01-25 03:19:02 +03:00
|
|
|
num_draw_calls: 0,
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode,
|
|
|
|
naming_hint: None,
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Up to the caller to call unfork()!
|
|
|
|
// TODO Canvas doesn't understand this change, so things like text drawing that use
|
|
|
|
// map_to_screen will just be confusing.
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn fork(&mut self, top_left_map: Pt2D, top_left_screen: ScreenPt, zoom: f64) {
|
2019-01-25 03:09:29 +03:00
|
|
|
// map_to_screen of top_left_map should be top_left_screen
|
|
|
|
let cam_x = (top_left_map.x() * zoom) - top_left_screen.x;
|
|
|
|
let cam_y = (top_left_map.y() * zoom) - top_left_screen.y;
|
|
|
|
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms.transform = [cam_x as f32, cam_y as f32, zoom as f32];
|
|
|
|
self.uniforms.window = [
|
|
|
|
self.canvas.window_width as f32,
|
|
|
|
self.canvas.window_height as f32,
|
2020-02-07 22:20:11 +03:00
|
|
|
SCREENSPACE_Z,
|
2019-09-11 00:08:05 +03:00
|
|
|
];
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn fork_screenspace(&mut self) {
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms.transform = [0.0, 0.0, 1.0];
|
|
|
|
self.uniforms.window = [
|
|
|
|
self.canvas.window_width as f32,
|
|
|
|
self.canvas.window_height as f32,
|
2020-02-07 22:20:11 +03:00
|
|
|
SCREENSPACE_Z,
|
2019-09-11 00:08:05 +03:00
|
|
|
];
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn unfork(&mut self) {
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms = Uniforms::new(&self.canvas);
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(&mut self, color: Color) {
|
2019-09-11 02:41:35 +03:00
|
|
|
match color {
|
|
|
|
Color::RGBA(r, g, b, a) => {
|
|
|
|
// Without this, SRGB gets enabled and post-processes the color from the fragment
|
|
|
|
// shader.
|
|
|
|
self.target.clear_color_srgb_and_depth((r, g, b, a), 1.0);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_line(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-01-25 19:01:08 +03:00
|
|
|
self.draw_polygon(color, &line.make_polygons(thickness));
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_rounded_line(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-04-12 03:04:59 +03:00
|
|
|
self.draw_polygons(
|
|
|
|
color,
|
|
|
|
&vec![
|
|
|
|
line.make_polygons(thickness),
|
|
|
|
Circle::new(line.pt1(), thickness / 2.0).to_polygon(),
|
|
|
|
Circle::new(line.pt2(), thickness / 2.0).to_polygon(),
|
|
|
|
],
|
|
|
|
);
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_arrow(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-05-29 03:58:47 +03:00
|
|
|
self.draw_polygon(color, &line.to_polyline().make_arrow(thickness).unwrap());
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:25:06 +03:00
|
|
|
pub fn draw_circle(&mut self, color: Color, circle: &Circle) {
|
2019-02-02 23:54:16 +03:00
|
|
|
self.draw_polygon(color, &circle.to_polygon());
|
2019-01-25 03:25:06 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:09:29 +03:00
|
|
|
pub fn draw_polygon(&mut self, color: Color, poly: &Polygon) {
|
2019-02-12 01:01:17 +03:00
|
|
|
let obj = self.prerender.upload_temporary(vec![(color, poly)]);
|
2019-01-25 21:59:39 +03:00
|
|
|
self.redraw(&obj);
|
2019-01-25 03:25:06 +03:00
|
|
|
}
|
|
|
|
|
2019-04-12 03:04:59 +03:00
|
|
|
pub fn draw_polygons(&mut self, color: Color, polygons: &Vec<Polygon>) {
|
2019-05-17 22:49:50 +03:00
|
|
|
let obj = self
|
|
|
|
.prerender
|
|
|
|
.upload_temporary(polygons.iter().map(|p| (color, p)).collect());
|
2019-01-25 21:24:29 +03:00
|
|
|
self.redraw(&obj);
|
2019-01-25 20:09:55 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 21:24:29 +03:00
|
|
|
pub fn redraw(&mut self, obj: &Drawable) {
|
2019-01-25 20:27:53 +03:00
|
|
|
self.target
|
|
|
|
.draw(
|
|
|
|
&obj.vertex_buffer,
|
|
|
|
&obj.index_buffer,
|
|
|
|
&self.program,
|
|
|
|
&self.uniforms,
|
|
|
|
&self.params,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
self.num_draw_calls += 1;
|
2019-05-17 03:10:22 +03:00
|
|
|
|
|
|
|
// println!("{:?}", backtrace::Backtrace::new());
|
2019-01-25 20:27:53 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 10:08:17 +03:00
|
|
|
pub fn redraw_at(&mut self, top_left: ScreenPt, obj: &Drawable) {
|
|
|
|
self.fork(Pt2D::new(0.0, 0.0), top_left, 1.0);
|
|
|
|
self.redraw(obj);
|
|
|
|
self.unfork();
|
|
|
|
}
|
|
|
|
|
2020-01-14 03:57:09 +03:00
|
|
|
// TODO Stateful API :(
|
|
|
|
pub fn enable_clipping(&mut self, rect: ScreenRectangle) {
|
|
|
|
assert!(self.params.scissor.is_none());
|
2020-01-16 21:38:01 +03:00
|
|
|
// The scissor rectangle has to be in device coordinates, so you would think some transform
|
|
|
|
// by self.canvas.hidpi_factor has to happen here. But actually, window dimensions and the
|
|
|
|
// rectangle passed in are already scaled up. So don't do anything here!
|
2020-01-14 03:57:09 +03:00
|
|
|
self.params.scissor = Some(glium::Rect {
|
2020-01-16 21:38:01 +03:00
|
|
|
left: rect.x1 as u32,
|
2019-11-24 18:21:21 +03:00
|
|
|
// Y-inversion
|
2020-01-16 21:38:01 +03:00
|
|
|
bottom: (self.canvas.window_height - rect.y2) as u32,
|
|
|
|
width: (rect.x2 - rect.x1) as u32,
|
|
|
|
height: (rect.y2 - rect.y1) as u32,
|
2019-11-24 18:21:21 +03:00
|
|
|
});
|
2020-01-14 03:57:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn disable_clipping(&mut self) {
|
|
|
|
assert!(self.params.scissor.is_some());
|
|
|
|
self.params.scissor = None;
|
2019-11-24 18:21:21 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 10:32:38 +03:00
|
|
|
// Canvas stuff.
|
|
|
|
|
|
|
|
// The text box covers up what's beneath and eats the cursor (for get_cursor_in_map_space).
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn draw_blocking_text(
|
|
|
|
&mut self,
|
2020-02-07 08:39:23 +03:00
|
|
|
txt: Text,
|
2019-02-01 03:43:09 +03:00
|
|
|
(horiz, vert): (HorizontalAlignment, VerticalAlignment),
|
|
|
|
) {
|
2020-02-07 09:04:16 +03:00
|
|
|
let mut dims = txt.clone().dims(&self.prerender.assets);
|
2019-12-19 21:59:39 +03:00
|
|
|
let top_left = self.canvas.align_window(dims, horiz, vert);
|
2020-02-07 04:13:42 +03:00
|
|
|
// TODO This doesn't take effect anymore
|
2019-12-19 21:59:39 +03:00
|
|
|
if let HorizontalAlignment::FillScreen = horiz {
|
|
|
|
dims.width = self.canvas.window_width;
|
|
|
|
}
|
2020-02-07 04:13:42 +03:00
|
|
|
|
|
|
|
self.draw_blocking_text_at_screenspace_topleft(txt, top_left);
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2020-02-07 08:39:23 +03:00
|
|
|
pub(crate) fn draw_blocking_text_at_screenspace_topleft(&mut self, txt: Text, pt: ScreenPt) {
|
2020-02-07 04:13:42 +03:00
|
|
|
let mut batch = GeomBatch::new();
|
2020-02-07 09:04:16 +03:00
|
|
|
batch.add_translated(txt.render(&self.prerender.assets), pt.x, pt.y);
|
2020-01-18 00:58:33 +03:00
|
|
|
self.canvas
|
2020-02-07 09:04:16 +03:00
|
|
|
.mark_covered_area(ScreenRectangle::top_left(pt, batch.get_dims()));
|
2020-02-07 04:13:42 +03:00
|
|
|
self.fork_screenspace();
|
|
|
|
batch.draw(self);
|
|
|
|
self.unfork();
|
2020-01-18 00:58:33 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn get_screen_bounds(&self) -> Bounds {
|
|
|
|
self.canvas.get_screen_bounds()
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
|
|
|
// TODO Rename these draw_nonblocking_text_*
|
2020-02-07 08:39:23 +03:00
|
|
|
pub fn draw_text_at(&mut self, txt: Text, map_pt: Pt2D) {
|
2020-02-07 09:04:16 +03:00
|
|
|
let txt_batch = txt.render(&self.prerender.assets);
|
|
|
|
let dims = txt_batch.get_dims();
|
2019-02-01 10:32:38 +03:00
|
|
|
let pt = self.canvas.map_to_screen(map_pt);
|
2020-02-07 04:13:42 +03:00
|
|
|
let mut batch = GeomBatch::new();
|
2020-02-07 09:04:16 +03:00
|
|
|
batch.add_translated(
|
|
|
|
txt_batch,
|
|
|
|
pt.x - (dims.width / 2.0),
|
|
|
|
pt.y - (dims.height / 2.0),
|
2019-04-23 06:23:50 +03:00
|
|
|
);
|
2020-02-07 04:13:42 +03:00
|
|
|
self.fork_screenspace();
|
|
|
|
batch.draw(self);
|
|
|
|
self.unfork();
|
2019-04-23 06:23:50 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 08:39:23 +03:00
|
|
|
pub fn draw_mouse_tooltip(&mut self, txt: Text) {
|
2020-02-07 09:04:16 +03:00
|
|
|
let txt_batch = txt.render(&self.prerender.assets);
|
|
|
|
let dims = txt_batch.get_dims();
|
2019-12-12 03:08:58 +03:00
|
|
|
// TODO Maybe also consider the cursor as a valid center
|
2019-11-24 18:21:30 +03:00
|
|
|
let pt = dims.top_left_for_corner(
|
2019-10-09 21:21:04 +03:00
|
|
|
ScreenPt::new(self.canvas.cursor_x, self.canvas.cursor_y),
|
|
|
|
&self.canvas,
|
|
|
|
);
|
2020-02-07 04:13:42 +03:00
|
|
|
let mut batch = GeomBatch::new();
|
2020-02-07 09:04:16 +03:00
|
|
|
batch.add_translated(txt_batch, pt.x, pt.y);
|
2020-02-07 22:20:11 +03:00
|
|
|
|
|
|
|
// fork_screenspace, but with an even more prominent Z
|
|
|
|
self.uniforms.transform = [0.0, 0.0, 1.0];
|
|
|
|
self.uniforms.window = [
|
|
|
|
self.canvas.window_width as f32,
|
|
|
|
self.canvas.window_height as f32,
|
|
|
|
TOOLTIP_Z,
|
|
|
|
];
|
|
|
|
// Temporarily disable clipping if needed.
|
|
|
|
let clip = self.params.scissor.take();
|
2020-02-07 04:13:42 +03:00
|
|
|
batch.draw(self);
|
|
|
|
self.unfork();
|
2020-02-07 22:20:11 +03:00
|
|
|
self.params.scissor = clip;
|
2019-02-01 03:43:09 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn screen_to_map(&self, pt: ScreenPt) -> Pt2D {
|
|
|
|
self.canvas.screen_to_map(pt)
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn get_cursor_in_map_space(&self) -> Option<Pt2D> {
|
|
|
|
self.canvas.get_cursor_in_map_space()
|
2019-01-25 21:59:39 +03:00
|
|
|
}
|
2019-02-06 01:43:46 +03:00
|
|
|
|
|
|
|
pub fn get_num_uploads(&self) -> usize {
|
|
|
|
self.prerender.num_uploads.get()
|
|
|
|
}
|
2019-04-24 01:33:52 +03:00
|
|
|
|
|
|
|
pub fn is_screencap(&self) -> bool {
|
|
|
|
self.screencap_mode
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_screencap_naming_hint(&mut self, hint: String) {
|
|
|
|
assert!(self.screencap_mode);
|
|
|
|
assert!(self.naming_hint.is_none());
|
|
|
|
self.naming_hint = Some(hint);
|
|
|
|
}
|
2019-05-03 22:49:27 +03:00
|
|
|
|
2019-08-15 00:32:02 +03:00
|
|
|
pub fn upload(&mut self, batch: GeomBatch) -> Drawable {
|
|
|
|
self.prerender.upload(batch)
|
|
|
|
}
|
2019-11-01 21:45:02 +03:00
|
|
|
|
|
|
|
pub fn button_tooltip(&self) -> Option<Text> {
|
|
|
|
self.canvas.button_tooltip.clone()
|
|
|
|
}
|
2019-11-29 09:41:08 +03:00
|
|
|
|
|
|
|
// Delegation to assets
|
|
|
|
pub fn default_line_height(&self) -> f64 {
|
2020-02-07 08:39:23 +03:00
|
|
|
self.prerender.assets.default_line_height
|
2019-11-29 09:41:08 +03:00
|
|
|
}
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
2019-05-17 03:10:22 +03:00
|
|
|
|
2019-10-26 21:37:10 +03:00
|
|
|
#[derive(Clone)]
|
2019-05-17 03:10:22 +03:00
|
|
|
pub struct GeomBatch {
|
2019-09-11 19:03:59 +03:00
|
|
|
list: Vec<(Color, Polygon)>,
|
2019-05-17 03:10:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GeomBatch {
|
|
|
|
pub fn new() -> GeomBatch {
|
|
|
|
GeomBatch { list: Vec::new() }
|
|
|
|
}
|
|
|
|
|
2019-09-12 20:25:34 +03:00
|
|
|
pub fn from(list: Vec<(Color, Polygon)>) -> GeomBatch {
|
|
|
|
GeomBatch { list }
|
|
|
|
}
|
|
|
|
|
2019-05-17 03:10:22 +03:00
|
|
|
pub fn push(&mut self, color: Color, p: Polygon) {
|
|
|
|
self.list.push((color, p));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn extend(&mut self, color: Color, polys: Vec<Polygon>) {
|
|
|
|
for p in polys {
|
|
|
|
self.list.push((color, p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-26 21:37:10 +03:00
|
|
|
pub fn append(&mut self, other: GeomBatch) {
|
|
|
|
self.list.extend(other.list);
|
2019-05-22 22:30:58 +03:00
|
|
|
}
|
|
|
|
|
2019-10-14 04:44:19 +03:00
|
|
|
pub fn consume(self) -> Vec<(Color, Polygon)> {
|
|
|
|
self.list
|
|
|
|
}
|
|
|
|
|
2019-05-17 03:10:22 +03:00
|
|
|
pub fn draw(self, g: &mut GfxCtx) {
|
|
|
|
let refs = self.list.iter().map(|(color, p)| (*color, p)).collect();
|
2019-05-17 22:49:50 +03:00
|
|
|
let obj = g.prerender.upload_temporary(refs);
|
|
|
|
g.redraw(&obj);
|
2019-05-17 03:10:22 +03:00
|
|
|
}
|
2019-10-12 01:00:08 +03:00
|
|
|
|
2019-11-24 18:21:21 +03:00
|
|
|
pub fn upload(self, ctx: &EventCtx) -> Drawable {
|
|
|
|
ctx.prerender.upload(self)
|
|
|
|
}
|
|
|
|
|
2020-02-07 06:18:45 +03:00
|
|
|
// Sets the top-left to 0, 0. Not sure exactly when this should be used.
|
2020-02-08 01:01:23 +03:00
|
|
|
pub(crate) fn autocrop(mut self) -> GeomBatch {
|
2020-02-07 03:41:52 +03:00
|
|
|
let mut bounds = Bounds::new();
|
|
|
|
for (_, poly) in &self.list {
|
|
|
|
bounds.union(poly.get_bounds());
|
|
|
|
}
|
2020-02-07 06:18:45 +03:00
|
|
|
if bounds.min_x == 0.0 && bounds.min_y == 0.0 {
|
2020-02-07 03:41:52 +03:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
for (_, poly) in &mut self.list {
|
2020-02-07 06:18:45 +03:00
|
|
|
*poly = poly.translate(-bounds.min_x, -bounds.min_y);
|
2020-02-07 03:41:52 +03:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-02-07 03:22:26 +03:00
|
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
|
|
self.list.is_empty()
|
|
|
|
}
|
|
|
|
|
2020-02-07 23:19:43 +03:00
|
|
|
pub fn get_dims(&self) -> ScreenDims {
|
2020-02-07 03:22:26 +03:00
|
|
|
if self.is_empty() {
|
|
|
|
panic!("Can't get_dims of empty GeomBatch");
|
|
|
|
}
|
2019-10-14 04:44:19 +03:00
|
|
|
let mut bounds = Bounds::new();
|
|
|
|
for (_, poly) in &self.list {
|
|
|
|
bounds.union(poly.get_bounds());
|
|
|
|
}
|
2020-01-14 01:57:46 +03:00
|
|
|
ScreenDims::new(bounds.width(), bounds.height())
|
2019-10-12 01:00:08 +03:00
|
|
|
}
|
2019-12-08 23:46:56 +03:00
|
|
|
|
|
|
|
// Slightly weird use case, but hotswap colors.
|
2019-12-09 00:44:43 +03:00
|
|
|
pub fn rewrite_color(&mut self, transformation: RewriteColor) {
|
2019-12-08 23:46:56 +03:00
|
|
|
for (c, _) in self.list.iter_mut() {
|
2019-12-09 00:44:43 +03:00
|
|
|
match transformation {
|
2020-01-29 23:16:18 +03:00
|
|
|
RewriteColor::NoOp => {}
|
2019-12-09 00:44:43 +03:00
|
|
|
RewriteColor::Change(from, to) => {
|
|
|
|
if *c == from {
|
|
|
|
*c = to;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
RewriteColor::ChangeAll(to) => {
|
|
|
|
*c = to;
|
|
|
|
}
|
2019-12-08 23:46:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Weird API...
|
2020-01-02 19:24:12 +03:00
|
|
|
pub fn add_svg(&mut self, filename: &str, center: Pt2D, scale: f64, rotate: Angle) {
|
2019-12-08 23:46:56 +03:00
|
|
|
let mut batch = GeomBatch::new();
|
|
|
|
svg::add_svg(&mut batch, filename);
|
2020-02-07 06:18:45 +03:00
|
|
|
self.add_transformed(batch, center, scale, rotate);
|
|
|
|
}
|
|
|
|
|
2020-02-07 09:04:16 +03:00
|
|
|
// This centers on the pt!
|
2020-02-07 06:18:45 +03:00
|
|
|
pub fn add_transformed(&mut self, other: GeomBatch, center: Pt2D, scale: f64, rotate: Angle) {
|
|
|
|
let dims = other.get_dims();
|
2020-01-01 22:47:55 +03:00
|
|
|
let dx = center.x() - dims.width * scale / 2.0;
|
|
|
|
let dy = center.y() - dims.height * scale / 2.0;
|
2020-02-07 06:18:45 +03:00
|
|
|
for (color, poly) in other.consume() {
|
2020-01-02 19:24:12 +03:00
|
|
|
self.push(color, poly.scale(scale).translate(dx, dy).rotate(rotate));
|
2019-12-08 23:46:56 +03:00
|
|
|
}
|
|
|
|
}
|
2020-02-07 09:04:16 +03:00
|
|
|
|
|
|
|
pub fn add_translated(&mut self, other: GeomBatch, dx: f64, dy: f64) {
|
|
|
|
for (color, poly) in other.consume() {
|
|
|
|
self.push(color, poly.translate(dx, dy));
|
|
|
|
}
|
|
|
|
}
|
2019-05-17 03:10:22 +03:00
|
|
|
}
|
2019-09-11 19:03:59 +03:00
|
|
|
|
2019-12-09 00:44:43 +03:00
|
|
|
pub enum RewriteColor {
|
2020-01-29 23:16:18 +03:00
|
|
|
NoOp,
|
2019-12-09 00:44:43 +03:00
|
|
|
Change(Color, Color),
|
|
|
|
ChangeAll(Color),
|
|
|
|
}
|
|
|
|
|
2019-09-11 19:03:59 +03:00
|
|
|
// Something that's been sent to the GPU already.
|
|
|
|
pub struct Drawable {
|
|
|
|
vertex_buffer: glium::VertexBuffer<Vertex>,
|
|
|
|
index_buffer: glium::IndexBuffer<u32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub(crate) struct Vertex {
|
|
|
|
position: [f32; 2],
|
2019-11-24 18:21:30 +03:00
|
|
|
// Each type of Color encodes something different here. See the actually_upload method and
|
|
|
|
// fragment_140.glsl.
|
2019-09-11 19:03:59 +03:00
|
|
|
// TODO Make this u8?
|
|
|
|
style: [f32; 4],
|
|
|
|
}
|
|
|
|
|
|
|
|
glium::implement_vertex!(Vertex, position, style);
|
|
|
|
|
|
|
|
// TODO Don't expose this directly
|
|
|
|
pub struct Prerender<'a> {
|
2020-02-07 08:39:23 +03:00
|
|
|
pub(crate) assets: Assets,
|
2019-09-11 19:03:59 +03:00
|
|
|
pub(crate) display: &'a glium::Display,
|
|
|
|
pub(crate) num_uploads: Cell<usize>,
|
|
|
|
// TODO Prerender doesn't know what things are temporary and permanent. Could make the API more
|
|
|
|
// detailed (and use the corresponding persistent glium types).
|
|
|
|
pub(crate) total_bytes_uploaded: Cell<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Prerender<'a> {
|
|
|
|
pub fn upload_borrowed(&self, list: Vec<(Color, &Polygon)>) -> Drawable {
|
|
|
|
self.actually_upload(true, list)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn upload(&self, batch: GeomBatch) -> Drawable {
|
|
|
|
let borrows = batch.list.iter().map(|(c, p)| (*c, p)).collect();
|
|
|
|
self.actually_upload(true, borrows)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_total_bytes_uploaded(&self) -> usize {
|
|
|
|
self.total_bytes_uploaded.get()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn upload_temporary(&self, list: Vec<(Color, &Polygon)>) -> Drawable {
|
|
|
|
self.actually_upload(false, list)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn actually_upload(&self, permanent: bool, list: Vec<(Color, &Polygon)>) -> Drawable {
|
2019-12-22 21:52:41 +03:00
|
|
|
// println!("{:?}", backtrace::Backtrace::new());
|
2019-11-23 01:06:44 +03:00
|
|
|
|
2019-09-11 19:03:59 +03:00
|
|
|
self.num_uploads.set(self.num_uploads.get() + 1);
|
|
|
|
|
|
|
|
let mut vertices: Vec<Vertex> = Vec::new();
|
|
|
|
let mut indices: Vec<u32> = Vec::new();
|
|
|
|
|
|
|
|
for (color, poly) in list {
|
|
|
|
let idx_offset = vertices.len();
|
2019-10-25 00:07:03 +03:00
|
|
|
let (pts, raw_indices, maybe_uv) = poly.raw_for_rendering();
|
|
|
|
for (idx, pt) in pts.iter().enumerate() {
|
2019-11-24 18:21:30 +03:00
|
|
|
// For the three texture cases, pass [U coordinate, V coordinate, texture group ID,
|
|
|
|
// 100 + texture offset ID] as the style. The last field is between 0 an 1 RGBA's
|
|
|
|
// alpha values, so bump by 100 to distinguish from that.
|
2019-09-11 19:03:59 +03:00
|
|
|
let style = match color {
|
|
|
|
Color::RGBA(r, g, b, a) => [r, g, b, a],
|
2019-11-24 18:21:27 +03:00
|
|
|
Color::TileTexture(id, tex_dims) => {
|
2019-09-11 20:46:03 +03:00
|
|
|
// The texture uses SamplerWrapFunction::Repeat, so don't clamp to [0, 1].
|
|
|
|
// Also don't offset based on the polygon's bounds -- even if there are
|
|
|
|
// separate but adjacent polygons, we want seamless tiling.
|
2019-11-24 18:21:27 +03:00
|
|
|
let tx = pt.x() / tex_dims.width;
|
|
|
|
let ty = pt.y() / tex_dims.height;
|
2019-11-24 18:21:30 +03:00
|
|
|
[tx as f32, ty as f32, id.0, 100.0 + id.1]
|
2019-09-11 19:03:59 +03:00
|
|
|
}
|
2019-11-24 18:21:21 +03:00
|
|
|
Color::StretchTexture(id, _, angle) => {
|
2019-10-24 01:46:00 +03:00
|
|
|
// TODO Cache
|
|
|
|
let b = poly.get_bounds();
|
2019-10-24 02:30:23 +03:00
|
|
|
let center = poly.center();
|
|
|
|
let origin_pt = Pt2D::new(pt.x() - center.x(), pt.y() - center.y());
|
2019-10-30 02:36:19 +03:00
|
|
|
let (sin, cos) = angle.invert_y().normalized_radians().sin_cos();
|
2019-10-24 02:30:23 +03:00
|
|
|
let rot_pt = Pt2D::new(
|
|
|
|
center.x() + origin_pt.x() * cos - origin_pt.y() * sin,
|
|
|
|
center.y() + origin_pt.y() * cos + origin_pt.x() * sin,
|
|
|
|
);
|
|
|
|
|
2020-01-14 01:57:46 +03:00
|
|
|
let tx = (rot_pt.x() - b.min_x) / b.width();
|
|
|
|
let ty = (rot_pt.y() - b.min_y) / b.height();
|
2019-11-24 18:21:30 +03:00
|
|
|
[tx as f32, ty as f32, id.0, 100.0 + id.1]
|
2019-10-24 01:46:00 +03:00
|
|
|
}
|
2019-10-25 00:07:03 +03:00
|
|
|
Color::CustomUVTexture(id) => {
|
|
|
|
let (tx, ty) =
|
|
|
|
maybe_uv.expect("CustomUVTexture with polygon lacking UV")[idx];
|
2019-11-24 18:21:30 +03:00
|
|
|
[tx, ty, id.0, 100.0 + id.1]
|
2019-10-25 00:07:03 +03:00
|
|
|
}
|
2019-11-24 18:21:30 +03:00
|
|
|
// Two final special cases
|
2019-10-31 01:01:11 +03:00
|
|
|
Color::HatchingStyle1 => [100.0, 0.0, 0.0, 0.0],
|
|
|
|
Color::HatchingStyle2 => [101.0, 0.0, 0.0, 0.0],
|
2019-09-11 19:03:59 +03:00
|
|
|
};
|
|
|
|
vertices.push(Vertex {
|
|
|
|
position: [pt.x() as f32, pt.y() as f32],
|
|
|
|
style,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for idx in raw_indices {
|
|
|
|
indices.push((idx_offset + *idx) as u32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let vertex_buffer = if permanent {
|
|
|
|
glium::VertexBuffer::immutable(self.display, &vertices).unwrap()
|
|
|
|
} else {
|
|
|
|
glium::VertexBuffer::new(self.display, &vertices).unwrap()
|
|
|
|
};
|
|
|
|
let index_buffer = if permanent {
|
|
|
|
glium::IndexBuffer::immutable(
|
|
|
|
self.display,
|
|
|
|
glium::index::PrimitiveType::TrianglesList,
|
|
|
|
&indices,
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
} else {
|
|
|
|
glium::IndexBuffer::new(
|
|
|
|
self.display,
|
|
|
|
glium::index::PrimitiveType::TrianglesList,
|
|
|
|
&indices,
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
if permanent {
|
|
|
|
self.total_bytes_uploaded.set(
|
|
|
|
self.total_bytes_uploaded.get()
|
|
|
|
+ vertex_buffer.get_size()
|
|
|
|
+ index_buffer.get_size(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Drawable {
|
|
|
|
vertex_buffer,
|
|
|
|
index_buffer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|