diff --git a/ezgui/src/backend_glium.rs b/ezgui/src/backend_glium.rs index fa03d2cd2b..0dcb9ae029 100644 --- a/ezgui/src/backend_glium.rs +++ b/ezgui/src/backend_glium.rs @@ -98,15 +98,11 @@ pub struct GfxCtxInnards<'a> { } impl<'a> GfxCtxInnards<'a> { - pub fn clear(&mut self, color: Color) { - 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!(), - } + pub fn clear(&mut self, c: Color) { + // Without this, SRGB gets enabled and post-processes the color from the fragment + // shader. + self.target + .clear_color_srgb_and_depth((c.r, c.g, c.b, c.a), 1.0); } pub fn redraw(&mut self, obj: &Drawable, uniforms: &Uniforms, prerender: &PrerenderInnards) { @@ -188,21 +184,9 @@ impl PrerenderInnards { let idx_offset = vertices.len(); let (pts, raw_indices) = poly.raw_for_rendering(); for pt in pts { - let style = match color { - FancyColor::Plain(Color::RGBA(r, g, b, a)) => [r, g, b, a], - // Two special cases - FancyColor::Plain(Color::HatchingStyle1) => [100.0, 0.0, 0.0, 0.0], - FancyColor::Plain(Color::HatchingStyle2) => [101.0, 0.0, 0.0, 0.0], - FancyColor::LinearGradient(ref line, ref lg) => { - match FancyColor::interp_lg(line, lg, *pt) { - Color::RGBA(r, g, b, a) => [r, g, b, a], - _ => unreachable!(), - } - } - }; vertices.push(Vertex { position: [pt.x() as f32, pt.y() as f32], - style, + style: color.style(*pt), }); } for idx in raw_indices { diff --git a/ezgui/src/backend_glow.rs b/ezgui/src/backend_glow.rs index d511faa44f..6007e1400b 100644 --- a/ezgui/src/backend_glow.rs +++ b/ezgui/src/backend_glow.rs @@ -1,5 +1,5 @@ use crate::drawing::Uniforms; -use crate::{Canvas, Color, ScreenDims, ScreenRectangle}; +use crate::{Canvas, Color, FancyColor, ScreenDims, ScreenRectangle}; use geom::Polygon; use glow::HasContext; use std::cell::Cell; @@ -96,15 +96,12 @@ pub struct GfxCtxInnards<'a> { impl<'a> GfxCtxInnards<'a> { pub fn clear(&mut self, color: Color) { - match color { - Color::RGBA(r, g, b, a) => unsafe { - self.gl.clear_color(r, g, b, a); - self.gl.clear(glow::COLOR_BUFFER_BIT); + unsafe { + self.gl.clear_color(color.r, color.g, color.b, color.a); + self.gl.clear(glow::COLOR_BUFFER_BIT); - self.gl.clear_depth_f32(1.0); - self.gl.clear(glow::DEPTH_BUFFER_BIT); - }, - _ => unreachable!(), + self.gl.clear_depth_f32(1.0); + self.gl.clear(glow::DEPTH_BUFFER_BIT); } } @@ -197,7 +194,7 @@ pub struct PrerenderInnards { } impl PrerenderInnards { - pub fn actually_upload(&self, permanent: bool, list: Vec<(Color, &Polygon)>) -> Drawable { + pub fn actually_upload(&self, permanent: bool, list: Vec<(FancyColor, &Polygon)>) -> Drawable { let mut vertices: Vec<[f32; 6]> = Vec::new(); let mut indices: Vec = Vec::new(); @@ -205,12 +202,7 @@ impl PrerenderInnards { let idx_offset = vertices.len(); let (pts, raw_indices) = poly.raw_for_rendering(); for pt in pts { - let style = match color { - Color::RGBA(r, g, b, a) => [r, g, b, a], - // Two special cases - Color::HatchingStyle1 => [100.0, 0.0, 0.0, 0.0], - Color::HatchingStyle2 => [101.0, 0.0, 0.0, 0.0], - }; + let style = color.style(*pt); vertices.push([ pt.x() as f32, pt.y() as f32, diff --git a/ezgui/src/backend_wasm.rs b/ezgui/src/backend_wasm.rs index caaeb20d77..625a7904f2 100644 --- a/ezgui/src/backend_wasm.rs +++ b/ezgui/src/backend_wasm.rs @@ -1,5 +1,5 @@ use crate::drawing::Uniforms; -use crate::{Canvas, Color, ScreenDims, ScreenRectangle}; +use crate::{Canvas, Color, FancyColor, ScreenDims, ScreenRectangle}; use geom::Polygon; use glow::HasContext; use std::cell::Cell; @@ -116,15 +116,12 @@ pub struct GfxCtxInnards<'a> { impl<'a> GfxCtxInnards<'a> { pub fn clear(&mut self, color: Color) { - match color { - Color::RGBA(r, g, b, a) => unsafe { - self.gl.clear_color(r, g, b, a); - self.gl.clear(glow::COLOR_BUFFER_BIT); + unsafe { + self.gl.clear_color(color.r, color.g, color.b, color.a); + self.gl.clear(glow::COLOR_BUFFER_BIT); - self.gl.clear_depth_f32(1.0); - self.gl.clear(glow::DEPTH_BUFFER_BIT); - }, - _ => unreachable!(), + self.gl.clear_depth_f32(1.0); + self.gl.clear(glow::DEPTH_BUFFER_BIT); } } @@ -215,7 +212,7 @@ pub struct PrerenderInnards { } impl PrerenderInnards { - pub fn actually_upload(&self, permanent: bool, list: Vec<(Color, &Polygon)>) -> Drawable { + pub fn actually_upload(&self, permanent: bool, list: Vec<(FancyColor, &Polygon)>) -> Drawable { let mut vertices: Vec<[f32; 6]> = Vec::new(); let mut indices: Vec = Vec::new(); @@ -223,12 +220,7 @@ impl PrerenderInnards { let idx_offset = vertices.len(); let (pts, raw_indices) = poly.raw_for_rendering(); for pt in pts { - let style = match color { - Color::RGBA(r, g, b, a) => [r, g, b, a], - // Two special cases - Color::HatchingStyle1 => [100.0, 0.0, 0.0, 0.0], - Color::HatchingStyle2 => [101.0, 0.0, 0.0, 0.0], - }; + let style = color.style(*pt); vertices.push([ pt.x() as f32, pt.y() as f32, diff --git a/ezgui/src/color.rs b/ezgui/src/color.rs index 69c08ef46c..cd0ae5a0f3 100644 --- a/ezgui/src/color.rs +++ b/ezgui/src/color.rs @@ -3,30 +3,29 @@ use serde_derive::{Deserialize, Serialize}; use std::fmt; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum Color { - RGBA(f32, f32, f32, f32), - // TODO Figure out how to pack more data into this. - HatchingStyle1, - HatchingStyle2, +pub struct Color { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Color::RGBA(r, g, b, a) => write!(f, "Color(r={}, g={}, b={}, a={})", r, g, b, a), - Color::HatchingStyle1 => write!(f, "Color::HatchingStyle1"), - Color::HatchingStyle2 => write!(f, "Color::HatchingStyle2"), - } + write!( + f, + "Color(r={}, g={}, b={}, a={})", + self.r, self.g, self.b, self.a + ) } } -// TODO Not sure if this is hacky or not. Maybe Color should be specialized to RGBA, and these are -// other cases... +// TODO Maybe needs a better name #[derive(Clone, PartialEq)] pub enum FancyColor { - Plain(Color), - // The line, then stops (percent along, color) - LinearGradient(Line, Vec<(f64, Color)>), + RGBA(Color), + Hatching, + LinearGradient(LinearGradient), } impl Color { @@ -52,38 +51,38 @@ impl Color { } pub const fn rgb_f(r: f32, g: f32, b: f32) -> Color { - Color::RGBA(r, g, b, 1.0) + Color { r, g, b, a: 1.0 } } pub fn rgba(r: usize, g: usize, b: usize, a: f32) -> Color { - Color::RGBA( - (r as f32) / 255.0, - (g as f32) / 255.0, - (b as f32) / 255.0, + Color { + r: (r as f32) / 255.0, + g: (g as f32) / 255.0, + b: (b as f32) / 255.0, a, - ) + } } pub const fn rgba_f(r: f32, g: f32, b: f32, a: f32) -> Color { - Color::RGBA(r, g, b, a) + Color { r, g, b, a } } pub const fn grey(f: f32) -> Color { - Color::RGBA(f, f, f, 1.0) + Color::rgb_f(f, f, f) } pub fn alpha(&self, a: f32) -> Color { - match self { - Color::RGBA(r, g, b, _) => Color::RGBA(*r, *g, *b, a), - _ => unreachable!(), - } + let mut c = self.clone(); + c.a = a; + c } pub fn fade(&self, factor: f32) -> Color { - match self { - Color::RGBA(r, g, b, a) => Color::RGBA(*r / factor, *g / factor, *b / factor, *a), - _ => unreachable!(), - } + let mut c = self.clone(); + c.r /= factor; + c.g /= factor; + c.b /= factor; + c } pub fn hex(raw: &str) -> Color { @@ -95,32 +94,34 @@ impl Color { } pub fn to_hex(&self) -> String { - match self { - Color::RGBA(r, g, b, _) => format!( - "#{:02X}{:02X}{:02X}", - (r * 255.0) as usize, - (g * 255.0) as usize, - (b * 255.0) as usize - ), - _ => unreachable!(), - } + format!( + "#{:02X}{:02X}{:02X}", + (self.r * 255.0) as usize, + (self.g * 255.0) as usize, + (self.b * 255.0) as usize + ) } fn lerp(self, other: Color, pct: f32) -> Color { - match (self, other) { - (Color::RGBA(r1, g1, b1, a1), Color::RGBA(r2, g2, b2, a2)) => Color::RGBA( - lerp(pct, (r1, r2)), - lerp(pct, (g1, g2)), - lerp(pct, (b1, b2)), - lerp(pct, (a1, a2)), - ), - _ => unreachable!(), - } + Color::rgba_f( + lerp(pct, (self.r, other.r)), + lerp(pct, (self.g, other.g)), + lerp(pct, (self.b, other.b)), + lerp(pct, (self.a, other.a)), + ) } } -impl FancyColor { - pub(crate) fn linear_gradient(lg: &usvg::LinearGradient) -> FancyColor { +// https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient is the best reference I've +// found, even though it's technically for CSS, not SVG +#[derive(Clone, PartialEq)] +pub struct LinearGradient { + line: Line, + stops: Vec<(f64, Color)>, +} + +impl LinearGradient { + pub(crate) fn new(lg: &usvg::LinearGradient) -> FancyColor { let line = Line::new(Pt2D::new(lg.x1, lg.y1), Pt2D::new(lg.x2, lg.y2)); let mut stops = Vec::new(); for stop in &lg.stops { @@ -132,23 +133,22 @@ impl FancyColor { ); stops.push((stop.offset.value(), color)); } - FancyColor::LinearGradient(line, stops) + FancyColor::LinearGradient(LinearGradient { line, stops }) } - pub(crate) fn interp_lg(line: &Line, stops: &Vec<(f64, Color)>, corner: Pt2D) -> Color { - // https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient is the best reference - // I've found, even though it's technically for CSS, not SVG - let pct = line - .percent_along_of_point(line.project_pt(corner)) + fn interp(&self, pt: Pt2D) -> Color { + let pct = self + .line + .percent_along_of_point(self.line.project_pt(pt)) .unwrap(); - if pct < stops[0].0 { - return stops[0].1; + if pct < self.stops[0].0 { + return self.stops[0].1; } - if pct > stops.last().unwrap().0 { - return stops.last().unwrap().1; + if pct > self.stops.last().unwrap().0 { + return self.stops.last().unwrap().1; } // In between two - for ((pct1, c1), (pct2, c2)) in stops.iter().zip(stops.iter().skip(1)) { + for ((pct1, c1), (pct2, c2)) in self.stops.iter().zip(self.stops.iter().skip(1)) { if pct >= *pct1 && pct <= *pct2 { return c1.lerp(*c2, to_pct(pct, (*pct1, *pct2)) as f32); } @@ -167,3 +167,16 @@ fn to_pct(value: f64, (low, high): (f64, f64)) -> f64 { fn lerp(pct: f32, (x1, x2): (f32, f32)) -> f32 { x1 + pct * (x2 - x1) } + +impl FancyColor { + pub(crate) fn style(&self, pt: Pt2D) -> [f32; 4] { + match self { + FancyColor::RGBA(c) => [c.r, c.g, c.b, c.a], + FancyColor::Hatching => [100.0, 0.0, 0.0, 0.0], + FancyColor::LinearGradient(ref lg) => { + let c = lg.interp(pt); + [c.r, c.g, c.b, c.a] + } + } + } +} diff --git a/ezgui/src/drawing.rs b/ezgui/src/drawing.rs index aa4893e3ee..d6e3dc140c 100644 --- a/ezgui/src/drawing.rs +++ b/ezgui/src/drawing.rs @@ -138,7 +138,7 @@ impl<'a> GfxCtx<'a> { pub fn draw_polygon(&mut self, color: Color, poly: &Polygon) { let obj = self .prerender - .upload_temporary(vec![(FancyColor::Plain(color), poly)]); + .upload_temporary(vec![(FancyColor::RGBA(color), poly)]); self.redraw(&obj); } @@ -146,7 +146,7 @@ impl<'a> GfxCtx<'a> { let obj = self.prerender.upload_temporary( polygons .iter() - .map(|p| (FancyColor::Plain(color), p)) + .map(|p| (FancyColor::RGBA(color), p)) .collect(), ); self.redraw(&obj); diff --git a/ezgui/src/geom.rs b/ezgui/src/geom.rs index d242f4070f..d36ea92481 100644 --- a/ezgui/src/geom.rs +++ b/ezgui/src/geom.rs @@ -23,7 +23,7 @@ impl GeomBatch { GeomBatch { list: list .into_iter() - .map(|(c, p)| (FancyColor::Plain(c), p)) + .map(|(c, p)| (FancyColor::RGBA(c), p)) .collect(), dims_text: false, } @@ -31,7 +31,7 @@ impl GeomBatch { /// Adds a single colored polygon. pub fn push(&mut self, color: Color, p: Polygon) { - self.list.push((FancyColor::Plain(color), p)); + self.list.push((FancyColor::RGBA(color), p)); } pub fn fancy_push(&mut self, color: FancyColor, p: Polygon) { self.list.push((color, p)); @@ -40,7 +40,7 @@ impl GeomBatch { /// Applies one color to many polygons. pub fn extend(&mut self, color: Color, polys: Vec) { for p in polys { - self.list.push((FancyColor::Plain(color), p)); + self.list.push((FancyColor::RGBA(color), p)); } } @@ -112,7 +112,7 @@ impl GeomBatch { /// Transforms all colors in a batch. pub fn rewrite_color(&mut self, transformation: RewriteColor) { for (fancy, _) in self.list.iter_mut() { - if let FancyColor::Plain(ref mut c) = fancy { + if let FancyColor::RGBA(ref mut c) = fancy { match transformation { RewriteColor::NoOp => {} RewriteColor::Change(from, to) => { diff --git a/ezgui/src/lib.rs b/ezgui/src/lib.rs index d9e81ee840..f69a2f72d4 100644 --- a/ezgui/src/lib.rs +++ b/ezgui/src/lib.rs @@ -40,7 +40,7 @@ mod widgets; pub use crate::backend::Drawable; pub use crate::canvas::{Canvas, HorizontalAlignment, VerticalAlignment}; -pub use crate::color::{Color, FancyColor}; +pub use crate::color::{Color, FancyColor, LinearGradient}; pub use crate::drawing::{GfxCtx, Prerender}; pub use crate::event::{hotkey, hotkeys, lctrl, Event, Key, MultiKey}; pub use crate::event_ctx::EventCtx; diff --git a/ezgui/src/shaders/fragment_140.glsl b/ezgui/src/shaders/fragment_140.glsl index d588d49e7c..d60dbeb7d9 100644 --- a/ezgui/src/shaders/fragment_140.glsl +++ b/ezgui/src/shaders/fragment_140.glsl @@ -11,19 +11,6 @@ out vec4 f_color; void main() { // See actually_upload in drawing.rs to understand the different things encoded. if (pass_style[0] == 100.0) { - // The hatching should be done in map-space, so panning/zooming doesn't move the stripes. - // This is screen_to_map, also accounting for the y-inversion done by the vertex shader. - float map_x = (gl_FragCoord.x + transform[0]) / transform[2]; - float map_y = (window[1] - gl_FragCoord.y + transform[1]) / transform[2]; - if (mod(map_x + map_y, 2.0) <= 0.1) { - f_color = vec4(0.0, 1.0, 1.0, 1.0); - } else if (mod(map_x - map_y, 2.0) <= 0.1) { - f_color = vec4(0.0, 1.0, 1.0, 1.0); - } else { - // Let the polygon with its original colors show instead. - discard; - } - } else if (pass_style[0] == 101.0) { float map_x = (gl_FragCoord.x + transform[0]) / transform[2]; float map_y = (window[1] - gl_FragCoord.y + transform[1]) / transform[2]; if (mod(map_x + map_y, 2.0) <= 0.5) { diff --git a/ezgui/src/shaders/fragment_300.glsl b/ezgui/src/shaders/fragment_300.glsl index 58a6423092..98574f5800 100644 --- a/ezgui/src/shaders/fragment_300.glsl +++ b/ezgui/src/shaders/fragment_300.glsl @@ -14,19 +14,6 @@ out vec4 f_color; void main() { // See actually_upload in drawing.rs to understand the different things encoded. if (pass_style[0] == 100.0) { - // The hatching should be done in map-space, so panning/zooming doesn't move the stripes. - // This is screen_to_map, also accounting for the y-inversion done by the vertex shader. - float map_x = (gl_FragCoord.x + transform[0]) / transform[2]; - float map_y = (window[1] - gl_FragCoord.y + transform[1]) / transform[2]; - if (mod(map_x + map_y, 2.0) <= 0.1) { - f_color = vec4(0.0, 1.0, 1.0, 1.0); - } else if (mod(map_x - map_y, 2.0) <= 0.1) { - f_color = vec4(0.0, 1.0, 1.0, 1.0); - } else { - // Let the polygon with its original colors show instead. - discard; - } - } else if (pass_style[0] == 101.0) { float map_x = (gl_FragCoord.x + transform[0]) / transform[2]; float map_y = (window[1] - gl_FragCoord.y + transform[1]) / transform[2]; if (mod(map_x + map_y, 2.0) <= 0.5) { diff --git a/ezgui/src/svg.rs b/ezgui/src/svg.rs index e110be5d82..008d76fe46 100644 --- a/ezgui/src/svg.rs +++ b/ezgui/src/svg.rs @@ -1,4 +1,4 @@ -use crate::{Color, FancyColor, GeomBatch, Prerender}; +use crate::{Color, FancyColor, GeomBatch, LinearGradient, Prerender}; use abstutil::VecMap; use geom::{Bounds, Polygon, Pt2D}; use lyon::math::Point; @@ -232,14 +232,14 @@ fn convert_stroke( fn convert_color(paint: &usvg::Paint, opacity: f64, tree: &usvg::Tree) -> FancyColor { match paint { - usvg::Paint::Color(c) => FancyColor::Plain(Color::rgba( + usvg::Paint::Color(c) => FancyColor::RGBA(Color::rgba( c.red as usize, c.green as usize, c.blue as usize, opacity as f32, )), usvg::Paint::Link(name) => match *tree.defs_by_id(name).unwrap().borrow() { - usvg::NodeKind::LinearGradient(ref lg) => FancyColor::linear_gradient(lg), + usvg::NodeKind::LinearGradient(ref lg) => LinearGradient::new(lg), _ => panic!("Unsupported color style {}", name), }, } diff --git a/game/src/helpers.rs b/game/src/helpers.rs index 512b03deae..a1a22c2484 100644 --- a/game/src/helpers.rs +++ b/game/src/helpers.rs @@ -88,15 +88,7 @@ impl ColorScheme { if false { let mut copy = OverrideColorScheme(BTreeMap::new()); for (name, c) in &map { - if let Color::RGBA(r, g, b, a) = *c { - let hex = format!( - "#{:02X}{:02X}{:02X}", - (r * 255.0) as usize, - (g * 255.0) as usize, - (b * 255.0) as usize - ); - copy.0.insert(name.clone(), (hex, a)); - } + copy.0.insert(name.clone(), (c.to_hex(), c.a)); } abstutil::write_json("../data/system/override_colors.json".to_string(), ©); } diff --git a/game/src/render/intersection.rs b/game/src/render/intersection.rs index db8e3bf509..70e909c3bc 100644 --- a/game/src/render/intersection.rs +++ b/game/src/render/intersection.rs @@ -4,7 +4,7 @@ use crate::render::{ draw_signal_phase, DrawOptions, Renderable, CROSSWALK_LINE_THICKNESS, OUTLINE_THICKNESS, }; use abstutil::Timer; -use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text}; +use ezgui::{Color, Drawable, FancyColor, GeomBatch, GfxCtx, Line, Prerender, Text}; use geom::{Angle, Distance, Line, PolyLine, Polygon, Pt2D, Time, EPSILON_DIST}; use map_model::raw::DrivingSide; use map_model::{ @@ -72,7 +72,7 @@ impl DrawIntersection { } } IntersectionType::Construction => { - default_geom.push(cs.get("construction hatching"), i.polygon.clone()); + default_geom.fancy_push(FancyColor::Hatching, i.polygon.clone()); } IntersectionType::TrafficSignal => {} } diff --git a/game/src/render/lane.rs b/game/src/render/lane.rs index 25bc48d976..8e37bd4269 100644 --- a/game/src/render/lane.rs +++ b/game/src/render/lane.rs @@ -2,7 +2,7 @@ use crate::app::App; use crate::helpers::{ColorScheme, ID}; use crate::render::{dashed_lines, DrawOptions, Renderable, OUTLINE_THICKNESS}; use abstutil::Timer; -use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Prerender}; +use ezgui::{Color, Drawable, FancyColor, GeomBatch, GfxCtx, Prerender}; use geom::{Angle, Distance, Line, PolyLine, Polygon, Pt2D}; use map_model::{Lane, LaneID, LaneType, Map, Road, TurnType, PARKING_SPOT_LENGTH}; @@ -147,10 +147,8 @@ impl DrawLane { ); } LaneType::Construction => { - draw.push( - cs.get_def("construction hatching", Color::HatchingStyle2), - polygon.clone(), - ); + // TODO Can't put this in ColorScheme without switching to FancyColor + draw.fancy_push(FancyColor::Hatching, polygon.clone()); } }; } diff --git a/map_editor/src/model.rs b/map_editor/src/model.rs index e8623bcffe..e165b3bd8b 100644 --- a/map_editor/src/model.rs +++ b/map_editor/src/model.rs @@ -671,15 +671,9 @@ impl Model { LaneType::Construction => Color::rgb(255, 109, 0), }; if unset { - match color { - Color::RGBA(_, g, b, _) => Color::rgba_f(0.9, g, b, 0.5), - _ => unreachable!(), - } + Color::rgba_f(0.9, color.g, color.b, 0.5) } else if lanes_unknown { - match color { - Color::RGBA(r, g, _, _) => Color::rgba_f(r, g, 0.9, 0.5), - _ => unreachable!(), - } + Color::rgba_f(color.r, color.g, 0.9, 0.5) } else { color }